From 86d907f14708056cff0cb389e6f94586f226f17a Mon Sep 17 00:00:00 2001 From: furudbat Date: Fri, 5 Dec 2025 12:33:32 +0100 Subject: [PATCH 01/18] fix: find-devices crash (#54) --- scripts/find_input_devices.sh | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/scripts/find_input_devices.sh b/scripts/find_input_devices.sh index b8c55827..019c6ed0 100755 --- a/scripts/find_input_devices.sh +++ b/scripts/find_input_devices.sh @@ -197,12 +197,15 @@ check_input_group() { get_device_status() { local device="$1" + # Check existence + if [[ ! -e "$device" ]]; then + return 2 # missing + fi + if [[ -r "$device" ]]; then - echo -e "${GREEN}${CHECK} Accessible${NC}" - return 0 + return 0 # accessible else - echo -e "${RED}${CROSS} Permission Denied${NC}" - return 1 + return 1 # permission denied fi } @@ -351,8 +354,19 @@ display_devices() { echo -e "${BLUE}│${NC} ${WHITE}Type:${NC} $(printf "%-50s" "$type") ${BLUE} │${NC}" local status - status=$(get_device_status "$path") - echo -e "${BLUE}│${NC} ${WHITE}Status:${NC} $status $(printf "%*s" $((50 - ${#status} + 10)) "") ${BLUE} │${NC}" + local status_code + set +e + get_device_status "$path" + status_code=$? + set -e + + case $status_code in + 0) status="${GREEN}${CHECK} Accessible${NC}" ;; + 1) status="${RED}[ERROR] Permission Denied${NC}" ;; + 2) status="${YELLOW}[MISSING] Device not found${NC}" ;; + esac + + echo -e "${BLUE}│${NC} ${WHITE}Status:${NC} $status $(printf "%*s" $((56 - ${#status} + 10)) "") ${BLUE} │${NC}" echo -e "${BLUE}└─────────────────────────────────────────────────────────────────┘${NC}" echo fi From f4984d01599c4c680f716ec68feb4dce6ff7a7e3 Mon Sep 17 00:00:00 2001 From: furudbat Date: Sat, 6 Dec 2025 00:26:31 +0100 Subject: [PATCH 02/18] fix: enable pmd in pkmn --- src/graphics/bar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/bar.cpp b/src/graphics/bar.cpp index b92d340b..7e5f55a9 100644 --- a/src/graphics/bar.cpp +++ b/src/graphics/bar.cpp @@ -670,7 +670,7 @@ namespace bongocat::animation { } break; case config::config_animation_custom_set_t::pmd: - if (features::EnableMiscEmbeddedAssets) { + if (features::EnablePmdEmbeddedAssets) { if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pmd_anims.count); } From 8068033842f54c4837a4e999345550f342d229ba Mon Sep 17 00:00:00 2001 From: Saatvik Sharma Date: Sat, 6 Dec 2025 16:13:31 +0530 Subject: [PATCH 03/18] fix: critical bug fixes for memory safety and config hot-reload --- include/platform/input.h | 6 +- src/config/config.c | 792 ++++++++++++----------- src/core/main.c | 826 +++++++++++------------ src/graphics/animation.c | 605 +++++++++-------- src/platform/input.c | 729 +++++++++++---------- src/platform/wayland.c | 1329 +++++++++++++++++++++----------------- src/utils/error.c | 130 ++-- src/utils/memory.c | 367 ++++++----- 8 files changed, 2537 insertions(+), 2247 deletions(-) diff --git a/include/platform/input.h b/include/platform/input.h index 59f21943..e23e85bd 100644 --- a/include/platform/input.h +++ b/include/platform/input.h @@ -6,8 +6,10 @@ extern int *any_key_pressed; -bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, int enable_debug); -bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, int enable_debug); +bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, + int enable_debug); +bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, + int enable_debug); void input_cleanup(void); #endif // INPUT_H \ No newline at end of file diff --git a/src/config/config.c b/src/config/config.c index caab0742..3d6b5164 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -30,368 +30,398 @@ static int config_num_devices = 0; // ============================================================================= static void config_clamp_int(int *value, int min, int max, const char *name) { - if (*value < min || *value > max) { - bongocat_log_warning("%s %d out of range [%d-%d], clamping", name, *value, min, max); - *value = (*value < min) ? min : max; - } + if (*value < min || *value > max) { + bongocat_log_warning("%s %d out of range [%d-%d], clamping", name, *value, + min, max); + *value = (*value < min) ? min : max; + } } static void config_validate_dimensions(config_t *config) { - config_clamp_int(&config->cat_height, MIN_CAT_HEIGHT, MAX_CAT_HEIGHT, "cat_height"); - config_clamp_int(&config->overlay_height, MIN_OVERLAY_HEIGHT, MAX_OVERLAY_HEIGHT, "overlay_height"); + config_clamp_int(&config->cat_height, MIN_CAT_HEIGHT, MAX_CAT_HEIGHT, + "cat_height"); + config_clamp_int(&config->overlay_height, MIN_OVERLAY_HEIGHT, + MAX_OVERLAY_HEIGHT, "overlay_height"); } static void config_validate_timing(config_t *config) { - config_clamp_int(&config->fps, MIN_FPS, MAX_FPS, "fps"); - config_clamp_int(&config->keypress_duration, MIN_DURATION, MAX_DURATION, "keypress_duration"); - config_clamp_int(&config->test_animation_duration, MIN_DURATION, MAX_DURATION, "test_animation_duration"); - - // Validate interval (0 is allowed to disable) - if (config->test_animation_interval < 0 || config->test_animation_interval > MAX_INTERVAL) { - bongocat_log_warning("test_animation_interval %d out of range [0-%d], clamping", - config->test_animation_interval, MAX_INTERVAL); - config->test_animation_interval = (config->test_animation_interval < 0) ? 0 : MAX_INTERVAL; - } + config_clamp_int(&config->fps, MIN_FPS, MAX_FPS, "fps"); + config_clamp_int(&config->keypress_duration, MIN_DURATION, MAX_DURATION, + "keypress_duration"); + config_clamp_int(&config->test_animation_duration, MIN_DURATION, MAX_DURATION, + "test_animation_duration"); + + // Validate interval (0 is allowed to disable) + if (config->test_animation_interval < 0 || + config->test_animation_interval > MAX_INTERVAL) { + bongocat_log_warning( + "test_animation_interval %d out of range [0-%d], clamping", + config->test_animation_interval, MAX_INTERVAL); + config->test_animation_interval = + (config->test_animation_interval < 0) ? 0 : MAX_INTERVAL; + } } static void config_validate_appearance(config_t *config) { - // Validate opacity - config_clamp_int(&config->overlay_opacity, 0, 255, "overlay_opacity"); - - // Validate idle frame - if (config->idle_frame < 0 || config->idle_frame >= NUM_FRAMES) { - bongocat_log_warning("idle_frame %d out of range [0-%d], resetting to 0", - config->idle_frame, NUM_FRAMES - 1); - config->idle_frame = 0; - } + // Validate opacity + config_clamp_int(&config->overlay_opacity, 0, 255, "overlay_opacity"); + + // Validate idle frame + if (config->idle_frame < 0 || config->idle_frame >= NUM_FRAMES) { + bongocat_log_warning("idle_frame %d out of range [0-%d], resetting to 0", + config->idle_frame, NUM_FRAMES - 1); + config->idle_frame = 0; + } } static void config_validate_enums(config_t *config) { - // Validate layer - if (config->layer != LAYER_TOP && config->layer != LAYER_OVERLAY) { - bongocat_log_warning("Invalid layer %d, resetting to top", config->layer); - config->layer = LAYER_TOP; - } - - // Validate overlay_position - if (config->overlay_position != POSITION_TOP && config->overlay_position != POSITION_BOTTOM) { - bongocat_log_warning("Invalid overlay_position %d, resetting to top", config->overlay_position); - config->overlay_position = POSITION_TOP; - } + // Validate layer + if (config->layer != LAYER_TOP && config->layer != LAYER_OVERLAY) { + bongocat_log_warning("Invalid layer %d, resetting to top", config->layer); + config->layer = LAYER_TOP; + } + + // Validate overlay_position + if (config->overlay_position != POSITION_TOP && + config->overlay_position != POSITION_BOTTOM) { + bongocat_log_warning("Invalid overlay_position %d, resetting to top", + config->overlay_position); + config->overlay_position = POSITION_TOP; + } } static void config_validate_positioning(config_t *config) { - // Validate cat positioning doesn't go off-screen - if (abs(config->cat_x_offset) > config->screen_width) { - bongocat_log_warning("cat_x_offset %d may position cat off-screen (screen width: %d)", - config->cat_x_offset, config->screen_width); - } + // Validate cat positioning doesn't go off-screen + if (abs(config->cat_x_offset) > config->screen_width) { + bongocat_log_warning( + "cat_x_offset %d may position cat off-screen (screen width: %d)", + config->cat_x_offset, config->screen_width); + } } static void config_validate_time(config_t *config) { - if (config->enable_scheduled_sleep) { - const int begin_minutes = config->sleep_begin.hour * 60 + config->sleep_begin.min; - const int end_minutes = config->sleep_end.hour * 60 + config->sleep_end.min; + if (config->enable_scheduled_sleep) { + const int begin_minutes = + config->sleep_begin.hour * 60 + config->sleep_begin.min; + const int end_minutes = config->sleep_end.hour * 60 + config->sleep_end.min; - if (begin_minutes == end_minutes) { - bongocat_log_warning("Sleep mode is enabled, but time is equal: %02d:%02d, disable sleep mode", config->sleep_begin.hour, config->sleep_begin.min); + if (begin_minutes == end_minutes) { + bongocat_log_warning("Sleep mode is enabled, but time is equal: " + "%02d:%02d, disable sleep mode", + config->sleep_begin.hour, config->sleep_begin.min); - config->enable_scheduled_sleep = 0; - } + config->enable_scheduled_sleep = 0; } + } } static bongocat_error_t config_validate(config_t *config) { - BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); - - // Normalize boolean values - config->enable_debug = config->enable_debug ? 1 : 0; - config->enable_scheduled_sleep = config->enable_scheduled_sleep ? 1 : 0; - - config_validate_dimensions(config); - config_validate_timing(config); - config_validate_appearance(config); - config_validate_enums(config); - config_validate_positioning(config); - - // Normalize boolean values - config->enable_debug = config->enable_debug ? 1 : 0; - config->mirror_x = config->mirror_x ? 1 : 0; - config->mirror_y = config->mirror_y ? 1 : 0; - config->enable_antialiasing = config->enable_antialiasing ? 1 : 0; - config_validate_time(config); - return BONGOCAT_SUCCESS; + BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); + + // Normalize boolean values + config->enable_debug = config->enable_debug ? 1 : 0; + config->enable_scheduled_sleep = config->enable_scheduled_sleep ? 1 : 0; + + config_validate_dimensions(config); + config_validate_timing(config); + config_validate_appearance(config); + config_validate_enums(config); + config_validate_positioning(config); + + // Normalize boolean values + config->mirror_x = config->mirror_x ? 1 : 0; + config->mirror_y = config->mirror_y ? 1 : 0; + config->enable_antialiasing = config->enable_antialiasing ? 1 : 0; + config_validate_time(config); + return BONGOCAT_SUCCESS; } // ============================================================================= // DEVICE MANAGEMENT MODULE // ============================================================================= -static bongocat_error_t config_add_keyboard_device(config_t *config, const char *device_path) { - // Reallocate device array - config_keyboard_devices = realloc(config_keyboard_devices, - (config_num_devices + 1) * sizeof(char*)); - if (!config_keyboard_devices) { - bongocat_log_error("Failed to allocate memory for keyboard_devices"); - return BONGOCAT_ERROR_MEMORY; - } - - size_t path_len = strlen(device_path); - config_keyboard_devices[config_num_devices] = BONGOCAT_MALLOC(path_len + 1); - if (!config_keyboard_devices[config_num_devices]) { - bongocat_log_error("Failed to allocate memory for keyboard_device entry"); - return BONGOCAT_ERROR_MEMORY; - } - - strncpy(config_keyboard_devices[config_num_devices], device_path, path_len); - config_keyboard_devices[config_num_devices][path_len] = '\0'; - config_num_devices++; - - config->keyboard_devices = config_keyboard_devices; - config->num_keyboard_devices = config_num_devices; - - return BONGOCAT_SUCCESS; +static bongocat_error_t config_add_keyboard_device(config_t *config, + const char *device_path) { + // Reallocate device array using temp variable to prevent memory leak on + // failure + char **new_devices = realloc(config_keyboard_devices, + (config_num_devices + 1) * sizeof(char *)); + if (!new_devices) { + bongocat_log_error("Failed to allocate memory for keyboard_devices"); + return BONGOCAT_ERROR_MEMORY; + } + config_keyboard_devices = new_devices; + + size_t path_len = strlen(device_path); + config_keyboard_devices[config_num_devices] = BONGOCAT_MALLOC(path_len + 1); + if (!config_keyboard_devices[config_num_devices]) { + bongocat_log_error("Failed to allocate memory for keyboard_device entry"); + return BONGOCAT_ERROR_MEMORY; + } + + strncpy(config_keyboard_devices[config_num_devices], device_path, path_len); + config_keyboard_devices[config_num_devices][path_len] = '\0'; + config_num_devices++; + + config->keyboard_devices = config_keyboard_devices; + config->num_keyboard_devices = config_num_devices; + + return BONGOCAT_SUCCESS; } static void config_cleanup_devices(void) { - if (config_keyboard_devices) { - for (int i = 0; i < config_num_devices; i++) { - BONGOCAT_SAFE_FREE(config_keyboard_devices[i]); - } - BONGOCAT_SAFE_FREE(config_keyboard_devices); - config_keyboard_devices = NULL; - config_num_devices = 0; + if (config_keyboard_devices) { + for (int i = 0; i < config_num_devices; i++) { + BONGOCAT_SAFE_FREE(config_keyboard_devices[i]); } + BONGOCAT_SAFE_FREE(config_keyboard_devices); + config_keyboard_devices = NULL; + config_num_devices = 0; + } } // ============================================================================= // CONFIGURATION PARSING MODULE // ============================================================================= -static char* config_trim_key(char *key) { - char *key_start = key; - while (*key_start == ' ' || *key_start == '\t') key_start++; +static char *config_trim_key(char *key) { + char *key_start = key; + while (*key_start == ' ' || *key_start == '\t') + key_start++; - char *key_end = key_start + strlen(key_start) - 1; - while (key_end > key_start && (*key_end == ' ' || *key_end == '\t')) { - *key_end = '\0'; - key_end--; - } + char *key_end = key_start + strlen(key_start) - 1; + while (key_end > key_start && (*key_end == ' ' || *key_end == '\t')) { + *key_end = '\0'; + key_end--; + } - return key_start; + return key_start; } -static bongocat_error_t config_parse_integer_key(config_t *config, const char *key, const char *value) { - int int_value = (int)strtol(value, NULL, 10); - - if (strcmp(key, "cat_x_offset") == 0) { - config->cat_x_offset = int_value; - } else if (strcmp(key, "cat_y_offset") == 0) { - config->cat_y_offset = int_value; - } else if (strcmp(key, "cat_height") == 0) { - config->cat_height = int_value; - } else if (strcmp(key, "overlay_height") == 0) { - config->overlay_height = int_value; - } else if (strcmp(key, "idle_frame") == 0) { - config->idle_frame = int_value; - } else if (strcmp(key, "keypress_duration") == 0) { - config->keypress_duration = int_value; - } else if (strcmp(key, "test_animation_duration") == 0) { - config->test_animation_duration = int_value; - } else if (strcmp(key, "test_animation_interval") == 0) { - config->test_animation_interval = int_value; - } else if (strcmp(key, "fps") == 0) { - config->fps = int_value; - } else if (strcmp(key, "overlay_opacity") == 0) { - config->overlay_opacity = int_value; - } else if (strcmp(key, "mirror_x") == 0) { - config->mirror_x = int_value; - } else if (strcmp(key, "mirror_y") == 0) { - config->mirror_y = int_value; - } else if (strcmp(key, "enable_antialiasing") == 0) { - config->enable_antialiasing = int_value; - } else if (strcmp(key, "enable_debug") == 0) { - config->enable_debug = int_value; - } else if (strcmp(key, "enable_scheduled_sleep") == 0) { - config->enable_scheduled_sleep = int_value; - } else if (strcmp(key, "idle_sleep_timeout") == 0) { - config->idle_sleep_timeout_sec = int_value; - } else { - return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key - } - - return BONGOCAT_SUCCESS; +static bongocat_error_t +config_parse_integer_key(config_t *config, const char *key, const char *value) { + int int_value = (int)strtol(value, NULL, 10); + + if (strcmp(key, "cat_x_offset") == 0) { + config->cat_x_offset = int_value; + } else if (strcmp(key, "cat_y_offset") == 0) { + config->cat_y_offset = int_value; + } else if (strcmp(key, "cat_height") == 0) { + config->cat_height = int_value; + } else if (strcmp(key, "overlay_height") == 0) { + config->overlay_height = int_value; + } else if (strcmp(key, "idle_frame") == 0) { + config->idle_frame = int_value; + } else if (strcmp(key, "keypress_duration") == 0) { + config->keypress_duration = int_value; + } else if (strcmp(key, "test_animation_duration") == 0) { + config->test_animation_duration = int_value; + } else if (strcmp(key, "test_animation_interval") == 0) { + config->test_animation_interval = int_value; + } else if (strcmp(key, "fps") == 0) { + config->fps = int_value; + } else if (strcmp(key, "overlay_opacity") == 0) { + config->overlay_opacity = int_value; + } else if (strcmp(key, "mirror_x") == 0) { + config->mirror_x = int_value; + } else if (strcmp(key, "mirror_y") == 0) { + config->mirror_y = int_value; + } else if (strcmp(key, "enable_antialiasing") == 0) { + config->enable_antialiasing = int_value; + } else if (strcmp(key, "enable_debug") == 0) { + config->enable_debug = int_value; + } else if (strcmp(key, "enable_scheduled_sleep") == 0) { + config->enable_scheduled_sleep = int_value; + } else if (strcmp(key, "idle_sleep_timeout") == 0) { + config->idle_sleep_timeout_sec = int_value; + } else { + return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } + + return BONGOCAT_SUCCESS; } -static bongocat_error_t config_parse_enum_key(config_t *config, const char *key, const char *value) { - if (strcmp(key, "layer") == 0) { - if (strcmp(value, "top") == 0) { - config->layer = LAYER_TOP; - } else if (strcmp(value, "overlay") == 0) { - config->layer = LAYER_OVERLAY; - } else { - bongocat_log_warning("Invalid layer '%s', using 'top'", value); - config->layer = LAYER_TOP; - } - } else if (strcmp(key, "overlay_position") == 0) { - if (strcmp(value, "top") == 0) { - config->overlay_position = POSITION_TOP; - } else if (strcmp(value, "bottom") == 0) { - config->overlay_position = POSITION_BOTTOM; - } else { - bongocat_log_warning("Invalid overlay_position '%s', using 'top'", value); - config->overlay_position = POSITION_TOP; - } - } else if (strcmp(key, "cat_align") == 0) { - if (strcmp(value, "left") == 0) { - config->cat_align = ALIGN_LEFT; - } else if (strcmp(value, "center") == 0) { - config->cat_align = ALIGN_CENTER; - } else if (strcmp(value, "right") == 0) { - config->cat_align = ALIGN_RIGHT; - } else { - bongocat_log_warning("Invalid cat_align '%s', using 'center'", value); - config->cat_align = ALIGN_CENTER; - } +static bongocat_error_t config_parse_enum_key(config_t *config, const char *key, + const char *value) { + if (strcmp(key, "layer") == 0) { + if (strcmp(value, "top") == 0) { + config->layer = LAYER_TOP; + } else if (strcmp(value, "overlay") == 0) { + config->layer = LAYER_OVERLAY; + } else { + bongocat_log_warning("Invalid layer '%s', using 'top'", value); + config->layer = LAYER_TOP; + } + } else if (strcmp(key, "overlay_position") == 0) { + if (strcmp(value, "top") == 0) { + config->overlay_position = POSITION_TOP; + } else if (strcmp(value, "bottom") == 0) { + config->overlay_position = POSITION_BOTTOM; + } else { + bongocat_log_warning("Invalid overlay_position '%s', using 'top'", value); + config->overlay_position = POSITION_TOP; + } + } else if (strcmp(key, "cat_align") == 0) { + if (strcmp(value, "left") == 0) { + config->cat_align = ALIGN_LEFT; + } else if (strcmp(value, "center") == 0) { + config->cat_align = ALIGN_CENTER; + } else if (strcmp(value, "right") == 0) { + config->cat_align = ALIGN_RIGHT; } else { - return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + bongocat_log_warning("Invalid cat_align '%s', using 'center'", value); + config->cat_align = ALIGN_CENTER; } + } else { + return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } - return BONGOCAT_SUCCESS; + return BONGOCAT_SUCCESS; } -static bongocat_error_t config_parse_time_key(config_t *config, const char *key, const char *value) { - // Only try to parse time for time-related keys - if (strcmp(key, "sleep_begin") != 0 && strcmp(key, "sleep_end") != 0) { - return BONGOCAT_ERROR_INVALID_PARAM; // Not a time key - } +static bongocat_error_t config_parse_time_key(config_t *config, const char *key, + const char *value) { + // Only try to parse time for time-related keys + if (strcmp(key, "sleep_begin") != 0 && strcmp(key, "sleep_end") != 0) { + return BONGOCAT_ERROR_INVALID_PARAM; // Not a time key + } - int hour, min; - if (sscanf(value, "%d:%d", &hour, &min) != 2) { - bongocat_log_warning("Invalid time format '%s', expected HH:MM", value); - return BONGOCAT_ERROR_INVALID_PARAM; - } + int hour, min; + if (sscanf(value, "%d:%d", &hour, &min) != 2) { + bongocat_log_warning("Invalid time format '%s', expected HH:MM", value); + return BONGOCAT_ERROR_INVALID_PARAM; + } - if (hour < 0 || hour > 23 || min < 0 || min > 59) { - bongocat_log_warning("Invalid time values '%s', hour must be 0-23, minute must be 0-59", value); - return BONGOCAT_ERROR_INVALID_PARAM; - } + if (hour < 0 || hour > 23 || min < 0 || min > 59) { + bongocat_log_warning( + "Invalid time values '%s', hour must be 0-23, minute must be 0-59", + value); + return BONGOCAT_ERROR_INVALID_PARAM; + } - if (strcmp(key, "sleep_begin") == 0) { - config->sleep_begin.hour = hour; - config->sleep_begin.min = min; - } else if (strcmp(key, "sleep_end") == 0) { - config->sleep_end.hour = hour; - config->sleep_end.min = min; - } + if (strcmp(key, "sleep_begin") == 0) { + config->sleep_begin.hour = hour; + config->sleep_begin.min = min; + } else if (strcmp(key, "sleep_end") == 0) { + config->sleep_end.hour = hour; + config->sleep_end.min = min; + } - return BONGOCAT_SUCCESS; + return BONGOCAT_SUCCESS; } -static bongocat_error_t config_parse_string_key(config_t *config, const char *key, const char *value) { - if (strcmp(key, "monitor") == 0) { - // Reallocate new name for monitor output - config->output_name = realloc(config->output_name, strlen(value) + 1); - if (!config->output_name) { - bongocat_log_error("Failed to allocate memory for interface output"); - return BONGOCAT_ERROR_MEMORY; - } - strcpy(config->output_name, value); - } else { - return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key +static bongocat_error_t +config_parse_string_key(config_t *config, const char *key, const char *value) { + if (strcmp(key, "monitor") == 0) { + // Reallocate using temp variable to prevent memory leak on failure + char *new_name = realloc(config->output_name, strlen(value) + 1); + if (!new_name) { + bongocat_log_error("Failed to allocate memory for interface output"); + return BONGOCAT_ERROR_MEMORY; } + config->output_name = new_name; + strcpy(config->output_name, value); + } else { + return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } - return BONGOCAT_SUCCESS; + return BONGOCAT_SUCCESS; } -static bongocat_error_t config_parse_key_value(config_t *config, const char *key, const char *value) { - // Try integer keys first - if (config_parse_integer_key(config, key, value) == BONGOCAT_SUCCESS) { - return BONGOCAT_SUCCESS; - } - - // Try enum keys - if (config_parse_enum_key(config, key, value) == BONGOCAT_SUCCESS) { - return BONGOCAT_SUCCESS; - } +static bongocat_error_t +config_parse_key_value(config_t *config, const char *key, const char *value) { + // Try integer keys first + if (config_parse_integer_key(config, key, value) == BONGOCAT_SUCCESS) { + return BONGOCAT_SUCCESS; + } + // Try enum keys + if (config_parse_enum_key(config, key, value) == BONGOCAT_SUCCESS) { + return BONGOCAT_SUCCESS; + } - // Try time keys - if (config_parse_time_key(config, key, value) == BONGOCAT_SUCCESS) { - return BONGOCAT_SUCCESS; - } + // Try time keys + if (config_parse_time_key(config, key, value) == BONGOCAT_SUCCESS) { + return BONGOCAT_SUCCESS; + } - // Try string keys - if (config_parse_string_key(config, key, value) == BONGOCAT_SUCCESS) { - return BONGOCAT_SUCCESS; - } + // Try string keys + if (config_parse_string_key(config, key, value) == BONGOCAT_SUCCESS) { + return BONGOCAT_SUCCESS; + } - // Handle device keys - if (strcmp(key, "keyboard_device") == 0 || strcmp(key, "keyboard_devices") == 0) { - return config_add_keyboard_device(config, value); - } + // Handle device keys + if (strcmp(key, "keyboard_device") == 0 || + strcmp(key, "keyboard_devices") == 0) { + return config_add_keyboard_device(config, value); + } - // Unknown key - return BONGOCAT_ERROR_INVALID_PARAM; + // Unknown key + return BONGOCAT_ERROR_INVALID_PARAM; } static bool config_is_comment_or_empty(const char *line) { - return (line[0] == '#' || line[0] == '\0' || strspn(line, " \t") == strlen(line)); + return (line[0] == '#' || line[0] == '\0' || + strspn(line, " \t") == strlen(line)); } -static bongocat_error_t config_parse_file(config_t *config, const char *config_file_path) { - BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); +static bongocat_error_t config_parse_file(config_t *config, + const char *config_file_path) { + BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); - const char *file_path = config_file_path ? config_file_path : "bongocat.conf"; + const char *file_path = config_file_path ? config_file_path : "bongocat.conf"; - FILE *file = fopen(file_path, "r"); - if (!file) { - bongocat_log_info("Config file '%s' not found, using defaults", file_path); - return BONGOCAT_SUCCESS; - } + FILE *file = fopen(file_path, "r"); + if (!file) { + bongocat_log_info("Config file '%s' not found, using defaults", file_path); + return BONGOCAT_SUCCESS; + } + + char line[512]; + char key[256], value[256]; + int line_number = 0; + bongocat_error_t result = BONGOCAT_SUCCESS; + + while (fgets(line, sizeof(line), file)) { + line_number++; - char line[512]; - char key[256], value[256]; - int line_number = 0; - bongocat_error_t result = BONGOCAT_SUCCESS; - - while (fgets(line, sizeof(line), file)) { - line_number++; - - // Remove trailing newline - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') { - line[len - 1] = '\0'; - } - - // Skip comments and empty lines - if (config_is_comment_or_empty(line)) { - continue; - } - - // Parse key=value pairs - if (sscanf(line, " %255[^=] = %255s", key, value) == 2) { - char *trimmed_key = config_trim_key(key); - - bongocat_error_t parse_result = config_parse_key_value(config, trimmed_key, value); - if (parse_result == BONGOCAT_ERROR_INVALID_PARAM) { - bongocat_log_warning("Unknown configuration key '%s' at line %d", trimmed_key, line_number); - } else if (parse_result != BONGOCAT_SUCCESS) { - result = parse_result; - break; - } - } else if (strlen(line) > 0) { - bongocat_log_warning("Invalid configuration line %d: %s", line_number, line); - } + // Remove trailing newline + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; } - fclose(file); + // Skip comments and empty lines + if (config_is_comment_or_empty(line)) { + continue; + } - if (result == BONGOCAT_SUCCESS) { - bongocat_log_info("Loaded configuration from %s", file_path); + // Parse key=value pairs + if (sscanf(line, " %255[^=] = %255s", key, value) == 2) { + char *trimmed_key = config_trim_key(key); + + bongocat_error_t parse_result = + config_parse_key_value(config, trimmed_key, value); + if (parse_result == BONGOCAT_ERROR_INVALID_PARAM) { + bongocat_log_warning("Unknown configuration key '%s' at line %d", + trimmed_key, line_number); + } else if (parse_result != BONGOCAT_SUCCESS) { + result = parse_result; + break; + } + } else if (strlen(line) > 0) { + bongocat_log_warning("Invalid configuration line %d: %s", line_number, + line); } + } - return result; + fclose(file); + + if (result == BONGOCAT_SUCCESS) { + bongocat_log_info("Loaded configuration from %s", file_path); + } + + return result; } // ============================================================================= @@ -399,69 +429,75 @@ static bongocat_error_t config_parse_file(config_t *config, const char *config_f // ============================================================================= static void config_set_defaults(config_t *config) { - *config = (config_t) { - .screen_width = DEFAULT_SCREEN_WIDTH, // Will be updated by Wayland detection - .output_name = NULL, // Will default to automatic one if kept null - .bar_height = DEFAULT_BAR_HEIGHT, - .asset_paths = { - "assets/bongo-cat-both-up.png", - "assets/bongo-cat-left-down.png", - "assets/bongo-cat-right-down.png", - "assets/bongo-cat-both-down.png" - }, - .keyboard_devices = NULL, - .num_keyboard_devices = 0, - .cat_x_offset = 100, - .cat_y_offset = 10, - .cat_height = 40, - .overlay_height = 50, - .idle_frame = 0, - .keypress_duration = 100, - .test_animation_duration = 200, - .test_animation_interval = 0, - .fps = 60, - .overlay_opacity = 150, - .mirror_x = 0, - .mirror_y = 0, - .enable_antialiasing = 1, - .enable_debug = 1, - .layer = LAYER_TOP, // Default to TOP for broader compatibility - .overlay_position = POSITION_TOP, - .cat_align = ALIGN_CENTER, - .enable_scheduled_sleep = 0, - .sleep_begin = (config_time_t){0, 0}, - .sleep_end = (config_time_t){0, 0}, - .idle_sleep_timeout_sec = 0, - }; + *config = (config_t){ + .screen_width = + DEFAULT_SCREEN_WIDTH, // Will be updated by Wayland detection + .output_name = NULL, // Will default to automatic one if kept null + .bar_height = DEFAULT_BAR_HEIGHT, + .asset_paths = {"assets/bongo-cat-both-up.png", + "assets/bongo-cat-left-down.png", + "assets/bongo-cat-right-down.png", + "assets/bongo-cat-both-down.png"}, + .keyboard_devices = NULL, + .num_keyboard_devices = 0, + .cat_x_offset = 100, + .cat_y_offset = 10, + .cat_height = 40, + .overlay_height = 50, + .idle_frame = 0, + .keypress_duration = 100, + .test_animation_duration = 200, + .test_animation_interval = 0, + .fps = 60, + .overlay_opacity = 150, + .mirror_x = 0, + .mirror_y = 0, + .enable_antialiasing = 1, + .enable_debug = 1, + .layer = LAYER_TOP, // Default to TOP for broader compatibility + .overlay_position = POSITION_TOP, + .cat_align = ALIGN_CENTER, + .enable_scheduled_sleep = 0, + .sleep_begin = (config_time_t){0, 0}, + .sleep_end = (config_time_t){0, 0}, + .idle_sleep_timeout_sec = 0, + }; } static bongocat_error_t config_set_default_devices(config_t *config) { - if (config_num_devices == 0) { - const char *default_device = "/dev/input/event4"; - return config_add_keyboard_device(config, default_device); - } - return BONGOCAT_SUCCESS; + if (config_num_devices == 0) { + const char *default_device = "/dev/input/event4"; + return config_add_keyboard_device(config, default_device); + } + return BONGOCAT_SUCCESS; } static void config_finalize(config_t *config) { - // Update bar_height from config - config->bar_height = config->overlay_height; + // Update bar_height from config + config->bar_height = config->overlay_height; - // Initialize error system with debug setting - bongocat_error_init(config->enable_debug); + // Initialize error system with debug setting + bongocat_error_init(config->enable_debug); } static void config_log_summary(const config_t *config) { - bongocat_log_debug("Configuration loaded successfully"); - bongocat_log_debug(" Screen: %dx%d", config->screen_width, config->bar_height); - bongocat_log_debug(" Cat: %dx%d at offset (%d,%d)", - config->cat_height, (config->cat_height * CAT_IMAGE_WIDTH) / CAT_IMAGE_HEIGHT, - config->cat_x_offset, config->cat_y_offset); - bongocat_log_debug(" FPS: %d, Opacity: %d", config->fps, config->overlay_opacity); - bongocat_log_debug(" Mirror: X=%d, Y=%d", config->mirror_x, config->mirror_y); - bongocat_log_debug(" Anti-aliasing: %s", config->enable_antialiasing ? "enabled" : "disabled"); - bongocat_log_debug(" Position: %s", config->overlay_position == POSITION_TOP ? "top" : "bottom"); - bongocat_log_debug(" Layer: %s", config->layer == LAYER_TOP ? "top" : "overlay"); + bongocat_log_debug("Configuration loaded successfully"); + bongocat_log_debug(" Screen: %dx%d", config->screen_width, + config->bar_height); + bongocat_log_debug(" Cat: %dx%d at offset (%d,%d)", config->cat_height, + (config->cat_height * CAT_IMAGE_WIDTH) / CAT_IMAGE_HEIGHT, + config->cat_x_offset, config->cat_y_offset); + bongocat_log_debug(" FPS: %d, Opacity: %d", config->fps, + config->overlay_opacity); + bongocat_log_debug(" Mirror: X=%d, Y=%d", config->mirror_x, + config->mirror_y); + bongocat_log_debug(" Anti-aliasing: %s", + config->enable_antialiasing ? "enabled" : "disabled"); + bongocat_log_debug(" Position: %s", config->overlay_position == POSITION_TOP + ? "top" + : "bottom"); + bongocat_log_debug(" Layer: %s", + config->layer == LAYER_TOP ? "top" : "overlay"); } // ============================================================================= @@ -469,63 +505,65 @@ static void config_log_summary(const config_t *config) { // ============================================================================= bongocat_error_t load_config(config_t *config, const char *config_file_path) { - BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); - // Clear existing keyboard devices to prevent accumulation during reloads - config_cleanup_devices(); + // Clear existing keyboard devices to prevent accumulation during reloads + config_cleanup_devices(); - // Initialize with defaults - config_set_defaults(config); - - // Parse config file and override defaults - bongocat_error_t result = config_parse_file(config, config_file_path); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to parse configuration file: %s", bongocat_error_string(result)); - return result; - } + // Initialize with defaults + config_set_defaults(config); - // Set default keyboard device if none specified - if (config->keyboard_devices == NULL || config->num_keyboard_devices == 0) { - result = config_set_default_devices(config); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to set default keyboard devices: %s", bongocat_error_string(result)); - return result; - } - } + // Parse config file and override defaults + bongocat_error_t result = config_parse_file(config, config_file_path); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to parse configuration file: %s", + bongocat_error_string(result)); + return result; + } - // Validate and sanitize configuration - result = config_validate(config); + // Set default keyboard device if none specified + if (config->keyboard_devices == NULL || config->num_keyboard_devices == 0) { + result = config_set_default_devices(config); if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Configuration validation failed: %s", bongocat_error_string(result)); - return result; + bongocat_log_error("Failed to set default keyboard devices: %s", + bongocat_error_string(result)); + return result; } + } + + // Validate and sanitize configuration + result = config_validate(config); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Configuration validation failed: %s", + bongocat_error_string(result)); + return result; + } - // Finalize configuration - config_finalize(config); + // Finalize configuration + config_finalize(config); - // Log configuration summary - config_log_summary(config); + // Log configuration summary + config_log_summary(config); - return BONGOCAT_SUCCESS; + return BONGOCAT_SUCCESS; } -void config_cleanup(void) { - config_cleanup_devices(); -} +void config_cleanup(void) { config_cleanup_devices(); } void config_cleanup_full(config_t *config) { - if (!config) return; + if (!config) + return; - config_cleanup_devices(); + config_cleanup_devices(); - if (config->output_name) { - free(config->output_name); - config->output_name = NULL; - } + if (config->output_name) { + free(config->output_name); + config->output_name = NULL; + } } int get_screen_width(void) { - // This function is now only used for initial config loading - // The actual screen width detection happens in wayland_init - return DEFAULT_SCREEN_WIDTH; + // This function is now only used for initial config loading + // The actual screen width detection happens in wayland_init + return DEFAULT_SCREEN_WIDTH; } diff --git a/src/core/main.c b/src/core/main.c index 6650bc29..57094107 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1,18 +1,18 @@ #define _POSIX_C_SOURCE 200809L #define _DEFAULT_SOURCE +#include "config/config.h" #include "core/bongocat.h" -#include "platform/wayland.h" #include "graphics/animation.h" #include "platform/input.h" -#include "config/config.h" +#include "platform/wayland.h" #include "utils/error.h" #include "utils/memory.h" #include -#include #include +#include #include +#include #include -#include // ============================================================================= // GLOBAL STATE AND CONFIGURATION @@ -29,11 +29,11 @@ static ConfigWatcher g_config_watcher; // ============================================================================= typedef struct { - const char *config_file; - bool watch_config; - bool toggle_mode; - bool show_help; - bool show_version; + const char *config_file; + bool watch_config; + bool toggle_mode; + bool show_help; + bool show_version; } cli_args_t; // ============================================================================= @@ -41,111 +41,110 @@ typedef struct { // ============================================================================= static int process_create_pid_file(void) { - int fd = open(PID_FILE, O_CREAT | O_WRONLY | O_TRUNC, 0644); - if (fd < 0) { - bongocat_log_error("Failed to create PID file: %s", strerror(errno)); - return -1; - } - - if (flock(fd, LOCK_EX | LOCK_NB) < 0) { - close(fd); - if (errno == EWOULDBLOCK) { - bongocat_log_info("Another instance is already running"); - return -2; // Already running - } - bongocat_log_error("Failed to lock PID file: %s", strerror(errno)); - return -1; - } - - char pid_str[32]; - snprintf(pid_str, sizeof(pid_str), "%d\n", getpid()); - if (write(fd, pid_str, strlen(pid_str)) < 0) { - bongocat_log_error("Failed to write PID to file: %s", strerror(errno)); - close(fd); - return -1; + int fd = open(PID_FILE, O_CREAT | O_WRONLY | O_TRUNC, 0644); + if (fd < 0) { + bongocat_log_error("Failed to create PID file: %s", strerror(errno)); + return -1; + } + + if (flock(fd, LOCK_EX | LOCK_NB) < 0) { + close(fd); + if (errno == EWOULDBLOCK) { + bongocat_log_info("Another instance is already running"); + return -2; // Already running } - - return fd; // Keep file descriptor open to maintain lock -} + bongocat_log_error("Failed to lock PID file: %s", strerror(errno)); + return -1; + } + + char pid_str[32]; + snprintf(pid_str, sizeof(pid_str), "%d\n", getpid()); + if (write(fd, pid_str, strlen(pid_str)) < 0) { + bongocat_log_error("Failed to write PID to file: %s", strerror(errno)); + close(fd); + return -1; + } -static void process_remove_pid_file(void) { - unlink(PID_FILE); + return fd; // Keep file descriptor open to maintain lock } +static void process_remove_pid_file(void) { unlink(PID_FILE); } + static pid_t process_get_running_pid(void) { - int fd = open(PID_FILE, O_RDONLY); - if (fd < 0) { - return -1; // No PID file exists - } - - // Try to get a shared lock to read the file - if (flock(fd, LOCK_SH | LOCK_NB) < 0) { - close(fd); - if (errno == EWOULDBLOCK) { - // File is locked by another process, so it's running - // We need to read the PID anyway, so let's try without lock - fd = open(PID_FILE, O_RDONLY); - if (fd < 0) return -1; - } else { - return -1; - } - } - - char pid_str[32]; - ssize_t bytes_read = read(fd, pid_str, sizeof(pid_str) - 1); + int fd = open(PID_FILE, O_RDONLY); + if (fd < 0) { + return -1; // No PID file exists + } + + // Try to get a shared lock to read the file + if (flock(fd, LOCK_SH | LOCK_NB) < 0) { close(fd); - - if (bytes_read <= 0) { - return -1; - } - - pid_str[bytes_read] = '\0'; - pid_t pid = (pid_t)atoi(pid_str); - - if (pid <= 0) { + if (errno == EWOULDBLOCK) { + // File is locked by another process, so it's running + // We need to read the PID anyway, so let's try without lock + fd = open(PID_FILE, O_RDONLY); + if (fd < 0) return -1; + } else { + return -1; } - - // Check if process is actually running - if (kill(pid, 0) == 0) { - return pid; // Process is running - } - - // Process is not running, remove stale PID file - process_remove_pid_file(); + } + + char pid_str[32]; + ssize_t bytes_read = read(fd, pid_str, sizeof(pid_str) - 1); + close(fd); + + if (bytes_read <= 0) { + return -1; + } + + pid_str[bytes_read] = '\0'; + pid_t pid = (pid_t)atoi(pid_str); + + if (pid <= 0) { return -1; + } + + // Check if process is actually running + if (kill(pid, 0) == 0) { + return pid; // Process is running + } + + // Process is not running, remove stale PID file + process_remove_pid_file(); + return -1; } static int process_handle_toggle(void) { - pid_t running_pid = process_get_running_pid(); - - if (running_pid > 0) { - // Process is running, kill it - bongocat_log_info("Stopping bongocat (PID: %d)", running_pid); - if (kill(running_pid, SIGTERM) == 0) { - // Wait a bit for graceful shutdown - for (int i = 0; i < 50; i++) { // Wait up to 5 seconds - if (kill(running_pid, 0) != 0) { - bongocat_log_info("Bongocat stopped successfully"); - return 0; - } - usleep(100000); // 100ms - } - - // Force kill if still running - bongocat_log_warning("Force killing bongocat"); - kill(running_pid, SIGKILL); - bongocat_log_info("Bongocat force stopped"); - } else { - bongocat_log_error("Failed to stop bongocat: %s", strerror(errno)); - return 1; + pid_t running_pid = process_get_running_pid(); + + if (running_pid > 0) { + // Process is running, kill it + bongocat_log_info("Stopping bongocat (PID: %d)", running_pid); + if (kill(running_pid, SIGTERM) == 0) { + // Wait a bit for graceful shutdown + for (int i = 0; i < 50; i++) { // Wait up to 5 seconds + if (kill(running_pid, 0) != 0) { + bongocat_log_info("Bongocat stopped successfully"); + return 0; } + usleep(100000); // 100ms + } + + // Force kill if still running + bongocat_log_warning("Force killing bongocat"); + kill(running_pid, SIGKILL); + bongocat_log_info("Bongocat force stopped"); } else { - bongocat_log_info("Bongocat is not running, starting it now"); - return -1; // Signal to continue with normal startup + bongocat_log_error("Failed to stop bongocat: %s", strerror(errno)); + return 1; } - - return 0; + } else { + bongocat_log_info("Bongocat is not running, starting it now"); + return -1; // Signal to continue with normal startup + } + + return 0; } // ============================================================================= @@ -153,135 +152,151 @@ static int process_handle_toggle(void) { // ============================================================================= static void signal_handler(int sig) { - switch (sig) { - case SIGINT: - case SIGTERM: - bongocat_log_info("Received signal %d, shutting down gracefully", sig); - running = 0; - break; - case SIGCHLD: - // Handle child process termination - while (waitpid(-1, NULL, WNOHANG) > 0); - break; - default: - bongocat_log_warning("Received unexpected signal %d", sig); - break; - } + switch (sig) { + case SIGINT: + case SIGTERM: + bongocat_log_info("Received signal %d, shutting down gracefully", sig); + running = 0; + break; + case SIGCHLD: + // Handle child process termination + while (waitpid(-1, NULL, WNOHANG) > 0) + ; + break; + default: + bongocat_log_warning("Received unexpected signal %d", sig); + break; + } } static bongocat_error_t signal_setup_handlers(void) { - struct sigaction sa; - - // Setup signal handler - sa.sa_handler = signal_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - - if (sigaction(SIGINT, &sa, NULL) == -1) { - bongocat_log_error("Failed to setup SIGINT handler: %s", strerror(errno)); - return BONGOCAT_ERROR_THREAD; - } - - if (sigaction(SIGTERM, &sa, NULL) == -1) { - bongocat_log_error("Failed to setup SIGTERM handler: %s", strerror(errno)); - return BONGOCAT_ERROR_THREAD; - } - - if (sigaction(SIGCHLD, &sa, NULL) == -1) { - bongocat_log_error("Failed to setup SIGCHLD handler: %s", strerror(errno)); - return BONGOCAT_ERROR_THREAD; - } - - // Ignore SIGPIPE - signal(SIGPIPE, SIG_IGN); - - return BONGOCAT_SUCCESS; + struct sigaction sa; + + // Setup signal handler + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGINT, &sa, NULL) == -1) { + bongocat_log_error("Failed to setup SIGINT handler: %s", strerror(errno)); + return BONGOCAT_ERROR_THREAD; + } + + if (sigaction(SIGTERM, &sa, NULL) == -1) { + bongocat_log_error("Failed to setup SIGTERM handler: %s", strerror(errno)); + return BONGOCAT_ERROR_THREAD; + } + + if (sigaction(SIGCHLD, &sa, NULL) == -1) { + bongocat_log_error("Failed to setup SIGCHLD handler: %s", strerror(errno)); + return BONGOCAT_ERROR_THREAD; + } + + // Ignore SIGPIPE + signal(SIGPIPE, SIG_IGN); + + return BONGOCAT_SUCCESS; } // ============================================================================= // CONFIGURATION MANAGEMENT MODULE // ============================================================================= -static bool config_devices_changed(const config_t *old_config, const config_t *new_config) { - if (old_config->num_keyboard_devices != new_config->num_keyboard_devices) { - return true; - } - - // Check if any device paths changed - for (int i = 0; i < new_config->num_keyboard_devices; i++) { - bool found = false; - for (int j = 0; j < old_config->num_keyboard_devices; j++) { - if (strcmp(new_config->keyboard_devices[i], old_config->keyboard_devices[j]) == 0) { - found = true; - break; - } - } - if (!found) { - return true; - } - } - - return false; +static bool config_devices_changed(const config_t *old_config, + const config_t *new_config) { + if (old_config->num_keyboard_devices != new_config->num_keyboard_devices) { + return true; + } + + // Check if any device paths changed + for (int i = 0; i < new_config->num_keyboard_devices; i++) { + bool found = false; + for (int j = 0; j < old_config->num_keyboard_devices; j++) { + if (strcmp(new_config->keyboard_devices[i], + old_config->keyboard_devices[j]) == 0) { + found = true; + break; + } + } + if (!found) { + return true; + } + } + + return false; } static void config_reload_callback(const char *config_path) { - bongocat_log_info("Reloading configuration from: %s", config_path); - - // Create a temporary config to test loading - config_t temp_config; - bongocat_error_t result = load_config(&temp_config, config_path); - - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to reload config: %s", bongocat_error_string(result)); - bongocat_log_info("Keeping current configuration"); - return; - } - - // If successful, check if input devices changed before updating config - bool devices_changed = config_devices_changed(&g_config, &temp_config); - - // Clean up old output_name if it exists and is different - if (g_config.output_name && temp_config.output_name && - strcmp(g_config.output_name, temp_config.output_name) != 0) { - free(g_config.output_name); - } else if (g_config.output_name && !temp_config.output_name) { - free(g_config.output_name); - } - - // Update the global config - g_config = temp_config; - - // Update the running systems with new config - wayland_update_config(&g_config); - - // Check if input devices changed and restart monitoring if needed - if (devices_changed) { - bongocat_log_info("Input devices changed, restarting input monitoring"); - bongocat_error_t input_result = input_restart_monitoring(g_config.keyboard_devices, - g_config.num_keyboard_devices, - g_config.enable_debug); - if (input_result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to restart input monitoring: %s", bongocat_error_string(input_result)); - } else { - bongocat_log_info("Input monitoring restarted successfully"); - } + bongocat_log_info("Reloading configuration from: %s", config_path); + + // IMPORTANT: Save old device count BEFORE calling load_config, + // because load_config calls config_cleanup_devices() which frees + // the keyboard_devices array that g_config still points to. + int old_num_devices = g_config.num_keyboard_devices; + + // Create a temporary config to test loading + config_t temp_config; + bongocat_error_t result = load_config(&temp_config, config_path); + + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to reload config: %s", + bongocat_error_string(result)); + bongocat_log_info("Keeping current configuration"); + return; + } + + // Check if device count changed (we can't access old device paths + // because they were freed by load_config, so just check count) + bool devices_changed = (old_num_devices != temp_config.num_keyboard_devices); + + // Clean up old output_name if it exists and is different + // Note: g_config.keyboard_devices is now invalid (freed by load_config) + // so we only handle output_name here + if (g_config.output_name && temp_config.output_name && + strcmp(g_config.output_name, temp_config.output_name) != 0) { + free(g_config.output_name); + } else if (g_config.output_name && !temp_config.output_name) { + free(g_config.output_name); + } + + // Update the global config + g_config = temp_config; + + // Update the running systems with new config + wayland_update_config(&g_config); + + // Check if input devices changed and restart monitoring if needed + if (devices_changed) { + bongocat_log_info("Input devices changed, restarting input monitoring"); + bongocat_error_t input_result = input_restart_monitoring( + g_config.keyboard_devices, g_config.num_keyboard_devices, + g_config.enable_debug); + if (input_result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to restart input monitoring: %s", + bongocat_error_string(input_result)); + } else { + bongocat_log_info("Input monitoring restarted successfully"); } - - bongocat_log_info("Configuration reloaded successfully!"); - bongocat_log_info("New screen dimensions: %dx%d", g_config.screen_width, g_config.bar_height); + } + + bongocat_log_info("Configuration reloaded successfully!"); + bongocat_log_info("New screen dimensions: %dx%d", g_config.screen_width, + g_config.bar_height); } static bongocat_error_t config_setup_watcher(const char *config_file) { - const char *watch_path = config_file ? config_file : "bongocat.conf"; - - if (config_watcher_init(&g_config_watcher, watch_path, config_reload_callback) == 0) { - config_watcher_start(&g_config_watcher); - bongocat_log_info("Config file watching enabled for: %s", watch_path); - return BONGOCAT_SUCCESS; - } else { - bongocat_log_warning("Failed to initialize config watcher, continuing without hot-reload"); - return BONGOCAT_ERROR_CONFIG; - } + const char *watch_path = config_file ? config_file : "bongocat.conf"; + + if (config_watcher_init(&g_config_watcher, watch_path, + config_reload_callback) == 0) { + config_watcher_start(&g_config_watcher); + bongocat_log_info("Config file watching enabled for: %s", watch_path); + return BONGOCAT_SUCCESS; + } else { + bongocat_log_warning( + "Failed to initialize config watcher, continuing without hot-reload"); + return BONGOCAT_ERROR_CONFIG; + } } // ============================================================================= @@ -289,72 +304,78 @@ static bongocat_error_t config_setup_watcher(const char *config_file) { // ============================================================================= static bongocat_error_t system_initialize_components(void) { - bongocat_error_t result; - - // Initialize Wayland - result = wayland_init(&g_config); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to initialize Wayland: %s", bongocat_error_string(result)); - return result; - } - - // Initialize animation system - result = animation_init(&g_config); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to initialize animation system: %s", bongocat_error_string(result)); - return result; - } - - // Start input monitoring - result = input_start_monitoring(g_config.keyboard_devices, g_config.num_keyboard_devices, g_config.enable_debug); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to start input monitoring: %s", bongocat_error_string(result)); - return result; - } - - // Start animation thread - result = animation_start(); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to start animation thread: %s", bongocat_error_string(result)); - return result; - } - - return BONGOCAT_SUCCESS; + bongocat_error_t result; + + // Initialize Wayland + result = wayland_init(&g_config); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to initialize Wayland: %s", + bongocat_error_string(result)); + return result; + } + + // Initialize animation system + result = animation_init(&g_config); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to initialize animation system: %s", + bongocat_error_string(result)); + return result; + } + + // Start input monitoring + result = input_start_monitoring(g_config.keyboard_devices, + g_config.num_keyboard_devices, + g_config.enable_debug); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to start input monitoring: %s", + bongocat_error_string(result)); + return result; + } + + // Start animation thread + result = animation_start(); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to start animation thread: %s", + bongocat_error_string(result)); + return result; + } + + return BONGOCAT_SUCCESS; } static void system_cleanup_and_exit(int exit_code) { - bongocat_log_info("Performing cleanup..."); - - // Remove PID file - process_remove_pid_file(); - - // Stop config watcher - config_watcher_cleanup(&g_config_watcher); - - // Stop animation system - animation_cleanup(); - - // Cleanup Wayland - wayland_cleanup(); - - // Cleanup input system - input_cleanup(); - - // Cleanup configuration - config_cleanup_full(&g_config); - config_cleanup(); - - // Print memory statistics in debug mode - if (g_config.enable_debug) { - memory_print_stats(); - } - + bongocat_log_info("Performing cleanup..."); + + // Remove PID file + process_remove_pid_file(); + + // Stop config watcher + config_watcher_cleanup(&g_config_watcher); + + // Stop animation system + animation_cleanup(); + + // Cleanup Wayland + wayland_cleanup(); + + // Cleanup input system + input_cleanup(); + + // Cleanup configuration + config_cleanup_full(&g_config); + config_cleanup(); + + // Print memory statistics in debug mode + if (g_config.enable_debug) { + memory_print_stats(); + } + #ifdef DEBUG - memory_leak_check(); + memory_leak_check(); #endif - - bongocat_log_info("Cleanup complete, exiting with code %d", exit_code); - exit(exit_code); + + bongocat_log_info("Cleanup complete, exiting with code %d", exit_code); + exit(exit_code); } // ============================================================================= @@ -362,55 +383,59 @@ static void system_cleanup_and_exit(int exit_code) { // ============================================================================= static void cli_show_help(const char *program_name) { - printf("Bongo Cat Wayland Overlay\n"); - printf("Usage: %s [options]\n", program_name); - printf("Options:\n"); - printf(" -h, --help Show this help message\n"); - printf(" -v, --version Show version information\n"); - printf(" -c, --config Specify config file (default: bongocat.conf)\n"); - printf(" -w, --watch-config Watch config file for changes and reload automatically\n"); - printf(" -t, --toggle Toggle bongocat on/off (start if not running, stop if running)\n"); - printf("\nConfiguration is loaded from bongocat.conf in the current directory.\n"); + printf("Bongo Cat Wayland Overlay\n"); + printf("Usage: %s [options]\n", program_name); + printf("Options:\n"); + printf(" -h, --help Show this help message\n"); + printf(" -v, --version Show version information\n"); + printf( + " -c, --config Specify config file (default: bongocat.conf)\n"); + printf(" -w, --watch-config Watch config file for changes and reload " + "automatically\n"); + printf(" -t, --toggle Toggle bongocat on/off (start if not " + "running, stop if running)\n"); + printf("\nConfiguration is loaded from bongocat.conf in the current " + "directory.\n"); } static void cli_show_version(void) { - printf("Bongo Cat Overlay v" BONGOCAT_VERSION "\n"); - printf("Built with fast optimizations\n"); + printf("Bongo Cat Overlay v" BONGOCAT_VERSION "\n"); + printf("Built with fast optimizations\n"); } static int cli_parse_arguments(int argc, char *argv[], cli_args_t *args) { - // Initialize arguments with defaults - *args = (cli_args_t){ - .config_file = NULL, - .watch_config = false, - .toggle_mode = false, - .show_help = false, - .show_version = false - }; - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - args->show_help = true; - } else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { - args->show_version = true; - } else if (strcmp(argv[i], "--config") == 0 || strcmp(argv[i], "-c") == 0) { - if (i + 1 < argc) { - args->config_file = argv[i + 1]; - i++; // Skip the next argument since it's the config file path - } else { - bongocat_log_error("--config option requires a file path"); - return 1; - } - } else if (strcmp(argv[i], "--watch-config") == 0 || strcmp(argv[i], "-w") == 0) { - args->watch_config = true; - } else if (strcmp(argv[i], "--toggle") == 0 || strcmp(argv[i], "-t") == 0) { - args->toggle_mode = true; - } else { - bongocat_log_warning("Unknown argument: %s", argv[i]); - } + // Initialize arguments with defaults + *args = (cli_args_t){.config_file = NULL, + .watch_config = false, + .toggle_mode = false, + .show_help = false, + .show_version = false}; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + args->show_help = true; + } else if (strcmp(argv[i], "--version") == 0 || + strcmp(argv[i], "-v") == 0) { + args->show_version = true; + } else if (strcmp(argv[i], "--config") == 0 || strcmp(argv[i], "-c") == 0) { + if (i + 1 < argc) { + args->config_file = argv[i + 1]; + i++; // Skip the next argument since it's the config file path + } else { + bongocat_log_error("--config option requires a file path"); + return 1; + } + } else if (strcmp(argv[i], "--watch-config") == 0 || + strcmp(argv[i], "-w") == 0) { + args->watch_config = true; + } else if (strcmp(argv[i], "--toggle") == 0 || strcmp(argv[i], "-t") == 0) { + args->toggle_mode = true; + } else { + bongocat_log_warning("Unknown argument: %s", argv[i]); } - - return 0; + } + + return 0; } // ============================================================================= @@ -418,87 +443,92 @@ static int cli_parse_arguments(int argc, char *argv[], cli_args_t *args) { // ============================================================================= int main(int argc, char *argv[]) { - bongocat_error_t result; - - // Initialize error system early - bongocat_error_init(1); // Enable debug initially - - bongocat_log_info("Starting Bongo Cat Overlay v" BONGOCAT_VERSION); - - // Parse command line arguments - cli_args_t args; - if (cli_parse_arguments(argc, argv, &args) != 0) { - return 1; - } - - // Handle help and version requests - if (args.show_help) { - cli_show_help(argv[0]); - return 0; - } - - if (args.show_version) { - cli_show_version(); - return 0; - } - - // Handle toggle mode - if (args.toggle_mode) { - int toggle_result = process_handle_toggle(); - if (toggle_result >= 0) { - return toggle_result; // Either successfully toggled off or error - } - // toggle_result == -1 means continue with startup - } - - // Setup signal handlers - result = signal_setup_handlers(); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to setup signal handlers: %s", bongocat_error_string(result)); - return 1; - } - - // Create PID file to track this instance - int pid_fd = process_create_pid_file(); - if (pid_fd == -2) { - bongocat_log_error("Another instance of bongocat is already running"); - return 1; - } else if (pid_fd < 0) { - bongocat_log_error("Failed to create PID file"); - return 1; - } - - // Load configuration - result = load_config(&g_config, args.config_file); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Failed to load configuration: %s", bongocat_error_string(result)); - return 1; - } - - bongocat_log_info("Screen dimensions: %dx%d", g_config.screen_width, g_config.bar_height); - - // Initialize config watcher if requested - if (args.watch_config) { - config_setup_watcher(args.config_file); - } - - // Initialize all system components - result = system_initialize_components(); - if (result != BONGOCAT_SUCCESS) { - system_cleanup_and_exit(1); - } - - bongocat_log_info("Bongo Cat Overlay started successfully"); - - // Main Wayland event loop with graceful shutdown - result = wayland_run(&running); - if (result != BONGOCAT_SUCCESS) { - bongocat_log_error("Wayland event loop error: %s", bongocat_error_string(result)); - system_cleanup_and_exit(1); - } - - bongocat_log_info("Main loop exited, shutting down"); - system_cleanup_and_exit(0); - - return 0; // Never reached + bongocat_error_t result; + + // Initialize error system early + bongocat_error_init(1); // Enable debug initially + + bongocat_log_info("Starting Bongo Cat Overlay v" BONGOCAT_VERSION); + + // Parse command line arguments + cli_args_t args; + if (cli_parse_arguments(argc, argv, &args) != 0) { + return 1; + } + + // Handle help and version requests + if (args.show_help) { + cli_show_help(argv[0]); + return 0; + } + + if (args.show_version) { + cli_show_version(); + return 0; + } + + // Handle toggle mode + if (args.toggle_mode) { + int toggle_result = process_handle_toggle(); + if (toggle_result >= 0) { + return toggle_result; // Either successfully toggled off or error + } + // toggle_result == -1 means continue with startup + } + + // Setup signal handlers + result = signal_setup_handlers(); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to setup signal handlers: %s", + bongocat_error_string(result)); + return 1; + } + + // Create PID file to track this instance + int pid_fd = process_create_pid_file(); + if (pid_fd == -2) { + bongocat_log_error("Another instance of bongocat is already running"); + return 1; + } else if (pid_fd < 0) { + bongocat_log_error("Failed to create PID file"); + return 1; + } + + // Load configuration + result = load_config(&g_config, args.config_file); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to load configuration: %s", + bongocat_error_string(result)); + process_remove_pid_file(); // Clean up PID file on error + return 1; + } + + bongocat_log_info("Screen dimensions: %dx%d", g_config.screen_width, + g_config.bar_height); + + // Initialize config watcher if requested + if (args.watch_config) { + config_setup_watcher(args.config_file); + } + + // Initialize all system components + result = system_initialize_components(); + if (result != BONGOCAT_SUCCESS) { + system_cleanup_and_exit(1); + } + + bongocat_log_info("Bongo Cat Overlay started successfully"); + + // Main Wayland event loop with graceful shutdown + result = wayland_run(&running); + if (result != BONGOCAT_SUCCESS) { + bongocat_log_error("Wayland event loop error: %s", + bongocat_error_string(result)); + system_cleanup_and_exit(1); + } + + bongocat_log_info("Main loop exited, shutting down"); + system_cleanup_and_exit(0); + + return 0; // Never reached } \ No newline at end of file diff --git a/src/graphics/animation.c b/src/graphics/animation.c index 2b6706fa..6a6692f6 100644 --- a/src/graphics/animation.c +++ b/src/graphics/animation.c @@ -1,10 +1,10 @@ #define _POSIX_C_SOURCE 199309L #define STB_IMAGE_IMPLEMENTATION #include "graphics/animation.h" -#include "platform/wayland.h" +#include "graphics/embedded_assets.h" #include "platform/input.h" +#include "platform/wayland.h" #include "utils/memory.h" -#include "graphics/embedded_assets.h" #include // ============================================================================= @@ -27,141 +27,160 @@ static volatile bool animation_running = false; // ============================================================================= static bool drawing_is_pixel_in_bounds(int x, int y, int width, int height) { - return (x >= 0 && y >= 0 && x < width && y < height); + return (x >= 0 && y >= 0 && x < width && y < height); } -static void drawing_copy_pixel(uint8_t *dest, const unsigned char *src, int dest_idx, int src_idx) { - dest[dest_idx + 0] = src[src_idx + 2]; // B - dest[dest_idx + 1] = src[src_idx + 1]; // G - dest[dest_idx + 2] = src[src_idx + 0]; // R - dest[dest_idx + 3] = src[src_idx + 3]; // A +static void drawing_copy_pixel(uint8_t *dest, const unsigned char *src, + int dest_idx, int src_idx) { + dest[dest_idx + 0] = src[src_idx + 2]; // B + dest[dest_idx + 1] = src[src_idx + 1]; // G + dest[dest_idx + 2] = src[src_idx + 0]; // R + dest[dest_idx + 3] = src[src_idx + 3]; // A } -static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_idx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - dest[dest_idx + 0] = b; // B - dest[dest_idx + 1] = g; // G - dest[dest_idx + 2] = r; // R - dest[dest_idx + 3] = a; // A +static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_idx, uint8_t r, + uint8_t g, uint8_t b, uint8_t a) { + dest[dest_idx + 0] = b; // B + dest[dest_idx + 1] = g; // G + dest[dest_idx + 2] = r; // R + dest[dest_idx + 3] = a; // A } // Bilinear interpolation for smooth scaling -static void drawing_get_interpolated_pixel(const unsigned char *src, int src_w, int src_h, - float fx, float fy, uint8_t *r, uint8_t *g, uint8_t *b, uint8_t *a) { - // Clamp coordinates to image bounds - if (fx < 0) fx = 0; - if (fy < 0) fy = 0; - if (fx >= src_w - 1) fx = src_w - 1; - if (fy >= src_h - 1) fy = src_h - 1; - - int x1 = (int)fx; - int y1 = (int)fy; - int x2 = x1 + 1; - int y2 = y1 + 1; - - // Clamp to bounds - if (x2 >= src_w) x2 = src_w - 1; - if (y2 >= src_h) y2 = src_h - 1; - - float dx = fx - x1; - float dy = fy - y1; - - // Get the four surrounding pixels - int idx_tl = (y1 * src_w + x1) * 4; // top-left - int idx_tr = (y1 * src_w + x2) * 4; // top-right - int idx_bl = (y2 * src_w + x1) * 4; // bottom-left - int idx_br = (y2 * src_w + x2) * 4; // bottom-right - - // Interpolate each channel - for (int c = 0; c < 4; c++) { - float top = src[idx_tl + c] * (1.0f - dx) + src[idx_tr + c] * dx; - float bottom = src[idx_bl + c] * (1.0f - dx) + src[idx_br + c] * dx; - float result = top * (1.0f - dy) + bottom * dy; - - switch (c) { - case 0: *r = (uint8_t)(result + 0.5f); break; // R - case 1: *g = (uint8_t)(result + 0.5f); break; // G - case 2: *b = (uint8_t)(result + 0.5f); break; // B - case 3: *a = (uint8_t)(result + 0.5f); break; // A - } +static void drawing_get_interpolated_pixel(const unsigned char *src, int src_w, + int src_h, float fx, float fy, + uint8_t *r, uint8_t *g, uint8_t *b, + uint8_t *a) { + // Clamp coordinates to image bounds + if (fx < 0) + fx = 0; + if (fy < 0) + fy = 0; + if (fx >= src_w - 1) + fx = src_w - 1; + if (fy >= src_h - 1) + fy = src_h - 1; + + int x1 = (int)fx; + int y1 = (int)fy; + int x2 = x1 + 1; + int y2 = y1 + 1; + + // Clamp to bounds + if (x2 >= src_w) + x2 = src_w - 1; + if (y2 >= src_h) + y2 = src_h - 1; + + float dx = fx - x1; + float dy = fy - y1; + + // Get the four surrounding pixels + int idx_tl = (y1 * src_w + x1) * 4; // top-left + int idx_tr = (y1 * src_w + x2) * 4; // top-right + int idx_bl = (y2 * src_w + x1) * 4; // bottom-left + int idx_br = (y2 * src_w + x2) * 4; // bottom-right + + // Interpolate each channel + for (int c = 0; c < 4; c++) { + float top = src[idx_tl + c] * (1.0f - dx) + src[idx_tr + c] * dx; + float bottom = src[idx_bl + c] * (1.0f - dx) + src[idx_br + c] * dx; + float result = top * (1.0f - dy) + bottom * dy; + + switch (c) { + case 0: + *r = (uint8_t)(result + 0.5f); + break; // R + case 1: + *g = (uint8_t)(result + 0.5f); + break; // G + case 2: + *b = (uint8_t)(result + 0.5f); + break; // B + case 3: + *a = (uint8_t)(result + 0.5f); + break; // A } + } } void blit_image_scaled(uint8_t *dest, int dest_w, int dest_h, - unsigned char *src, int src_w, int src_h, - int offset_x, int offset_y, int target_w, int target_h) { - - bool use_antialiasing = current_config && current_config->enable_antialiasing; - - for (int y = 0; y < target_h; y++) { - for (int x = 0; x < target_w; x++) { - int dx = x + offset_x; - int dy = y + offset_y; - - if (!drawing_is_pixel_in_bounds(dx, dy, dest_w, dest_h)) { - continue; - } - - int dest_idx = (dy * dest_w + dx) * 4; - - if (use_antialiasing) { - // Use bilinear interpolation for smooth scaling - float fx = ((float)x * src_w) / target_w; - float fy = ((float)y * src_h) / target_h; - - // Apply mirroring based on configuration - if (current_config->mirror_x) { - fx = (src_w - 1) - fx; - } - if (current_config->mirror_y) { - fy = (src_h - 1) - fy; - } - - uint8_t r, g, b, a; - drawing_get_interpolated_pixel(src, src_w, src_h, fx, fy, &r, &g, &b, &a); - - // Only draw non-transparent pixels - if (a > 128) { - drawing_copy_pixel_rgba(dest, dest_idx, r, g, b, a); - } - } else { - // Use nearest-neighbor scaling (original behavior) - int sx = (x * src_w) / target_w; - int sy = (y * src_h) / target_h; - - // Apply mirroring based on configuration - if (current_config->mirror_x) { - sx = (src_w - 1) - sx; - } - if (current_config->mirror_y) { - sy = (src_h - 1) - sy; - } - - int src_idx = (sy * src_w + sx) * 4; - - // Only draw non-transparent pixels - if (src[src_idx + 3] > 128) { - drawing_copy_pixel(dest, src, dest_idx, src_idx); - } - } + unsigned char *src, int src_w, int src_h, int offset_x, + int offset_y, int target_w, int target_h) { + + bool use_antialiasing = current_config && current_config->enable_antialiasing; + + for (int y = 0; y < target_h; y++) { + for (int x = 0; x < target_w; x++) { + int dx = x + offset_x; + int dy = y + offset_y; + + if (!drawing_is_pixel_in_bounds(dx, dy, dest_w, dest_h)) { + continue; + } + + int dest_idx = (dy * dest_w + dx) * 4; + + if (use_antialiasing) { + // Use bilinear interpolation for smooth scaling + float fx = ((float)x * src_w) / target_w; + float fy = ((float)y * src_h) / target_h; + + // Apply mirroring based on configuration + if (current_config->mirror_x) { + fx = (src_w - 1) - fx; } + if (current_config->mirror_y) { + fy = (src_h - 1) - fy; + } + + uint8_t r, g, b, a; + drawing_get_interpolated_pixel(src, src_w, src_h, fx, fy, &r, &g, &b, + &a); + + // Only draw non-transparent pixels + if (a > 128) { + drawing_copy_pixel_rgba(dest, dest_idx, r, g, b, a); + } + } else { + // Use nearest-neighbor scaling (original behavior) + int sx = (x * src_w) / target_w; + int sy = (y * src_h) / target_h; + + // Apply mirroring based on configuration + if (current_config->mirror_x) { + sx = (src_w - 1) - sx; + } + if (current_config->mirror_y) { + sy = (src_h - 1) - sy; + } + + int src_idx = (sy * src_w + sx) * 4; + + // Only draw non-transparent pixels + if (src[src_idx + 3] > 128) { + drawing_copy_pixel(dest, src, dest_idx, src_idx); + } + } } + } } void draw_rect(uint8_t *dest, int width, int height, int x, int y, int w, int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - for (int j = y; j < y + h; j++) { - for (int i = x; i < x + w; i++) { - if (!drawing_is_pixel_in_bounds(i, j, width, height)) { - continue; - } - - int idx = (j * width + i) * 4; - dest[idx + 0] = b; - dest[idx + 1] = g; - dest[idx + 2] = r; - dest[idx + 3] = a; - } + for (int j = y; j < y + h; j++) { + for (int i = x; i < x + w; i++) { + if (!drawing_is_pixel_in_bounds(i, j, width, height)) { + continue; + } + + int idx = (j * width + i) * 4; + dest[idx + 0] = b; + dest[idx + 1] = g; + dest[idx + 2] = r; + dest[idx + 3] = a; } + } } // ============================================================================= @@ -169,127 +188,137 @@ void draw_rect(uint8_t *dest, int width, int height, int x, int y, int w, int h, // ============================================================================= typedef struct { - long hold_until; - int test_counter; - int test_interval_frames; - long frame_time_ns; - long last_key_pressed_timestamp; + long hold_until; + int test_counter; + int test_interval_frames; + long frame_time_ns; + long last_key_pressed_timestamp; } animation_state_t; static long anim_get_current_time_us(void) { - struct timeval now; - gettimeofday(&now, NULL); - return now.tv_sec * 1000000 + now.tv_usec; + struct timeval now; + gettimeofday(&now, NULL); + return now.tv_sec * 1000000 + now.tv_usec; } static bool anim_is_sleep_time(const config_t *config) { - time_t raw_time; - struct tm time_info; - time(&raw_time); - localtime_r(&raw_time, &time_info); - - const int now_minutes = time_info.tm_hour * 60 + time_info.tm_min; - const int begin = config->sleep_begin.hour * 60 + config->sleep_begin.min; - const int end = config->sleep_end.hour * 60 + config->sleep_end.min; - - // Normal range (e.g., 10:00–22:00): begin < end && (now_minutes >= begin && now_minutes < end) - // Overnight range (e.g., 22:00–06:00): begin > end && (now_minutes >= begin || now_minutes < end) - return (begin == end) || - (begin < end ? (now_minutes >= begin && now_minutes < end) - : (now_minutes >= begin || now_minutes < end)); + time_t raw_time; + struct tm time_info; + time(&raw_time); + localtime_r(&raw_time, &time_info); + + const int now_minutes = time_info.tm_hour * 60 + time_info.tm_min; + const int begin = config->sleep_begin.hour * 60 + config->sleep_begin.min; + const int end = config->sleep_end.hour * 60 + config->sleep_end.min; + + // Normal range (e.g., 10:00–22:00): begin < end && (now_minutes >= begin && + // now_minutes < end) Overnight range (e.g., 22:00–06:00): begin > end && + // (now_minutes >= begin || now_minutes < end) + return (begin == end) || + (begin < end ? (now_minutes >= begin && now_minutes < end) + : (now_minutes >= begin || now_minutes < end)); } static int anim_get_random_active_frame(void) { - return (rand() % 2) + 1; // Frame 1 or 2 (active frames) + return (rand() % 2) + 1; // Frame 1 or 2 (active frames) } -static void anim_trigger_frame_change(int new_frame, long duration_us, long current_time_us, - animation_state_t *state) { - if (current_config->enable_debug) { - bongocat_log_debug("Animation frame change: %d (duration: %ld us)", new_frame, duration_us); - } +static void anim_trigger_frame_change(int new_frame, long duration_us, + long current_time_us, + animation_state_t *state) { + if (current_config->enable_debug) { + bongocat_log_debug("Animation frame change: %d (duration: %ld us)", + new_frame, duration_us); + } - anim_index = new_frame; - state->hold_until = current_time_us + duration_us; + anim_index = new_frame; + state->hold_until = current_time_us + duration_us; } -static void anim_handle_test_animation(animation_state_t *state, long current_time_us) { - if (current_config->test_animation_interval <= 0) { - return; - } +static void anim_handle_test_animation(animation_state_t *state, + long current_time_us) { + if (current_config->test_animation_interval <= 0) { + return; + } - state->test_counter++; - if (state->test_counter > state->test_interval_frames) { - int new_frame = anim_get_random_active_frame(); - long duration_us = current_config->test_animation_duration * 1000; + state->test_counter++; + if (state->test_counter > state->test_interval_frames) { + int new_frame = anim_get_random_active_frame(); + long duration_us = current_config->test_animation_duration * 1000; - bongocat_log_debug("Test animation trigger"); - anim_trigger_frame_change(new_frame, duration_us, current_time_us, state); - state->test_counter = 0; - } + bongocat_log_debug("Test animation trigger"); + anim_trigger_frame_change(new_frame, duration_us, current_time_us, state); + state->test_counter = 0; + } } -static void anim_handle_key_press(animation_state_t *state, long current_time_us) { - if (!*any_key_pressed) { - return; - } +static void anim_handle_key_press(animation_state_t *state, + long current_time_us) { + if (!*any_key_pressed) { + return; + } - if (!current_config->enable_scheduled_sleep || !anim_is_sleep_time(current_config)) { - int new_frame = anim_get_random_active_frame(); - long duration_us = current_config->keypress_duration * 1000; + if (!current_config->enable_scheduled_sleep || + !anim_is_sleep_time(current_config)) { + int new_frame = anim_get_random_active_frame(); + long duration_us = current_config->keypress_duration * 1000; - bongocat_log_debug("Key press detected - switching to frame %d", new_frame); - anim_trigger_frame_change(new_frame, duration_us, current_time_us, state); + bongocat_log_debug("Key press detected - switching to frame %d", new_frame); + anim_trigger_frame_change(new_frame, duration_us, current_time_us, state); - *any_key_pressed = 0; - state->test_counter = 0; // Reset test counter - state->last_key_pressed_timestamp = current_time_us; - } + *any_key_pressed = 0; + state->test_counter = 0; // Reset test counter + state->last_key_pressed_timestamp = current_time_us; + } } -static void anim_handle_idle_return(animation_state_t *state, long current_time_us) { - int show_sleep_frame = 0; - // Sleep Mode - if (current_config->enable_scheduled_sleep) { - if (anim_is_sleep_time(current_config)) { - show_sleep_frame = 1; - } +static void anim_handle_idle_return(animation_state_t *state, + long current_time_us) { + int show_sleep_frame = 0; + // Sleep Mode + if (current_config->enable_scheduled_sleep) { + if (anim_is_sleep_time(current_config)) { + show_sleep_frame = 1; } - // Idle Sleep - if (current_config->idle_sleep_timeout_sec > 0 && state->last_key_pressed_timestamp > 0) { - if (anim_get_current_time_us() - state->last_key_pressed_timestamp >= current_config->idle_sleep_timeout_sec*1000000L) { - show_sleep_frame = 1; - } + } + // Idle Sleep + if (current_config->idle_sleep_timeout_sec > 0 && + state->last_key_pressed_timestamp > 0) { + if (anim_get_current_time_us() - state->last_key_pressed_timestamp >= + current_config->idle_sleep_timeout_sec * 1000000L) { + show_sleep_frame = 1; } + } - if (show_sleep_frame) { - if (anim_index != BONGOCAT_FRAME_BOTH_DOWN) { - bongocat_log_debug("Returning to sleep frame"); - anim_index = BONGOCAT_FRAME_BOTH_DOWN; - } - return; - } - - if (current_time_us <= state->hold_until) { - return; - } - - if (anim_index != current_config->idle_frame) { - bongocat_log_debug("Returning to idle frame %d", current_config->idle_frame); - anim_index = current_config->idle_frame; + if (show_sleep_frame) { + if (anim_index != BONGOCAT_FRAME_BOTH_DOWN) { + bongocat_log_debug("Returning to sleep frame"); + anim_index = BONGOCAT_FRAME_BOTH_DOWN; } + return; + } + + if (current_time_us <= state->hold_until) { + return; + } + + if (anim_index != current_config->idle_frame) { + bongocat_log_debug("Returning to idle frame %d", + current_config->idle_frame); + anim_index = current_config->idle_frame; + } } static void anim_update_state(animation_state_t *state) { - long current_time_us = anim_get_current_time_us(); + long current_time_us = anim_get_current_time_us(); - pthread_mutex_lock(&anim_lock); + pthread_mutex_lock(&anim_lock); - anim_handle_test_animation(state, current_time_us); - anim_handle_key_press(state, current_time_us); - anim_handle_idle_return(state, current_time_us); + anim_handle_test_animation(state, current_time_us); + anim_handle_key_press(state, current_time_us); + anim_handle_idle_return(state, current_time_us); - pthread_mutex_unlock(&anim_lock); + pthread_mutex_unlock(&anim_lock); } // ============================================================================= @@ -297,30 +326,31 @@ static void anim_update_state(animation_state_t *state) { // ============================================================================= static void anim_init_state(animation_state_t *state) { - state->hold_until = 0; - state->test_counter = 0; - state->test_interval_frames = current_config->test_animation_interval * current_config->fps; - state->frame_time_ns = 1000000000L / current_config->fps; - state->last_key_pressed_timestamp = anim_get_current_time_us(); + state->hold_until = 0; + state->test_counter = 0; + state->test_interval_frames = + current_config->test_animation_interval * current_config->fps; + state->frame_time_ns = 1000000000L / current_config->fps; + state->last_key_pressed_timestamp = anim_get_current_time_us(); } static void *anim_thread_main(void *arg __attribute__((unused))) { - animation_state_t state; - anim_init_state(&state); + animation_state_t state; + anim_init_state(&state); - struct timespec frame_delay = {0, state.frame_time_ns}; + struct timespec frame_delay = {0, state.frame_time_ns}; - animation_running = true; - bongocat_log_debug("Animation thread main loop started"); + animation_running = true; + bongocat_log_debug("Animation thread main loop started"); - while (animation_running) { - anim_update_state(&state); - draw_bar(); - nanosleep(&frame_delay, NULL); - } + while (animation_running) { + anim_update_state(&state); + draw_bar(); + nanosleep(&frame_delay, NULL); + } - bongocat_log_debug("Animation thread main loop exited"); - return NULL; + bongocat_log_debug("Animation thread main loop exited"); + return NULL; } // ============================================================================= @@ -328,47 +358,56 @@ static void *anim_thread_main(void *arg __attribute__((unused))) { // ============================================================================= typedef struct { - const unsigned char *data; - size_t size; - const char *name; + const unsigned char *data; + size_t size; + const char *name; } embedded_image_t; static embedded_image_t embedded_images[NUM_FRAMES]; static void init_embedded_images(void) { - embedded_images[BONGOCAT_FRAME_BOTH_UP] = (embedded_image_t){bongo_cat_both_up_png, bongo_cat_both_up_png_size, "embedded bongo-cat-both-up.png"}; - embedded_images[BONGOCAT_FRAME_LEFT_DOWN] = (embedded_image_t){bongo_cat_left_down_png, bongo_cat_left_down_png_size, "embedded bongo-cat-left-down.png"}; - embedded_images[BONGOCAT_FRAME_RIGHT_DOWN] = (embedded_image_t){bongo_cat_right_down_png, bongo_cat_right_down_png_size, "embedded bongo-cat-right-down.png"}; - embedded_images[BONGOCAT_FRAME_BOTH_DOWN] = (embedded_image_t){bongo_cat_both_down_png, bongo_cat_both_down_png_size, "embedded bongo-cat-both-down.png"}; + embedded_images[BONGOCAT_FRAME_BOTH_UP] = + (embedded_image_t){bongo_cat_both_up_png, bongo_cat_both_up_png_size, + "embedded bongo-cat-both-up.png"}; + embedded_images[BONGOCAT_FRAME_LEFT_DOWN] = + (embedded_image_t){bongo_cat_left_down_png, bongo_cat_left_down_png_size, + "embedded bongo-cat-left-down.png"}; + embedded_images[BONGOCAT_FRAME_RIGHT_DOWN] = (embedded_image_t){ + bongo_cat_right_down_png, bongo_cat_right_down_png_size, + "embedded bongo-cat-right-down.png"}; + embedded_images[BONGOCAT_FRAME_BOTH_DOWN] = + (embedded_image_t){bongo_cat_both_down_png, bongo_cat_both_down_png_size, + "embedded bongo-cat-both-down.png"}; } static void anim_cleanup_loaded_images(int count) { - for (int i = 0; i < count; i++) { - if (anim_imgs[i]) { - stbi_image_free(anim_imgs[i]); - anim_imgs[i] = NULL; - } + for (int i = 0; i < count; i++) { + if (anim_imgs[i]) { + stbi_image_free(anim_imgs[i]); + anim_imgs[i] = NULL; } + } } static bongocat_error_t anim_load_embedded_images(void) { - for (int i = 0; i < NUM_FRAMES; i++) { - const embedded_image_t *img = &embedded_images[i]; - - bongocat_log_debug("Loading embedded image: %s", img->name); + for (int i = 0; i < NUM_FRAMES; i++) { + const embedded_image_t *img = &embedded_images[i]; - anim_imgs[i] = stbi_load_from_memory(img->data, img->size, - &anim_width[i], &anim_height[i], NULL, 4); - if (!anim_imgs[i]) { - bongocat_log_error("Failed to load embedded image: %s", img->name); - anim_cleanup_loaded_images(i); - return BONGOCAT_ERROR_FILE_IO; - } + bongocat_log_debug("Loading embedded image: %s", img->name); - bongocat_log_debug("Loaded %dx%d embedded image", anim_width[i], anim_height[i]); + anim_imgs[i] = stbi_load_from_memory(img->data, img->size, &anim_width[i], + &anim_height[i], NULL, 4); + if (!anim_imgs[i]) { + bongocat_log_error("Failed to load embedded image: %s", img->name); + anim_cleanup_loaded_images(i); + return BONGOCAT_ERROR_FILE_IO; } - return BONGOCAT_SUCCESS; + bongocat_log_debug("Loaded %dx%d embedded image", anim_width[i], + anim_height[i]); + } + + return BONGOCAT_SUCCESS; } // ============================================================================= @@ -376,55 +415,55 @@ static bongocat_error_t anim_load_embedded_images(void) { // ============================================================================= bongocat_error_t animation_init(config_t *config) { - BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); - current_config = config; - bongocat_log_info("Initializing animation system"); + current_config = config; + bongocat_log_info("Initializing animation system"); - // Initialize embedded images data - init_embedded_images(); + // Initialize embedded images data + init_embedded_images(); - bongocat_error_t result = anim_load_embedded_images(); - if (result != BONGOCAT_SUCCESS) { - return result; - } + bongocat_error_t result = anim_load_embedded_images(); + if (result != BONGOCAT_SUCCESS) { + return result; + } - bongocat_log_info("Animation system initialized successfully with embedded assets"); - return BONGOCAT_SUCCESS; + bongocat_log_info( + "Animation system initialized successfully with embedded assets"); + return BONGOCAT_SUCCESS; } bongocat_error_t animation_start(void) { - bongocat_log_info("Starting animation thread"); + bongocat_log_info("Starting animation thread"); - int result = pthread_create(&anim_thread, NULL, anim_thread_main, NULL); - if (result != 0) { - bongocat_log_error("Failed to create animation thread: %s", strerror(result)); - return BONGOCAT_ERROR_THREAD; - } + int result = pthread_create(&anim_thread, NULL, anim_thread_main, NULL); + if (result != 0) { + bongocat_log_error("Failed to create animation thread: %s", + strerror(result)); + return BONGOCAT_ERROR_THREAD; + } - bongocat_log_debug("Animation thread started successfully"); - return BONGOCAT_SUCCESS; + bongocat_log_debug("Animation thread started successfully"); + return BONGOCAT_SUCCESS; } void animation_cleanup(void) { - if (animation_running) { - bongocat_log_debug("Stopping animation thread"); - animation_running = false; + if (animation_running) { + bongocat_log_debug("Stopping animation thread"); + animation_running = false; - // Wait for thread to finish gracefully - pthread_join(anim_thread, NULL); - bongocat_log_debug("Animation thread stopped"); - } + // Wait for thread to finish gracefully + pthread_join(anim_thread, NULL); + bongocat_log_debug("Animation thread stopped"); + } - // Cleanup loaded images - anim_cleanup_loaded_images(NUM_FRAMES); + // Cleanup loaded images + anim_cleanup_loaded_images(NUM_FRAMES); - // Cleanup mutex - pthread_mutex_destroy(&anim_lock); + // Cleanup mutex + pthread_mutex_destroy(&anim_lock); - bongocat_log_debug("Animation cleanup complete"); + bongocat_log_debug("Animation cleanup complete"); } -void animation_trigger(void) { - *any_key_pressed = 1; -} \ No newline at end of file +void animation_trigger(void) { *any_key_pressed = 1; } \ No newline at end of file diff --git a/src/platform/input.c b/src/platform/input.c index c0ec7002..d086f81b 100644 --- a/src/platform/input.c +++ b/src/platform/input.c @@ -3,395 +3,432 @@ #include "platform/input.h" #include "graphics/animation.h" #include "utils/memory.h" +#include +#include #include -#include #include -#include +#include #include -#include int *any_key_pressed; static pid_t input_child_pid = -1; // Child process signal handler - exits quietly without logging static void child_signal_handler(int sig) { - (void)sig; // Suppress unused parameter warning - exit(0); + (void)sig; // Suppress unused parameter warning + exit(0); } -static void capture_input_multiple(char **device_paths, int num_devices, int enable_debug) { - // Set up child-specific signal handlers to avoid duplicate logging - struct sigaction sa; - sa.sa_handler = child_signal_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - - bongocat_log_debug("Starting input capture on %d devices", num_devices); - - int *fds = BONGOCAT_MALLOC(num_devices * sizeof(int)); - char **unique_paths = BONGOCAT_MALLOC(num_devices * sizeof(char*)); - if (!fds || !unique_paths) { - bongocat_log_error("Failed to allocate memory for file descriptors"); - exit(1); +static void capture_input_multiple(char **device_paths, int num_devices, + int enable_debug) { + // Set up child-specific signal handlers to avoid duplicate logging + struct sigaction sa; + sa.sa_handler = child_signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGTERM, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + bongocat_log_debug("Starting input capture on %d devices", num_devices); + + int *fds = BONGOCAT_MALLOC(num_devices * sizeof(int)); + char **unique_paths = BONGOCAT_MALLOC(num_devices * sizeof(char *)); + if (!fds || !unique_paths) { + bongocat_log_error("Failed to allocate memory for file descriptors"); + exit(1); + } + + int max_fd = -1; + int valid_devices = 0; + int unique_devices = 0; + + // First pass: deduplicate device paths + for (int i = 0; i < num_devices; i++) { + bool is_duplicate = false; + for (int j = 0; j < unique_devices; j++) { + if (strcmp(device_paths[i], unique_paths[j]) == 0) { + is_duplicate = true; + break; + } } - - int max_fd = -1; - int valid_devices = 0; - int unique_devices = 0; - - // First pass: deduplicate device paths - for (int i = 0; i < num_devices; i++) { - bool is_duplicate = false; - for (int j = 0; j < unique_devices; j++) { - if (strcmp(device_paths[i], unique_paths[j]) == 0) { - is_duplicate = true; - break; - } - } - if (!is_duplicate) { - unique_paths[unique_devices] = device_paths[i]; - unique_devices++; - } + if (!is_duplicate) { + unique_paths[unique_devices] = device_paths[i]; + unique_devices++; } - - bongocat_log_debug("Deduplicated %d devices to %d unique devices", num_devices, unique_devices); - - // Open all unique devices - for (int i = 0; i < unique_devices; i++) { - fds[i] = -1; - - // Validate device path exists and is readable - struct stat st; - if (stat(unique_paths[i], &st) != 0) { - bongocat_log_warning("Input device does not exist: %s", unique_paths[i]); - continue; - } - - if (!S_ISCHR(st.st_mode)) { - bongocat_log_warning("Input device is not a character device: %s", unique_paths[i]); - continue; - } - - fds[i] = open(unique_paths[i], O_RDONLY | O_NONBLOCK); - if (fds[i] < 0) { - bongocat_log_warning("Failed to open %s: %s", unique_paths[i], strerror(errno)); - continue; - } - - bongocat_log_info("Input monitoring started on %s (fd=%d)", unique_paths[i], fds[i]); - if (fds[i] > max_fd) { - max_fd = fds[i]; + } + + bongocat_log_debug("Deduplicated %d devices to %d unique devices", + num_devices, unique_devices); + + // Open all unique devices + for (int i = 0; i < unique_devices; i++) { + fds[i] = -1; + + // Validate device path exists and is readable + struct stat st; + if (stat(unique_paths[i], &st) != 0) { + bongocat_log_warning("Input device does not exist: %s", unique_paths[i]); + continue; + } + + if (!S_ISCHR(st.st_mode)) { + bongocat_log_warning("Input device is not a character device: %s", + unique_paths[i]); + continue; + } + + fds[i] = open(unique_paths[i], O_RDONLY | O_NONBLOCK); + if (fds[i] < 0) { + bongocat_log_warning("Failed to open %s: %s", unique_paths[i], + strerror(errno)); + continue; + } + + bongocat_log_info("Input monitoring started on %s (fd=%d)", unique_paths[i], + fds[i]); + if (fds[i] > max_fd) { + max_fd = fds[i]; + } + valid_devices++; + } + + // Update num_devices to reflect unique devices for the rest of the function + num_devices = unique_devices; + + if (valid_devices == 0) { + bongocat_log_error("No valid input devices found"); + BONGOCAT_SAFE_FREE(fds); + exit(1); + } + + bongocat_log_info("Successfully opened %d/%d input devices", valid_devices, + num_devices); + + struct input_event ev[128]; // Increased buffer size for better I/O efficiency + int rd; + fd_set readfds; + struct timeval timeout; + int check_counter = 0; + int adaptive_check_interval = 5; // Start with 5 seconds, can increase to 30 + + while (1) { + FD_ZERO(&readfds); + + // Optimize: only rebuild fd_set when devices change, track current max_fd + int current_max_fd = -1; + for (int i = 0; i < num_devices; i++) { + if (fds[i] >= 0) { + FD_SET(fds[i], &readfds); + if (fds[i] > current_max_fd) { + current_max_fd = fds[i]; } - valid_devices++; + } } - - // Update num_devices to reflect unique devices for the rest of the function - num_devices = unique_devices; - - if (valid_devices == 0) { - bongocat_log_error("No valid input devices found"); - BONGOCAT_SAFE_FREE(fds); - exit(1); + max_fd = current_max_fd; + + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + int select_result = select(max_fd + 1, &readfds, NULL, NULL, &timeout); + if (select_result < 0) { + if (errno == EINTR) + continue; // Interrupted by signal + bongocat_log_error("Select error: %s", strerror(errno)); + break; } - - bongocat_log_info("Successfully opened %d/%d input devices", valid_devices, num_devices); - - struct input_event ev[128]; // Increased buffer size for better I/O efficiency - int rd; - fd_set readfds; - struct timeval timeout; - int check_counter = 0; - int adaptive_check_interval = 5; // Start with 5 seconds, can increase to 30 - - while (1) { - FD_ZERO(&readfds); - - // Optimize: only rebuild fd_set when devices change, track current max_fd - int current_max_fd = -1; + + if (select_result == 0) { + // Adaptive device checking - start at 5 seconds, increase to 30 if no new + // devices found + check_counter++; + if (check_counter >= adaptive_check_interval) { + check_counter = 0; + bool found_new_device = false; + + // Check for devices that have become available for (int i = 0; i < num_devices; i++) { - if (fds[i] >= 0) { - FD_SET(fds[i], &readfds); - if (fds[i] > current_max_fd) { - current_max_fd = fds[i]; + if (fds[i] < 0) { // Device was not available before + struct stat st; + if (stat(unique_paths[i], &st) == 0 && S_ISCHR(st.st_mode)) { + // Device is now available, try to open it + int new_fd = open(unique_paths[i], O_RDONLY | O_NONBLOCK); + if (new_fd >= 0) { + fds[i] = new_fd; + if (new_fd > max_fd) { + max_fd = new_fd; } + valid_devices++; + found_new_device = true; + bongocat_log_info( + "New input device detected and opened: %s (fd=%d)", + unique_paths[i], new_fd); + } } + } } - max_fd = current_max_fd; - - timeout.tv_sec = 1; - timeout.tv_usec = 0; - - int select_result = select(max_fd + 1, &readfds, NULL, NULL, &timeout); - if (select_result < 0) { - if (errno == EINTR) continue; // Interrupted by signal - bongocat_log_error("Select error: %s", strerror(errno)); - break; + + // Adaptive interval: if no new devices found, increase check interval + // up to 30 seconds + if (!found_new_device && adaptive_check_interval < 30) { + adaptive_check_interval = (adaptive_check_interval < 15) ? 15 : 30; + bongocat_log_debug("Increased device check interval to %d seconds", + adaptive_check_interval); + } else if (found_new_device && adaptive_check_interval > 5) { + // Reset to frequent checking when devices are being connected + adaptive_check_interval = 5; + bongocat_log_debug("Reset device check interval to 5 seconds"); } - - if (select_result == 0) { - // Adaptive device checking - start at 5 seconds, increase to 30 if no new devices found - check_counter++; - if (check_counter >= adaptive_check_interval) { - check_counter = 0; - bool found_new_device = false; - - // Check for devices that have become available - for (int i = 0; i < num_devices; i++) { - if (fds[i] < 0) { // Device was not available before - struct stat st; - if (stat(unique_paths[i], &st) == 0 && S_ISCHR(st.st_mode)) { - // Device is now available, try to open it - int new_fd = open(unique_paths[i], O_RDONLY | O_NONBLOCK); - if (new_fd >= 0) { - fds[i] = new_fd; - if (new_fd > max_fd) { - max_fd = new_fd; - } - valid_devices++; - found_new_device = true; - bongocat_log_info("New input device detected and opened: %s (fd=%d)", unique_paths[i], new_fd); - } - } - } - } - - // Adaptive interval: if no new devices found, increase check interval up to 30 seconds - if (!found_new_device && adaptive_check_interval < 30) { - adaptive_check_interval = (adaptive_check_interval < 15) ? 15 : 30; - bongocat_log_debug("Increased device check interval to %d seconds", adaptive_check_interval); - } else if (found_new_device && adaptive_check_interval > 5) { - // Reset to frequent checking when devices are being connected - adaptive_check_interval = 5; - bongocat_log_debug("Reset device check interval to 5 seconds"); - } - } - continue; // Continue to next iteration + } + continue; // Continue to next iteration + } + + // Check which devices have data + for (int i = 0; i < num_devices; i++) { + if (fds[i] >= 0 && FD_ISSET(fds[i], &readfds)) { + rd = read(fds[i], ev, sizeof(ev)); + if (rd < 0) { + if (errno == EAGAIN) + continue; + bongocat_log_warning("Read error on %s: %s", unique_paths[i], + strerror(errno)); + close(fds[i]); + fds[i] = -1; + valid_devices--; + continue; } - - // Check which devices have data - for (int i = 0; i < num_devices; i++) { - if (fds[i] >= 0 && FD_ISSET(fds[i], &readfds)) { - rd = read(fds[i], ev, sizeof(ev)); - if (rd < 0) { - if (errno == EAGAIN) continue; - bongocat_log_warning("Read error on %s: %s", unique_paths[i], strerror(errno)); - close(fds[i]); - fds[i] = -1; - valid_devices--; - continue; - } - - if (rd == 0) { - bongocat_log_warning("EOF on input device %s", unique_paths[i]); - close(fds[i]); - fds[i] = -1; - valid_devices--; - continue; - } - // Batch process events for better performance - int num_events = rd / sizeof(struct input_event); - bool key_pressed = false; - - for (int j = 0; j < num_events; j++) { - if (ev[j].type == EV_KEY && ev[j].value == 1) { - key_pressed = true; - if (enable_debug) { - bongocat_log_debug("Key event: device=%s, code=%d, time=%ld.%06ld", - unique_paths[i], ev[j].code, ev[j].time.tv_sec, ev[j].time.tv_usec); - } - } - } - - // Trigger animation only once per batch to reduce overhead - if (key_pressed) { - animation_trigger(); - } - } + if (rd == 0) { + bongocat_log_warning("EOF on input device %s", unique_paths[i]); + close(fds[i]); + fds[i] = -1; + valid_devices--; + continue; } - - // Exit if no valid devices remain - if (valid_devices == 0) { - bongocat_log_error("All input devices became unavailable"); - break; + + // Batch process events for better performance + int num_events = rd / sizeof(struct input_event); + bool key_pressed = false; + + for (int j = 0; j < num_events; j++) { + if (ev[j].type == EV_KEY && ev[j].value == 1) { + key_pressed = true; + if (enable_debug) { + bongocat_log_debug( + "Key event: device=%s, code=%d, time=%ld.%06ld", + unique_paths[i], ev[j].code, ev[j].time.tv_sec, + ev[j].time.tv_usec); + } + } } - } - - // Close all file descriptors - for (int i = 0; i < num_devices; i++) { - if (fds[i] >= 0) { - close(fds[i]); + + // Trigger animation only once per batch to reduce overhead + if (key_pressed) { + animation_trigger(); } + } } - - BONGOCAT_SAFE_FREE(fds); - BONGOCAT_SAFE_FREE(unique_paths); - bongocat_log_info("Input monitoring stopped"); -} -bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, int enable_debug) { - BONGOCAT_CHECK_NULL(device_paths, BONGOCAT_ERROR_INVALID_PARAM); - - if (num_devices <= 0) { - bongocat_log_error("No input devices specified"); - return BONGOCAT_ERROR_INVALID_PARAM; - } - - bongocat_log_info("Initializing input monitoring system for %d devices", num_devices); - - // Initialize shared memory for key press flag - any_key_pressed = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, -1, 0); - if (any_key_pressed == MAP_FAILED) { - bongocat_log_error("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return BONGOCAT_ERROR_MEMORY; + // Exit if no valid devices remain + if (valid_devices == 0) { + bongocat_log_error("All input devices became unavailable"); + break; } - *any_key_pressed = 0; + } - // Fork process for input monitoring - input_child_pid = fork(); - if (input_child_pid < 0) { - bongocat_log_error("Failed to fork input monitoring process: %s", strerror(errno)); - munmap(any_key_pressed, sizeof(int)); - any_key_pressed = NULL; - return BONGOCAT_ERROR_THREAD; - } - - if (input_child_pid == 0) { - // Child process - handle keyboard input from multiple devices - bongocat_log_debug("Input monitoring child process started (PID: %d)", getpid()); - capture_input_multiple(device_paths, num_devices, enable_debug); - exit(0); + // Close all file descriptors + for (int i = 0; i < num_devices; i++) { + if (fds[i] >= 0) { + close(fds[i]); } - - bongocat_log_info("Input monitoring started (child PID: %d)", input_child_pid); - return BONGOCAT_SUCCESS; + } + + BONGOCAT_SAFE_FREE(fds); + BONGOCAT_SAFE_FREE(unique_paths); + bongocat_log_info("Input monitoring stopped"); } -bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, int enable_debug) { - bongocat_log_info("Restarting input monitoring system"); - - // Stop current monitoring - if (input_child_pid > 0) { - bongocat_log_debug("Stopping current input monitoring (PID: %d)", input_child_pid); - kill(input_child_pid, SIGTERM); - - // Wait for child to terminate with timeout - int status; - int wait_attempts = 0; - while (wait_attempts < 10) { - pid_t result = waitpid(input_child_pid, &status, WNOHANG); - if (result == input_child_pid) { - bongocat_log_debug("Previous input monitoring process terminated"); - break; - } else if (result == -1) { - if (errno == ECHILD) { - // Child already reaped by signal handler - this is normal - bongocat_log_debug("Input child process already cleaned up by signal handler"); - break; - } else { - bongocat_log_warning("Error waiting for input child process: %s", strerror(errno)); - break; - } - } - - usleep(100000); // Wait 100ms - wait_attempts++; - } - - // Force kill if still running - if (wait_attempts >= 10) { - bongocat_log_warning("Force killing previous input monitoring process"); - kill(input_child_pid, SIGKILL); - waitpid(input_child_pid, &status, 0); +bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, + int enable_debug) { + BONGOCAT_CHECK_NULL(device_paths, BONGOCAT_ERROR_INVALID_PARAM); + + if (num_devices <= 0) { + bongocat_log_error("No input devices specified"); + return BONGOCAT_ERROR_INVALID_PARAM; + } + + bongocat_log_info("Initializing input monitoring system for %d devices", + num_devices); + + // Initialize shared memory for key press flag + any_key_pressed = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (any_key_pressed == MAP_FAILED) { + bongocat_log_error( + "Failed to create shared memory for input monitoring: %s", + strerror(errno)); + return BONGOCAT_ERROR_MEMORY; + } + *any_key_pressed = 0; + + // Fork process for input monitoring + input_child_pid = fork(); + if (input_child_pid < 0) { + bongocat_log_error("Failed to fork input monitoring process: %s", + strerror(errno)); + munmap(any_key_pressed, sizeof(int)); + any_key_pressed = NULL; + return BONGOCAT_ERROR_THREAD; + } + + if (input_child_pid == 0) { + // Child process - handle keyboard input from multiple devices + bongocat_log_debug("Input monitoring child process started (PID: %d)", + getpid()); + capture_input_multiple(device_paths, num_devices, enable_debug); + exit(0); + } + + bongocat_log_info("Input monitoring started (child PID: %d)", + input_child_pid); + return BONGOCAT_SUCCESS; +} + +bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, + int enable_debug) { + bongocat_log_info("Restarting input monitoring system"); + + // Stop current monitoring + if (input_child_pid > 0) { + bongocat_log_debug("Stopping current input monitoring (PID: %d)", + input_child_pid); + kill(input_child_pid, SIGTERM); + + // Wait for child to terminate with timeout + int status; + int wait_attempts = 0; + while (wait_attempts < 10) { + pid_t result = waitpid(input_child_pid, &status, WNOHANG); + if (result == input_child_pid) { + bongocat_log_debug("Previous input monitoring process terminated"); + break; + } else if (result == -1) { + if (errno == ECHILD) { + // Child already reaped by signal handler - this is normal + bongocat_log_debug( + "Input child process already cleaned up by signal handler"); + break; + } else { + bongocat_log_warning("Error waiting for input child process: %s", + strerror(errno)); + break; } - - input_child_pid = -1; + } + + usleep(100000); // Wait 100ms + wait_attempts++; } - - // Start new monitoring (reuse shared memory if it exists) - bool need_new_shm = (any_key_pressed == NULL || any_key_pressed == MAP_FAILED); - - if (need_new_shm) { - any_key_pressed = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, -1, 0); - if (any_key_pressed == MAP_FAILED) { - bongocat_log_error("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return BONGOCAT_ERROR_MEMORY; - } - *any_key_pressed = 0; + + // Force kill if still running + if (wait_attempts >= 10) { + bongocat_log_warning("Force killing previous input monitoring process"); + kill(input_child_pid, SIGKILL); + waitpid(input_child_pid, &status, 0); } - // Fork new process for input monitoring - input_child_pid = fork(); - if (input_child_pid < 0) { - bongocat_log_error("Failed to fork input monitoring process: %s", strerror(errno)); - if (need_new_shm) { - munmap(any_key_pressed, sizeof(int)); - any_key_pressed = NULL; - } - return BONGOCAT_ERROR_THREAD; + input_child_pid = -1; + } + + // Start new monitoring (reuse shared memory if it exists) + bool need_new_shm = + (any_key_pressed == NULL || any_key_pressed == MAP_FAILED); + + if (need_new_shm) { + any_key_pressed = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (any_key_pressed == MAP_FAILED) { + bongocat_log_error( + "Failed to create shared memory for input monitoring: %s", + strerror(errno)); + return BONGOCAT_ERROR_MEMORY; } - - if (input_child_pid == 0) { - // Child process - handle keyboard input from multiple devices - bongocat_log_debug("Input monitoring child process restarted (PID: %d)", getpid()); - capture_input_multiple(device_paths, num_devices, enable_debug); - exit(0); + *any_key_pressed = 0; + } + + // Fork new process for input monitoring + input_child_pid = fork(); + if (input_child_pid < 0) { + bongocat_log_error("Failed to fork input monitoring process: %s", + strerror(errno)); + if (need_new_shm) { + munmap(any_key_pressed, sizeof(int)); + any_key_pressed = NULL; } - - bongocat_log_info("Input monitoring restarted (child PID: %d)", input_child_pid); - return BONGOCAT_SUCCESS; + return BONGOCAT_ERROR_THREAD; + } + + if (input_child_pid == 0) { + // Child process - handle keyboard input from multiple devices + bongocat_log_debug("Input monitoring child process restarted (PID: %d)", + getpid()); + capture_input_multiple(device_paths, num_devices, enable_debug); + exit(0); + } + + bongocat_log_info("Input monitoring restarted (child PID: %d)", + input_child_pid); + return BONGOCAT_SUCCESS; } void input_cleanup(void) { - bongocat_log_info("Cleaning up input monitoring system"); - - // Terminate child process if it exists - if (input_child_pid > 0) { - bongocat_log_debug("Terminating input monitoring child process (PID: %d)", input_child_pid); - kill(input_child_pid, SIGTERM); - - // Wait for child to terminate with timeout - int status; - int wait_attempts = 0; - while (wait_attempts < 10) { - pid_t result = waitpid(input_child_pid, &status, WNOHANG); - if (result == input_child_pid) { - bongocat_log_debug("Input monitoring child process terminated gracefully"); - break; - } else if (result == -1) { - if (errno == ECHILD) { - // Child already reaped by signal handler - this is normal - bongocat_log_debug("Input child process already cleaned up by signal handler"); - break; - } else { - bongocat_log_warning("Error waiting for input child process: %s", strerror(errno)); - break; - } - } - - usleep(100000); // Wait 100ms - wait_attempts++; - } - - // Force kill if still running - if (wait_attempts >= 10) { - bongocat_log_warning("Force killing input monitoring child process"); - kill(input_child_pid, SIGKILL); - waitpid(input_child_pid, &status, 0); + bongocat_log_info("Cleaning up input monitoring system"); + + // Terminate child process if it exists + if (input_child_pid > 0) { + bongocat_log_debug("Terminating input monitoring child process (PID: %d)", + input_child_pid); + kill(input_child_pid, SIGTERM); + + // Wait for child to terminate with timeout + int status; + int wait_attempts = 0; + while (wait_attempts < 10) { + pid_t result = waitpid(input_child_pid, &status, WNOHANG); + if (result == input_child_pid) { + bongocat_log_debug( + "Input monitoring child process terminated gracefully"); + break; + } else if (result == -1) { + if (errno == ECHILD) { + // Child already reaped by signal handler - this is normal + bongocat_log_debug( + "Input child process already cleaned up by signal handler"); + break; + } else { + bongocat_log_warning("Error waiting for input child process: %s", + strerror(errno)); + break; } - - input_child_pid = -1; + } + + usleep(100000); // Wait 100ms + wait_attempts++; } - - // Cleanup shared memory - if (any_key_pressed && any_key_pressed != MAP_FAILED) { - munmap(any_key_pressed, sizeof(int)); - any_key_pressed = NULL; + + // Force kill if still running + if (wait_attempts >= 10) { + bongocat_log_warning("Force killing input monitoring child process"); + kill(input_child_pid, SIGKILL); + waitpid(input_child_pid, &status, 0); } - - bongocat_log_debug("Input monitoring cleanup complete"); + + input_child_pid = -1; + } + + // Cleanup shared memory + if (any_key_pressed && any_key_pressed != MAP_FAILED) { + munmap(any_key_pressed, sizeof(int)); + any_key_pressed = NULL; + } + + bongocat_log_debug("Input monitoring cleanup complete"); } \ No newline at end of file diff --git a/src/platform/wayland.c b/src/platform/wayland.c index 98092f0d..e67e6944 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -1,10 +1,10 @@ #define _POSIX_C_SOURCE 200809L #include "platform/wayland.h" +#include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" +#include "../protocols/xdg-output-unstable-v1-client-protocol.h" #include "graphics/animation.h" #include #include -#include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" -#include "../protocols/xdg-output-unstable-v1-client-protocol.h" // ============================================================================= // GLOBAL STATE AND CONFIGURATION @@ -31,13 +31,13 @@ static config_t *current_config; // ============================================================================= typedef struct { - int screen_width; - int screen_height; - int transform; - int raw_width; - int raw_height; - bool mode_received; - bool geometry_received; + int screen_width; + int screen_height; + int transform; + int raw_width; + int raw_height; + bool mode_received; + bool geometry_received; } screen_info_t; static screen_info_t screen_info = {0}; @@ -49,22 +49,30 @@ static struct zxdg_output_manager_v1 *xdg_output_manager = NULL; // ZXDG LISTENER IMPLEMENTATION // ============================================================================= -static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *xdg_output __attribute__((unused)), +static void handle_xdg_output_name(void *data, + struct zxdg_output_v1 *xdg_output + __attribute__((unused)), const char *name) { - output_ref_t *oref = data; - snprintf(oref->name_str, sizeof(oref->name_str), "%s", name); - oref->name_received = true; - bongocat_log_debug("xdg-output name received: %s", name); + output_ref_t *oref = data; + snprintf(oref->name_str, sizeof(oref->name_str), "%s", name); + oref->name_received = true; + bongocat_log_debug("xdg-output name received: %s", name); } -static void handle_xdg_output_logical_position(void *data, struct zxdg_output_v1 *xdg_output, - int32_t x, int32_t y) {} -static void handle_xdg_output_logical_size(void *data, struct zxdg_output_v1 *xdg_output, +static void handle_xdg_output_logical_position( + void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {} +static void handle_xdg_output_logical_size(void *data, + struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {} -static void handle_xdg_output_done(void *data, struct zxdg_output_v1 *xdg_output) {} +static void handle_xdg_output_done(void *data, + struct zxdg_output_v1 *xdg_output) {} -static void handle_xdg_output_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { - (void)data; (void)xdg_output; (void)description; +static void handle_xdg_output_description(void *data, + struct zxdg_output_v1 *xdg_output, + const char *description) { + (void)data; + (void)xdg_output; + (void)description; } static const struct zxdg_output_v1_listener xdg_output_listener = { @@ -72,17 +80,17 @@ static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_size = handle_xdg_output_logical_size, .done = handle_xdg_output_done, .name = handle_xdg_output_name, - .description = handle_xdg_output_description -}; + .description = handle_xdg_output_description}; // ============================================================================= // FULLSCREEN DETECTION MODULE // ============================================================================= typedef struct { - struct zwlr_foreign_toplevel_manager_v1 *manager; - bool has_fullscreen_toplevel; - struct timeval last_check; + struct zwlr_foreign_toplevel_manager_v1 *manager; + bool has_fullscreen_toplevel; + int fullscreen_toplevel_count; // Track number of fullscreen toplevels + struct timeval last_check; } fullscreen_detector_t; static fullscreen_detector_t fs_detector = {0}; @@ -92,150 +100,226 @@ static fullscreen_detector_t fs_detector = {0}; // ============================================================================= static void fs_update_state(bool new_state) { - if (new_state != fs_detector.has_fullscreen_toplevel) { - fs_detector.has_fullscreen_toplevel = new_state; - fullscreen_detected = new_state; - - bongocat_log_info("Fullscreen state changed: %s", - fullscreen_detected ? "detected" : "cleared"); - - if (configured) { - draw_bar(); - } - } -} - -static bool fs_check_compositor_fallback(void) { - bongocat_log_debug("Using compositor-specific fullscreen detection"); - - // Try Hyprland first - FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); - if (fp) { - char line[512]; - bool is_fullscreen = false; - - while (fgets(line, sizeof(line), fp)) { - size_t len = strlen(line); - if (len > 0 && line[len-1] == '\n') { - line[len-1] = '\0'; - } - - if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || - strstr(line, "fullscreen: true")) { - is_fullscreen = true; - bongocat_log_debug("Fullscreen detected in Hyprland"); - break; - } - } - pclose(fp); - return is_fullscreen; - } - - // Try Sway as fallback - fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); - if (fp) { - char sway_buffer[4096]; - bool is_fullscreen = false; - - while (fgets(sway_buffer, sizeof(sway_buffer), fp)) { - if (strstr(sway_buffer, "\"fullscreen_mode\":1")) { - is_fullscreen = true; - bongocat_log_debug("Fullscreen detected in Sway"); - break; - } - } - pclose(fp); - return is_fullscreen; - } - - bongocat_log_debug("No supported compositor found for fullscreen detection"); - return false; -} + if (new_state != fs_detector.has_fullscreen_toplevel) { + fs_detector.has_fullscreen_toplevel = new_state; + fullscreen_detected = new_state; -static bool fs_check_status(void) { - if (fs_detector.manager) { - return fs_detector.has_fullscreen_toplevel; - } - return fs_check_compositor_fallback(); -} + bongocat_log_info("Fullscreen state changed: %s", + fullscreen_detected ? "detected" : "cleared"); -// Foreign toplevel protocol event handlers -static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct wl_array *state) { - (void)data; (void)handle; - - bool is_fullscreen = false; - uint32_t *state_ptr; - - wl_array_for_each(state_ptr, state) { - if (*state_ptr == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { - is_fullscreen = true; - break; - } + if (configured) { + draw_bar(); } - - fs_update_state(is_fullscreen); + } } -static void fs_handle_toplevel_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { - (void)data; - zwlr_foreign_toplevel_handle_v1_destroy(handle); -} +static bool fs_check_compositor_fallback(void) { + bongocat_log_debug("Using compositor-specific fullscreen detection"); -// Minimal event handlers for unused events -static void fs_handle_title(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *title) { - (void)data; (void)handle; (void)title; -} + // Try Hyprland first + FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); + if (fp) { + char line[512]; + bool is_fullscreen = false; -static void fs_handle_app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id) { - (void)data; (void)handle; (void)app_id; -} + while (fgets(line, sizeof(line), fp)) { + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } + + if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || + strstr(line, "fullscreen: true")) { + is_fullscreen = true; + bongocat_log_debug("Fullscreen detected in Hyprland"); + break; + } + } + pclose(fp); + return is_fullscreen; + } + + // Try Sway as fallback + fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); + if (fp) { + char sway_buffer[4096]; + bool is_fullscreen = false; -static void fs_handle_output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { - (void)data; (void)handle; (void)output; -} + while (fgets(sway_buffer, sizeof(sway_buffer), fp)) { + if (strstr(sway_buffer, "\"fullscreen_mode\":1")) { + is_fullscreen = true; + bongocat_log_debug("Fullscreen detected in Sway"); + break; + } + } + pclose(fp); + return is_fullscreen; + } -static void fs_handle_output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_output *output) { - (void)data; (void)handle; (void)output; + bongocat_log_debug("No supported compositor found for fullscreen detection"); + return false; } -static void fs_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { - (void)data; (void)handle; +static bool fs_check_status(void) { + if (fs_detector.manager) { + return fs_detector.has_fullscreen_toplevel; + } + return fs_check_compositor_fallback(); } -static void fs_handle_parent(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct zwlr_foreign_toplevel_handle_v1 *parent) { - (void)data; (void)handle; (void)parent; +// Foreign toplevel protocol event handlers +// Each toplevel tracks its own fullscreen state via user data +typedef struct { + bool is_fullscreen; +} toplevel_data_t; + +static void +fs_handle_toplevel_state(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_array *state) { + toplevel_data_t *toplevel_data = (toplevel_data_t *)data; + if (!toplevel_data) + return; + + bool was_fullscreen = toplevel_data->is_fullscreen; + bool is_fullscreen = false; + uint32_t *state_ptr; + + wl_array_for_each(state_ptr, state) { + if (*state_ptr == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { + is_fullscreen = true; + break; + } + } + + // Update count if state changed + if (is_fullscreen && !was_fullscreen) { + fs_detector.fullscreen_toplevel_count++; + } else if (!is_fullscreen && was_fullscreen) { + fs_detector.fullscreen_toplevel_count--; + if (fs_detector.fullscreen_toplevel_count < 0) { + fs_detector.fullscreen_toplevel_count = 0; + } + } + toplevel_data->is_fullscreen = is_fullscreen; + + // Update global state based on count + bool new_state = (fs_detector.fullscreen_toplevel_count > 0); + fs_update_state(new_state); +} + +static void +fs_handle_toplevel_closed(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle) { + toplevel_data_t *toplevel_data = (toplevel_data_t *)data; + if (toplevel_data) { + // Decrement count if this toplevel was fullscreen + if (toplevel_data->is_fullscreen) { + fs_detector.fullscreen_toplevel_count--; + if (fs_detector.fullscreen_toplevel_count < 0) { + fs_detector.fullscreen_toplevel_count = 0; + } + bool new_state = (fs_detector.fullscreen_toplevel_count > 0); + fs_update_state(new_state); + } + free(toplevel_data); + } + zwlr_foreign_toplevel_handle_v1_destroy(handle); } -static const struct zwlr_foreign_toplevel_handle_v1_listener fs_toplevel_listener = { - .title = fs_handle_title, - .app_id = fs_handle_app_id, - .output_enter = fs_handle_output_enter, - .output_leave = fs_handle_output_leave, - .state = fs_handle_toplevel_state, - .done = fs_handle_done, - .closed = fs_handle_toplevel_closed, - .parent = fs_handle_parent, +// Minimal event handlers for unused events +static void fs_handle_title(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + const char *title) { + (void)data; + (void)handle; + (void)title; +} + +static void fs_handle_app_id(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + const char *app_id) { + (void)data; + (void)handle; + (void)app_id; +} + +static void +fs_handle_output_enter(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_output *output) { + (void)data; + (void)handle; + (void)output; +} + +static void +fs_handle_output_leave(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct wl_output *output) { + (void)data; + (void)handle; + (void)output; +} + +static void fs_handle_done(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle) { + (void)data; + (void)handle; +} + +static void fs_handle_parent(void *data, + struct zwlr_foreign_toplevel_handle_v1 *handle, + struct zwlr_foreign_toplevel_handle_v1 *parent) { + (void)data; + (void)handle; + (void)parent; +} + +static const struct zwlr_foreign_toplevel_handle_v1_listener + fs_toplevel_listener = { + .title = fs_handle_title, + .app_id = fs_handle_app_id, + .output_enter = fs_handle_output_enter, + .output_leave = fs_handle_output_leave, + .state = fs_handle_toplevel_state, + .done = fs_handle_done, + .closed = fs_handle_toplevel_closed, + .parent = fs_handle_parent, }; -static void fs_handle_manager_toplevel(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager, - struct zwlr_foreign_toplevel_handle_v1 *toplevel) { - (void)data; (void)manager; - - zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &fs_toplevel_listener, NULL); - bongocat_log_debug("New toplevel registered for fullscreen monitoring"); -} - -static void fs_handle_manager_finished(void *data, struct zwlr_foreign_toplevel_manager_v1 *manager) { - (void)data; - bongocat_log_info("Foreign toplevel manager finished"); - zwlr_foreign_toplevel_manager_v1_destroy(manager); - fs_detector.manager = NULL; -} - -static const struct zwlr_foreign_toplevel_manager_v1_listener fs_manager_listener = { - .toplevel = fs_handle_manager_toplevel, - .finished = fs_handle_manager_finished, +static void +fs_handle_manager_toplevel(void *data, + struct zwlr_foreign_toplevel_manager_v1 *manager, + struct zwlr_foreign_toplevel_handle_v1 *toplevel) { + (void)data; + (void)manager; + + // Allocate per-toplevel data to track its fullscreen state + toplevel_data_t *toplevel_data = calloc(1, sizeof(toplevel_data_t)); + if (!toplevel_data) { + bongocat_log_error("Failed to allocate toplevel data"); + return; + } + + zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &fs_toplevel_listener, + toplevel_data); + bongocat_log_debug("New toplevel registered for fullscreen monitoring"); +} + +static void +fs_handle_manager_finished(void *data, + struct zwlr_foreign_toplevel_manager_v1 *manager) { + (void)data; + bongocat_log_info("Foreign toplevel manager finished"); + zwlr_foreign_toplevel_manager_v1_destroy(manager); + fs_detector.manager = NULL; +} + +static const struct zwlr_foreign_toplevel_manager_v1_listener + fs_manager_listener = { + .toplevel = fs_handle_manager_toplevel, + .finished = fs_handle_manager_finished, }; // ============================================================================= @@ -243,26 +327,29 @@ static const struct zwlr_foreign_toplevel_manager_v1_listener fs_manager_listene // ============================================================================= static void screen_calculate_dimensions(void) { - if (!screen_info.mode_received || !screen_info.geometry_received || screen_info.screen_width > 0) { - return; - } - - bool is_rotated = (screen_info.transform == WL_OUTPUT_TRANSFORM_90 || - screen_info.transform == WL_OUTPUT_TRANSFORM_270 || - screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 || - screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_270); - - if (is_rotated) { - screen_info.screen_width = screen_info.raw_height; - screen_info.screen_height = screen_info.raw_width; - bongocat_log_info("Detected rotated screen: %dx%d (transform: %d)", - screen_info.raw_height, screen_info.raw_width, screen_info.transform); - } else { - screen_info.screen_width = screen_info.raw_width; - screen_info.screen_height = screen_info.raw_height; - bongocat_log_info("Detected screen: %dx%d (transform: %d)", - screen_info.raw_width, screen_info.raw_height, screen_info.transform); - } + if (!screen_info.mode_received || !screen_info.geometry_received || + screen_info.screen_width > 0) { + return; + } + + bool is_rotated = (screen_info.transform == WL_OUTPUT_TRANSFORM_90 || + screen_info.transform == WL_OUTPUT_TRANSFORM_270 || + screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 || + screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_270); + + if (is_rotated) { + screen_info.screen_width = screen_info.raw_height; + screen_info.screen_height = screen_info.raw_width; + bongocat_log_info("Detected rotated screen: %dx%d (transform: %d)", + screen_info.raw_height, screen_info.raw_width, + screen_info.transform); + } else { + screen_info.screen_width = screen_info.raw_width; + screen_info.screen_height = screen_info.raw_height; + bongocat_log_info("Detected screen: %dx%d (transform: %d)", + screen_info.raw_width, screen_info.raw_height, + screen_info.transform); + } } // ============================================================================= @@ -270,76 +357,84 @@ static void screen_calculate_dimensions(void) { // ============================================================================= int create_shm(int size) { - char name[] = "/bar-shm-XXXXXX"; - int fd; - - for (int i = 0; i < 100; i++) { - for (int j = 0; j < 6; j++) { - name[9 + j] = 'A' + (rand() % 26); - } - fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - shm_unlink(name); - break; - } - } + char name[] = "/bar-shm-XXXXXX"; + int fd; - if (fd < 0 || ftruncate(fd, size) < 0) { - perror("shm"); - exit(1); + for (int i = 0; i < 100; i++) { + for (int j = 0; j < 6; j++) { + name[9 + j] = 'A' + (rand() % 26); } - - return fd; -} - -void draw_bar(void) { - if (!configured) { - bongocat_log_debug("Surface not configured yet, skipping draw"); - return; + fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + break; } + } - int effective_opacity = fullscreen_detected ? 0 : current_config->overlay_opacity; - - // Clear buffer with transparency - for (int i = 0; i < current_config->screen_width * current_config->bar_height * 4; i += 4) { - pixels[i] = 0; // B - pixels[i + 1] = 0; // G - pixels[i + 2] = 0; // R - pixels[i + 3] = effective_opacity; // A - } + if (fd < 0 || ftruncate(fd, size) < 0) { + perror("shm"); + exit(1); + } - // Draw cat if visible - if (!fullscreen_detected) { - pthread_mutex_lock(&anim_lock); - int cat_height = current_config->cat_height; - int cat_width = (cat_height * CAT_IMAGE_WIDTH) / CAT_IMAGE_HEIGHT; - int cat_y = (current_config->bar_height - cat_height) / 2 + current_config->cat_y_offset; - - int cat_x = 0; - switch (current_config->cat_align) { - case ALIGN_CENTER: - cat_x = (current_config->screen_width - cat_width) / 2 + current_config->cat_x_offset; - break; - case ALIGN_LEFT: - cat_x = current_config->cat_x_offset; - break; - case ALIGN_RIGHT: - cat_x = current_config->screen_width - cat_width - current_config->cat_x_offset; - break; - } - - blit_image_scaled(pixels, current_config->screen_width, current_config->bar_height, - anim_imgs[anim_index], anim_width[anim_index], anim_height[anim_index], - cat_x, cat_y, cat_width, cat_height); - pthread_mutex_unlock(&anim_lock); - } else { - bongocat_log_debug("Cat hidden due to fullscreen detection"); - } + return fd; +} - wl_surface_attach(surface, buffer, 0, 0); - wl_surface_damage_buffer(surface, 0, 0, current_config->screen_width, current_config->bar_height); - wl_surface_commit(surface); - wl_display_flush(display); +void draw_bar(void) { + if (!configured) { + bongocat_log_debug("Surface not configured yet, skipping draw"); + return; + } + + int effective_opacity = + fullscreen_detected ? 0 : current_config->overlay_opacity; + + // Clear buffer with transparency + for (int i = 0; + i < current_config->screen_width * current_config->bar_height * 4; + i += 4) { + pixels[i] = 0; // B + pixels[i + 1] = 0; // G + pixels[i + 2] = 0; // R + pixels[i + 3] = effective_opacity; // A + } + + // Draw cat if visible + if (!fullscreen_detected) { + pthread_mutex_lock(&anim_lock); + int cat_height = current_config->cat_height; + int cat_width = (cat_height * CAT_IMAGE_WIDTH) / CAT_IMAGE_HEIGHT; + int cat_y = (current_config->bar_height - cat_height) / 2 + + current_config->cat_y_offset; + + int cat_x = 0; + switch (current_config->cat_align) { + case ALIGN_CENTER: + cat_x = (current_config->screen_width - cat_width) / 2 + + current_config->cat_x_offset; + break; + case ALIGN_LEFT: + cat_x = current_config->cat_x_offset; + break; + case ALIGN_RIGHT: + cat_x = current_config->screen_width - cat_width - + current_config->cat_x_offset; + break; + } + + blit_image_scaled(pixels, current_config->screen_width, + current_config->bar_height, anim_imgs[anim_index], + anim_width[anim_index], anim_height[anim_index], cat_x, + cat_y, cat_width, cat_height); + pthread_mutex_unlock(&anim_lock); + } else { + bongocat_log_debug("Cat hidden due to fullscreen detection"); + } + + wl_surface_attach(surface, buffer, 0, 0); + wl_surface_damage_buffer(surface, 0, 0, current_config->screen_width, + current_config->bar_height); + wl_surface_commit(surface); + wl_display_flush(display); } // ============================================================================= @@ -347,22 +442,30 @@ void draw_bar(void) { // ============================================================================= static void layer_surface_configure(void *data __attribute__((unused)), - struct zwlr_layer_surface_v1 *ls, - uint32_t serial, uint32_t w, uint32_t h) { - bongocat_log_debug("Layer surface configured: %dx%d", w, h); - zwlr_layer_surface_v1_ack_configure(ls, serial); - configured = true; - draw_bar(); + struct zwlr_layer_surface_v1 *ls, + uint32_t serial, uint32_t w, uint32_t h) { + bongocat_log_debug("Layer surface configured: %dx%d", w, h); + zwlr_layer_surface_v1_ack_configure(ls, serial); + configured = true; + draw_bar(); +} + +// Handle compositor-requested surface closure +static void layer_surface_closed(void *data __attribute__((unused)), + struct zwlr_layer_surface_v1 *ls + __attribute__((unused))) { + bongocat_log_info("Layer surface closed by compositor"); + configured = false; } static struct zwlr_layer_surface_v1_listener layer_listener = { .configure = layer_surface_configure, - .closed = NULL, + .closed = layer_surface_closed, }; -static void xdg_wm_base_ping(void *data __attribute__((unused)), - struct xdg_wm_base *wm_base, uint32_t serial) { - xdg_wm_base_pong(wm_base, serial); +static void xdg_wm_base_ping(void *data __attribute__((unused)), + struct xdg_wm_base *wm_base, uint32_t serial) { + xdg_wm_base_pong(wm_base, serial); } static struct xdg_wm_base_listener xdg_wm_base_listener = { @@ -370,44 +473,44 @@ static struct xdg_wm_base_listener xdg_wm_base_listener = { }; static void output_geometry(void *data __attribute__((unused)), - struct wl_output *wl_output __attribute__((unused)), - int32_t x __attribute__((unused)), - int32_t y __attribute__((unused)), - int32_t physical_width __attribute__((unused)), - int32_t physical_height __attribute__((unused)), - int32_t subpixel __attribute__((unused)), - const char *make __attribute__((unused)), - const char *model __attribute__((unused)), - int32_t transform) { - screen_info.transform = transform; - screen_info.geometry_received = true; - bongocat_log_debug("Output transform: %d", transform); - screen_calculate_dimensions(); + struct wl_output *wl_output __attribute__((unused)), + int32_t x __attribute__((unused)), + int32_t y __attribute__((unused)), + int32_t physical_width __attribute__((unused)), + int32_t physical_height __attribute__((unused)), + int32_t subpixel __attribute__((unused)), + const char *make __attribute__((unused)), + const char *model __attribute__((unused)), + int32_t transform) { + screen_info.transform = transform; + screen_info.geometry_received = true; + bongocat_log_debug("Output transform: %d", transform); + screen_calculate_dimensions(); } static void output_mode(void *data __attribute__((unused)), - struct wl_output *wl_output __attribute__((unused)), - uint32_t flags, int32_t width, int32_t height, - int32_t refresh __attribute__((unused))) { - if (flags & WL_OUTPUT_MODE_CURRENT) { - screen_info.raw_width = width; - screen_info.raw_height = height; - screen_info.mode_received = true; - bongocat_log_debug("Received raw screen mode: %dx%d", width, height); - screen_calculate_dimensions(); - } + struct wl_output *wl_output __attribute__((unused)), + uint32_t flags, int32_t width, int32_t height, + int32_t refresh __attribute__((unused))) { + if (flags & WL_OUTPUT_MODE_CURRENT) { + screen_info.raw_width = width; + screen_info.raw_height = height; + screen_info.mode_received = true; + bongocat_log_debug("Received raw screen mode: %dx%d", width, height); + screen_calculate_dimensions(); + } } static void output_done(void *data __attribute__((unused)), - struct wl_output *wl_output __attribute__((unused))) { - screen_calculate_dimensions(); - bongocat_log_debug("Output configuration complete"); + struct wl_output *wl_output __attribute__((unused))) { + screen_calculate_dimensions(); + bongocat_log_debug("Output configuration complete"); } static void output_scale(void *data __attribute__((unused)), - struct wl_output *wl_output __attribute__((unused)), - int32_t factor __attribute__((unused))) { - // Scale not needed for our use case + struct wl_output *wl_output __attribute__((unused)), + int32_t factor __attribute__((unused))) { + // Scale not needed for our use case } static struct wl_output_listener output_listener = { @@ -421,405 +524,421 @@ static struct wl_output_listener output_listener = { // WAYLAND PROTOCOL REGISTRY // ============================================================================= -static void registry_global(void *data __attribute__((unused)), struct wl_registry *reg, - uint32_t name, const char *iface, uint32_t ver __attribute__((unused))) { - if (strcmp(iface, wl_compositor_interface.name) == 0) { - compositor = (struct wl_compositor *)wl_registry_bind(reg, name, &wl_compositor_interface, 4); - } else if (strcmp(iface, wl_shm_interface.name) == 0) { - shm = (struct wl_shm *)wl_registry_bind(reg, name, &wl_shm_interface, 1); - } else if (strcmp(iface, zwlr_layer_shell_v1_interface.name) == 0) { - layer_shell = (struct zwlr_layer_shell_v1 *)wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 1); - } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { - xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(reg, name, &xdg_wm_base_interface, 1); - if (xdg_wm_base) { - xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); - } - } else if (strcmp(iface, zxdg_output_manager_v1_interface.name) == 0) { - xdg_output_manager = wl_registry_bind(reg, name, &zxdg_output_manager_v1_interface, 3); - } else if (strcmp(iface, wl_output_interface.name) == 0) { - if (output_count < MAX_OUTPUTS) { - outputs[output_count].name = name; - outputs[output_count].wl_output = wl_registry_bind(reg, name, &wl_output_interface, 2); - wl_output_add_listener(outputs[output_count].wl_output, &output_listener, NULL); - output_count++; - } - } else if (strcmp(iface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { - fs_detector.manager = (struct zwlr_foreign_toplevel_manager_v1 *) - wl_registry_bind(reg, name, &zwlr_foreign_toplevel_manager_v1_interface, 3); - if (fs_detector.manager) { - zwlr_foreign_toplevel_manager_v1_add_listener(fs_detector.manager, &fs_manager_listener, NULL); - bongocat_log_info("Foreign toplevel manager bound - using Wayland protocol for fullscreen detection"); - } +static void registry_global(void *data __attribute__((unused)), + struct wl_registry *reg, uint32_t name, + const char *iface, + uint32_t ver __attribute__((unused))) { + if (strcmp(iface, wl_compositor_interface.name) == 0) { + compositor = (struct wl_compositor *)wl_registry_bind( + reg, name, &wl_compositor_interface, 4); + } else if (strcmp(iface, wl_shm_interface.name) == 0) { + shm = (struct wl_shm *)wl_registry_bind(reg, name, &wl_shm_interface, 1); + } else if (strcmp(iface, zwlr_layer_shell_v1_interface.name) == 0) { + layer_shell = (struct zwlr_layer_shell_v1 *)wl_registry_bind( + reg, name, &zwlr_layer_shell_v1_interface, 1); + } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { + xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind( + reg, name, &xdg_wm_base_interface, 1); + if (xdg_wm_base) { + xdg_wm_base_add_listener(xdg_wm_base, &xdg_wm_base_listener, NULL); + } + } else if (strcmp(iface, zxdg_output_manager_v1_interface.name) == 0) { + xdg_output_manager = + wl_registry_bind(reg, name, &zxdg_output_manager_v1_interface, 3); + } else if (strcmp(iface, wl_output_interface.name) == 0) { + if (output_count < MAX_OUTPUTS) { + outputs[output_count].name = name; + outputs[output_count].wl_output = + wl_registry_bind(reg, name, &wl_output_interface, 2); + wl_output_add_listener(outputs[output_count].wl_output, &output_listener, + NULL); + output_count++; + } + } else if (strcmp(iface, zwlr_foreign_toplevel_manager_v1_interface.name) == + 0) { + fs_detector.manager = + (struct zwlr_foreign_toplevel_manager_v1 *)wl_registry_bind( + reg, name, &zwlr_foreign_toplevel_manager_v1_interface, 3); + if (fs_detector.manager) { + zwlr_foreign_toplevel_manager_v1_add_listener(fs_detector.manager, + &fs_manager_listener, NULL); + bongocat_log_info("Foreign toplevel manager bound - using Wayland " + "protocol for fullscreen detection"); } + } } static void registry_remove(void *data __attribute__((unused)), - struct wl_registry *registry __attribute__((unused)), - uint32_t name __attribute__((unused))) {} + struct wl_registry *registry + __attribute__((unused)), + uint32_t name __attribute__((unused))) {} static struct wl_registry_listener reg_listener = { - .global = registry_global, - .global_remove = registry_remove -}; + .global = registry_global, .global_remove = registry_remove}; // ============================================================================= // MAIN WAYLAND INTERFACE IMPLEMENTATION // ============================================================================= static bongocat_error_t wayland_setup_protocols(void) { - struct wl_registry *registry = wl_display_get_registry(display); - if (!registry) { - bongocat_log_error("Failed to get Wayland registry"); - return BONGOCAT_ERROR_WAYLAND; + struct wl_registry *registry = wl_display_get_registry(display); + if (!registry) { + bongocat_log_error("Failed to get Wayland registry"); + return BONGOCAT_ERROR_WAYLAND; + } + + wl_registry_add_listener(registry, ®_listener, NULL); + wl_display_roundtrip(display); + + if (xdg_output_manager) { + for (size_t i = 0; i < output_count; ++i) { + outputs[i].xdg_output = zxdg_output_manager_v1_get_xdg_output( + xdg_output_manager, outputs[i].wl_output); + zxdg_output_v1_add_listener(outputs[i].xdg_output, &xdg_output_listener, + &outputs[i]); } - wl_registry_add_listener(registry, ®_listener, NULL); + // Wait for all xdg_output events wl_display_roundtrip(display); + } - if (xdg_output_manager) { - for (size_t i = 0; i < output_count; ++i) { - outputs[i].xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, outputs[i].wl_output); - zxdg_output_v1_add_listener(outputs[i].xdg_output, &xdg_output_listener, &outputs[i]); - } - - // Wait for all xdg_output events - wl_display_roundtrip(display); + output = NULL; + if (current_config->output_name) { + for (size_t i = 0; i < output_count; ++i) { + if (outputs[i].name_received && + strcmp(outputs[i].name_str, current_config->output_name) == 0) { + output = outputs[i].wl_output; + bongocat_log_info("Matched output: %s", outputs[i].name_str); + break; + } } - output = NULL; - if (current_config->output_name) { - for (size_t i = 0; i < output_count; ++i) { - if (outputs[i].name_received && - strcmp(outputs[i].name_str, current_config->output_name) == 0) { - output = outputs[i].wl_output; - bongocat_log_info("Matched output: %s", outputs[i].name_str); - break; - } - } - - if (!output) { - bongocat_log_error("Could not find output named '%s', defaulting to first output", - current_config->output_name); - } + if (!output) { + bongocat_log_error( + "Could not find output named '%s', defaulting to first output", + current_config->output_name); } + } - // Fallback - if (!output && output_count > 0) { - output = outputs[0].wl_output; - bongocat_log_warning("Falling back to first output"); - } + // Fallback + if (!output && output_count > 0) { + output = outputs[0].wl_output; + bongocat_log_warning("Falling back to first output"); + } - if (!compositor || !shm || !layer_shell) { - bongocat_log_error("Missing required Wayland protocols"); - wl_registry_destroy(registry); - return BONGOCAT_ERROR_WAYLAND; - } + if (!compositor || !shm || !layer_shell) { + bongocat_log_error("Missing required Wayland protocols"); + wl_registry_destroy(registry); + return BONGOCAT_ERROR_WAYLAND; + } - // Configure screen dimensions - if (output) { - wl_display_roundtrip(display); - if (screen_info.screen_width > 0) { - bongocat_log_info("Detected screen width: %d", screen_info.screen_width); - current_config->screen_width = screen_info.screen_width; - } else { - bongocat_log_warning("Using default screen width: %d", DEFAULT_SCREEN_WIDTH); - current_config->screen_width = DEFAULT_SCREEN_WIDTH; - } + // Configure screen dimensions + if (output) { + wl_display_roundtrip(display); + if (screen_info.screen_width > 0) { + bongocat_log_info("Detected screen width: %d", screen_info.screen_width); + current_config->screen_width = screen_info.screen_width; } else { - bongocat_log_warning("No output found, using default screen width: %d", DEFAULT_SCREEN_WIDTH); - current_config->screen_width = DEFAULT_SCREEN_WIDTH; + bongocat_log_warning("Using default screen width: %d", + DEFAULT_SCREEN_WIDTH); + current_config->screen_width = DEFAULT_SCREEN_WIDTH; } + } else { + bongocat_log_warning("No output found, using default screen width: %d", + DEFAULT_SCREEN_WIDTH); + current_config->screen_width = DEFAULT_SCREEN_WIDTH; + } - wl_registry_destroy(registry); - return BONGOCAT_SUCCESS; + wl_registry_destroy(registry); + return BONGOCAT_SUCCESS; } static bongocat_error_t wayland_setup_surface(void) { - surface = wl_compositor_create_surface(compositor); - if (!surface) { - bongocat_log_error("Failed to create surface"); - return BONGOCAT_ERROR_WAYLAND; - } - - layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, surface, output, - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - "bongocat-overlay"); - - if (!layer_surface) { - bongocat_log_error("Failed to create layer surface"); - return BONGOCAT_ERROR_WAYLAND; - } - - // Configure layer surface - uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - if (current_config->overlay_position == POSITION_TOP) { - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - } else { - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - } - - zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); - zwlr_layer_surface_v1_set_size(layer_surface, 0, current_config->bar_height); - zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); - zwlr_layer_surface_v1_add_listener(layer_surface, &layer_listener, NULL); - - // Make surface click-through - struct wl_region *input_region = wl_compositor_create_region(compositor); - if (input_region) { - wl_surface_set_input_region(surface, input_region); - wl_region_destroy(input_region); - } - - wl_surface_commit(surface); - return BONGOCAT_SUCCESS; + surface = wl_compositor_create_surface(compositor); + if (!surface) { + bongocat_log_error("Failed to create surface"); + return BONGOCAT_ERROR_WAYLAND; + } + + layer_surface = zwlr_layer_shell_v1_get_layer_surface( + layer_shell, surface, output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + "bongocat-overlay"); + + if (!layer_surface) { + bongocat_log_error("Failed to create layer surface"); + return BONGOCAT_ERROR_WAYLAND; + } + + // Configure layer surface + uint32_t anchor = + ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + if (current_config->overlay_position == POSITION_TOP) { + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + } else { + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + } + + zwlr_layer_surface_v1_set_anchor(layer_surface, anchor); + zwlr_layer_surface_v1_set_size(layer_surface, 0, current_config->bar_height); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity( + layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); + zwlr_layer_surface_v1_add_listener(layer_surface, &layer_listener, NULL); + + // Make surface click-through + struct wl_region *input_region = wl_compositor_create_region(compositor); + if (input_region) { + wl_surface_set_input_region(surface, input_region); + wl_region_destroy(input_region); + } + + wl_surface_commit(surface); + return BONGOCAT_SUCCESS; } static bongocat_error_t wayland_setup_buffer(void) { - int size = current_config->screen_width * current_config->bar_height * 4; - if (size <= 0) { - bongocat_log_error("Invalid buffer size: %d", size); - return BONGOCAT_ERROR_WAYLAND; - } - - int fd = create_shm(size); - if (fd < 0) { - return BONGOCAT_ERROR_WAYLAND; - } - - pixels = (uint8_t *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if (pixels == MAP_FAILED) { - bongocat_log_error("Failed to map shared memory: %s", strerror(errno)); - close(fd); - return BONGOCAT_ERROR_MEMORY; - } - - struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); - if (!pool) { - bongocat_log_error("Failed to create shared memory pool"); - munmap(pixels, size); - pixels = NULL; - close(fd); - return BONGOCAT_ERROR_WAYLAND; - } - - buffer = wl_shm_pool_create_buffer(pool, 0, current_config->screen_width, - current_config->bar_height, - current_config->screen_width * 4, - WL_SHM_FORMAT_ARGB8888); - if (!buffer) { - bongocat_log_error("Failed to create buffer"); - wl_shm_pool_destroy(pool); - munmap(pixels, size); - pixels = NULL; - close(fd); - return BONGOCAT_ERROR_WAYLAND; - } - + int size = current_config->screen_width * current_config->bar_height * 4; + if (size <= 0) { + bongocat_log_error("Invalid buffer size: %d", size); + return BONGOCAT_ERROR_WAYLAND; + } + + int fd = create_shm(size); + if (fd < 0) { + return BONGOCAT_ERROR_WAYLAND; + } + + pixels = + (uint8_t *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (pixels == MAP_FAILED) { + bongocat_log_error("Failed to map shared memory: %s", strerror(errno)); + close(fd); + return BONGOCAT_ERROR_MEMORY; + } + + struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size); + if (!pool) { + bongocat_log_error("Failed to create shared memory pool"); + munmap(pixels, size); + pixels = NULL; + close(fd); + return BONGOCAT_ERROR_WAYLAND; + } + + buffer = wl_shm_pool_create_buffer( + pool, 0, current_config->screen_width, current_config->bar_height, + current_config->screen_width * 4, WL_SHM_FORMAT_ARGB8888); + if (!buffer) { + bongocat_log_error("Failed to create buffer"); wl_shm_pool_destroy(pool); + munmap(pixels, size); + pixels = NULL; close(fd); - return BONGOCAT_SUCCESS; + return BONGOCAT_ERROR_WAYLAND; + } + + wl_shm_pool_destroy(pool); + close(fd); + return BONGOCAT_SUCCESS; } bongocat_error_t wayland_init(config_t *config) { - BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(config, BONGOCAT_ERROR_INVALID_PARAM); - current_config = config; - bongocat_log_info("Initializing Wayland connection"); + current_config = config; + bongocat_log_info("Initializing Wayland connection"); - display = wl_display_connect(NULL); - if (!display) { - bongocat_log_error("Failed to connect to Wayland display"); - return BONGOCAT_ERROR_WAYLAND; - } + // Initialize random seed for shared memory name generation + srand((unsigned int)time(NULL)); - bongocat_error_t result; - if ((result = wayland_setup_protocols()) != BONGOCAT_SUCCESS || - (result = wayland_setup_surface()) != BONGOCAT_SUCCESS || - (result = wayland_setup_buffer()) != BONGOCAT_SUCCESS) { - wayland_cleanup(); - return result; - } + display = wl_display_connect(NULL); + if (!display) { + bongocat_log_error("Failed to connect to Wayland display"); + return BONGOCAT_ERROR_WAYLAND; + } + + bongocat_error_t result; + if ((result = wayland_setup_protocols()) != BONGOCAT_SUCCESS || + (result = wayland_setup_surface()) != BONGOCAT_SUCCESS || + (result = wayland_setup_buffer()) != BONGOCAT_SUCCESS) { + wayland_cleanup(); + return result; + } - bongocat_log_info("Wayland initialization complete (%dx%d buffer)", - current_config->screen_width, current_config->bar_height); - return BONGOCAT_SUCCESS; + bongocat_log_info("Wayland initialization complete (%dx%d buffer)", + current_config->screen_width, current_config->bar_height); + return BONGOCAT_SUCCESS; } bongocat_error_t wayland_run(volatile sig_atomic_t *running) { - BONGOCAT_CHECK_NULL(running, BONGOCAT_ERROR_INVALID_PARAM); - - bongocat_log_info("Starting Wayland event loop"); - const int check_interval_ms = 100; - - while (*running && display) { - // Periodic fullscreen check for fallback detection - struct timeval now; - gettimeofday(&now, NULL); - long elapsed_ms = (now.tv_sec - fs_detector.last_check.tv_sec) * 1000 + - (now.tv_usec - fs_detector.last_check.tv_usec) / 1000; - - if (elapsed_ms >= check_interval_ms) { - bool new_state = fs_check_status(); - if (new_state != fullscreen_detected) { - fs_update_state(new_state); - } - fs_detector.last_check = now; - } - - // Handle Wayland events - struct pollfd pfd = { - .fd = wl_display_get_fd(display), - .events = POLLIN, - }; - - while (wl_display_prepare_read(display) != 0) { - if (wl_display_dispatch_pending(display) == -1) { - bongocat_log_error("Failed to dispatch pending events"); - return BONGOCAT_ERROR_WAYLAND; - } - } - - int poll_result = poll(&pfd, 1, 100); - - if (poll_result > 0) { - if (wl_display_read_events(display) == -1 || - wl_display_dispatch_pending(display) == -1) { - bongocat_log_error("Failed to handle Wayland events"); - return BONGOCAT_ERROR_WAYLAND; - } - } else if (poll_result == 0) { - wl_display_cancel_read(display); - } else { - wl_display_cancel_read(display); - if (errno != EINTR) { - bongocat_log_error("Poll error: %s", strerror(errno)); - return BONGOCAT_ERROR_WAYLAND; - } - } - - wl_display_flush(display); + BONGOCAT_CHECK_NULL(running, BONGOCAT_ERROR_INVALID_PARAM); + + bongocat_log_info("Starting Wayland event loop"); + const int check_interval_ms = 100; + + while (*running && display) { + // Periodic fullscreen check for fallback detection + struct timeval now; + gettimeofday(&now, NULL); + long elapsed_ms = (now.tv_sec - fs_detector.last_check.tv_sec) * 1000 + + (now.tv_usec - fs_detector.last_check.tv_usec) / 1000; + + if (elapsed_ms >= check_interval_ms) { + bool new_state = fs_check_status(); + if (new_state != fullscreen_detected) { + fs_update_state(new_state); + } + fs_detector.last_check = now; + } + + // Handle Wayland events + struct pollfd pfd = { + .fd = wl_display_get_fd(display), + .events = POLLIN, + }; + + while (wl_display_prepare_read(display) != 0) { + if (wl_display_dispatch_pending(display) == -1) { + bongocat_log_error("Failed to dispatch pending events"); + return BONGOCAT_ERROR_WAYLAND; + } } - bongocat_log_info("Wayland event loop exited"); - return BONGOCAT_SUCCESS; + int poll_result = poll(&pfd, 1, 100); + + if (poll_result > 0) { + if (wl_display_read_events(display) == -1 || + wl_display_dispatch_pending(display) == -1) { + bongocat_log_error("Failed to handle Wayland events"); + return BONGOCAT_ERROR_WAYLAND; + } + } else if (poll_result == 0) { + wl_display_cancel_read(display); + } else { + wl_display_cancel_read(display); + if (errno != EINTR) { + bongocat_log_error("Poll error: %s", strerror(errno)); + return BONGOCAT_ERROR_WAYLAND; + } + } + + wl_display_flush(display); + } + + bongocat_log_info("Wayland event loop exited"); + return BONGOCAT_SUCCESS; } // ============================================================================= // PUBLIC API IMPLEMENTATION // ============================================================================= -int wayland_get_screen_width(void) { - return screen_info.screen_width; -} +int wayland_get_screen_width(void) { return screen_info.screen_width; } void wayland_update_config(config_t *config) { - if (!config) { - bongocat_log_error("Cannot update wayland config: config is NULL"); - return; - } + if (!config) { + bongocat_log_error("Cannot update wayland config: config is NULL"); + return; + } - current_config = config; - if (configured) { - draw_bar(); - } + current_config = config; + if (configured) { + draw_bar(); + } } void wayland_cleanup(void) { - bongocat_log_info("Cleaning up Wayland resources"); - - // First destroy xdg_output objects - for (size_t i = 0; i < output_count; ++i) { - if (outputs[i].xdg_output) { - bongocat_log_debug("Destroying xdg_output %zu", i); - zxdg_output_v1_destroy(outputs[i].xdg_output); - outputs[i].xdg_output = NULL; - } - } - - // Then destroy the manager - if (xdg_output_manager) { - bongocat_log_debug("Destroying xdg_output_manager"); - zxdg_output_manager_v1_destroy(xdg_output_manager); - xdg_output_manager = NULL; - } + bongocat_log_info("Cleaning up Wayland resources"); - // Finally destroy wl_output objects - for (size_t i = 0; i < output_count; ++i) { - if (outputs[i].wl_output) { - bongocat_log_debug("Destroying wl_output %zu", i); - wl_output_destroy(outputs[i].wl_output); - outputs[i].wl_output = NULL; - } + // First destroy xdg_output objects + for (size_t i = 0; i < output_count; ++i) { + if (outputs[i].xdg_output) { + bongocat_log_debug("Destroying xdg_output %zu", i); + zxdg_output_v1_destroy(outputs[i].xdg_output); + outputs[i].xdg_output = NULL; } - - output_count = 0; + } - if (buffer) { - wl_buffer_destroy(buffer); - buffer = NULL; - } + // Then destroy the manager + if (xdg_output_manager) { + bongocat_log_debug("Destroying xdg_output_manager"); + zxdg_output_manager_v1_destroy(xdg_output_manager); + xdg_output_manager = NULL; + } - if (pixels && current_config) { - int size = current_config->screen_width * current_config->bar_height * 4; - munmap(pixels, size); - pixels = NULL; + // Finally destroy wl_output objects + for (size_t i = 0; i < output_count; ++i) { + if (outputs[i].wl_output) { + bongocat_log_debug("Destroying wl_output %zu", i); + wl_output_destroy(outputs[i].wl_output); + outputs[i].wl_output = NULL; } + } - if (layer_surface) { - zwlr_layer_surface_v1_destroy(layer_surface); - layer_surface = NULL; - } + output_count = 0; - if (surface) { - wl_surface_destroy(surface); - surface = NULL; - } + if (buffer) { + wl_buffer_destroy(buffer); + buffer = NULL; + } - // Note: output is just a reference to one of the outputs[] entries - // It will be destroyed when we destroy the outputs[] array above - output = NULL; - - if (layer_shell) { - zwlr_layer_shell_v1_destroy(layer_shell); - layer_shell = NULL; - } - - if (xdg_wm_base) { - xdg_wm_base_destroy(xdg_wm_base); - xdg_wm_base = NULL; - } + if (pixels && current_config) { + int size = current_config->screen_width * current_config->bar_height * 4; + munmap(pixels, size); + pixels = NULL; + } + + if (layer_surface) { + zwlr_layer_surface_v1_destroy(layer_surface); + layer_surface = NULL; + } + + if (surface) { + wl_surface_destroy(surface); + surface = NULL; + } + + // Note: output is just a reference to one of the outputs[] entries + // It will be destroyed when we destroy the outputs[] array above + output = NULL; + + if (layer_shell) { + zwlr_layer_shell_v1_destroy(layer_shell); + layer_shell = NULL; + } + + if (xdg_wm_base) { + xdg_wm_base_destroy(xdg_wm_base); + xdg_wm_base = NULL; + } + + if (fs_detector.manager) { + zwlr_foreign_toplevel_manager_v1_destroy(fs_detector.manager); + fs_detector.manager = NULL; + } - if (fs_detector.manager) { - zwlr_foreign_toplevel_manager_v1_destroy(fs_detector.manager); - fs_detector.manager = NULL; - } + if (shm) { + wl_shm_destroy(shm); + shm = NULL; + } - if (shm) { - wl_shm_destroy(shm); - shm = NULL; - } + if (compositor) { + wl_compositor_destroy(compositor); + compositor = NULL; + } - if (compositor) { - wl_compositor_destroy(compositor); - compositor = NULL; - } + if (display) { + wl_display_disconnect(display); + display = NULL; + } - if (display) { - wl_display_disconnect(display); - display = NULL; - } + // Reset state + configured = false; + fullscreen_detected = false; + memset(&fs_detector, 0, sizeof(fs_detector)); + memset(&screen_info, 0, sizeof(screen_info)); - // Reset state - configured = false; - fullscreen_detected = false; - memset(&fs_detector, 0, sizeof(fs_detector)); - memset(&screen_info, 0, sizeof(screen_info)); - - bongocat_log_debug("Wayland cleanup complete"); + bongocat_log_debug("Wayland cleanup complete"); } -const char* wayland_get_current_layer_name(void) { - return "OVERLAY"; -} +const char *wayland_get_current_layer_name(void) { return "OVERLAY"; } diff --git a/src/utils/error.c b/src/utils/error.c index 645f8933..ed613ab9 100644 --- a/src/utils/error.c +++ b/src/utils/error.c @@ -1,83 +1,93 @@ +#define _POSIX_C_SOURCE 200809L #include "utils/error.h" #include -#include #include +#include static int debug_enabled = 1; -void bongocat_error_init(int enable_debug) { - debug_enabled = enable_debug; -} +void bongocat_error_init(int enable_debug) { debug_enabled = enable_debug; } static void log_timestamp(FILE *stream) { - struct timeval tv; - struct tm *tm_info; - char timestamp[64]; - - gettimeofday(&tv, NULL); - tm_info = localtime(&tv.tv_sec); - - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info); - fprintf(stream, "[%s.%03ld] ", timestamp, tv.tv_usec / 1000); + struct timeval tv; + struct tm tm_info; + char timestamp[64]; + + gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &tm_info); // Thread-safe version + + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); + fprintf(stream, "[%s.%03ld] ", timestamp, tv.tv_usec / 1000); } void bongocat_log_error(const char *format, ...) { - va_list args; - log_timestamp(stderr); - fprintf(stderr, "ERROR: "); - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - fprintf(stderr, "\n"); - fflush(stderr); + va_list args; + log_timestamp(stderr); + fprintf(stderr, "ERROR: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stderr); } void bongocat_log_warning(const char *format, ...) { - va_list args; - log_timestamp(stderr); - fprintf(stderr, "WARNING: "); - va_start(args, format); - vfprintf(stderr, format, args); - va_end(args); - fprintf(stderr, "\n"); - fflush(stderr); + va_list args; + log_timestamp(stderr); + fprintf(stderr, "WARNING: "); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stderr); } void bongocat_log_info(const char *format, ...) { - va_list args; - log_timestamp(stdout); - fprintf(stdout, "INFO: "); - va_start(args, format); - vfprintf(stdout, format, args); - va_end(args); - fprintf(stdout, "\n"); - fflush(stdout); + va_list args; + log_timestamp(stdout); + fprintf(stdout, "INFO: "); + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + fprintf(stdout, "\n"); + fflush(stdout); } void bongocat_log_debug(const char *format, ...) { - if (!debug_enabled) return; - - va_list args; - log_timestamp(stdout); - fprintf(stdout, "DEBUG: "); - va_start(args, format); - vfprintf(stdout, format, args); - va_end(args); - fprintf(stdout, "\n"); - fflush(stdout); + if (!debug_enabled) + return; + + va_list args; + log_timestamp(stdout); + fprintf(stdout, "DEBUG: "); + va_start(args, format); + vfprintf(stdout, format, args); + va_end(args); + fprintf(stdout, "\n"); + fflush(stdout); } -const char* bongocat_error_string(bongocat_error_t error) { - switch (error) { - case BONGOCAT_SUCCESS: return "Success"; - case BONGOCAT_ERROR_MEMORY: return "Memory allocation error"; - case BONGOCAT_ERROR_FILE_IO: return "File I/O error"; - case BONGOCAT_ERROR_WAYLAND: return "Wayland error"; - case BONGOCAT_ERROR_CONFIG: return "Configuration error"; - case BONGOCAT_ERROR_INPUT: return "Input error"; - case BONGOCAT_ERROR_ANIMATION: return "Animation error"; - case BONGOCAT_ERROR_THREAD: return "Thread error"; - case BONGOCAT_ERROR_INVALID_PARAM: return "Invalid parameter"; - default: return "Unknown error"; - } +const char *bongocat_error_string(bongocat_error_t error) { + switch (error) { + case BONGOCAT_SUCCESS: + return "Success"; + case BONGOCAT_ERROR_MEMORY: + return "Memory allocation error"; + case BONGOCAT_ERROR_FILE_IO: + return "File I/O error"; + case BONGOCAT_ERROR_WAYLAND: + return "Wayland error"; + case BONGOCAT_ERROR_CONFIG: + return "Configuration error"; + case BONGOCAT_ERROR_INPUT: + return "Input error"; + case BONGOCAT_ERROR_ANIMATION: + return "Animation error"; + case BONGOCAT_ERROR_THREAD: + return "Thread error"; + case BONGOCAT_ERROR_INVALID_PARAM: + return "Invalid parameter"; + default: + return "Unknown error"; + } } \ No newline at end of file diff --git a/src/utils/memory.c b/src/utils/memory.c index cbbc0c3f..ee43153f 100644 --- a/src/utils/memory.c +++ b/src/utils/memory.c @@ -1,225 +1,240 @@ #include "utils/memory.h" #include "utils/error.h" -#include #include +#include static memory_stats_t g_memory_stats = {0}; static pthread_mutex_t memory_mutex = PTHREAD_MUTEX_INITIALIZER; #ifdef DEBUG typedef struct allocation_record { - void *ptr; - size_t size; - const char *file; - int line; - struct allocation_record *next; + void *ptr; + size_t size; + const char *file; + int line; + struct allocation_record *next; } allocation_record_t; static allocation_record_t *allocations = NULL; #endif -void* bongocat_malloc(size_t size) { - if (size == 0) { - bongocat_log_warning("Attempted to allocate 0 bytes"); - return NULL; - } - - void *ptr = malloc(size); - if (!ptr) { - bongocat_log_error("Failed to allocate %zu bytes", size); - return NULL; - } - - pthread_mutex_lock(&memory_mutex); - g_memory_stats.total_allocated += size; - g_memory_stats.current_allocated += size; - if (g_memory_stats.current_allocated > g_memory_stats.peak_allocated) { - g_memory_stats.peak_allocated = g_memory_stats.current_allocated; - } - g_memory_stats.allocation_count++; - pthread_mutex_unlock(&memory_mutex); - - return ptr; +void *bongocat_malloc(size_t size) { + if (size == 0) { + bongocat_log_warning("Attempted to allocate 0 bytes"); + return NULL; + } + + void *ptr = malloc(size); + if (!ptr) { + bongocat_log_error("Failed to allocate %zu bytes", size); + return NULL; + } + + pthread_mutex_lock(&memory_mutex); + g_memory_stats.total_allocated += size; + g_memory_stats.current_allocated += size; + if (g_memory_stats.current_allocated > g_memory_stats.peak_allocated) { + g_memory_stats.peak_allocated = g_memory_stats.current_allocated; + } + g_memory_stats.allocation_count++; + pthread_mutex_unlock(&memory_mutex); + + return ptr; } -void* bongocat_calloc(size_t count, size_t size) { - if (count == 0 || size == 0) { - bongocat_log_warning("Attempted to allocate 0 bytes"); - return NULL; - } - - // Check for overflow - if (count > SIZE_MAX / size) { - bongocat_log_error("Integer overflow in calloc"); - return NULL; - } - - void *ptr = calloc(count, size); - if (!ptr) { - bongocat_log_error("Failed to allocate %zu bytes", count * size); - return NULL; - } - - pthread_mutex_lock(&memory_mutex); - size_t total_size = count * size; - g_memory_stats.total_allocated += total_size; - g_memory_stats.current_allocated += total_size; - if (g_memory_stats.current_allocated > g_memory_stats.peak_allocated) { - g_memory_stats.peak_allocated = g_memory_stats.current_allocated; - } - g_memory_stats.allocation_count++; - pthread_mutex_unlock(&memory_mutex); - - return ptr; +void *bongocat_calloc(size_t count, size_t size) { + if (count == 0 || size == 0) { + bongocat_log_warning("Attempted to allocate 0 bytes"); + return NULL; + } + + // Check for overflow + if (count > SIZE_MAX / size) { + bongocat_log_error("Integer overflow in calloc"); + return NULL; + } + + void *ptr = calloc(count, size); + if (!ptr) { + bongocat_log_error("Failed to allocate %zu bytes", count * size); + return NULL; + } + + pthread_mutex_lock(&memory_mutex); + size_t total_size = count * size; + g_memory_stats.total_allocated += total_size; + g_memory_stats.current_allocated += total_size; + if (g_memory_stats.current_allocated > g_memory_stats.peak_allocated) { + g_memory_stats.peak_allocated = g_memory_stats.current_allocated; + } + g_memory_stats.allocation_count++; + pthread_mutex_unlock(&memory_mutex); + + return ptr; } -void* bongocat_realloc(void *ptr, size_t size) { - if (size == 0) { - bongocat_free(ptr); - return NULL; - } - - void *new_ptr = realloc(ptr, size); - if (!new_ptr) { - bongocat_log_error("Failed to reallocate to %zu bytes", size); - return NULL; - } - - // Note: We can't track size changes accurately without storing original sizes - // This is a limitation of this simple tracking system - - return new_ptr; +void *bongocat_realloc(void *ptr, size_t size) { + if (size == 0) { + bongocat_free(ptr); + return NULL; + } + + void *new_ptr = realloc(ptr, size); + if (!new_ptr) { + bongocat_log_error("Failed to reallocate to %zu bytes", size); + return NULL; + } + + // Note: We can't track size changes accurately without storing original sizes + // This is a limitation of this simple tracking system + + return new_ptr; } void bongocat_free(void *ptr) { - if (!ptr) return; - - free(ptr); - - pthread_mutex_lock(&memory_mutex); - g_memory_stats.free_count++; - pthread_mutex_unlock(&memory_mutex); + if (!ptr) + return; + + free(ptr); + + pthread_mutex_lock(&memory_mutex); + g_memory_stats.free_count++; + pthread_mutex_unlock(&memory_mutex); } -memory_pool_t* memory_pool_create(size_t size, size_t alignment) { - if (size == 0 || alignment == 0) { - bongocat_log_error("Invalid memory pool parameters"); - return NULL; - } - - memory_pool_t *pool = bongocat_malloc(sizeof(memory_pool_t)); - if (!pool) return NULL; - - pool->data = bongocat_malloc(size); - if (!pool->data) { - bongocat_free(pool); - return NULL; - } - - pool->size = size; - pool->used = 0; - pool->alignment = alignment; - - return pool; +memory_pool_t *memory_pool_create(size_t size, size_t alignment) { + if (size == 0 || alignment == 0) { + bongocat_log_error("Invalid memory pool parameters"); + return NULL; + } + + // Validate alignment is a power of 2 + if ((alignment & (alignment - 1)) != 0) { + bongocat_log_error("Memory pool alignment must be a power of 2, got %zu", + alignment); + return NULL; + } + + memory_pool_t *pool = bongocat_malloc(sizeof(memory_pool_t)); + if (!pool) + return NULL; + + pool->data = bongocat_malloc(size); + if (!pool->data) { + bongocat_free(pool); + return NULL; + } + + pool->size = size; + pool->used = 0; + pool->alignment = alignment; + + return pool; } -void* memory_pool_alloc(memory_pool_t *pool, size_t size) { - if (!pool || size == 0) return NULL; - - // Align the size - size_t aligned_size = (size + pool->alignment - 1) & ~(pool->alignment - 1); - - if (pool->used + aligned_size > pool->size) { - bongocat_log_error("Memory pool exhausted"); - return NULL; - } - - void *ptr = (char*)pool->data + pool->used; - pool->used += aligned_size; - - return ptr; +void *memory_pool_alloc(memory_pool_t *pool, size_t size) { + if (!pool || size == 0) + return NULL; + + // Align the size + size_t aligned_size = (size + pool->alignment - 1) & ~(pool->alignment - 1); + + if (pool->used + aligned_size > pool->size) { + bongocat_log_error("Memory pool exhausted"); + return NULL; + } + + void *ptr = (char *)pool->data + pool->used; + pool->used += aligned_size; + + return ptr; } void memory_pool_reset(memory_pool_t *pool) { - if (pool) { - pool->used = 0; - } + if (pool) { + pool->used = 0; + } } void memory_pool_destroy(memory_pool_t *pool) { - if (pool) { - bongocat_free(pool->data); - bongocat_free(pool); - } + if (pool) { + bongocat_free(pool->data); + bongocat_free(pool); + } } void memory_get_stats(memory_stats_t *stats) { - if (!stats) return; - - pthread_mutex_lock(&memory_mutex); - *stats = g_memory_stats; - pthread_mutex_unlock(&memory_mutex); + if (!stats) + return; + + pthread_mutex_lock(&memory_mutex); + *stats = g_memory_stats; + pthread_mutex_unlock(&memory_mutex); } void memory_print_stats(void) { - memory_stats_t stats; - memory_get_stats(&stats); - - bongocat_log_info("Memory Statistics:"); - bongocat_log_info(" Total allocated: %zu bytes", stats.total_allocated); - bongocat_log_info(" Current allocated: %zu bytes", stats.current_allocated); - bongocat_log_info(" Peak allocated: %zu bytes", stats.peak_allocated); - bongocat_log_info(" Allocations: %zu", stats.allocation_count); - bongocat_log_info(" Frees: %zu", stats.free_count); - bongocat_log_info(" Potential leaks: %zu", stats.allocation_count - stats.free_count); + memory_stats_t stats; + memory_get_stats(&stats); + + bongocat_log_info("Memory Statistics:"); + bongocat_log_info(" Total allocated: %zu bytes", stats.total_allocated); + bongocat_log_info(" Current allocated: %zu bytes", stats.current_allocated); + bongocat_log_info(" Peak allocated: %zu bytes", stats.peak_allocated); + bongocat_log_info(" Allocations: %zu", stats.allocation_count); + bongocat_log_info(" Frees: %zu", stats.free_count); + bongocat_log_info(" Potential leaks: %zu", + stats.allocation_count - stats.free_count); } #ifdef DEBUG -void* bongocat_malloc_debug(size_t size, const char *file, int line) { - void *ptr = bongocat_malloc(size); - if (!ptr) return NULL; - - allocation_record_t *record = malloc(sizeof(allocation_record_t)); - if (record) { - record->ptr = ptr; - record->size = size; - record->file = file; - record->line = line; - record->next = allocations; - allocations = record; - } - - return ptr; +void *bongocat_malloc_debug(size_t size, const char *file, int line) { + void *ptr = bongocat_malloc(size); + if (!ptr) + return NULL; + + allocation_record_t *record = malloc(sizeof(allocation_record_t)); + if (record) { + record->ptr = ptr; + record->size = size; + record->file = file; + record->line = line; + record->next = allocations; + allocations = record; + } + + return ptr; } void bongocat_free_debug(void *ptr, const char *file, int line) { - if (!ptr) return; - - allocation_record_t **current = &allocations; - while (*current) { - if ((*current)->ptr == ptr) { - allocation_record_t *to_remove = *current; - *current = (*current)->next; - free(to_remove); - break; - } - current = &(*current)->next; + if (!ptr) + return; + + allocation_record_t **current = &allocations; + while (*current) { + if ((*current)->ptr == ptr) { + allocation_record_t *to_remove = *current; + *current = (*current)->next; + free(to_remove); + break; } - - bongocat_free(ptr); + current = &(*current)->next; + } + + bongocat_free(ptr); } void memory_leak_check(void) { - if (!allocations) { - bongocat_log_info("No memory leaks detected"); - return; - } - - bongocat_log_error("Memory leaks detected:"); - allocation_record_t *current = allocations; - while (current) { - bongocat_log_error(" %zu bytes at %s:%d", current->size, current->file, current->line); - current = current->next; - } + if (!allocations) { + bongocat_log_info("No memory leaks detected"); + return; + } + + bongocat_log_error("Memory leaks detected:"); + allocation_record_t *current = allocations; + while (current) { + bongocat_log_error(" %zu bytes at %s:%d", current->size, current->file, + current->line); + current = current->next; + } } #endif \ No newline at end of file From 3c387583603d85ebf60467648ea8b63650a4dbb7 Mon Sep 17 00:00:00 2001 From: Saatvik Sharma Date: Sat, 6 Dec 2025 21:03:42 +0530 Subject: [PATCH 04/18] feat: v1.3.0 - anti-aliasing, interactive keyboard detection, hot-reload fixes - Box filter + alpha blending for smooth edges at any size - `bongocat-find-devices -i` detects keyboards by key events - Hot-reload now works when changing keyboard device paths - C23 codebase modernization (RAII, nodiscard, atomics) - Updated nix module with new config options --- .clang-format | 113 +++ .clang-tidy | 77 ++ .editorconfig | 48 ++ CHANGELOG.md | 35 + Makefile | 47 +- README.md | 510 +++--------- bongocat.conf | 95 --- bongocat.conf.example | 81 ++ include/config/config.h | 116 ++- include/core/bongocat.h | 106 ++- include/graphics/animation.h | 46 +- include/graphics/embedded_assets.h | 2 +- include/platform/input.h | 29 +- include/platform/wayland.h | 63 +- include/utils/error.h | 122 ++- include/utils/memory.h | 131 ++- nix/common.nix | 88 +- nix/default.nix | 2 +- scripts/find_input_devices.sh | 753 +++++++----------- .../{test-nix-build.sh => test_nix_build.sh} | 0 scripts/test_toggle.sh | 0 src/config/config.c | 33 +- src/config/config_watcher.c | 304 +++---- src/core/main.c | 106 ++- src/graphics/animation.c | 186 ++++- src/platform/input.c | 47 +- src/platform/wayland.c | 185 ++--- src/utils/error.c | 7 +- src/utils/memory.c | 2 + 29 files changed, 1813 insertions(+), 1521 deletions(-) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .editorconfig delete mode 100644 bongocat.conf create mode 100644 bongocat.conf.example rename scripts/{test-nix-build.sh => test_nix_build.sh} (100%) mode change 100644 => 100755 scripts/test_toggle.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..52eb0afb --- /dev/null +++ b/.clang-format @@ -0,0 +1,113 @@ +# Clang-Format Configuration for wayland-bongocat +# Best practices for C23-compatible code + +# Base style +BasedOnStyle: LLVM + +# Language +Language: Cpp +Standard: Latest + +# Indentation +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +IndentCaseLabels: false +IndentPPDirectives: AfterHash +IndentExternBlock: NoIndent + +# Column limit +ColumnLimit: 80 + +# Alignment +AlignAfterOpenBracket: Align +AlignArrayOfStructures: Left +AlignConsecutiveAssignments: false +AlignConsecutiveBitFields: Consecutive +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: Consecutive +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true + +# Pointer and reference alignment +PointerAlignment: Right +ReferenceAlignment: Pointer +DerivePointerAlignment: false + +# Braces +BreakBeforeBraces: Attach +BraceWrapping: + AfterCaseLabel: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeElse: false + BeforeWhile: false + IndentBraces: false + +# Line breaks +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Empty +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakStringLiterals: true + +# Spaces +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInCStyleCastParentheses: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +# Includes +SortIncludes: CaseSensitive +IncludeBlocks: Regroup +IncludeCategories: + # Project headers first (quoted includes) + - Regex: '^"' + Priority: 1 + # System headers + - Regex: "^<" + Priority: 2 + +# Penalties (to control line breaking decisions) +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 + +# Miscellaneous +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +ReflowComments: true +SeparateDefinitionBlocks: Leave +InsertBraces: false +RemoveBracesLLVM: false diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..6cc0e1cb --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,77 @@ +# Clang-Tidy Configuration for wayland-bongocat +# Static analysis for C code with best practices + +# Enable checks +Checks: > + -*, + bugprone-*, + -bugprone-easily-swappable-parameters, + -bugprone-reserved-identifier, + clang-analyzer-*, + -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, + concurrency-*, + misc-*, + -misc-no-recursion, + performance-*, + portability-*, + readability-*, + -readability-identifier-length, + -readability-magic-numbers, + -readability-function-cognitive-complexity, + -readability-else-after-return, + +# Only analyze project headers, not system/lib headers +HeaderFilterRegex: '.*/(include|src)/.*\.h$' + +# Treat warnings as warnings (not errors by default) +WarningsAsErrors: '' + +# Check options +CheckOptions: + # Naming conventions (C style) + - key: readability-identifier-naming.GlobalConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.MacroDefinitionCase + value: UPPER_CASE + - key: readability-identifier-naming.FunctionCase + value: lower_case + - key: readability-identifier-naming.VariableCase + value: lower_case + - key: readability-identifier-naming.ParameterCase + value: lower_case + - key: readability-identifier-naming.StructCase + value: lower_case + - key: readability-identifier-naming.TypedefCase + value: lower_case + - key: readability-identifier-naming.TypedefSuffix + value: '_t' + - key: readability-identifier-naming.EnumCase + value: lower_case + - key: readability-identifier-naming.EnumConstantCase + value: UPPER_CASE + + # Braces around statements + - key: readability-braces-around-statements.ShortStatementLines + value: '1' + + # Function size limits (advisory) + - key: readability-function-size.LineThreshold + value: '200' + - key: readability-function-size.StatementThreshold + value: '100' + - key: readability-function-size.ParameterThreshold + value: '8' + + # Performance checks + - key: performance-unnecessary-value-param.AllowedTypes + value: '' + + # Misc settings + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: 'true' + +# System header directories to ignore +SystemHeaders: false + +# Use colors in diagnostics +UseColor: true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..23069752 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,48 @@ +# EditorConfig - Cross-editor consistency +# https://editorconfig.org + +root = true + +# Default settings for all files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +# C/C++ source and headers +[*.{c,h}] +indent_size = 2 +max_line_length = 80 + +# Makefile (requires tabs) +[Makefile] +indent_style = tab +indent_size = 8 + +# Shell scripts +[*.sh] +indent_size = 2 + +# Markdown +[*.md] +trim_trailing_whitespace = false +max_line_length = off + +# XML (Wayland protocols) +[*.xml] +indent_size = 2 + +# YAML/Config files +[*.{yml,yaml,conf}] +indent_size = 2 + +# Nix files +[*.nix] +indent_size = 2 + +# JSON +[*.json] +indent_size = 2 diff --git a/CHANGELOG.md b/CHANGELOG.md index e32136fb..0455bd79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,31 +2,60 @@ All notable changes to this project will be documented in this file. +## [1.3.0] - 2025-12-06 + +### Added + +- **Improved Anti-Aliasing** - Box filter for downscaling + proper alpha blending for smooth edges at any size +- **Interactive Keyboard Detection** - New `--interactive` mode in `bongocat-find-devices` listens for actual key presses +- **Hot-Reload Device Changes** - Changing keyboard devices in config now works without restart +- **C23 Modern Codebase** - RAII macros, `[[nodiscard]]` attributes, guard clauses throughout + +### Fixed + +- **Hot-Reload Bug** - Keyboard device path changes now properly trigger input restart +- **Memory Leaks** - Fixed realloc leaks in config parsing +- **Thread Safety** - Atomic operations for shared state between threads +- **Use-After-Free** - Fixed crash in config reload callback +- **Fullscreen Detection** - Now correctly tracks active window per workspace + +### Improved + +- **README** - Streamlined documentation with minimal config example +- **Script Reliability** - Device detection script distinguishes actual keyboards from power buttons/hotkeys +- **Code Quality** - Organized headers, consistent naming, comprehensive error handling + ## [1.2.5] - 2025-08-26 ### Added + - **Enhanced Configuration System** - New config variables for fine-tuning appearance and behavior - **Sleep Mode** - Scheduled or idle-based sleep mode with customizable timing ### Fixed + - **Fixed Positioning** - Fine-tune position, defaults to center ### Improved + - **Default Values** - Refined default configuration values for better out-of-box experience ## [1.2.4] - 2025-08-08 ### Added + - **Multi-Monitor Support** - Choose which monitor to display bongocat on using the `monitor` configuration option - **Monitor Detection** - Automatic detection of available monitors with fallback to first monitor if specified monitor not found - **XDG Output Protocol** - Proper Wayland protocol implementation for monitor identification ### Fixed + - **Memory Leaks** - Fixed memory leak in monitor configuration cleanup - **Process Cleanup** - Resolved child process cleanup warnings during shutdown - **Segmentation Fault** - Fixed crash during application exit related to Wayland resource cleanup ### Improved + - **Error Handling** - Better error messages when specified monitor is not found - **Resource Management** - Improved cleanup order for Wayland resources - **Logging** - Enhanced debug logging for monitor detection and selection @@ -34,11 +63,13 @@ All notable changes to this project will be documented in this file. ## [1.2.3] - 2025-08-02 ### Added + - **Smart Fullscreen Detection** - Automatically hides overlay during fullscreen applications for a cleaner experience - **Enhanced Artwork** - Custom-drawn bongocat image files by [@Shreyabardia](https://github.com/Shreyabardia) - **Modular Architecture** - Reorganized codebase into logical modules for better maintainability ### Improved + - **Signal Handling** - Fixed duplicate log messages during shutdown - **Code Organization** - Separated concerns into core, graphics, platform, config, and utils modules - **Build System** - Updated to support new modular structure @@ -46,18 +77,21 @@ All notable changes to this project will be documented in this file. ## [1.2.2] - Previous Release ### Added + - Automatic screen detection for all sizes and orientations - Enhanced performance optimizations ## [1.2.1] - Previous Release ### Added + - Configuration hot-reload system - Dynamic device detection ## [1.2.0] - Previous Release ### Added + - Hot-reload configuration support - Dynamic Bluetooth/USB keyboard detection - Performance optimizations with adaptive monitoring @@ -66,6 +100,7 @@ All notable changes to this project will be documented in this file. ## [1.1.x] - Previous Releases ### Added + - Multi-device support - Embedded assets - Cross-platform compatibility (x86_64 and ARM64) diff --git a/Makefile b/Makefile index 8e617198..83fbaaf4 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ CC = gcc # Build type (debug or release) BUILD_TYPE ?= release -# Base flags -BASE_CFLAGS = -std=c11 -Iinclude -Ilib -Iprotocols +# Base flags (using c2x for C23 compatibility on GCC 9+) +BASE_CFLAGS = -std=c2x -Iinclude -Ilib -Iprotocols BASE_CFLAGS += -Wall -Wextra -Wpedantic -Wformat=2 -Wstrict-prototypes BASE_CFLAGS += -Wmissing-prototypes -Wold-style-definition -Wredundant-decls BASE_CFLAGS += -Wnested-externs -Wmissing-include-dirs -Wlogical-op @@ -54,7 +54,7 @@ PROTOCOL_OBJECTS = $(C_PROTOCOL_SRC:$(PROTOCOLDIR)/%.c=$(OBJDIR)/%.o) # Target executable TARGET = $(BUILDDIR)/bongocat -.PHONY: all clean protocols embed-assets +.PHONY: all clean protocols embed-assets format format-check lint all: protocols $(TARGET) @@ -130,4 +130,43 @@ profile: release perf record -g ./$(TARGET) perf report -.PHONY: debug release install uninstall analyze memcheck profile +.PHONY: debug release install uninstall analyze memcheck profile format format-check lint + +# ============================================================================= +# CODE QUALITY TARGETS +# ============================================================================= + +# Find all project source files (exclude lib/ and protocols/) +PROJECT_SOURCES = $(shell find $(SRCDIR) -name '*.c' ! -path '*/embedded_assets.c') +PROJECT_HEADERS = $(shell find $(INCDIR) -name '*.h') +ALL_PROJECT_FILES = $(PROJECT_SOURCES) $(PROJECT_HEADERS) + +# Format all project source files +format: + @echo "Formatting source files..." + @clang-format -i $(ALL_PROJECT_FILES) + @echo "Done! Formatted $(words $(ALL_PROJECT_FILES)) files." + +# Check if formatting is correct (for CI) +format-check: + @echo "Checking code formatting..." + @clang-format --dry-run --Werror $(ALL_PROJECT_FILES) + @echo "All files are properly formatted." + +# Static analysis with clang-tidy (uses .clang-tidy config) +lint: protocols + @echo "Running static analysis..." + @clang-tidy $(PROJECT_SOURCES) -- $(CFLAGS) 2>/dev/null || true + @echo "Static analysis complete." + +# Alias for lint +analyze: lint + +# Generate compile_commands.json for IDE support (requires bear) +# Run: make compiledb +compiledb: clean + @echo "Generating compile_commands.json..." + @bear -- $(MAKE) all 2>/dev/null || (echo "Note: 'bear' not installed. Install with: sudo pacman -S bear" && false) + @echo "compile_commands.json generated!" + +.PHONY: compiledb diff --git a/README.md b/README.md index f4ea44a2..6aa6c547 100644 --- a/README.md +++ b/README.md @@ -1,493 +1,165 @@ # Bongo Cat Wayland Overlay [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) -[![Version](https://img.shields.io/badge/version-1.2.5-blue.svg)](https://github.com/saatvik333/wayland-bongocat/releases) +[![Version](https://img.shields.io/badge/version-1.3.0-blue.svg)](https://github.com/saatvik333/wayland-bongocat/releases) -A delightful Wayland overlay that displays an animated bongo cat reacting to your keyboard input! Perfect for streamers, content creators, or anyone who wants to add some fun to their desktop. +A cute Wayland overlay that shows an animated bongo cat reacting to your keyboard input. ![Demo](assets/demo.gif) -## ✨ Features +## Features -- **🎯 Real-time Animation** - Bongo cat reacts instantly to keyboard input -- **🔥 Hot-Reload Configuration** - Modify settings without restarting (v1.2.0) -- **🔄 Dynamic Device Detection** - Automatically detects Bluetooth/USB keyboards (v1.2.0) -- **⚡ Performance Optimized** - Adaptive monitoring and batch processing (v1.2.0) -- **🖥️ Screen Detection** - Automatic screen detection for all sizes and orientations (v1.2.2) -- **🎮 Smart Fullscreen Detection** - Automatically hides during fullscreen applications (v1.2.3) -- **🖥️ Multi-Monitor Support** - Choose which monitor to display on in multi-monitor setups (v1.2.4) -- **😴 Sleep Mode** - Scheduled or idle-based sleep mode with custom timing (v1.2.5) -- **🎨 Customizable Appearance** - Fine-tune position, size, alignment, and opacity -- **💾 Lightweight** - Minimal resource usage (~7MB RAM) -- **🎛️ Multi-device Support** - Monitor multiple keyboards simultaneously -- **🏗️ Cross-platform** - Works on x86_64 and ARM64 +- 🎯 Real-time keyboard animation +- 🔥 Hot-reload configuration +- 🎮 Auto-hides in fullscreen apps +- 🖥️ Multi-monitor support +- 😴 Idle/scheduled sleep mode +- ⚡ Lightweight (~7MB RAM) -## 🚀 Installation +## Quick Start -### Arch Linux (Recommended) +### Install ```bash -# Using yay +# Arch Linux yay -S bongocat -# Using paru -paru -S bongocat - -# Run immediately -bongocat --watch-config - -# Custom config with hot-reload -bongocat --config ~/.config/bongocat.conf --watch-config -``` - -### Other Distributions - -
-Fedora - -```bash -# Install dependencies -sudo dnf install wayland-devel wayland-protocols-devel gcc make - -# Build from source +# Other distros - build from source git clone https://github.com/saatvik333/wayland-bongocat.git -cd wayland-bongocat -make - -# Run -./build/bongocat -``` - -
- -
-NixOS - -```bash -# Quick start with flakes -nix run github:saatvik333/wayland-bongocat -- --watch-config - -# Install to user profile -nix profile install github:saatvik333/wayland-bongocat +cd wayland-bongocat && make ``` -📖 **For comprehensive NixOS setup, see [nix/NIXOS.md](nix/NIXOS.md)** - -
- -## 🎮 Quick Start - -### 1. Setup Permissions +### Setup Permissions ```bash -# Add your user to the input group sudo usermod -a -G input $USER -# Log out and back in for changes to take effect +# Log out and back in ``` -### 2. Find Your Input Devices +### Find Your Keyboard ```bash -# If installed via AUR -bongocat-find-devices - -# If built from source -./scripts/find_input_devices.sh +bongocat-find-devices # or ./scripts/find_input_devices.sh ``` -### 3. Run with Hot-Reload +### Run ```bash -# AUR installation bongocat --watch-config - -# From source -./build/bongocat --watch-config ``` -## ⚙️ Configuration - -Bongo Cat uses a simple configuration file format. With hot-reload enabled (`--watch-config`), changes apply instantly without restarting. +## Configuration -### Basic Configuration - -Create or edit `bongocat.conf`: +Create `~/.config/bongocat/bongocat.conf`: ```ini -# Position settings -cat_x_offset=0 # Horizontal offset from center position -cat_y_offset=0 # Vertical offset from default position -cat_align=center # Horizontal alignment in the bar (left/center/right) - -# Size settings -cat_height=80 # Height of bongo cat (10-200) - -# Visual settings -mirror_x=0 # Flip horizontally (mirror across Y axis) -mirror_y=0 # Flip vertically (mirror across X axis) - -# Anti-aliasing settings -enable_antialiasing=1 # Use bilinear interpolation for smooth scaling (0=off, 1=on) - -# Overlay settings (requires restart) -overlay_height=60 # Height of the entire overlay bar (20-300) - -overlay_opacity=150 # Background opacity (0-255) -overlay_position=top # Position on screen (top/bottom) -layer=top # Layer type (top/overlay) - -# Animation settings -idle_frame=0 # Frame to show when idle (0-3) -fps=60 # Frame rate (1-120) -keypress_duration=100 # Animation duration (ms) -test_animation_duration=200 # Test animation duration (ms) -test_animation_interval=0 # Test animation every N seconds (0=off) - -# Input devices (add multiple lines for multiple keyboards) +# ═══════════════════════════════════════════════════════════════════════════ +# BONGO CAT CONFIG - Minimal defaults, uncomment to customize +# ═══════════════════════════════════════════════════════════════════════════ + +# Position & Size +cat_height=80 +cat_align=center +# cat_x_offset=0 +# cat_y_offset=0 + +# Appearance +enable_antialiasing=1 +overlay_height=80 +overlay_opacity=0 +overlay_position=bottom +# mirror_x=0 +# mirror_y=0 + +# Input device (run bongocat-find-devices to find yours) keyboard_device=/dev/input/event4 -# keyboard_device=/dev/input/event20 # External/Bluetooth keyboard - -# Multi-monitor support -monitor=eDP-1 # Specify which monitor to display on (optional) - -# Sleep mode settings -enable_scheduled_sleep=0 # Enable scheduled sleep mode (0=off, 1=on) -sleep_begin=20:00 # Begin of sleeping phase (HH:MM) -sleep_end=06:00 # End of sleeping phase (HH:MM) -idle_sleep_timeout=0 # Inactivity timeout before sleep (seconds, 0=off) - -# Debug -enable_debug=0 # Show debug messages -``` -### Configuration Reference - -| Setting | Type | Range | Default | Description | -|---------------------------|--------|---------------------------|--------------------|--------------------------------------------------------------| -| `cat_align` | String | "left"/"center"/"right" | "center" | Horizontal alignment in the bar | -| `cat_height` | Integer| 10-200 | 40 | Height of bongo cat in pixels | -| `cat_x_offset` | Integer| -9999 to 9999 | 100 | Horizontal offset from center | -| `cat_y_offset` | Integer| -9999 to 9999 | 10 | Vertical offset from center | -| `enable_antialiasing` | Boolean| 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling | -| `enable_debug` | Boolean| 0 or 1 | 1 | Enable debug logging | -| `enable_scheduled_sleep` | Boolean| 0 or 1 | 0 | Enable Sleep mode | -| `fps` | Integer| 1-120 | 60 | Animation frame rate | -| `idle_frame` | Integer| 0-3 | 0 | Frame to show when idle (0=both up, 1=left down, 2=right down, 3=both down) | -| `idle_sleep_timeout` | Integer| 0+ | 0 | Duration of user inactivity before entering sleep (seconds) | -| `keyboard_device` | String | Valid path | "/dev/input/event4"| Input device path (multiple allowed) | -| `keypress_duration` | Integer| 10-5000 | 100 | Animation duration after keypress (ms) | -| `mirror_x` | Boolean| 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | -| `mirror_y` | Boolean| 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | -| `monitor` | String | Monitor name | Auto-detect | Monitor to display on (e.g., "eDP-1", "HDMI-A-1") | -| `overlay_height` | Integer| 20-300 | 50 | Height of the entire overlay bar | -| `overlay_opacity` | Integer| 0-255 | 150 | Background opacity (0=transparent) | -| `overlay_position` | String | "top" or "bottom" | "top" | Position of overlay on screen | -| `sleep_begin` | String | "00:00" - "23:59" | "00:00" | Begin of the sleeping phase | -| `sleep_end` | String | "00:00" - "23:59" | "00:00" | End of the sleeping phase | -| `test_animation_duration` | Integer| 10-5000 | 200 | Test animation duration (ms) | -| `test_animation_interval` | Integer| 0-3600 | 0 | Test animation interval (seconds, 0=disabled) | - -## 🔧 Usage - -### Command Line Options - -```bash -bongocat [OPTIONS] - -Options: - -h, --help Show this help message - -v, --version Show version information - -c, --config Specify config file (default: bongocat.conf) - -w, --watch-config Watch config file for changes and reload automatically - -t, --toggle Toggle bongocat on/off (start if not running, stop if running) -``` - -### Examples - -```bash -# Basic usage -bongocat - -# With hot-reload (recommended) -bongocat --watch-config - -# Custom config with hot-reload -bongocat --config ~/.config/bongocat.conf --watch-config - -# Debug mode -bongocat --watch-config --config bongocat.conf - -# Toggle mode -bongocat --toggle -``` - -## 🛠️ Building from Source - -### Prerequisites - -**Required:** - -- Wayland compositor with layer shell support -- C11 compiler (GCC 4.9+ or Clang 3.4+) -- Make -- libwayland-client -- wayland-protocols -- wayland-scanner -### Build Process - -```bash -# Clone repository -git clone https://github.com/saatvik333/wayland-bongocat.git -cd wayland-bongocat - -# Build (production) -make -# Build (debug) -make debug +# Multi-monitor (optional - auto-detects by default) +# monitor=eDP-1 -# Clean -make clean +# Sleep mode (optional) +# idle_sleep_timeout=300 +# enable_scheduled_sleep=0 +# sleep_begin=22:00 +# sleep_end=06:00 ``` -The build process automatically: +### Options Reference -1. Generates Wayland protocol files -2. Compiles with optimizations and security hardening -3. Embeds assets directly in the binary -4. Links with required libraries - -## 🔍 Device Discovery - -The `bongocat-find-devices` tool provides professional input device analysis with a clean, user-friendly interface: +
+Click to expand all options + +| Option | Values | Default | Description | +| --------------------- | ----------------- | ------- | -------------------- | +| `cat_height` | 10-200 | 80 | Cat size in pixels | +| `cat_align` | left/center/right | center | Horizontal alignment | +| `cat_x_offset` | any int | 0 | Horizontal offset | +| `cat_y_offset` | any int | 0 | Vertical offset | +| `enable_antialiasing` | 0/1 | 1 | Smooth scaling | +| `overlay_height` | 20-300 | 80 | Bar height | +| `overlay_opacity` | 0-255 | 0 | Background opacity | +| `overlay_position` | top/bottom | bottom | Screen position | +| `layer` | top/overlay | top | Layer type | +| `keyboard_device` | path | — | Device to monitor | +| `monitor` | name | auto | Target monitor | +| `fps` | 1-120 | 60 | Frame rate | +| `idle_sleep_timeout` | seconds | 0 | Sleep after idle | +| `mirror_x` | 0/1 | 0 | Flip horizontal | +| `mirror_y` | 0/1 | 0 | Flip vertical | -```bash -$ bongocat-find-devices - -╔══════════════════════════════════════════════════════════════════╗ -║ Wayland Bongo Cat - Input Device Discovery v1.2.0 ║ -╚══════════════════════════════════════════════════════════════════╝ - -[SCAN] Scanning for input devices... - -[DEVICES] Found Input Devices: -┌─────────────────────────────────────────────────────────────────┐ -│ Device: AT Translated Set 2 keyboard │ -│ Path: /dev/input/event4 │ -│ Type: Keyboard │ -│ Status: [OK] Accessible │ -└─────────────────────────────────────────────────────────────────┘ - -┌─────────────────────────────────────────────────────────────────┐ -│ Device: Logitech MX Keys │ -│ Path: /dev/input/event20 │ -│ Type: Keyboard (Bluetooth) │ -│ Status: [OK] Accessible │ -└─────────────────────────────────────────────────────────────────┘ - -[CONFIG] Configuration Suggestions: -Add these lines to your bongocat.conf: - -keyboard_device=/dev/input/event4 # AT Translated Set 2 keyboard -keyboard_device=/dev/input/event20 # Logitech MX Keys -``` +
-### Advanced Features +## Command Line ```bash -# Show all input devices (including mice, touchpads) -bongocat-find-devices --all - -# Generate complete configuration file -bongocat-find-devices --generate-config > bongocat.conf - -# Test device responsiveness (requires root) -sudo bongocat-find-devices --test - -# Show detailed device information -bongocat-find-devices --verbose +bongocat [OPTIONS] -# Get help and usage information -bongocat-find-devices --help + -c, --config FILE Config file path + -w, --watch-config Auto-reload on config change + -t, --toggle Start/stop toggle + -h, --help Help + -v, --version Version ``` -### Key Features - -- **Smart Detection** - Automatically identifies keyboards vs other input devices -- **Device Classification** - Distinguishes between built-in, Bluetooth, and USB keyboards -- **Permission Checking** - Verifies device accessibility and provides fix suggestions -- **Config Generation** - Creates ready-to-use configuration snippets -- **Device Testing** - Integrated evtest functionality for troubleshooting -- **Professional UI** - Clean, colorized output with status indicators -- **Error Handling** - Comprehensive error messages and troubleshooting guidance - -## 📊 Performance - -### System Requirements - -- **CPU:** Any modern x86_64 or ARM64 processor -- **RAM:** ~7MB runtime usage -- **Storage:** ~0.4MB executable size -- **Compositor:** Wayland with layer shell protocol support - -### Tested Compositors - -- ✅ **Hyprland** - Full support -- ✅ **Sway** - Full support -- ✅ **Wayfire** - Compatible -- ✅ **KDE Wayland** - Compatiable -- ❌ **GNOME Wayland** - Unsupported - -## 🐛 Troubleshooting - -### Common Issues +## Troubleshooting
-Permission denied accessing /dev/input/eventX - -**Solution:** +Permission denied on input device ```bash -# Add user to input group (recommended) sudo usermod -a -G input $USER -# Log out and back in - -# Or create udev rule -echo 'KERNEL=="event*", GROUP="input", MODE="0664"' | sudo tee /etc/udev/rules.d/99-input.rules -sudo udevadm control --reload-rules +# Then log out and back in ```
-Keyboard input not detected - -**Diagnosis:** - -```bash -# Find correct device -bongocat-find-devices +Cat not responding to keyboard -# Test device manually -sudo evtest /dev/input/event4 -``` - -**Solution:** Update `keyboard_device` in `bongocat.conf` with correct path. +1. Run `bongocat-find-devices` to find correct device +2. Update `keyboard_device` in config +3. Restart bongocat
-Overlay not visible or clickable - -**Check:** - -- Ensure compositor supports `wlr-layer-shell-unstable-v1` -- Verify `WAYLAND_DISPLAY` environment variable is set -- Try different `overlay_opacity` values +Not showing on correct monitor -**Tested compositors:** Hyprland, Sway, Wayfire +Add `monitor=YOUR_MONITOR` to config. Find monitor names with `wlr-randr` or `hyprctl monitors`.
-
-Multi-monitor setup issues - -**Finding monitor names:** - -```bash -# Using wlr-randr (recommended) -wlr-randr - -# Using swaymsg (Sway only) -swaymsg -t get_outputs - -# Check bongocat logs for detected monitors -bongocat --watch-config # Look for "xdg-output name received" messages -``` - -**Configuration:** - -```ini -# Specify exact monitor name -monitor=eDP-1 # Laptop screen -monitor=HDMI-A-1 # External HDMI monitor -monitor=DP-1 # DisplayPort monitor -``` - -**Troubleshooting:** - -- If monitor name is wrong, bongocat falls back to first available monitor -- Monitor names are case-sensitive -- Remove or comment out `monitor=` line to use auto-detection -
- -
-Build errors - -**Common fixes:** - -- Install development packages: `libwayland-dev wayland-protocols` -- Ensure C11 compiler: GCC 4.9+ or Clang 3.4+ -- Install `wayland-scanner` package -
- -### Getting Help - -1. Enable debug logging: `bongocat --watch-config` (ensure `enable_debug=1`) -2. Check compositor compatibility -3. Verify all dependencies are installed -4. Test with minimal configuration - -## 🏗️ Architecture - -### Project Structure - -``` -wayland-bongocat/ -├── src/ # Source code -│ ├── main.c # Application entry point -│ ├── config.c # Configuration management -│ ├── config_watcher.c # Hot-reload system (v1.2.1) -│ ├── input.c # Input device monitoring -│ ├── wayland.c # Wayland protocol handling -│ └── ... -├── include/ # Header files -├── scripts/ # Build and utility scripts -├── assets/ # Animation frames -├── protocols/ # Generated Wayland protocols -└── nix/ # NixOS integration -``` - -## 🤝 Contributing - -This project follows industry best practices with a modular architecture. Contributions are welcome! - -### Development Setup +## Building ```bash git clone https://github.com/saatvik333/wayland-bongocat.git cd wayland-bongocat -make debug +make # Release build +make debug # Debug build ``` -### Code Standards - -- C11 standard compliance -- Comprehensive error handling -- Memory safety with leak detection -- Extensive documentation - -## 📄 License - -MIT License - see [LICENSE](LICENSE) file for details. - -## 🙏 Acknowledgments - -Built with ❤️ for the Wayland community. Special thanks to: - -- Redditor: [u/akonzu](https://www.reddit.com/user/akonzu/) for the inspiration -- [@Shreyabardia](https://github.com/Shreyabardia) for the beautiful custom-drawn bongo cat artwork -- All the contributors and users +**Requirements:** wayland-client, wayland-protocols, gcc/clang, make ---- +## License -**₍^. .^₎ Wayland Bongo Cat Overlay v1.2.5** - Making desktops more delightful, one keystroke at a time! +MIT License - see [LICENSE](LICENSE) diff --git a/bongocat.conf b/bongocat.conf deleted file mode 100644 index e7d7f69a..00000000 --- a/bongocat.conf +++ /dev/null @@ -1,95 +0,0 @@ -# Bongo Cat Configuration File -# Edit these values to customize your bongo cat overlay - -# Position settings (in pixels) -# cat_x_offset: Horizontal offset from center position -# Positive values move right, negative values move left -cat_x_offset=0 - -# cat_y_offset: Vertical offset from default position -# Positive values move down, negative values move up -cat_y_offset=0 - -# cat_align: Horizontal alignment in the bar (default: "center") -# Options: "center", "left" or "right" -cat_align=center - -# Mirroring options -# mirror_x: Flip cat horizontally (mirror across Y axis) | 0 = off, 1 = on -mirror_x=0 -# mirror_y: Flip cat vertically (mirror across X axis) | 0 = off, 1 = on -mirror_y=0 - -# Anti-aliasing settings -# enable_antialiasing: Use bilinear interpolation for smooth scaling | 0 = off, 1 = on -# When enabled, provides smoother edges when scaling the cat image -enable_antialiasing=1 - -# Size settings -# cat_height: Height of the bongo cat in pixels -# Width is automatically calculated to maintain aspect ratio -cat_height=60 - -# NOTE: OVERLAY SETTINGS DOESN'T WORK WITH HOT RELOAD, NEEDS BONGOCAT RESTART -# DRAWN LAYERS GETS GLITCHY SOMETIMES, BETTER TO RESTART WHEN THESE CHANGE -# Overlay settings -# overlay_height: Height of the entire overlay bar -overlay_height=60 -# overlay_position: Position of the overlay on screen -# Options: "top" or "bottom" -overlay_position=top - -# Animation settings -# idle_frame: Which frame to use when idle (0, 1, 2, 3) -# 0 = both paws up, 1 = left paw down, 2 = right paw down, 3 = both paw down -idle_frame=0 - -# Sleep Mode settings -# enable_scheduled_sleep: When on, animations will be paused and show sleep frame (0 = off, 1 = on) -# Requires both sleep_begin and sleep_end to be defined -enable_scheduled_sleep=0 -# Start time for scheduled sleep mode (24-hour format: hh:mm) -sleep_begin=21:00 -# End time for scheduled sleep mode (24-hour format: hh:mm) -sleep_end=06:00 - -# Duration of user inactivity before entering sleep mode (in seconds) -# Set to 0 to disable idle-based sleep -idle_sleep_timeout=0 - -# Animation timing (in milliseconds) -# keypress_duration: How long to show animation after keypress -keypress_duration=100 - -# test_animation_duration: How long to show test animation -test_animation_duration=200 - -# test_animation_interval: How often to trigger test animation (seconds) -# Set to 0 to disable test animations -test_animation_interval=0 - -# Frame rate settings -# fps: Animation frame rate (frames per second) -fps=60 - -# Transparency settings -# overlay_opacity: Opacity of the overlay background (0-255) -# 0 = fully transparent, 255 = fully opaque -overlay_opacity=150 - -# Debug settings -# enable_debug: Show debug messages (0 = off, 1 = on) -enable_debug=0 - -# Input devices (you can specify multiple devices) -# Use keyboard_device for each device you want to monitor -# Examples: -keyboard_device=/dev/input/event4 -# keyboard_device=/dev/input/event20 # External bluetooth keyboard (commented out - doesn't exist) -# keyboard_device=/dev/input/event5 # Another input device - -# Multi-monitor support -# Specify which monitor to display bongocat on (optional) -# Use wlr-randr or swaymsg -t get_outputs to find monitor names -# If not specified or monitor not found, uses first available monitor -monitor = eDP-1 \ No newline at end of file diff --git a/bongocat.conf.example b/bongocat.conf.example new file mode 100644 index 00000000..2fe33256 --- /dev/null +++ b/bongocat.conf.example @@ -0,0 +1,81 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# BONGO CAT CONFIG - Default Configuration +# ═══════════════════════════════════════════════════════════════════════════════ +# Save this file to: ~/.config/bongocat/bongocat.conf +# Run with: bongocat --watch-config +# ═══════════════════════════════════════════════════════════════════════════════ + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ APPEARANCE │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +# Cat size and position +cat_height=80 +cat_align=center +cat_x_offset=0 +cat_y_offset=0 + +# Smooth scaling (recommended) +enable_antialiasing=1 + +# Flip the cat +mirror_x=0 +mirror_y=0 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ OVERLAY BAR │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +# Bar dimensions +overlay_height=80 + +# Background: 0=transparent, 255=opaque +overlay_opacity=0 + +# Position: top, bottom +overlay_position=bottom + +# Layer: top (above windows), overlay (always visible) +layer=top + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ INPUT DEVICES │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +# Find your device with: bongocat-find-devices +# Add multiple lines for multiple keyboards +keyboard_device=/dev/input/event4 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ MULTI-MONITOR (optional) │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +# Leave commented to auto-detect +# Find monitor names with: wlr-randr or hyprctl monitors +# monitor=eDP-1 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ ANIMATION │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +fps=60 +idle_frame=0 +keypress_duration=100 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ SLEEP MODE (optional) │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +# Hide after N seconds of no keyboard activity (0=disabled) +idle_sleep_timeout=0 + +# Scheduled sleep (requires enable_scheduled_sleep=1) +enable_scheduled_sleep=0 +sleep_begin=22:00 +sleep_end=06:00 + +# ┌─────────────────────────────────────────────────────────────────────────────┐ +# │ DEBUG │ +# └─────────────────────────────────────────────────────────────────────────────┘ + +enable_debug=0 diff --git a/include/config/config.h b/include/config/config.h index a35f59b4..ec845cd6 100644 --- a/include/config/config.h +++ b/include/config/config.h @@ -4,61 +4,91 @@ #include "core/bongocat.h" #include "utils/error.h" +#include + +// ============================================================================= +// CONFIGURATION ENUMS +// ============================================================================= + typedef enum { - POSITION_TOP = 0, - POSITION_BOTTOM = 1 + POSITION_TOP = 0, + POSITION_BOTTOM = 1 } overlay_position_t; typedef enum { - LAYER_TOP = 0, - LAYER_OVERLAY = 1 + LAYER_TOP = 0, + LAYER_OVERLAY = 1 } layer_type_t; -typedef struct { - int hour; - int min; -} config_time_t; - typedef enum { - ALIGN_LEFT = -1, - ALIGN_CENTER = 0, - ALIGN_RIGHT = 1, + ALIGN_LEFT = -1, + ALIGN_CENTER = 0, + ALIGN_RIGHT = 1, } align_type_t; +// ============================================================================= +// CONFIGURATION TYPES +// ============================================================================= + +typedef struct { + int hour; + int min; +} config_time_t; + typedef struct { - int screen_width; - char *output_name; - int bar_height; - const char *asset_paths[NUM_FRAMES]; - char **keyboard_devices; - int num_keyboard_devices; - int cat_x_offset; - int cat_y_offset; - int cat_height; - int overlay_height; - int idle_frame; - int keypress_duration; - int test_animation_duration; - int test_animation_interval; - int fps; - int overlay_opacity; - int mirror_x; // reflect across Y axis (horizontal flip) - int mirror_y; // reflect across X axis (vertical flip) - int enable_antialiasing; // enable bilinear interpolation for smooth scaling - int enable_debug; - layer_type_t layer; - overlay_position_t overlay_position; - - int enable_scheduled_sleep; - config_time_t sleep_begin; - config_time_t sleep_end; - int idle_sleep_timeout_sec; - align_type_t cat_align; + // Display settings + int screen_width; + char *output_name; + int bar_height; + int overlay_height; + int overlay_opacity; + layer_type_t layer; + overlay_position_t overlay_position; + + // Cat appearance + const char *asset_paths[NUM_FRAMES]; + int cat_x_offset; + int cat_y_offset; + int cat_height; + int mirror_x; // Reflect across Y axis (horizontal flip) + int mirror_y; // Reflect across X axis (vertical flip) + int enable_antialiasing; // Enable bilinear interpolation + align_type_t cat_align; + + // Animation timing + int idle_frame; + int keypress_duration; + int test_animation_duration; + int test_animation_interval; + int fps; + + // Input devices + char **keyboard_devices; + int num_keyboard_devices; + + // Sleep schedule + int enable_scheduled_sleep; + config_time_t sleep_begin; + config_time_t sleep_end; + int idle_sleep_timeout_sec; + + // Debug + int enable_debug; } config_t; -bongocat_error_t load_config(config_t *config, const char *config_file_path); +// ============================================================================= +// CONFIGURATION FUNCTIONS +// ============================================================================= + +// Load configuration - returns error code (must be checked) +BONGOCAT_NODISCARD bongocat_error_t load_config(config_t *config, + const char *config_file_path); + +// Get screen width - returns 0 on failure (should be checked) +BONGOCAT_NODISCARD int get_screen_width(void); + +// Cleanup functions void config_cleanup(void); void config_cleanup_full(config_t *config); -int get_screen_width(void); -#endif // CONFIG_H \ No newline at end of file +#endif // CONFIG_H \ No newline at end of file diff --git a/include/core/bongocat.h b/include/core/bongocat.h index db93c8ca..190cd26f 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -1,70 +1,96 @@ #ifndef BONGOCAT_H #define BONGOCAT_H -#include -#include +// POSIX feature test macro - must be before includes +#define _POSIX_C_SOURCE 200809L + +#include "../lib/stb_image.h" + +#include +#include +#include +#include #include #include +#include +#include #include -#include -#include -#include +#include +#include #include #include #include -#include -#include -#include -#include - -#define _POSIX_C_SOURCE 200809L +#include #include -#include "../lib/stb_image.h" +// ============================================================================= +// VERSION +// ============================================================================= + +#define BONGOCAT_VERSION "1.3.0" -// Version -#define BONGOCAT_VERSION "1.2.5" +// ============================================================================= +// COMPILE-TIME CONSTANTS +// ============================================================================= -// Common constants -#define NUM_FRAMES 4 +// Display defaults #define DEFAULT_SCREEN_WIDTH 1920 -#define DEFAULT_BAR_HEIGHT 40 -#define MAX_OUTPUTS 8 // Maximum monitor outputs to store -#define CAT_IMAGE_WIDTH 864 +#define DEFAULT_BAR_HEIGHT 40 +#define MAX_OUTPUTS 8 // Maximum monitor outputs to store + +// Frame constants +#define NUM_FRAMES 4 +#define CAT_IMAGE_WIDTH 864 #define CAT_IMAGE_HEIGHT 360 -#define BONGOCAT_FRAME_BOTH_UP 0 -#define BONGOCAT_FRAME_LEFT_DOWN 1 +// Frame indices +#define BONGOCAT_FRAME_BOTH_UP 0 +#define BONGOCAT_FRAME_LEFT_DOWN 1 #define BONGOCAT_FRAME_RIGHT_DOWN 2 -#define BONGOCAT_FRAME_BOTH_DOWN 3 +#define BONGOCAT_FRAME_BOTH_DOWN 3 -// Config watcher constants +// Inotify buffer sizing #define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event)) -#define INOTIFY_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16)) +#define INOTIFY_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16)) + +// ============================================================================= +// TYPE DEFINITIONS +// ============================================================================= -// Config watcher structure +// Config watcher for hot-reload support typedef struct { - int inotify_fd; - int watch_fd; - pthread_t watcher_thread; - bool watching; - char *config_path; - void (*reload_callback)(const char *config_path); + int inotify_fd; + int watch_fd; + pthread_t watcher_thread; + bool watching; + char *config_path; + void (*reload_callback)(const char *config_path); } ConfigWatcher; -// Output monitor reference structure +// Output monitor reference for multi-monitor support typedef struct { - struct wl_output *wl_output; - struct zxdg_output_v1 *xdg_output; - uint32_t name; // Registry name - char name_str[128]; // From xdg-output - bool name_received; + struct wl_output *wl_output; + struct zxdg_output_v1 *xdg_output; + uint32_t name; // Registry name + char name_str[128]; // From xdg-output + bool name_received; } output_ref_t; -// Config watcher function declarations -int config_watcher_init(ConfigWatcher *watcher, const char *config_path, void (*callback)(const char *)); +// ============================================================================= +// CONFIG WATCHER FUNCTIONS +// ============================================================================= + +// Initialize config watcher - returns 0 on success, -1 on failure +int config_watcher_init(ConfigWatcher *watcher, const char *config_path, + void (*callback)(const char *)); + +// Start watching for config changes void config_watcher_start(ConfigWatcher *watcher); + +// Stop watching for config changes void config_watcher_stop(ConfigWatcher *watcher); + +// Cleanup config watcher resources void config_watcher_cleanup(ConfigWatcher *watcher); -#endif // BONGOCAT_H \ No newline at end of file +#endif // BONGOCAT_H \ No newline at end of file diff --git a/include/graphics/animation.h b/include/graphics/animation.h index 1aa5cfb6..b020ad85 100644 --- a/include/graphics/animation.h +++ b/include/graphics/animation.h @@ -1,25 +1,53 @@ #ifndef ANIMATION_H #define ANIMATION_H -#include "core/bongocat.h" #include "config/config.h" +#include "core/bongocat.h" #include "utils/error.h" +#include +#include + +// ============================================================================= +// ANIMATION STATE +// ============================================================================= + +// Frame images and dimensions extern unsigned char *anim_imgs[NUM_FRAMES]; -extern int anim_width[NUM_FRAMES], anim_height[NUM_FRAMES]; +extern int anim_width[NUM_FRAMES]; +extern int anim_height[NUM_FRAMES]; + +// Current frame and synchronization extern int anim_index; extern pthread_mutex_t anim_lock; -bongocat_error_t animation_init(config_t *config); -bongocat_error_t animation_start(void); +// ============================================================================= +// ANIMATION LIFECYCLE +// ============================================================================= + +// Initialize animation system - must be checked +BONGOCAT_NODISCARD bongocat_error_t animation_init(config_t *config); + +// Start animation thread - must be checked +BONGOCAT_NODISCARD bongocat_error_t animation_start(void); + +// Cleanup animation resources void animation_cleanup(void); + +// Trigger key press animation void animation_trigger(void); +// ============================================================================= +// RENDERING UTILITIES +// ============================================================================= + +// Blit scaled image to destination buffer void blit_image_scaled(uint8_t *dest, int dest_w, int dest_h, - unsigned char *src, int src_w, int src_h, - int offset_x, int offset_y, int target_w, int target_h); + unsigned char *src, int src_w, int src_h, int offset_x, + int offset_y, int target_w, int target_h); -void draw_rect(uint8_t *dest, int width, int height, int x, int y, - int w, int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a); +// Draw filled rectangle +void draw_rect(uint8_t *dest, int width, int height, int x, int y, int w, int h, + uint8_t r, uint8_t g, uint8_t b, uint8_t a); -#endif // ANIMATION_H \ No newline at end of file +#endif // ANIMATION_H \ No newline at end of file diff --git a/include/graphics/embedded_assets.h b/include/graphics/embedded_assets.h index 8049963b..213c3970 100644 --- a/include/graphics/embedded_assets.h +++ b/include/graphics/embedded_assets.h @@ -16,4 +16,4 @@ extern const size_t bongo_cat_right_down_png_size; extern const unsigned char bongo_cat_both_down_png[]; extern const size_t bongo_cat_both_down_png_size; -#endif // EMBEDDED_ASSETS_H +#endif // EMBEDDED_ASSETS_H diff --git a/include/platform/input.h b/include/platform/input.h index e23e85bd..5bc13a20 100644 --- a/include/platform/input.h +++ b/include/platform/input.h @@ -4,12 +4,29 @@ #include "core/bongocat.h" #include "utils/error.h" -extern int *any_key_pressed; +#include -bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, - int enable_debug); -bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, - int enable_debug); +// ============================================================================= +// INPUT STATE +// ============================================================================= + +// Shared memory for key press state (thread-safe) +extern atomic_int *any_key_pressed; + +// ============================================================================= +// INPUT MONITORING FUNCTIONS +// ============================================================================= + +// Start input monitoring - must be checked +BONGOCAT_NODISCARD bongocat_error_t input_start_monitoring(char **device_paths, + int num_devices, + int enable_debug); + +// Restart input monitoring with new devices - must be checked +BONGOCAT_NODISCARD bongocat_error_t input_restart_monitoring( + char **device_paths, int num_devices, int enable_debug); + +// Cleanup input monitoring resources void input_cleanup(void); -#endif // INPUT_H \ No newline at end of file +#endif // INPUT_H \ No newline at end of file diff --git a/include/platform/wayland.h b/include/platform/wayland.h index f6b7993f..86f99ed5 100644 --- a/include/platform/wayland.h +++ b/include/platform/wayland.h @@ -1,34 +1,69 @@ #ifndef WAYLAND_H #define WAYLAND_H -#include "core/bongocat.h" +#include "../protocols/xdg-shell-client-protocol.h" +#include "../protocols/zwlr-layer-shell-v1-client-protocol.h" #include "config/config.h" +#include "core/bongocat.h" #include "utils/error.h" + #include -#include "../protocols/zwlr-layer-shell-v1-client-protocol.h" -#include "../protocols/xdg-shell-client-protocol.h" +#include + +// ============================================================================= +// WAYLAND GLOBAL STATE +// ============================================================================= -// Wayland globals +// Core Wayland objects extern struct wl_display *display; extern struct wl_compositor *compositor; extern struct wl_shm *shm; +extern struct wl_output *output; + +// Layer shell objects extern struct zwlr_layer_shell_v1 *layer_shell; +extern struct zwlr_layer_surface_v1 *layer_surface; extern struct xdg_wm_base *xdg_wm_base; -extern struct wl_output *output; + +// Surface and buffer extern struct wl_surface *surface; extern struct wl_buffer *buffer; -extern struct zwlr_layer_surface_v1 *layer_surface; extern uint8_t *pixels; -extern bool configured; -extern bool fullscreen_detected; -bongocat_error_t wayland_init(config_t *config); -bongocat_error_t wayland_run(volatile sig_atomic_t *running); +// Thread-safe state flags +extern atomic_bool configured; +extern atomic_bool fullscreen_detected; + +// ============================================================================= +// WAYLAND LIFECYCLE FUNCTIONS +// ============================================================================= + +// Initialize Wayland connection - must be checked +BONGOCAT_NODISCARD bongocat_error_t wayland_init(config_t *config); + +// Run Wayland event loop - must be checked +BONGOCAT_NODISCARD bongocat_error_t wayland_run(volatile sig_atomic_t *running); + +// Cleanup Wayland resources void wayland_cleanup(void); + +// ============================================================================= +// WAYLAND UTILITY FUNCTIONS +// ============================================================================= + +// Update configuration (hot-reload support) void wayland_update_config(config_t *config); + +// Draw the overlay bar void draw_bar(void); -int create_shm(int size); -int wayland_get_screen_width(void); -const char* wayland_get_current_layer_name(void); -#endif // WAYLAND_H \ No newline at end of file +// Create shared memory buffer - returns fd or -1 on error +BONGOCAT_NODISCARD int create_shm(int size); + +// Get detected screen width +BONGOCAT_NODISCARD int wayland_get_screen_width(void); + +// Get current layer name for logging +BONGOCAT_NODISCARD const char *wayland_get_current_layer_name(void); + +#endif // WAYLAND_H \ No newline at end of file diff --git a/include/utils/error.h b/include/utils/error.h index 925138fc..4cdb80b6 100644 --- a/include/utils/error.h +++ b/include/utils/error.h @@ -1,57 +1,99 @@ #ifndef ERROR_H #define ERROR_H +#include +#include +#include #include #include -#include #include -// Error codes +// ============================================================================= +// C23 COMPATIBILITY MACROS +// ============================================================================= + +// Nodiscard for functions that return values that must be used +#if __STDC_VERSION__ >= 202311L +# define BONGOCAT_NODISCARD [[nodiscard]] +#else +# define BONGOCAT_NODISCARD __attribute__((warn_unused_result)) +#endif + +// Null pointer (C23 nullptr or fallback) +#if __STDC_VERSION__ >= 202311L +# define BONGOCAT_NULLPTR nullptr +#else +# define BONGOCAT_NULLPTR NULL +#endif + +// Unreachable code hint for optimizer +#if __STDC_VERSION__ >= 202311L +# define BONGOCAT_UNREACHABLE() unreachable() +#else +# define BONGOCAT_UNREACHABLE() __builtin_unreachable() +#endif + +// ============================================================================= +// ERROR CODES +// ============================================================================= + typedef enum { - BONGOCAT_SUCCESS = 0, - BONGOCAT_ERROR_MEMORY, - BONGOCAT_ERROR_FILE_IO, - BONGOCAT_ERROR_WAYLAND, - BONGOCAT_ERROR_CONFIG, - BONGOCAT_ERROR_INPUT, - BONGOCAT_ERROR_ANIMATION, - BONGOCAT_ERROR_THREAD, - BONGOCAT_ERROR_INVALID_PARAM + BONGOCAT_SUCCESS = 0, + BONGOCAT_ERROR_MEMORY, + BONGOCAT_ERROR_FILE_IO, + BONGOCAT_ERROR_WAYLAND, + BONGOCAT_ERROR_CONFIG, + BONGOCAT_ERROR_INPUT, + BONGOCAT_ERROR_ANIMATION, + BONGOCAT_ERROR_THREAD, + BONGOCAT_ERROR_INVALID_PARAM } bongocat_error_t; -// Error handling macros -#define BONGOCAT_CHECK_NULL(ptr, error_code) \ - do { \ - if ((ptr) == NULL) { \ - bongocat_log_error("NULL pointer: %s at %s:%d", #ptr, __FILE__, __LINE__); \ - return (error_code); \ - } \ - } while(0) - -#define BONGOCAT_CHECK_ERROR(condition, error_code, message) \ - do { \ - if (condition) { \ - bongocat_log_error("%s at %s:%d", message, __FILE__, __LINE__); \ - return (error_code); \ - } \ - } while(0) - -#define BONGOCAT_SAFE_FREE(ptr) \ - do { \ - if (ptr) { \ - free(ptr); \ - ptr = NULL; \ - } \ - } while(0) - -// Logging functions +// ============================================================================= +// GUARD CLAUSE MACROS +// ============================================================================= + +// Guard clause for null pointer - returns early with error +#define BONGOCAT_CHECK_NULL(ptr, error_code) \ + do { \ + if ((ptr) == BONGOCAT_NULLPTR) { \ + bongocat_log_error("NULL pointer: %s at %s:%d", #ptr, __FILE__, \ + __LINE__); \ + return (error_code); \ + } \ + } while (0) + +// Guard clause for error conditions - returns early with error +#define BONGOCAT_CHECK_ERROR(condition, error_code, message) \ + do { \ + if (condition) { \ + bongocat_log_error("%s at %s:%d", message, __FILE__, __LINE__); \ + return (error_code); \ + } \ + } while (0) + +// Guard clause for boolean conditions - returns early with value +#define BONGOCAT_GUARD(condition, return_value) \ + do { \ + if (condition) { \ + return (return_value); \ + } \ + } while (0) + +// ============================================================================= +// LOGGING FUNCTIONS +// ============================================================================= + void bongocat_log_error(const char *format, ...); void bongocat_log_warning(const char *format, ...); void bongocat_log_info(const char *format, ...); void bongocat_log_debug(const char *format, ...); -// Error handling initialization +// ============================================================================= +// ERROR HANDLING +// ============================================================================= + void bongocat_error_init(int enable_debug); -const char* bongocat_error_string(bongocat_error_t error); +BONGOCAT_NODISCARD const char *bongocat_error_string(bongocat_error_t error); -#endif // ERROR_H \ No newline at end of file +#endif // ERROR_H \ No newline at end of file diff --git a/include/utils/memory.h b/include/utils/memory.h index 329fab2d..de732242 100644 --- a/include/utils/memory.h +++ b/include/utils/memory.h @@ -1,52 +1,129 @@ #ifndef MEMORY_H #define MEMORY_H -#include -#include #include "utils/error.h" -// Memory pool for efficient allocation +#include +#include + +// ============================================================================= +// C23 MODERN FEATURES +// ============================================================================= + +// Nodiscard attribute for functions that return values that must be used +#if __STDC_VERSION__ >= 202311L +# define BONGOCAT_NODISCARD [[nodiscard]] +#else +# define BONGOCAT_NODISCARD __attribute__((warn_unused_result)) +#endif + +// Null pointer (C23 nullptr or fallback) +#if __STDC_VERSION__ >= 202311L +# define BONGOCAT_NULLPTR nullptr +#else +# define BONGOCAT_NULLPTR NULL +#endif + +// ============================================================================= +// RAII CLEANUP MACROS +// ============================================================================= + +// Cleanup function for auto-freeing malloc'd memory +static inline void bongocat_auto_free_impl(void *ptr) { + void **p = (void **)ptr; + if (*p) { + free(*p); + *p = BONGOCAT_NULLPTR; + } +} + +// Auto-free heap allocations when variable goes out of scope +#define BONGOCAT_AUTO_FREE __attribute__((cleanup(bongocat_auto_free_impl))) + +// Safe free macro that nullifies pointer after freeing +#define BONGOCAT_SAFE_FREE(ptr) \ + do { \ + if ((ptr)) { \ + bongocat_free((void *)(ptr)); \ + (ptr) = BONGOCAT_NULLPTR; \ + } \ + } while (0) + +// Forward declaration for pool cleanup +struct memory_pool; +static inline void bongocat_auto_pool_impl(struct memory_pool **pool); + +// Auto-destroy memory pool when variable goes out of scope +#define BONGOCAT_AUTO_POOL __attribute__((cleanup(bongocat_auto_pool_impl))) + +// ============================================================================= +// MEMORY POOL +// ============================================================================= + typedef struct memory_pool { - void *data; - size_t size; - size_t used; - size_t alignment; + void *data; + size_t size; + size_t used; + size_t alignment; } memory_pool_t; -// Safe memory allocation functions -void* bongocat_malloc(size_t size); -void* bongocat_calloc(size_t count, size_t size); -void* bongocat_realloc(void *ptr, size_t size); +// ============================================================================= +// MEMORY ALLOCATION FUNCTIONS +// ============================================================================= + +// All allocation functions are nodiscard - caller must handle the result +BONGOCAT_NODISCARD void *bongocat_malloc(size_t size); +BONGOCAT_NODISCARD void *bongocat_calloc(size_t count, size_t size); +BONGOCAT_NODISCARD void *bongocat_realloc(void *ptr, size_t size); void bongocat_free(void *ptr); -// Memory pool functions -memory_pool_t* memory_pool_create(size_t size, size_t alignment); -void* memory_pool_alloc(memory_pool_t *pool, size_t size); +// ============================================================================= +// MEMORY POOL FUNCTIONS +// ============================================================================= + +BONGOCAT_NODISCARD memory_pool_t *memory_pool_create(size_t size, + size_t alignment); +BONGOCAT_NODISCARD void *memory_pool_alloc(memory_pool_t *pool, size_t size); void memory_pool_reset(memory_pool_t *pool); void memory_pool_destroy(memory_pool_t *pool); -// Memory statistics +// Cleanup implementation for auto pool (must be after memory_pool_t definition) +static inline void bongocat_auto_pool_impl(memory_pool_t **pool) { + if (*pool) { + memory_pool_destroy(*pool); + *pool = BONGOCAT_NULLPTR; + } +} + +// ============================================================================= +// MEMORY STATISTICS +// ============================================================================= + typedef struct { - size_t total_allocated; - size_t current_allocated; - size_t peak_allocated; - size_t allocation_count; - size_t free_count; + size_t total_allocated; + size_t current_allocated; + size_t peak_allocated; + size_t allocation_count; + size_t free_count; } memory_stats_t; void memory_get_stats(memory_stats_t *stats); void memory_print_stats(void); -// Memory leak detection (debug builds) +// ============================================================================= +// DEBUG BUILD FEATURES +// ============================================================================= + #ifdef DEBUG -#define BONGOCAT_MALLOC(size) bongocat_malloc_debug(size, __FILE__, __LINE__) -#define BONGOCAT_FREE(ptr) bongocat_free_debug(ptr, __FILE__, __LINE__) -void* bongocat_malloc_debug(size_t size, const char *file, int line); +# define BONGOCAT_MALLOC(size) bongocat_malloc_debug(size, __FILE__, __LINE__) +# define BONGOCAT_FREE(ptr) bongocat_free_debug(ptr, __FILE__, __LINE__) +BONGOCAT_NODISCARD void *bongocat_malloc_debug(size_t size, const char *file, + int line); void bongocat_free_debug(void *ptr, const char *file, int line); void memory_leak_check(void); #else -#define BONGOCAT_MALLOC(size) bongocat_malloc(size) -#define BONGOCAT_FREE(ptr) bongocat_free(ptr) +# define BONGOCAT_MALLOC(size) bongocat_malloc(size) +# define BONGOCAT_FREE(ptr) bongocat_free(ptr) #endif -#endif // MEMORY_H \ No newline at end of file +#endif // MEMORY_H \ No newline at end of file diff --git a/nix/common.nix b/nix/common.nix index 513de9f6..18fa4946 100644 --- a/nix/common.nix +++ b/nix/common.nix @@ -11,33 +11,41 @@ text = '' # Auto-generated config for `wayland-bongocat` - # Cat + # Cat position and size cat_x_offset=${toString cfg.catXOffset} cat_y_offset=${toString cfg.catYOffset} cat_height=${toString cfg.catHeight} + cat_align=${cfg.catAlign} - # Overlay - overlay_position=${toString cfg.overlayPosition} + # Visual settings + mirror_x=${if cfg.mirrorX then "1" else "0"} + mirror_y=${if cfg.mirrorY then "1" else "0"} + enable_antialiasing=${if cfg.enableAntialiasing then "1" else "0"} + + # Overlay settings + overlay_position=${cfg.overlayPosition} overlay_height=${toString cfg.overlayHeight} + overlay_opacity=${toString cfg.overlayOpacity} + layer=${cfg.layer} - # Animations + # Animation settings idle_frame=${toString cfg.idleFrame} keypress_duration=${toString cfg.keypressDuration} test_animation_duration=${toString cfg.testAnimationDuration} test_animation_interval=${toString cfg.testAnimationInterval} - - # Performance fps=${toString cfg.fps} - overlay_opacity=${toString cfg.overlayOpacity} + + # Sleep mode + idle_sleep_timeout=${toString cfg.idleSleepTimeout} + enable_scheduled_sleep=${if cfg.enableScheduledSleep then "1" else "0"} + sleep_begin=${cfg.sleepBegin} + sleep_end=${cfg.sleepEnd} # Debug mode - enable_debug=${ - if cfg.enableDebug - then "1" - else "0" - } + enable_debug=${if cfg.enableDebug then "1" else "0"} - monitor=${toString cfg.monitor} + # Monitor + monitor=${cfg.monitor} # Input devices ${lib.concatMapStringsSep "\n" (device: "keyboard_device=${device}") cfg.inputDevices} @@ -110,10 +118,62 @@ in { # Size catHeight = lib.mkOption { type = lib.types.int; - default = 40; + default = 80; example = 50; description = "Height of the bongo cat in pixels"; }; + catAlign = lib.mkOption { + type = lib.types.str; + default = "center"; + example = "right"; + description = "Horizontal alignment: left, center, or right"; + }; + + # Visual settings + mirrorX = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Flip cat horizontally"; + }; + mirrorY = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Flip cat vertically"; + }; + enableAntialiasing = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Enable smooth scaling (recommended)"; + }; + layer = lib.mkOption { + type = lib.types.str; + default = "top"; + example = "overlay"; + description = "Layer type: top or overlay"; + }; + + # Sleep mode + idleSleepTimeout = lib.mkOption { + type = lib.types.int; + default = 0; + example = 300; + description = "Seconds of inactivity before sleep (0 = disabled)"; + }; + enableScheduledSleep = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable scheduled sleep mode"; + }; + sleepBegin = lib.mkOption { + type = lib.types.str; + default = "22:00"; + description = "Sleep schedule start time (HH:MM)"; + }; + sleepEnd = lib.mkOption { + type = lib.types.str; + default = "06:00"; + description = "Sleep schedule end time (HH:MM)"; + }; # Animations idleFrame = lib.mkOption { diff --git a/nix/default.nix b/nix/default.nix index 8f32c3d4..3c0aec2a 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -8,7 +8,7 @@ }: stdenv.mkDerivation (finalAttrs: { pname = "wayland-bongocat"; - version = "1.2.5"; + version = "1.3.0"; src = ../.; # Build toolchain and dependencies diff --git a/scripts/find_input_devices.sh b/scripts/find_input_devices.sh index 019c6ed0..0e72fc95 100755 --- a/scripts/find_input_devices.sh +++ b/scripts/find_input_devices.sh @@ -1,495 +1,338 @@ -#!/usr/bin/bash - -# Wayland Bongo Cat - Input Device Discovery Tool -# Professional input device finder with comprehensive analysis -# Version: 1.2.5 +#!/usr/bin/env bash +# ═══════════════════════════════════════════════════════════════════════════════ +# Bongo Cat - Input Device Discovery Tool v1.3.0 +# Interactive keyboard detection by listening for actual key events +# ═══════════════════════════════════════════════════════════════════════════════ set -euo pipefail -# Color setup - detect if colors are supported -if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]] && [[ "${NO_COLOR:-}" != "1" ]]; then - readonly RED='\033[0;31m' - readonly GREEN='\033[0;32m' - readonly YELLOW='\033[1;33m' - readonly BLUE='\033[0;34m' - readonly PURPLE='\033[0;35m' - readonly CYAN='\033[0;36m' - readonly WHITE='\033[1;37m' - readonly NC='\033[0m' # No Color - readonly USE_COLORS=true +VERSION="1.3.0" +SCRIPT_NAME="bongocat-find-devices" + +# Colors +if [[ -t 1 ]] && [[ "${NO_COLOR:-}" != "1" ]]; then + RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' + BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' DIM='\033[2m' NC='\033[0m' else - # No colors - use empty strings - readonly RED='' - readonly GREEN='' - readonly YELLOW='' - readonly BLUE='' - readonly PURPLE='' - readonly CYAN='' - readonly WHITE='' - readonly NC='' - readonly USE_COLORS=false + RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' DIM='' NC='' fi -# Helper function for colored output -print_colored() { - local color="$1" - local text="$2" - if [[ "$USE_COLORS" == "true" ]]; then - echo -e "${color}${text}${NC}" - else - echo "$text" - fi -} - -# Status symbols -readonly CHECK="[OK]" -readonly CROSS="[ERROR]" -readonly WARNING="[WARN]" -readonly INFO="[INFO]" -readonly SEARCH="[SCAN]" -readonly KEYBOARD="[DEVICES]" -readonly CONFIG="[CONFIG]" -readonly TEST="[TEST]" - -# Script metadata -readonly SCRIPT_NAME="bongocat-find-devices" -readonly VERSION="1.2.5" - -# Command line options -SHOW_ALL=false -GENERATE_CONFIG=false -TEST_DEVICES=false -VERBOSE=false - -# Usage information -usage() { - if [[ "$USE_COLORS" == "true" ]]; then - printf "${WHITE}%s v%s${NC}\n" "$SCRIPT_NAME" "$VERSION" - printf "Professional input device discovery for Wayland Bongo Cat\n\n" - printf "${WHITE}USAGE:${NC}\n" - printf " %s [OPTIONS]\n\n" "$0" - printf "${WHITE}OPTIONS:${NC}\n" - printf " -a, --all Show all input devices (including mice, touchpads)\n" - printf " -g, --generate-config Generate configuration file to stdout\n" - printf " -t, --test Test device responsiveness (requires root)\n" - printf " -v, --verbose Show detailed device information\n" - printf " -h, --help Show this help message\n\n" - printf "${WHITE}EXAMPLES:${NC}\n" - printf " %s # Basic device discovery\n" "$0" - printf " %s --all --verbose # Comprehensive device analysis\n" "$0" - printf " %s --generate-config > bongocat.conf # Generate config file\n\n" "$0" - printf "${WHITE}DESCRIPTION:${NC}\n" - printf " This tool scans your system for input devices and provides configuration\n" - printf " suggestions for Wayland Bongo Cat. It identifies keyboards, checks\n" - printf " permissions, and generates ready-to-use configuration snippets.\n\n" - printf "${WHITE}MONITOR DETECTION:${NC}\n" - printf " For multi-monitor setups, use these commands to find monitor names:\n" - printf " • wlr-randr # List all monitors (recommended)\n" - printf " • swaymsg -t get_outputs # Sway compositor only\n" - printf " • bongocat logs show detected monitors during startup\n\n" - else - cat << EOF -${SCRIPT_NAME} v${VERSION} -Professional input device discovery for Wayland Bongo Cat - -USAGE: - $0 [OPTIONS] - -OPTIONS: - -a, --all Show all input devices (including mice, touchpads) - -g, --generate-config Generate configuration file to stdout - -t, --test Test device responsiveness (requires root) - -v, --verbose Show detailed device information - -h, --help Show this help message +# ───────────────────────────────────────────────────────────────────────────── +# Helper Functions +# ───────────────────────────────────────────────────────────────────────────── -EXAMPLES: - $0 # Basic device discovery - $0 --all --verbose # Comprehensive device analysis - $0 --generate-config > bongocat.conf # Generate config file +info() { echo -e "${BLUE}→${NC} $*"; } +success() { echo -e "${GREEN}✓${NC} $*"; } +warn() { echo -e "${YELLOW}!${NC} $*"; } +error() { echo -e "${RED}✗${NC} $*" >&2; } -DESCRIPTION: - This tool scans your system for input devices and provides configuration - suggestions for Wayland Bongo Cat. It identifies keyboards, checks - permissions, and generates ready-to-use configuration snippets. - -MONITOR DETECTION: - For multi-monitor setups, use these commands to find monitor names: - • wlr-randr # List all monitors (recommended) - • swaymsg -t get_outputs # Sway compositor only - • bongocat logs show detected monitors during startup - -EOF - fi -} - -# Parse command line arguments -parse_args() { - while [[ $# -gt 0 ]]; do - case $1 in - -a|--all) - SHOW_ALL=true - shift - ;; - -g|--generate-config) - GENERATE_CONFIG=true - shift - ;; - -t|--test) - TEST_DEVICES=true - shift - ;; - -v|--verbose) - VERBOSE=true - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - if [[ "$USE_COLORS" == "true" ]]; then - echo -e "${RED}Error: Unknown option '$1'${NC}" >&2 - else - echo "Error: Unknown option '$1'" >&2 - fi - echo "Use --help for usage information." >&2 - exit 1 - ;; - esac - done +header() { + echo + echo -e "${BOLD}$*${NC}" + echo -e "${BLUE}$(printf '─%.0s' {1..60})${NC}" } -# Print header -print_header() { - if [[ "$GENERATE_CONFIG" == "false" ]]; then - if [[ "$USE_COLORS" == "true" ]]; then - echo -e "${PURPLE}╔══════════════════════════════════════════════════════════════════╗${NC}" - echo -e "${PURPLE}║${NC} ${WHITE}Wayland Bongo Cat - Input Device Discovery v${VERSION}${NC} ${PURPLE}║${NC}" - echo -e "${PURPLE}╚══════════════════════════════════════════════════════════════════╝${NC}" - else - echo "==================================================================" - echo " Wayland Bongo Cat - Input Device Discovery v${VERSION}" - echo "==================================================================" +# ───────────────────────────────────────────────────────────────────────────── +# Device Discovery +# ───────────────────────────────────────────────────────────────────────────── + +# Get all event devices with kbd handler (potential keyboards) +get_kbd_devices() { + local devices=() + + if [[ ! -r /proc/bus/input/devices ]]; then + return 1 + fi + + local name="" handlers="" + + while IFS= read -r line; do + case "$line" in + N:\ Name=\"*\") + name="${line#N: Name=\"}" + name="${name%\"}" + ;; + H:\ Handlers=*) + handlers="${line#H: Handlers=}" + ;; + "") + if [[ "$handlers" =~ kbd ]] && [[ "$handlers" =~ event ]]; then + local event + event=$(echo "$handlers" | grep -o 'event[0-9]*' | head -1) + if [[ -n "$event" ]] && [[ -e "/dev/input/$event" ]]; then + devices+=("$event|$name") + fi fi - echo - fi + name="" handlers="" + ;; + esac + done < /proc/bus/input/devices + + printf '%s\n' "${devices[@]}" } -# Check if running as root -check_permissions() { - if [[ $EUID -eq 0 ]]; then - return 0 # Running as root - else - return 1 # Not running as root - fi +# Check if device is readable +check_device() { + local path="$1" + [[ -r "$path" ]] } -# Check if user is in input group -check_input_group() { - if groups | grep -q '\binput\b'; then - return 0 # User is in input group - else - return 1 # User is not in input group - fi +# ───────────────────────────────────────────────────────────────────────────── +# Interactive Detection +# ───────────────────────────────────────────────────────────────────────────── + +# Listen for key events on a device (runs in background) +listen_device() { + local device="$1" + local output_file="$2" + local timeout="$3" + + # Use timeout + cat to read raw events + # Key events are detected by the presence of specific byte patterns + # Type 1 (EV_KEY) events indicate keyboard activity + timeout "$timeout" cat "/dev/input/$device" 2>/dev/null | head -c 1000 > "$output_file" & + echo $! } -# Get device accessibility status -get_device_status() { - local device="$1" - - # Check existence - if [[ ! -e "$device" ]]; then - return 2 # missing +# Detect keyboards interactively +interactive_detect() { + local timeout="${1:-5}" + local devices + devices=$(get_kbd_devices) || { error "Cannot read device list"; return 1; } + + if [[ -z "$devices" ]]; then + error "No input devices with kbd handler found" + info "Try: sudo $SCRIPT_NAME --interactive" + return 1 + fi + + # Check permissions + local has_permission=false + while IFS='|' read -r event name; do + if check_device "/dev/input/$event"; then + has_permission=true + break fi - - if [[ -r "$device" ]]; then - return 0 # accessible - else - return 1 # permission denied + done <<< "$devices" + + if [[ "$has_permission" == "false" ]]; then + error "Cannot read input devices (permission denied)" + echo + info "Fix with: ${CYAN}sudo usermod -a -G input \$USER${NC}" + info "Then log out and back in" + echo + info "Or run: ${CYAN}sudo $SCRIPT_NAME --interactive${NC}" + return 1 + fi + + # Create temp directory for output files + local tmpdir + tmpdir=$(mktemp -d) + trap "rm -rf '$tmpdir'" EXIT + + header "Interactive Keyboard Detection" + echo + echo -e " ${BOLD}Press keys on ALL your keyboards for ${timeout} seconds...${NC}" + echo -e " ${DIM}(Internal laptop keyboard, external keyboards, etc.)${NC}" + echo + + # Start listening on all accessible devices + local pids=() + local device_list=() + + while IFS='|' read -r event name; do + if check_device "/dev/input/$event"; then + local outfile="$tmpdir/$event" + local pid + pid=$(listen_device "$event" "$outfile" "$timeout") + pids+=("$pid") + device_list+=("$event|$name|$outfile") fi -} - -# Get device type based on name, capabilities, and handlers -get_device_type() { - local device_name="$1" - local capabilities="$2" - local handlers="$3" - - # Convert to lowercase for matching - local name_lower=$(echo "$device_name" | tr '[:upper:]' '[:lower:]') - local handlers_lower=$(echo "$handlers" | tr '[:upper:]' '[:lower:]') - - # Check for keyboard indicators - # Look for "kbd" handler (most reliable), keyboard in name, or keyboard-like capabilities - if [[ "$handlers_lower" =~ kbd ]] || [[ "$name_lower" =~ keyboard ]] || [[ "$capabilities" =~ "120013" ]] || [[ "$capabilities" =~ "12001f" ]]; then - # Determine keyboard type - if [[ "$name_lower" =~ (bluetooth|wireless|bt) ]] || [[ "$handlers_lower" =~ bluetooth ]]; then - echo "Keyboard (Bluetooth)" - elif [[ "$name_lower" =~ (usb|external) ]]; then - echo "Keyboard (USB)" - else - # Check if it's likely a Bluetooth keyboard based on common brands - if [[ "$name_lower" =~ (keychron|logitech|corsair|razer|steelseries|apple|microsoft) ]] && [[ ! "$name_lower" =~ (mouse|trackpad|touchpad) ]]; then - echo "Keyboard (Bluetooth)" - else - echo "Keyboard" - fi - fi - elif [[ "$name_lower" =~ mouse ]] || [[ "$capabilities" =~ "110000" ]]; then - echo "Mouse" - elif [[ "$name_lower" =~ (touchpad|trackpad|synaptics) ]]; then - echo "Touchpad" - elif [[ "$name_lower" =~ (touchscreen|touch) ]]; then - echo "Touchscreen" + done <<< "$devices" + + # Show countdown + for ((i=timeout; i>0; i--)); do + echo -ne "\r ${CYAN}Listening... ${i}s remaining ${NC} " + sleep 1 + done + echo -e "\r ${GREEN}✓ Detection complete!${NC} " + + # Wait for all listeners to finish + for pid in "${pids[@]}"; do + wait "$pid" 2>/dev/null || true + done + + echo + + # Check which devices received input + local detected_keyboards=() + local other_devices=() + + for entry in "${device_list[@]}"; do + IFS='|' read -r event name outfile <<< "$entry" + + if [[ -s "$outfile" ]]; then + # Device received input - it's a keyboard! + detected_keyboards+=("$event|$name") else - echo "Input Device" + other_devices+=("$event|$name") fi + done + + # Show results + header "Detection Results" + + if [[ ${#detected_keyboards[@]} -eq 0 ]]; then + warn "No keyboards detected" + echo + info "Make sure you pressed keys during the detection window" + info "Try again with: $SCRIPT_NAME --interactive" + return 1 + fi + + echo -e " ${GREEN}Detected keyboards:${NC}" + for entry in "${detected_keyboards[@]}"; do + IFS='|' read -r event name <<< "$entry" + echo -e " ${GREEN}✓${NC} ${BOLD}$name${NC}" + echo -e " ${CYAN}/dev/input/$event${NC}" + done + + if [[ ${#other_devices[@]} -gt 0 ]]; then + echo + echo -e " ${DIM}Other devices (no input detected):${NC}" + for entry in "${other_devices[@]}"; do + IFS='|' read -r event name <<< "$entry" + echo -e " ${DIM}○ $name (/dev/input/$event)${NC}" + done + fi + + # Config suggestion + header "Add to Config" + echo -e " ${BOLD}~/.config/bongocat/bongocat.conf:${NC}" + echo + for entry in "${detected_keyboards[@]}"; do + IFS='|' read -r event name <<< "$entry" + echo -e " ${CYAN}keyboard_device=/dev/input/$event${NC} ${BOLD}# $name${NC}" + done + + echo } -# Parse device information from /proc/bus/input/devices -parse_devices() { - local show_all="$1" - local devices=() - - if [[ ! -r /proc/bus/input/devices ]]; then - echo -e "${RED}${CROSS} Cannot read /proc/bus/input/devices${NC}" >&2 - echo -e "${INFO} Try running with sudo for full device information" >&2 - return 1 - fi - - # Parse the devices file - local current_name="" - local current_handlers="" - local current_capabilities="" - - while IFS= read -r line; do - if [[ "$line" =~ ^I: ]]; then - # Reset for new device - current_name="" - current_handlers="" - current_capabilities="" - elif [[ "$line" =~ ^N:\ Name=\"(.*)\" ]]; then - current_name="${BASH_REMATCH[1]}" - elif [[ "$line" =~ ^H:\ Handlers=(.*) ]]; then - current_handlers="${BASH_REMATCH[1]}" - elif [[ "$line" =~ ^B:\ EV=(.*) ]]; then - current_capabilities="${BASH_REMATCH[1]}" - elif [[ "$line" =~ ^$ ]] && [[ -n "$current_name" ]]; then - # End of device block, process it - local device_type=$(get_device_type "$current_name" "$current_capabilities" "$current_handlers") - - # Extract event handlers - local event_handlers=($(echo "$current_handlers" | grep -o 'event[0-9]\+' || true)) - - # Filter devices based on show_all flag - if [[ "$show_all" == "true" ]] || [[ "$device_type" =~ Keyboard ]]; then - for handler in "${event_handlers[@]}"; do - local device_path="/dev/input/$handler" - if [[ -e "$device_path" ]]; then - devices+=("$current_name|$device_path|$device_type") - fi - done - fi - fi - done < /proc/bus/input/devices - - # Handle last device if file doesn't end with empty line - if [[ -n "$current_name" ]]; then - local device_type=$(get_device_type "$current_name" "$current_capabilities" "$current_handlers") - local event_handlers=($(echo "$current_handlers" | grep -o 'event[0-9]\+' || true)) - - if [[ "$show_all" == "true" ]] || [[ "$device_type" =~ Keyboard ]]; then - for handler in "${event_handlers[@]}"; do - local device_path="/dev/input/$handler" - if [[ -e "$device_path" ]]; then - devices+=("$current_name|$device_path|$device_type") - fi - done - fi - fi - - printf '%s\n' "${devices[@]}" +# ───────────────────────────────────────────────────────────────────────────── +# Quick Mode (non-interactive, name-based) +# ───────────────────────────────────────────────────────────────────────────── + +# Guess if device is keyboard by name +is_likely_keyboard() { + local name="$1" + local name_lower + name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]') + + # Exclude obvious non-keyboards + [[ "$name_lower" =~ (button|hotkey|speaker|video|consumer|system|avrcp|mouse|touchpad|trackpad) ]] && return 1 + + # Include devices with "keyboard" in name + [[ "$name_lower" =~ keyboard ]] && return 0 + + # Include standard laptop keyboard + [[ "$name_lower" =~ "at translated set 2" ]] && return 0 + + return 1 } -# Display device information -display_devices() { - local show_all="$1" - local devices - - if [[ "$GENERATE_CONFIG" == "false" ]]; then - if [[ "$USE_COLORS" == "true" ]]; then - echo -e "${SEARCH} ${WHITE}Scanning for input devices...${NC}" - else - echo "${SEARCH} Scanning for input devices..." - fi - echo - fi - - # Get device list - if ! devices=$(parse_devices "$show_all"); then - return 1 - fi - - if [[ -z "$devices" ]]; then - if [[ "$GENERATE_CONFIG" == "false" ]]; then - echo -e "${WARNING} ${YELLOW}No input devices found${NC}" - echo -e "${INFO} Try running with sudo: ${WHITE}sudo $0${NC}" - fi - return 1 - fi - - local keyboard_devices=() - local accessible_keyboards=() - - if [[ "$GENERATE_CONFIG" == "false" ]]; then - echo -e "${KEYBOARD} ${WHITE}Found Input Devices:${NC}" - fi - - # Process and display each device - while IFS='|' read -r name path type; do - if [[ "$GENERATE_CONFIG" == "false" ]]; then - echo -e "${BLUE}┌─────────────────────────────────────────────────────────────────┐${NC}" - echo -e "${BLUE}│${NC} ${WHITE}Device:${NC} $(printf "%-50s" "$name") ${BLUE} │${NC}" - echo -e "${BLUE}│${NC} ${WHITE}Path:${NC} $(printf "%-50s" "$path") ${BLUE} │${NC}" - echo -e "${BLUE}│${NC} ${WHITE}Type:${NC} $(printf "%-50s" "$type") ${BLUE} │${NC}" - - local status - local status_code - set +e - get_device_status "$path" - status_code=$? - set -e - - case $status_code in - 0) status="${GREEN}${CHECK} Accessible${NC}" ;; - 1) status="${RED}[ERROR] Permission Denied${NC}" ;; - 2) status="${YELLOW}[MISSING] Device not found${NC}" ;; - esac - - echo -e "${BLUE}│${NC} ${WHITE}Status:${NC} $status $(printf "%*s" $((56 - ${#status} + 10)) "") ${BLUE} │${NC}" - echo -e "${BLUE}└─────────────────────────────────────────────────────────────────┘${NC}" - echo - fi - - # Collect keyboard devices for configuration - if [[ "$type" =~ Keyboard ]]; then - keyboard_devices+=("$path|$name") - if [[ -r "$path" ]]; then - accessible_keyboards+=("$path|$name") - fi - fi - done <<< "$devices" - - # Generate configuration suggestions - if [[ "${#keyboard_devices[@]}" -gt 0 ]]; then - generate_config_suggestions "${keyboard_devices[@]}" +quick_detect() { + local devices + devices=$(get_kbd_devices) || { error "Cannot read devices"; return 1; } + + if [[ -z "$devices" ]]; then + warn "No input devices found" + return 1 + fi + + echo + echo -e "${BOLD}🐱 Bongo Cat Device Discovery${NC} v$VERSION" + + header "Detected Devices" + + local keyboards=() + + while IFS='|' read -r event name; do + local status="ok" + check_device "/dev/input/$event" || status="denied" + + if is_likely_keyboard "$name"; then + echo -e " ${GREEN}✓${NC} ${GREEN}[KEYBOARD]${NC} ${BOLD}$name${NC}" + keyboards+=("$event|$name") else - if [[ "$GENERATE_CONFIG" == "false" ]]; then - echo -e "${WARNING} ${YELLOW}No keyboard devices found${NC}" - fi - fi - - # Show permission help if needed - if [[ "${#accessible_keyboards[@]}" -lt "${#keyboard_devices[@]}" ]] && [[ "$GENERATE_CONFIG" == "false" ]]; then - show_permission_help + echo -e " ${DIM}○ [other] $name${NC}" fi + echo -e " ${CYAN}/dev/input/$event${NC}" + done <<< "$devices" + + if [[ ${#keyboards[@]} -eq 0 ]]; then + echo + warn "Could not auto-detect keyboards by name" + info "Use interactive mode: ${CYAN}$SCRIPT_NAME --interactive${NC}" + return 1 + fi + + # Config suggestion + header "Add to Config" + echo -e " ${BOLD}~/.config/bongocat/bongocat.conf:${NC}" + echo + for entry in "${keyboards[@]}"; do + IFS='|' read -r event name <<< "$entry" + echo -e " ${CYAN}keyboard_device=/dev/input/$event${NC} ${BOLD}# $name${NC}" + done + + echo + echo -e " ${DIM}Not accurate? Use: $SCRIPT_NAME --interactive${NC}" + echo } -# Generate configuration suggestions -generate_config_suggestions() { - local devices=("$@") - - if [[ "$GENERATE_CONFIG" == "true" ]]; then - # Generate full config file - cat << 'EOF' -# Wayland Bongo Cat Configuration -# Generated by bongocat-find-devices +# ───────────────────────────────────────────────────────────────────────────── +# Main +# ───────────────────────────────────────────────────────────────────────────── -# Visual settings -cat_height=50 # Size of bongo cat (16-128) -cat_x_offset=0 # Horizontal position offset -cat_y_offset=0 # Vertical position offset -overlay_opacity=150 # Background opacity (0-255) +show_usage() { + cat << EOF +${BOLD}$SCRIPT_NAME${NC} v$VERSION - Find keyboards for Bongo Cat -# Animation settings -fps=60 # Frame rate (1-120) -keypress_duration=100 # Animation duration (ms) -test_animation_interval=3 # Test animation every N seconds (0=off) +${BOLD}USAGE${NC} + $SCRIPT_NAME [OPTIONS] -# Input devices -EOF - for device_info in "${devices[@]}"; do - IFS='|' read -r path name <<< "$device_info" - echo "keyboard_device=$path # $name" - done - cat << 'EOF' +${BOLD}OPTIONS${NC} + -i, --interactive Detect keyboards by listening for key presses (recommended) + -t, --timeout SEC Detection timeout in seconds (default: 5) + -g, --generate Output config lines only (for piping) + -h, --help Show this help -# Debug -enable_debug=1 # Show debug messages +${BOLD}EXAMPLES${NC} + $SCRIPT_NAME # Quick detection (name-based) + $SCRIPT_NAME -i # Interactive detection (recommended) + $SCRIPT_NAME -i -t 10 # Interactive with 10 second timeout EOF - else - echo -e "${CONFIG} ${WHITE}Configuration Suggestions:${NC}" - echo -e "${WHITE}Add these lines to your bongocat.conf:${NC}" - echo - - for device_info in "${devices[@]}"; do - IFS='|' read -r path name <<< "$device_info" - echo -e "${CYAN}keyboard_device=$path${NC} ${WHITE}# $name${NC}" - done - echo - fi -} - -# Show permission help -show_permission_help() { - echo -e "${WARNING} ${WHITE}Permission Issues Detected:${NC}" - echo -e "${INFO} Some devices are not accessible. To fix this:" - echo - echo -e "${WHITE}1. Add your user to the input group:${NC}" - echo -e " ${CYAN}sudo usermod -a -G input \$USER${NC}" - echo -e " ${YELLOW}(Log out and back in for changes to take effect)${NC}" - echo - echo -e "${WHITE}2. Or create a udev rule:${NC}" - echo -e " ${CYAN}echo 'KERNEL==\"event*\", GROUP=\"input\", MODE=\"0664\"' | sudo tee /etc/udev/rules.d/99-input.rules${NC}" - echo -e " ${CYAN}sudo udevadm control --reload-rules${NC}" - echo } -# Test device responsiveness -test_devices() { - if ! command -v evtest >/dev/null 2>&1; then - echo -e "${RED}${CROSS} evtest not found${NC}" >&2 - echo -e "${INFO} Install with: ${WHITE}sudo apt install evtest${NC} (Ubuntu/Debian)" >&2 - echo -e "${INFO} Or: ${WHITE}sudo pacman -S evtest${NC} (Arch Linux)" >&2 - return 1 - fi - - if ! check_permissions; then - echo -e "${RED}${CROSS} Root privileges required for device testing${NC}" >&2 - echo -e "${INFO} Run with: ${WHITE}sudo $0 --test${NC}" >&2 - return 1 - fi - - echo -e "${TEST} ${WHITE}Device Testing Mode${NC}" - echo -e "${INFO} This will launch evtest for device testing" - echo -e "${INFO} Press Ctrl+C to exit evtest when done" - echo - - exec evtest -} - -# Main function main() { - parse_args "$@" - - if [[ "$TEST_DEVICES" == "true" ]]; then - test_devices - return $? - fi - - print_header - display_devices "$SHOW_ALL" + local mode="quick" + local timeout=5 + local generate=false + + while [[ $# -gt 0 ]]; do + case "$1" in + -i|--interactive) mode="interactive"; shift ;; + -t|--timeout) timeout="$2"; shift 2 ;; + -g|--generate) generate=true; shift ;; + -h|--help) show_usage; exit 0 ;; + *) error "Unknown option: $1"; show_usage; exit 1 ;; + esac + done + + case "$mode" in + interactive) interactive_detect "$timeout" ;; + quick) quick_detect ;; + esac } -# Run main function with all arguments main "$@" diff --git a/scripts/test-nix-build.sh b/scripts/test_nix_build.sh similarity index 100% rename from scripts/test-nix-build.sh rename to scripts/test_nix_build.sh diff --git a/scripts/test_toggle.sh b/scripts/test_toggle.sh old mode 100644 new mode 100755 diff --git a/src/config/config.c b/src/config/config.c index 3d6b5164..87fbed5e 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -1,22 +1,24 @@ #define _POSIX_C_SOURCE 200809L #include "config/config.h" + #include "utils/error.h" #include "utils/memory.h" + #include // ============================================================================= // CONFIGURATION CONSTANTS AND VALIDATION RANGES // ============================================================================= -#define MIN_CAT_HEIGHT 10 -#define MAX_CAT_HEIGHT 200 +#define MIN_CAT_HEIGHT 10 +#define MAX_CAT_HEIGHT 200 #define MIN_OVERLAY_HEIGHT 20 #define MAX_OVERLAY_HEIGHT 300 -#define MIN_FPS 1 -#define MAX_FPS 120 -#define MIN_DURATION 10 -#define MAX_DURATION 5000 -#define MAX_INTERVAL 3600 +#define MIN_FPS 1 +#define MAX_FPS 120 +#define MIN_DURATION 10 +#define MAX_DURATION 5000 +#define MAX_INTERVAL 3600 // ============================================================================= // GLOBAL STATE FOR DEVICE MANAGEMENT @@ -235,7 +237,7 @@ config_parse_integer_key(config_t *config, const char *key, const char *value) { } else if (strcmp(key, "idle_sleep_timeout") == 0) { config->idle_sleep_timeout_sec = int_value; } else { - return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key } return BONGOCAT_SUCCESS; @@ -273,7 +275,7 @@ static bongocat_error_t config_parse_enum_key(config_t *config, const char *key, config->cat_align = ALIGN_CENTER; } } else { - return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key } return BONGOCAT_SUCCESS; @@ -283,7 +285,7 @@ static bongocat_error_t config_parse_time_key(config_t *config, const char *key, const char *value) { // Only try to parse time for time-related keys if (strcmp(key, "sleep_begin") != 0 && strcmp(key, "sleep_end") != 0) { - return BONGOCAT_ERROR_INVALID_PARAM; // Not a time key + return BONGOCAT_ERROR_INVALID_PARAM; // Not a time key } int hour, min; @@ -322,7 +324,7 @@ config_parse_string_key(config_t *config, const char *key, const char *value) { config->output_name = new_name; strcpy(config->output_name, value); } else { - return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + return BONGOCAT_ERROR_INVALID_PARAM; // Unknown key } return BONGOCAT_SUCCESS; @@ -432,11 +434,10 @@ static void config_set_defaults(config_t *config) { *config = (config_t){ .screen_width = DEFAULT_SCREEN_WIDTH, // Will be updated by Wayland detection - .output_name = NULL, // Will default to automatic one if kept null + .output_name = NULL, // Will default to automatic one if kept null .bar_height = DEFAULT_BAR_HEIGHT, .asset_paths = {"assets/bongo-cat-both-up.png", - "assets/bongo-cat-left-down.png", - "assets/bongo-cat-right-down.png", + "assets/bongo-cat-left-down.png", "assets/bongo-cat-right-down.png", "assets/bongo-cat-both-down.png"}, .keyboard_devices = NULL, .num_keyboard_devices = 0, @@ -548,7 +549,9 @@ bongocat_error_t load_config(config_t *config, const char *config_file_path) { return BONGOCAT_SUCCESS; } -void config_cleanup(void) { config_cleanup_devices(); } +void config_cleanup(void) { + config_cleanup_devices(); +} void config_cleanup_full(config_t *config) { if (!config) diff --git a/src/config/config_watcher.c b/src/config/config_watcher.c index c8241965..26fc2328 100644 --- a/src/config/config_watcher.c +++ b/src/config/config_watcher.c @@ -1,171 +1,179 @@ #define _POSIX_C_SOURCE 200809L #define _DEFAULT_SOURCE +#include "config/config.h" #include "core/bongocat.h" #include "utils/error.h" -#include "config/config.h" + #include -#include -#include #include +#include +#include static void *config_watcher_thread(void *arg) { - ConfigWatcher *watcher = (ConfigWatcher *)arg; - char buffer[INOTIFY_BUF_LEN]; - time_t last_reload_time = 0; - - bongocat_log_info("Config watcher started for: %s", watcher->config_path); - - while (watcher->watching) { - fd_set read_fds; - struct timeval timeout; - - FD_ZERO(&read_fds); - FD_SET(watcher->inotify_fd, &read_fds); - - // Set timeout to 1 second to allow checking watching flag - timeout.tv_sec = 1; - timeout.tv_usec = 0; - - int select_result = select(watcher->inotify_fd + 1, &read_fds, NULL, NULL, &timeout); - - if (select_result < 0) { - if (errno == EINTR) continue; - bongocat_log_error("Config watcher select failed: %s", strerror(errno)); - break; - } - - if (select_result == 0) { - // Timeout, continue to check watching flag - continue; + ConfigWatcher *watcher = (ConfigWatcher *)arg; + char buffer[INOTIFY_BUF_LEN]; + time_t last_reload_time = 0; + + bongocat_log_info("Config watcher started for: %s", watcher->config_path); + + while (watcher->watching) { + fd_set read_fds; + struct timeval timeout; + + FD_ZERO(&read_fds); + FD_SET(watcher->inotify_fd, &read_fds); + + // Set timeout to 1 second to allow checking watching flag + timeout.tv_sec = 1; + timeout.tv_usec = 0; + + int select_result = + select(watcher->inotify_fd + 1, &read_fds, NULL, NULL, &timeout); + + if (select_result < 0) { + if (errno == EINTR) + continue; + bongocat_log_error("Config watcher select failed: %s", strerror(errno)); + break; + } + + if (select_result == 0) { + // Timeout, continue to check watching flag + continue; + } + + if (FD_ISSET(watcher->inotify_fd, &read_fds)) { + ssize_t length = read(watcher->inotify_fd, buffer, INOTIFY_BUF_LEN); + + if (length < 0) { + bongocat_log_error("Config watcher read failed: %s", strerror(errno)); + continue; + } + + bool should_reload = false; + ssize_t i = 0; + while (i < length) { + struct inotify_event *event = (struct inotify_event *)&buffer[i]; + + if (event->mask & (IN_MODIFY | IN_MOVED_TO)) { + should_reload = true; } - - if (FD_ISSET(watcher->inotify_fd, &read_fds)) { - ssize_t length = read(watcher->inotify_fd, buffer, INOTIFY_BUF_LEN); - - if (length < 0) { - bongocat_log_error("Config watcher read failed: %s", strerror(errno)); - continue; - } - - bool should_reload = false; - ssize_t i = 0; - while (i < length) { - struct inotify_event *event = (struct inotify_event *)&buffer[i]; - - if (event->mask & (IN_MODIFY | IN_MOVED_TO)) { - should_reload = true; - } - - i += INOTIFY_EVENT_SIZE + event->len; - } - - // Debounce: only reload if at least 200ms have passed since last reload - if (should_reload) { - time_t current_time = time(NULL); - if (current_time - last_reload_time >= 1) { // 1 second debounce - bongocat_log_info("Config file changed, reloading..."); - last_reload_time = current_time; - - // Small delay to ensure file write is complete - usleep(100000); // 100ms - - if (watcher->reload_callback) { - watcher->reload_callback(watcher->config_path); - } - } - } + + i += INOTIFY_EVENT_SIZE + event->len; + } + + // Debounce: only reload if at least 200ms have passed since last reload + if (should_reload) { + time_t current_time = time(NULL); + if (current_time - last_reload_time >= 1) { // 1 second debounce + bongocat_log_info("Config file changed, reloading..."); + last_reload_time = current_time; + + // Small delay to ensure file write is complete + usleep(100000); // 100ms + + if (watcher->reload_callback) { + watcher->reload_callback(watcher->config_path); + } } + } } - - bongocat_log_info("Config watcher stopped"); - return NULL; + } + + bongocat_log_info("Config watcher stopped"); + return NULL; } -int config_watcher_init(ConfigWatcher *watcher, const char *config_path, void (*callback)(const char *)) { - if (!watcher || !config_path || !callback) { - return -1; - } - - memset(watcher, 0, sizeof(ConfigWatcher)); - - // Initialize inotify - watcher->inotify_fd = inotify_init1(IN_NONBLOCK); - if (watcher->inotify_fd < 0) { - bongocat_log_error("Failed to initialize inotify: %s", strerror(errno)); - return -1; - } - - // Store config path - watcher->config_path = strdup(config_path); - if (!watcher->config_path) { - close(watcher->inotify_fd); - return -1; - } - - // Add watch for the config file - watcher->watch_fd = inotify_add_watch(watcher->inotify_fd, config_path, - IN_MODIFY | IN_MOVED_TO | IN_CREATE); - if (watcher->watch_fd < 0) { - bongocat_log_error("Failed to add inotify watch for %s: %s", config_path, strerror(errno)); - free(watcher->config_path); - close(watcher->inotify_fd); - return -1; - } - - watcher->reload_callback = callback; - watcher->watching = false; - - return 0; +int config_watcher_init(ConfigWatcher *watcher, const char *config_path, + void (*callback)(const char *)) { + if (!watcher || !config_path || !callback) { + return -1; + } + + memset(watcher, 0, sizeof(ConfigWatcher)); + + // Initialize inotify + watcher->inotify_fd = inotify_init1(IN_NONBLOCK); + if (watcher->inotify_fd < 0) { + bongocat_log_error("Failed to initialize inotify: %s", strerror(errno)); + return -1; + } + + // Store config path + watcher->config_path = strdup(config_path); + if (!watcher->config_path) { + close(watcher->inotify_fd); + return -1; + } + + // Add watch for the config file + watcher->watch_fd = inotify_add_watch(watcher->inotify_fd, config_path, + IN_MODIFY | IN_MOVED_TO | IN_CREATE); + if (watcher->watch_fd < 0) { + bongocat_log_error("Failed to add inotify watch for %s: %s", config_path, + strerror(errno)); + free(watcher->config_path); + close(watcher->inotify_fd); + return -1; + } + + watcher->reload_callback = callback; + watcher->watching = false; + + return 0; } void config_watcher_start(ConfigWatcher *watcher) { - if (!watcher || watcher->watching) { - return; - } - - watcher->watching = true; - - if (pthread_create(&watcher->watcher_thread, NULL, config_watcher_thread, watcher) != 0) { - bongocat_log_error("Failed to create config watcher thread: %s", strerror(errno)); - watcher->watching = false; - return; - } - - bongocat_log_info("Config watcher thread started"); + if (!watcher || watcher->watching) { + return; + } + + watcher->watching = true; + + if (pthread_create(&watcher->watcher_thread, NULL, config_watcher_thread, + watcher) != 0) { + bongocat_log_error("Failed to create config watcher thread: %s", + strerror(errno)); + watcher->watching = false; + return; + } + + bongocat_log_info("Config watcher thread started"); } void config_watcher_stop(ConfigWatcher *watcher) { - if (!watcher || !watcher->watching) { - return; - } - - watcher->watching = false; - - // Wait for thread to finish - if (pthread_join(watcher->watcher_thread, NULL) != 0) { - bongocat_log_error("Failed to join config watcher thread: %s", strerror(errno)); - } + if (!watcher || !watcher->watching) { + return; + } + + watcher->watching = false; + + // Wait for thread to finish + if (pthread_join(watcher->watcher_thread, NULL) != 0) { + bongocat_log_error("Failed to join config watcher thread: %s", + strerror(errno)); + } } void config_watcher_cleanup(ConfigWatcher *watcher) { - if (!watcher) { - return; - } - - config_watcher_stop(watcher); - - if (watcher->watch_fd >= 0) { - inotify_rm_watch(watcher->inotify_fd, watcher->watch_fd); - } - - if (watcher->inotify_fd >= 0) { - close(watcher->inotify_fd); - } - - if (watcher->config_path) { - free(watcher->config_path); - watcher->config_path = NULL; - } - - memset(watcher, 0, sizeof(ConfigWatcher)); + if (!watcher) { + return; + } + + config_watcher_stop(watcher); + + if (watcher->watch_fd >= 0) { + inotify_rm_watch(watcher->inotify_fd, watcher->watch_fd); + } + + if (watcher->inotify_fd >= 0) { + close(watcher->inotify_fd); + } + + if (watcher->config_path) { + free(watcher->config_path); + watcher->config_path = NULL; + } + + memset(watcher, 0, sizeof(ConfigWatcher)); } \ No newline at end of file diff --git a/src/core/main.c b/src/core/main.c index 57094107..616095af 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -7,6 +7,7 @@ #include "platform/wayland.h" #include "utils/error.h" #include "utils/memory.h" + #include #include #include @@ -51,7 +52,7 @@ static int process_create_pid_file(void) { close(fd); if (errno == EWOULDBLOCK) { bongocat_log_info("Another instance is already running"); - return -2; // Already running + return -2; // Already running } bongocat_log_error("Failed to lock PID file: %s", strerror(errno)); return -1; @@ -65,15 +66,17 @@ static int process_create_pid_file(void) { return -1; } - return fd; // Keep file descriptor open to maintain lock + return fd; // Keep file descriptor open to maintain lock } -static void process_remove_pid_file(void) { unlink(PID_FILE); } +static void process_remove_pid_file(void) { + unlink(PID_FILE); +} static pid_t process_get_running_pid(void) { int fd = open(PID_FILE, O_RDONLY); if (fd < 0) { - return -1; // No PID file exists + return -1; // No PID file exists } // Try to get a shared lock to read the file @@ -107,7 +110,7 @@ static pid_t process_get_running_pid(void) { // Check if process is actually running if (kill(pid, 0) == 0) { - return pid; // Process is running + return pid; // Process is running } // Process is not running, remove stale PID file @@ -123,12 +126,12 @@ static int process_handle_toggle(void) { bongocat_log_info("Stopping bongocat (PID: %d)", running_pid); if (kill(running_pid, SIGTERM) == 0) { // Wait a bit for graceful shutdown - for (int i = 0; i < 50; i++) { // Wait up to 5 seconds + for (int i = 0; i < 50; i++) { // Wait up to 5 seconds if (kill(running_pid, 0) != 0) { bongocat_log_info("Bongocat stopped successfully"); return 0; } - usleep(100000); // 100ms + usleep(100000); // 100ms } // Force kill if still running @@ -141,7 +144,7 @@ static int process_handle_toggle(void) { } } else { bongocat_log_info("Bongocat is not running, starting it now"); - return -1; // Signal to continue with normal startup + return -1; // Signal to continue with normal startup } return 0; @@ -169,6 +172,23 @@ static void signal_handler(int sig) { } } +// Crash signal handler - ensures child cleanup on crash +static void crash_signal_handler(int sig) { + // Clean up child process immediately + input_cleanup(); + process_remove_pid_file(); + + // Reset to default handler and re-raise + signal(sig, SIG_DFL); + raise(sig); +} + +// atexit handler - ensures cleanup on any exit +static void atexit_cleanup(void) { + input_cleanup(); + process_remove_pid_file(); +} + static bongocat_error_t signal_setup_handlers(void) { struct sigaction sa; @@ -195,6 +215,20 @@ static bongocat_error_t signal_setup_handlers(void) { // Ignore SIGPIPE signal(SIGPIPE, SIG_IGN); + // Setup crash signal handlers to ensure child cleanup + struct sigaction crash_sa; + crash_sa.sa_handler = crash_signal_handler; + sigemptyset(&crash_sa.sa_mask); + crash_sa.sa_flags = SA_RESETHAND; // Reset to default after handling + + sigaction(SIGSEGV, &crash_sa, NULL); + sigaction(SIGABRT, &crash_sa, NULL); + sigaction(SIGFPE, &crash_sa, NULL); + sigaction(SIGILL, &crash_sa, NULL); + + // Register atexit handler for normal exits + atexit(atexit_cleanup); + return BONGOCAT_SUCCESS; } @@ -229,11 +263,24 @@ static bool config_devices_changed(const config_t *old_config, static void config_reload_callback(const char *config_path) { bongocat_log_info("Reloading configuration from: %s", config_path); - // IMPORTANT: Save old device count BEFORE calling load_config, + // IMPORTANT: Save old device info BEFORE calling load_config, // because load_config calls config_cleanup_devices() which frees // the keyboard_devices array that g_config still points to. int old_num_devices = g_config.num_keyboard_devices; + // Copy old device paths so we can compare after reload + char **old_device_paths = NULL; + if (old_num_devices > 0 && g_config.keyboard_devices != NULL) { + old_device_paths = malloc(sizeof(char *) * old_num_devices); + if (old_device_paths != NULL) { + for (int i = 0; i < old_num_devices; i++) { + old_device_paths[i] = g_config.keyboard_devices[i] + ? strdup(g_config.keyboard_devices[i]) + : NULL; + } + } + } + // Create a temporary config to test loading config_t temp_config; bongocat_error_t result = load_config(&temp_config, config_path); @@ -242,12 +289,39 @@ static void config_reload_callback(const char *config_path) { bongocat_log_error("Failed to reload config: %s", bongocat_error_string(result)); bongocat_log_info("Keeping current configuration"); + // Free saved paths + if (old_device_paths != NULL) { + for (int i = 0; i < old_num_devices; i++) { + free(old_device_paths[i]); + } + free(old_device_paths); + } return; } - // Check if device count changed (we can't access old device paths - // because they were freed by load_config, so just check count) + // Check if devices changed (count or any path differs) bool devices_changed = (old_num_devices != temp_config.num_keyboard_devices); + if (!devices_changed && old_device_paths != NULL) { + // Same count - check if any paths differ + for (int i = 0; i < old_num_devices; i++) { + const char *old_path = old_device_paths[i]; + const char *new_path = temp_config.keyboard_devices[i]; + if ((old_path == NULL) != (new_path == NULL) || + (old_path != NULL && new_path != NULL && + strcmp(old_path, new_path) != 0)) { + devices_changed = true; + break; + } + } + } + + // Free saved paths + if (old_device_paths != NULL) { + for (int i = 0; i < old_num_devices; i++) { + free(old_device_paths[i]); + } + free(old_device_paths); + } // Clean up old output_name if it exists and is different // Note: g_config.keyboard_devices is now invalid (freed by load_config) @@ -420,7 +494,7 @@ static int cli_parse_arguments(int argc, char *argv[], cli_args_t *args) { } else if (strcmp(argv[i], "--config") == 0 || strcmp(argv[i], "-c") == 0) { if (i + 1 < argc) { args->config_file = argv[i + 1]; - i++; // Skip the next argument since it's the config file path + i++; // Skip the next argument since it's the config file path } else { bongocat_log_error("--config option requires a file path"); return 1; @@ -446,7 +520,7 @@ int main(int argc, char *argv[]) { bongocat_error_t result; // Initialize error system early - bongocat_error_init(1); // Enable debug initially + bongocat_error_init(1); // Enable debug initially bongocat_log_info("Starting Bongo Cat Overlay v" BONGOCAT_VERSION); @@ -471,7 +545,7 @@ int main(int argc, char *argv[]) { if (args.toggle_mode) { int toggle_result = process_handle_toggle(); if (toggle_result >= 0) { - return toggle_result; // Either successfully toggled off or error + return toggle_result; // Either successfully toggled off or error } // toggle_result == -1 means continue with startup } @@ -499,7 +573,7 @@ int main(int argc, char *argv[]) { if (result != BONGOCAT_SUCCESS) { bongocat_log_error("Failed to load configuration: %s", bongocat_error_string(result)); - process_remove_pid_file(); // Clean up PID file on error + process_remove_pid_file(); // Clean up PID file on error return 1; } @@ -530,5 +604,5 @@ int main(int argc, char *argv[]) { bongocat_log_info("Main loop exited, shutting down"); system_cleanup_and_exit(0); - return 0; // Never reached + return 0; // Never reached } \ No newline at end of file diff --git a/src/graphics/animation.c b/src/graphics/animation.c index 6a6692f6..202c50b5 100644 --- a/src/graphics/animation.c +++ b/src/graphics/animation.c @@ -1,10 +1,12 @@ #define _POSIX_C_SOURCE 199309L #define STB_IMAGE_IMPLEMENTATION #include "graphics/animation.h" + #include "graphics/embedded_assets.h" #include "platform/input.h" #include "platform/wayland.h" #include "utils/memory.h" + #include // ============================================================================= @@ -32,18 +34,112 @@ static bool drawing_is_pixel_in_bounds(int x, int y, int width, int height) { static void drawing_copy_pixel(uint8_t *dest, const unsigned char *src, int dest_idx, int src_idx) { - dest[dest_idx + 0] = src[src_idx + 2]; // B - dest[dest_idx + 1] = src[src_idx + 1]; // G - dest[dest_idx + 2] = src[src_idx + 0]; // R - dest[dest_idx + 3] = src[src_idx + 3]; // A + dest[dest_idx + 0] = src[src_idx + 2]; // B + dest[dest_idx + 1] = src[src_idx + 1]; // G + dest[dest_idx + 2] = src[src_idx + 0]; // R + dest[dest_idx + 3] = src[src_idx + 3]; // A } static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_idx, uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - dest[dest_idx + 0] = b; // B - dest[dest_idx + 1] = g; // G - dest[dest_idx + 2] = r; // R - dest[dest_idx + 3] = a; // A + dest[dest_idx + 0] = b; // B + dest[dest_idx + 1] = g; // G + dest[dest_idx + 2] = r; // R + dest[dest_idx + 3] = a; // A +} + +// Alpha blend source pixel onto destination - enables smooth anti-aliased edges +static void drawing_blend_pixel(uint8_t *dest, int dest_idx, uint8_t src_r, + uint8_t src_g, uint8_t src_b, uint8_t src_a) { + // Skip fully transparent pixels + if (src_a == 0) { + return; + } + + // Fully opaque - direct copy (fast path) + if (src_a == 255) { + dest[dest_idx + 0] = src_b; + dest[dest_idx + 1] = src_g; + dest[dest_idx + 2] = src_r; + dest[dest_idx + 3] = 255; + return; + } + + // Alpha blend: out = src * alpha + dest * (1 - alpha) + float alpha = src_a / 255.0f; + float inv_alpha = 1.0f - alpha; + + uint8_t dest_b = dest[dest_idx + 0]; + uint8_t dest_g = dest[dest_idx + 1]; + uint8_t dest_r = dest[dest_idx + 2]; + + dest[dest_idx + 0] = (uint8_t)(src_b * alpha + dest_b * inv_alpha + 0.5f); + dest[dest_idx + 1] = (uint8_t)(src_g * alpha + dest_g * inv_alpha + 0.5f); + dest[dest_idx + 2] = (uint8_t)(src_r * alpha + dest_r * inv_alpha + 0.5f); + dest[dest_idx + 3] = 255; +} + +// Box filter for high-quality downscaling - averages all source pixels that +// map to a destination pixel. Produces much smoother results than bilinear +// when shrinking images significantly. +static void drawing_get_box_filtered_pixel(const unsigned char *src, int src_w, + int src_h, int dest_x, int dest_y, + int target_w, int target_h, + int mirror_x, int mirror_y, + uint8_t *r, uint8_t *g, uint8_t *b, + uint8_t *a) { + // Calculate the source region that maps to this destination pixel + float src_x_start = ((float)dest_x * src_w) / target_w; + float src_x_end = ((float)(dest_x + 1) * src_w) / target_w; + float src_y_start = ((float)dest_y * src_h) / target_h; + float src_y_end = ((float)(dest_y + 1) * src_h) / target_h; + + // Clamp to image bounds + int x0 = (int)src_x_start; + int x1 = (int)src_x_end; + int y0 = (int)src_y_start; + int y1 = (int)src_y_end; + + if (x0 < 0) + x0 = 0; + if (y0 < 0) + y0 = 0; + if (x1 >= src_w) + x1 = src_w - 1; + if (y1 >= src_h) + y1 = src_h - 1; + if (x1 < x0) + x1 = x0; + if (y1 < y0) + y1 = y0; + + // Accumulate all pixels in the source region + float sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0; + int count = 0; + + for (int sy = y0; sy <= y1; sy++) { + for (int sx = x0; sx <= x1; sx++) { + int mx = mirror_x ? (src_w - 1 - sx) : sx; + int my = mirror_y ? (src_h - 1 - sy) : sy; + int idx = (my * src_w + mx) * 4; + + sum_r += src[idx + 0]; + sum_g += src[idx + 1]; + sum_b += src[idx + 2]; + sum_a += src[idx + 3]; + count++; + } + } + + // Average the accumulated values + if (count > 0) { + *r = (uint8_t)(sum_r / count + 0.5f); + *g = (uint8_t)(sum_g / count + 0.5f); + *b = (uint8_t)(sum_b / count + 0.5f); + *a = (uint8_t)(sum_a / count + 0.5f); + } else { + *r = *g = *b = *a = 0; + } } // Bilinear interpolation for smooth scaling @@ -76,10 +172,10 @@ static void drawing_get_interpolated_pixel(const unsigned char *src, int src_w, float dy = fy - y1; // Get the four surrounding pixels - int idx_tl = (y1 * src_w + x1) * 4; // top-left - int idx_tr = (y1 * src_w + x2) * 4; // top-right - int idx_bl = (y2 * src_w + x1) * 4; // bottom-left - int idx_br = (y2 * src_w + x2) * 4; // bottom-right + int idx_tl = (y1 * src_w + x1) * 4; // top-left + int idx_tr = (y1 * src_w + x2) * 4; // top-right + int idx_bl = (y2 * src_w + x1) * 4; // bottom-left + int idx_br = (y2 * src_w + x2) * 4; // bottom-right // Interpolate each channel for (int c = 0; c < 4; c++) { @@ -90,16 +186,16 @@ static void drawing_get_interpolated_pixel(const unsigned char *src, int src_w, switch (c) { case 0: *r = (uint8_t)(result + 0.5f); - break; // R + break; // R case 1: *g = (uint8_t)(result + 0.5f); - break; // G + break; // G case 2: *b = (uint8_t)(result + 0.5f); - break; // B + break; // B case 3: *a = (uint8_t)(result + 0.5f); - break; // A + break; // A } } } @@ -107,7 +203,6 @@ static void drawing_get_interpolated_pixel(const unsigned char *src, int src_w, void blit_image_scaled(uint8_t *dest, int dest_w, int dest_h, unsigned char *src, int src_w, int src_h, int offset_x, int offset_y, int target_w, int target_h) { - bool use_antialiasing = current_config && current_config->enable_antialiasing; for (int y = 0; y < target_h; y++) { @@ -122,26 +217,37 @@ void blit_image_scaled(uint8_t *dest, int dest_w, int dest_h, int dest_idx = (dy * dest_w + dx) * 4; if (use_antialiasing) { - // Use bilinear interpolation for smooth scaling - float fx = ((float)x * src_w) / target_w; - float fy = ((float)y * src_h) / target_h; - - // Apply mirroring based on configuration - if (current_config->mirror_x) { - fx = (src_w - 1) - fx; - } - if (current_config->mirror_y) { - fy = (src_h - 1) - fy; - } - uint8_t r, g, b, a; - drawing_get_interpolated_pixel(src, src_w, src_h, fx, fy, &r, &g, &b, - &a); - // Only draw non-transparent pixels - if (a > 128) { - drawing_copy_pixel_rgba(dest, dest_idx, r, g, b, a); + // Use box filter for downscaling (shrinking) - produces smoother + // results Use bilinear interpolation for upscaling or same size + bool is_downscaling = (target_w < src_w) || (target_h < src_h); + + if (is_downscaling) { + // Box filter: average all source pixels that map to this dest pixel + drawing_get_box_filtered_pixel(src, src_w, src_h, x, y, target_w, + target_h, current_config->mirror_x, + current_config->mirror_y, &r, &g, &b, + &a); + } else { + // Bilinear interpolation for upscaling + float fx = ((float)x * src_w) / target_w; + float fy = ((float)y * src_h) / target_h; + + // Apply mirroring based on configuration + if (current_config->mirror_x) { + fx = (src_w - 1) - fx; + } + if (current_config->mirror_y) { + fy = (src_h - 1) - fy; + } + + drawing_get_interpolated_pixel(src, src_w, src_h, fx, fy, &r, &g, &b, + &a); } + + // Alpha blend onto destination (enables smooth anti-aliased edges) + drawing_blend_pixel(dest, dest_idx, r, g, b, a); } else { // Use nearest-neighbor scaling (original behavior) int sx = (x * src_w) / target_w; @@ -220,7 +326,7 @@ static bool anim_is_sleep_time(const config_t *config) { } static int anim_get_random_active_frame(void) { - return (rand() % 2) + 1; // Frame 1 or 2 (active frames) + return (rand() % 2) + 1; // Frame 1 or 2 (active frames) } static void anim_trigger_frame_change(int new_frame, long duration_us, @@ -254,7 +360,7 @@ static void anim_handle_test_animation(animation_state_t *state, static void anim_handle_key_press(animation_state_t *state, long current_time_us) { - if (!*any_key_pressed) { + if (!atomic_load(any_key_pressed)) { return; } @@ -266,8 +372,8 @@ static void anim_handle_key_press(animation_state_t *state, bongocat_log_debug("Key press detected - switching to frame %d", new_frame); anim_trigger_frame_change(new_frame, duration_us, current_time_us, state); - *any_key_pressed = 0; - state->test_counter = 0; // Reset test counter + atomic_store(any_key_pressed, 0); + state->test_counter = 0; // Reset test counter state->last_key_pressed_timestamp = current_time_us; } } @@ -466,4 +572,6 @@ void animation_cleanup(void) { bongocat_log_debug("Animation cleanup complete"); } -void animation_trigger(void) { *any_key_pressed = 1; } \ No newline at end of file +void animation_trigger(void) { + atomic_store(any_key_pressed, 1); +} \ No newline at end of file diff --git a/src/platform/input.c b/src/platform/input.c index d086f81b..48d982ad 100644 --- a/src/platform/input.c +++ b/src/platform/input.c @@ -1,26 +1,39 @@ #define _POSIX_C_SOURCE 200809L #define _DEFAULT_SOURCE #include "platform/input.h" + #include "graphics/animation.h" #include "utils/memory.h" + #include +#include #include +#include #include #include #include #include -int *any_key_pressed; +atomic_int *any_key_pressed; static pid_t input_child_pid = -1; // Child process signal handler - exits quietly without logging static void child_signal_handler(int sig) { - (void)sig; // Suppress unused parameter warning + (void)sig; // Suppress unused parameter warning exit(0); } static void capture_input_multiple(char **device_paths, int num_devices, int enable_debug) { + // CRITICAL: Set this process to die when parent dies + // This prevents ghost child processes if parent crashes + prctl(PR_SET_PDEATHSIG, SIGTERM); + + // Check if parent already died before we set PR_SET_PDEATHSIG + if (getppid() == 1) { + exit(0); // Parent already died, orphaned to init + } + // Set up child-specific signal handlers to avoid duplicate logging struct sigaction sa; sa.sa_handler = child_signal_handler; @@ -35,6 +48,8 @@ static void capture_input_multiple(char **device_paths, int num_devices, char **unique_paths = BONGOCAT_MALLOC(num_devices * sizeof(char *)); if (!fds || !unique_paths) { bongocat_log_error("Failed to allocate memory for file descriptors"); + BONGOCAT_SAFE_FREE(fds); + BONGOCAT_SAFE_FREE(unique_paths); exit(1); } @@ -98,18 +113,20 @@ static void capture_input_multiple(char **device_paths, int num_devices, if (valid_devices == 0) { bongocat_log_error("No valid input devices found"); BONGOCAT_SAFE_FREE(fds); + BONGOCAT_SAFE_FREE(unique_paths); exit(1); } bongocat_log_info("Successfully opened %d/%d input devices", valid_devices, num_devices); - struct input_event ev[128]; // Increased buffer size for better I/O efficiency + struct input_event + ev[128]; // Increased buffer size for better I/O efficiency int rd; fd_set readfds; struct timeval timeout; int check_counter = 0; - int adaptive_check_interval = 5; // Start with 5 seconds, can increase to 30 + int adaptive_check_interval = 5; // Start with 5 seconds, can increase to 30 while (1) { FD_ZERO(&readfds); @@ -132,7 +149,7 @@ static void capture_input_multiple(char **device_paths, int num_devices, int select_result = select(max_fd + 1, &readfds, NULL, NULL, &timeout); if (select_result < 0) { if (errno == EINTR) - continue; // Interrupted by signal + continue; // Interrupted by signal bongocat_log_error("Select error: %s", strerror(errno)); break; } @@ -147,7 +164,7 @@ static void capture_input_multiple(char **device_paths, int num_devices, // Check for devices that have become available for (int i = 0; i < num_devices; i++) { - if (fds[i] < 0) { // Device was not available before + if (fds[i] < 0) { // Device was not available before struct stat st; if (stat(unique_paths[i], &st) == 0 && S_ISCHR(st.st_mode)) { // Device is now available, try to open it @@ -179,7 +196,7 @@ static void capture_input_multiple(char **device_paths, int num_devices, bongocat_log_debug("Reset device check interval to 5 seconds"); } } - continue; // Continue to next iteration + continue; // Continue to next iteration } // Check which devices have data @@ -260,15 +277,16 @@ bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, num_devices); // Initialize shared memory for key press flag - any_key_pressed = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, -1, 0); + any_key_pressed = + (atomic_int *)mmap(NULL, sizeof(atomic_int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (any_key_pressed == MAP_FAILED) { bongocat_log_error( "Failed to create shared memory for input monitoring: %s", strerror(errno)); return BONGOCAT_ERROR_MEMORY; } - *any_key_pressed = 0; + atomic_store(any_key_pressed, 0); // Fork process for input monitoring input_child_pid = fork(); @@ -324,7 +342,7 @@ bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, } } - usleep(100000); // Wait 100ms + usleep(100000); // Wait 100ms wait_attempts++; } @@ -343,8 +361,9 @@ bongocat_error_t input_restart_monitoring(char **device_paths, int num_devices, (any_key_pressed == NULL || any_key_pressed == MAP_FAILED); if (need_new_shm) { - any_key_pressed = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, -1, 0); + any_key_pressed = + (atomic_int *)mmap(NULL, sizeof(atomic_int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); if (any_key_pressed == MAP_FAILED) { bongocat_log_error( "Failed to create shared memory for input monitoring: %s", @@ -410,7 +429,7 @@ void input_cleanup(void) { } } - usleep(100000); // Wait 100ms + usleep(100000); // Wait 100ms wait_attempts++; } diff --git a/src/platform/wayland.c b/src/platform/wayland.c index e67e6944..b2c5bb11 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -1,9 +1,12 @@ #define _POSIX_C_SOURCE 200809L #include "platform/wayland.h" + #include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" #include "../protocols/xdg-output-unstable-v1-client-protocol.h" #include "graphics/animation.h" + #include +#include #include // ============================================================================= @@ -11,8 +14,8 @@ // ============================================================================= // Wayland globals -bool configured = false; -bool fullscreen_detected = false; +atomic_bool configured = false; +atomic_bool fullscreen_detected = false; struct wl_display *display; struct wl_compositor *compositor; struct wl_shm *shm; @@ -89,8 +92,6 @@ static const struct zxdg_output_v1_listener xdg_output_listener = { typedef struct { struct zwlr_foreign_toplevel_manager_v1 *manager; bool has_fullscreen_toplevel; - int fullscreen_toplevel_count; // Track number of fullscreen toplevels - struct timeval last_check; } fullscreen_detector_t; static fullscreen_detector_t fs_detector = {0}; @@ -102,110 +103,66 @@ static fullscreen_detector_t fs_detector = {0}; static void fs_update_state(bool new_state) { if (new_state != fs_detector.has_fullscreen_toplevel) { fs_detector.has_fullscreen_toplevel = new_state; - fullscreen_detected = new_state; + atomic_store(&fullscreen_detected, new_state); bongocat_log_info("Fullscreen state changed: %s", - fullscreen_detected ? "detected" : "cleared"); + new_state ? "detected" : "cleared"); - if (configured) { + if (atomic_load(&configured)) { draw_bar(); } } } -static bool fs_check_compositor_fallback(void) { - bongocat_log_debug("Using compositor-specific fullscreen detection"); - - // Try Hyprland first - FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); - if (fp) { - char line[512]; - bool is_fullscreen = false; - - while (fgets(line, sizeof(line), fp)) { - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') { - line[len - 1] = '\0'; - } - - if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || - strstr(line, "fullscreen: true")) { - is_fullscreen = true; - bongocat_log_debug("Fullscreen detected in Hyprland"); - break; - } - } - pclose(fp); - return is_fullscreen; - } - - // Try Sway as fallback - fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); - if (fp) { - char sway_buffer[4096]; - bool is_fullscreen = false; - - while (fgets(sway_buffer, sizeof(sway_buffer), fp)) { - if (strstr(sway_buffer, "\"fullscreen_mode\":1")) { - is_fullscreen = true; - bongocat_log_debug("Fullscreen detected in Sway"); - break; - } - } - pclose(fp); - return is_fullscreen; - } - - bongocat_log_debug("No supported compositor found for fullscreen detection"); - return false; -} - -static bool fs_check_status(void) { - if (fs_detector.manager) { - return fs_detector.has_fullscreen_toplevel; - } - return fs_check_compositor_fallback(); -} - // Foreign toplevel protocol event handlers -// Each toplevel tracks its own fullscreen state via user data +// Each toplevel tracks its fullscreen and activated state typedef struct { bool is_fullscreen; + bool is_activated; // Track if this toplevel is the currently focused one } toplevel_data_t; +// Track the currently active toplevel's fullscreen state +static bool active_toplevel_fullscreen = false; + static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state) { + (void)handle; toplevel_data_t *toplevel_data = (toplevel_data_t *)data; if (!toplevel_data) return; - bool was_fullscreen = toplevel_data->is_fullscreen; bool is_fullscreen = false; + bool is_activated = false; uint32_t *state_ptr; wl_array_for_each(state_ptr, state) { if (*state_ptr == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { is_fullscreen = true; - break; } - } - - // Update count if state changed - if (is_fullscreen && !was_fullscreen) { - fs_detector.fullscreen_toplevel_count++; - } else if (!is_fullscreen && was_fullscreen) { - fs_detector.fullscreen_toplevel_count--; - if (fs_detector.fullscreen_toplevel_count < 0) { - fs_detector.fullscreen_toplevel_count = 0; + if (*state_ptr == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED) { + is_activated = true; } } + + bool was_activated = toplevel_data->is_activated; + bool was_fullscreen = toplevel_data->is_fullscreen; + toplevel_data->is_fullscreen = is_fullscreen; + toplevel_data->is_activated = is_activated; - // Update global state based on count - bool new_state = (fs_detector.fullscreen_toplevel_count > 0); - fs_update_state(new_state); + // Case 1: Window becomes active - update state based on its fullscreen status + if (is_activated) { + active_toplevel_fullscreen = is_fullscreen; + fs_update_state(is_fullscreen); + } + // Case 2: Previously active fullscreen window loses activation + // (e.g., switching to empty workspace) - show bongocat + else if (was_activated && was_fullscreen && !is_activated) { + active_toplevel_fullscreen = false; + fs_update_state(false); + } } static void @@ -213,14 +170,10 @@ fs_handle_toplevel_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { toplevel_data_t *toplevel_data = (toplevel_data_t *)data; if (toplevel_data) { - // Decrement count if this toplevel was fullscreen - if (toplevel_data->is_fullscreen) { - fs_detector.fullscreen_toplevel_count--; - if (fs_detector.fullscreen_toplevel_count < 0) { - fs_detector.fullscreen_toplevel_count = 0; - } - bool new_state = (fs_detector.fullscreen_toplevel_count > 0); - fs_update_state(new_state); + // If the closed toplevel was the active fullscreen one, clear state + if (toplevel_data->is_activated && toplevel_data->is_fullscreen) { + active_toplevel_fullscreen = false; + fs_update_state(false); } free(toplevel_data); } @@ -247,19 +200,19 @@ static void fs_handle_app_id(void *data, static void fs_handle_output_enter(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct wl_output *output) { + struct wl_output *toplevel_output) { (void)data; (void)handle; - (void)output; + (void)toplevel_output; } static void fs_handle_output_leave(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, - struct wl_output *output) { + struct wl_output *toplevel_output) { (void)data; (void)handle; - (void)output; + (void)toplevel_output; } static void fs_handle_done(void *data, @@ -296,12 +249,17 @@ fs_handle_manager_toplevel(void *data, (void)manager; // Allocate per-toplevel data to track its fullscreen state - toplevel_data_t *toplevel_data = calloc(1, sizeof(toplevel_data_t)); + toplevel_data_t *toplevel_data = malloc(sizeof(toplevel_data_t)); if (!toplevel_data) { bongocat_log_error("Failed to allocate toplevel data"); return; } + // Initialize: toplevel starts as not fullscreen and not activated + // State events will update these values + toplevel_data->is_fullscreen = false; + toplevel_data->is_activated = false; + zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &fs_toplevel_listener, toplevel_data); bongocat_log_debug("New toplevel registered for fullscreen monitoring"); @@ -380,26 +338,26 @@ int create_shm(int size) { } void draw_bar(void) { - if (!configured) { + if (!atomic_load(&configured)) { bongocat_log_debug("Surface not configured yet, skipping draw"); return; } - int effective_opacity = - fullscreen_detected ? 0 : current_config->overlay_opacity; + bool is_fullscreen = atomic_load(&fullscreen_detected); + int effective_opacity = is_fullscreen ? 0 : current_config->overlay_opacity; // Clear buffer with transparency for (int i = 0; i < current_config->screen_width * current_config->bar_height * 4; i += 4) { - pixels[i] = 0; // B - pixels[i + 1] = 0; // G - pixels[i + 2] = 0; // R - pixels[i + 3] = effective_opacity; // A + pixels[i] = 0; // B + pixels[i + 1] = 0; // G + pixels[i + 2] = 0; // R + pixels[i + 3] = effective_opacity; // A } // Draw cat if visible - if (!fullscreen_detected) { + if (!is_fullscreen) { pthread_mutex_lock(&anim_lock); int cat_height = current_config->cat_height; int cat_width = (cat_height * CAT_IMAGE_WIDTH) / CAT_IMAGE_HEIGHT; @@ -446,7 +404,7 @@ static void layer_surface_configure(void *data __attribute__((unused)), uint32_t serial, uint32_t w, uint32_t h) { bongocat_log_debug("Layer surface configured: %dx%d", w, h); zwlr_layer_surface_v1_ack_configure(ls, serial); - configured = true; + atomic_store(&configured, true); draw_bar(); } @@ -455,7 +413,7 @@ static void layer_surface_closed(void *data __attribute__((unused)), struct zwlr_layer_surface_v1 *ls __attribute__((unused))) { bongocat_log_info("Layer surface closed by compositor"); - configured = false; + atomic_store(&configured, false); } static struct zwlr_layer_surface_v1_listener layer_listener = { @@ -774,23 +732,8 @@ bongocat_error_t wayland_run(volatile sig_atomic_t *running) { BONGOCAT_CHECK_NULL(running, BONGOCAT_ERROR_INVALID_PARAM); bongocat_log_info("Starting Wayland event loop"); - const int check_interval_ms = 100; while (*running && display) { - // Periodic fullscreen check for fallback detection - struct timeval now; - gettimeofday(&now, NULL); - long elapsed_ms = (now.tv_sec - fs_detector.last_check.tv_sec) * 1000 + - (now.tv_usec - fs_detector.last_check.tv_usec) / 1000; - - if (elapsed_ms >= check_interval_ms) { - bool new_state = fs_check_status(); - if (new_state != fullscreen_detected) { - fs_update_state(new_state); - } - fs_detector.last_check = now; - } - // Handle Wayland events struct pollfd pfd = { .fd = wl_display_get_fd(display), @@ -833,7 +776,9 @@ bongocat_error_t wayland_run(volatile sig_atomic_t *running) { // PUBLIC API IMPLEMENTATION // ============================================================================= -int wayland_get_screen_width(void) { return screen_info.screen_width; } +int wayland_get_screen_width(void) { + return screen_info.screen_width; +} void wayland_update_config(config_t *config) { if (!config) { @@ -842,7 +787,7 @@ void wayland_update_config(config_t *config) { } current_config = config; - if (configured) { + if (atomic_load(&configured)) { draw_bar(); } } @@ -933,12 +878,14 @@ void wayland_cleanup(void) { } // Reset state - configured = false; - fullscreen_detected = false; + atomic_store(&configured, false); + atomic_store(&fullscreen_detected, false); memset(&fs_detector, 0, sizeof(fs_detector)); memset(&screen_info, 0, sizeof(screen_info)); bongocat_log_debug("Wayland cleanup complete"); } -const char *wayland_get_current_layer_name(void) { return "OVERLAY"; } +const char *wayland_get_current_layer_name(void) { + return "OVERLAY"; +} diff --git a/src/utils/error.c b/src/utils/error.c index ed613ab9..557e0936 100644 --- a/src/utils/error.c +++ b/src/utils/error.c @@ -1,12 +1,15 @@ #define _POSIX_C_SOURCE 200809L #include "utils/error.h" + #include #include #include static int debug_enabled = 1; -void bongocat_error_init(int enable_debug) { debug_enabled = enable_debug; } +void bongocat_error_init(int enable_debug) { + debug_enabled = enable_debug; +} static void log_timestamp(FILE *stream) { struct timeval tv; @@ -14,7 +17,7 @@ static void log_timestamp(FILE *stream) { char timestamp[64]; gettimeofday(&tv, NULL); - localtime_r(&tv.tv_sec, &tm_info); // Thread-safe version + localtime_r(&tv.tv_sec, &tm_info); // Thread-safe version strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); fprintf(stream, "[%s.%03ld] ", timestamp, tv.tv_usec / 1000); diff --git a/src/utils/memory.c b/src/utils/memory.c index ee43153f..1d44573a 100644 --- a/src/utils/memory.c +++ b/src/utils/memory.c @@ -1,5 +1,7 @@ #include "utils/memory.h" + #include "utils/error.h" + #include #include From b2efcffdd880d2b07b6e8e2f45f86b5ad7eda010 Mon Sep 17 00:00:00 2001 From: Saatvik Sharma Date: Sat, 6 Dec 2025 21:52:20 +0530 Subject: [PATCH 05/18] fix: Prevent Wayland overlay layer from auto-hiding in fullscreen and update RAM estimate. --- README.md | 2 +- bongocat.conf.example | 6 +++--- src/platform/wayland.c | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6aa6c547..cf5a6f49 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A cute Wayland overlay that shows an animated bongo cat reacting to your keyboar - 🎮 Auto-hides in fullscreen apps - 🖥️ Multi-monitor support - 😴 Idle/scheduled sleep mode -- ⚡ Lightweight (~7MB RAM) +- ⚡ Lightweight (~20MB RAM) ## Quick Start diff --git a/bongocat.conf.example b/bongocat.conf.example index 2fe33256..960876d2 100644 --- a/bongocat.conf.example +++ b/bongocat.conf.example @@ -26,7 +26,7 @@ mirror_y=0 # │ OVERLAY BAR │ # └─────────────────────────────────────────────────────────────────────────────┘ -# Bar dimensions +# Bar dimensions (Attention!: Requires restart of bongocat) overlay_height=80 # Background: 0=transparent, 255=opaque @@ -58,7 +58,7 @@ keyboard_device=/dev/input/event4 # │ ANIMATION │ # └─────────────────────────────────────────────────────────────────────────────┘ -fps=60 +fps=30 idle_frame=0 keypress_duration=100 @@ -78,4 +78,4 @@ sleep_end=06:00 # │ DEBUG │ # └─────────────────────────────────────────────────────────────────────────────┘ -enable_debug=0 +enable_debug=0 \ No newline at end of file diff --git a/src/platform/wayland.c b/src/platform/wayland.c index b2c5bb11..2b266c33 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -343,7 +343,10 @@ void draw_bar(void) { return; } - bool is_fullscreen = atomic_load(&fullscreen_detected); + // Skip fullscreen hiding when layer is LAYER_OVERLAY (always visible) + bool is_overlay_layer = + current_config && current_config->layer == LAYER_OVERLAY; + bool is_fullscreen = !is_overlay_layer && atomic_load(&fullscreen_detected); int effective_opacity = is_fullscreen ? 0 : current_config->overlay_opacity; // Clear buffer with transparency From 19fb15709d8cfbf3413807485fab617c6c7da304 Mon Sep 17 00:00:00 2001 From: Saatvik Sharma Date: Sat, 6 Dec 2025 22:12:59 +0530 Subject: [PATCH 06/18] feat: add GitHub Actions workflow for AUR publishing and a contributing guide. --- .github/workflows/aur-publish.yml | 64 ++++++++++++++++++++++ CONTRIBUTING.md | 89 +++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 .github/workflows/aur-publish.yml create mode 100644 CONTRIBUTING.md diff --git a/.github/workflows/aur-publish.yml b/.github/workflows/aur-publish.yml new file mode 100644 index 00000000..699804ee --- /dev/null +++ b/.github/workflows/aur-publish.yml @@ -0,0 +1,64 @@ +name: Publish to AUR + +on: + release: + types: [published] + +jobs: + aur-publish: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate PKGBUILD + run: | + VERSION="${{ github.event.release.tag_name }}" + VERSION="${VERSION#v}" # Remove 'v' prefix if present + + # Download the release tarball to calculate sha256sum + curl -sL "https://github.com/${{ github.repository }}/archive/refs/tags/${{ github.event.release.tag_name }}.tar.gz" -o release.tar.gz + SHA256=$(sha256sum release.tar.gz | cut -d' ' -f1) + + cat > PKGBUILD << 'PKGBUILD_EOF' + # Maintainer: saatvik333 + pkgname=bongocat + pkgver=${VERSION} + pkgrel=1 + pkgdesc="Delightful Wayland overlay that displays an animated bongo cat reacting to keyboard input" + arch=('x86_64' 'aarch64') + url="https://github.com/saatvik333/wayland-bongocat" + license=('MIT') + depends=('wayland') + makedepends=('wayland-protocols' 'make' 'gcc') + source=("$pkgname-$pkgver.tar.gz::https://github.com/saatvik333/wayland-bongocat/archive/refs/tags/v$pkgver.tar.gz") + sha256sums=('${SHA256}') + + build() { + cd "wayland-bongocat-$pkgver" + make release + } + + package() { + cd "wayland-bongocat-$pkgver" + install -Dm755 build/bongocat "$pkgdir/usr/bin/bongocat" + install -Dm755 scripts/find_input_devices.sh "$pkgdir/usr/bin/bongocat-find-devices" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" + install -Dm644 bongocat.conf.example "$pkgdir/usr/share/doc/$pkgname/bongocat.conf.example" + } + PKGBUILD_EOF + + # Replace placeholders with actual values + sed -i "s/\${VERSION}/${VERSION}/g" PKGBUILD + sed -i "s/\${SHA256}/${SHA256}/g" PKGBUILD + + - name: Publish to AUR + uses: KSXGitHub/github-actions-deploy-aur@v3.0.1 + with: + pkgname: bongocat + pkgbuild: ./PKGBUILD + commit_username: ${{ secrets.AUR_USERNAME }} + commit_email: ${{ secrets.AUR_EMAIL }} + ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} + commit_message: "Update to version ${{ github.event.release.tag_name }}" + force_push: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..2aae5694 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,89 @@ +# Contributing to Bongo Cat Wayland Overlay + +Thank you for your interest in contributing! 🐱 + +## Getting Started + +### Prerequisites + +- Wayland compositor with layer shell support +- GCC or Clang (C23 support) +- wayland-client, wayland-protocols +- Make + +### Building + +```bash +git clone https://github.com/saatvik333/wayland-bongocat.git +cd wayland-bongocat +make debug # Development build with debug symbols +make # Release build +``` + +### Running + +```bash +./build/bongocat -c bongocat.conf -w +``` + +## Development Workflow + +### Code Style + +- Run `make format` before committing +- Follow the existing code patterns +- Use meaningful variable names + +### Making Changes + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/my-feature` +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +### Commit Messages + +Use conventional commits: + +``` +feat: add new feature +fix: resolve bug +docs: update documentation +refactor: improve code structure +``` + +## Code Structure + +``` +src/ +├── core/ # Main application logic +├── config/ # Configuration parsing +├── graphics/ # Animation and rendering +├── platform/ # Wayland integration +└── utils/ # Error handling, memory +``` + +## Testing + +```bash +# Run with debug logging +./build/bongocat -c bongocat.conf -w + +# Check for memory leaks +make memcheck +``` + +## Reporting Issues + +When reporting bugs, please include: + +- Your compositor (Sway, Hyprland, etc.) +- Config file contents +- Debug output (`enable_debug=1`) + +## Questions? + +Open an issue or reach out to the maintainer. + +Thanks for helping make Bongo Cat better! 🎉 From b29659e4211cc0b7868e8168cbf5407d6f8fe588 Mon Sep 17 00:00:00 2001 From: Saatvik Sharma Date: Sat, 6 Dec 2025 23:11:29 +0530 Subject: [PATCH 07/18] feat: keyboard hand mapping (v1.3.1) --- CHANGELOG.md | 15 +++++++++++ README.md | 2 +- bongocat.conf.example | 3 +++ include/config/config.h | 1 + include/core/bongocat.h | 2 +- include/platform/input.h | 3 +++ nix/common.nix | 6 +++++ nix/default.nix | 2 +- scripts/find_input_devices.sh | 4 +-- src/config/config.c | 3 +++ src/graphics/animation.c | 51 ++++++++++++++++++++++++++++++++--- src/platform/input.c | 18 ++++++++++++- 12 files changed, 100 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0455bd79..1a21f5b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## [1.3.1] - 2025-12-06 + +### Added + +- **Keyboard Hand Mapping** - Left half of keyboard triggers left cat hand, right half triggers right hand +- New config option `enable_hand_mapping=1` (enabled by default) +- Hand mapping respects `mirror_x` - hands flip when cat is mirrored + +### Changed + +- `enable_hand_mapping` default is now `1` (enabled) +- NixOS module `enableHandMapping` default is now `true` + +--- + ## [1.3.0] - 2025-12-06 ### Added diff --git a/README.md b/README.md index cf5a6f49..39276dcc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bongo Cat Wayland Overlay [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) -[![Version](https://img.shields.io/badge/version-1.3.0-blue.svg)](https://github.com/saatvik333/wayland-bongocat/releases) +[![Version](https://img.shields.io/badge/version-1.3.1-blue.svg)](https://github.com/saatvik333/wayland-bongocat/releases) A cute Wayland overlay that shows an animated bongo cat reacting to your keyboard input. diff --git a/bongocat.conf.example b/bongocat.conf.example index 960876d2..f1984e19 100644 --- a/bongocat.conf.example +++ b/bongocat.conf.example @@ -62,6 +62,9 @@ fps=30 idle_frame=0 keypress_duration=100 +# Hand mapping: 0=random hands, 1=left keys→left hand, right keys→right hand +enable_hand_mapping=1 + # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ SLEEP MODE (optional) │ # └─────────────────────────────────────────────────────────────────────────────┘ diff --git a/include/config/config.h b/include/config/config.h index ec845cd6..3def3c2b 100644 --- a/include/config/config.h +++ b/include/config/config.h @@ -61,6 +61,7 @@ typedef struct { int test_animation_duration; int test_animation_interval; int fps; + int enable_hand_mapping; // 0=random hands, 1=based on key position // Input devices char **keyboard_devices; diff --git a/include/core/bongocat.h b/include/core/bongocat.h index 190cd26f..a0e533dd 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -27,7 +27,7 @@ // VERSION // ============================================================================= -#define BONGOCAT_VERSION "1.3.0" +#define BONGOCAT_VERSION "1.3.1" // ============================================================================= // COMPILE-TIME CONSTANTS diff --git a/include/platform/input.h b/include/platform/input.h index 5bc13a20..6d3c66e5 100644 --- a/include/platform/input.h +++ b/include/platform/input.h @@ -13,6 +13,9 @@ // Shared memory for key press state (thread-safe) extern atomic_int *any_key_pressed; +// Last pressed key code for hand mapping (0 = none) +extern atomic_int *last_key_code; + // ============================================================================= // INPUT MONITORING FUNCTIONS // ============================================================================= diff --git a/nix/common.nix b/nix/common.nix index 18fa4946..e3339736 100644 --- a/nix/common.nix +++ b/nix/common.nix @@ -34,6 +34,7 @@ test_animation_duration=${toString cfg.testAnimationDuration} test_animation_interval=${toString cfg.testAnimationInterval} fps=${toString cfg.fps} + enable_hand_mapping=${if cfg.enableHandMapping then "1" else "0"} # Sleep mode idle_sleep_timeout=${toString cfg.idleSleepTimeout} @@ -208,6 +209,11 @@ in { example = 120; description = "Animation framerate (FPS)"; }; + enableHandMapping = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Map left/right keyboard halves to left/right cat hands"; + }; # Input devices inputDevices = lib.mkOption { diff --git a/nix/default.nix b/nix/default.nix index 3c0aec2a..c7306138 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -8,7 +8,7 @@ }: stdenv.mkDerivation (finalAttrs: { pname = "wayland-bongocat"; - version = "1.3.0"; + version = "1.3.1"; src = ../.; # Build toolchain and dependencies diff --git a/scripts/find_input_devices.sh b/scripts/find_input_devices.sh index 0e72fc95..ffd140d3 100755 --- a/scripts/find_input_devices.sh +++ b/scripts/find_input_devices.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash # ═══════════════════════════════════════════════════════════════════════════════ -# Bongo Cat - Input Device Discovery Tool v1.3.0 +# Bongo Cat - Input Device Discovery Tool v1.3.1 # Interactive keyboard detection by listening for actual key events # ═══════════════════════════════════════════════════════════════════════════════ set -euo pipefail -VERSION="1.3.0" +VERSION="1.3.1" SCRIPT_NAME="bongocat-find-devices" # Colors diff --git a/src/config/config.c b/src/config/config.c index 87fbed5e..53147905 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -230,6 +230,8 @@ config_parse_integer_key(config_t *config, const char *key, const char *value) { config->mirror_y = int_value; } else if (strcmp(key, "enable_antialiasing") == 0) { config->enable_antialiasing = int_value; + } else if (strcmp(key, "enable_hand_mapping") == 0) { + config->enable_hand_mapping = int_value; } else if (strcmp(key, "enable_debug") == 0) { config->enable_debug = int_value; } else if (strcmp(key, "enable_scheduled_sleep") == 0) { @@ -454,6 +456,7 @@ static void config_set_defaults(config_t *config) { .mirror_x = 0, .mirror_y = 0, .enable_antialiasing = 1, + .enable_hand_mapping = 1, // Enabled by default .enable_debug = 1, .layer = LAYER_TOP, // Default to TOP for broader compatibility .overlay_position = POSITION_TOP, diff --git a/src/graphics/animation.c b/src/graphics/animation.c index 202c50b5..3d6ae28e 100644 --- a/src/graphics/animation.c +++ b/src/graphics/animation.c @@ -325,8 +325,51 @@ static bool anim_is_sleep_time(const config_t *config) { : (now_minutes >= begin || now_minutes < end)); } -static int anim_get_random_active_frame(void) { - return (rand() % 2) + 1; // Frame 1 or 2 (active frames) +// Get frame based on keyboard position (left=1, right=2) +// Uses Linux input keycodes from +static int get_frame_for_keycode(int keycode) { + // Left-hand keys on QWERTY keyboard + // clang-format off + static const int left_keys[] = { + // Number row left half (1-6) + 2, 3, 4, 5, 6, 7, // KEY_1 to KEY_6 + // QWERTY row left half + 16, 17, 18, 19, 20, // KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T + // Home row left half + 30, 31, 32, 33, 34, // KEY_A, KEY_S, KEY_D, KEY_F, KEY_G + // Bottom row left half + 44, 45, 46, 47, 48, // KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B + // Modifiers and special keys (left side) + 1, // KEY_ESC + 15, // KEY_TAB + 58, // KEY_CAPSLOCK + 42, // KEY_LEFTSHIFT + 29, // KEY_LEFTCTRL + 56, // KEY_LEFTALT + 41, // KEY_GRAVE (backtick) + 125, // KEY_LEFTMETA (super) + }; + // clang-format on + + for (size_t i = 0; i < sizeof(left_keys) / sizeof(left_keys[0]); i++) { + if (keycode == left_keys[i]) { + return 1; // Left hand + } + } + return 2; // Right hand (default for all other keys) +} + +static int anim_get_active_frame(void) { + if (current_config && current_config->enable_hand_mapping) { + int keycode = atomic_load(last_key_code); + int frame = get_frame_for_keycode(keycode); + // Flip hands when cat is mirrored horizontally + if (current_config->mirror_x) { + frame = (frame == 1) ? 2 : 1; + } + return frame; + } + return (rand() % 2) + 1; // Random: frame 1 or 2 } static void anim_trigger_frame_change(int new_frame, long duration_us, @@ -349,7 +392,7 @@ static void anim_handle_test_animation(animation_state_t *state, state->test_counter++; if (state->test_counter > state->test_interval_frames) { - int new_frame = anim_get_random_active_frame(); + int new_frame = anim_get_active_frame(); long duration_us = current_config->test_animation_duration * 1000; bongocat_log_debug("Test animation trigger"); @@ -366,7 +409,7 @@ static void anim_handle_key_press(animation_state_t *state, if (!current_config->enable_scheduled_sleep || !anim_is_sleep_time(current_config)) { - int new_frame = anim_get_random_active_frame(); + int new_frame = anim_get_active_frame(); long duration_us = current_config->keypress_duration * 1000; bongocat_log_debug("Key press detected - switching to frame %d", new_frame); diff --git a/src/platform/input.c b/src/platform/input.c index 48d982ad..9c5cac96 100644 --- a/src/platform/input.c +++ b/src/platform/input.c @@ -15,6 +15,7 @@ #include atomic_int *any_key_pressed; +atomic_int *last_key_code; static pid_t input_child_pid = -1; // Child process signal handler - exits quietly without logging @@ -225,10 +226,12 @@ static void capture_input_multiple(char **device_paths, int num_devices, // Batch process events for better performance int num_events = rd / sizeof(struct input_event); bool key_pressed = false; + int captured_keycode = 0; for (int j = 0; j < num_events; j++) { if (ev[j].type == EV_KEY && ev[j].value == 1) { key_pressed = true; + captured_keycode = ev[j].code; // Store for hand mapping if (enable_debug) { bongocat_log_debug( "Key event: device=%s, code=%d, time=%ld.%06ld", @@ -240,6 +243,7 @@ static void capture_input_multiple(char **device_paths, int num_devices, // Trigger animation only once per batch to reduce overhead if (key_pressed) { + atomic_store(last_key_code, captured_keycode); animation_trigger(); } } @@ -276,7 +280,7 @@ bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, bongocat_log_info("Initializing input monitoring system for %d devices", num_devices); - // Initialize shared memory for key press flag + // Initialize shared memory for key press flag and keycode any_key_pressed = (atomic_int *)mmap(NULL, sizeof(atomic_int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); @@ -288,6 +292,18 @@ bongocat_error_t input_start_monitoring(char **device_paths, int num_devices, } atomic_store(any_key_pressed, 0); + // Shared memory for last key code (for hand mapping) + last_key_code = + (atomic_int *)mmap(NULL, sizeof(atomic_int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (last_key_code == MAP_FAILED) { + bongocat_log_error("Failed to create shared memory for key code: %s", + strerror(errno)); + munmap(any_key_pressed, sizeof(atomic_int)); + return BONGOCAT_ERROR_MEMORY; + } + atomic_store(last_key_code, 0); + // Fork process for input monitoring input_child_pid = fork(); if (input_child_pid < 0) { From e08ce0500bbe140d58e1de479e8533b092dcaa1d Mon Sep 17 00:00:00 2001 From: Saatvik Sharma Date: Sun, 7 Dec 2025 15:33:59 +0530 Subject: [PATCH 08/18] Release v1.3.2: Fix monitor reconnection (#15) --- CHANGELOG.md | 19 +++ README.md | 4 +- bongocat.conf.example | 4 +- include/core/bongocat.h | 2 +- nix/default.nix | 2 +- scripts/find_input_devices.sh | 4 +- src/core/main.c | 15 +- src/graphics/animation.c | 93 ++++++++---- src/platform/input.c | 7 + src/platform/wayland.c | 262 +++++++++++++++++++++++++++++++--- 10 files changed, 356 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a21f5b3..c255893b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. +## [1.3.2] - 2025-12-07 + +### Fixed + +- **Monitor Reconnection** - Overlay now survives monitor disconnect/reconnect (fixes #15) +- **Dynamic Overlay Resize** - Changing `overlay_height` via config reload no longer crashes +- **Ghost Process Prevention** - Added SIGQUIT/SIGHUP handlers and parent liveness check in child + +### Improved + +- **Performance Optimizations** + - Fast buffer clearing using memset (~4x faster) + - Skip unchanged frames when idle (~95% fewer redraws) + - Hoisted loop invariants in image scaling +- **Thread Safety** - Mutex protection during buffer recreation +- **Memory Usage** - Reduced from ~20MB to ~8MB RAM + +--- + ## [1.3.1] - 2025-12-06 ### Added diff --git a/README.md b/README.md index 39276dcc..6d33a705 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bongo Cat Wayland Overlay [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) -[![Version](https://img.shields.io/badge/version-1.3.1-blue.svg)](https://github.com/saatvik333/wayland-bongocat/releases) +[![Version](https://img.shields.io/badge/version-1.3.2-blue.svg)](https://github.com/saatvik333/wayland-bongocat/releases) A cute Wayland overlay that shows an animated bongo cat reacting to your keyboard input. @@ -14,7 +14,7 @@ A cute Wayland overlay that shows an animated bongo cat reacting to your keyboar - 🎮 Auto-hides in fullscreen apps - 🖥️ Multi-monitor support - 😴 Idle/scheduled sleep mode -- ⚡ Lightweight (~20MB RAM) +- ⚡ Lightweight (~8MB RAM) ## Quick Start diff --git a/bongocat.conf.example b/bongocat.conf.example index f1984e19..55c47cbb 100644 --- a/bongocat.conf.example +++ b/bongocat.conf.example @@ -32,7 +32,7 @@ overlay_height=80 # Background: 0=transparent, 255=opaque overlay_opacity=0 -# Position: top, bottom +# Position: top, bottom (Attention!: Requires restart of bongocat) overlay_position=bottom # Layer: top (above windows), overlay (always visible) @@ -52,7 +52,7 @@ keyboard_device=/dev/input/event4 # Leave commented to auto-detect # Find monitor names with: wlr-randr or hyprctl monitors -# monitor=eDP-1 +# monitor=HDMI-A-1 # ┌─────────────────────────────────────────────────────────────────────────────┐ # │ ANIMATION │ diff --git a/include/core/bongocat.h b/include/core/bongocat.h index a0e533dd..7c463284 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -27,7 +27,7 @@ // VERSION // ============================================================================= -#define BONGOCAT_VERSION "1.3.1" +#define BONGOCAT_VERSION "1.3.2" // ============================================================================= // COMPILE-TIME CONSTANTS diff --git a/nix/default.nix b/nix/default.nix index c7306138..49435c36 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -8,7 +8,7 @@ }: stdenv.mkDerivation (finalAttrs: { pname = "wayland-bongocat"; - version = "1.3.1"; + version = "1.3.2"; src = ../.; # Build toolchain and dependencies diff --git a/scripts/find_input_devices.sh b/scripts/find_input_devices.sh index ffd140d3..30ea5a86 100755 --- a/scripts/find_input_devices.sh +++ b/scripts/find_input_devices.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash # ═══════════════════════════════════════════════════════════════════════════════ -# Bongo Cat - Input Device Discovery Tool v1.3.1 +# Bongo Cat - Input Device Discovery Tool v1.3.2 # Interactive keyboard detection by listening for actual key events # ═══════════════════════════════════════════════════════════════════════════════ set -euo pipefail -VERSION="1.3.1" +VERSION="1.3.2" SCRIPT_NAME="bongocat-find-devices" # Colors diff --git a/src/core/main.c b/src/core/main.c index 616095af..1c2ae47c 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -158,11 +158,13 @@ static void signal_handler(int sig) { switch (sig) { case SIGINT: case SIGTERM: + case SIGQUIT: // Handle Ctrl+\ for graceful shutdown + case SIGHUP: // Handle terminal hangup bongocat_log_info("Received signal %d, shutting down gracefully", sig); running = 0; break; case SIGCHLD: - // Handle child process termination + // Handle child process termination - reap zombies while (waitpid(-1, NULL, WNOHANG) > 0) ; break; @@ -212,6 +214,17 @@ static bongocat_error_t signal_setup_handlers(void) { return BONGOCAT_ERROR_THREAD; } + // Handle SIGQUIT (Ctrl+\) and SIGHUP (terminal hangup) + if (sigaction(SIGQUIT, &sa, NULL) == -1) { + bongocat_log_error("Failed to setup SIGQUIT handler: %s", strerror(errno)); + return BONGOCAT_ERROR_THREAD; + } + + if (sigaction(SIGHUP, &sa, NULL) == -1) { + bongocat_log_error("Failed to setup SIGHUP handler: %s", strerror(errno)); + return BONGOCAT_ERROR_THREAD; + } + // Ignore SIGPIPE signal(SIGPIPE, SIG_IGN); diff --git a/src/graphics/animation.c b/src/graphics/animation.c index 3d6ae28e..d4427dfd 100644 --- a/src/graphics/animation.c +++ b/src/graphics/animation.c @@ -203,44 +203,53 @@ static void drawing_get_interpolated_pixel(const unsigned char *src, int src_w, void blit_image_scaled(uint8_t *dest, int dest_w, int dest_h, unsigned char *src, int src_w, int src_h, int offset_x, int offset_y, int target_w, int target_h) { - bool use_antialiasing = current_config && current_config->enable_antialiasing; + // OPTIMIZATION: Hoist invariants outside loops + const bool use_aa = current_config && current_config->enable_antialiasing; + const bool mirror_x = current_config && current_config->mirror_x; + const bool mirror_y = current_config && current_config->mirror_y; + const bool is_downscaling = (target_w < src_w) || (target_h < src_h); + + // Pre-calculate scale factors (avoid division in loop) + const float scale_x = (float)src_w / target_w; + const float scale_y = (float)src_h / target_h; for (int y = 0; y < target_h; y++) { + const int dy = y + offset_y; + + // Skip entire row if out of bounds + if (dy < 0 || dy >= dest_h) + continue; + + // Pre-calculate row offset + const int row_offset = dy * dest_w * 4; + for (int x = 0; x < target_w; x++) { - int dx = x + offset_x; - int dy = y + offset_y; + const int dx = x + offset_x; - if (!drawing_is_pixel_in_bounds(dx, dy, dest_w, dest_h)) { + // Skip if out of horizontal bounds + if (dx < 0 || dx >= dest_w) continue; - } - int dest_idx = (dy * dest_w + dx) * 4; + const int dest_idx = row_offset + dx * 4; - if (use_antialiasing) { + if (use_aa) { uint8_t r, g, b, a; - // Use box filter for downscaling (shrinking) - produces smoother - // results Use bilinear interpolation for upscaling or same size - bool is_downscaling = (target_w < src_w) || (target_h < src_h); - if (is_downscaling) { // Box filter: average all source pixels that map to this dest pixel drawing_get_box_filtered_pixel(src, src_w, src_h, x, y, target_w, - target_h, current_config->mirror_x, - current_config->mirror_y, &r, &g, &b, - &a); + target_h, mirror_x, mirror_y, &r, &g, + &b, &a); } else { // Bilinear interpolation for upscaling - float fx = ((float)x * src_w) / target_w; - float fy = ((float)y * src_h) / target_h; + float fx = x * scale_x; + float fy = y * scale_y; - // Apply mirroring based on configuration - if (current_config->mirror_x) { + // Apply mirroring + if (mirror_x) fx = (src_w - 1) - fx; - } - if (current_config->mirror_y) { + if (mirror_y) fy = (src_h - 1) - fy; - } drawing_get_interpolated_pixel(src, src_w, src_h, fx, fy, &r, &g, &b, &a); @@ -253,15 +262,13 @@ void blit_image_scaled(uint8_t *dest, int dest_w, int dest_h, int sx = (x * src_w) / target_w; int sy = (y * src_h) / target_h; - // Apply mirroring based on configuration - if (current_config->mirror_x) { + // Apply mirroring + if (mirror_x) sx = (src_w - 1) - sx; - } - if (current_config->mirror_y) { + if (mirror_y) sy = (src_h - 1) - sy; - } - int src_idx = (sy * src_w + sx) * 4; + const int src_idx = (sy * src_w + sx) * 4; // Only draw non-transparent pixels if (src[src_idx + 3] > 128) { @@ -492,10 +499,38 @@ static void *anim_thread_main(void *arg __attribute__((unused))) { animation_running = true; bongocat_log_debug("Animation thread main loop started"); + // Track last drawn state to skip redundant redraws + int last_drawn_frame = -1; + bool force_redraw = true; // Force first draw + while (animation_running) { + int prev_frame = anim_index; anim_update_state(&state); - draw_bar(); - nanosleep(&frame_delay, NULL); + + // Check if frame actually changed + bool frame_changed = (anim_index != last_drawn_frame); + bool state_changed = (anim_index != prev_frame); + + // Only redraw if something changed + if (frame_changed || force_redraw) { + draw_bar(); + last_drawn_frame = anim_index; + force_redraw = false; + } + + // Reduce sleep when actively animating, increase when idle + if (state_changed) { + // Active animation - use configured frame rate + nanosleep(&frame_delay, NULL); + } else { + // Idle - can sleep a bit longer (reduce polling frequency) + // Clamp to prevent nanoseconds overflow (max 999999999) + long idle_ns = state.frame_time_ns * 2; + if (idle_ns > 999999999L) + idle_ns = 999999999L; + struct timespec idle_delay = {0, idle_ns}; + nanosleep(&idle_delay, NULL); + } } bongocat_log_debug("Animation thread main loop exited"); diff --git a/src/platform/input.c b/src/platform/input.c index 9c5cac96..06d87e87 100644 --- a/src/platform/input.c +++ b/src/platform/input.c @@ -155,6 +155,13 @@ static void capture_input_multiple(char **device_paths, int num_devices, break; } + // GHOST PROCESS PREVENTION: Check if parent is still alive + // If parent died and we're now orphaned to init (PID 1), exit gracefully + if (getppid() == 1) { + bongocat_log_info("Parent process died, child exiting to prevent ghost"); + break; + } + if (select_result == 0) { // Adaptive device checking - start at 5 seconds, increase to 30 if no new // devices found diff --git a/src/platform/wayland.c b/src/platform/wayland.c index 2b266c33..0cefeaef 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -48,18 +48,81 @@ static output_ref_t outputs[MAX_OUTPUTS]; static size_t output_count = 0; static struct zxdg_output_manager_v1 *xdg_output_manager = NULL; +// Output reconnection handling +static struct wl_registry *global_registry = NULL; +static uint32_t bound_output_name = 0; // Registry name of our bound output +static atomic_bool output_lost = false; // Set when our output disconnects +static bool using_named_output = + false; // True if user specified an output name + // ============================================================================= // ZXDG LISTENER IMPLEMENTATION // ============================================================================= +// Forward declarations for reconnection handling +static bongocat_error_t wayland_setup_surface(void); + static void handle_xdg_output_name(void *data, struct zxdg_output_v1 *xdg_output __attribute__((unused)), const char *name) { + // Defensive null check + if (!data || !name) { + return; + } + output_ref_t *oref = data; snprintf(oref->name_str, sizeof(oref->name_str), "%s", name); oref->name_received = true; bongocat_log_debug("xdg-output name received: %s", name); + + // Check if this is the output we're waiting for (reconnection case) + if (!atomic_load(&output_lost) || !current_config) { + return; + } + + bool should_reconnect = false; + + // Case 1: User specified an output name - match exactly + if (using_named_output && current_config->output_name) { + should_reconnect = (strcmp(name, current_config->output_name) == 0); + } + // Case 2: Using fallback (first output) - reconnect to any output + else if (!using_named_output) { + should_reconnect = true; + bongocat_log_debug("Using fallback output, accepting '%s'", name); + } + + if (should_reconnect) { + bongocat_log_info("Target output '%s' reconnected!", name); + + // Clean up old surface if it exists + if (layer_surface) { + zwlr_layer_surface_v1_destroy(layer_surface); + layer_surface = NULL; + } + if (surface) { + wl_surface_destroy(surface); + surface = NULL; + } + + // Set new output + output = oref->wl_output; + bound_output_name = oref->name; + atomic_store(&output_lost, false); + + // Recreate surface on new output + // Note: wayland_setup_surface already commits, triggering a configure + // event. The layer_surface_configure callback will ack and call draw_bar() + // to render. + if (wayland_setup_surface() == BONGOCAT_SUCCESS) { + // Wait for configure event to be processed + wl_display_roundtrip(display); + bongocat_log_info("Surface recreated, configure event processed"); + } else { + bongocat_log_error("Failed to recreate surface on reconnected output"); + } + } } static void handle_xdg_output_logical_position( @@ -343,20 +406,28 @@ void draw_bar(void) { return; } + // Critical null checks - prevent crash during buffer recreation + if (!current_config || !pixels) { + bongocat_log_debug("Config or pixels not ready, skipping draw"); + return; + } + // Skip fullscreen hiding when layer is LAYER_OVERLAY (always visible) - bool is_overlay_layer = - current_config && current_config->layer == LAYER_OVERLAY; + bool is_overlay_layer = current_config->layer == LAYER_OVERLAY; bool is_fullscreen = !is_overlay_layer && atomic_load(&fullscreen_detected); int effective_opacity = is_fullscreen ? 0 : current_config->overlay_opacity; - // Clear buffer with transparency - for (int i = 0; - i < current_config->screen_width * current_config->bar_height * 4; - i += 4) { - pixels[i] = 0; // B - pixels[i + 1] = 0; // G - pixels[i + 2] = 0; // R - pixels[i + 3] = effective_opacity; // A + // Clear buffer with transparency - OPTIMIZED + // Use memset for RGB (zeros) then set only alpha bytes + int buffer_size = + current_config->screen_width * current_config->bar_height * 4; + memset(pixels, 0, buffer_size); + + // Set alpha channel only (every 4th byte starting at offset 3) + if (effective_opacity > 0) { + for (int i = 3; i < buffer_size; i += 4) { + pixels[i] = effective_opacity; + } } // Draw cat if visible @@ -513,6 +584,21 @@ static void registry_global(void *data __attribute__((unused)), wl_registry_bind(reg, name, &wl_output_interface, 2); wl_output_add_listener(outputs[output_count].wl_output, &output_listener, NULL); + + // If we lost our output, get xdg_output to check if this is the one + // reconnecting + if (atomic_load(&output_lost) && xdg_output_manager) { + outputs[output_count].xdg_output = + zxdg_output_manager_v1_get_xdg_output( + xdg_output_manager, outputs[output_count].wl_output); + outputs[output_count].name_received = false; + zxdg_output_v1_add_listener(outputs[output_count].xdg_output, + &xdg_output_listener, + &outputs[output_count]); + bongocat_log_debug( + "New output appeared while output_lost, checking name..."); + } + output_count++; } } else if (strcmp(iface, zwlr_foreign_toplevel_manager_v1_interface.name) == @@ -532,7 +618,39 @@ static void registry_global(void *data __attribute__((unused)), static void registry_remove(void *data __attribute__((unused)), struct wl_registry *registry __attribute__((unused)), - uint32_t name __attribute__((unused))) {} + uint32_t name) { + // Check if the removed global is our bound output + if (name == bound_output_name && bound_output_name != 0) { + bongocat_log_warning("Bound output disconnected (registry name %u)", name); + atomic_store(&output_lost, true); + atomic_store(&configured, false); + + // Clean up the old output reference + output = NULL; + + // Remove from outputs array + for (size_t i = 0; i < output_count; ++i) { + if (outputs[i].name == name) { + if (outputs[i].xdg_output) { + zxdg_output_v1_destroy(outputs[i].xdg_output); + outputs[i].xdg_output = NULL; + } + if (outputs[i].wl_output) { + wl_output_destroy(outputs[i].wl_output); + outputs[i].wl_output = NULL; + } + // Shift remaining outputs + for (size_t j = i; j < output_count - 1; ++j) { + outputs[j] = outputs[j + 1]; + } + // Zero out the now-unused slot + memset(&outputs[output_count - 1], 0, sizeof(output_ref_t)); + output_count--; + break; + } + } + } +} static struct wl_registry_listener reg_listener = { .global = registry_global, .global_remove = registry_remove}; @@ -542,13 +660,13 @@ static struct wl_registry_listener reg_listener = { // ============================================================================= static bongocat_error_t wayland_setup_protocols(void) { - struct wl_registry *registry = wl_display_get_registry(display); - if (!registry) { + global_registry = wl_display_get_registry(display); + if (!global_registry) { bongocat_log_error("Failed to get Wayland registry"); return BONGOCAT_ERROR_WAYLAND; } - wl_registry_add_listener(registry, ®_listener, NULL); + wl_registry_add_listener(global_registry, ®_listener, NULL); wl_display_roundtrip(display); if (xdg_output_manager) { @@ -564,12 +682,19 @@ static bongocat_error_t wayland_setup_protocols(void) { } output = NULL; + bound_output_name = 0; + using_named_output = false; + if (current_config->output_name) { for (size_t i = 0; i < output_count; ++i) { if (outputs[i].name_received && strcmp(outputs[i].name_str, current_config->output_name) == 0) { output = outputs[i].wl_output; - bongocat_log_info("Matched output: %s", outputs[i].name_str); + bound_output_name = + outputs[i].name; // Store registry name for tracking + using_named_output = true; // User specified this output + bongocat_log_info("Matched output: %s (registry name %u)", + outputs[i].name_str, bound_output_name); break; } } @@ -584,12 +709,16 @@ static bongocat_error_t wayland_setup_protocols(void) { // Fallback if (!output && output_count > 0) { output = outputs[0].wl_output; - bongocat_log_warning("Falling back to first output"); + bound_output_name = outputs[0].name; + using_named_output = false; // Using fallback, not a named output + bongocat_log_warning("Falling back to first output (registry name %u)", + bound_output_name); } if (!compositor || !shm || !layer_shell) { bongocat_log_error("Missing required Wayland protocols"); - wl_registry_destroy(registry); + wl_registry_destroy(global_registry); + global_registry = NULL; return BONGOCAT_ERROR_WAYLAND; } @@ -610,10 +739,41 @@ static bongocat_error_t wayland_setup_protocols(void) { current_config->screen_width = DEFAULT_SCREEN_WIDTH; } - wl_registry_destroy(registry); + // Keep registry alive for output reconnection handling return BONGOCAT_SUCCESS; } +// Helper to handle output reconnection +static void wayland_handle_output_reconnect(struct wl_output *new_output, + uint32_t registry_name, + const char *output_name) { + bongocat_log_info("Output '%s' reconnected (registry name %u)", output_name, + registry_name); + + // Clean up old surface if it exists + if (layer_surface) { + zwlr_layer_surface_v1_destroy(layer_surface); + layer_surface = NULL; + } + if (surface) { + wl_surface_destroy(surface); + surface = NULL; + } + + // Set new output + output = new_output; + bound_output_name = registry_name; + atomic_store(&output_lost, false); + + // Recreate surface on new output + if (wayland_setup_surface() == BONGOCAT_SUCCESS) { + bongocat_log_info("Surface recreated on reconnected output"); + wl_display_roundtrip(display); + } else { + bongocat_log_error("Failed to recreate surface on reconnected output"); + } +} + static bongocat_error_t wayland_setup_surface(void) { surface = wl_compositor_create_surface(compositor); if (!surface) { @@ -789,7 +949,69 @@ void wayland_update_config(config_t *config) { return; } + // Lock animation mutex to prevent draw_bar() during config update + // This is critical - animation thread must not access buffer while we + // recreate it + pthread_mutex_lock(&anim_lock); + + // Check if dimensions changed - requires buffer/surface recreation + int old_height = current_config ? current_config->bar_height : 0; + int old_width = current_config ? current_config->screen_width : 0; + current_config = config; + + bool dimensions_changed = + (old_height != config->bar_height) || (old_width != config->screen_width); + + if (dimensions_changed && old_height > 0 && old_width > 0) { + bongocat_log_info( + "Dimensions changed (%dx%d -> %dx%d), recreating buffer...", old_width, + old_height, config->screen_width, config->bar_height); + + // Mark as not configured first + atomic_store(&configured, false); + + // Cleanup old buffer + if (buffer) { + wl_buffer_destroy(buffer); + buffer = NULL; + } + if (pixels) { + munmap(pixels, old_width * old_height * 4); + pixels = NULL; + } + + // Cleanup old surface + if (layer_surface) { + zwlr_layer_surface_v1_destroy(layer_surface); + layer_surface = NULL; + } + if (surface) { + wl_surface_destroy(surface); + surface = NULL; + } + + // Recreate surface and buffer with new dimensions + if (wayland_setup_surface() != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to recreate surface after config change"); + pthread_mutex_unlock(&anim_lock); + return; + } + if (wayland_setup_buffer() != BONGOCAT_SUCCESS) { + bongocat_log_error("Failed to recreate buffer after config change"); + pthread_mutex_unlock(&anim_lock); + return; + } + + // Wait for new configure event + wl_display_roundtrip(display); + + bongocat_log_info("Buffer recreated successfully (%dx%d)", + config->screen_width, config->bar_height); + } + + pthread_mutex_unlock(&anim_lock); + if (atomic_load(&configured)) { draw_bar(); } @@ -883,6 +1105,10 @@ void wayland_cleanup(void) { // Reset state atomic_store(&configured, false); atomic_store(&fullscreen_detected, false); + atomic_store(&output_lost, false); + bound_output_name = 0; + using_named_output = false; + global_registry = NULL; // Destroyed when display disconnects memset(&fs_detector, 0, sizeof(fs_detector)); memset(&screen_info, 0, sizeof(screen_info)); From 578c27b0575004ef0a0df3b9c5dbaec35f729fd4 Mon Sep 17 00:00:00 2001 From: furudbat Date: Sun, 7 Dec 2025 16:28:08 +0100 Subject: [PATCH 09/18] fix: find_input_devices restore all/old functions --- CONTRIBUTING.md | 73 +++++--- bongocat.conf | 12 +- scripts/find_input_devices.sh | 311 +++++++++++++++++++++++++++------- src/config/config.cpp | 6 +- 4 files changed, 307 insertions(+), 95 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7660f723..c163bc7a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,18 +5,6 @@ This guide will help you get started, whether you want to report issues, suggest --- -## Table of Contents - -1. [Reporting Issues](#reporting-issues) -2. [Feature Requests](#feature-requests) -3. [Development Setup](#development-setup) -4. [Code Standards](#code-standards) -5. [Submitting Pull Requests](#submitting-pull-requests) -6. [Style Guidelines](#style-guidelines) -7. [License](#license) - ---- - ## Reporting Issues If you encounter a bug or unexpected behavior: @@ -30,8 +18,6 @@ If you encounter a bug or unexpected behavior: > Please avoid sending private screenshots of proprietary content; only include relevant logs and minimal reproduction steps. ---- - ## Feature Requests Feature requests are welcome! To submit a request: @@ -45,13 +31,16 @@ Feature requests are welcome! To submit a request: --- -## Development Setup +## Getting Started ### Prerequisites -See [Building from Source](README.md#-building-from-source) for detailed instructions. +- Wayland compositor with layer shell support +- GCC or Clang (C++23/C23 support) +- wayland-client, wayland-protocols +- Make and CMake -Quick start: +### Building ```bash git clone https://github.com/furudbat/wayland-vpets.git @@ -62,9 +51,15 @@ cmake --build build > Legacy `make debug` is supported for old Bongo Cat workflows, or you can just use CMake. ---- +### Running -## Code Standards +```bash +./build/bongocat -c bongocat.conf -w +``` + +## Development Workflow + +### Code Standards Follow the project’s coding guidelines: @@ -76,6 +71,8 @@ Follow the project’s coding guidelines: * **Assets**: Embed large assets in separate TUs * **Global State**: Avoid globals, pass context structs +_Run `make format` before committing_ + ### Key practices * **Modern Compiler Features:** Requires C23/C++23 (`#embed`) @@ -105,23 +102,20 @@ Follow the project’s coding guidelines: * Prefer `create` functions with RVO instead of `init` with out-parameters * Stop all threads before releasing memory ---- - -## Submitting Pull Requests +### Making Changes 1. Fork the repository and create a branch for your feature or fix: - ```bash git checkout -b feature/my-new-feature ``` 2. Make your changes following the code standards. -3. Test your changes thoroughly, including multi-monitor and keyboard input scenarios. +3. Test your changes thoroughly, including multi-monitor and keyboard input scenarios (if you can). 4. Commit your changes with clear messages: ```bash git commit -m "feat: Add support for X feature" ``` -5. Push your branch and open a Pull Request against `main`. +5. Push your branch and open a Pull Request against `develop`. 6. Include a description of what your PR changes and any relevant screenshots or logs. --- @@ -130,9 +124,9 @@ Follow the project’s coding guidelines: * **Branch Naming**: `feature/xxx`, `bugfix/xxx`, `docs/xxx` * **Commit Messages**: Use present tense, concise, and descriptive -* **Formatting**: try to Follow existing formatting and indentation, -- @TODO: add `.clang-format` and `.clang-tidy` +* **Formatting**: try to Follow existing formatting and indentation * **Documentation**: Update relevant README sections if needed - * when adding new configuration, pls update [`bongocat.conf`](bongocat.conf) + * when adding new configuration, pls update [`bongocat.conf.example`](bongocat.conf.example) * when adding new program arguments update [`cli_show_help` in main](src/core/main.cpp) * update [man pages](docs/fragments/options.md) @@ -158,9 +152,32 @@ src/ └── utils/ # Error handling, memory ``` ---- + +## Testing + +```bash +# Run with debug logging +./build/bongocat -c bongocat.conf -w +# Build with Sanitizers (UBSAN,ASAN) enabled for checking for memory leaks +``` + +### Test Scripts + +```bash +./scripts/test_bongocat.sh +``` + +There are also some test scripts, they are just for running, reloading and changing config for integration tests, meaning it's just for triggering the `asserts`. +Also test on your own and trust your eyes when testing four your rice :) + +## Reporting Issues + When reporting bugs, please include: +- Your compositor (Sway, Hyprland, etc.) +- Config file contents +- Debug output (`enable_debug=1`) + ## License All contributions must comply with the project’s MIT License. By submitting code, you agree to license your contributions under MIT. diff --git a/bongocat.conf b/bongocat.conf index 709f69f7..5d8437a9 100644 --- a/bongocat.conf +++ b/bongocat.conf @@ -1,6 +1,10 @@ # Bongo Cat Configuration File # Edit these values to customize your bongo cat overlay +# Save this file to: ~/.config/bongocat/bongocat.conf +# Run with: bongocat --watch-config + + # NOTE: OVERLAY SETTINGS DOESN'T WORK WITH HOT RELOAD, NEEDS BONGOCAT RESTART # DRAWN LAYERS GETS GLITCHY SOMETIMES, BETTER TO RESTART WHEN THESE CHANGE # Overlay settings @@ -9,14 +13,14 @@ overlay_height=60 # overlay_position: Position of the overlay on screen # Options: "top" or "bottom" overlay_position=top -# layer: layer for surface of overlay (default: "overlay") -# Options: "overlay", "top", "bottom", "background" +# layer: layer for surface of overlay (default: "top") +# Options: "overlay", "top", "bottom", "background"; top (above windows), overlay (always visible) #overlay_layer=top # Transparency settings # overlay_opacity: Opacity of the overlay background (0-255) # 0 = fully transparent, 255 = fully opaque -overlay_opacity=60 +overlay_opacity=0 # Position settings (in pixels) # cat_x_offset: Horizontal offset from center position @@ -47,7 +51,7 @@ enable_antialiasing=1 # Size settings # cat_height: Height of the bongo cat in pixels # Width is automatically calculated to maintain aspect ratio -cat_height=60 +cat_height=80 # animation_name: Sprite name (CASE SENSITIVE) # Default Option: "bongocat" diff --git a/scripts/find_input_devices.sh b/scripts/find_input_devices.sh index 01e5eac6..b7c36b9b 100755 --- a/scripts/find_input_devices.sh +++ b/scripts/find_input_devices.sh @@ -36,15 +36,31 @@ header() { # Device Discovery # ───────────────────────────────────────────────────────────────────────────── +device_is_ignored() { + local name="$1" + shift + local ignore_devices=("$@") + for pattern in "${ignore_devices[@]}"; do + if [[ "$name" =~ $pattern ]]; then + return 0 # ignored + fi + done + return 1 # not ignored +} + # Get all event devices with kbd handler (potential keyboards) get_kbd_devices() { + local prefer_byid="$1" # "true" = prefer /dev/input/by-id symlinks + shift 1 + local ignore_devices=("$@") # now receives array properly + local devices=() if [[ ! -r /proc/bus/input/devices ]]; then return 1 fi - local name="" handlers="" + local name="" handlers="" capabilities="" while IFS= read -r line; do case "$line" in @@ -55,15 +71,32 @@ get_kbd_devices() { H:\ Handlers=*) handlers="${line#H: Handlers=}" ;; + B:\ EV=*) + capabilities="${line#B: EV=}" + ;; "") + if [ ${#ignore_devices[@]} -gt 0 ]; then + # Skip ignored devices + if device_is_ignored "$name" "${ignore_devices[@]}"; then + name="" handlers="" capabilities="" + continue + fi + fi if [[ "$handlers" =~ kbd ]] && [[ "$handlers" =~ event ]]; then local event event=$(echo "$handlers" | grep -o 'event[0-9]*' | head -1) - if [[ -n "$event" ]] && [[ -e "/dev/input/$event" ]]; then - devices+=("$event|$name") + local real_path="/dev/input/$event" + if [[ -n "$event" ]] && [[ -e "$real_path" ]]; then + local device_path="$real_path" + if [[ "$prefer_byid" == "true" ]]; then + local symlink + symlink=$(find -L /dev/input/by-id/ -samefile "$real_path" -print -quit 2>/dev/null) + [[ -n "$symlink" ]] && device_path="$symlink" + fi + devices+=("$event|$name|$handlers|$capabilities|$device_path") fi fi - name="" handlers="" + name="" handlers="" capabilities="" ;; esac done < /proc/bus/input/devices @@ -97,8 +130,9 @@ listen_device() { # Detect keyboards interactively interactive_detect() { local timeout="${1:-5}" + local prefer_byid="$2" # "true" = prefer /dev/input/by-id symlinks local devices - devices=$(get_kbd_devices) || { error "Cannot read device list"; return 1; } + devices=$(get_kbd_devices "${prefer_byid}") || { error "Cannot read device list"; return 1; } if [[ -z "$devices" ]]; then error "No input devices with kbd handler found" @@ -108,7 +142,7 @@ interactive_detect() { # Check permissions local has_permission=false - while IFS='|' read -r event name; do + while IFS='|' read -r event name handlers device_path; do if check_device "/dev/input/$event"; then has_permission=true break @@ -140,13 +174,13 @@ interactive_detect() { local pids=() local device_list=() - while IFS='|' read -r event name; do + while IFS='|' read -r event name handlers device_path; do if check_device "/dev/input/$event"; then local outfile="$tmpdir/$event" local pid pid=$(listen_device "$event" "$outfile" "$timeout") pids+=("$pid") - device_list+=("$event|$name|$outfile") + device_list+=("$event|$name|$outfile|$device_path") fi done <<< "$devices" @@ -169,13 +203,13 @@ interactive_detect() { local other_devices=() for entry in "${device_list[@]}"; do - IFS='|' read -r event name outfile <<< "$entry" + IFS='|' read -r event name outfile device_path <<< "$entry" if [[ -s "$outfile" ]]; then # Device received input - it's a keyboard! - detected_keyboards+=("$event|$name") + detected_keyboards+=("$event|$name|$device_path") else - other_devices+=("$event|$name") + other_devices+=("$event|$name|$device_path") fi done @@ -192,16 +226,16 @@ interactive_detect() { echo -e " ${GREEN}Detected keyboards:${NC}" for entry in "${detected_keyboards[@]}"; do - IFS='|' read -r event name <<< "$entry" + IFS='|' read -r event name device_path <<< "$entry" echo -e " ${GREEN}✓${NC} ${BOLD}$name${NC}" - echo -e " ${CYAN}/dev/input/$event${NC}" + echo -e " ${CYAN}$device_path${NC}" done if [[ ${#other_devices[@]} -gt 0 ]]; then echo echo -e " ${DIM}Other devices (no input detected):${NC}" for entry in "${other_devices[@]}"; do - IFS='|' read -r event name <<< "$entry" + IFS='|' read -r event name device_path <<< "$entry" echo -e " ${DIM}○ $name (/dev/input/$event)${NC}" done fi @@ -211,8 +245,8 @@ interactive_detect() { echo -e " ${BOLD}~/.config/bongocat/bongocat.conf:${NC}" echo for entry in "${detected_keyboards[@]}"; do - IFS='|' read -r event name <<< "$entry" - echo -e " ${CYAN}keyboard_device=/dev/input/$event${NC} ${BOLD}# $name${NC}" + IFS='|' read -r event name device_path <<< "$entry" + echo -e " ${CYAN}keyboard_device=$device_path${NC} ${BOLD}# $name${NC}" done echo @@ -239,10 +273,95 @@ is_likely_keyboard() { return 1 } +is_likely_mouse() { + local name="$1" + local name_lower + name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]') + + # obvious non-keyboards + [[ "$name_lower" =~ (mouse|touchpad|trackpad) ]] && return 0 + + # Include devices with "mouse" in name + [[ "$name_lower" =~ mouse ]] && return 0 + + return 1 +} + +generate_config() { + local devices=("$@") + + if [[ -z "$devices" ]]; then + return 1 + fi + + # Calculate max path length for alignment + local maxlen=0 + for entry in "${devices[@]}"; do + IFS='|' read -r event name device_path <<< "$entry" + [[ ${#device_path} -gt $maxlen ]] && maxlen=${#device_path} + done + + # Config suggestion + for entry in "${devices[@]}"; do + IFS='|' read -r event name device_path <<< "$entry" + #echo -e " ${CYAN}keyboard_device=$device_path${NC} ${BOLD}# $name${NC}" + printf "${CYAN}keyboard_device=%-${maxlen}s${NC} ${BOLD}# %s ${NC}\n" "$device_path" "$name" + done +} + +get_device_type() { + local device_name="$1" + local handlers="$2" + local capabilities="$3" + + # Convert to lowercase for matching + local name_lower=$(echo "$device_name" | tr '[:upper:]' '[:lower:]') + local handlers_lower=$(echo "$handlers" | tr '[:upper:]' '[:lower:]') + + # Check for keyboard indicators + # Look for "kbd" handler (most reliable), keyboard in name, or keyboard-like capabilities + if [[ "$name_lower" =~ mouse ]] || [[ "$handlers_lower" =~ mouse ]]; then + echo "MOUSE" + elif [[ "$name_lower" =~ keyboard ]] || [[ "$capabilities" =~ "120013" ]] || [[ "$capabilities" =~ "12001f" ]]; then + # Determine keyboard type + if [[ "$name_lower" =~ (bluetooth|wireless|bt) ]] || [[ "$handlers_lower" =~ bluetooth ]]; then + echo "KEYBOARD" + elif [[ "$name_lower" =~ (usb|external) ]]; then + echo "KEYBOARD" + else + # Check if it's likely a Bluetooth keyboard based on common brands + if [[ "$name_lower" =~ (keychron|logitech|corsair|razer|steelseries|apple|microsoft) ]] && [[ ! "$name_lower" =~ (mouse|trackpad|touchpad) ]]; then + echo "KEYBOARD" + else + echo "KEYBOARD" + fi + fi + elif [[ "$capabilities" =~ (110000|17|7) ]]; then + echo "MOUSE" + elif [[ "$name_lower" =~ (touchpad|trackpad|synaptics) ]]; then + echo "Touchpad" + elif [[ "$name_lower" =~ (touchscreen|touch) ]]; then + echo "Touchscreen" + elif [[ "$handlers_lower" =~ kbd ]]; then + if is_likely_keyboard "$name"; then + echo "KEYBOARD" + else + echo "other" + fi + else + echo "other" + fi +} quick_detect() { + local show_all="$1" + local prefer_byid="$2" # "true" = prefer /dev/input/by-id symlinks + local include_mouse_devices="$3" + shift 3 + local ignore_devices=("$@") # now receives array properly + local devices - devices=$(get_kbd_devices) || { error "Cannot read devices"; return 1; } + devices=$(get_kbd_devices "${prefer_byid}" "${ignore_devices[@]}") || { error "Cannot read devices"; return 1; } if [[ -z "$devices" ]]; then warn "No input devices found" @@ -255,16 +374,37 @@ quick_detect() { header "Detected Devices" local keyboards=() + local mouses=() + local others=() + local config_devices=() + local type="" - while IFS='|' read -r event name; do + while IFS='|' read -r event name handlers capabilities device_path; do local status="ok" check_device "/dev/input/$event" || status="denied" - - if is_likely_keyboard "$name"; then - echo -e " ${GREEN}✓${NC} ${GREEN}[KEYBOARD]${NC} ${BOLD}$name${NC}" - keyboards+=("$event|$name") + type=$(get_device_type "${name}" "${handlers}" "${capabilities}") + + if is_likely_mouse "$name"; then + if [[ "$include_mouse_devices" == "true" ]]; then + echo -e " ${GREEN}✓${NC} ${GREEN}[$type]${NC} ${BOLD}$name${NC}" + config_devices+=("$event|$name|$device_path") + else + echo -e " ${DIM}○ [$type] $name${NC}" + if [[ "$show_all" == "true" ]]; then + config_devices+=("$event|$name|$device_path") + fi + fi + mouses+=("$event|$name") + elif is_likely_keyboard "$name"; then + echo -e " ${GREEN}✓${NC} ${GREEN}[$type]${NC} ${BOLD}$name${NC}" + keyboards+=("$event|$name|$device_path") + config_devices+=("$event|$name|$device_path") else - echo -e " ${DIM}○ [other] $name${NC}" + echo -e " ${DIM}○ [$type] $name${NC}" + others+=("$event|$name") + if [[ "$show_all" == "true" ]]; then + config_devices+=("$event|$name|$device_path") + fi fi echo -e " ${CYAN}/dev/input/$event${NC}" done <<< "$devices" @@ -280,16 +420,66 @@ quick_detect() { header "Add to Config" echo -e " ${BOLD}~/.config/bongocat/bongocat.conf:${NC}" echo - for entry in "${keyboards[@]}"; do - IFS='|' read -r event name <<< "$entry" - echo -e " ${CYAN}keyboard_device=/dev/input/$event${NC} ${BOLD}# $name${NC}" - done + generate_config "${config_devices[@]}" echo echo -e " ${DIM}Not accurate? Use: $SCRIPT_NAME --interactive${NC}" echo } +quick_config() { + local show_all="$1" + local prefer_byid="$2" # "true" = prefer /dev/input/by-id symlinks + local include_mouse_devices="$3" + shift 3 + local ignore_devices=("$@") # now receives array properly + + local devices + devices=$(get_kbd_devices "${prefer_byid}" "${ignore_devices[@]}") || { error "Cannot read devices"; return 1; } + + if [[ -z "$devices" ]]; then + return 1 + fi + + local keyboards=() + local mouses=() + local others=() + local config_devices=() + + while IFS='|' read -r event name handlers capabilities device_path; do + local status="ok" + check_device "/dev/input/$event" || status="denied" + local type + type=$(get_device_type "${name}" "${handlers}" "${capabilities}") + + if is_likely_mouse "$name"; then + if [[ "$include_mouse_devices" == "true" ]]; then + config_devices+=("$event|$name|$device_path") + else + if [[ "$show_all" == "true" ]]; then + config_devices+=("$event|$name|$device_path") + fi + fi + mouses+=("$event|$name|$device_path") + elif is_likely_keyboard "$name"; then + keyboards+=("$event|$name|$device_path") + config_devices+=("$event|$name|$device_path") + else + others+=("$event|$name|$device_path") + if [[ "$show_all" == "true" ]]; then + config_devices+=("$event|$name|$device_path") + fi + fi + done <<< "$devices" + + if [[ ${#keyboards[@]} -eq 0 ]]; then + return 1 + fi + + # Config suggestion + generate_config "${config_devices[@]}" +} + # ───────────────────────────────────────────────────────────────────────────── # Main # ───────────────────────────────────────────────────────────────────────────── @@ -302,60 +492,61 @@ ${BOLD}USAGE${NC} $SCRIPT_NAME [OPTIONS] ${BOLD}OPTIONS${NC} - -i, --interactive Detect keyboards by listening for key presses (recommended) - -t, --timeout SEC Detection timeout in seconds (default: 5) - -g, --generate Output config lines only (for piping) - -a, --all Show all input devices (including mice, touchpads) - --by-id Show input devices as id (symlink, if available) - -e, --ignore-device Ignore device (multiple arguments) - -d, --devices-only Print Input devices only (when generating configuration) - -m, --include-mouse Include Mouse Device in config - -h, --help Show this help + --all Show all input devices (including mice, touchpads) + --by-id Show input devices as id (symlink, if available) + --ignore-device PATTERN Ignore device (multiple arguments) + --include-mouse Include Mouse Device in config + -i, --interactive Detect keyboards by listening for key presses (recommended) + -t, --timeout SEC Detection timeout in seconds (default: 5) + -g, --generate Output config lines only (for piping) + -h, --help Show this help ${BOLD}EXAMPLES${NC} - $SCRIPT_NAME # Quick detection (name-based) - $SCRIPT_NAME -i # Interactive detection (recommended) - $SCRIPT_NAME -i -t 10 # Interactive with 10 second timeout - $SCRIPT_NAME -g > bongocat.conf # Generate config file + $SCRIPT_NAME # Quick detection (name-based) + $SCRIPT_NAME -i # Interactive detection (recommended) + $SCRIPT_NAME -i -t 10 # Interactive with 10 second timeout + $SCRIPT_NAME --generate > bongocat.conf # Generate config file + + { cat bongocat.conf.example; "$SCRIPT_NAME" --generate --devices-only; } > bongocat.conf EOF } main() { local mode="quick" local timeout=5 - local generate=false + local show_all=false local prefer_byid=false + local include_mouse_devices=false local ignore_devices=() - local devices_only=false - local mouse_devices=false while [[ $# -gt 0 ]]; do case "$1" in + --all) show_all=true; shift ;; + --by-id) prefer_byid=true; shift ;; + --include-mouse) include_mouse_devices=true; shift ;; + --ignore-device) + if [[ -n "$2" ]]; then + ignore_devices+=("$2") + shift 2 + else + echo "Error: --ignore-device requires a value" >&2 + exit 1 + fi + ;; -i|--interactive) mode="interactive"; shift ;; -t|--timeout) timeout="$2"; shift 2 ;; - -g|--generate) generate=true; shift ;; - -g|--generate-config) generate=true; shift ;; - --by-id) prefer_byid=true; shift ;; - -d|--devices-only) devices_only=true; shift ;; - -m|--include-mouse) mouse_devices=true; shift ;; - -e|--ignore-device) - if [[ -n "$2" ]]; then - ignore_devices+=("$2") - shift 2 - else - echo "Error: --ignore-device requires a value" >&2 - exit 1 - fi - ;; + -g|--generate) mode="generate"; shift ;; + --generate-config) mode="generate"; shift ;; -h|--help) show_usage; exit 0 ;; *) error "Unknown option: $1"; show_usage; exit 1 ;; esac done case "$mode" in - interactive) interactive_detect "$timeout" ;; - quick) quick_detect ;; + interactive) interactive_detect "$timeout" "$prefer_byid" ;; + quick) quick_detect "$show_all" "$prefer_byid" "$include_mouse_devices" "${ignore_devices[@]}" ;; + generate) quick_config "$show_all" "$prefer_byid" "$include_mouse_devices" "${ignore_devices[@]}" ;; esac } -main "$@" +main "$@" \ No newline at end of file diff --git a/src/config/config.cpp b/src/config/config.cpp index f25cd33e..b3b03a3b 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -88,12 +88,12 @@ namespace bongocat::config { static inline constexpr int32_t DEFAULT_CAT_X_OFFSET = 100; static inline constexpr int32_t DEFAULT_CAT_Y_OFFSET = 10; static inline constexpr int32_t DEFAULT_CAT_HEIGHT = 40; - static inline constexpr int32_t DEFAULT_OVERLAY_HEIGHT = 50; + static inline constexpr int32_t DEFAULT_OVERLAY_HEIGHT = 80; static inline constexpr int32_t DEFAULT_IDLE_FRAME = 0; static inline constexpr platform::time_ms_t DEFAULT_KEYPRESS_DURATION_MS = 100; - static inline constexpr int32_t DEFAULT_OVERLAY_OPACITY = 60; + static inline constexpr int32_t DEFAULT_OVERLAY_OPACITY = 0; static inline constexpr int32_t DEFAULT_ANIMATION_INDEX = 0; - static inline constexpr layer_type_t DEFAULT_LAYER = layer_type_t::LAYER_OVERLAY; + static inline constexpr layer_type_t DEFAULT_LAYER = layer_type_t::LAYER_TOP; static inline constexpr overlay_position_t DEFAULT_OVERLAY_POSITION = overlay_position_t::POSITION_TOP; static inline constexpr int32_t DEFAULT_HAPPY_KPM = 0; static inline constexpr platform::time_sec_t DEFAULT_IDLE_SLEEP_TIMEOUT_SEC = 0; From cd943e67fd8c21a5c3da25c2715a0f574783e250 Mon Sep 17 00:00:00 2001 From: furudbat Date: Mon, 8 Dec 2025 22:18:36 +0100 Subject: [PATCH 10/18] Update README * update tests * bump version --- CMakeLists.txt | 4 +- CONTRIBUTING.md | 51 +- README.md | 737 +++++------------------- bongocat.conf.example | 4 +- docs/begin.base.bongocat.conf.md | 5 +- examples/custom-sprite-sheets/README.md | 5 +- examples/minimal.bongocat.conf | 4 +- scripts/test_bongocat.sh | 4 +- scripts/test_bongocat_10.sh | 405 +++++++++++++ scripts/test_bongocat_8.sh | 525 +++++------------ scripts/test_bongocat_9.sh | 32 + scripts/test_toggle.sh | 6 +- src/core/main.cpp | 41 +- src/platform/wayland_callbacks.cpp | 3 + 14 files changed, 815 insertions(+), 1011 deletions(-) create mode 100755 scripts/test_bongocat_10.sh create mode 100755 scripts/test_bongocat_9.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ef68e54..0e8fea0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,7 @@ endif() -project(bongocat LANGUAGES C CXX VERSION 3.5.0) +project(bongocat LANGUAGES C CXX VERSION 3.6.0) # Feature Flags include(CMakeDependentOption) @@ -384,7 +384,7 @@ endif() # Package set(CPACK_PACKAGE_NAME "bongocat") -set(CPACK_PACKAGE_VERSION "3.5.0") +set(CPACK_PACKAGE_VERSION "3.6.0") set(CPACK_PACKAGE_CONTACT "hircreacc@gmail.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A delightful Wayland overlay that displays an animated V-Pet reacting to your keyboard input! ") set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c163bc7a..3225778f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,11 @@ cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug cmake --build build ```` -> Legacy `make debug` is supported for old Bongo Cat workflows, or you can just use CMake. +> Legacy `make debug` is supported for old Bongo Cat dev workflows. + +`make debug` provides a quick debug build. +You can inspect the old workflow in the old [`Makefile`](Makefile.old). +_**Note:** The binary name in AUR is `wpets`, but during development and `make install` it is still `bongocat`._ ### Running @@ -73,6 +77,11 @@ Follow the project’s coding guidelines: _Run `make format` before committing_ +#### Moving to C++ + +This project is migrated to C++ while retaining a C-style foundation for performance, Wayland compatibility and mosty compatible with [upstream](https://github.com/saatvik333/wayland-bongocat). +The codebase remains largely C under the hood, using Linux + Wayland libraries, while gradually adopting modern C++ practices for safety and maintainability. + ### Key practices * **Modern Compiler Features:** Requires C23/C++23 (`#embed`) @@ -143,13 +152,31 @@ refactor: improve code structure ## Code Structure + +- `assets/` - Sprite sheets and media +- `src/` - Core application logic and platform-specific code +- `include/` - Headers +- `scripts/` - Utilities and codegen +- `lib/` - External libraries + ``` -src/ -├── core/ # Main application logic -├── config/ # Configuration parsing -├── graphics/ # Animation and rendering -├── platform/ # Wayland integration -└── utils/ # Error handling, memory +wayland-vpets/ +├── assets/ # sprite sheets and media resources +├── Dockerfiles/ # Container build definitions +├── examples/ # Example configurations +├── include/ # Header files (same structure as src/) +├── lib/ # External libraries (image loader) +├── nix/ # NixOS integration +├── protocols/ # Generated Wayland protocols +├── scripts/ # Codegen and utility scripts +└── src/ # Source code +├──── config/ # Configuration system implementation +├──── core/ # Core application logic (main) +├──── embedded_assets/ # Embedded assets +├──── graphics/ # Rendering and graphics implementation +├──── image_loader/ # Assets loading implementations +├──── platform/ # Platform-specific code (input and wayland) +└──── utils/ # General utilities ``` @@ -182,6 +209,16 @@ When reporting bugs, please include: All contributions must comply with the project’s MIT License. By submitting code, you agree to license your contributions under MIT. +
+Copyright + +This project is **free**, **non-commercial** and not associated with these entities. +Pokémon are owned by Nintendo, Creatures Inc. and GAME FREAK Inc. +Digimon and all related characters, and associated images are owned by Bandai Co., Ltd, Akiyoshi Hongo, and Toei Animation Co., Ltd. +Clippy and other MS Agents are owed by Microsoft. +See [COPYRIGHT](assets/COPYRIGHT.md) for more details. +
+ --- Thank you for helping make **Wayland Bongo Cat + V-Pets** a better, more delightful overlay! 💖 diff --git a/README.md b/README.md index 92980f46..69a8f00d 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,7 @@ [![Version](https://img.shields.io/badge/version-3.6.0-blue.svg)](https://github.com/furudbat/wayland-vpets/releases) [![Release Build](https://github.com/furudbat/wayland-vpets/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/furudbat/wayland-vpets/actions/workflows/release.yml) -A delightful Wayland overlay that displays an animated V-Pet reacting to your keyboard input! -Perfect for streamers, content creators, or anyone who wants to add some fun to their desktop. - -## 🖼️ Demo +A cute Wayland overlay that shows an animated pets reacting to your keyboard input. ![Bongocat - Demo](assets/demo.gif) _Classic Bongocat_ @@ -21,295 +18,156 @@ _Clippy_ ![Pokemon Charizard - Demo](assets/pokemon-demo.png) _Pokemon_ -## ✨ Features +## Features -- **🐈 More Pets** - More Sprite to choose from +- **🐈 More Pets** - Bongocat 😺 - - Digimon V-Pets 🦖 (v1.3.0) - - Clippy 📎 (v2.1.0) - - Pokemon 🐭 (v3.0.0) - - Misc 🐈‍⬛ (v3.2.2) -- **🎯 Real-time Animation** - Bongo cat reacts instantly to keyboard input -- **🔥 Hot-Reload Configuration** - Modify settings without restarting (v1.2.0) -- **🔄 Dynamic Device Detection** - Automatically detects Bluetooth/USB keyboards (v1.2.0) -- **⚡ Performance Optimized** - Adaptive monitoring and batch processing (v1.4.0) - - triggers rendering, only when needed (v2.0.0) -- **🖥️ Screen Detection** - Automatic screen detection for all sizes and orientations (v1.2.2) -- **🎮 Smart Fullscreen Detection** - Automatically hides during fullscreen applications (v1.2.3) -- **🖥️ Multi-Monitor Support** - Choose which monitor to display on in multi-monitor setups (v1.2.4) -- **😴 Sleep Mode** - Scheduled or idle-based sleep mode with custom timing (v1.2.5) -- **🎨 Customizable Appearance** - Fine-tune position, size, alignment, and opacity -- **💾 Lightweight** - Minimal resource usage (~10MB RAM, depends on the loaded sprites) - - Lazy loading - Load only used assets into RAM (v2.4.0) -- **🎛️ Multi-device Support** - Monitor multiple keyboards simultaneously -- **🏗️ Cross-platform** - Works on x86_64 and ARM64 -- **😄 Happy Frame** - Reach KPM (Keystroke per minute) to trigger the happy frame (Digimon) -- **🎲 Random Frame** - Randomize sprite frame at start up (Digimon) (v2.4.0) -- **🔲 CPU Stat** - React to CPU usage (Digimon) (v3.1.0) -- **↔️ Movement** - Movement on screen (Digimon) (v3.2.0) -- **🔢 Custom Sprite-Sheet** - Load your own Sprite Sheet at runtime (v3.3.0) - -## 🏁 Getting Started - -### 1. Install the App, Arch Linux (Recommended) + - Digimon V-Pets 🦖 + - Clippy 📎 + - Pokemon 🐭 + - Misc & custom sprite sheets 🐈‍⬛ +- 🎯 Real-time keyboard animation +- 🔥 Hot-reload configuration +- 🎮 Auto-hides in fullscreen apps +- 🖥️ Multi-monitor support +- 😴 Idle/scheduled sleep mode +- 😄 Happy animation when reach KPM (Keystroke per minute) +- 🎲 Randomize sprite frame at start up +- 🔲 React to CPU usage +- ↔️ Movement on screen +- ⚡ Lightweight (~6MB RAM) + + +## Quick Start + +### Install ```bash -# Install using yay +# Arch Linux yay -S wpets - -# Or using paru -paru -S wpets -``` - -### 2. Configure Permissions - -```bash -# Add user to input group for keyboard access -sudo usermod -a -G input $USER -# Log out and back in for changes to take effect ``` -### 3. Find Your Input Devices +#### Other distros - build from source ```bash -# Installed via AUR -wpets-find-devices -``` - -### 4. Configure Bongo Cat - -Create or edit `~/.config/bongocat.conf`: - -```ini -# Example minimal configuration -cat_x_offset=0 -cat_y_offset=0 -cat_align=center -cat_height=60 -overlay_height=80 -overlay_opacity=150 -overlay_position=top -layer=overlay -fps=60 -enable_antialiasing=1 -animation_name=bongocat -keypress_duration=200 - -# add devices found with find-devices -keyboard_device=/dev/input/event4 # Keyboard -``` - -Full configuration reference: see the [Configuration Section](#%EF%B8%8F-configuration) below. - -### 5. Run the Overlay - -```bash -wpets --watch-config --config ~/.config/bongocat.conf -``` - - - -## 🚀 Installation - -### Arch Linux (Recommended) - -```bash -# Install using yay -yay -S wpets - -# Using paru -paru -S wpets - -# Run immediately -wpets --watch-config - -# Custom config with hot-reload -wpets --config ~/.config/bongocat.conf --watch-config - - -# drop-in replacement for bongocat -wpets --config ~/.config/bongocat.conf --watch-config - -# only pokemon sprites -wpets-pkmn --config ~/.config/pkmn.bongocat.conf --watch-config - -# all sprites available (Recommended) -wpets-all --config ~/.config/bongocat.conf --watch-config -``` - -_`wpets` is the default **minimal** binary. **`wpets-all`** and `wpets-pkmn` are variants with specific sprite sets._ -_Just use `wpets-all` with all sprites included, try out all the pets._ - -#### From Source ⚠️ - -```bash -# Install dependencies -pacman -S gcc make cmake libinput wayland wayland-protocols - -# Clone repository git clone https://github.com/furudbat/wayland-vpets.git cd wayland-vpets - -# Build cmake -S . -B build -DCMAKE_BUILD_TYPE=Release cmake --build build - -# Install - ⚠️ - If you only want to test without replacing bongocat, run the binary directly from `./build/` instead of installing. -sudo cmake --install build -``` - -**⚠️ this will overwrite the original installation of [bongocat](https://github.com/saatvik333/wayland-bongocat) ⚠️** - - - -### Other Distributions - -
-Fedora - -```bash -# Install dependencies -sudo dnf install wayland-devel wayland-protocols-devel gcc make cmake - -# Build from source -git clone https://github.com/furudbat/wayland-vpets.git -cd wayland-vpets -cmake -B build -cmake --build build - -# Run -./build/bongocat ``` -
+_If you only want to test without replacing bongocat, run the binary directly from `./build/` instead of installing._ -
-NixOS +##### Install ⚠️ ```bash -# Quick start with flakes -nix run github:furudbat/wayland-vpets -- --watch-config - -# Install to user profile -nix profile install github:furudbat/wayland-vpets +sudo cmake --install build ``` +⚠️ **this can overwrite the original installation of bongocat** ⚠️ -📖 **For comprehensive NixOS setup, see [nix/NIXOS.md](nix/NIXOS.md)** -
- -## 🎮 Run -### 1. Setup Permissions (once) +### Setup Permissions ```bash -# Add your user to the input group sudo usermod -a -G input $USER -# Log out and back in for changes to take effect +# Log out and back in ``` -### 3. Run with Hot-Reload +### Find Your Keyboard ```bash -# AUR installation -wpets --watch-config - -# From source -./build/bongocat --watch-config +wpets-find-devices # or ./scripts/find_input_devices.sh ``` -## ⚙️ Configuration +### Run -Once installed, you can customize Bongo Cat Bar using a simple config file. +```bash +wpets-all --watch-config +``` -### Basic Configuration +## Configuration -Create or edit `bongocat.conf`: +Create `~/.config/bongocat/bongocat.conf`: ```ini -# Position settings -cat_x_offset=0 # Horizontal offset from center position -cat_y_offset=0 # Vertical offset from default position -cat_align=center # Horizontal alignment in the bar (left/center/right) +# Bongo Cat Configuration File +# Edit these values to customize your bongo cat overlay -# Size settings -cat_height=80 # Height of bongo cat (10-200) +# Save this file to: ~/.config/bongocat/bongocat.conf +# Run with: wpets-all --watch-config --config ~/.config/bongocat/bongocat.conf -# Visual settings -mirror_x=0 # Flip horizontally (mirror across Y axis) -mirror_y=0 # Flip vertically (mirror across X axis) - -# Anti-aliasing settings -enable_antialiasing=1 # Use bilinear interpolation for smooth scaling (0=off, 1=on) - -# Overlay settings (requires restart) -overlay_height=60 # Height of the entire overlay bar (20-300) -overlay_opacity=150 # Background opacity (0-255) -overlay_position=top # Position on screen (top/bottom) +# Position & Size +cat_height=80 +cat_align=center +# cat_x_offset=0 +# cat_y_offset=0 -# Animation settings -idle_frame=0 # Frame to show when idle (0-3) -fps=60 # Frame rate (1-120) -keypress_duration=100 # Animation duration (ms) +# Appearance +enable_antialiasing=1 +overlay_height=80 +overlay_opacity=0 +overlay_position=bottom +# mirror_x=0 +# mirror_y=0 -# Input devices (add multiple lines for multiple keyboards) +# Input device (run wpet-find-devices to find yours) keyboard_device=/dev/input/event4 -# keyboard_device=/dev/input/event20 # External/Bluetooth keyboard - -# Multi-monitor support -monitor=eDP-1 # Specify which monitor to display on (optional) -# Sleep mode settings -enable_scheduled_sleep=0 # Enable scheduled sleep mode (0=off, 1=on) -sleep_begin=20:00 # Begin of sleeping phase (HH:MM) -sleep_end=06:00 # End of sleeping phase (HH:MM) -idle_sleep_timeout=0 # Inactivity timeout before sleep (seconds, 0=off) +# Multi-monitor (optional - auto-detects by default) +# monitor=HDMI-A-1 -# Debug -enable_debug=0 # Show debug messages +# Sleep mode (optional) +# idle_sleep_timeout=300 +# enable_scheduled_sleep=0 +# sleep_begin=22:00 +# sleep_end=06:00 ``` -### Configuration Reference - -| Setting | Type | Range / Options | Default | Description | -|---------------------------|---------|------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------| -| `cat_height` | Integer | 10–200 | 40 | Height of bongo cat in pixels (width auto-calculated to maintain aspect ratio) | -| `cat_x_offset` | Integer | -16000 to 16000 | 100 | Horizontal offset from center (behavior depends on `cat_align`) | -| `cat_y_offset` | Integer | -16000 to 16000 | 10 | Vertical offset from center (positive=down, negative=up) | -| `cat_align` | String | "center", "left", "right" | "center" | Horizontal alignment of cat inside overlay bar | -| `overlay_height` | Integer | 20–300 | 50 | Height of the entire overlay bar | -| `overlay_position` | String | "top" or "bottom" | "top" | Position of overlay on screen | -| `overlay_opacity` | Integer | 0–255 | 60 | Background opacity (0=transparent, 255=opaque) | -| `overlay_layer` | String | "overlay", "top", "bottom" or "background" | "overlay" | Surface layer of overlay on screen | -| `animation_name` | String | "bongocat", ``, "clippy" or `` | "bongocat" | Name of the V-Pet sprite (see list below) | -| `invert_color` | Boolean | 0 or 1 | 0 | Invert color (useful for white digimon sprites & dark mode) | -| `idle_frame` | Integer | 0–2 (varies by sprite type) | 0 | Which frame to use when idle (sprite-specific options) | -| `idle_animation` | Boolean | 0 or 1 | 0 | Enable idle animation | -| `animation_speed` | Integer | 0–5000 | 0 | Frame duration in ms (0 = use `fps`) | -| `keypress_duration` | Integer | 50–5000 | 100 | Duration to display keypress animation (ms) | -| `mirror_x` | Boolean | 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | -| `mirror_y` | Boolean | 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | -| `test_animation_duration` | Integer | 0–5000 | 0 | Duration of test animation (ms) (deprecated, use `animation_speed`) | -| `test_animation_interval` | Integer | 0–60 | 0 | Interval for test animation in seconds (0 = disabled, deprecated) | -| `fps` | Integer | 1–144 | 60 | Animation frame rate | -| `input_fps` | Integer | 0–144 | 0 | Input thread frame rate (0 = use `fps`) | -| `enable_scheduled_sleep` | Boolean | 0 or 1 | 0 | Enable scheduled sleep mode | -| `sleep_begin` | String | "00:00" – "23:59" | "00:00" | Start time of scheduled sleep (24h format) | -| `sleep_end` | String | "00:00" – "23:59" | "00:00" | End time of scheduled sleep (24h format) | -| `idle_sleep_timeout` | Integer | 0+ | 0 | Time of inactivity before entering sleep (0 = disabled) (in seconds) | -| `happy_kpm` | Integer | 0–10000 | 0 | Minimum keystrokes per minute to trigger happy animation (0 = disabled) | -| `keyboard_device` | String | Valid `/dev/input/*` path(s) | \ | Input device path (multiple entries allowed) | -| `enable_antialiasing` | Boolean | 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling (Bongocat and MS Agent only) | -| `enable_debug` | Boolean | 0 or 1 | 0 | Enable debug logging | -| `monitor` | String | Monitor name | Auto-detect | Which monitor to display on (e.g., "eDP-1", "HDMI-A-1") | -| `random` | Boolean | 0 or 1 | 0 | Randomize `animation_index` (`animation_name` needs to be set as base sprite sheet set) | -| `random_on_reload` | Boolean | 0 or 1 | 0 | Randomize `animation_index` when reloading config (`random` needs to be `1`) | -| `update_rate` | Integer | 0–10000 | 0 | Check (CPU) states rate (0 = disabled) (in milliseconds) | -| `cpu_threshold` | Double | 0–100 | 0 | Threshold of CPU usage for triggering work animation (0 = disabled) | -| `movement_radius` | Integer | 0-8000 | 0 | Radius of moving area (0 = disabled) | -| `movement_speed` | Integer | 0–5000 | 0 | Movement speed (0 = disabled) | -| `enable_movement_debug` | Boolean | 0 or 1 | 0 | Show Movement area | -| `cpu_running_factor` | Double | 0.0–50.0 | 0 | Speed up factor for 100% CPU, it's linear so the animation slowly speed up (0 = disabled) | +### Options Reference + +
+Click to expand all options + +| Setting | Type | Range / Options | Default | Description | +|---------------------------|---------|--------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------| +| `cat_height` | Integer | 10–200 | 40 | Height of bongo cat in pixels (width auto-calculated to maintain aspect ratio) | +| `cat_x_offset` | Integer | -16000 to 16000 | 100 | Horizontal offset from center (behavior depends on `cat_align`) | +| `cat_y_offset` | Integer | -16000 to 16000 | 10 | Vertical offset from center (positive=down, negative=up) | +| `cat_align` | String | "center", "left", "right" | "center" | Horizontal alignment of cat inside overlay bar | +| `overlay_height` | Integer | 20–300 | 50 | Height of the entire overlay bar | +| `overlay_position` | String | "top" or "bottom" | "top" | Position of overlay on screen | +| `overlay_opacity` | Integer | 0–255 | 60 | Background opacity (0=transparent, 255=opaque) | +| `overlay_layer` | String | "overlay", "top", "bottom" or "background" | "overlay" | Surface layer of overlay on screen | +| `animation_name` | String | "bongocat", ``, "clippy", `` or "neko" | "bongocat" | Name of the V-Pet sprite (see list below) | +| `invert_color` | Boolean | 0 or 1 | 0 | Invert color (useful for white digimon sprites & dark mode) | +| `idle_frame` | Integer | 0–2 (varies by sprite type) | 0 | Which frame to use when idle (sprite-specific options) | +| `idle_animation` | Boolean | 0 or 1 | 0 | Enable idle animation | +| `animation_speed` | Integer | 0–5000 | 0 | Frame duration in ms (0 = use `fps`) | +| `keypress_duration` | Integer | 50–5000 | 100 | Duration to display keypress animation (ms) | +| `mirror_x` | Boolean | 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | +| `mirror_y` | Boolean | 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | +| `test_animation_duration` | Integer | 0–5000 | 0 | Duration of test animation (ms) (deprecated, use `animation_speed`) | +| `test_animation_interval` | Integer | 0–60 | 0 | Interval for test animation in seconds (0 = disabled, deprecated) | +| `fps` | Integer | 1–144 | 60 | Animation frame rate | +| `input_fps` | Integer | 0–144 | 0 | Input thread frame rate (0 = use `fps`) | +| `enable_scheduled_sleep` | Boolean | 0 or 1 | 0 | Enable scheduled sleep mode | +| `sleep_begin` | String | "00:00" – "23:59" | "00:00" | Start time of scheduled sleep (24h format) | +| `sleep_end` | String | "00:00" – "23:59" | "00:00" | End time of scheduled sleep (24h format) | +| `idle_sleep_timeout` | Integer | 0+ | 0 | Time of inactivity before entering sleep (0 = disabled) (in seconds) | +| `happy_kpm` | Integer | 0–10000 | 0 | Minimum keystrokes per minute to trigger happy animation (0 = disabled) | +| `keyboard_device` | String | Valid `/dev/input/*` path(s) | \ | Input device path (multiple entries allowed) | +| `enable_antialiasing` | Boolean | 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling (Bongocat and MS Agent only) | +| `enable_debug` | Boolean | 0 or 1 | 0 | Enable debug logging | +| `monitor` | String | Monitor name | Auto-detect | Which monitor to display on (e.g., "eDP-1", "HDMI-A-1") | +| `random` | Boolean | 0 or 1 | 0 | Randomize `animation_index` (`animation_name` needs to be set as base sprite sheet set) | +| `random_on_reload` | Boolean | 0 or 1 | 0 | Randomize `animation_index` when reloading config (`random` needs to be `1`) | +| `update_rate` | Integer | 0–10000 | 0 | Check (CPU) states rate (0 = disabled) (in milliseconds) | +| `cpu_threshold` | Double | 0–100 | 0 | Threshold of CPU usage for triggering work animation (0 = disabled) | +| `movement_radius` | Integer | 0-8000 | 0 | Radius of moving area (0 = disabled) | +| `movement_speed` | Integer | 0–5000 | 0 | Movement speed (0 = disabled) | +| `enable_movement_debug` | Boolean | 0 or 1 | 0 | Show Movement area | +| `cpu_running_factor` | Double | 0.0–50.0 | 0 | Speed up factor for 100% CPU, it's linear so the animation slowly speed up (0 = disabled) | #### Available Sprites (`animation_name`) @@ -375,12 +233,13 @@ _If you build with ALL assets included you can void naming conflicts by using th See [examples](examples/custom-sprite-sheets) for more details. -## 🔧 Usage +
+ -### Command Line Options +## Command Line ```bash -wpets [OPTIONS] +wpets-all [OPTIONS] Options: -h, --help Show this help message @@ -398,30 +257,38 @@ Options: ### Examples ```bash -# Basic usage +# Basic usage (bongocat) wpets -# With hot-reload (recommended) +# Run immediately (bongocat) wpets --watch-config -# Custom config with hot-reload -wpets --config ~/.config/bongocat.conf --watch-config +# Custom config with hot-reload (bongocat) +wpets --watch-config --config ~/.config/bongocat/bongocat.conf -# Debug mode -wpets --watch-config --config bongocat.conf -# Toggle mode -wpets --toggle +# drop-in replacement for bongocat (bongocat) +wpets --watch-config --config ~/.config/bongocat/bongocat.conf -# Custom config with hot-reload and custom output_name -wpets --watch-config --output-name DP-2 --config ~/.config/bongocat.conf +# pokemon sprites +wpets-pkmn --watch-config --config ~/.config/bongocat/pkmn.bongocat.conf +wpets-pkmn --watch-config --config ~/.config/bongocat/pmd.bongocat.conf + +# all sprites available (Recommended) +wpets-all --watch-config --config ~/.config/bongocat/bongocat.conf +wpets-all --watch-config --config ~/.config/bongocat/digimon.bongocat.conf +wpets-all --watch-config --config ~/.config/bongocat/clippy.bongocat.conf +wpets-all --watch-config --config ~/.config/bongocat/neko.bongocat.conf ``` -See [`examples/`](examples) for more configs. +_`wpets` is the default **minimal** binary. **`wpets-all`** and `wpets-pkmn` are variants with specific sprite sets._ +_Just use `wpets-all` with all sprites included, try out all the pets._ + +See [examples/](examples) for more configs. #### Hyprland -For [`hyprland`](https://hypr.land/) users, you can autostart `wpets` in your `hyprland.conf`: +For [hyprland](https://hypr.land/) users, you can autostart `wpets` in your `hyprland.conf`: ```ini # Auto start @@ -431,359 +298,45 @@ exec-once = wpets-all --watch-config --config ~/.config/bongocat/screen3.bongoca exec-once = wpets-all --watch-config --config ~/.config/bongocat/screen4.bongocat.conf --random ``` -## 🛠️ Building from Source - -### Prerequisites - -Before building, ensure your system has the required tools and libraries: - -#### Required: - -- Wayland compositor with layer shell support -- C23/C++23 compiler (GCC 15+ or Clang 19+) -- CMake and make -- `libwayland-client` -- `wayland-protocols` -- `wayland-scanner` -- `libudev` - -##### Arch Linux / Manjaro: - -```bash -sudo pacman -S git gcc g++ clang cmake base-devel libinput wayland wayland-protocols systemd-libs` -``` - -##### Fedora: - -```bash -sudo dnf install -y @c-development git-core cmake glibc-langpack-en 'pkgconfig(libevdev)' 'pkgconfig(libinput)' 'pkgconfig(libudev)' 'pkgconfig(wayland-client)' 'pkgconfig(wayland-protocols)' -``` - -##### Debian / Ubuntu: - -```bash -sudo apt-get install build-essential cmake libinput-dev libudev-dev libwayland-dev wayland-protocols -``` - -### Build Instructions - -```bash -# Clone repository -git clone https://github.com/furudbat/wayland-vpets.git -cd wayland-vpets - -# Build (Release or Debug) -cmake -S . -B build -DCMAKE_BUILD_TYPE=Release # Or Debug -cmake --build build - -# Clean -cmake --build build --target clean -``` - -The build process automatically: - -1. Generates Wayland protocol files -2. Compiles with optimizations and security hardening -3. Embeds assets directly in the binary -4. Links with required libraries - - - -## 🔍 Device Discovery - -The `wpets-find-devices` tool provides professional input device analysis with a clean, user-friendly interface: - -```bash -$ wpets-find-devices - -╔══════════════════════════════════════════════════════════════════╗ -║ Wayland Bongo Cat - Input Device Discovery v3.1.0 ║ -╚══════════════════════════════════════════════════════════════════╝ - -[SCAN] Scanning for input devices... - -[DEVICES] Found Input Devices: -┌─────────────────────────────────────────────────────────────────┐ -│ Device: AT Translated Set 2 keyboard │ -│ Path: /dev/input/event4 │ -│ Type: Keyboard │ -│ Status: [OK] Accessible │ -└─────────────────────────────────────────────────────────────────┘ - -┌─────────────────────────────────────────────────────────────────┐ -│ Device: Logitech MX Keys │ -│ Path: /dev/input/event20 │ -│ Type: Keyboard (Bluetooth) │ -│ Status: [OK] Accessible │ -└─────────────────────────────────────────────────────────────────┘ - -[CONFIG] Configuration Suggestions: -Add these lines to your bongocat.conf: - -keyboard_device=/dev/input/event4 # AT Translated Set 2 keyboard -keyboard_device=/dev/input/event20 # Logitech MX Keys -``` - -### Advanced Features - -```bash -# Show all input devices (including mice, touchpads) -wpets-find-devices --all - -# Generate complete configuration file -wpets-find-devices --generate-config > bongocat.conf - -# Generate complete configuration file with named devices by-id -wpets-find-devices --generate-config --by-id > bongocat.conf - -# Test device responsiveness (requires root) -sudo wpets-find-devices --test - -# Show detailed device information -wpets-find-devices --verbose - -# Get help and usage information -wpets-find-devices --help -``` - -### Key Features - -- **Smart Detection** - Automatically identifies keyboards vs other input devices -- **Device Classification** - Distinguishes between built-in, Bluetooth, and USB keyboards -- **Permission Checking** - Verifies device accessibility and provides fix suggestions -- **Config Generation** - Creates ready-to-use configuration snippets -- **Device Testing** - Integrated evtest functionality for troubleshooting -- **Professional UI** - Clean, colorized output with status indicators -- **Error Handling** - Comprehensive error messages and troubleshooting guidance - -## 📊 Performance - -### System Requirements - -This program is lightweight and runs even on very modern desktop systems. -Minimal builds require only a few MB of RAM, whereas asset-heavy builds use more. - -## 🖥️ System Requirements - -| | Minimum | Recommended | -|----------------|----------------------------------------------------------|------------------------------------------------------------------------------| -| **CPU** | Any modern **x86_64** or **ARM64** processor (SSE2/NEON) | Dual-core **x86_64** or **ARM64** processor | -| **RAM** | **8 MB free** (minimal build with minimal assets) | **64 MB free** (full builds with all assets, preloaded, and config overhead) | -| **Storage** | **6 MB free** (binary + config files) | **20 MB free** (multiple binaries/builds + config files) | -| **Compositor** | Wayland with **wlr-layer-shell** protocol support | Modern Wayland compositor (Sway, Hyprland, Wayfire, KDE Plasma 6) | - - -### Tested Compositors - -- ✅ **Hyprland** - Full support -- ✅ **Sway** - Full support -- ✅ **Wayfire** - Compatible -- ✅ **KDE Wayland** - Compatible -- ❌ **GNOME Wayland** - Unsupported - -## 🐛 Troubleshooting - -Enable debug logging for detailed output: -```bash -# ensure enable_debug=1 in bongocat.conf -wpets --watch-config --config bongocat.conf -``` - -### Common Issues +## Troubleshooting
-Permission denied accessing `/dev/input/eventX` - -**Solution:** +Permission denied on input device ```bash -# Add user to input group (recommended) sudo usermod -a -G input $USER -# Log out and back in - -# Or create udev rule -echo 'KERNEL=="event*", GROUP="input", MODE="0664"' | sudo tee /etc/udev/rules.d/99-input.rules -sudo udevadm control --reload-rules -``` - -
- -
-Keyboard input not detected - -**Diagnosis:** - -```bash -# Find correct device -wpets-find-devices - -# Test device manually -sudo evtest /dev/input/event4 +# Then log out and back in ``` -**Solution:** Update `keyboard_device` in `bongocat.conf` with correct path. -
-Overlay not visible or clickable - -**Check:** +Cat not responding to keyboard -- Ensure compositor supports `wlr-layer-shell-unstable-v1` -- Verify `WAYLAND_DISPLAY` environment variable is set -- Try different `overlay_opacity` values - -**Tested compositors:** Hyprland, Sway, Wayfire +1. Run `wpets-find-devices` to find correct device +2. Update `keyboard_device` in config +3. Restart bongocat
-Multi-monitor setup issues - -**Finding monitor names:** - -```bash -# Using wlr-randr (recommended) -wlr-randr - -# Using swaymsg (Sway only) -swaymsg -t get_outputs +Not showing on correct monitor -# Check bongocat logs for detected monitors -wpets --watch-config # Look for "xdg-output name received" messages -``` - -**Configuration:** +Add `monitor=YOUR_MONITOR` to config. Find monitor names with `wlr-randr` or `hyprctl monitors`. -```ini -# Specify exact monitor name -monitor=eDP-1 # Laptop screen -monitor=HDMI-A-1 # External HDMI monitor -monitor=DP-1 # DisplayPort monitor -``` - -**Troubleshooting:** - -- If monitor name is wrong, bongocat falls back to first available monitor -- Monitor names are case-sensitive -- Remove or comment out `monitor=` line to use auto-detection
-
-Build errors - -**Common fixes:** - -- Install development packages: `libwayland-dev wayland-protocols` -- Ensure C23/C++23 compiler: GCC 15+ or Clang 19+ _(requires [`#embed`](https://en.cppreference.com/w/c/preprocessor/embed.html) feature)_ -- Install `wayland-scanner` package -
- -### Getting Help - -1. Enable debug logging: `wpets --watch-config` (ensure `enable_debug=1`) -2. Check compositor compatibility -3. Verify all dependencies are installed -4. Test with minimal configuration - -## 🏗️ Architecture - -### Project Structure - -- `assets/` - Sprite sheets and media -- `src/` - Core application logic and platform-specific code -- `include/` - Headers -- `scripts/` - Utilities and codegen -- `lib/` - External libraries - -``` -wayland-vpets/ -├── assets/ # sprite sheets and media resources -├── Dockerfiles/ # Container build definitions -├── examples/ # Example configurations -├── include/ # Header files (same structure as src/) -├── lib/ # External libraries (image loader) -├── nix/ # NixOS integration -├── protocols/ # Generated Wayland protocols -├── scripts/ # Codegen and utility scripts -└── src/ # Source code -├──── config/ # Configuration system implementation -├──── core/ # Core application logic (main) -├──── embedded_assets/ # Embedded assets -├──── graphics/ # Rendering and graphics implementation -├──── image_loader/ # Assets loading implementations -├──── platform/ # Platform-specific code (input and wayland) -└──── utils/ # General utilities -``` - -## 🤝 Contributing - -This project follows industry best practices with a modular architecture. -Issues and pull requests are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for details. - - -### Development Setup - -#### Build with CMake (Recommended) - -```bash -cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -cmake --build build -``` - -#### Legacy `make` (for old `bongocat` users) +## Building ```bash git clone https://github.com/furudbat/wayland-vpets.git cd wayland-vpets -make debug +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build ``` -`make debug` provides a quick debug build. -You can inspect the old workflow in the old [`Makefile`](Makefile.old). -_**Note:** The binary name in AUR is `wpets`, but during development and `make install` it is still `bongocat`._ - -### Code Standards - -- C23/C++23 standard compliance -- Comprehensive error handling -- Memory safety with leak detection -- Extensive documentation - -#### Moving to C++ - -The project is gradually migrating to C++ while retaining a C-style foundation for performance and Wayland compatibility. - -For more details, see the [Code Standards section in CONTRIBUTING](CONTRIBUTING.md#code-standards). - -The project remains largely C under the hood, using Linux + Wayland libraries, while gradually adopting modern C++ practices for safety and maintainability. - -## 📄 License - -MIT License - see [LICENSE](LICENSE) file for details. - -## 🙏 Acknowledgments - -Built with ❤️ for the Wayland community. Special thanks to: - -- Redditor: [u/akonzu](https://www.reddit.com/user/akonzu/) for the inspiration -- [@Shreyabardia](https://github.com/Shreyabardia) for the beautiful custom-drawn bongo cat artwork -- All the contributors and users -- [Waybar](https://github.com/Alexays/Waybar) - - -
-Copyright - -This project is **free**, **non-commercial** and not associated with these entities. -Pokémon are owned by Nintendo, Creatures Inc. and GAME FREAK Inc. -Digimon and all related characters, and associated images are owned by Bandai Co., Ltd, Akiyoshi Hongo, and Toei Animation Co., Ltd. -Clippy and other MS Agents are owed by Microsoft. -See [COPYRIGHT](assets/COPYRIGHT.md) for more details. -
+**Requirements:** wayland-client, wayland-protocols, gcc/clang, make, cmake ---- +## License -**₍^. .^₎ Wayland Bongo Cat Overlay v3.5.0** - Making desktops more delightful, one keystroke at a time! -Now with Digimon V-Pets, Clippy and Pokémon. +MIT License - see [LICENSE](LICENSE) \ No newline at end of file diff --git a/bongocat.conf.example b/bongocat.conf.example index db64180f..f5ebb4dd 100644 --- a/bongocat.conf.example +++ b/bongocat.conf.example @@ -2,7 +2,7 @@ # Edit these values to customize your bongo cat overlay # Save this file to: ~/.config/bongocat/bongocat.conf -# Run with: bongocat --watch-config +# Run with: wpets-all --watch-config # NOTE: OVERLAY SETTINGS DOESN'T WORK WITH HOT RELOAD, NEEDS BONGOCAT RESTART @@ -102,7 +102,7 @@ idle_frame=0 # Requires both sleep_begin and sleep_end to be defined enable_scheduled_sleep=0 # Start time for scheduled sleep mode (24-hour format: hh:mm) -sleep_begin=21:00 +sleep_begin=22:00 # End time for scheduled sleep mode (24-hour format: hh:mm) sleep_end=06:00 diff --git a/docs/begin.base.bongocat.conf.md b/docs/begin.base.bongocat.conf.md index 2fbcc016..c9fc6c78 100644 --- a/docs/begin.base.bongocat.conf.md +++ b/docs/begin.base.bongocat.conf.md @@ -1,6 +1,6 @@ % BONGOCAT.CONF(5) % -% September 2025 +% December 2025 # NAME @@ -34,6 +34,9 @@ Changes to some settings require restarting Bongo Cat to take effect. - **overlay_position**: Screen position of overlay. Options: `top`, `bottom`. - **overlay_layer**: Layer of overlay. Options: `overlay`, `top`, `bottom` or `background`. +_Some overlay settings require a restart of the application_ +_**overlay_height** should work on config reload, it may take a second to reappear_ + # FRAME RATE - **fps**: Animation frames per second. diff --git a/examples/custom-sprite-sheets/README.md b/examples/custom-sprite-sheets/README.md index b31a6008..cd2315d9 100644 --- a/examples/custom-sprite-sheets/README.md +++ b/examples/custom-sprite-sheets/README.md @@ -3,6 +3,8 @@ Custom sprite sheets has a full animation per row. To determine the number of rows, the number of frames needs to be provided per row. +_NOTE: Bigger sprite sheet means more RAM usage_ + - `Idle` -- Idle Pose - `Boring` -- Boring animation for inactivity - `StartWriting` -- First keystroke @@ -24,7 +26,6 @@ To determine the number of rows, the number of frames needs to be provided per r Rows can be skipped by not providing the frames/columns, but the order of the rows needs to be the same. - ## General Example **Sprite Sheet** @@ -178,7 +179,7 @@ custom_rows=1 ## Know issues -### extra sprite when sprite is flipping (moving) +### part of other frame is shown when sprite is flipped (while moving) Please add some left and right empty padding in your frames. Doing to some rounding error, when flipping the frame, some pixels can be visible from the nearer frames. \ No newline at end of file diff --git a/examples/minimal.bongocat.conf b/examples/minimal.bongocat.conf index e53d7752..de6b2c30 100644 --- a/examples/minimal.bongocat.conf +++ b/examples/minimal.bongocat.conf @@ -4,9 +4,9 @@ cat_y_offset=0 cat_align=center cat_height=60 overlay_height=80 -overlay_opacity=150 +overlay_opacity=0 overlay_position=top -layer=overlay +layer=top fps=60 enable_antialiasing=1 animation_name=bongocat diff --git a/scripts/test_bongocat.sh b/scripts/test_bongocat.sh index ceb7026f..88571a3f 100755 --- a/scripts/test_bongocat.sh +++ b/scripts/test_bongocat.sh @@ -35,7 +35,7 @@ cleanup() { } trap cleanup EXIT - echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG (pid=${PID})" +echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG (pid=${PID})" echo "[TEST] Sending SIGUSR2..." echo "[INFO] Send SIGUSR2" @@ -385,7 +385,7 @@ for i in {1..5}; do sleep 1 done echo "[TEST] Start with stdin default config..." -cat bongocat.conf | "$PROGRAM" --ignore-running --config - & +cat bongocat.conf.example | "$PROGRAM" --ignore-running --config - & PID=$! sleep 10 # --- verify running --- diff --git a/scripts/test_bongocat_10.sh b/scripts/test_bongocat_10.sh new file mode 100755 index 00000000..11292c51 --- /dev/null +++ b/scripts/test_bongocat_10.sh @@ -0,0 +1,405 @@ +#!/usr/bin/env bash + +set -euo pipefail + +for group in relwithdebinfo-tsan debug-all-assets-preload debug relwithdebinfo; do + find ./cmake-build-* -type f -executable -name "bongocat*" | grep -i "$group" | while read -r PROGRAM; do + WORKDIR=$(mktemp -d) + CONFIG="$WORKDIR/test.bongocat.conf" # config file to modify + OG_CONFIG=./examples/test.bongocat.conf + cp $OG_CONFIG $CONFIG + + echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG ..." + echo "[TEST] Starting program..." + "$PROGRAM" --config "$CONFIG" --ignore-running & + PID=$! + echo "[TEST] Program PID = $PID" + sleep 5 + sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" + sleep 5 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + + echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG (pid=${PID})" + + # --- trap cleanup --- + cleanup() { + echo "[TEST] Cleaning up..." + kill -9 "$PID" 2>/dev/null || true + cp $OG_CONFIG $CONFIG + rm -rf "$WORKDIR" + } + trap cleanup EXIT + + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 5 + echo "[INFO] Spam SIGUSR2" + kill -USR2 "$PID" + kill -USR2 "$PID" + kill -USR2 "$PID" + kill -USR2 "$PID" + sleep 7 + + # --- function to toggle idle_sleep_timeout --- + toggle_config() { + if grep -q '^idle_sleep_timeout=10' "$CONFIG"; then + new=3600 + else + new=10 + fi + sed -i -E "s/^idle_sleep_timeout=[0-9]+/idle_sleep_timeout=$new/" "$CONFIG" + echo "[TEST] Setting idle_sleep_timeout=$new" + } + + # --- modify config to trigger hot reload --- + sed -i -E 's/^cpu_threshold=[0-9]+/cpu_threshold=0/' "$CONFIG" + sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" + sleep 3 + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + toggle_config + sleep 10 + toggle_config + sleep 10 + echo "[TEST] Trigger Sleep" + echo "[INFO] Enable idle_sleep_timeout..." + sed -i -E "s/^idle_sleep_timeout=[0-9]+/idle_sleep_timeout=10/" "$CONFIG" + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + sleep 5 + sed -i 's/^enable_scheduled_sleep=0/enable_scheduled_sleep=1/' "$CONFIG" + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + sleep 20 + echo "[TEST] Wake up Sleep" + if [[ -f "/proc/$PID/fd/0" ]]; then + printf '\e' > /proc/$PID/fd/0 + sleep 5 + fi + echo "[INFO] Disable idle_sleep_timeout..." + sed -i -E "s/^idle_sleep_timeout=[0-9]+/idle_sleep_timeout=3600/" "$CONFIG" + sleep 5 + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" + sleep 5 + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + echo "[TEST] Change animation sprite" + echo "[INFO] Set animation_name..." + sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" + sleep 5 + echo "[INFO] Set animation_name..." + sed -i -E 's/^animation_name=.*/animation_name=greymon/' "$CONFIG" + sleep 3 + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 5 + + echo "[TEST] Invalid animation sprite" + echo "[INFO] Set animation_name..." + sed -i -E 's/^animation_name=.*/animation_name=NoNo/' "$CONFIG" + sleep 5 + echo "[INFO] Set animation_name..." + sed -i -E 's/^animation_name=.*/animation_name=greymon/' "$CONFIG" + sleep 5 + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + + echo "[TEST] move and delete config..." + echo "[INFO] Move Config: $CONFIG > ${CONFIG}.del" + mv $CONFIG "${CONFIG}.del" + sleep 5 + rm "${CONFIG}.del" + sleep 5 + echo "[INFO] Recreate Config: $CONFIG" + cp ./examples/digimon.bongocat.conf $CONFIG + sleep 5 + echo "[INFO] Delete Config: $CONFIG" + rm $CONFIG + sleep 5 + echo "[INFO] Recreate Config: $CONFIG" + cp ./examples/digimon.bongocat.conf $CONFIG + sleep 3 + + echo "[INFO] Disable sleep" + sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" + sleep 10 + if [[ -f "/proc/$PID/fd/0" ]]; then + # --- simulate pressing ESC --- + echo "[TEST] Sending ESC key..." + echo "[INFO] Send stdin" + printf '\e' > /proc/$PID/fd/0 + sleep 5 + echo "[INFO] Send stdin" + printf '\e' > /proc/$PID/fd/0 + sleep 1 + # a bit of a spam + echo "[INFO] Spam stdin" + printf '\e' > /proc/$PID/fd/0 + printf '\e' > /proc/$PID/fd/0 + printf '\e' > /proc/$PID/fd/0 + printf '\e' > /proc/$PID/fd/0 + sleep 5 + echo "[INFO] Spam stdin slower" + printf '\e' > /proc/$PID/fd/0 + sleep 1 + printf '\e' > /proc/$PID/fd/0 + sleep 1 + printf '\e' > /proc/$PID/fd/0 + sleep 1 + printf '\e' > /proc/$PID/fd/0 + sleep 3 + fi + + echo "[INFO] Disable sleep" + sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" + sleep 5 + echo "[TEST] Sending SIGUSR2..." + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 3 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" + sleep 5 + echo "[INFO] Spam SIGUSR2" + kill -USR2 "$PID" + kill -USR2 "$PID" + kill -USR2 "$PID" + kill -USR2 "$PID" + sleep 10 + echo "[INFO] Spam SIGUSR2 slower" + kill -USR2 "$PID" + sleep 5 + kill -USR2 "$PID" + sleep 3 + kill -USR2 "$PID" + sleep 2 + kill -USR2 "$PID" + sleep 15 + + echo "[TEST] Sending SIGUSR1..." + echo "[INFO] Send SIGUSR1" + kill -USR1 "$PID" + sleep 2 + echo "[INFO] Send SIGUSR1" + kill -USR1 "$PID" + sleep 2 + + echo "[TEST] replace config..." + echo "[INFO] Replace Config: $CONFIG > ${CONFIG}.del" + cp ./examples/dmc.bongocat.conf $CONFIG + sleep 5 + echo "[TEST] Sending ESC key..." + if [[ -f "/proc/$PID/fd/0" ]]; then + echo "[INFO] Send stdin" + printf '\e' > /proc/$PID/fd/0 + sleep 2 + printf '\e' > /proc/$PID/fd/0 + sleep 2 + printf '\e' > /proc/$PID/fd/0 + sleep 5 + fi + echo "[INFO] Restore old config" + cp $OG_CONFIG $CONFIG + sleep 5 + + echo "[TEST] Fully replace config (Digimon -> Clippy): $CONFIG" + cp ./examples/clippy.bongocat.conf $CONFIG + sleep 5 + + + # --- send SIGTERM --- + echo "[INFO] Sending SIGTERM..." + kill -TERM "$PID" + sleep 15 + echo "[INFO] Wait for TERM" + # wait up to 5 seconds + for i in {1..5}; do + if ! kill -0 "$PID" 2>/dev/null; then + break + fi + sleep 1 + done + echo "[TEST] Re-start..." + "$PROGRAM" --ignore-running --strict --config "$CONFIG" & + PID=$! + sleep 10 + echo "[TEST] Load biggest assets" + echo "[INFO] Set Sprite Sheet: Links" + sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=Links/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 5 + echo "[INFO] Set Sprite Sheet: pkmn:dialga" + sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=pkmn:dialga/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: dmx:Hexeblaumon" + sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=dmx:Hexeblaumon/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: dm20:Omegamon" + sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=dm20:Omegamon/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: pen20:Megalo Growmon" + sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=pen20:Megalo Growmon/' "$CONFIG" + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: dmc:Omegamon" + sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=dmc:Omegamon/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: dm:Coronamon" + sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=dm:Coronamon/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: Metal Greymon" + sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=Metal Greymon/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + echo "[INFO] Set Sprite Sheet: pmd:dialga" + sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=pmd:dialga/' "$CONFIG" + sleep 2 + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 5 + + + echo "[TEST] CPU threshold" + echo "[INFO] Enable CPU threshold" + sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" + sed -i -E 's/^animation_name=.*/animation_name=dm20:Agumon/' "$CONFIG" + sed -i -E 's/^update_rate=[0-9]+/update_rate=1000/' "$CONFIG" + sed -i -E 's/^cpu_threshold=[0-9]+/cpu_threshold=30/' "$CONFIG" + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 2 + if command -v stress-ng >/dev/null 2>&1; then + echo "[INFO] Running stress-ng to generate load" + stress-ng --cpu 0 --timeout 15s --metrics-brief & + sleep 20 + elif command -v stress >/dev/null 2>&1; then + echo "[INFO] Running stress to generate load" + stress --cpu "$(nproc)" --timeout 15s & + sleep 20 + else + echo "[WARN] No stress tool found, skipping load generation" + fi + echo "[INFO] Disable CPU threshold" + sed -i -E 's/^update_rate=[0-9]+/update_rate=0/' "$CONFIG" + sed -i -E 's/^cpu_threshold=[0-9]+/cpu_threshold=90/' "$CONFIG" + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 5 + + # --- verify running --- + if kill -0 "$PID" 2>/dev/null; then + echo "[PASS] Process $PID still running!" + else + echo "[FAIL] Process terminated" + exit 1 + fi + + # --- send SIGTERM --- + echo "[INFO] Sending SIGTERM..." + kill -TERM "$PID" + echo "[TEST] Reload config while terminating..." + # set config when terminating + sed -i -E 's/^animation_name=.*/animation_name=Tyranomon/' "$CONFIG" + echo "[INFO] Send SIGUSR2" + kill -USR2 "$PID" # Reload config + sleep 5 + sleep 15 + echo "[INFO] Wait for TERM" + # wait up to 5 seconds + for i in {1..5}; do + if ! kill -0 "$PID" 2>/dev/null; then + break + fi + sleep 1 + done + echo "[TEST] Start with stdin config..." + cat "$CONFIG" | "$PROGRAM" --ignore-running --strict --config - & + PID=$! + sleep 10 + sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" + sleep 5 + # --- verify running --- + if kill -0 "$PID" 2>/dev/null; then + echo "[PASS] Process $PID still running!" + else + echo "[FAIL] Process terminated" + exit 1 + fi + + + # --- send SIGTERM --- + echo "[TEST] Sending SIGTERM..." + kill -TERM "$PID" + sleep 10 + echo "[INFO] Wait for TERM" + # wait up to 5 seconds + for i in {1..5}; do + if ! kill -0 "$PID" 2>/dev/null; then + break + fi + sleep 1 + done + + # --- verify not running --- + if kill -0 "$PID" 2>/dev/null; then + echo "[FAIL] Process $PID still running!" + kill -9 "$PID" 2>/dev/null + exit 1 + else + echo "[PASS] Process terminated successfully" + fi + done +done \ No newline at end of file diff --git a/scripts/test_bongocat_8.sh b/scripts/test_bongocat_8.sh index 11292c51..72a250ef 100755 --- a/scripts/test_bongocat_8.sh +++ b/scripts/test_bongocat_8.sh @@ -2,404 +2,141 @@ set -euo pipefail -for group in relwithdebinfo-tsan debug-all-assets-preload debug relwithdebinfo; do - find ./cmake-build-* -type f -executable -name "bongocat*" | grep -i "$group" | while read -r PROGRAM; do - WORKDIR=$(mktemp -d) - CONFIG="$WORKDIR/test.bongocat.conf" # config file to modify - OG_CONFIG=./examples/test.bongocat.conf - cp $OG_CONFIG $CONFIG - - echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG ..." +#make debug +#PROGRAM="./cmake-build-debug-all-assets-colored-preload/bongocat" +#PROGRAM="./cmake-build-debug-all-assets-preload/bongocat-all" +PROGRAM="./cmake-build-debug/bongocat-all" +#PROGRAM="./build/bongocat-all" + +WORKDIR=$(mktemp -d) +CONFIG="$WORKDIR/test.bongocat.conf" # config file to modify +OG_CONFIG=./examples/test.bongocat.conf +cp $OG_CONFIG $CONFIG + +sed -i -E 's/^cat_height=[0-9]+/cat_height=96/' "$CONFIG" +sed -i -E 's/^overlay_height=[0-9]+/overlay_height=128/' "$CONFIG" + +if [[ $# -ge 1 ]]; then + PID="$1" + CONFIG="$2" + cp $CONFIG "${CONFIG}.bak" + OG_CONFIG="${CONFIG}.bak" + echo "[TEST] Using provided PID = $PID" +else echo "[TEST] Starting program..." - "$PROGRAM" --config "$CONFIG" --ignore-running & + "$PROGRAM" --config "$CONFIG" --ignore-running --strict & PID=$! echo "[TEST] Program PID = $PID" sleep 5 - sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" - sleep 5 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - - echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG (pid=${PID})" - - # --- trap cleanup --- - cleanup() { - echo "[TEST] Cleaning up..." - kill -9 "$PID" 2>/dev/null || true - cp $OG_CONFIG $CONFIG - rm -rf "$WORKDIR" - } - trap cleanup EXIT - - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 5 - echo "[INFO] Spam SIGUSR2" - kill -USR2 "$PID" - kill -USR2 "$PID" - kill -USR2 "$PID" - kill -USR2 "$PID" - sleep 7 - - # --- function to toggle idle_sleep_timeout --- - toggle_config() { - if grep -q '^idle_sleep_timeout=10' "$CONFIG"; then - new=3600 - else - new=10 - fi - sed -i -E "s/^idle_sleep_timeout=[0-9]+/idle_sleep_timeout=$new/" "$CONFIG" - echo "[TEST] Setting idle_sleep_timeout=$new" - } - - # --- modify config to trigger hot reload --- - sed -i -E 's/^cpu_threshold=[0-9]+/cpu_threshold=0/' "$CONFIG" - sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" - sleep 3 - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - toggle_config - sleep 10 - toggle_config - sleep 10 - echo "[TEST] Trigger Sleep" - echo "[INFO] Enable idle_sleep_timeout..." - sed -i -E "s/^idle_sleep_timeout=[0-9]+/idle_sleep_timeout=10/" "$CONFIG" - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - sleep 5 - sed -i 's/^enable_scheduled_sleep=0/enable_scheduled_sleep=1/' "$CONFIG" - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - sleep 20 - echo "[TEST] Wake up Sleep" - if [[ -f "/proc/$PID/fd/0" ]]; then - printf '\e' > /proc/$PID/fd/0 - sleep 5 - fi - echo "[INFO] Disable idle_sleep_timeout..." - sed -i -E "s/^idle_sleep_timeout=[0-9]+/idle_sleep_timeout=3600/" "$CONFIG" - sleep 5 - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" - sleep 5 - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - echo "[TEST] Change animation sprite" - echo "[INFO] Set animation_name..." - sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" - sleep 5 - echo "[INFO] Set animation_name..." - sed -i -E 's/^animation_name=.*/animation_name=greymon/' "$CONFIG" - sleep 3 - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 5 - - echo "[TEST] Invalid animation sprite" - echo "[INFO] Set animation_name..." - sed -i -E 's/^animation_name=.*/animation_name=NoNo/' "$CONFIG" - sleep 5 - echo "[INFO] Set animation_name..." - sed -i -E 's/^animation_name=.*/animation_name=greymon/' "$CONFIG" - sleep 5 - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - - echo "[TEST] move and delete config..." - echo "[INFO] Move Config: $CONFIG > ${CONFIG}.del" - mv $CONFIG "${CONFIG}.del" - sleep 5 - rm "${CONFIG}.del" - sleep 5 - echo "[INFO] Recreate Config: $CONFIG" - cp ./examples/digimon.bongocat.conf $CONFIG - sleep 5 - echo "[INFO] Delete Config: $CONFIG" - rm $CONFIG - sleep 5 - echo "[INFO] Recreate Config: $CONFIG" - cp ./examples/digimon.bongocat.conf $CONFIG - sleep 3 - - echo "[INFO] Disable sleep" - sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" - sleep 10 - if [[ -f "/proc/$PID/fd/0" ]]; then - # --- simulate pressing ESC --- - echo "[TEST] Sending ESC key..." - echo "[INFO] Send stdin" - printf '\e' > /proc/$PID/fd/0 - sleep 5 - echo "[INFO] Send stdin" - printf '\e' > /proc/$PID/fd/0 - sleep 1 - # a bit of a spam - echo "[INFO] Spam stdin" - printf '\e' > /proc/$PID/fd/0 - printf '\e' > /proc/$PID/fd/0 - printf '\e' > /proc/$PID/fd/0 - printf '\e' > /proc/$PID/fd/0 - sleep 5 - echo "[INFO] Spam stdin slower" - printf '\e' > /proc/$PID/fd/0 - sleep 1 - printf '\e' > /proc/$PID/fd/0 - sleep 1 - printf '\e' > /proc/$PID/fd/0 - sleep 1 - printf '\e' > /proc/$PID/fd/0 - sleep 3 - fi - - echo "[INFO] Disable sleep" - sed -i 's/^enable_scheduled_sleep=1/enable_scheduled_sleep=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" - sleep 5 - echo "[TEST] Sending SIGUSR2..." - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 3 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" - sleep 5 - echo "[INFO] Spam SIGUSR2" - kill -USR2 "$PID" - kill -USR2 "$PID" - kill -USR2 "$PID" - kill -USR2 "$PID" - sleep 10 - echo "[INFO] Spam SIGUSR2 slower" - kill -USR2 "$PID" - sleep 5 - kill -USR2 "$PID" - sleep 3 - kill -USR2 "$PID" - sleep 2 - kill -USR2 "$PID" - sleep 15 +fi - echo "[TEST] Sending SIGUSR1..." - echo "[INFO] Send SIGUSR1" - kill -USR1 "$PID" - sleep 2 - echo "[INFO] Send SIGUSR1" - kill -USR1 "$PID" - sleep 2 - - echo "[TEST] replace config..." - echo "[INFO] Replace Config: $CONFIG > ${CONFIG}.del" - cp ./examples/dmc.bongocat.conf $CONFIG - sleep 5 - echo "[TEST] Sending ESC key..." - if [[ -f "/proc/$PID/fd/0" ]]; then - echo "[INFO] Send stdin" - printf '\e' > /proc/$PID/fd/0 - sleep 2 - printf '\e' > /proc/$PID/fd/0 - sleep 2 - printf '\e' > /proc/$PID/fd/0 - sleep 5 - fi - echo "[INFO] Restore old config" +# --- trap cleanup --- +cleanup() { + echo "[TEST] Cleaning up..." + kill -9 "$PID" 2>/dev/null || true cp $OG_CONFIG $CONFIG - sleep 5 - - echo "[TEST] Fully replace config (Digimon -> Clippy): $CONFIG" - cp ./examples/clippy.bongocat.conf $CONFIG - sleep 5 - - - # --- send SIGTERM --- - echo "[INFO] Sending SIGTERM..." - kill -TERM "$PID" - sleep 15 - echo "[INFO] Wait for TERM" - # wait up to 5 seconds - for i in {1..5}; do - if ! kill -0 "$PID" 2>/dev/null; then - break - fi - sleep 1 - done - echo "[TEST] Re-start..." - "$PROGRAM" --ignore-running --strict --config "$CONFIG" & - PID=$! - sleep 10 - echo "[TEST] Load biggest assets" - echo "[INFO] Set Sprite Sheet: Links" - sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=Links/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 5 - echo "[INFO] Set Sprite Sheet: pkmn:dialga" - sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=pkmn:dialga/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: dmx:Hexeblaumon" - sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=dmx:Hexeblaumon/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: dm20:Omegamon" - sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=dm20:Omegamon/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: pen20:Megalo Growmon" - sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=pen20:Megalo Growmon/' "$CONFIG" - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: dmc:Omegamon" - sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=dmc:Omegamon/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: dm:Coronamon" - sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=dm:Coronamon/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: Metal Greymon" - sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=Metal Greymon/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - echo "[INFO] Set Sprite Sheet: pmd:dialga" - sed -i -E 's/^invert_color=[0-9]+/invert_color=0/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=pmd:dialga/' "$CONFIG" - sleep 2 - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 5 - - - echo "[TEST] CPU threshold" - echo "[INFO] Enable CPU threshold" - sed -i -E 's/^invert_color=[0-9]+/invert_color=1/' "$CONFIG" - sed -i -E 's/^animation_name=.*/animation_name=dm20:Agumon/' "$CONFIG" - sed -i -E 's/^update_rate=[0-9]+/update_rate=1000/' "$CONFIG" - sed -i -E 's/^cpu_threshold=[0-9]+/cpu_threshold=30/' "$CONFIG" - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 2 - if command -v stress-ng >/dev/null 2>&1; then - echo "[INFO] Running stress-ng to generate load" - stress-ng --cpu 0 --timeout 15s --metrics-brief & - sleep 20 - elif command -v stress >/dev/null 2>&1; then - echo "[INFO] Running stress to generate load" - stress --cpu "$(nproc)" --timeout 15s & - sleep 20 - else - echo "[WARN] No stress tool found, skipping load generation" - fi - echo "[INFO] Disable CPU threshold" - sed -i -E 's/^update_rate=[0-9]+/update_rate=0/' "$CONFIG" - sed -i -E 's/^cpu_threshold=[0-9]+/cpu_threshold=90/' "$CONFIG" - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 5 - - # --- verify running --- - if kill -0 "$PID" 2>/dev/null; then - echo "[PASS] Process $PID still running!" - else - echo "[FAIL] Process terminated" - exit 1 - fi - - # --- send SIGTERM --- - echo "[INFO] Sending SIGTERM..." - kill -TERM "$PID" - echo "[TEST] Reload config while terminating..." - # set config when terminating - sed -i -E 's/^animation_name=.*/animation_name=Tyranomon/' "$CONFIG" - echo "[INFO] Send SIGUSR2" - kill -USR2 "$PID" # Reload config - sleep 5 - sleep 15 - echo "[INFO] Wait for TERM" - # wait up to 5 seconds - for i in {1..5}; do - if ! kill -0 "$PID" 2>/dev/null; then - break - fi - sleep 1 - done - echo "[TEST] Start with stdin config..." - cat "$CONFIG" | "$PROGRAM" --ignore-running --strict --config - & - PID=$! - sleep 10 - sed -i -E 's/^animation_name=.*/animation_name=agumon/' "$CONFIG" - sleep 5 - # --- verify running --- - if kill -0 "$PID" 2>/dev/null; then - echo "[PASS] Process $PID still running!" - else - echo "[FAIL] Process terminated" - exit 1 - fi - - - # --- send SIGTERM --- - echo "[TEST] Sending SIGTERM..." - kill -TERM "$PID" - sleep 10 - echo "[INFO] Wait for TERM" - # wait up to 5 seconds - for i in {1..5}; do - if ! kill -0 "$PID" 2>/dev/null; then - break - fi - sleep 1 - done - - # --- verify not running --- - if kill -0 "$PID" 2>/dev/null; then - echo "[FAIL] Process $PID still running!" - kill -9 "$PID" 2>/dev/null - exit 1 - else - echo "[PASS] Process terminated successfully" + rm -rf "$WORKDIR" +} +trap cleanup EXIT + +echo "[INFO] Test Program: ${PROGRAM} --config $CONFIG (pid=${PID})" + +echo "[TEST] Sending SIGUSR2..." +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" +sleep 3 +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" +sleep 5 +echo "[INFO] Spam SIGUSR2" +kill -USR2 "$PID" +kill -USR2 "$PID" +kill -USR2 "$PID" +kill -USR2 "$PID" +sleep 7 + +echo "[TEST] Change overlay settings" +echo "[INFO] Set overlay_height" +sed -i -E 's/^overlay_height=[0-9]+/overlay_height=100/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 +echo "[INFO] Set overlay_position" +sed -i -E 's/^overlay_position=.*/overlay_position=top/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 +echo "[INFO] Set overlay_position" +sed -i -E 's/^overlay_position=.*/overlay_position=bottom/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 +echo "[INFO] Set overlay_layer" +sed -i -E 's/^overlay_layer=.*/overlay_layer=overlay/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 +echo "[INFO] Set overlay_layer" +sed -i -E 's/^overlay_layer=.*/overlay_layer=top/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 +echo "[INFO] Set overlay_height" +sed -i -E 's/^overlay_height=[0-9]+/overlay_height=128/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 + +sleep 20 + +# --- verify running --- +if kill -0 "$PID" 2>/dev/null; then + echo "[PASS] Process $PID still running!" +else + echo "[FAIL] Process terminated" + exit 1 +fi + +echo "[TEST] Set Monitor" +echo "[INFO] Set monitor" +sed -i -E 's/^monitor=.*/monitor=HDMI-A-1/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 +echo "[INFO] Set monitor" +sed -i -E 's/^monitor=.*/monitor=DP-1/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 10 + +sleep 20 + +# --- verify running --- +if kill -0 "$PID" 2>/dev/null; then + echo "[PASS] Process $PID still running!" +else + echo "[FAIL] Process terminated" + exit 1 +fi + +# --- send SIGTERM --- +echo "[TEST] Sending SIGTERM..." +kill -TERM "$PID" +sleep 10 +echo "[INFO] Wait for TERM" +# wait up to 5 seconds +for i in {1..5}; do + if ! kill -0 "$PID" 2>/dev/null; then + break fi - done -done \ No newline at end of file + sleep 1 +done + +# --- verify not running --- +if kill -0 "$PID" 2>/dev/null; then + echo "[FAIL] Process $PID still running!" + kill -9 "$PID" 2>/dev/null + exit 1 +else + echo "[PASS] Process terminated successfully" +fi diff --git a/scripts/test_bongocat_9.sh b/scripts/test_bongocat_9.sh new file mode 100755 index 00000000..510cd3c6 --- /dev/null +++ b/scripts/test_bongocat_9.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +set -euo pipefail + +#make debug +#PROGRAM="./cmake-build-debug-all-assets-colored-preload/bongocat" +PROGRAM="./cmake-build-debug/bongocat-all" +#PROGRAM="./build/bongocat-all" + +echo "[TEST] Starting program (with wrong config)..." +"$PROGRAM" --ignore-running --config 123.not-found.bongocat.conf & +PID=$! +echo "[TEST] Program PID = $PID" +sleep 5 + +# --- trap cleanup --- +cleanup() { + echo "[TEST] Cleaning up..." + kill -9 "$PID" 2>/dev/null || true +} +trap cleanup EXIT + +echo "[INFO] Test Program: ${PROGRAM} (pid=${PID})" + +# --- verify not running --- +if kill -0 "$PID" 2>/dev/null; then + echo "[FAIL] Process $PID still running!" + kill -9 "$PID" 2>/dev/null + exit 1 +else + echo "[PASS] Process terminated successfully" +fi diff --git a/scripts/test_toggle.sh b/scripts/test_toggle.sh index 216d0a6b..1976c553 100755 --- a/scripts/test_toggle.sh +++ b/scripts/test_toggle.sh @@ -15,7 +15,7 @@ if [[ $# -ge 1 ]]; then echo "[TEST] Using provided PID = $TOGGLE_PID" else echo "[TEST] Starting program..." - "$PROGRAM" --toggle --config bongocat.conf & + "$PROGRAM" --toggle --config bongocat.conf.example & TOGGLE_PID=$! echo "[TEST] Program PID = $TOGGLE_PID" sleep 20 @@ -25,7 +25,7 @@ sleep 2 # Toggle off echo "[TEST] Sending --toggle to stop the instance..." -"$PROGRAM" --toggle --config bongocat.conf +"$PROGRAM" --toggle --config bongocat.conf.example # Wait for shutdown for i in {1..10}; do if ! kill -0 "$TOGGLE_PID" 2>/dev/null; then break; fi @@ -42,7 +42,7 @@ fi # Toggle on (should start new instance) echo "[TEST] Sending --toggle to start a new instance..." -"$PROGRAM" --toggle --config bongocat.conf & +"$PROGRAM" --toggle --config bongocat.conf.example & NEW_PID=$! sleep 2 diff --git a/src/core/main.cpp b/src/core/main.cpp index fff5b0ce..d58393c9 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -129,7 +129,7 @@ namespace bongocat { if (context.pid_filename) ::free(context.pid_filename); context.pid_filename = nullptr; - if (context.default_config_filename) ::free(context.pid_filename); + if (context.default_config_filename) ::free(context.default_config_filename); context.default_config_filename = nullptr; } @@ -836,12 +836,17 @@ int main(int argc, char *argv[]) { .strict = args.strict, }; if (args.config_file == nullptr) { + /// @TODO: RAII default_config_filename string get_main_context().default_config_filename = default_config_file_path(); } const char* config_file = args.config_file == nullptr ? get_main_context().default_config_filename : args.config_file; if (args.strict) { if (strcmp(config_file, "-") != 0 && access(config_file, F_OK) != 0) { BONGOCAT_LOG_ERROR("Configuration file required: %s", config_file); + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } return EXIT_FAILURE; } } @@ -849,6 +854,10 @@ int main(int argc, char *argv[]) { auto [config, config_error] = config::load(config_file, ctx.overwrite_config_parameters); if (config_error != bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_ERROR("Failed to load configuration: %s", bongocat::error_string(config_error)); + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } return EXIT_FAILURE; } ctx.config = bongocat::move(config); @@ -857,10 +866,18 @@ int main(int argc, char *argv[]) { // validate args if (config._strict) { if (args.nr_set && args.nr < 0) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } BONGOCAT_LOG_ERROR("--nr needs to be a positive number"); return EXIT_FAILURE; } if (args.output_name_set && (!args.output_name || strlen(args.output_name) <= 0)) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } BONGOCAT_LOG_ERROR("--output_name value is missing"); return EXIT_FAILURE; } @@ -893,9 +910,13 @@ int main(int argc, char *argv[]) { } if (ctx.pid_filename == nullptr) { - BONGOCAT_LOG_ERROR("Failed to allocate PID filename"); - return EXIT_FAILURE; - } + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + BONGOCAT_LOG_ERROR("Failed to allocate PID filename"); + return EXIT_FAILURE; + } } else { ctx.pid_filename = strdup(DEFAULT_PID_FILE); } @@ -912,11 +933,19 @@ int main(int argc, char *argv[]) { // Create PID file to track this instance const platform::FileDescriptor pid_fd = process_create_pid_file(ctx.pid_filename); if (pid_fd._fd < 0) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } BONGOCAT_LOG_ERROR("Failed to create PID file"); return EXIT_FAILURE; } if (!args.ignore_running) { if (pid_fd._fd == -2) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } BONGOCAT_LOG_ERROR("Another instance of bongocat is already running"); return EXIT_FAILURE; } @@ -928,6 +957,10 @@ int main(int argc, char *argv[]) { ctx.signal_watch_path = config_file; bongocat_error_t signal_result = signal_setup_handlers(ctx); if (signal_result != bongocat_error_t::BONGOCAT_SUCCESS) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } BONGOCAT_LOG_ERROR("Failed to setup signal handlers: %s", bongocat::error_string(signal_result)); return EXIT_FAILURE; } diff --git a/src/platform/wayland_callbacks.cpp b/src/platform/wayland_callbacks.cpp index 45414e08..01baf460 100644 --- a/src/platform/wayland_callbacks.cpp +++ b/src/platform/wayland_callbacks.cpp @@ -52,6 +52,9 @@ for (type *pos = reinterpret_cast((array)->data); \ BONGOCAT_LOG_DEBUG("xdg_output.name: xdg-output name received: %s", name); } + // @NOTE: this should always be set ? + //assert(oref->wayland); + /// Reconnection handling if (oref->wayland) { wayland_context_t& wayland_ctx = oref->wayland->wayland_context; From 07ecebbbfcbadf7e702fc02f502d2890a93e9c22 Mon Sep 17 00:00:00 2001 From: furudbat Date: Mon, 8 Dec 2025 23:33:34 +0100 Subject: [PATCH 11/18] cmake: add formatting and cmake-format --- .clang-format | 14 +- .clang-tidy | 53 +- CMakeLists.txt | 699 +++++++++++++++----------- Makefile | 23 +- Makefile.old | 27 +- cmake-format.yaml | 219 ++++++++ cmake/CPM.cmake | 24 + include/embedded_assets/.clang-format | 2 + include/image_loader/.clang-format | 2 + lib/.clang-format | 2 + protocols/.clang-format | 2 + src/embedded_assets/.clang-format | 2 + src/image_loader/.clang-format | 2 + 13 files changed, 742 insertions(+), 329 deletions(-) create mode 100644 cmake-format.yaml create mode 100644 cmake/CPM.cmake create mode 100644 include/embedded_assets/.clang-format create mode 100644 include/image_loader/.clang-format create mode 100644 lib/.clang-format create mode 100644 protocols/.clang-format create mode 100644 src/embedded_assets/.clang-format create mode 100644 src/image_loader/.clang-format diff --git a/.clang-format b/.clang-format index 85b39631..49341704 100644 --- a/.clang-format +++ b/.clang-format @@ -1,9 +1,10 @@ # Clang-Format Configuration for wayland-bongocat # Best practices for C23-compatible code - +--- # Base style BasedOnStyle: LLVM - +IndentWidth: 2 +--- # Language Language: Cpp Standard: Latest @@ -17,7 +18,7 @@ IndentPPDirectives: AfterHash IndentExternBlock: NoIndent # Column limit -ColumnLimit: 80 +ColumnLimit: 120 # Alignment AlignAfterOpenBracket: Align @@ -32,7 +33,7 @@ AlignTrailingComments: true # Pointer and reference alignment PointerAlignment: Right -ReferenceAlignment: Pointer +ReferenceAlignment: Left DerivePointerAlignment: false # Braces @@ -66,6 +67,7 @@ BreakBeforeBinaryOperators: None BreakBeforeTernaryOperators: true BreakStringLiterals: true NamespaceIndentation: Inner +FixNamespaceComments: true # Spaces SpaceAfterCStyleCast: false @@ -112,3 +114,7 @@ ReflowComments: true SeparateDefinitionBlocks: Leave InsertBraces: false RemoveBracesLLVM: false +--- +Language: JavaScript +IndentWidth: 2 +ColumnLimit: 120 \ No newline at end of file diff --git a/.clang-tidy b/.clang-tidy index e886d27b..b796e4cf 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -19,9 +19,11 @@ Checks: > -readability-magic-numbers, -readability-function-cognitive-complexity, -readability-else-after-return, + -readability-uppercase-literal-suffix, + -misc-use-anonymous-namespace, # Only analyze project headers, not system/lib headers -HeaderFilterRegex: '.*/(include|src)/.*\.h$' +HeaderFilterRegex: '.*/(include|src)/.*\.(h|hpp)$' # Treat warnings as warnings (not errors by default) WarningsAsErrors: '' @@ -49,6 +51,49 @@ CheckOptions: value: lower_case # - key: readability-identifier-naming.EnumConstantCase # value: UPPER_CASE + # Naming conventions (C++ style) + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.ClassMemberCase + value: camelBack + - key: readability-identifier-naming.PrivateMemberCase + value: camelBack + - key: readability-identifier-naming.ProtectedMemberCase + value: camelBack + - key: readability-identifier-naming.PublicMemberCase + value: lower_case + - key: readability-identifier-naming.StaticConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.ClassConstantCase + value: CamelCase + - key: readability-identifier-naming.ConstexprMethodCase + value: CamelCase + - key: readability-identifier-naming.ClassMethodCase + value: camelBack + - key: readability-identifier-naming.PublicMethodCase + value: camelBack + - key: readability-identifier-naming.PrivateMethodCase + value: camelBack + - key: readability-identifier-naming.ProtectedMethodCase + value: camelBack + - key: readability-identifier-naming.ConstexprMethodCase + value: camelBack + - key: readability-identifier-naming.LocalVariableCase + value: lower_case + - key: readability-identifier-naming.ConstantParameterCase + value: lower_case + - key: readability-identifier-naming.ParameterPackCase + value: lower_case + - key: readability-identifier-naming.LocalConstantCase + value: lower_case + - key: readability-identifier-naming.ConstexprVariableCase + value: UPPER_CASE + - key: readability-identifier-naming.TemplateParameterCase + value: CamelCase + - key: readability-identifier-naming.TemplateTemplateParameterCase + value: CamelCase # Braces around statements - key: readability-braces-around-statements.ShortStatementLines @@ -69,9 +114,15 @@ CheckOptions: # Misc settings - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic value: 'true' + - key: readability-identifier-length.IgnoredVariableNames + value: 'i|j|k|x|y|z' + - key: readability-identifier-length.IgnoredParameterNames + value: 'i|j|k|x|y|z' # System header directories to ignore SystemHeaders: false # Use colors in diagnostics UseColor: true + +#FormatStyle: file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e8fea0a..1495d312 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,46 +6,53 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_C_STANDARD_REQUIRED ON) include(FetchContent) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") - cmake_policy(SET CMP0135 NEW) + cmake_policy(SET CMP0135 NEW) endif() get_property(BUILDING_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(BUILDING_MULTI_CONFIG) - if(NOT CMAKE_BUILD_TYPE) - # Make sure that all supported configuration types have their associated conan packages available. You can reduce this list to only the - # configuration types you use, but only if one is not forced-set on the command line for VS - message(TRACE "Setting up multi-config build types") - set(CMAKE_CONFIGURATION_TYPES - Debug Release RelWithDebInfo MinSizeRel - CACHE STRING "Enabled build types" FORCE) - else() - message(TRACE "User chose a specific build type, so we are using that") - set(CMAKE_CONFIGURATION_TYPES - ${CMAKE_BUILD_TYPE} - CACHE STRING "Enabled build types" FORCE) - endif() + if(NOT CMAKE_BUILD_TYPE) + # Make sure that all supported configuration types have their associated conan packages available. You can reduce this list to only the + # configuration types you use, but only if one is not forced-set on the command line for VS + message(TRACE "Setting up multi-config build types") + set(CMAKE_CONFIGURATION_TYPES + Debug Release RelWithDebInfo MinSizeRel + CACHE STRING "Enabled build types" FORCE) + else() + message(TRACE "User chose a specific build type, so we are using that") + set(CMAKE_CONFIGURATION_TYPES + ${CMAKE_BUILD_TYPE} + CACHE STRING "Enabled build types" FORCE) + endif() endif() if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the build type." FORCE) + set(CMAKE_BUILD_TYPE + Release + CACHE STRING "Choose the build type." FORCE) endif() - - -project(bongocat LANGUAGES C CXX VERSION 3.6.0) +project( + bongocat + LANGUAGES C CXX + VERSION 3.6.0) # Feature Flags include(CMakeDependentOption) option(FEATURE_BONGOCAT_EMBEDDED_ASSETS "Include bongocat assets (default)" ON) option(FEATURE_ENABLE_DM_EMBEDDED_ASSETS "Enable include dm embedded assets" OFF) cmake_dependent_option(FEATURE_DM_EMBEDDED_ASSETS "Include dm embedded assets" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) -cmake_dependent_option(FEATURE_DM20_EMBEDDED_ASSETS "Include dm20 embedded assets (replaces original dm)" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) +cmake_dependent_option(FEATURE_DM20_EMBEDDED_ASSETS "Include dm20 embedded assets (replaces original dm)" OFF + FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) cmake_dependent_option(FEATURE_DMC_EMBEDDED_ASSETS "Include dmc embedded assets" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) cmake_dependent_option(FEATURE_DMX_EMBEDDED_ASSETS "Include dmx embedded assets" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) cmake_dependent_option(FEATURE_PEN_EMBEDDED_ASSETS "Include pen embedded assets" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) -cmake_dependent_option(FEATURE_PEN20_EMBEDDED_ASSETS "Include pen20 embedded assets (replaces original pen)" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) -cmake_dependent_option(FEATURE_DMALL_EMBEDDED_ASSETS "Include custom colored dm (replace dmc) embedded assets" OFF FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) +cmake_dependent_option(FEATURE_PEN20_EMBEDDED_ASSETS "Include pen20 embedded assets (replaces original pen)" OFF + FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) +cmake_dependent_option(FEATURE_DMALL_EMBEDDED_ASSETS "Include custom colored dm (replace dmc) embedded assets" OFF + FEATURE_ENABLE_DM_EMBEDDED_ASSETS OFF) option(FEATURE_MS_AGENT_EMBEDDED_ASSETS "Include MS agent (Clippy) embedded assets" OFF) -cmake_dependent_option(FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS "Include more MS agents (Links) embedded assets" OFF FEATURE_MS_AGENT_EMBEDDED_ASSETS OFF) +cmake_dependent_option(FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS "Include more MS agents (Links) embedded assets" OFF + FEATURE_MS_AGENT_EMBEDDED_ASSETS OFF) option(FEATURE_ENABLE_PKMN_EMBEDDED_ASSETS "Enable include pkmn embedded assets" OFF) option(FEATURE_ENABLE_PMD_EMBEDDED_ASSETS "Enable include pkmn pmd (replace pkmn) embedded assets" OFF) option(FEATURE_MISC_EMBEDDED_ASSETS "Enable include misc embedded assets" OFF) @@ -55,7 +62,8 @@ option(FEATURE_DISABLE_LOGGER "Disable Logger (makes enable_debug option obsolet option(FEATURE_PRELOAD_ASSETS "Preload available assets (More RAM usage, faster sprite switching on hot-reload)" OFF) option(FEATURE_MULTI_VERSIONS "Build multiple versions with different assets for installation" ON) option(FEATURE_LAZY_LOAD_ASSETS "No Preload assets (Less RAM usage, more CPU usage, sprite lazy-load on config reload) (Recommended)" ON) -option(FEATURE_USE_HYBRID_IMAGE_BACKEND "Use pngle or stb_image as assets (png) loader (Less RAM usage, more loading time, balanced) (Recommended)" ON) +option(FEATURE_USE_HYBRID_IMAGE_BACKEND + "Use pngle or stb_image as assets (png) loader (Less RAM usage, more loading time, balanced) (Recommended)" ON) option(FEATURE_USE_PNGLE "Use pngle as assets (png) loader (Less RAM usage, more loading time; replace stb_image)" OFF) option(FEATURE_CUSTOM_SPRITE_SHEETS "Enable custom sprite sheet at runtime" ON) @@ -63,339 +71,422 @@ option(ENABLE_ASAN "Enable Address Sanitizer" OFF) option(ENABLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF) option(ENABLE_TSAN "Enable Thread Sanitizer" OFF) -# project_options -# More Warnings +# project_options More Warnings set(CLANG_WARNINGS - -Wall - -Wextra # reasonable and standard - -Wextra-semi # Warn about semicolon after in-class function definition. - -Wshadow # warn the user if a variable declaration shadows one from a parent context - -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps - # catch hard to track down memory errors - -Wold-style-cast # warn for c-style casts - -Wcast-align # warn for potential performance problem casts - -Wunused # warn on anything being unused - -Woverloaded-virtual # warn if you overload (not override) a virtual function - -Wpedantic # warn if non-standard C++ is used - -Wconversion # warn on type conversions that may lose data - -Wsign-conversion # warn on sign conversions - -Wnull-dereference # warn if a null dereference is detected - -Wdouble-promotion # warn if float is implicit promoted to double - -Wformat=2 # warn on security issues around functions that format output (ie printf) - -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation + -Wall + -Wextra # reasonable and standard + -Wextra-semi # Warn about semicolon after in-class function definition. + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wimplicit-fallthrough # warn on statements that fallthrough without an explicit annotation ) set(GCC_WARNINGS - ${CLANG_WARNINGS} - -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist - -Wduplicated-cond # warn if if / else chain has duplicated conditions - -Wduplicated-branches # warn if if / else branches have duplicated code - -Wlogical-op # warn about logical operations being used where bitwise were probably wanted - -Wuseless-cast # warn if you perform a cast to the same type + ${CLANG_WARNINGS} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type ) set(C_WARNING_FLAGS - -Wall -Wextra -Wpedantic -Wformat=2 -Wstrict-prototypes - -Wmissing-prototypes -Wold-style-definition -Wredundant-decls - -Wnested-externs -Wmissing-include-dirs -Wlogical-op - -Wjump-misses-init -Wdouble-promotion -Wshadow -) + -Wall + -Wextra + -Wpedantic + -Wformat=2 + -Wstrict-prototypes + -Wmissing-prototypes + -Wold-style-definition + -Wredundant-decls + -Wnested-externs + -Wmissing-include-dirs + -Wlogical-op + -Wjump-misses-init + -Wdouble-promotion + -Wshadow) set(GCC_C_WARNING_FLAGS - -Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wunused - -Wconversion -Wsign-conversion -Wformat=2 -Wimplicit-fallthrough - -Wnull-dereference -Wdouble-promotion -Wmissing-prototypes - -Wstrict-prototypes -Wpointer-arith -Wundef - -Wmissing-field-initializers -Wvla) + -Wall + -Wextra + -Wpedantic + -Wshadow + -Wcast-align + -Wunused + -Wconversion + -Wsign-conversion + -Wformat=2 + -Wimplicit-fallthrough + -Wnull-dereference + -Wdouble-promotion + -Wmissing-prototypes + -Wstrict-prototypes + -Wpointer-arith + -Wundef + -Wmissing-field-initializers + -Wvla) set(CLANG_C_WARNING_FLAGS - -Wall -Wextra -Wpedantic -Wshadow -Wcast-align -Wunused - -Wconversion -Wsign-conversion -Wformat=2 -Wimplicit-fallthrough - -Wnull-dereference -Wdouble-promotion -Wmissing-prototypes - -Wstrict-prototypes -Wpointer-arith -Wundef - -Wmissing-field-initializers -Wvla - -Wmisleading-indentation -Wduplicated-cond -Wduplicated-branches -Wlogical-op) + -Wall + -Wextra + -Wpedantic + -Wshadow + -Wcast-align + -Wunused + -Wconversion + -Wsign-conversion + -Wformat=2 + -Wimplicit-fallthrough + -Wnull-dereference + -Wdouble-promotion + -Wmissing-prototypes + -Wstrict-prototypes + -Wpointer-arith + -Wundef + -Wmissing-field-initializers + -Wvla + -Wmisleading-indentation + -Wduplicated-cond + -Wduplicated-branches + -Wlogical-op) add_library(project_warnings INTERFACE) target_compile_options(project_warnings INTERFACE $<$:${C_WARNING_FLAGS}>) -target_compile_options(project_warnings INTERFACE - $<$,$>:${CLANG_C_WARNINGS}> - $<$,$>:${GCC_C_WARNINGS}> - $<$,$>:${CLANG_WARNINGS}> - $<$,$>:${GCC_WARNINGS}> -) +target_compile_options( + project_warnings + INTERFACE $<$,$>:${CLANG_C_WARNINGS}> + $<$,$>:${GCC_C_WARNINGS}> + $<$,$>:${CLANG_WARNINGS}> + $<$,$>:${GCC_WARNINGS}>) add_library(project_options INTERFACE) -#target_compile_options(project_options INTERFACE $<$:/external:anglebrackets /external:W0>) -#target_compile_options(project_options INTERFACE $<$:/external:anglebrackets /external:W0>) +# target_compile_options(project_options INTERFACE $<$:/external:anglebrackets /external:W0>) +# target_compile_options(project_options INTERFACE $<$:/external:anglebrackets /external:W0>) set_target_properties(project_options PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) set_target_properties(project_options PROPERTIES INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) add_library(project_sanitizers INTERFACE) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") - # Check for incompatible options - if(ENABLE_ASAN AND ENABLE_TSAN) - message(FATAL_ERROR "ASan and TSan are incompatible. Please enable only one of them.") - endif() - if(ENABLE_ASAN) - target_compile_options(project_sanitizers INTERFACE -fsanitize=address) - target_link_options(project_sanitizers INTERFACE -fsanitize=address) - endif() - if(ENABLE_UBSAN) - target_compile_options(project_sanitizers INTERFACE -fsanitize=undefined) - target_link_options(project_sanitizers INTERFACE -fsanitize=undefined) - endif() - if(ENABLE_TSAN) - target_compile_options(project_sanitizers INTERFACE -fsanitize=thread) - target_link_options(project_sanitizers INTERFACE -fsanitize=thread) - endif() + # Check for incompatible options + if(ENABLE_ASAN AND ENABLE_TSAN) + message(FATAL_ERROR "ASan and TSan are incompatible. Please enable only one of them.") + endif() + if(ENABLE_ASAN) + target_compile_options(project_sanitizers INTERFACE -fsanitize=address) + target_link_options(project_sanitizers INTERFACE -fsanitize=address) + endif() + if(ENABLE_UBSAN) + target_compile_options(project_sanitizers INTERFACE -fsanitize=undefined) + target_link_options(project_sanitizers INTERFACE -fsanitize=undefined) + endif() + if(ENABLE_TSAN) + target_compile_options(project_sanitizers INTERFACE -fsanitize=thread) + target_link_options(project_sanitizers INTERFACE -fsanitize=thread) + endif() endif() set(PROTOCOLS_DIR ${PROJECT_SOURCE_DIR}/protocols) add_subdirectory(protocols) - set(SRC_DIR ${PROJECT_SOURCE_DIR}/src) set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include) add_subdirectory(src) - - add_executable(bongocat) -if (FEATURE_BONGOCAT_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - message(STATUS "Include bongocat assets") +if(FEATURE_BONGOCAT_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) + message(STATUS "Include bongocat assets") endif() -if (FEATURE_ENABLE_DM_EMBEDDED_ASSETS) - if (NOT FEATURE_DM20_EMBEDDED_ASSETS AND NOT FEATURE_DM_EMBEDDED_ASSETS AND NOT FEATURE_DMX_EMBEDDED_ASSETS AND NOT FEATURE_DMC_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_min_dm_loader assets_min_dm assets_min_dm_feature assets_min_dm_interface) - message(STATUS "Include min_dm assets") - else() - if (FEATURE_DM20_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) - message(STATUS "Include dm20 assets") - elseif (FEATURE_DM_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_dm_loader assets_dm assets_dm_feature assets_dm_interface) - message(STATUS "Include dm assets") - endif() - if (FEATURE_DMX_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) - message(STATUS "Include dmx assets") - endif() - if (FEATURE_PEN20_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) - message(STATUS "Include pen20 assets") - elseif (FEATURE_PEN_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_pen_loader assets_pen assets_pen_feature assets_pen_interface) - message(STATUS "Include pen assets") - endif() - if (FEATURE_DMALL_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) - message(STATUS "Include dmall assets") - elseif (FEATURE_DMC_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_dmc_loader assets_dmc assets_dmc_feature assets_dmc_interface) - message(STATUS "Include dmc assets") - endif() +if(FEATURE_ENABLE_DM_EMBEDDED_ASSETS) + if(NOT FEATURE_DM20_EMBEDDED_ASSETS + AND NOT FEATURE_DM_EMBEDDED_ASSETS + AND NOT FEATURE_DMX_EMBEDDED_ASSETS + AND NOT FEATURE_DMC_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_min_dm_loader assets_min_dm assets_min_dm_feature assets_min_dm_interface) + message(STATUS "Include min_dm assets") + else() + if(FEATURE_DM20_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) + message(STATUS "Include dm20 assets") + elseif(FEATURE_DM_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_dm_loader assets_dm assets_dm_feature assets_dm_interface) + message(STATUS "Include dm assets") endif() -endif() -if (FEATURE_MS_AGENT_EMBEDDED_ASSETS) - if (FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_more_ms_agent_loader assets_more_ms_agent assets_more_ms_agent_feature assets_more_ms_agent_interface) - else() - target_link_libraries(bongocat PRIVATE assets_ms_agent_loader assets_ms_agent assets_ms_agent_feature assets_ms_agent_interface) + if(FEATURE_DMX_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) + message(STATUS "Include dmx assets") endif() - message(STATUS "Include MS agent assets") + if(FEATURE_PEN20_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) + message(STATUS "Include pen20 assets") + elseif(FEATURE_PEN_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_pen_loader assets_pen assets_pen_feature assets_pen_interface) + message(STATUS "Include pen assets") + endif() + if(FEATURE_DMALL_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) + message(STATUS "Include dmall assets") + elseif(FEATURE_DMC_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_dmc_loader assets_dmc assets_dmc_feature assets_dmc_interface) + message(STATUS "Include dmc assets") + endif() + endif() +endif() +if(FEATURE_MS_AGENT_EMBEDDED_ASSETS) + if(FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_more_ms_agent_loader assets_more_ms_agent assets_more_ms_agent_feature + assets_more_ms_agent_interface) + else() + target_link_libraries(bongocat PRIVATE assets_ms_agent_loader assets_ms_agent assets_ms_agent_feature assets_ms_agent_interface) + endif() + message(STATUS "Include MS agent assets") +endif() +if(FEATURE_PMD_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader) + message(STATUS "Include pkmn pmd assets") +elseif(FEATURE_PKMN_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_pkmn_loader assets_pkmn assets_pkmn_feature assets_pkmn_interface) + message(STATUS "Include pkmn assets") endif() -if (FEATURE_PMD_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader) - message(STATUS "Include pkmn pmd assets") -elseif (FEATURE_PKMN_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_pkmn_loader assets_pkmn assets_pkmn_feature assets_pkmn_interface) - message(STATUS "Include pkmn assets") +if(FEATURE_MISC_EMBEDDED_ASSETS) + target_link_libraries(bongocat PRIVATE assets_misc_loader assets_misc assets_misc_feature assets_misc_interface assets_custom_loader) + message(STATUS "Include misc assets") endif() -if (FEATURE_MISC_EMBEDDED_ASSETS) - target_link_libraries(bongocat PRIVATE assets_misc_loader assets_misc assets_misc_feature assets_misc_interface assets_custom_loader) - message(STATUS "Include misc assets") +if(FEATURE_CUSTOM_SPRITE_SHEETS) + target_compile_definitions(bongocat PRIVATE FEATURE_CUSTOM_SPRITE_SHEETS) + target_link_libraries(bongocat PRIVATE assets_custom_sprite_sheet_feature assets_custom_loader) endif() -if (FEATURE_CUSTOM_SPRITE_SHEETS) - target_compile_definitions(bongocat PRIVATE FEATURE_CUSTOM_SPRITE_SHEETS) - target_link_libraries(bongocat PRIVATE assets_custom_sprite_sheet_feature assets_custom_loader) -endif () # @NOTE(assets): 1.1. link embedded assets target_link_libraries(bongocat PRIVATE bongocat_base bongocat_options bongocat_libs) -if (FEATURE_MULTI_VERSIONS) - add_executable(bongocat-dm-classic) - target_link_libraries(bongocat-dm-classic PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) # include bongocat as fallback - target_link_libraries(bongocat-dm-classic PRIVATE assets_dm_loader assets_dm assets_dm_feature assets_dm_interface) - target_link_libraries(bongocat-dm-classic PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) - target_link_libraries(bongocat-dm-classic PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) - target_link_libraries(bongocat-dm-classic PRIVATE assets_pen_loader assets_pen assets_pen_feature assets_pen_interface) - target_link_libraries(bongocat-dm-classic PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) - target_link_libraries(bongocat-dm-classic PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-dm-colored) - target_link_libraries(bongocat-dm-colored PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - target_link_libraries(bongocat-dm-colored PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) - target_link_libraries(bongocat-dm-colored PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) - target_link_libraries(bongocat-dm-colored PRIVATE assets_dmc_loader assets_dmc assets_dmc_feature assets_dmc_interface) - target_link_libraries(bongocat-dm-colored PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) - target_link_libraries(bongocat-dm-colored PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-dm-colored-all) - target_link_libraries(bongocat-dm-colored-all PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - target_link_libraries(bongocat-dm-colored-all PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) - target_link_libraries(bongocat-dm-colored-all PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-dm) - target_link_libraries(bongocat-dm PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - target_link_libraries(bongocat-dm PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) - target_link_libraries(bongocat-dm PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) - target_link_libraries(bongocat-dm PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) - target_link_libraries(bongocat-dm PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) - target_link_libraries(bongocat-dm PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-ms-agent) - target_link_libraries(bongocat-ms-agent PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - target_link_libraries(bongocat-ms-agent PRIVATE assets_more_ms_agent_loader assets_more_ms_agent assets_ms_agent_feature assets_more_ms_agent_interface) - target_link_libraries(bongocat-ms-agent PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-pkmn) - target_link_libraries(bongocat-pkmn PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - target_link_libraries(bongocat-pkmn PRIVATE assets_pkmn_loader assets_pkmn assets_pkmn_feature assets_pkmn_interface) - target_link_libraries(bongocat-pkmn PRIVATE assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader) - target_link_libraries(bongocat-pkmn PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-all) - target_link_libraries(bongocat-all PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) # include bongocat as fallback - target_link_libraries(bongocat-all PRIVATE assets_dm_loader assets_dm assets_dm_feature assets_dm_interface) - target_link_libraries(bongocat-all PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) - target_link_libraries(bongocat-all PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) - target_link_libraries(bongocat-all PRIVATE assets_dmc_loader assets_dmc assets_dmc_feature assets_dmc_interface) - target_link_libraries(bongocat-all PRIVATE assets_pen_loader assets_pen assets_pen_feature assets_pen_interface) - target_link_libraries(bongocat-all PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) - target_link_libraries(bongocat-all PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) - target_link_libraries(bongocat-all PRIVATE assets_more_ms_agent_loader assets_more_ms_agent assets_more_ms_agent_feature assets_more_ms_agent_interface) - target_link_libraries(bongocat-all PRIVATE assets_pkmn_loader assets_pkmn assets_pkmn_feature assets_pkmn_interface) - # TODO: include pmd in -all when ready (experimental) - target_link_libraries(bongocat-all PRIVATE - # only include pmd for testing - $<$:assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader> - $<$:assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader> - $<$:assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader> - ) - #target_link_libraries(bongocat-all PRIVATE assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader) - target_link_libraries(bongocat-all PRIVATE assets_misc_loader assets_misc assets_misc_feature assets_misc_interface assets_custom_loader) - target_link_libraries(bongocat-all PRIVATE assets_custom_sprite_sheet_feature assets_custom_loader) - target_link_libraries(bongocat-all PRIVATE bongocat_base bongocat_options bongocat_libs) - - add_executable(bongocat-neko) - target_link_libraries(bongocat-neko PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) - target_link_libraries(bongocat-neko PRIVATE assets_misc_loader assets_misc assets_misc_feature assets_misc_interface assets_custom_loader) - target_link_libraries(bongocat-neko PRIVATE bongocat_base bongocat_options bongocat_libs) - - # @NOTE(assets): 1.2. add exec for multi versions +if(FEATURE_MULTI_VERSIONS) + add_executable(bongocat-dm-classic) + target_link_libraries(bongocat-dm-classic PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature + assets_bongocat_interface) # include bongocat as fallback + target_link_libraries(bongocat-dm-classic PRIVATE assets_dm_loader assets_dm assets_dm_feature assets_dm_interface) + target_link_libraries(bongocat-dm-classic PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) + target_link_libraries(bongocat-dm-classic PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) + target_link_libraries(bongocat-dm-classic PRIVATE assets_pen_loader assets_pen assets_pen_feature assets_pen_interface) + target_link_libraries(bongocat-dm-classic PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) + target_link_libraries(bongocat-dm-classic PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-dm-colored) + target_link_libraries(bongocat-dm-colored PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature + assets_bongocat_interface) + target_link_libraries(bongocat-dm-colored PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) + target_link_libraries(bongocat-dm-colored PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) + target_link_libraries(bongocat-dm-colored PRIVATE assets_dmc_loader assets_dmc assets_dmc_feature assets_dmc_interface) + target_link_libraries(bongocat-dm-colored PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) + target_link_libraries(bongocat-dm-colored PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-dm-colored-all) + target_link_libraries(bongocat-dm-colored-all PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature + assets_bongocat_interface) + target_link_libraries(bongocat-dm-colored-all PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) + target_link_libraries(bongocat-dm-colored-all PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-dm) + target_link_libraries(bongocat-dm PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) + target_link_libraries(bongocat-dm PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) + target_link_libraries(bongocat-dm PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) + target_link_libraries(bongocat-dm PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) + target_link_libraries(bongocat-dm PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) + target_link_libraries(bongocat-dm PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-ms-agent) + target_link_libraries(bongocat-ms-agent PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) + target_link_libraries(bongocat-ms-agent PRIVATE assets_more_ms_agent_loader assets_more_ms_agent assets_ms_agent_feature + assets_more_ms_agent_interface) + target_link_libraries(bongocat-ms-agent PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-pkmn) + target_link_libraries(bongocat-pkmn PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) + target_link_libraries(bongocat-pkmn PRIVATE assets_pkmn_loader assets_pkmn assets_pkmn_feature assets_pkmn_interface) + target_link_libraries(bongocat-pkmn PRIVATE assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader) + target_link_libraries(bongocat-pkmn PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-all) + target_link_libraries(bongocat-all PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface + )# include bongocat as fallback + target_link_libraries(bongocat-all PRIVATE assets_dm_loader assets_dm assets_dm_feature assets_dm_interface) + target_link_libraries(bongocat-all PRIVATE assets_dm20_loader assets_dm20 assets_dm20_feature assets_dm20_interface) + target_link_libraries(bongocat-all PRIVATE assets_dmx_loader assets_dmx assets_dmx_feature assets_dmx_interface) + target_link_libraries(bongocat-all PRIVATE assets_dmc_loader assets_dmc assets_dmc_feature assets_dmc_interface) + target_link_libraries(bongocat-all PRIVATE assets_pen_loader assets_pen assets_pen_feature assets_pen_interface) + target_link_libraries(bongocat-all PRIVATE assets_pen20_loader assets_pen20 assets_pen20_feature assets_pen20_interface) + target_link_libraries(bongocat-all PRIVATE assets_dmall_loader assets_dmall assets_dmall_feature assets_dmall_interface) + target_link_libraries(bongocat-all PRIVATE assets_more_ms_agent_loader assets_more_ms_agent assets_more_ms_agent_feature + assets_more_ms_agent_interface) + target_link_libraries(bongocat-all PRIVATE assets_pkmn_loader assets_pkmn assets_pkmn_feature assets_pkmn_interface) + # TODO: include pmd in -all when ready (experimental) + target_link_libraries( + bongocat-all + PRIVATE # only include pmd for testing + $<$:assets_pmd_loader + assets_pmd + assets_pmd_feature + assets_pmd_interface + assets_custom_loader> + $<$:assets_pmd_loader + assets_pmd + assets_pmd_feature + assets_pmd_interface + assets_custom_loader> + $<$:assets_pmd_loader + assets_pmd + assets_pmd_feature + assets_pmd_interface + assets_custom_loader>) + # target_link_libraries(bongocat-all PRIVATE assets_pmd_loader assets_pmd assets_pmd_feature assets_pmd_interface assets_custom_loader) + target_link_libraries(bongocat-all PRIVATE assets_misc_loader assets_misc assets_misc_feature assets_misc_interface assets_custom_loader) + target_link_libraries(bongocat-all PRIVATE assets_custom_sprite_sheet_feature assets_custom_loader) + target_link_libraries(bongocat-all PRIVATE bongocat_base bongocat_options bongocat_libs) + + add_executable(bongocat-neko) + target_link_libraries(bongocat-neko PRIVATE assets_bongocat_loader assets_bongocat assets_bongocat_feature assets_bongocat_interface) + target_link_libraries(bongocat-neko PRIVATE assets_misc_loader assets_misc assets_misc_feature assets_misc_interface assets_custom_loader) + target_link_libraries(bongocat-neko PRIVATE bongocat_base bongocat_options bongocat_libs) + + # @NOTE(assets): 1.2. add exec for multi versions endif() - +# Formatting +include(cmake/CPM.cmake) +cpmaddpackage( + NAME + Format.cmake + VERSION + 1.8.3 + GITHUB_REPOSITORY + TheLartians/Format.cmake + OPTIONS + # set to yes skip cmake formatting + "FORMAT_SKIP_CMAKE NO" + # set to yes skip clang formatting + "FORMAT_SKIP_CLANG NO" + # path to exclude (optional, supports regular expressions) + "CMAKE_FORMAT_EXCLUDE cmake/CPM.cmake" + # extra arguments for cmake_format (optional) + "CMAKE_FORMAT_EXTRA_ARGS -c ${PROJECT_SOURCE_DIR}/cmake-format.yaml" + # path to exclude (optional, supports regular expressions) + "CMAKE_FORMAT_EXCLUDE lib|assets|protocols|docs|examples|nix") # Install include(GNUInstallDirs) -if (FEATURE_MULTI_VERSIONS) - # sudo cmake --install ./cmake-build-release-all-assets-multi - install(TARGETS bongocat bongocat-dm-classic bongocat-dm bongocat-ms-agent bongocat-pkmn bongocat-all COMPONENT bongocat-targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +if(FEATURE_MULTI_VERSIONS) + # sudo cmake --install ./cmake-build-release-all-assets-multi + install( + TARGETS bongocat bongocat-dm-classic bongocat-dm bongocat-ms-agent bongocat-pkmn bongocat-all + COMPONENT bongocat-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) else() - install(TARGETS bongocat RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(TARGETS bongocat RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() +install( + FILES bongocat.conf.example + DESTINATION ${CMAKE_INSTALL_DATADIR}/bongocat + RENAME bongocat.conf.example) +install( + PROGRAMS scripts/find_input_devices.sh + DESTINATION ${CMAKE_INSTALL_BINDIR} + RENAME bongocat-find-devices) +if(FEATURE_MULTI_VERSIONS) + # more configs + install(FILES examples/clippy.bongocat.conf examples/digimon.bongocat.conf examples/pokemon.bongocat.conf + DESTINATION ${CMAKE_INSTALL_DATADIR}/bongocat) endif() -install(FILES bongocat.conf.example DESTINATION ${CMAKE_INSTALL_DATADIR}/bongocat RENAME bongocat.conf.example) -install(PROGRAMS scripts/find_input_devices.sh DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME bongocat-find-devices) -if (FEATURE_MULTI_VERSIONS) - # more configs - install(FILES examples/clippy.bongocat.conf examples/digimon.bongocat.conf examples/pokemon.bongocat.conf DESTINATION ${CMAKE_INSTALL_DATADIR}/bongocat) -endif () # man pages find_program(PANDOC pandoc) -if (PANDOC) - # List all binaries - set(BINARY_TARGETS - bongocat - bongocat-dm-classic - bongocat-dm - bongocat-ms-agent - bongocat-pkmn - bongocat-all - ) - - set(ALL_MAN_OUTPUTS) - foreach(BIN ${BINARY_TARGETS}) - set(MD_FILE_1 ${PROJECT_SOURCE_DIR}/docs/${BIN}.md) - set(MAN_OUTPUT_1 ${CMAKE_CURRENT_BINARY_DIR}/${BIN}.1) - # man exec - add_custom_command( - OUTPUT ${MAN_OUTPUT_1} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs - COMMAND ${PANDOC} --lua-filter=${PROJECT_SOURCE_DIR}/docs/include.lua -s -t man -o ${MAN_OUTPUT_1} ${MD_FILE_1} - DEPENDS ${MD_FILE_1} - COMMENT "Generating man page for ${BIN} using Pandoc" - VERBATIM - ) - - set(MD_FILE_5 ${PROJECT_SOURCE_DIR}/docs/${BIN}.conf.md) - set(MAN_OUTPUT_5 ${CMAKE_CURRENT_BINARY_DIR}/${BIN}.5) - # man conf - add_custom_command( - OUTPUT ${MAN_OUTPUT_5} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs - COMMAND ${PANDOC} --lua-filter=${PROJECT_SOURCE_DIR}/docs/include.lua -s -t man -o ${MAN_OUTPUT_5} ${PROJECT_SOURCE_DIR}/docs/begin.base.bongocat.conf.md ${MD_FILE_5} ${PROJECT_SOURCE_DIR}/docs/end.base.bongocat.conf.md - DEPENDS ${MD_FILE_5} ${PROJECT_SOURCE_DIR}/docs/begin.base.bongocat.conf.md ${MD_FILE_5} ${PROJECT_SOURCE_DIR}/docs/end.base.bongocat.conf.md - COMMENT "Generating man page for ${BIN}.conf using Pandoc" - VERBATIM - ) - - # Target to build manpages - add_custom_target(manpages-${BIN} ALL DEPENDS ${MAN_OUTPUT_1} ${MAN_OUTPUT_5}) - list(APPEND ALL_MAN_OUTPUTS ${MAN_OUTPUT_1} ${MAN_OUTPUT_5}) - - # Install manpage - install( - FILES ${MAN_OUTPUT_1} - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 - ) - install( - FILES ${MAN_OUTPUT_5} - DESTINATION ${CMAKE_INSTALL_MANDIR}/man5 - ) - endforeach() - - # find-devices - set(FD_MD_FILE_1 ${PROJECT_SOURCE_DIR}/docs/bongocat-find-devices.md) - set(FD_MAN_OUTPUT_1 ${CMAKE_CURRENT_BINARY_DIR}/bongocat-find-devices.1) +if(PANDOC) + # List all binaries + set(BINARY_TARGETS bongocat bongocat-dm-classic bongocat-dm bongocat-ms-agent bongocat-pkmn bongocat-all) + + set(ALL_MAN_OUTPUTS) + foreach(BIN ${BINARY_TARGETS}) + set(MD_FILE_1 ${PROJECT_SOURCE_DIR}/docs/${BIN}.md) + set(MAN_OUTPUT_1 ${CMAKE_CURRENT_BINARY_DIR}/${BIN}.1) + # man exec add_custom_command( - OUTPUT ${FD_MAN_OUTPUT_1} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs - COMMAND ${PANDOC} --lua-filter=${PROJECT_SOURCE_DIR}/docs/include.lua -s -t man -o ${FD_MAN_OUTPUT_1} ${FD_MD_FILE_1} - DEPENDS ${FD_MD_FILE_1} - COMMENT "Generating man page for bongocat-find-devices using Pandoc" - VERBATIM - ) - add_custom_target(manpages-bongocat-find-devices ALL DEPENDS ${FD_MAN_OUTPUT_1}) - list(APPEND ALL_MAN_OUTPUTS ${FD_MAN_OUTPUT_1}) - install( - FILES ${FD_MAN_OUTPUT_1} - DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 - ) - - add_custom_target(manpages ALL DEPENDS ${ALL_MAN_OUTPUTS}) + OUTPUT ${MAN_OUTPUT_1} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs + COMMAND ${PANDOC} --lua-filter=${PROJECT_SOURCE_DIR}/docs/include.lua -s -t man -o ${MAN_OUTPUT_1} ${MD_FILE_1} + DEPENDS ${MD_FILE_1} + COMMENT "Generating man page for ${BIN} using Pandoc" + VERBATIM) + + set(MD_FILE_5 ${PROJECT_SOURCE_DIR}/docs/${BIN}.conf.md) + set(MAN_OUTPUT_5 ${CMAKE_CURRENT_BINARY_DIR}/${BIN}.5) + # man conf + add_custom_command( + OUTPUT ${MAN_OUTPUT_5} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs + COMMAND ${PANDOC} --lua-filter=${PROJECT_SOURCE_DIR}/docs/include.lua -s -t man -o ${MAN_OUTPUT_5} + ${PROJECT_SOURCE_DIR}/docs/begin.base.bongocat.conf.md ${MD_FILE_5} ${PROJECT_SOURCE_DIR}/docs/end.base.bongocat.conf.md + DEPENDS ${MD_FILE_5} ${PROJECT_SOURCE_DIR}/docs/begin.base.bongocat.conf.md ${MD_FILE_5} + ${PROJECT_SOURCE_DIR}/docs/end.base.bongocat.conf.md + COMMENT "Generating man page for ${BIN}.conf using Pandoc" + VERBATIM) + + # Target to build manpages + add_custom_target(manpages-${BIN} ALL DEPENDS ${MAN_OUTPUT_1} ${MAN_OUTPUT_5}) + list(APPEND ALL_MAN_OUTPUTS ${MAN_OUTPUT_1} ${MAN_OUTPUT_5}) + + # Install manpage + install(FILES ${MAN_OUTPUT_1} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) + install(FILES ${MAN_OUTPUT_5} DESTINATION ${CMAKE_INSTALL_MANDIR}/man5) + endforeach() + + # find-devices + set(FD_MD_FILE_1 ${PROJECT_SOURCE_DIR}/docs/bongocat-find-devices.md) + set(FD_MAN_OUTPUT_1 ${CMAKE_CURRENT_BINARY_DIR}/bongocat-find-devices.1) + add_custom_command( + OUTPUT ${FD_MAN_OUTPUT_1} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/docs + COMMAND ${PANDOC} --lua-filter=${PROJECT_SOURCE_DIR}/docs/include.lua -s -t man -o ${FD_MAN_OUTPUT_1} ${FD_MD_FILE_1} + DEPENDS ${FD_MD_FILE_1} + COMMENT "Generating man page for bongocat-find-devices using Pandoc" + VERBATIM) + add_custom_target(manpages-bongocat-find-devices ALL DEPENDS ${FD_MAN_OUTPUT_1}) + list(APPEND ALL_MAN_OUTPUTS ${FD_MAN_OUTPUT_1}) + install(FILES ${FD_MAN_OUTPUT_1} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) + + add_custom_target(manpages ALL DEPENDS ${ALL_MAN_OUTPUTS}) endif() # Package +cpmaddpackage("gh:TheLartians/PackageProject.cmake@1.13.0") set(CPACK_PACKAGE_NAME "bongocat") set(CPACK_PACKAGE_VERSION "3.6.0") set(CPACK_PACKAGE_CONTACT "hircreacc@gmail.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A delightful Wayland overlay that displays an animated V-Pet reacting to your keyboard input! ") set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") set(CPACK_GENERATOR "TGZ;ZIP") -#set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6, libstdc++6, libgcc-s1, libwayland-client0, libffi8") -include(CPack) +packageproject( + # the name of the target to export + NAME + ${PROJECT_NAME} + # the version of the target to export + VERSION + ${PROJECT_VERSION} + # a temporary directory to create the config files + BINARY_DIR + ${PROJECT_BINARY_DIR} + # should match the target's INSTALL_INTERFACE include directory + INCLUDE_DESTINATION + include/${PROJECT_NAME}-${PROJECT_VERSION} + # (optional) define the project's version compatibility, defaults to `AnyNewerVersion` supported values: + # `AnyNewerVersion|SameMajorVersion|SameMinorVersion|ExactVersion` + COMPATIBILITY + SameMajorVersion + # (optional) option to generate CPack variables + CPACK + YES) # If MSVC is being used, and ASAN is enabled, we need to set the debugger environment so that it behaves well with MSVC's debugger, and we -# can run the target from visual studio -#if(MSVC) -# get_all_installable_targets(all_targets) -# message("all_targets=${all_targets}") -# set_target_properties(${all_targets} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$(VC_ExecutablePath_x64);%PATH%") -#endif() +# can run the target from visual studio if(MSVC) get_all_installable_targets(all_targets) message("all_targets=${all_targets}") +# set_target_properties(${all_targets} PROPERTIES VS_DEBUGGER_ENVIRONMENT "PATH=$(VC_ExecutablePath_x64);%PATH%") endif() diff --git a/Makefile b/Makefile index 49b326cd..11fd5f84 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,14 @@ ONLY_BONGOCAT ?= 1 ifeq ($(ONLY_BONGOCAT),1) FEATURES = -DFEATURE_BONGOCAT_EMBEDDED_ASSETS else - FEATURES = -DFEATURE_BONGOCAT_EMBEDDED_ASSETS -DFEATURE_ENABLE_DM_EMBEDDED_ASSETS -DFEATURE_MS_AGENT_EMBEDDED_ASSETS + FEATURES = -DFEATURE_BONGOCAT_EMBEDDED_ASSETS -DFEATURE_ENABLE_DM_EMBEDDED_ASSETS -DFEATURE_MS_AGENT_EMBEDDED_ASSETS -DFEATURE_PKMN_EMBEDDED_ASSETS endif +# Target executable +TARGET = $(BUILDDIR)/bongocat + +.PHONY: all format format-check lint + # Default target default: build all: build @@ -70,30 +75,24 @@ ALL_PROJECT_FILES = $(PROJECT_SOURCES) $(PROJECT_HEADERS) # Format all project source files format: - @echo "Formatting source files..." - @clang-format -i $(ALL_PROJECT_FILES) - @echo "Done! Formatted $(words $(ALL_PROJECT_FILES)) files." + cmake --build build --target fix-format # Check if formatting is correct (for CI) format-check: - @echo "Checking code formatting..." - @clang-format --dry-run --Werror $(ALL_PROJECT_FILES) - @echo "All files are properly formatted." + cmake --build build --target check-format # Static analysis with clang-tidy (uses .clang-tidy config) lint: protocols @echo "Running static analysis..." - @clang-tidy $(PROJECT_SOURCES) -- $(CFLAGS) 2>/dev/null || true + @clang-tidy $(PROJECT_SOURCES) -- $(CFLAGS) $(CXXFLAGS) 2>/dev/null || true @echo "Static analysis complete." # Alias for lint analyze: lint -# Generate compile_commands.json for IDE support (requires bear) +# Generate compile_commands.json for IDE support, or just use cmake and presets # Run: make compiledb compiledb: clean - @echo "Generating compile_commands.json..." - @bear -- $(MAKE) all 2>/dev/null || (echo "Note: 'bear' not installed. Install with: sudo pacman -S bear" && false) - @echo "compile_commands.json generated!" + cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .PHONY: compiledb \ No newline at end of file diff --git a/Makefile.old b/Makefile.old index 22c7e666..c561d63d 100644 --- a/Makefile.old +++ b/Makefile.old @@ -16,14 +16,14 @@ PROTOCOLDIR = protocols WAYLAND_PROTOCOLS_DIR ?= /usr/share/wayland-protocols # Base flags -BASE_CFLAGS = -std=c2x -I$(INCDIR) -I$(SRCDIR) -isystem lib -isystem protocols # -fembed-dir=assets/ +BASE_CFLAGS = -std=c2x -I$(INCDIR) -I$(SRCDIR) -isystem lib -isystem protocols BASE_CFLAGS += -Wall -Wextra -Wpedantic -Wformat=2 -Wstrict-prototypes BASE_CFLAGS += -Wmissing-prototypes -Wold-style-definition -Wredundant-decls BASE_CFLAGS += -Wnested-externs -Wmissing-include-dirs -Wlogical-op BASE_CFLAGS += -Wjump-misses-init -Wdouble-promotion -Wshadow -BASE_CFLAGS += -fstack-protector-strong +BASE_CFLAGS += -fstack-protector-strong -D_FORTIFY_SOURCE=2 -BASE_CXXFLAGS = -std=c++23 -I$(INCDIR) -I$(SRCDIR) -isystem lib -isystem protocols # -fembed-dir=assets/ +BASE_CXXFLAGS = -std=c++23 -I$(INCDIR) -I$(SRCDIR) -isystem lib -isystem protocols BASE_CXXFLAGS += -Wall -Wextra -Wpedantic -Wformat=2 BASE_CXXFLAGS += -Wredundant-decls BASE_CXXFLAGS += -Wmissing-include-dirs -Wlogical-op @@ -34,8 +34,8 @@ ifeq ($(ONLY_BONGOCAT),1) BASE_CFLAGS += -DFEATURE_BONGOCAT_EMBEDDED_ASSETS BASE_CXXFLAGS += -DFEATURE_BONGOCAT_EMBEDDED_ASSETS else - BASE_CFLAGS += -DFEATURE_BONGOCAT_EMBEDDED_ASSETS -DFEATURE_ENABLE_DM_EMBEDDED_ASSETS -DFEATURE_MS_AGENT_EMBEDDED_ASSETS - BASE_CXXFLAGS += -DFEATURE_BONGOCAT_EMBEDDED_ASSETS -DFEATURE_ENABLE_DM_EMBEDDED_ASSETS -DFEATURE_MS_AGENT_EMBEDDED_ASSETS + BASE_CFLAGS += -DFEATURE_BONGOCAT_EMBEDDED_ASSETS -DFEATURE_ENABLE_DM_EMBEDDED_ASSETS -DFEATURE_MS_AGENT_EMBEDDED_ASSETS -DFEATURE_PKMN_EMBEDDED_ASSETS + BASE_CXXFLAGS += -DFEATURE_BONGOCAT_EMBEDDED_ASSETS -DFEATURE_ENABLE_DM_EMBEDDED_ASSETS -DFEATURE_MS_AGENT_EMBEDDED_ASSETS -DFEATURE_PKMN_EMBEDDED_ASSETS BASE_CFLAGS += -I$(INCDIR)/embedded_assets/min_dm -I$(INCDIR)/image_loader/min_dm -I$(SRCDIR)/embedded_assets/min_dm/include BASE_CXXFLAGS += -I$(INCDIR)/embedded_assets/min_dm -I$(INCDIR)/image_loader/min_dm -I$(SRCDIR)/embedded_assets/min_dm/include endif @@ -76,6 +76,7 @@ CXX_SRC = $(SRCDIR)/image_loader/load_images.cpp \ $(SRCDIR)/platform/wayland.cpp \ $(SRCDIR)/platform/wayland_callbacks.cpp \ $(SRCDIR)/platform/wayland_hyprland.cpp \ + $(SRCDIR)/platform/wayland_setups.cpp \ $(SRCDIR)/platform/wayland_sway.cpp \ $(SRCDIR)/graphics/bar.cpp \ $(SRCDIR)/graphics/animation.cpp \ @@ -83,6 +84,7 @@ CXX_SRC = $(SRCDIR)/image_loader/load_images.cpp \ $(SRCDIR)/graphics/drawing_images.cpp \ $(SRCDIR)/config/config_watcher.cpp \ $(SRCDIR)/config/config.cpp + C_SRC = $(SRCDIR)/image_loader/stb_image.c ifeq ($(ONLY_BONGOCAT),1) C_SRC += $(SRCDIR)/embedded_assets/bongocat/bongocat_images.c @@ -110,7 +112,7 @@ PROTOCOL_OBJECTS = $(C_PROTOCOL_SRC:$(PROTOCOLDIR)/%.c=$(OBJDIR)/%.o) # Target executable TARGET = $(BUILDDIR)/bongocat -.PHONY: all clean protocols +.PHONY: all clean protocols embed-assets format format-check lint all: protocols $(TARGET) @@ -128,22 +130,31 @@ $(OBJDIR): mkdir -p $(OBJDIR)/embedded_assets/dm20 mkdir -p $(OBJDIR)/embedded_assets/dmc mkdir -p $(OBJDIR)/embedded_assets/dmx + mkdir -p $(OBJDIR)/embedded_assets/pen + mkdir -p $(OBJDIR)/embedded_assets/pen20 mkdir -p $(OBJDIR)/embedded_assets/dmall mkdir -p $(OBJDIR)/embedded_assets/min_dm mkdir -p $(OBJDIR)/embedded_assets/ms_agent + mkdir -p $(OBJDIR)/embedded_assets/misc mkdir -p $(OBJDIR)/embedded_assets/pkmn + mkdir -p $(OBJDIR)/embedded_assets/pmd mkdir -p $(OBJDIR)/graphics mkdir -p $(OBJDIR)/image_loader mkdir -p $(OBJDIR)/image_loader/base_dm mkdir -p $(OBJDIR)/image_loader/bongocat + mkdir -p $(OBJDIR)/image_loader/custom mkdir -p $(OBJDIR)/image_loader/dm mkdir -p $(OBJDIR)/image_loader/dm20 mkdir -p $(OBJDIR)/image_loader/dmc mkdir -p $(OBJDIR)/image_loader/dmx + mkdir -p $(OBJDIR)/image_loader/pen + mkdir -p $(OBJDIR)/image_loader/pen20 mkdir -p $(OBJDIR)/image_loader/dmall mkdir -p $(OBJDIR)/image_loader/min_dm mkdir -p $(OBJDIR)/image_loader/ms_agent + mkdir -p $(OBJDIR)/image_loader/misc mkdir -p $(OBJDIR)/image_loader/pkmn + mkdir -p $(OBJDIR)/image_loader/pmd mkdir -p $(OBJDIR)/platform mkdir -p $(OBJDIR)/utils mkdir -p $(BUILDDIR) @@ -186,7 +197,7 @@ release: install: $(TARGET) install -D $(TARGET) $(DESTDIR)/usr/local/bin/bongocat - install -D bongocat.conf $(DESTDIR)/usr/local/share/bongocat/bongocat.conf.example + install -D bongocat.conf.example $(DESTDIR)/usr/local/share/bongocat/bongocat.conf.example install -D scripts/find_input_devices.sh $(DESTDIR)/usr/local/bin/bongocat-find-devices uninstall: @@ -196,7 +207,7 @@ uninstall: # Static analysis analyze: - clang-tidy $(SOURCES) -- $(CFLAGS) + clang-tidy $(SOURCES) -- $(CFLAGS) $(CXXFLAGS) # Memory check (requires valgrind) memcheck: debug diff --git a/cmake-format.yaml b/cmake-format.yaml new file mode 100644 index 00000000..c31ccc87 --- /dev/null +++ b/cmake-format.yaml @@ -0,0 +1,219 @@ +_help_parse: Options affecting listfile parsing +parse: + _help_additional_commands: + - Specify structure for custom cmake functions + additional_commands: + foo: + flags: + - BAR + - BAZ + kwargs: + HEADERS: '*' + SOURCES: '*' + DEPENDS: '*' + _help_vartags: + - Specify variable tags. + vartags: [] + _help_proptags: + - Specify property tags. + proptags: [] +_help_format: Options affecting formatting. +format: + _help_line_width: + - How wide to allow formatted cmake files + line_width: 140 + _help_tab_size: + - How many spaces to tab for indent + tab_size: 2 + _help_max_subgroups_hwrap: + - If an argument group contains more than this many sub-groups + - (parg or kwarg groups) then force it to a vertical layout. + max_subgroups_hwrap: 2 + _help_max_pargs_hwrap: + - If a positional argument group contains more than this many + - arguments, then force it to a vertical layout. + max_pargs_hwrap: 6 + _help_max_rows_cmdline: + - If a cmdline positional group consumes more than this many + - lines without nesting, then invalidate the layout (and nest) + max_rows_cmdline: 2 + _help_separate_ctrl_name_with_space: + - If true, separate flow control names from their parentheses + - with a space + separate_ctrl_name_with_space: false + _help_separate_fn_name_with_space: + - If true, separate function names from parentheses with a + - space + separate_fn_name_with_space: false + _help_dangle_parens: + - If a statement is wrapped to more than one line, than dangle + - the closing parenthesis on its own line. + dangle_parens: false + _help_dangle_align: + - If the trailing parenthesis must be 'dangled' on its on + - 'line, then align it to this reference: `prefix`: the start' + - 'of the statement, `prefix-indent`: the start of the' + - 'statement, plus one indentation level, `child`: align to' + - the column of the arguments + dangle_align: prefix + _help_min_prefix_chars: + - If the statement spelling length (including space and + - parenthesis) is smaller than this amount, then force reject + - nested layouts. + min_prefix_chars: 4 + _help_max_prefix_chars: + - If the statement spelling length (including space and + - parenthesis) is larger than the tab width by more than this + - amount, then force reject un-nested layouts. + max_prefix_chars: 10 + _help_max_lines_hwrap: + - If a candidate layout is wrapped horizontally but it exceeds + - this many lines, then reject the layout. + max_lines_hwrap: 2 + _help_line_ending: + - What style line endings to use in the output. + line_ending: unix + _help_command_case: + - Format command names consistently as 'lower' or 'upper' case + command_case: canonical + _help_keyword_case: + - Format keywords consistently as 'lower' or 'upper' case + keyword_case: unchanged + _help_always_wrap: + - A list of command names which should always be wrapped + always_wrap: [] + _help_enable_sort: + - If true, the argument lists which are known to be sortable + - will be sorted lexicographicall + enable_sort: true + _help_autosort: + - If true, the parsers may infer whether or not an argument + - list is sortable (without annotation). + autosort: false + _help_require_valid_layout: + - By default, if cmake-format cannot successfully fit + - everything into the desired linewidth it will apply the + - last, most agressive attempt that it made. If this flag is + - True, however, cmake-format will print error, exit with non- + - zero status code, and write-out nothing + require_valid_layout: false + _help_layout_passes: + - A dictionary mapping layout nodes to a list of wrap + - decisions. See the documentation for more information. + layout_passes: {} +_help_markup: Options affecting comment reflow and formatting. +markup: + _help_bullet_char: + - What character to use for bulleted lists + bullet_char: '*' + _help_enum_char: + - What character to use as punctuation after numerals in an + - enumerated list + enum_char: . + _help_first_comment_is_literal: + - If comment markup is enabled, don't reflow the first comment + - block in each listfile. Use this to preserve formatting of + - your copyright/license statements. + first_comment_is_literal: false + _help_literal_comment_pattern: + - If comment markup is enabled, don't reflow any comment block + - which matches this (regex) pattern. Default is `None` + - (disabled). + literal_comment_pattern: null + _help_fence_pattern: + - Regular expression to match preformat fences in comments + - default= ``r'^\s*([`~]{3}[`~]*)(.*)$'`` + fence_pattern: ^\s*([`~]{3}[`~]*)(.*)$ + _help_ruler_pattern: + - Regular expression to match rulers in comments default= + - '``r''^\s*[^\w\s]{3}.*[^\w\s]{3}$''``' + ruler_pattern: ^\s*[^\w\s]{3}.*[^\w\s]{3}$ + _help_explicit_trailing_pattern: + - If a comment line matches starts with this pattern then it + - is explicitly a trailing comment for the preceeding + - argument. Default is '#<' + explicit_trailing_pattern: '#<' + _help_hashruler_min_length: + - If a comment line starts with at least this many consecutive + - hash characters, then don't lstrip() them off. This allows + - for lazy hash rulers where the first hash char is not + - separated by space + hashruler_min_length: 10 + _help_canonicalize_hashrulers: + - If true, then insert a space between the first hash char and + - remaining hash chars in a hash ruler, and normalize its + - length to fill the column + canonicalize_hashrulers: true + _help_enable_markup: + - enable comment markup parsing and reflow + enable_markup: true +_help_lint: Options affecting the linter +lint: + _help_disabled_codes: + - a list of lint codes to disable + disabled_codes: [] + _help_function_pattern: + - regular expression pattern describing valid function names + function_pattern: '[0-9a-z_]+' + _help_macro_pattern: + - regular expression pattern describing valid macro names + macro_pattern: '[0-9A-Z_]+' + _help_global_var_pattern: + - regular expression pattern describing valid names for + - variables with global scope + global_var_pattern: '[0-9A-Z][0-9A-Z_]+' + _help_internal_var_pattern: + - regular expression pattern describing valid names for + - variables with global scope (but internal semantic) + internal_var_pattern: _[0-9A-Z][0-9A-Z_]+ + _help_local_var_pattern: + - regular expression pattern describing valid names for + - variables with local scope + local_var_pattern: '[0-9a-z_]+' + _help_private_var_pattern: + - regular expression pattern describing valid names for + - privatedirectory variables + private_var_pattern: _[0-9a-z_]+ + _help_public_var_pattern: + - regular expression pattern describing valid names for + - publicdirectory variables + public_var_pattern: '[0-9A-Z][0-9A-Z_]+' + _help_keyword_pattern: + - regular expression pattern describing valid names for + - keywords used in functions or macros + keyword_pattern: '[0-9A-Z_]+' + _help_max_conditionals_custom_parser: + - In the heuristic for C0201, how many conditionals to match + - within a loop in before considering the loop a parser. + max_conditionals_custom_parser: 2 + _help_min_statement_spacing: + - Require at least this many newlines between statements + min_statement_spacing: 1 + _help_max_statement_spacing: + - Require no more than this many newlines between statements + max_statement_spacing: 1 + max_returns: 6 + max_branches: 12 + max_arguments: 5 + max_localvars: 15 + max_statements: 50 +_help_encode: Options affecting file encoding +encode: + _help_emit_byteorder_mark: + - If true, emit the unicode byte-order mark (BOM) at the start + - of the file + emit_byteorder_mark: false + _help_input_encoding: + - Specify the encoding of the input file. Defaults to utf-8 + input_encoding: utf-8 + _help_output_encoding: + - Specify the encoding of the output file. Defaults to utf-8. + - Note that cmake only claims to support utf-8 so be careful + - when using anything else + output_encoding: utf-8 +_help_misc: Miscellaneous configurations options. +misc: + _help_per_command: + - A dictionary containing any per-command configuration + - overrides. Currently only `command_case` is supported. + per_command: {} diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake new file mode 100644 index 00000000..84748734 --- /dev/null +++ b/cmake/CPM.cmake @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: MIT +# +# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors + +set(CPM_DOWNLOAD_VERSION 0.42.0) +set(CPM_HASH_SUM "2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a") + +if(CPM_SOURCE_CACHE) + set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +elseif(DEFINED ENV{CPM_SOURCE_CACHE}) + set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +else() + set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") +endif() + +# Expand relative path. This is important if the provided path contains a tilde (~) +get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) + +file(DOWNLOAD + https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake + ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} +) + +include(${CPM_DOWNLOAD_LOCATION}) diff --git a/include/embedded_assets/.clang-format b/include/embedded_assets/.clang-format new file mode 100644 index 00000000..15f9b6fc --- /dev/null +++ b/include/embedded_assets/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file diff --git a/include/image_loader/.clang-format b/include/image_loader/.clang-format new file mode 100644 index 00000000..15f9b6fc --- /dev/null +++ b/include/image_loader/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file diff --git a/lib/.clang-format b/lib/.clang-format new file mode 100644 index 00000000..15f9b6fc --- /dev/null +++ b/lib/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file diff --git a/protocols/.clang-format b/protocols/.clang-format new file mode 100644 index 00000000..15f9b6fc --- /dev/null +++ b/protocols/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file diff --git a/src/embedded_assets/.clang-format b/src/embedded_assets/.clang-format new file mode 100644 index 00000000..15f9b6fc --- /dev/null +++ b/src/embedded_assets/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file diff --git a/src/image_loader/.clang-format b/src/image_loader/.clang-format new file mode 100644 index 00000000..15f9b6fc --- /dev/null +++ b/src/image_loader/.clang-format @@ -0,0 +1,2 @@ +DisableFormat: true +SortIncludes: Never \ No newline at end of file From 81fdf020066bd8f55d03ebcc82b10e4f5824647e Mon Sep 17 00:00:00 2001 From: furudbat Date: Tue, 9 Dec 2025 10:28:07 +0100 Subject: [PATCH 12/18] chore: formatting --- .clang-format | 4 + cmake/CPM.cmake | 6 +- include/config/config.h | 879 +- include/config/config_watcher.h | 123 +- include/core/bongocat.h | 338 +- include/embedded_assets/bongocat/bongocat.h | 42 +- include/embedded_assets/bongocat/bongocat.hpp | 34 +- .../bongocat/bongocat_images.h | 2 +- .../embedded_assets/custom/custom_sprite.h | 381 +- include/embedded_assets/embedded_image.h | 51 +- include/embedded_assets/min_dm/min_dm.hpp | 176 +- .../embedded_assets/min_dm/min_dm_images.h | 2 +- .../embedded_assets/min_dm/min_dm_sprite.h | 2 +- include/embedded_assets/misc/misc.hpp | 73 +- include/embedded_assets/misc/misc_images.h | 2 +- include/embedded_assets/misc/misc_sprite.h | 10 +- include/embedded_assets/ms_agent/ms_agent.hpp | 199 +- .../ms_agent/ms_agent_images.h | 2 +- .../ms_agent/ms_agent_sprite.h | 139 +- include/graphics/animation.h | 56 +- include/graphics/animation_context.h | 86 +- include/graphics/animation_shared_memory.h | 509 +- include/graphics/drawing.h | 51 +- include/graphics/embedded_assets_dms.h | 171 +- include/graphics/embedded_assets_pkmn.h | 26 +- include/graphics/global_animation_session.h | 92 +- include/graphics/sprite_sheet.h | 1093 +- include/image_loader/base_dm/load_dm.h | 10 +- .../bongocat/load_images_bongocat.h | 13 +- include/image_loader/custom/load_custom.h | 85 +- include/image_loader/load_images.h | 174 +- .../image_loader/min_dm/load_images_min_dm.h | 12 +- include/image_loader/misc/load_images_misc.h | 14 +- .../ms_agent/load_images_ms_agent.h | 23 +- include/platform/global_wayland_session.h | 312 +- include/platform/input.h | 24 +- include/platform/input_context.h | 281 +- include/platform/input_shared_memory.h | 118 +- include/platform/update.h | 22 +- include/platform/update_context.h | 109 +- include/platform/update_shared_memory.h | 55 +- include/platform/wayland-protocols.hpp | 42 +- include/platform/wayland.h | 41 +- include/platform/wayland_callbacks.h | 288 +- include/platform/wayland_context.h | 357 +- include/platform/wayland_setups.h | 19 +- include/platform/wayland_shared_memory.h | 251 +- include/utils/error.h | 148 +- include/utils/memory.h | 773 +- include/utils/random.h | 24 +- include/utils/system_memory.h | 2244 ++-- include/utils/time.h | 24 +- src/CMakeLists.txt | 135 +- src/config/config.cpp | 3534 +++--- src/config/config_watcher.cpp | 515 +- src/core/main.cpp | 1915 ++-- .../bongocat/bongocat_get_sprite_sheet.cpp | 31 +- .../bongocat/bongocat_images.c | 3 +- .../min_dm/min_dm_get_sprite_sheet.cpp | 59 +- src/embedded_assets/min_dm/min_dm_images.c | 1 + .../misc/misc_get_sprite_sheet.cpp | 38 +- src/embedded_assets/misc/misc_images.c | 2 +- .../ms_agent/embedded_assets_ms_agent.cpp | 224 +- .../ms_agent/ms_agent_images.c | 8 +- src/graphics/animation.cpp | 9752 +++++++++-------- src/graphics/animation_init.cpp | 1177 +- src/graphics/bar.cpp | 1650 +-- src/graphics/bar.h | 20 +- src/graphics/drawing_images.cpp | 540 +- src/image_loader/CMakeLists.txt | 74 +- src/image_loader/base_dm/CMakeLists.txt | 10 +- src/image_loader/base_dm/load_dm.cpp | 289 +- src/image_loader/bongocat/CMakeLists.txt | 14 +- .../bongocat/load_images_bongocat.cpp | 258 +- src/image_loader/custom/CMakeLists.txt | 10 +- src/image_loader/custom/load_custom.cpp | 526 +- src/image_loader/dm/CMakeLists.txt | 14 +- src/image_loader/dm20/CMakeLists.txt | 14 +- src/image_loader/dmall/CMakeLists.txt | 14 +- src/image_loader/dmc/CMakeLists.txt | 14 +- src/image_loader/dmx/CMakeLists.txt | 14 +- src/image_loader/load_images.cpp | 467 +- src/image_loader/load_images_hybrid.cpp | 259 +- src/image_loader/load_images_pngle.cpp | 156 +- src/image_loader/min_dm/CMakeLists.txt | 14 +- .../min_dm/load_images_min_dm.cpp | 41 +- .../min_dm/min_dm_load_sprite_sheet.cpp | 81 +- src/image_loader/misc/CMakeLists.txt | 14 +- src/image_loader/misc/load_images_misc.cpp | 39 +- .../misc/misc_load_sprite_sheet.cpp | 29 +- src/image_loader/ms_agent/CMakeLists.txt | 30 +- .../ms_agent/load_images_ms_agent.cpp | 468 +- src/image_loader/pen/CMakeLists.txt | 14 +- src/image_loader/pen20/CMakeLists.txt | 14 +- src/image_loader/pkmn/CMakeLists.txt | 14 +- src/image_loader/pmd/CMakeLists.txt | 14 +- src/platform/input.cpp | 2299 ++-- src/platform/update.cpp | 1247 +-- src/platform/wayland.cpp | 940 +- src/platform/wayland_callbacks.cpp | 1690 ++- src/platform/wayland_hyprland.cpp | 161 +- src/platform/wayland_hyprland.h | 14 +- src/platform/wayland_setups.cpp | 741 +- src/platform/wayland_sway.cpp | 46 +- src/platform/wayland_sway.h | 2 +- src/utils/error.cpp | 192 +- src/utils/memory.cpp | 540 +- src/utils/random.cpp | 89 +- src/utils/system_memory.cpp | 105 +- src/utils/time.cpp | 39 +- 110 files changed, 20768 insertions(+), 19858 deletions(-) diff --git a/.clang-format b/.clang-format index 49341704..edcd12be 100644 --- a/.clang-format +++ b/.clang-format @@ -68,6 +68,10 @@ BreakBeforeTernaryOperators: true BreakStringLiterals: true NamespaceIndentation: Inner FixNamespaceComments: true +BreakConstructorInitializers: BeforeComma +ConstructorInitializerAllOnOneLineOrOnePerLine: true +AllowAllConstructorInitializersOnNextLine: false +IndentWrappedFunctionNames: false # Spaces SpaceAfterCStyleCast: false diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 84748734..fcc9c8ae 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -16,9 +16,7 @@ endif() # Expand relative path. This is important if the provided path contains a tilde (~) get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE) -file(DOWNLOAD - https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake - ${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM} -) +file(DOWNLOAD https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake ${CPM_DOWNLOAD_LOCATION} + EXPECTED_HASH SHA256=${CPM_HASH_SUM}) include(${CPM_DOWNLOAD_LOCATION}) diff --git a/include/config/config.h b/include/config/config.h index 5934118f..a14bcd22 100644 --- a/include/config/config.h +++ b/include/config/config.h @@ -1,465 +1,476 @@ #ifndef BONGOCAT_CONFIG_H #define BONGOCAT_CONFIG_H -#include "embedded_assets/custom/custom_sprite.h" #include "core/bongocat.h" +#include "embedded_assets/custom/custom_sprite.h" #include "utils/error.h" -#include + #include +#include #include // POSIX feature test macro - must be before includes #define _POSIX_C_SOURCE 200809L namespace bongocat::config { - enum class overlay_position_t : uint8_t { - POSITION_TOP, - POSITION_BOTTOM, - /* - POSITION_TOP_LEFT, - POSITION_BOTTOM_LEFT, - POSITION_TOP_RIGHT, - POSITION_BOTTOM_RIGHT, - */ - }; - inline static constexpr const char* POSITION_TOP_STR = "top"; - inline static constexpr const char* POSITION_BOTTOM_STR = "bottom"; - - enum class layer_type_t : int8_t { - LAYER_BACKGROUND = 0, - LAYER_BOTTOM = 1, - LAYER_TOP = 2, - LAYER_OVERLAY = 3, - }; - inline static constexpr const char* LAYER_BACKGROUND_STR = "background"; - inline static constexpr const char* LAYER_BOTTOM_STR = "bottom"; - inline static constexpr const char* LAYER_TOP_STR = "top"; - inline static constexpr const char* LAYER_OVERLAY_STR = "overlay"; - - enum class align_type_t : int8_t { - ALIGN_CENTER = 0, - ALIGN_LEFT = -1, - ALIGN_RIGHT = 1, - }; - inline static constexpr const char* ALIGN_CENTER_STR = "center"; - inline static constexpr const char* ALIGN_LEFT_STR = "left"; - inline static constexpr const char* ALIGN_RIGHT_STR = "right"; +enum class overlay_position_t : uint8_t { + POSITION_TOP, + POSITION_BOTTOM, + /* + POSITION_TOP_LEFT, + POSITION_BOTTOM_LEFT, + POSITION_TOP_RIGHT, + POSITION_BOTTOM_RIGHT, + */ +}; +inline static constexpr const char *POSITION_TOP_STR = "top"; +inline static constexpr const char *POSITION_BOTTOM_STR = "bottom"; + +enum class layer_type_t : int8_t { + LAYER_BACKGROUND = 0, + LAYER_BOTTOM = 1, + LAYER_TOP = 2, + LAYER_OVERLAY = 3, +}; +inline static constexpr const char *LAYER_BACKGROUND_STR = "background"; +inline static constexpr const char *LAYER_BOTTOM_STR = "bottom"; +inline static constexpr const char *LAYER_TOP_STR = "top"; +inline static constexpr const char *LAYER_OVERLAY_STR = "overlay"; + +enum class align_type_t : int8_t { + ALIGN_CENTER = 0, + ALIGN_LEFT = -1, + ALIGN_RIGHT = 1, +}; +inline static constexpr const char *ALIGN_CENTER_STR = "center"; +inline static constexpr const char *ALIGN_LEFT_STR = "left"; +inline static constexpr const char *ALIGN_RIGHT_STR = "right"; // ============================================================================= // CONFIGURATION FUNCTIONS // ============================================================================= - struct config_t; - void config_free_keyboard_devices(config_t& config); - void config_copy_keyboard_devices_from(config_t& config, const config_t& other); - void cleanup(config_t& config); +struct config_t; +void config_free_keyboard_devices(config_t& config); +void config_copy_keyboard_devices_from(config_t& config, const config_t& other); +void cleanup(config_t& config); // ============================================================================= // CONFIGURATION TYPES // ============================================================================= - struct config_time_t { - int hour{0}; - int min{0}; - }; - enum class config_animation_sprite_sheet_layout_t : uint8_t { - None, - Bongocat, - Dm, - MsAgent, - Pkmn, - Custom, - }; - enum class config_animation_dm_set_t : uint8_t { - None, - min_dm, - dm, - dm20, - dmx, - pen, - pen20, - dmc, - dmall, - }; - enum class config_animation_custom_set_t : uint8_t { - None, - misc, - pmd, - custom, - }; - - struct config_t { - char *output_name{nullptr}; - char *keyboard_devices[input::MAX_INPUT_DEVICES]{}; - int32_t num_keyboard_devices{0}; - int32_t cat_x_offset{0}; - int32_t cat_y_offset{0}; - int32_t cat_height{0}; - int32_t overlay_height{0}; - int32_t idle_frame{0}; - int32_t keypress_duration_ms{0}; - int32_t test_animation_duration_ms{0}; - int32_t test_animation_interval_sec{0}; - int32_t animation_speed_ms{0}; - int32_t fps{0}; - int32_t overlay_opacity{0}; - int32_t mirror_x{0}; // reflect across Y axis (horizontal flip) - int32_t mirror_y{0}; // reflect across X axis (vertical flip) - int32_t enable_antialiasing{0}; // enable bilinear interpolation for smooth scaling - int32_t enable_debug{0}; - layer_type_t layer{layer_type_t::LAYER_TOP}; - overlay_position_t overlay_position{overlay_position_t::POSITION_TOP}; - - int32_t animation_index{0}; - int32_t invert_color{0}; - int32_t padding_x{0}; - int32_t padding_y{0}; - - int32_t enable_scheduled_sleep{0}; - config_time_t sleep_begin; - config_time_t sleep_end; - int32_t idle_sleep_timeout_sec{0}; - - int32_t happy_kpm{0}; - - align_type_t cat_align{align_type_t::ALIGN_CENTER}; - - config_animation_sprite_sheet_layout_t animation_sprite_sheet_layout{config_animation_sprite_sheet_layout_t::None}; - config_animation_dm_set_t animation_dm_set{config_animation_dm_set_t::None}; - config_animation_custom_set_t animation_custom_set{config_animation_custom_set_t::None}; - int32_t idle_animation{0}; - int32_t input_fps{0}; - int32_t randomize_index{0}; - int32_t randomize_on_reload{0}; - - int32_t update_rate_ms{0}; - double cpu_threshold{0}; - double cpu_running_factor{0}; - - int32_t movement_radius{0}; - int32_t enable_movement_debug{0}; - int32_t movement_speed{0}; - double movement_wait_factor{0}; - - int32_t screen_width{0}; - - char *custom_sprite_sheet_filename{nullptr}; // must be png file - assets::custom_animation_settings_t custom_sprite_sheet_settings{}; - - int32_t enable_hand_mapping{0}; - - // for keep old index when reload config - bool _keep_old_animation_index{false}; - bool _strict{false}; - bool _custom{false}; // is custom sprite sheet - char *_animation_name{nullptr}; // original animation_anim from parsing config - char *_loaded_animation_fqname{nullptr}; - - - // Make Config movable and copyable - config_t() { - for (size_t i = 0; i < input::MAX_INPUT_DEVICES; ++i) { - keyboard_devices[i] = nullptr; - } - } - ~config_t() { - cleanup(*this); - } - - config_t(const config_t& other) - : cat_x_offset(other.cat_x_offset), - cat_y_offset(other.cat_y_offset), - cat_height(other.cat_height), - overlay_height(other.overlay_height), - idle_frame(other.idle_frame), - keypress_duration_ms(other.keypress_duration_ms), - test_animation_duration_ms(other.test_animation_duration_ms), - test_animation_interval_sec(other.test_animation_interval_sec), - animation_speed_ms(other.animation_speed_ms), - fps(other.fps), - overlay_opacity(other.overlay_opacity), - mirror_x(other.mirror_x), - mirror_y(other.mirror_y), - enable_antialiasing(other.enable_antialiasing), - enable_debug(other.enable_debug), - layer(other.layer), - overlay_position(other.overlay_position), - animation_index(other.animation_index), - invert_color(other.invert_color), - padding_x(other.padding_x), - padding_y(other.padding_y), - enable_scheduled_sleep(other.enable_scheduled_sleep), - sleep_begin(other.sleep_begin), - sleep_end(other.sleep_end), - idle_sleep_timeout_sec(other.idle_sleep_timeout_sec), - happy_kpm(other.happy_kpm), - cat_align(other.cat_align), - animation_sprite_sheet_layout(other.animation_sprite_sheet_layout), - animation_dm_set(other.animation_dm_set), - animation_custom_set(other.animation_custom_set), - idle_animation(other.idle_animation), - input_fps(other.input_fps), - randomize_index(other.randomize_index), - randomize_on_reload(other.randomize_on_reload), - update_rate_ms(other.update_rate_ms), - cpu_threshold(other.cpu_threshold), - cpu_running_factor(other.cpu_running_factor), - movement_radius(other.movement_radius), - enable_movement_debug(other.enable_movement_debug), - movement_speed(other.movement_speed), - movement_wait_factor(other.movement_wait_factor), - screen_width(other.screen_width), - custom_sprite_sheet_settings(other.custom_sprite_sheet_settings), - enable_hand_mapping(other.enable_hand_mapping), - _keep_old_animation_index(other._keep_old_animation_index), - _strict(other._strict), - _custom(other._custom) - { - output_name = other.output_name ? strdup(other.output_name) : nullptr; - config_copy_keyboard_devices_from(*this, other); - custom_sprite_sheet_filename = other.custom_sprite_sheet_filename ? strdup(other.custom_sprite_sheet_filename) : nullptr; - _animation_name = other._animation_name ? strdup(other._animation_name) : nullptr; - _loaded_animation_fqname = other._loaded_animation_fqname ? strdup(other._loaded_animation_fqname) : nullptr; - } - - config_t& operator=(const config_t& other) { - if (this != &other) { - cleanup(*this); - - cat_x_offset = other.cat_x_offset; - cat_y_offset = other.cat_y_offset; - cat_height = other.cat_height; - overlay_height = other.overlay_height; - idle_frame = other.idle_frame; - keypress_duration_ms = other.keypress_duration_ms; - test_animation_duration_ms = other.test_animation_duration_ms; - test_animation_interval_sec = other.test_animation_interval_sec; - animation_speed_ms = other.animation_speed_ms; - fps = other.fps; - overlay_opacity = other.overlay_opacity; - mirror_x = other.mirror_x; - mirror_y = other.mirror_y; - enable_antialiasing = other.enable_antialiasing; - enable_debug = other.enable_debug; - layer = other.layer; - overlay_position = other.overlay_position; - animation_index = other.animation_index; - invert_color = other.invert_color; - padding_x = other.padding_x; - padding_y = other.padding_y; - enable_scheduled_sleep = other.enable_scheduled_sleep; - sleep_begin = other.sleep_begin; - sleep_end = other.sleep_end; - idle_sleep_timeout_sec = other.idle_sleep_timeout_sec; - happy_kpm = other.happy_kpm; - cat_align = other.cat_align; - animation_sprite_sheet_layout = other.animation_sprite_sheet_layout; - animation_dm_set = other.animation_dm_set; - animation_custom_set = other.animation_custom_set; - idle_animation = other.idle_animation; - input_fps = other.input_fps; - randomize_index = other.randomize_index; - randomize_on_reload = other.randomize_on_reload; - update_rate_ms = other.update_rate_ms; - movement_radius = other.movement_radius; - enable_movement_debug = other.enable_movement_debug; - movement_speed = other.movement_speed; - movement_wait_factor = other.movement_wait_factor; - cpu_threshold = other.cpu_threshold; - cpu_running_factor = other.cpu_running_factor; - screen_width = other.screen_width; - custom_sprite_sheet_settings = other.custom_sprite_sheet_settings; - enable_hand_mapping = other.enable_hand_mapping; - _keep_old_animation_index = other._keep_old_animation_index; - _strict = other._strict; - _custom = other._custom; - - output_name = other.output_name ? strdup(other.output_name) : nullptr; - config_copy_keyboard_devices_from(*this, other); - custom_sprite_sheet_filename = other.custom_sprite_sheet_filename ? strdup(other.custom_sprite_sheet_filename) : nullptr; - _animation_name = other._animation_name ? strdup(other._animation_name) : nullptr; - _loaded_animation_fqname = other._loaded_animation_fqname ? strdup(other._loaded_animation_fqname) : nullptr; - } - return *this; - } - - config_t(config_t&& other) noexcept - : output_name(other.output_name), - num_keyboard_devices(other.num_keyboard_devices), - cat_x_offset(other.cat_x_offset), - cat_y_offset(other.cat_y_offset), - cat_height(other.cat_height), - overlay_height(other.overlay_height), - idle_frame(other.idle_frame), - keypress_duration_ms(other.keypress_duration_ms), - test_animation_duration_ms(other.test_animation_duration_ms), - test_animation_interval_sec(other.test_animation_interval_sec), - animation_speed_ms(other.animation_speed_ms), - fps(other.fps), - overlay_opacity(other.overlay_opacity), - mirror_x(other.mirror_x), - mirror_y(other.mirror_y), - enable_antialiasing(other.enable_antialiasing), - enable_debug(other.enable_debug), - layer(other.layer), - overlay_position(other.overlay_position), - animation_index(other.animation_index), - invert_color(other.invert_color), - padding_x(other.padding_x), - padding_y(other.padding_y), - enable_scheduled_sleep(other.enable_scheduled_sleep), - sleep_begin(other.sleep_begin), - sleep_end(other.sleep_end), - idle_sleep_timeout_sec(other.idle_sleep_timeout_sec), - happy_kpm(other.happy_kpm), - cat_align(other.cat_align), - animation_sprite_sheet_layout(other.animation_sprite_sheet_layout), - animation_dm_set(other.animation_dm_set), - animation_custom_set(other.animation_custom_set), - idle_animation(other.idle_animation), - input_fps(other.input_fps), - randomize_index(other.randomize_index), - randomize_on_reload(other.randomize_on_reload), - update_rate_ms(other.update_rate_ms), - cpu_threshold(other.cpu_threshold), - cpu_running_factor(other.cpu_running_factor), - movement_radius(other.movement_radius), - enable_movement_debug(other.enable_movement_debug), - movement_speed(other.movement_speed), - movement_wait_factor(other.movement_wait_factor), - screen_width(other.screen_width), - custom_sprite_sheet_filename(other.custom_sprite_sheet_filename), - custom_sprite_sheet_settings(other.custom_sprite_sheet_settings), - enable_hand_mapping(other.enable_hand_mapping), - _keep_old_animation_index(other._keep_old_animation_index), - _strict(other._strict), - _custom(other._custom), - _animation_name(other._animation_name), - _loaded_animation_fqname(other._loaded_animation_fqname) - { - for (int i = 0; i < num_keyboard_devices; ++i) { - keyboard_devices[i] = other.keyboard_devices[i]; - other.keyboard_devices[i] = nullptr; - } - other.num_keyboard_devices = 0; - other.output_name = nullptr; - other.custom_sprite_sheet_filename = nullptr; - other._animation_name = nullptr; - other._loaded_animation_fqname = nullptr; - } - - config_t& operator=(config_t&& other) noexcept { - if (this != &other) { - cleanup(*this); - - output_name = other.output_name; - num_keyboard_devices = other.num_keyboard_devices; - cat_x_offset = other.cat_x_offset; - cat_y_offset = other.cat_y_offset; - cat_height = other.cat_height; - overlay_height = other.overlay_height; - idle_frame = other.idle_frame; - keypress_duration_ms = other.keypress_duration_ms; - test_animation_duration_ms = other.test_animation_duration_ms; - test_animation_interval_sec = other.test_animation_interval_sec; - animation_speed_ms = other.animation_speed_ms; - fps = other.fps; - overlay_opacity = other.overlay_opacity; - mirror_x = other.mirror_x; - mirror_y = other.mirror_y; - enable_antialiasing = other.enable_antialiasing; - enable_debug = other.enable_debug; - layer = other.layer; - overlay_position = other.overlay_position; - animation_index = other.animation_index; - invert_color = other.invert_color; - padding_x = other.padding_x; - padding_y = other.padding_y; - enable_scheduled_sleep = other.enable_scheduled_sleep; - sleep_begin = other.sleep_begin; - sleep_end = other.sleep_end; - idle_sleep_timeout_sec = other.idle_sleep_timeout_sec; - happy_kpm = other.happy_kpm; - cat_align = other.cat_align; - animation_sprite_sheet_layout = other.animation_sprite_sheet_layout; - animation_dm_set = other.animation_dm_set; - animation_custom_set = other.animation_custom_set; - idle_animation = other.idle_animation; - input_fps = other.input_fps; - randomize_index = other.randomize_index; - randomize_on_reload = other.randomize_on_reload; - update_rate_ms = other.update_rate_ms; - cpu_threshold = other.cpu_threshold; - cpu_running_factor = other.cpu_running_factor; - movement_radius = other.movement_radius; - enable_movement_debug = other.enable_movement_debug; - movement_speed = other.movement_speed; - movement_wait_factor = other.movement_wait_factor; - custom_sprite_sheet_filename = other.custom_sprite_sheet_filename; - screen_width = other.screen_width; - custom_sprite_sheet_settings = other.custom_sprite_sheet_settings; - enable_hand_mapping = other.enable_hand_mapping; - _keep_old_animation_index = other._keep_old_animation_index; - _strict = other._strict; - _custom = other._custom; - _animation_name = other._animation_name; - _loaded_animation_fqname = other._loaded_animation_fqname; - - for (int i = 0; i < num_keyboard_devices; ++i) { - keyboard_devices[i] = other.keyboard_devices[i]; - other.keyboard_devices[i] = nullptr; - } - other.num_keyboard_devices = 0; - other.output_name = nullptr; - other.custom_sprite_sheet_filename = nullptr; - other._animation_name = nullptr; - other._loaded_animation_fqname = nullptr; - } - return *this; - } - }; - inline void cleanup(config_t& config) { - if (config.output_name) ::free(config.output_name); - config.output_name = nullptr; - config_free_keyboard_devices(config); - if (config.custom_sprite_sheet_filename) ::free(config.custom_sprite_sheet_filename); - config.custom_sprite_sheet_filename = nullptr; - if (config._animation_name) ::free(config._animation_name); - config._animation_name = nullptr; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; +struct config_time_t { + int hour{0}; + int min{0}; +}; +enum class config_animation_sprite_sheet_layout_t : uint8_t { + None, + Bongocat, + Dm, + MsAgent, + Pkmn, + Custom, +}; +enum class config_animation_dm_set_t : uint8_t { + None, + min_dm, + dm, + dm20, + dmx, + pen, + pen20, + dmc, + dmall, +}; +enum class config_animation_custom_set_t : uint8_t { + None, + misc, + pmd, + custom, +}; + +struct config_t { + char *output_name{nullptr}; + char *keyboard_devices[input::MAX_INPUT_DEVICES]{}; + int32_t num_keyboard_devices{0}; + int32_t cat_x_offset{0}; + int32_t cat_y_offset{0}; + int32_t cat_height{0}; + int32_t overlay_height{0}; + int32_t idle_frame{0}; + int32_t keypress_duration_ms{0}; + int32_t test_animation_duration_ms{0}; + int32_t test_animation_interval_sec{0}; + int32_t animation_speed_ms{0}; + int32_t fps{0}; + int32_t overlay_opacity{0}; + int32_t mirror_x{0}; // reflect across Y axis (horizontal flip) + int32_t mirror_y{0}; // reflect across X axis (vertical flip) + int32_t enable_antialiasing{0}; // enable bilinear interpolation for smooth scaling + int32_t enable_debug{0}; + layer_type_t layer{layer_type_t::LAYER_TOP}; + overlay_position_t overlay_position{overlay_position_t::POSITION_TOP}; + + int32_t animation_index{0}; + int32_t invert_color{0}; + int32_t padding_x{0}; + int32_t padding_y{0}; + + int32_t enable_scheduled_sleep{0}; + config_time_t sleep_begin; + config_time_t sleep_end; + int32_t idle_sleep_timeout_sec{0}; + + int32_t happy_kpm{0}; + + align_type_t cat_align{align_type_t::ALIGN_CENTER}; + + config_animation_sprite_sheet_layout_t animation_sprite_sheet_layout{config_animation_sprite_sheet_layout_t::None}; + config_animation_dm_set_t animation_dm_set{config_animation_dm_set_t::None}; + config_animation_custom_set_t animation_custom_set{config_animation_custom_set_t::None}; + int32_t idle_animation{0}; + int32_t input_fps{0}; + int32_t randomize_index{0}; + int32_t randomize_on_reload{0}; + + int32_t update_rate_ms{0}; + double cpu_threshold{0}; + double cpu_running_factor{0}; + + int32_t movement_radius{0}; + int32_t enable_movement_debug{0}; + int32_t movement_speed{0}; + double movement_wait_factor{0}; + + int32_t screen_width{0}; + + char *custom_sprite_sheet_filename{nullptr}; // must be png file + assets::custom_animation_settings_t custom_sprite_sheet_settings{}; + + int32_t enable_hand_mapping{0}; + + // for keep old index when reload config + bool _keep_old_animation_index{false}; + bool _strict{false}; + bool _custom{false}; // is custom sprite sheet + char *_animation_name{nullptr}; // original animation_anim from parsing config + char *_loaded_animation_fqname{nullptr}; + + // Make Config movable and copyable + config_t() { + for (size_t i = 0; i < input::MAX_INPUT_DEVICES; ++i) { + keyboard_devices[i] = nullptr; } - - inline void config_free_keyboard_devices(config_t& config) { - assert(config.num_keyboard_devices >= 0); - for (size_t i = 0; i < static_cast(config.num_keyboard_devices) && i < input::MAX_INPUT_DEVICES; i++) { - if (config.keyboard_devices[i]) ::free(config.keyboard_devices[i]); - config.keyboard_devices[i] = nullptr; - } - config.num_keyboard_devices = 0; + } + ~config_t() { + cleanup(*this); + } + + config_t(const config_t& other) + : cat_x_offset(other.cat_x_offset) + , cat_y_offset(other.cat_y_offset) + , cat_height(other.cat_height) + , overlay_height(other.overlay_height) + , idle_frame(other.idle_frame) + , keypress_duration_ms(other.keypress_duration_ms) + , test_animation_duration_ms(other.test_animation_duration_ms) + , test_animation_interval_sec(other.test_animation_interval_sec) + , animation_speed_ms(other.animation_speed_ms) + , fps(other.fps) + , overlay_opacity(other.overlay_opacity) + , mirror_x(other.mirror_x) + , mirror_y(other.mirror_y) + , enable_antialiasing(other.enable_antialiasing) + , enable_debug(other.enable_debug) + , layer(other.layer) + , overlay_position(other.overlay_position) + , animation_index(other.animation_index) + , invert_color(other.invert_color) + , padding_x(other.padding_x) + , padding_y(other.padding_y) + , enable_scheduled_sleep(other.enable_scheduled_sleep) + , sleep_begin(other.sleep_begin) + , sleep_end(other.sleep_end) + , idle_sleep_timeout_sec(other.idle_sleep_timeout_sec) + , happy_kpm(other.happy_kpm) + , cat_align(other.cat_align) + , animation_sprite_sheet_layout(other.animation_sprite_sheet_layout) + , animation_dm_set(other.animation_dm_set) + , animation_custom_set(other.animation_custom_set) + , idle_animation(other.idle_animation) + , input_fps(other.input_fps) + , randomize_index(other.randomize_index) + , randomize_on_reload(other.randomize_on_reload) + , update_rate_ms(other.update_rate_ms) + , cpu_threshold(other.cpu_threshold) + , cpu_running_factor(other.cpu_running_factor) + , movement_radius(other.movement_radius) + , enable_movement_debug(other.enable_movement_debug) + , movement_speed(other.movement_speed) + , movement_wait_factor(other.movement_wait_factor) + , screen_width(other.screen_width) + , custom_sprite_sheet_settings(other.custom_sprite_sheet_settings) + , enable_hand_mapping(other.enable_hand_mapping) + , _keep_old_animation_index(other._keep_old_animation_index) + , _strict(other._strict) + , _custom(other._custom) { + output_name = other.output_name != nullptr ? strdup(other.output_name) : nullptr; + config_copy_keyboard_devices_from(*this, other); + custom_sprite_sheet_filename = + other.custom_sprite_sheet_filename != nullptr ? strdup(other.custom_sprite_sheet_filename) : nullptr; + _animation_name = other._animation_name != nullptr? strdup(other._animation_name) : nullptr; + _loaded_animation_fqname = other._loaded_animation_fqname != nullptr ? strdup(other._loaded_animation_fqname) : nullptr; + } + + config_t& operator=(const config_t& other) { + if (this != &other) { + cleanup(*this); + + cat_x_offset = other.cat_x_offset; + cat_y_offset = other.cat_y_offset; + cat_height = other.cat_height; + overlay_height = other.overlay_height; + idle_frame = other.idle_frame; + keypress_duration_ms = other.keypress_duration_ms; + test_animation_duration_ms = other.test_animation_duration_ms; + test_animation_interval_sec = other.test_animation_interval_sec; + animation_speed_ms = other.animation_speed_ms; + fps = other.fps; + overlay_opacity = other.overlay_opacity; + mirror_x = other.mirror_x; + mirror_y = other.mirror_y; + enable_antialiasing = other.enable_antialiasing; + enable_debug = other.enable_debug; + layer = other.layer; + overlay_position = other.overlay_position; + animation_index = other.animation_index; + invert_color = other.invert_color; + padding_x = other.padding_x; + padding_y = other.padding_y; + enable_scheduled_sleep = other.enable_scheduled_sleep; + sleep_begin = other.sleep_begin; + sleep_end = other.sleep_end; + idle_sleep_timeout_sec = other.idle_sleep_timeout_sec; + happy_kpm = other.happy_kpm; + cat_align = other.cat_align; + animation_sprite_sheet_layout = other.animation_sprite_sheet_layout; + animation_dm_set = other.animation_dm_set; + animation_custom_set = other.animation_custom_set; + idle_animation = other.idle_animation; + input_fps = other.input_fps; + randomize_index = other.randomize_index; + randomize_on_reload = other.randomize_on_reload; + update_rate_ms = other.update_rate_ms; + movement_radius = other.movement_radius; + enable_movement_debug = other.enable_movement_debug; + movement_speed = other.movement_speed; + movement_wait_factor = other.movement_wait_factor; + cpu_threshold = other.cpu_threshold; + cpu_running_factor = other.cpu_running_factor; + screen_width = other.screen_width; + custom_sprite_sheet_settings = other.custom_sprite_sheet_settings; + enable_hand_mapping = other.enable_hand_mapping; + _keep_old_animation_index = other._keep_old_animation_index; + _strict = other._strict; + _custom = other._custom; + + output_name = other.output_name != nullptr ? strdup(other.output_name) : nullptr; + config_copy_keyboard_devices_from(*this, other); + custom_sprite_sheet_filename = + other.custom_sprite_sheet_filename != nullptr ? strdup(other.custom_sprite_sheet_filename) : nullptr; + _animation_name = other._animation_name != nullptr ? strdup(other._animation_name) : nullptr; + _loaded_animation_fqname = other._loaded_animation_fqname != nullptr ? strdup(other._loaded_animation_fqname) : nullptr; + } + return *this; + } + + config_t(config_t&& other) noexcept + : output_name(other.output_name) + , num_keyboard_devices(other.num_keyboard_devices) + , cat_x_offset(other.cat_x_offset) + , cat_y_offset(other.cat_y_offset) + , cat_height(other.cat_height) + , overlay_height(other.overlay_height) + , idle_frame(other.idle_frame) + , keypress_duration_ms(other.keypress_duration_ms) + , test_animation_duration_ms(other.test_animation_duration_ms) + , test_animation_interval_sec(other.test_animation_interval_sec) + , animation_speed_ms(other.animation_speed_ms) + , fps(other.fps) + , overlay_opacity(other.overlay_opacity) + , mirror_x(other.mirror_x) + , mirror_y(other.mirror_y) + , enable_antialiasing(other.enable_antialiasing) + , enable_debug(other.enable_debug) + , layer(other.layer) + , overlay_position(other.overlay_position) + , animation_index(other.animation_index) + , invert_color(other.invert_color) + , padding_x(other.padding_x) + , padding_y(other.padding_y) + , enable_scheduled_sleep(other.enable_scheduled_sleep) + , sleep_begin(other.sleep_begin) + , sleep_end(other.sleep_end) + , idle_sleep_timeout_sec(other.idle_sleep_timeout_sec) + , happy_kpm(other.happy_kpm) + , cat_align(other.cat_align) + , animation_sprite_sheet_layout(other.animation_sprite_sheet_layout) + , animation_dm_set(other.animation_dm_set) + , animation_custom_set(other.animation_custom_set) + , idle_animation(other.idle_animation) + , input_fps(other.input_fps) + , randomize_index(other.randomize_index) + , randomize_on_reload(other.randomize_on_reload) + , update_rate_ms(other.update_rate_ms) + , cpu_threshold(other.cpu_threshold) + , cpu_running_factor(other.cpu_running_factor) + , movement_radius(other.movement_radius) + , enable_movement_debug(other.enable_movement_debug) + , movement_speed(other.movement_speed) + , movement_wait_factor(other.movement_wait_factor) + , screen_width(other.screen_width) + , custom_sprite_sheet_filename(other.custom_sprite_sheet_filename) + , custom_sprite_sheet_settings(other.custom_sprite_sheet_settings) + , enable_hand_mapping(other.enable_hand_mapping) + , _keep_old_animation_index(other._keep_old_animation_index) + , _strict(other._strict) + , _custom(other._custom) + , _animation_name(other._animation_name) + , _loaded_animation_fqname(other._loaded_animation_fqname) { + for (int i = 0; i < num_keyboard_devices; ++i) { + keyboard_devices[i] = other.keyboard_devices[i]; + other.keyboard_devices[i] = nullptr; } - inline void config_copy_keyboard_devices_from(config_t& config, const config_t& other) { - config_free_keyboard_devices(config); - config.num_keyboard_devices = other.num_keyboard_devices; - assert(config.num_keyboard_devices >= 0); - for (size_t i = 0; i < static_cast(config.num_keyboard_devices) && i < input::MAX_INPUT_DEVICES; i++) { - config.keyboard_devices[i] = other.keyboard_devices[i] != nullptr ? strdup(other.keyboard_devices[i]) : nullptr; - } + other.num_keyboard_devices = 0; + other.output_name = nullptr; + other.custom_sprite_sheet_filename = nullptr; + other._animation_name = nullptr; + other._loaded_animation_fqname = nullptr; + } + + config_t& operator=(config_t&& other) noexcept { + if (this != &other) { + cleanup(*this); + + output_name = other.output_name; + num_keyboard_devices = other.num_keyboard_devices; + cat_x_offset = other.cat_x_offset; + cat_y_offset = other.cat_y_offset; + cat_height = other.cat_height; + overlay_height = other.overlay_height; + idle_frame = other.idle_frame; + keypress_duration_ms = other.keypress_duration_ms; + test_animation_duration_ms = other.test_animation_duration_ms; + test_animation_interval_sec = other.test_animation_interval_sec; + animation_speed_ms = other.animation_speed_ms; + fps = other.fps; + overlay_opacity = other.overlay_opacity; + mirror_x = other.mirror_x; + mirror_y = other.mirror_y; + enable_antialiasing = other.enable_antialiasing; + enable_debug = other.enable_debug; + layer = other.layer; + overlay_position = other.overlay_position; + animation_index = other.animation_index; + invert_color = other.invert_color; + padding_x = other.padding_x; + padding_y = other.padding_y; + enable_scheduled_sleep = other.enable_scheduled_sleep; + sleep_begin = other.sleep_begin; + sleep_end = other.sleep_end; + idle_sleep_timeout_sec = other.idle_sleep_timeout_sec; + happy_kpm = other.happy_kpm; + cat_align = other.cat_align; + animation_sprite_sheet_layout = other.animation_sprite_sheet_layout; + animation_dm_set = other.animation_dm_set; + animation_custom_set = other.animation_custom_set; + idle_animation = other.idle_animation; + input_fps = other.input_fps; + randomize_index = other.randomize_index; + randomize_on_reload = other.randomize_on_reload; + update_rate_ms = other.update_rate_ms; + cpu_threshold = other.cpu_threshold; + cpu_running_factor = other.cpu_running_factor; + movement_radius = other.movement_radius; + enable_movement_debug = other.enable_movement_debug; + movement_speed = other.movement_speed; + movement_wait_factor = other.movement_wait_factor; + custom_sprite_sheet_filename = other.custom_sprite_sheet_filename; + screen_width = other.screen_width; + custom_sprite_sheet_settings = other.custom_sprite_sheet_settings; + enable_hand_mapping = other.enable_hand_mapping; + _keep_old_animation_index = other._keep_old_animation_index; + _strict = other._strict; + _custom = other._custom; + _animation_name = other._animation_name; + _loaded_animation_fqname = other._loaded_animation_fqname; + + for (int i = 0; i < num_keyboard_devices; ++i) { + keyboard_devices[i] = other.keyboard_devices[i]; + other.keyboard_devices[i] = nullptr; + } + other.num_keyboard_devices = 0; + other.output_name = nullptr; + other.custom_sprite_sheet_filename = nullptr; + other._animation_name = nullptr; + other._loaded_animation_fqname = nullptr; } + return *this; + } +}; +inline void cleanup(config_t& config) { + if (config.output_name != nullptr) { + ::free(config.output_name); + } + config.output_name = nullptr; + config_free_keyboard_devices(config); + if (config.custom_sprite_sheet_filename != nullptr) { + ::free(config.custom_sprite_sheet_filename); + } + config.custom_sprite_sheet_filename = nullptr; + if (config._animation_name != nullptr) { + ::free(config._animation_name); + } + config._animation_name = nullptr; + if (config._loaded_animation_fqname != nullptr) { + ::free(config._loaded_animation_fqname); + } + config._loaded_animation_fqname = nullptr; +} + +inline void config_free_keyboard_devices(config_t& config) { + assert(config.num_keyboard_devices >= 0); + for (size_t i = 0; i < static_cast(config.num_keyboard_devices) && i < input::MAX_INPUT_DEVICES; i++) { + if (config.keyboard_devices[i] != nullptr) { + ::free(config.keyboard_devices[i]); + } + config.keyboard_devices[i] = nullptr; + } + config.num_keyboard_devices = 0; +} +inline void config_copy_keyboard_devices_from(config_t& config, const config_t& other) { + config_free_keyboard_devices(config); + config.num_keyboard_devices = other.num_keyboard_devices; + assert(config.num_keyboard_devices >= 0); + for (size_t i = 0; i < static_cast(config.num_keyboard_devices) && i < input::MAX_INPUT_DEVICES; i++) { + config.keyboard_devices[i] = other.keyboard_devices[i] != nullptr ? strdup(other.keyboard_devices[i]) : nullptr; + } +} // ============================================================================= // CONFIGURATION FUNCTIONS // ============================================================================= - struct load_config_overwrite_parameters_t { - const char* output_name{nullptr}; - int32_t randomize_index{-1}; - int32_t strict{-1}; - const char* animation_name{nullptr}; - }; - BONGOCAT_NODISCARD created_result_t load(const char *config_file_path, load_config_overwrite_parameters_t overwrite_parameters); - void reset(config_t& config); +struct load_config_overwrite_parameters_t { + const char *output_name{nullptr}; + int32_t randomize_index{-1}; + int32_t strict{-1}; + const char *animation_name{nullptr}; +}; +BONGOCAT_NODISCARD created_result_t load(const char *config_file_path, + load_config_overwrite_parameters_t overwrite_parameters); +void reset(config_t& config); - void set_defaults(config_t& config); -} +void set_defaults(config_t& config); +} // namespace bongocat::config -#endif // BONGOCAT_CONFIG_H \ No newline at end of file +#endif // BONGOCAT_CONFIG_H \ No newline at end of file diff --git a/include/config/config_watcher.h b/include/config/config_watcher.h index d9ab4b3b..79a1ef0c 100644 --- a/include/config/config_watcher.h +++ b/include/config/config_watcher.h @@ -3,81 +3,80 @@ #include "core/bongocat.h" #include "utils/system_memory.h" + +#include #include #include -#include - namespace bongocat::config { - // Inotify buffer sizing - inline static constexpr size_t INOTIFY_EVENT_SIZE = sizeof(struct inotify_event); - inline static constexpr size_t INOTIFY_BUF_LEN = 1024 * (INOTIFY_EVENT_SIZE + 16); +// Inotify buffer sizing +inline static constexpr size_t INOTIFY_EVENT_SIZE = sizeof(struct inotify_event); +inline static constexpr size_t INOTIFY_BUF_LEN = 1024 * (INOTIFY_EVENT_SIZE + 16); // ============================================================================= // TYPE DEFINITIONS // ============================================================================= - struct config_watcher_t; - // Stop watching for config changes - void stop_watcher(config_watcher_t& watcher); - - // Cleanup config watcher resources - void cleanup_watcher(config_watcher_t& watcher); - - // Config watcher structure - struct config_watcher_t { - platform::FileDescriptor inotify_fd; - platform::FileDescriptor wd_file; - platform::FileDescriptor wd_dir; - char* config_path{nullptr}; - - platform::FileDescriptor reload_efd; - - pthread_t _watcher_thread{0}; - atomic_bool _running{false}; - - - config_watcher_t() = default; - ~config_watcher_t() { - cleanup_watcher(*this); - } - - config_watcher_t(const config_watcher_t&) = delete; - config_watcher_t& operator=(const config_watcher_t&) = delete; - config_watcher_t(config_watcher_t&& other) noexcept = delete; - config_watcher_t& operator=(config_watcher_t&& other) noexcept = delete; - }; - inline void cleanup_watcher(config_watcher_t& watcher) { - stop_watcher(watcher); - - // remove watches first (requires inotify fd still open) - if (watcher.inotify_fd._fd >= 0 && watcher.wd_file._fd >= 0) { - inotify_rm_watch(watcher.inotify_fd._fd, watcher.wd_file._fd); - watcher.wd_file._fd = -1; - } - if (watcher.inotify_fd._fd >= 0 && watcher.wd_dir._fd >= 0) { - inotify_rm_watch(watcher.inotify_fd._fd, watcher.wd_dir._fd); - watcher.wd_dir._fd = -1; - } - - close_fd(watcher.inotify_fd); - close_fd(watcher.reload_efd); - - if (watcher.config_path) { - ::free(watcher.config_path); - watcher.config_path = nullptr; - } - } +struct config_watcher_t; +// Stop watching for config changes +void stop_watcher(config_watcher_t& watcher); + +// Cleanup config watcher resources +void cleanup_watcher(config_watcher_t& watcher); + +// Config watcher structure +struct config_watcher_t { + platform::FileDescriptor inotify_fd; + platform::FileDescriptor wd_file; + platform::FileDescriptor wd_dir; + char *config_path{nullptr}; + + platform::FileDescriptor reload_efd; + + pthread_t _watcher_thread{0}; + atomic_bool _running{false}; + + config_watcher_t() = default; + ~config_watcher_t() { + cleanup_watcher(*this); + } + + config_watcher_t(const config_watcher_t&) = delete; + config_watcher_t& operator=(const config_watcher_t&) = delete; + config_watcher_t(config_watcher_t&& other) noexcept = delete; + config_watcher_t& operator=(config_watcher_t&& other) noexcept = delete; +}; +inline void cleanup_watcher(config_watcher_t& watcher) { + stop_watcher(watcher); + + // remove watches first (requires inotify fd still open) + if (watcher.inotify_fd._fd >= 0 && watcher.wd_file._fd >= 0) { + inotify_rm_watch(watcher.inotify_fd._fd, watcher.wd_file._fd); + watcher.wd_file._fd = -1; + } + if (watcher.inotify_fd._fd >= 0 && watcher.wd_dir._fd >= 0) { + inotify_rm_watch(watcher.inotify_fd._fd, watcher.wd_dir._fd); + watcher.wd_dir._fd = -1; + } + + close_fd(watcher.inotify_fd); + close_fd(watcher.reload_efd); + + if (watcher.config_path) { + ::free(watcher.config_path); + watcher.config_path = nullptr; + } +} // ============================================================================= // CONFIG WATCHER FUNCTIONS // ============================================================================= - // Initialize config watcher - BONGOCAT_NODISCARD created_result_t> create_watcher(const char *config_path); +// Initialize config watcher +BONGOCAT_NODISCARD created_result_t> create_watcher(const char *config_path); - // Start watching for config changes - void start_watcher(config_watcher_t& watcher); -} +// Start watching for config changes +void start_watcher(config_watcher_t& watcher); +} // namespace bongocat::config -#endif // BONGOCAT_CONFIG_WATCHER_H \ No newline at end of file +#endif // BONGOCAT_CONFIG_WATCHER_H \ No newline at end of file diff --git a/include/core/bongocat.h b/include/core/bongocat.h index b9ec7bbf..2f7e030a 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -1,17 +1,18 @@ #ifndef BONGOCAT_BONGOCAT_H #define BONGOCAT_BONGOCAT_H -#include -#include #include "utils/error.h" #include "utils/memory.h" +#include +#include + // ============================================================================= // VERSION // ============================================================================= // Version -inline static constexpr const char* BONGOCAT_VERSION = "3.6.0"; +inline static constexpr const char *BONGOCAT_VERSION = "3.6.0"; // ============================================================================= // COMPILE-TIME CONSTANTS @@ -25,223 +26,226 @@ inline static constexpr int32_t RGBA_CHANNELS = 4; inline static constexpr int32_t BGRA_CHANNELS = 4; namespace bongocat { - template - struct created_result_t { - T result{}; - bongocat_error_t error{bongocat_error_t::BONGOCAT_SUCCESS}; - - created_result_t() = default; - explicit(false) created_result_t(bongocat_error_t err) : error(err) {} - explicit(false) created_result_t(T&& res) : result(bongocat::move(res)), error(bongocat_error_t::BONGOCAT_SUCCESS) {} - }; - - // feature flags - namespace features { - // experimental - inline static constexpr bool BongocatIdleAnimation = false; - inline static constexpr bool BongocatBoringAnimation = false; +template struct created_result_t { + T result{}; + bongocat_error_t error{bongocat_error_t::BONGOCAT_SUCCESS}; + + created_result_t() = default; + explicit(false) created_result_t(bongocat_error_t err) : error(err) {} + explicit(false) created_result_t(T&& res) : result(bongocat::move(res)), error(bongocat_error_t::BONGOCAT_SUCCESS) {} +}; + +// feature flags +namespace features { + // experimental + inline static constexpr bool BongocatIdleAnimation = false; + inline static constexpr bool BongocatBoringAnimation = false; #ifndef NDEBUG - inline static constexpr bool Debug = true; + inline static constexpr bool Debug = true; #else - inline static constexpr bool Debug = false; + inline static constexpr bool Debug = false; #endif #ifdef FEATURE_BONGOCAT_EMBEDDED_ASSETS - inline static constexpr bool EnableBongocatEmbeddedAssets = true; + inline static constexpr bool EnableBongocatEmbeddedAssets = true; #else - inline static constexpr bool EnableBongocatEmbeddedAssets = false; + inline static constexpr bool EnableBongocatEmbeddedAssets = false; #endif #ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS - inline static constexpr bool EnableDmEmbeddedAssets = true; -#ifdef FEATURE_DM_EMBEDDED_ASSETS - inline static constexpr bool EnableFullDmEmbeddedAssets = true; -#else - inline static constexpr bool EnableFullDmEmbeddedAssets = false; -#endif -#ifdef FEATURE_DM20_EMBEDDED_ASSETS - inline static constexpr bool EnableDm20EmbeddedAssets = true; -#else - inline static constexpr bool EnableDm20EmbeddedAssets = false; -#endif -#ifdef FEATURE_DMC_EMBEDDED_ASSETS - inline static constexpr bool EnableDmcEmbeddedAssets = true; -#else - inline static constexpr bool EnableDmcEmbeddedAssets = false; -#endif -#ifdef FEATURE_DMX_EMBEDDED_ASSETS - inline static constexpr bool EnableDmxEmbeddedAssets = true; -#else - inline static constexpr bool EnableDmxEmbeddedAssets = false; -#endif -#ifdef FEATURE_PEN_EMBEDDED_ASSETS - inline static constexpr bool EnablePenEmbeddedAssets = true; -#else - inline static constexpr bool EnablePenEmbeddedAssets = false; -#endif -#ifdef FEATURE_PEN20_EMBEDDED_ASSETS - inline static constexpr bool EnablePen20EmbeddedAssets = true; -#else - inline static constexpr bool EnablePen20EmbeddedAssets = false; -#endif -#ifdef FEATURE_DMALL_EMBEDDED_ASSETS - inline static constexpr bool EnableDmAllEmbeddedAssets = true; -#else - inline static constexpr bool EnableDmAllEmbeddedAssets = false; -#endif -#if !defined(FEATURE_DM_EMBEDDED_ASSETS) && !defined(FEATURE_DM20_EMBEDDED_ASSETS) && !defined(FEATURE_DMC_EMBEDDED_ASSETS) && !defined(FEATURE_DMX_EMBEDDED_ASSETS) && !defined(FEATURE_PEN20_EMBEDDED_ASSETS) && !defined(FEATURE_DMALL_EMBEDDED_ASSETS) - inline static constexpr bool EnableMinDmEmbeddedAssets = true; -#else - inline static constexpr bool EnableMinDmEmbeddedAssets = false; -#endif -#else - inline static constexpr bool EnableDmEmbeddedAssets = false; - inline static constexpr bool EnableFullDmEmbeddedAssets = false; - inline static constexpr bool EnableDm20EmbeddedAssets = false; - inline static constexpr bool EnableDmcEmbeddedAssets = false; - inline static constexpr bool EnableDmxEmbeddedAssets = false; - inline static constexpr bool EnablePenEmbeddedAssets = false; - inline static constexpr bool EnablePen20EmbeddedAssets = false; - inline static constexpr bool EnableMinDmEmbeddedAssets = false; - inline static constexpr bool EnableDmAllEmbeddedAssets = false; + inline static constexpr bool EnableDmEmbeddedAssets = true; +# ifdef FEATURE_DM_EMBEDDED_ASSETS + inline static constexpr bool EnableFullDmEmbeddedAssets = true; +# else + inline static constexpr bool EnableFullDmEmbeddedAssets = false; +# endif +# ifdef FEATURE_DM20_EMBEDDED_ASSETS + inline static constexpr bool EnableDm20EmbeddedAssets = true; +# else + inline static constexpr bool EnableDm20EmbeddedAssets = false; +# endif +# ifdef FEATURE_DMC_EMBEDDED_ASSETS + inline static constexpr bool EnableDmcEmbeddedAssets = true; +# else + inline static constexpr bool EnableDmcEmbeddedAssets = false; +# endif +# ifdef FEATURE_DMX_EMBEDDED_ASSETS + inline static constexpr bool EnableDmxEmbeddedAssets = true; +# else + inline static constexpr bool EnableDmxEmbeddedAssets = false; +# endif +# ifdef FEATURE_PEN_EMBEDDED_ASSETS + inline static constexpr bool EnablePenEmbeddedAssets = true; +# else + inline static constexpr bool EnablePenEmbeddedAssets = false; +# endif +# ifdef FEATURE_PEN20_EMBEDDED_ASSETS + inline static constexpr bool EnablePen20EmbeddedAssets = true; +# else + inline static constexpr bool EnablePen20EmbeddedAssets = false; +# endif +# ifdef FEATURE_DMALL_EMBEDDED_ASSETS + inline static constexpr bool EnableDmAllEmbeddedAssets = true; +# else + inline static constexpr bool EnableDmAllEmbeddedAssets = false; +# endif +# if !defined(FEATURE_DM_EMBEDDED_ASSETS) && !defined(FEATURE_DM20_EMBEDDED_ASSETS) && \ + !defined(FEATURE_DMC_EMBEDDED_ASSETS) && !defined(FEATURE_DMX_EMBEDDED_ASSETS) && \ + !defined(FEATURE_PEN20_EMBEDDED_ASSETS) && !defined(FEATURE_DMALL_EMBEDDED_ASSETS) + inline static constexpr bool EnableMinDmEmbeddedAssets = true; +# else + inline static constexpr bool EnableMinDmEmbeddedAssets = false; +# endif +#else + inline static constexpr bool EnableDmEmbeddedAssets = false; + inline static constexpr bool EnableFullDmEmbeddedAssets = false; + inline static constexpr bool EnableDm20EmbeddedAssets = false; + inline static constexpr bool EnableDmcEmbeddedAssets = false; + inline static constexpr bool EnableDmxEmbeddedAssets = false; + inline static constexpr bool EnablePenEmbeddedAssets = false; + inline static constexpr bool EnablePen20EmbeddedAssets = false; + inline static constexpr bool EnableMinDmEmbeddedAssets = false; + inline static constexpr bool EnableDmAllEmbeddedAssets = false; #endif #ifdef FEATURE_MS_AGENT_EMBEDDED_ASSETS - inline static constexpr bool EnableMsAgentEmbeddedAssets = true; + inline static constexpr bool EnableMsAgentEmbeddedAssets = true; #else - inline static constexpr bool EnableMsAgentEmbeddedAssets = false; + inline static constexpr bool EnableMsAgentEmbeddedAssets = false; #endif #ifdef FEATURE_PKMN_EMBEDDED_ASSETS - inline static constexpr bool EnablePkmnEmbeddedAssets = true; + inline static constexpr bool EnablePkmnEmbeddedAssets = true; #else - inline static constexpr bool EnablePkmnEmbeddedAssets = false; + inline static constexpr bool EnablePkmnEmbeddedAssets = false; #endif #ifdef FEATURE_PMD_EMBEDDED_ASSETS - inline static constexpr bool EnablePmdEmbeddedAssets = true; + inline static constexpr bool EnablePmdEmbeddedAssets = true; #else - inline static constexpr bool EnablePmdEmbeddedAssets = false; + inline static constexpr bool EnablePmdEmbeddedAssets = false; #endif #ifdef FEATURE_MISC_EMBEDDED_ASSETS - inline static constexpr bool EnableMiscEmbeddedAssets = true; + inline static constexpr bool EnableMiscEmbeddedAssets = true; #else - inline static constexpr bool EnableMiscEmbeddedAssets = false; + inline static constexpr bool EnableMiscEmbeddedAssets = false; #endif #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - inline static constexpr bool EnableMemoryStatistics = true; + inline static constexpr bool EnableMemoryStatistics = true; #else - inline static constexpr bool EnableMemoryStatistics = false; + inline static constexpr bool EnableMemoryStatistics = false; #endif #if !defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER) - inline static constexpr bool EnableLogger = true; + inline static constexpr bool EnableLogger = true; #else - inline static constexpr bool EnableLogger = false; + inline static constexpr bool EnableLogger = false; #endif #ifdef FEATURE_PRELOAD_ASSETS - inline static constexpr bool EnablePreloadAssets = true; + inline static constexpr bool EnablePreloadAssets = true; #else - inline static constexpr bool EnablePreloadAssets = false; + inline static constexpr bool EnablePreloadAssets = false; #endif #ifdef FEATURE_LAZY_LOAD_ASSETS - inline static constexpr bool EnableLazyLoadAssets = true; + inline static constexpr bool EnableLazyLoadAssets = true; #else - inline static constexpr bool EnableLazyLoadAssets = false; + inline static constexpr bool EnableLazyLoadAssets = false; #endif #ifdef FEATURE_USE_HYBRID_IMAGE_BACKEND - inline static constexpr bool UseHybridImageBackend = true; + inline static constexpr bool UseHybridImageBackend = true; #else - inline static constexpr bool UseHybridImageBackend = false; + inline static constexpr bool UseHybridImageBackend = false; -#ifdef FEATURE_USE_PNGLE - inline static constexpr bool UsePngleImageBackend = true; -#else - inline static constexpr bool UsePngleImageBackend = false; -#endif +# ifdef FEATURE_USE_PNGLE + inline static constexpr bool UsePngleImageBackend = true; +# else + inline static constexpr bool UsePngleImageBackend = false; +# endif -#ifdef FEATURE_USE_STB_IMAGE - inline static constexpr bool UseStbImageBackend = true; -#else - inline static constexpr bool UseStbImageBackend = false; -#endif +# ifdef FEATURE_USE_STB_IMAGE + inline static constexpr bool UseStbImageBackend = true; +# else + inline static constexpr bool UseStbImageBackend = false; +# endif #endif #ifdef FEATURE_CUSTOM_SPRITE_SHEETS - inline static constexpr bool EnableCustomSpriteSheetsAssets = true; + inline static constexpr bool EnableCustomSpriteSheetsAssets = true; #else - inline static constexpr bool EnableCustomSpriteSheetsAssets = false; + inline static constexpr bool EnableCustomSpriteSheetsAssets = false; #endif - } - - // Global constants - namespace input { - inline static constexpr size_t MAX_INPUT_DEVICES = 256; - static_assert(MAX_INPUT_DEVICES <= INT32_MAX); - } - namespace platform { - inline static constexpr double ENABLED_MIN_CPU_PERCENT = 1.0; // in percent - inline static constexpr double TRIGGER_ANIMATION_CPU_DIFF_PERCENT = 1.0; // in percent - } - - - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_or(Enum lhs, Enum rhs) noexcept { - return static_cast(static_cast>(lhs) | static_cast>(rhs)); - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_and(Enum lhs, Enum rhs) noexcept { - return static_cast(static_cast>(lhs) & static_cast>(rhs)); - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_xor(Enum lhs, Enum rhs) noexcept { - return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_not(Enum rhs) noexcept { - return static_cast(~static_cast>(rhs)); - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_add(Enum lhs, Enum rhs) noexcept { - lhs = flag_or(lhs, rhs); - return lhs; - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_remove(Enum lhs, Enum rhs) noexcept { - return static_cast(static_cast(lhs) & ~static_cast(rhs)); - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr Enum flag_assign(Enum lhs, Enum rhs) noexcept { - lhs = flag_and(lhs, rhs); - return lhs; - } - template - requires std::is_enum_v && - (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) - BONGOCAT_NODISCARD inline constexpr bool has_flag(Enum value, Enum flag) noexcept { - return (static_cast(value) & static_cast(flag)) != 0; - } +} // namespace features + +// Global constants +namespace input { + inline static constexpr size_t MAX_INPUT_DEVICES = 256; + static_assert(MAX_INPUT_DEVICES <= INT32_MAX); +} // namespace input +namespace platform { + inline static constexpr double ENABLED_MIN_CPU_PERCENT = 1.0; // in percent + inline static constexpr double TRIGGER_ANIMATION_CPU_DIFF_PERCENT = 1.0; // in percent +} // namespace platform + +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_or(Enum lhs, Enum rhs) noexcept { + return static_cast(static_cast>(lhs) | + static_cast>(rhs)); +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_and(Enum lhs, Enum rhs) noexcept { + return static_cast(static_cast>(lhs) & + static_cast>(rhs)); +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_xor(Enum lhs, Enum rhs) noexcept { + return static_cast(static_cast>(lhs) ^ + static_cast>(rhs)); +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_not(Enum rhs) noexcept { + return static_cast(~static_cast>(rhs)); +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_add(Enum lhs, Enum rhs) noexcept { + lhs = flag_or(lhs, rhs); + return lhs; +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_remove(Enum lhs, Enum rhs) noexcept { + return static_cast(static_cast(lhs) & ~static_cast(rhs)); +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr Enum flag_assign(Enum lhs, Enum rhs) noexcept { + lhs = flag_and(lhs, rhs); + return lhs; +} +template + requires std::is_enum_v && (std::is_same_v, uint32_t> || + std::is_same_v, uint64_t>) +BONGOCAT_NODISCARD inline constexpr bool has_flag(Enum value, Enum flag) noexcept { + return (static_cast(value) & static_cast(flag)) != 0; } +} // namespace bongocat -#endif // BONGOCAT_BONGOCAT_H \ No newline at end of file +#endif // BONGOCAT_BONGOCAT_H \ No newline at end of file diff --git a/include/embedded_assets/bongocat/bongocat.h b/include/embedded_assets/bongocat/bongocat.h index 3a02c382..bab6de9b 100644 --- a/include/embedded_assets/bongocat/bongocat.h +++ b/include/embedded_assets/bongocat/bongocat.h @@ -1,33 +1,37 @@ #ifndef BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_H #define BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_H -#include "graphics/sprite_sheet.h" #include "core/bongocat.h" #include "embedded_assets/embedded_image.h" +#include "graphics/sprite_sheet.h" + #include -namespace bongocat::animation { struct animation_context_t; } +namespace bongocat::animation { +struct animation_context_t; +} namespace bongocat::assets { - // Bongocat Frames - inline static constexpr int BONGOCAT_FRAME_BOTH_UP = 0; - inline static constexpr int BONGOCAT_FRAME_LEFT_DOWN = 1; - inline static constexpr int BONGOCAT_FRAME_RIGHT_DOWN = 2; - inline static constexpr int BONGOCAT_FRAME_BOTH_DOWN = 3; +// Bongocat Frames +inline static constexpr int BONGOCAT_FRAME_BOTH_UP = 0; +inline static constexpr int BONGOCAT_FRAME_LEFT_DOWN = 1; +inline static constexpr int BONGOCAT_FRAME_RIGHT_DOWN = 2; +inline static constexpr int BONGOCAT_FRAME_BOTH_DOWN = 3; - inline static constexpr size_t BONGOCAT_SPRITE_SHEET_COLS = 4; - inline static constexpr size_t BONGOCAT_SPRITE_SHEET_ROWS = 1; - inline static constexpr size_t BONGOCAT_SPRITE_SHEET_ROW = 0; +inline static constexpr size_t BONGOCAT_SPRITE_SHEET_COLS = 4; +inline static constexpr size_t BONGOCAT_SPRITE_SHEET_ROWS = 1; +inline static constexpr size_t BONGOCAT_SPRITE_SHEET_ROW = 0; - // apparently - inline static constexpr int BONGOCAT_FRAME_WIDTH = 864; - inline static constexpr int BONGOCAT_FRAME_HEIGHT = 360; +// apparently +inline static constexpr int BONGOCAT_FRAME_WIDTH = 864; +inline static constexpr int BONGOCAT_FRAME_HEIGHT = 360; - inline static constexpr size_t BONGOCAT_EMBEDDED_IMAGES_COUNT = animation::BONGOCAT_NUM_FRAMES; - inline static constexpr size_t BONGOCAT_ANIMATIONS_COUNT = 1; +inline static constexpr size_t BONGOCAT_EMBEDDED_IMAGES_COUNT = animation::BONGOCAT_NUM_FRAMES; +inline static constexpr size_t BONGOCAT_ANIMATIONS_COUNT = 1; - [[nodiscard]] extern embedded_image_t get_bongocat_sprite(size_t i); - [[nodiscard]] extern created_result_t get_bongocat_sprite_sheet(const animation::animation_context_t& ctx, int index); -} +[[nodiscard]] extern embedded_image_t get_bongocat_sprite(size_t i); +[[nodiscard]] extern created_result_t +get_bongocat_sprite_sheet(const animation::animation_context_t& ctx, int index); +} // namespace bongocat::assets -#endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_H +#endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_H diff --git a/include/embedded_assets/bongocat/bongocat.hpp b/include/embedded_assets/bongocat/bongocat.hpp index a4ec3986..9d66181a 100644 --- a/include/embedded_assets/bongocat/bongocat.hpp +++ b/include/embedded_assets/bongocat/bongocat.hpp @@ -4,22 +4,22 @@ #include namespace bongocat::assets { - // Name: Bongo Cat - inline static constexpr char BONGOCAT_FQID_ARR[] = "bongocat"; - inline static constexpr const char* BONGOCAT_FQID = BONGOCAT_FQID_ARR; - inline static constexpr std::size_t BONGOCAT_FQID_LEN = sizeof(BONGOCAT_FQID_ARR)-1; - inline static constexpr char BONGOCAT_ID_ARR[] = "bongocat"; - inline static constexpr const char* BONGOCAT_ID = BONGOCAT_ID_ARR; - inline static constexpr std::size_t BONGOCAT_ID_LEN = sizeof(BONGOCAT_ID_ARR)-1; - inline static constexpr char BONGOCAT_NAME_ARR[] = "bongocat"; - inline static constexpr const char* BONGOCAT_NAME = BONGOCAT_NAME_ARR; - inline static constexpr std::size_t BONGOCAT_NAME_LEN = sizeof(BONGOCAT_NAME_ARR)-1; - inline static constexpr char BONGOCAT_FQNAME_ARR[] = "bongocat"; - inline static constexpr const char* BONGOCAT_FQNAME = BONGOCAT_FQNAME_ARR; - inline static constexpr std::size_t BONGOCAT_FQNAME_LEN = sizeof(BONGOCAT_FQNAME_ARR)-1; - inline static constexpr std::size_t BONGOCAT_ANIM_INDEX = 0; +// Name: Bongo Cat +inline static constexpr char BONGOCAT_FQID_ARR[] = "bongocat"; +inline static constexpr const char *BONGOCAT_FQID = BONGOCAT_FQID_ARR; +inline static constexpr std::size_t BONGOCAT_FQID_LEN = sizeof(BONGOCAT_FQID_ARR) - 1; +inline static constexpr char BONGOCAT_ID_ARR[] = "bongocat"; +inline static constexpr const char *BONGOCAT_ID = BONGOCAT_ID_ARR; +inline static constexpr std::size_t BONGOCAT_ID_LEN = sizeof(BONGOCAT_ID_ARR) - 1; +inline static constexpr char BONGOCAT_NAME_ARR[] = "bongocat"; +inline static constexpr const char *BONGOCAT_NAME = BONGOCAT_NAME_ARR; +inline static constexpr std::size_t BONGOCAT_NAME_LEN = sizeof(BONGOCAT_NAME_ARR) - 1; +inline static constexpr char BONGOCAT_FQNAME_ARR[] = "bongocat"; +inline static constexpr const char *BONGOCAT_FQNAME = BONGOCAT_FQNAME_ARR; +inline static constexpr std::size_t BONGOCAT_FQNAME_LEN = sizeof(BONGOCAT_FQNAME_ARR) - 1; +inline static constexpr std::size_t BONGOCAT_ANIM_INDEX = 0; - inline static constexpr std::size_t BONGOCAT_ANIM_COUNT = 1; -} +inline static constexpr std::size_t BONGOCAT_ANIM_COUNT = 1; +} // namespace bongocat::assets -#endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_HPP +#endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_HPP diff --git a/include/embedded_assets/bongocat/bongocat_images.h b/include/embedded_assets/bongocat/bongocat_images.h index 31405a74..e8a459d0 100644 --- a/include/embedded_assets/bongocat/bongocat_images.h +++ b/include/embedded_assets/bongocat/bongocat_images.h @@ -16,4 +16,4 @@ extern const size_t bongo_cat_right_down_png_size; extern const unsigned char bongo_cat_both_down_png[]; extern const size_t bongo_cat_both_down_png_size; -#endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_IMAGES_H +#endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_IMAGES_H diff --git a/include/embedded_assets/custom/custom_sprite.h b/include/embedded_assets/custom/custom_sprite.h index 8a3fa7e9..b9669ae3 100644 --- a/include/embedded_assets/custom/custom_sprite.h +++ b/include/embedded_assets/custom/custom_sprite.h @@ -5,179 +5,212 @@ #include namespace bongocat::assets { - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_IDLE = 0; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_BORING = 1; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_WRITING = 2; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_WRITING = 3; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_WRITING = 4; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_HAPPY = 5; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_ASLEEP = 6; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_SLEEP = 7; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_WAKE_UP = 8; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_WORKING = 9; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_WORKING = 10; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_WORKING = 11; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_MOVING = 12; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_MOVING = 13; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_MOVING = 14; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_RUNNING = 15; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_RUNNING = 16; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_RUNNING = 17; - enum class CustomAnimations : uint8_t { - Idle = CUSTOM_SPRITE_SHEET_ROW_IDLE, - Boring = CUSTOM_SPRITE_SHEET_ROW_BORING, - StartWriting = CUSTOM_SPRITE_SHEET_ROW_START_WRITING, - Writing = CUSTOM_SPRITE_SHEET_ROW_WRITING, - EndWriting = CUSTOM_SPRITE_SHEET_ROW_END_WRITING, - Happy = CUSTOM_SPRITE_SHEET_ROW_HAPPY, - ASleep = CUSTOM_SPRITE_SHEET_ROW_ASLEEP, - Sleep = CUSTOM_SPRITE_SHEET_ROW_SLEEP, - WakeUp = CUSTOM_SPRITE_SHEET_ROW_WAKE_UP, - StartWorking = CUSTOM_SPRITE_SHEET_ROW_START_WORKING, - Working = CUSTOM_SPRITE_SHEET_ROW_WORKING, - EndWorking = CUSTOM_SPRITE_SHEET_ROW_END_WORKING, - StartMoving = CUSTOM_SPRITE_SHEET_ROW_START_MOVING, - Moving = CUSTOM_SPRITE_SHEET_ROW_MOVING, - EndMoving = CUSTOM_SPRITE_SHEET_ROW_END_MOVING, - StartRunning = CUSTOM_SPRITE_SHEET_ROW_START_RUNNING, - Running = CUSTOM_SPRITE_SHEET_ROW_RUNNING, - EndRunning = CUSTOM_SPRITE_SHEET_ROW_END_RUNNING, - }; - inline static constexpr size_t CUSTOM_SPRITE_SHEET_MAX_ROWS = 18; - - // custom (sprite sheet) - inline static constexpr char CUSTOM_ID_ARR[] = "custom"; - inline static constexpr const char* CUSTOM_ID = CUSTOM_ID_ARR; - inline static constexpr std::size_t CUSTOM_ID_LEN = sizeof(CUSTOM_ID_ARR)-1; - inline static constexpr char CUSTOM_NAME_ARR[] = "custom"; - inline static constexpr const char* CUSTOM_NAME = CUSTOM_NAME_ARR; - inline static constexpr std::size_t CUSTOM_NAME_LEN = sizeof(CUSTOM_NAME_ARR)-1; - - - static inline constexpr int CUSTOM_HAPPY_CHANCE_PERCENT = 60; - - - struct custom_animation_settings_t { - int32_t idle_frames{0}; - - int32_t boring_frames{0}; - - int32_t start_writing_frames{0}; - int32_t writing_frames{0}; - int32_t end_writing_frames{0}; - int32_t happy_frames{0}; - - int32_t asleep_frames{0}; - int32_t sleep_frames{0}; - int32_t wake_up_frames{0}; - - int32_t start_working_frames{0}; - int32_t working_frames{0}; - int32_t end_working_frames{0}; - - int32_t start_moving_frames{0}; - int32_t moving_frames{0}; - int32_t end_moving_frames{0}; - - int32_t start_running_frames{0}; - int32_t running_frames{0}; - int32_t end_running_frames{0}; - - int32_t feature_toggle_writing_frames{-1}; - int32_t feature_toggle_writing_frames_random{-1}; - int32_t feature_mirror_x_moving{-1}; - - // row lines (optional) - int32_t idle_row_index{-1}; - - int32_t boring_row_index{-1}; - - int32_t start_writing_row_index{-1}; - int32_t writing_row_index{-1}; - int32_t end_writing_row_index{-1}; - int32_t happy_row_index{-1}; - - int32_t asleep_row_index{-1}; - int32_t sleep_row_index{-1}; - int32_t wake_up_row_index{-1}; - - int32_t start_working_row_index{-1}; - int32_t working_row_index{-1}; - int32_t end_working_row_index{-1}; - - int32_t start_moving_row_index{-1}; - int32_t moving_row_index{-1}; - int32_t end_moving_row_index{-1}; - - int32_t start_running_row_index{-1}; - int32_t running_row_index{-1}; - int32_t end_running_row_index{-1}; - - int32_t rows{-1}; - }; - - inline int get_custom_animation_settings_rows_count(const custom_animation_settings_t& sprite_sheet_settings) { - if (sprite_sheet_settings.rows >= 1) { - return sprite_sheet_settings.rows; - } - - int sprite_sheet_rows{0}; - - // detect sprite sheet rows - if (sprite_sheet_settings.idle_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.boring_frames > 0) sprite_sheet_rows++; - - if (sprite_sheet_settings.start_writing_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.writing_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.end_writing_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.happy_frames > 0) sprite_sheet_rows++; - - if (sprite_sheet_settings.asleep_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.sleep_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.wake_up_frames > 0) sprite_sheet_rows++; - - if (sprite_sheet_settings.start_working_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.working_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.end_working_frames > 0) sprite_sheet_rows++; - - if (sprite_sheet_settings.start_moving_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.moving_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.end_moving_frames > 0) sprite_sheet_rows++; - - if (sprite_sheet_settings.start_running_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.running_frames > 0) sprite_sheet_rows++; - if (sprite_sheet_settings.end_running_frames > 0) sprite_sheet_rows++; - - return sprite_sheet_rows; - } - inline int get_custom_animation_settings_max_cols(const custom_animation_settings_t& sprite_sheet_settings) { - int sprite_sheet_cols = sprite_sheet_settings.idle_frames; - - if (sprite_sheet_settings.boring_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.boring_frames; - - if (sprite_sheet_settings.start_writing_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.start_writing_frames; - if (sprite_sheet_settings.writing_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.writing_frames; - if (sprite_sheet_settings.end_writing_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.end_writing_frames; - if (sprite_sheet_settings.happy_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.happy_frames; - - if (sprite_sheet_settings.asleep_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.asleep_frames; - if (sprite_sheet_settings.sleep_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.sleep_frames; - if (sprite_sheet_settings.wake_up_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.wake_up_frames; - - if (sprite_sheet_settings.start_working_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.start_working_frames; - if (sprite_sheet_settings.working_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.working_frames; - if (sprite_sheet_settings.end_working_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.end_working_frames; - - if (sprite_sheet_settings.start_moving_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.start_moving_frames; - if (sprite_sheet_settings.moving_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.moving_frames; - if (sprite_sheet_settings.end_moving_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.end_moving_frames; - - if (sprite_sheet_settings.start_running_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.start_running_frames; - if (sprite_sheet_settings.running_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.running_frames; - if (sprite_sheet_settings.end_running_frames >= sprite_sheet_cols) sprite_sheet_cols = sprite_sheet_settings.end_running_frames; - - return sprite_sheet_cols; - } +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_IDLE = 0; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_BORING = 1; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_WRITING = 2; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_WRITING = 3; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_WRITING = 4; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_HAPPY = 5; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_ASLEEP = 6; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_SLEEP = 7; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_WAKE_UP = 8; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_WORKING = 9; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_WORKING = 10; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_WORKING = 11; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_MOVING = 12; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_MOVING = 13; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_MOVING = 14; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_START_RUNNING = 15; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_RUNNING = 16; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_ROW_END_RUNNING = 17; +enum class CustomAnimations : uint8_t { + Idle = CUSTOM_SPRITE_SHEET_ROW_IDLE, + Boring = CUSTOM_SPRITE_SHEET_ROW_BORING, + StartWriting = CUSTOM_SPRITE_SHEET_ROW_START_WRITING, + Writing = CUSTOM_SPRITE_SHEET_ROW_WRITING, + EndWriting = CUSTOM_SPRITE_SHEET_ROW_END_WRITING, + Happy = CUSTOM_SPRITE_SHEET_ROW_HAPPY, + ASleep = CUSTOM_SPRITE_SHEET_ROW_ASLEEP, + Sleep = CUSTOM_SPRITE_SHEET_ROW_SLEEP, + WakeUp = CUSTOM_SPRITE_SHEET_ROW_WAKE_UP, + StartWorking = CUSTOM_SPRITE_SHEET_ROW_START_WORKING, + Working = CUSTOM_SPRITE_SHEET_ROW_WORKING, + EndWorking = CUSTOM_SPRITE_SHEET_ROW_END_WORKING, + StartMoving = CUSTOM_SPRITE_SHEET_ROW_START_MOVING, + Moving = CUSTOM_SPRITE_SHEET_ROW_MOVING, + EndMoving = CUSTOM_SPRITE_SHEET_ROW_END_MOVING, + StartRunning = CUSTOM_SPRITE_SHEET_ROW_START_RUNNING, + Running = CUSTOM_SPRITE_SHEET_ROW_RUNNING, + EndRunning = CUSTOM_SPRITE_SHEET_ROW_END_RUNNING, +}; +inline static constexpr size_t CUSTOM_SPRITE_SHEET_MAX_ROWS = 18; + +// custom (sprite sheet) +inline static constexpr char CUSTOM_ID_ARR[] = "custom"; +inline static constexpr const char *CUSTOM_ID = CUSTOM_ID_ARR; +inline static constexpr std::size_t CUSTOM_ID_LEN = sizeof(CUSTOM_ID_ARR) - 1; +inline static constexpr char CUSTOM_NAME_ARR[] = "custom"; +inline static constexpr const char *CUSTOM_NAME = CUSTOM_NAME_ARR; +inline static constexpr std::size_t CUSTOM_NAME_LEN = sizeof(CUSTOM_NAME_ARR) - 1; + +static inline constexpr int CUSTOM_HAPPY_CHANCE_PERCENT = 60; + +struct custom_animation_settings_t { + int32_t idle_frames{0}; + + int32_t boring_frames{0}; + + int32_t start_writing_frames{0}; + int32_t writing_frames{0}; + int32_t end_writing_frames{0}; + int32_t happy_frames{0}; + + int32_t asleep_frames{0}; + int32_t sleep_frames{0}; + int32_t wake_up_frames{0}; + + int32_t start_working_frames{0}; + int32_t working_frames{0}; + int32_t end_working_frames{0}; + + int32_t start_moving_frames{0}; + int32_t moving_frames{0}; + int32_t end_moving_frames{0}; + + int32_t start_running_frames{0}; + int32_t running_frames{0}; + int32_t end_running_frames{0}; + + int32_t feature_toggle_writing_frames{-1}; + int32_t feature_toggle_writing_frames_random{-1}; + int32_t feature_mirror_x_moving{-1}; + + // row lines (optional) + int32_t idle_row_index{-1}; + + int32_t boring_row_index{-1}; + + int32_t start_writing_row_index{-1}; + int32_t writing_row_index{-1}; + int32_t end_writing_row_index{-1}; + int32_t happy_row_index{-1}; + + int32_t asleep_row_index{-1}; + int32_t sleep_row_index{-1}; + int32_t wake_up_row_index{-1}; + + int32_t start_working_row_index{-1}; + int32_t working_row_index{-1}; + int32_t end_working_row_index{-1}; + + int32_t start_moving_row_index{-1}; + int32_t moving_row_index{-1}; + int32_t end_moving_row_index{-1}; + + int32_t start_running_row_index{-1}; + int32_t running_row_index{-1}; + int32_t end_running_row_index{-1}; + + int32_t rows{-1}; +}; + +inline int get_custom_animation_settings_rows_count(const custom_animation_settings_t& sprite_sheet_settings) { + if (sprite_sheet_settings.rows >= 1) { + return sprite_sheet_settings.rows; + } + + int sprite_sheet_rows{0}; + + // detect sprite sheet rows + if (sprite_sheet_settings.idle_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.boring_frames > 0) + sprite_sheet_rows++; + + if (sprite_sheet_settings.start_writing_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.writing_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.end_writing_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.happy_frames > 0) + sprite_sheet_rows++; + + if (sprite_sheet_settings.asleep_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.sleep_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.wake_up_frames > 0) + sprite_sheet_rows++; + + if (sprite_sheet_settings.start_working_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.working_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.end_working_frames > 0) + sprite_sheet_rows++; + + if (sprite_sheet_settings.start_moving_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.moving_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.end_moving_frames > 0) + sprite_sheet_rows++; + + if (sprite_sheet_settings.start_running_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.running_frames > 0) + sprite_sheet_rows++; + if (sprite_sheet_settings.end_running_frames > 0) + sprite_sheet_rows++; + + return sprite_sheet_rows; +} +inline int get_custom_animation_settings_max_cols(const custom_animation_settings_t& sprite_sheet_settings) { + int sprite_sheet_cols = sprite_sheet_settings.idle_frames; + + if (sprite_sheet_settings.boring_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.boring_frames; + + if (sprite_sheet_settings.start_writing_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.start_writing_frames; + if (sprite_sheet_settings.writing_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.writing_frames; + if (sprite_sheet_settings.end_writing_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.end_writing_frames; + if (sprite_sheet_settings.happy_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.happy_frames; + + if (sprite_sheet_settings.asleep_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.asleep_frames; + if (sprite_sheet_settings.sleep_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.sleep_frames; + if (sprite_sheet_settings.wake_up_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.wake_up_frames; + + if (sprite_sheet_settings.start_working_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.start_working_frames; + if (sprite_sheet_settings.working_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.working_frames; + if (sprite_sheet_settings.end_working_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.end_working_frames; + + if (sprite_sheet_settings.start_moving_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.start_moving_frames; + if (sprite_sheet_settings.moving_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.moving_frames; + if (sprite_sheet_settings.end_moving_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.end_moving_frames; + + if (sprite_sheet_settings.start_running_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.start_running_frames; + if (sprite_sheet_settings.running_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.running_frames; + if (sprite_sheet_settings.end_running_frames >= sprite_sheet_cols) + sprite_sheet_cols = sprite_sheet_settings.end_running_frames; + + return sprite_sheet_cols; } +} // namespace bongocat::assets -#endif // BONGOCAT_EMBEDDED_ASSETS_CUSTOM_SPRITE_H +#endif // BONGOCAT_EMBEDDED_ASSETS_CUSTOM_SPRITE_H diff --git a/include/embedded_assets/embedded_image.h b/include/embedded_assets/embedded_image.h index ad421fb7..86022d82 100644 --- a/include/embedded_assets/embedded_image.h +++ b/include/embedded_assets/embedded_image.h @@ -2,33 +2,34 @@ #define BONGOCAT_EMBEDDED_ASSETS_IMAGE_H #include "config/config.h" + #include namespace bongocat::assets { - struct embedded_image_t { - const unsigned char *data{nullptr}; - size_t size{0}; - const char *name{""}; - }; +struct embedded_image_t { + const unsigned char *data{nullptr}; + size_t size{0}; + const char *name{""}; +}; - struct config_animation_entry_t { - const char* name{""}; - const char* id{""}; - const char* fqid{""}; - const char* fqname{""}; - int anim_index{0}; - config::config_animation_dm_set_t set{config::config_animation_dm_set_t::None}; - config::config_animation_sprite_sheet_layout_t layout{config::config_animation_sprite_sheet_layout_t::None}; - }; - struct config_custom_animation_entry_t { - const char* name{""}; - const char* id{""}; - const char* fqid{""}; - const char* fqname{""}; - int anim_index{0}; - config::config_animation_custom_set_t set{config::config_animation_custom_set_t::None}; - config::config_animation_sprite_sheet_layout_t layout{config::config_animation_sprite_sheet_layout_t::None}; - }; -} +struct config_animation_entry_t { + const char *name{""}; + const char *id{""}; + const char *fqid{""}; + const char *fqname{""}; + int anim_index{0}; + config::config_animation_dm_set_t set{config::config_animation_dm_set_t::None}; + config::config_animation_sprite_sheet_layout_t layout{config::config_animation_sprite_sheet_layout_t::None}; +}; +struct config_custom_animation_entry_t { + const char *name{""}; + const char *id{""}; + const char *fqid{""}; + const char *fqname{""}; + int anim_index{0}; + config::config_animation_custom_set_t set{config::config_animation_custom_set_t::None}; + config::config_animation_sprite_sheet_layout_t layout{config::config_animation_sprite_sheet_layout_t::None}; +}; +} // namespace bongocat::assets -#endif // BONGOCAT_EMBEDDED_ASSETS_IMAGE_H +#endif // BONGOCAT_EMBEDDED_ASSETS_IMAGE_H diff --git a/include/embedded_assets/min_dm/min_dm.hpp b/include/embedded_assets/min_dm/min_dm.hpp index c483e783..235e871f 100644 --- a/include/embedded_assets/min_dm/min_dm.hpp +++ b/include/embedded_assets/min_dm/min_dm.hpp @@ -4,91 +4,91 @@ #include namespace bongocat::assets { - // Botamon - inline static constexpr std::size_t DM_BOTAMON_SPRITE_SHEET_COLS = 11; - inline static constexpr std::size_t DM_BOTAMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_BOTAMON_SPRITE_SHEET_FRAMES_COUNT = 11; - inline static constexpr std::size_t DM_BOTAMON_ANIM_INDEX = 0; - - // Koromon - inline static constexpr std::size_t DM_KOROMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_KOROMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_KOROMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_KOROMON_ANIM_INDEX = 1; - - // Agumon - inline static constexpr std::size_t DM_AGUMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_AGUMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_AGUMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_AGUMON_ANIM_INDEX = 2; - - // Betamon - inline static constexpr std::size_t DM_BETAMON_SPRITE_SHEET_COLS = 10; - inline static constexpr std::size_t DM_BETAMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_BETAMON_SPRITE_SHEET_FRAMES_COUNT = 10; - inline static constexpr std::size_t DM_BETAMON_ANIM_INDEX = 3; - - // Greymon - inline static constexpr std::size_t DM_GREYMON_SPRITE_SHEET_COLS = 10; - inline static constexpr std::size_t DM_GREYMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_GREYMON_SPRITE_SHEET_FRAMES_COUNT = 10; - inline static constexpr std::size_t DM_GREYMON_ANIM_INDEX = 4; - - // Tyranomon - inline static constexpr std::size_t DM_TYRANOMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_TYRANOMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_TYRANOMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_TYRANOMON_ANIM_INDEX = 5; - - // Devimon - inline static constexpr std::size_t DM_DEVIMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_DEVIMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_DEVIMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_DEVIMON_ANIM_INDEX = 6; - - // Meramon - inline static constexpr std::size_t DM_MERAMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_MERAMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_MERAMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_MERAMON_ANIM_INDEX = 7; - - // Airdramon - inline static constexpr std::size_t DM_AIRDRAMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_AIRDRAMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_AIRDRAMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_AIRDRAMON_ANIM_INDEX = 8; - - // Seadramon - inline static constexpr std::size_t DM_SEADRAMON_SPRITE_SHEET_COLS = 8; - inline static constexpr std::size_t DM_SEADRAMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_SEADRAMON_SPRITE_SHEET_FRAMES_COUNT = 8; - inline static constexpr std::size_t DM_SEADRAMON_ANIM_INDEX = 9; - - // Numemon - inline static constexpr std::size_t DM_NUMEMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_NUMEMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_NUMEMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_NUMEMON_ANIM_INDEX = 10; - - // Metal Greymon - inline static constexpr std::size_t DM_METAL_GREYMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_METAL_GREYMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_METAL_GREYMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_METAL_GREYMON_ANIM_INDEX = 11; - - // Mamemon - inline static constexpr std::size_t DM_MAMEMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_MAMEMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_MAMEMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_MAMEMON_ANIM_INDEX = 12; - - // Monzaemon - inline static constexpr std::size_t DM_MONZAEMON_SPRITE_SHEET_COLS = 9; - inline static constexpr std::size_t DM_MONZAEMON_SPRITE_SHEET_ROWS = 1; - inline static constexpr std::size_t DM_MONZAEMON_SPRITE_SHEET_FRAMES_COUNT = 9; - inline static constexpr std::size_t DM_MONZAEMON_ANIM_INDEX = 13; - - inline static constexpr std::size_t MIN_DM_ANIM_COUNT = 14; -} - -#endif // DM_EMBEDDED_ASSETS_HPP \ No newline at end of file +// Botamon +inline static constexpr std::size_t DM_BOTAMON_SPRITE_SHEET_COLS = 11; +inline static constexpr std::size_t DM_BOTAMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_BOTAMON_SPRITE_SHEET_FRAMES_COUNT = 11; +inline static constexpr std::size_t DM_BOTAMON_ANIM_INDEX = 0; + +// Koromon +inline static constexpr std::size_t DM_KOROMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_KOROMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_KOROMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_KOROMON_ANIM_INDEX = 1; + +// Agumon +inline static constexpr std::size_t DM_AGUMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_AGUMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_AGUMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_AGUMON_ANIM_INDEX = 2; + +// Betamon +inline static constexpr std::size_t DM_BETAMON_SPRITE_SHEET_COLS = 10; +inline static constexpr std::size_t DM_BETAMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_BETAMON_SPRITE_SHEET_FRAMES_COUNT = 10; +inline static constexpr std::size_t DM_BETAMON_ANIM_INDEX = 3; + +// Greymon +inline static constexpr std::size_t DM_GREYMON_SPRITE_SHEET_COLS = 10; +inline static constexpr std::size_t DM_GREYMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_GREYMON_SPRITE_SHEET_FRAMES_COUNT = 10; +inline static constexpr std::size_t DM_GREYMON_ANIM_INDEX = 4; + +// Tyranomon +inline static constexpr std::size_t DM_TYRANOMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_TYRANOMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_TYRANOMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_TYRANOMON_ANIM_INDEX = 5; + +// Devimon +inline static constexpr std::size_t DM_DEVIMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_DEVIMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_DEVIMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_DEVIMON_ANIM_INDEX = 6; + +// Meramon +inline static constexpr std::size_t DM_MERAMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_MERAMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_MERAMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_MERAMON_ANIM_INDEX = 7; + +// Airdramon +inline static constexpr std::size_t DM_AIRDRAMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_AIRDRAMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_AIRDRAMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_AIRDRAMON_ANIM_INDEX = 8; + +// Seadramon +inline static constexpr std::size_t DM_SEADRAMON_SPRITE_SHEET_COLS = 8; +inline static constexpr std::size_t DM_SEADRAMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_SEADRAMON_SPRITE_SHEET_FRAMES_COUNT = 8; +inline static constexpr std::size_t DM_SEADRAMON_ANIM_INDEX = 9; + +// Numemon +inline static constexpr std::size_t DM_NUMEMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_NUMEMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_NUMEMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_NUMEMON_ANIM_INDEX = 10; + +// Metal Greymon +inline static constexpr std::size_t DM_METAL_GREYMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_METAL_GREYMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_METAL_GREYMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_METAL_GREYMON_ANIM_INDEX = 11; + +// Mamemon +inline static constexpr std::size_t DM_MAMEMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_MAMEMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_MAMEMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_MAMEMON_ANIM_INDEX = 12; + +// Monzaemon +inline static constexpr std::size_t DM_MONZAEMON_SPRITE_SHEET_COLS = 9; +inline static constexpr std::size_t DM_MONZAEMON_SPRITE_SHEET_ROWS = 1; +inline static constexpr std::size_t DM_MONZAEMON_SPRITE_SHEET_FRAMES_COUNT = 9; +inline static constexpr std::size_t DM_MONZAEMON_ANIM_INDEX = 13; + +inline static constexpr std::size_t MIN_DM_ANIM_COUNT = 14; +} // namespace bongocat::assets + +#endif // DM_EMBEDDED_ASSETS_HPP \ No newline at end of file diff --git a/include/embedded_assets/min_dm/min_dm_images.h b/include/embedded_assets/min_dm/min_dm_images.h index 2d55959b..f2a1697b 100644 --- a/include/embedded_assets/min_dm/min_dm_images.h +++ b/include/embedded_assets/min_dm/min_dm_images.h @@ -59,4 +59,4 @@ extern const size_t dm_mamemon_png_size; extern const unsigned char dm_monzaemon_png[]; extern const size_t dm_monzaemon_png_size; -#endif // DM_EMBEDDED_ASSETS_IMAGES_H \ No newline at end of file +#endif // DM_EMBEDDED_ASSETS_IMAGES_H \ No newline at end of file diff --git a/include/embedded_assets/min_dm/min_dm_sprite.h b/include/embedded_assets/min_dm/min_dm_sprite.h index 8aab16c1..b0985fdd 100644 --- a/include/embedded_assets/min_dm/min_dm_sprite.h +++ b/include/embedded_assets/min_dm/min_dm_sprite.h @@ -4,7 +4,7 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::assets { - [[nodiscard]] extern embedded_image_t get_min_dm_sprite_sheet(size_t i); +[[nodiscard]] extern embedded_image_t get_min_dm_sprite_sheet(size_t i); } #endif \ No newline at end of file diff --git a/include/embedded_assets/misc/misc.hpp b/include/embedded_assets/misc/misc.hpp index b26b8122..a7991b25 100644 --- a/include/embedded_assets/misc/misc.hpp +++ b/include/embedded_assets/misc/misc.hpp @@ -1,45 +1,46 @@ #ifndef MISC_EMBEDDED_ASSETS_HPP #define MISC_EMBEDDED_ASSETS_HPP -#include #include "embedded_assets/custom/custom_sprite.h" +#include + namespace bongocat::assets { - // neko - inline static constexpr char MISC_NEKO_FQID_ARR[] = "misc:neko"; - inline static constexpr const char* MISC_NEKO_FQID = MISC_NEKO_FQID_ARR; - inline static constexpr std::size_t MISC_NEKO_FQID_LEN = sizeof(MISC_NEKO_FQID_ARR)-1; - inline static constexpr char MISC_NEKO_ID_ARR[] = "neko"; - inline static constexpr const char* MISC_NEKO_ID = MISC_NEKO_ID_ARR; - inline static constexpr std::size_t MISC_NEKO_ID_LEN = sizeof(MISC_NEKO_ID_ARR)-1; - inline static constexpr char MISC_NEKO_NAME_ARR[] = "neko"; - inline static constexpr const char* MISC_NEKO_NAME = MISC_NEKO_NAME_ARR; - inline static constexpr std::size_t MISC_NEKO_NAME_LEN = sizeof(MISC_NEKO_NAME_ARR)-1; - inline static constexpr char MISC_NEKO_FQNAME_ARR[] = "misc:neko"; - inline static constexpr const char* MISC_NEKO_FQNAME = MISC_NEKO_FQNAME_ARR; - inline static constexpr std::size_t MISC_NEKO_FQNAME_LEN = sizeof(MISC_NEKO_FQNAME_ARR)-1; - inline static constexpr std::size_t MISC_NEKO_ANIM_INDEX = 0; - inline static constexpr custom_animation_settings_t MISC_NEKO_SPRITE_SHEET_SETTINGS { - .idle_frames = 2, - .boring_frames = 2, - .writing_frames = 2, - .happy_frames = 2, - .asleep_frames = 2, - .sleep_frames = 2, - .wake_up_frames = 1, - .working_frames = 2, - .moving_frames = 2, - .feature_toggle_writing_frames = 1, - }; - inline static constexpr std::size_t MISC_NEKO_SPRITE_SHEET_ROWS = 9; - inline static constexpr std::size_t MISC_NEKO_SPRITE_SHEET_MAX_COLS = 2; +// neko +inline static constexpr char MISC_NEKO_FQID_ARR[] = "misc:neko"; +inline static constexpr const char *MISC_NEKO_FQID = MISC_NEKO_FQID_ARR; +inline static constexpr std::size_t MISC_NEKO_FQID_LEN = sizeof(MISC_NEKO_FQID_ARR) - 1; +inline static constexpr char MISC_NEKO_ID_ARR[] = "neko"; +inline static constexpr const char *MISC_NEKO_ID = MISC_NEKO_ID_ARR; +inline static constexpr std::size_t MISC_NEKO_ID_LEN = sizeof(MISC_NEKO_ID_ARR) - 1; +inline static constexpr char MISC_NEKO_NAME_ARR[] = "neko"; +inline static constexpr const char *MISC_NEKO_NAME = MISC_NEKO_NAME_ARR; +inline static constexpr std::size_t MISC_NEKO_NAME_LEN = sizeof(MISC_NEKO_NAME_ARR) - 1; +inline static constexpr char MISC_NEKO_FQNAME_ARR[] = "misc:neko"; +inline static constexpr const char *MISC_NEKO_FQNAME = MISC_NEKO_FQNAME_ARR; +inline static constexpr std::size_t MISC_NEKO_FQNAME_LEN = sizeof(MISC_NEKO_FQNAME_ARR) - 1; +inline static constexpr std::size_t MISC_NEKO_ANIM_INDEX = 0; +inline static constexpr custom_animation_settings_t MISC_NEKO_SPRITE_SHEET_SETTINGS{ + .idle_frames = 2, + .boring_frames = 2, + .writing_frames = 2, + .happy_frames = 2, + .asleep_frames = 2, + .sleep_frames = 2, + .wake_up_frames = 1, + .working_frames = 2, + .moving_frames = 2, + .feature_toggle_writing_frames = 1, +}; +inline static constexpr std::size_t MISC_NEKO_SPRITE_SHEET_ROWS = 9; +inline static constexpr std::size_t MISC_NEKO_SPRITE_SHEET_MAX_COLS = 2; - inline static constexpr std::size_t MAX_MISC_ANIM_INDEX = 0; - inline static constexpr std::size_t MISC_ANIM_COUNT = 1; - // custom sprite sheet (at run time) - inline static constexpr std::size_t CUSTOM_ANIM_INDEX = 1; +inline static constexpr std::size_t MAX_MISC_ANIM_INDEX = 0; +inline static constexpr std::size_t MISC_ANIM_COUNT = 1; +// custom sprite sheet (at run time) +inline static constexpr std::size_t CUSTOM_ANIM_INDEX = 1; - inline static constexpr size_t MISC_MAX_SPRITE_SHEET_COL_FRAMES = 2; -} +inline static constexpr size_t MISC_MAX_SPRITE_SHEET_COL_FRAMES = 2; +} // namespace bongocat::assets -#endif // MISC_EMBEDDED_ASSETS_HPP \ No newline at end of file +#endif // MISC_EMBEDDED_ASSETS_HPP \ No newline at end of file diff --git a/include/embedded_assets/misc/misc_images.h b/include/embedded_assets/misc/misc_images.h index 880a1a5a..62ce6582 100644 --- a/include/embedded_assets/misc/misc_images.h +++ b/include/embedded_assets/misc/misc_images.h @@ -7,4 +7,4 @@ extern const unsigned char misc_neko_png[]; extern const size_t misc_neko_png_size; -#endif // MISC_EMBEDDED_ASSETS_IMAGES_H \ No newline at end of file +#endif // MISC_EMBEDDED_ASSETS_IMAGES_H \ No newline at end of file diff --git a/include/embedded_assets/misc/misc_sprite.h b/include/embedded_assets/misc/misc_sprite.h index 756be7ba..2cd947fe 100644 --- a/include/embedded_assets/misc/misc_sprite.h +++ b/include/embedded_assets/misc/misc_sprite.h @@ -4,11 +4,11 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::assets { - inline static constexpr size_t MISC_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 1; - inline static constexpr size_t MISC_ANIMATIONS_COUNT = 1; +inline static constexpr size_t MISC_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 1; +inline static constexpr size_t MISC_ANIMATIONS_COUNT = 1; - [[nodiscard]] extern embedded_image_t get_misc_sprite_sheet(size_t i); - [[nodiscard]] extern custom_animation_settings_t get_misc_sprite_sheet_columns(size_t i); -} +[[nodiscard]] extern embedded_image_t get_misc_sprite_sheet(size_t i); +[[nodiscard]] extern custom_animation_settings_t get_misc_sprite_sheet_columns(size_t i); +} // namespace bongocat::assets #endif \ No newline at end of file diff --git a/include/embedded_assets/ms_agent/ms_agent.hpp b/include/embedded_assets/ms_agent/ms_agent.hpp index e7fa11c7..e2aed5b8 100644 --- a/include/embedded_assets/ms_agent/ms_agent.hpp +++ b/include/embedded_assets/ms_agent/ms_agent.hpp @@ -4,115 +4,114 @@ #include namespace bongocat::assets { - // Name: Clippy - inline static constexpr size_t CLIPPY_SPRITE_SHEET_COLS = 40; - inline static constexpr size_t CLIPPY_SPRITE_SHEET_ROWS = 6; - inline static constexpr size_t CLIPPY_FRAMES_IDLE = 4; - inline static constexpr size_t CLIPPY_FRAMES_BORING = 40; - inline static constexpr size_t CLIPPY_FRAMES_START_WRITING = 9; - inline static constexpr size_t CLIPPY_FRAMES_WRITING = 35; - inline static constexpr size_t CLIPPY_FRAMES_END_WRITING = 5; - inline static constexpr size_t CLIPPY_FRAMES_SLEEP = 19; - inline static constexpr size_t CLIPPY_FRAMES_WAKE_UP = 16; +// Name: Clippy +inline static constexpr size_t CLIPPY_SPRITE_SHEET_COLS = 40; +inline static constexpr size_t CLIPPY_SPRITE_SHEET_ROWS = 6; +inline static constexpr size_t CLIPPY_FRAMES_IDLE = 4; +inline static constexpr size_t CLIPPY_FRAMES_BORING = 40; +inline static constexpr size_t CLIPPY_FRAMES_START_WRITING = 9; +inline static constexpr size_t CLIPPY_FRAMES_WRITING = 35; +inline static constexpr size_t CLIPPY_FRAMES_END_WRITING = 5; +inline static constexpr size_t CLIPPY_FRAMES_SLEEP = 19; +inline static constexpr size_t CLIPPY_FRAMES_WAKE_UP = 16; - inline static constexpr char CLIPPY_FQID_ARR[] = "ms_agent:clippy"; - inline static constexpr const char* CLIPPY_FQID = CLIPPY_FQID_ARR; - inline static constexpr std::size_t CLIPPY_FQID_LEN = sizeof(CLIPPY_FQID_ARR)-1; - inline static constexpr char CLIPPY_ID_ARR[] = "clippy"; - inline static constexpr const char* CLIPPY_ID = CLIPPY_ID_ARR; - inline static constexpr std::size_t CLIPPY_ID_LEN = sizeof(CLIPPY_ID_ARR)-1; - inline static constexpr char CLIPPY_NAME_ARR[] = "Clippy"; - inline static constexpr const char* CLIPPY_NAME = CLIPPY_NAME_ARR; - inline static constexpr std::size_t CLIPPY_NAME_LEN = sizeof(CLIPPY_NAME_ARR)-1; - inline static constexpr char CLIPPY_FQNAME_ARR[] = "ms_agent:Clippy"; - inline static constexpr const char* CLIPPY_FQNAME = CLIPPY_FQNAME_ARR; - inline static constexpr std::size_t CLIPPY_FQNAME_LEN = sizeof(CLIPPY_FQNAME_ARR)-1; - inline static constexpr size_t CLIPPY_ANIM_INDEX = 0; +inline static constexpr char CLIPPY_FQID_ARR[] = "ms_agent:clippy"; +inline static constexpr const char *CLIPPY_FQID = CLIPPY_FQID_ARR; +inline static constexpr std::size_t CLIPPY_FQID_LEN = sizeof(CLIPPY_FQID_ARR) - 1; +inline static constexpr char CLIPPY_ID_ARR[] = "clippy"; +inline static constexpr const char *CLIPPY_ID = CLIPPY_ID_ARR; +inline static constexpr std::size_t CLIPPY_ID_LEN = sizeof(CLIPPY_ID_ARR) - 1; +inline static constexpr char CLIPPY_NAME_ARR[] = "Clippy"; +inline static constexpr const char *CLIPPY_NAME = CLIPPY_NAME_ARR; +inline static constexpr std::size_t CLIPPY_NAME_LEN = sizeof(CLIPPY_NAME_ARR) - 1; +inline static constexpr char CLIPPY_FQNAME_ARR[] = "ms_agent:Clippy"; +inline static constexpr const char *CLIPPY_FQNAME = CLIPPY_FQNAME_ARR; +inline static constexpr std::size_t CLIPPY_FQNAME_LEN = sizeof(CLIPPY_FQNAME_ARR) - 1; +inline static constexpr size_t CLIPPY_ANIM_INDEX = 0; #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - // Name: Links - inline static constexpr size_t LINKS_SPRITE_SHEET_COLS = 35; - inline static constexpr size_t LINKS_SPRITE_SHEET_ROWS = 6; - inline static constexpr size_t LINKS_FRAMES_IDLE = 3; - inline static constexpr size_t LINKS_FRAMES_BORING = 18; - inline static constexpr size_t LINKS_FRAMES_START_WRITING = 13; - inline static constexpr size_t LINKS_FRAMES_WRITING = 35; - inline static constexpr size_t LINKS_FRAMES_END_WRITING = 5; - inline static constexpr size_t LINKS_FRAMES_SLEEP = 20; - inline static constexpr size_t LINKS_FRAMES_WAKE_UP = 14; +// Name: Links +inline static constexpr size_t LINKS_SPRITE_SHEET_COLS = 35; +inline static constexpr size_t LINKS_SPRITE_SHEET_ROWS = 6; +inline static constexpr size_t LINKS_FRAMES_IDLE = 3; +inline static constexpr size_t LINKS_FRAMES_BORING = 18; +inline static constexpr size_t LINKS_FRAMES_START_WRITING = 13; +inline static constexpr size_t LINKS_FRAMES_WRITING = 35; +inline static constexpr size_t LINKS_FRAMES_END_WRITING = 5; +inline static constexpr size_t LINKS_FRAMES_SLEEP = 20; +inline static constexpr size_t LINKS_FRAMES_WAKE_UP = 14; - inline static constexpr char LINKS_FQID_ARR[] = "ms_agent:links"; - inline static constexpr const char* LINKS_FQID = LINKS_FQID_ARR; - inline static constexpr std::size_t LINKS_FQID_LEN = sizeof(LINKS_FQID_ARR)-1; - inline static constexpr char LINKS_ID_ARR[] = "links"; - inline static constexpr const char* LINKS_ID = LINKS_ID_ARR; - inline static constexpr std::size_t LINKS_ID_LEN = sizeof(LINKS_ID_ARR)-1; - inline static constexpr char LINKS_NAME_ARR[] = "Links"; - inline static constexpr const char* LINKS_NAME = LINKS_NAME_ARR; - inline static constexpr std::size_t LINKS_NAME_LEN = sizeof(LINKS_NAME_ARR)-1; - inline static constexpr char LINKS_FQNAME_ARR[] = "ms_agent:Links"; - inline static constexpr const char* LINKS_FQNAME = LINKS_FQNAME_ARR; - inline static constexpr std::size_t LINKS_FQNAME_LEN = sizeof(LINKS_FQNAME_ARR)-1; - inline static constexpr size_t LINKS_ANIM_INDEX = 1; +inline static constexpr char LINKS_FQID_ARR[] = "ms_agent:links"; +inline static constexpr const char *LINKS_FQID = LINKS_FQID_ARR; +inline static constexpr std::size_t LINKS_FQID_LEN = sizeof(LINKS_FQID_ARR) - 1; +inline static constexpr char LINKS_ID_ARR[] = "links"; +inline static constexpr const char *LINKS_ID = LINKS_ID_ARR; +inline static constexpr std::size_t LINKS_ID_LEN = sizeof(LINKS_ID_ARR) - 1; +inline static constexpr char LINKS_NAME_ARR[] = "Links"; +inline static constexpr const char *LINKS_NAME = LINKS_NAME_ARR; +inline static constexpr std::size_t LINKS_NAME_LEN = sizeof(LINKS_NAME_ARR) - 1; +inline static constexpr char LINKS_FQNAME_ARR[] = "ms_agent:Links"; +inline static constexpr const char *LINKS_FQNAME = LINKS_FQNAME_ARR; +inline static constexpr std::size_t LINKS_FQNAME_LEN = sizeof(LINKS_FQNAME_ARR) - 1; +inline static constexpr size_t LINKS_ANIM_INDEX = 1; - // Name: Rover - inline static constexpr size_t ROVER_SPRITE_SHEET_COLS = 102; - inline static constexpr size_t ROVER_SPRITE_SHEET_ROWS = 6; - inline static constexpr size_t ROVER_FRAMES_IDLE = 1; - inline static constexpr size_t ROVER_FRAMES_BORING = 102; - inline static constexpr size_t ROVER_FRAMES_START_WRITING = 9; - inline static constexpr size_t ROVER_FRAMES_WRITING = 34; - inline static constexpr size_t ROVER_FRAMES_END_WRITING = 12; - inline static constexpr size_t ROVER_FRAMES_SLEEP = 85; - inline static constexpr size_t ROVER_FRAMES_WAKE_UP = 14; +// Name: Rover +inline static constexpr size_t ROVER_SPRITE_SHEET_COLS = 102; +inline static constexpr size_t ROVER_SPRITE_SHEET_ROWS = 6; +inline static constexpr size_t ROVER_FRAMES_IDLE = 1; +inline static constexpr size_t ROVER_FRAMES_BORING = 102; +inline static constexpr size_t ROVER_FRAMES_START_WRITING = 9; +inline static constexpr size_t ROVER_FRAMES_WRITING = 34; +inline static constexpr size_t ROVER_FRAMES_END_WRITING = 12; +inline static constexpr size_t ROVER_FRAMES_SLEEP = 85; +inline static constexpr size_t ROVER_FRAMES_WAKE_UP = 14; - inline static constexpr char ROVER_FQID_ARR[] = "ms_agent:rover"; - inline static constexpr const char* ROVER_FQID = ROVER_FQID_ARR; - inline static constexpr std::size_t ROVER_FQID_LEN = sizeof(ROVER_FQID_ARR)-1; - inline static constexpr char ROVER_ID_ARR[] = "rover"; - inline static constexpr const char* ROVER_ID = ROVER_ID_ARR; - inline static constexpr std::size_t ROVER_ID_LEN = sizeof(ROVER_ID_ARR)-1; - inline static constexpr char ROVER_NAME_ARR[] = "Rover"; - inline static constexpr const char* ROVER_NAME = ROVER_NAME_ARR; - inline static constexpr std::size_t ROVER_NAME_LEN = sizeof(ROVER_NAME_ARR)-1; - inline static constexpr char ROVER_FQNAME_ARR[] = "ms_agent:Rover"; - inline static constexpr const char* ROVER_FQNAME = ROVER_FQNAME_ARR; - inline static constexpr std::size_t ROVER_FQNAME_LEN = sizeof(ROVER_FQNAME_ARR)-1; - inline static constexpr size_t ROVER_ANIM_INDEX = 2; +inline static constexpr char ROVER_FQID_ARR[] = "ms_agent:rover"; +inline static constexpr const char *ROVER_FQID = ROVER_FQID_ARR; +inline static constexpr std::size_t ROVER_FQID_LEN = sizeof(ROVER_FQID_ARR) - 1; +inline static constexpr char ROVER_ID_ARR[] = "rover"; +inline static constexpr const char *ROVER_ID = ROVER_ID_ARR; +inline static constexpr std::size_t ROVER_ID_LEN = sizeof(ROVER_ID_ARR) - 1; +inline static constexpr char ROVER_NAME_ARR[] = "Rover"; +inline static constexpr const char *ROVER_NAME = ROVER_NAME_ARR; +inline static constexpr std::size_t ROVER_NAME_LEN = sizeof(ROVER_NAME_ARR) - 1; +inline static constexpr char ROVER_FQNAME_ARR[] = "ms_agent:Rover"; +inline static constexpr const char *ROVER_FQNAME = ROVER_FQNAME_ARR; +inline static constexpr std::size_t ROVER_FQNAME_LEN = sizeof(ROVER_FQNAME_ARR) - 1; +inline static constexpr size_t ROVER_ANIM_INDEX = 2; - // Name: Merlin - inline static constexpr size_t MERLIN_SPRITE_SHEET_COLS = 22; - inline static constexpr size_t MERLIN_SPRITE_SHEET_ROWS = 6; - inline static constexpr size_t MERLIN_FRAMES_IDLE = 1; - inline static constexpr size_t MERLIN_FRAMES_BORING = 22; - inline static constexpr size_t MERLIN_FRAMES_START_WRITING = 6; - inline static constexpr size_t MERLIN_FRAMES_WRITING = 14; - inline static constexpr size_t MERLIN_FRAMES_END_WRITING = 6; - inline static constexpr size_t MERLIN_FRAMES_SLEEP = 20; - inline static constexpr size_t MERLIN_FRAMES_WAKE_UP = 6; +// Name: Merlin +inline static constexpr size_t MERLIN_SPRITE_SHEET_COLS = 22; +inline static constexpr size_t MERLIN_SPRITE_SHEET_ROWS = 6; +inline static constexpr size_t MERLIN_FRAMES_IDLE = 1; +inline static constexpr size_t MERLIN_FRAMES_BORING = 22; +inline static constexpr size_t MERLIN_FRAMES_START_WRITING = 6; +inline static constexpr size_t MERLIN_FRAMES_WRITING = 14; +inline static constexpr size_t MERLIN_FRAMES_END_WRITING = 6; +inline static constexpr size_t MERLIN_FRAMES_SLEEP = 20; +inline static constexpr size_t MERLIN_FRAMES_WAKE_UP = 6; - inline static constexpr char MERLIN_FQID_ARR[] = "ms_agent:merlin"; - inline static constexpr const char* MERLIN_FQID = MERLIN_FQID_ARR; - inline static constexpr std::size_t MERLIN_FQID_LEN = sizeof(MERLIN_FQID_ARR)-1; - inline static constexpr char MERLIN_ID_ARR[] = "merlin"; - inline static constexpr const char* MERLIN_ID = MERLIN_ID_ARR; - inline static constexpr std::size_t MERLIN_ID_LEN = sizeof(MERLIN_ID_ARR)-1; - inline static constexpr char MERLIN_NAME_ARR[] = "Merlin"; - inline static constexpr const char* MERLIN_NAME = MERLIN_NAME_ARR; - inline static constexpr std::size_t MERLIN_NAME_LEN = sizeof(MERLIN_NAME_ARR)-1; - inline static constexpr char MERLIN_FQNAME_ARR[] = "ms_agent:Merlin"; - inline static constexpr const char* MERLIN_FQNAME = MERLIN_FQNAME_ARR; - inline static constexpr std::size_t MERLIN_FQNAME_LEN = sizeof(MERLIN_FQNAME_ARR)-1; - inline static constexpr size_t MERLIN_ANIM_INDEX = 3; +inline static constexpr char MERLIN_FQID_ARR[] = "ms_agent:merlin"; +inline static constexpr const char *MERLIN_FQID = MERLIN_FQID_ARR; +inline static constexpr std::size_t MERLIN_FQID_LEN = sizeof(MERLIN_FQID_ARR) - 1; +inline static constexpr char MERLIN_ID_ARR[] = "merlin"; +inline static constexpr const char *MERLIN_ID = MERLIN_ID_ARR; +inline static constexpr std::size_t MERLIN_ID_LEN = sizeof(MERLIN_ID_ARR) - 1; +inline static constexpr char MERLIN_NAME_ARR[] = "Merlin"; +inline static constexpr const char *MERLIN_NAME = MERLIN_NAME_ARR; +inline static constexpr std::size_t MERLIN_NAME_LEN = sizeof(MERLIN_NAME_ARR) - 1; +inline static constexpr char MERLIN_FQNAME_ARR[] = "ms_agent:Merlin"; +inline static constexpr const char *MERLIN_FQNAME = MERLIN_FQNAME_ARR; +inline static constexpr std::size_t MERLIN_FQNAME_LEN = sizeof(MERLIN_FQNAME_ARR) - 1; +inline static constexpr size_t MERLIN_ANIM_INDEX = 3; - - inline static constexpr size_t MS_AGENTS_ANIM_COUNT = 4; - /// @TODO: determine the biggest cols from MS agents - inline static constexpr size_t MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES = ROVER_SPRITE_SHEET_COLS; +inline static constexpr size_t MS_AGENTS_ANIM_COUNT = 4; +/// @TODO: determine the biggest cols from MS agents +inline static constexpr size_t MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES = ROVER_SPRITE_SHEET_COLS; #else - inline static constexpr size_t MS_AGENTS_ANIM_COUNT = 1; - inline static constexpr size_t MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES = CLIPPY_SPRITE_SHEET_COLS; +inline static constexpr size_t MS_AGENTS_ANIM_COUNT = 1; +inline static constexpr size_t MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES = CLIPPY_SPRITE_SHEET_COLS; #endif -} +} // namespace bongocat::assets -#endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_HPP +#endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_HPP diff --git a/include/embedded_assets/ms_agent/ms_agent_images.h b/include/embedded_assets/ms_agent/ms_agent_images.h index ae37bab5..7733a917 100644 --- a/include/embedded_assets/ms_agent/ms_agent_images.h +++ b/include/embedded_assets/ms_agent/ms_agent_images.h @@ -18,4 +18,4 @@ extern const unsigned char merlin_png[]; extern const size_t merlin_png_size; #endif -#endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_IMAGES_H +#endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_IMAGES_H diff --git a/include/embedded_assets/ms_agent/ms_agent_sprite.h b/include/embedded_assets/ms_agent/ms_agent_sprite.h index 113c9d86..46c00af7 100644 --- a/include/embedded_assets/ms_agent/ms_agent_sprite.h +++ b/include/embedded_assets/ms_agent/ms_agent_sprite.h @@ -2,90 +2,91 @@ #define BONGOCAT_EMBEDDED_ASSETS_CLIPPY_H #include "embedded_assets/embedded_image.h" + #include #include namespace bongocat::assets { - struct ms_agent_animation_indices_t { - int32_t start_index_frame_idle{0}; - int32_t end_index_frame_idle{0}; +struct ms_agent_animation_indices_t { + int32_t start_index_frame_idle{0}; + int32_t end_index_frame_idle{0}; - int32_t start_index_frame_boring{0}; - int32_t end_index_frame_boring{0}; + int32_t start_index_frame_boring{0}; + int32_t end_index_frame_boring{0}; - int32_t start_index_frame_start_writing{0}; - int32_t end_index_frame_start_writing{0}; - int32_t start_index_frame_writing{0}; - int32_t end_index_frame_writing{0}; - int32_t start_index_frame_end_writing{0}; - int32_t end_index_frame_end_writing{0}; + int32_t start_index_frame_start_writing{0}; + int32_t end_index_frame_start_writing{0}; + int32_t start_index_frame_writing{0}; + int32_t end_index_frame_writing{0}; + int32_t start_index_frame_end_writing{0}; + int32_t end_index_frame_end_writing{0}; - int32_t start_index_frame_sleep{0}; - int32_t end_index_frame_sleep{0}; + int32_t start_index_frame_sleep{0}; + int32_t end_index_frame_sleep{0}; - int32_t start_index_frame_wake_up{0}; - int32_t end_index_frame_wake_up{0}; + int32_t start_index_frame_wake_up{0}; + int32_t end_index_frame_wake_up{0}; - int32_t start_index_frame_start_working{0}; - int32_t end_index_frame_start_working{0}; - int32_t start_index_frame_working{0}; - int32_t end_index_frame_working{0}; - int32_t start_index_frame_end_working{0}; - int32_t end_index_frame_end_working{0}; + int32_t start_index_frame_start_working{0}; + int32_t end_index_frame_start_working{0}; + int32_t start_index_frame_working{0}; + int32_t end_index_frame_working{0}; + int32_t start_index_frame_end_working{0}; + int32_t end_index_frame_end_working{0}; - int32_t start_index_frame_start_moving{0}; - int32_t end_index_frame_start_moving{0}; - int32_t start_index_frame_moving{0}; - int32_t end_index_frame_moving{0}; - int32_t start_index_frame_end_moving{0}; - int32_t end_index_frame_end_moving{0}; + int32_t start_index_frame_start_moving{0}; + int32_t end_index_frame_start_moving{0}; + int32_t start_index_frame_moving{0}; + int32_t end_index_frame_moving{0}; + int32_t start_index_frame_end_moving{0}; + int32_t end_index_frame_end_moving{0}; - int32_t start_index_frame_happy{0}; - int32_t end_index_frame_happy{0}; - }; + int32_t start_index_frame_happy{0}; + int32_t end_index_frame_happy{0}; +}; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_IDLE = 0; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_BORING = 0; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_START_WRITING = 1; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_WRITING = 2; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_END_WRITING = 3; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_SLEEP = 4; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP = 5; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_START_WORKING = 6; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_WORKING = 7; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_END_WORKING = 8; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_START_MOVING = 9; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_MOVING = 10; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_END_MOVING = 11; - inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_HAPPY = 12; - enum class ClippyAnimations : uint8_t { - Idle = MS_AGENT_SPRITE_SHEET_ROW_IDLE, - Boring = MS_AGENT_SPRITE_SHEET_ROW_BORING, - StartWriting = MS_AGENT_SPRITE_SHEET_ROW_START_WRITING, - Writing = MS_AGENT_SPRITE_SHEET_ROW_WRITING, - EndWriting = MS_AGENT_SPRITE_SHEET_ROW_END_WRITING, - Sleep = MS_AGENT_SPRITE_SHEET_ROW_SLEEP, - WakeUp = MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP, - // optional - StartWorking = MS_AGENT_SPRITE_SHEET_ROW_START_WORKING, - Working = MS_AGENT_SPRITE_SHEET_ROW_WORKING, - EndWorking = MS_AGENT_SPRITE_SHEET_ROW_END_WORKING, - StartMoving = MS_AGENT_SPRITE_SHEET_ROW_START_MOVING, - Moving = MS_AGENT_SPRITE_SHEET_ROW_MOVING, - EndMoving = MS_AGENT_SPRITE_SHEET_ROW_END_MOVING, - Happy = MS_AGENT_SPRITE_SHEET_ROW_HAPPY, - }; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_IDLE = 0; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_BORING = 0; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_START_WRITING = 1; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_WRITING = 2; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_END_WRITING = 3; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_SLEEP = 4; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP = 5; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_START_WORKING = 6; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_WORKING = 7; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_END_WORKING = 8; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_START_MOVING = 9; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_MOVING = 10; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_END_MOVING = 11; +inline static constexpr size_t MS_AGENT_SPRITE_SHEET_ROW_HAPPY = 12; +enum class ClippyAnimations : uint8_t { + Idle = MS_AGENT_SPRITE_SHEET_ROW_IDLE, + Boring = MS_AGENT_SPRITE_SHEET_ROW_BORING, + StartWriting = MS_AGENT_SPRITE_SHEET_ROW_START_WRITING, + Writing = MS_AGENT_SPRITE_SHEET_ROW_WRITING, + EndWriting = MS_AGENT_SPRITE_SHEET_ROW_END_WRITING, + Sleep = MS_AGENT_SPRITE_SHEET_ROW_SLEEP, + WakeUp = MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP, + // optional + StartWorking = MS_AGENT_SPRITE_SHEET_ROW_START_WORKING, + Working = MS_AGENT_SPRITE_SHEET_ROW_WORKING, + EndWorking = MS_AGENT_SPRITE_SHEET_ROW_END_WORKING, + StartMoving = MS_AGENT_SPRITE_SHEET_ROW_START_MOVING, + Moving = MS_AGENT_SPRITE_SHEET_ROW_MOVING, + EndMoving = MS_AGENT_SPRITE_SHEET_ROW_END_MOVING, + Happy = MS_AGENT_SPRITE_SHEET_ROW_HAPPY, +}; #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - inline static constexpr size_t MS_AGENTS_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 4; - inline static constexpr size_t MS_AGENTS_ANIMATIONS_COUNT = 4; +inline static constexpr size_t MS_AGENTS_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 4; +inline static constexpr size_t MS_AGENTS_ANIMATIONS_COUNT = 4; #else - inline static constexpr size_t MS_AGENTS_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 1; - inline static constexpr size_t MS_AGENTS_ANIMATIONS_COUNT = 1; +inline static constexpr size_t MS_AGENTS_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 1; +inline static constexpr size_t MS_AGENTS_ANIMATIONS_COUNT = 1; #endif - [[nodiscard]] embedded_image_t get_ms_agent_sprite_sheet(size_t i); - [[nodiscard]] ms_agent_animation_indices_t get_ms_agent_animation_indices(size_t i); -} +[[nodiscard]] embedded_image_t get_ms_agent_sprite_sheet(size_t i); +[[nodiscard]] ms_agent_animation_indices_t get_ms_agent_animation_indices(size_t i); +} // namespace bongocat::assets -#endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_H +#endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_H diff --git a/include/graphics/animation.h b/include/graphics/animation.h index 0cab2f14..4772fc7f 100644 --- a/include/graphics/animation.h +++ b/include/graphics/animation.h @@ -1,45 +1,49 @@ #ifndef BONGOCAT_ANIMATION_H #define BONGOCAT_ANIMATION_H +#include "animation_context.h" #include "config/config.h" -#include "utils/error.h" +#include "global_animation_session.h" #include "platform/input_context.h" #include "platform/update_context.h" -#include "animation_context.h" -#include "global_animation_session.h" +#include "utils/error.h" namespace bongocat::animation { - enum class trigger_animation_cause_mask_t : uint64_t { - NONE = 0, - Init = (1U << 0), - KeyPress = (1U << 1), - IdleUpdate = (1U << 2), - CpuUpdate = (1U << 3), - UpdateConfig = (1U << 4), - Timeout = (1U << 5), - }; +enum class trigger_animation_cause_mask_t : uint64_t { + NONE = 0, + Init = (1U << 0), + KeyPress = (1U << 1), + IdleUpdate = (1U << 2), + CpuUpdate = (1U << 3), + UpdateConfig = (1U << 4), + Timeout = (1U << 5), +}; // ============================================================================= // ANIMATION LIFECYCLE // ============================================================================= - // Initialize animation system - must be checked - BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); +// Initialize animation system - must be checked +BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); - // Start animation thread - must be checked - BONGOCAT_NODISCARD bongocat_error_t start(animation_session_t& ctx, platform::input::input_context_t& input, platform::update::update_context_t& upd, const config::config_t& config, platform::CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); +// Start animation thread - must be checked +BONGOCAT_NODISCARD bongocat_error_t start(animation_session_t& ctx, platform::input::input_context_t& input, + platform::update::update_context_t& upd, const config::config_t& config, + platform::CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation); - // Trigger key press animation - void trigger(animation_session_t& ctx, trigger_animation_cause_mask_t cause); - void trigger_update_config(animation_session_t& ctx, const config::config_t& config, uint64_t config_generation); +// Trigger key press animation +void trigger(animation_session_t& ctx, trigger_animation_cause_mask_t cause); +void trigger_update_config(animation_session_t& ctx, const config::config_t& config, uint64_t config_generation); - void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen); - created_result_t hot_load_animation(animation_context_t& ctx); - BONGOCAT_NODISCARD animation_t& get_current_animation(animation_context_t& ctx); +void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen); +created_result_t hot_load_animation(animation_context_t& ctx); +BONGOCAT_NODISCARD animation_t& get_current_animation(animation_context_t& ctx); - namespace details { - created_result_t anim_load_custom_animation(animation_context_t& ctx, const config::config_t& config); - } +namespace details { + created_result_t anim_load_custom_animation(animation_context_t& ctx, + const config::config_t& config); } +} // namespace bongocat::animation -#endif // BONGOCAT_ANIMATION_H \ No newline at end of file +#endif // BONGOCAT_ANIMATION_H \ No newline at end of file diff --git a/include/graphics/animation_context.h b/include/graphics/animation_context.h index e9a8937f..2efa24e4 100644 --- a/include/graphics/animation_context.h +++ b/include/graphics/animation_context.h @@ -3,8 +3,9 @@ #include "animation_shared_memory.h" #include "config/config.h" -#include "utils/system_memory.h" #include "utils/random.h" +#include "utils/system_memory.h" + #include namespace bongocat::animation { @@ -13,54 +14,53 @@ namespace bongocat::animation { // ANIMATION STATE // ============================================================================= - struct animation_context_t; - void stop(animation_context_t& ctx); - // Cleanup animation resources - void cleanup(animation_context_t& ctx); - - struct animation_context_t { - // local copy from other thread, update after reload (shared memory) - platform::MMapMemory _local_copy_config; - platform::MMapMemory shm; +struct animation_context_t; +void stop(animation_context_t& ctx); +// Cleanup animation resources +void cleanup(animation_context_t& ctx); - // Animation system state - atomic_bool _animation_running{false}; - pthread_t _anim_thread{0}; - platform::random_xoshiro128 _rng; - // lock for shm - platform::Mutex anim_lock; +struct animation_context_t { + // local copy from other thread, update after reload (shared memory) + platform::MMapMemory _local_copy_config; + platform::MMapMemory shm; - // config reload threading - platform::FileDescriptor update_config_efd; // get new_gen from here - atomic_uint64_t config_seen_generation{0}; - platform::CondVariable config_updated; + // Animation system state + atomic_bool _animation_running{false}; + pthread_t _anim_thread{0}; + platform::random_xoshiro128 _rng; + // lock for shm + platform::Mutex anim_lock; + // config reload threading + platform::FileDescriptor update_config_efd; // get new_gen from here + atomic_uint64_t config_seen_generation{0}; + platform::CondVariable config_updated; - animation_context_t() = default; - ~animation_context_t() { - cleanup(*this); - } + animation_context_t() = default; + ~animation_context_t() { + cleanup(*this); + } - animation_context_t(const animation_context_t&) = delete; - animation_context_t& operator=(const animation_context_t&) = delete; - animation_context_t(animation_context_t&& other) = delete; - animation_context_t& operator=(animation_context_t&& other) = delete; - }; - inline void cleanup(animation_context_t& ctx) { - if (atomic_load(&ctx._animation_running)) { - stop(ctx); - // ctx.anim_lock should be unlocked - } - atomic_store(&ctx._animation_running, false); - ctx._anim_thread = 0; + animation_context_t(const animation_context_t&) = delete; + animation_context_t& operator=(const animation_context_t&) = delete; + animation_context_t(animation_context_t&& other) = delete; + animation_context_t& operator=(animation_context_t&& other) = delete; +}; +inline void cleanup(animation_context_t& ctx) { + if (atomic_load(&ctx._animation_running)) { + stop(ctx); + // ctx.anim_lock should be unlocked + } + atomic_store(&ctx._animation_running, false); + ctx._anim_thread = 0; - close_fd(ctx.update_config_efd); - atomic_store(&ctx.config_seen_generation, 0); + close_fd(ctx.update_config_efd); + atomic_store(&ctx.config_seen_generation, 0); - platform::release_allocated_mmap_memory(ctx.shm); - platform::release_allocated_mmap_memory(ctx._local_copy_config); - ctx._rng = platform::random_xoshiro128(0); - } + platform::release_allocated_mmap_memory(ctx.shm); + platform::release_allocated_mmap_memory(ctx._local_copy_config); + ctx._rng = platform::random_xoshiro128(0); } +} // namespace bongocat::animation -#endif //BONGOCAT_ANIMATION_CONTEXT_H +#endif // BONGOCAT_ANIMATION_CONTEXT_H diff --git a/include/graphics/animation_shared_memory.h b/include/graphics/animation_shared_memory.h index 1079192d..37bee41c 100644 --- a/include/graphics/animation_shared_memory.h +++ b/include/graphics/animation_shared_memory.h @@ -1,265 +1,270 @@ #ifndef BONGOCAT_ANIMATION_SHARED_MEMORY_H #define BONGOCAT_ANIMATION_SHARED_MEMORY_H -#include "sprite_sheet.h" #include "config/config.h" +#include "sprite_sheet.h" #include "utils/system_memory.h" #include "utils/time.h" namespace bongocat::animation { - enum class animation_player_custom_overwrite_mirror_x : uint32_t { - None, - NoMirror, - Mirror - }; - struct animation_player_result_t { - int32_t sprite_sheet_col{0}; - int32_t sprite_sheet_row{0}; - animation_player_custom_overwrite_mirror_x overwrite_mirror_x{animation_player_custom_overwrite_mirror_x::None}; - }; +enum class animation_player_custom_overwrite_mirror_x : uint32_t { + None, + NoMirror, + Mirror +}; +struct animation_player_result_t { + int32_t sprite_sheet_col{0}; + int32_t sprite_sheet_row{0}; + animation_player_custom_overwrite_mirror_x overwrite_mirror_x{animation_player_custom_overwrite_mirror_x::None}; +}; // ============================================================================= // ANIMATION STATE (shared memory between threads) // ============================================================================= - struct animation_shared_memory_t { - // animation state - animation_player_result_t animation_player_result{}; - int32_t anim_index{0}; - config::config_animation_sprite_sheet_layout_t anim_type{config::config_animation_sprite_sheet_layout_t::None}; - config::config_animation_dm_set_t anim_dm_set{config::config_animation_dm_set_t::None}; - config::config_animation_custom_set_t anim_custom_set{config::config_animation_custom_set_t::None}; - float movement_offset_x{0.0}; - float anim_direction{0.0}; - - // Animation frame data for sprite sheet preload - platform::MMapArray bongocat_anims; - platform::MMapArray dm_anims; - platform::MMapArray dm20_anims; - platform::MMapArray dmc_anims; - platform::MMapArray dmx_anims; - platform::MMapArray pen_anims; - platform::MMapArray pen20_anims; - platform::MMapArray dmall_anims; - platform::MMapArray min_dm_anims; - platform::MMapArray ms_anims; - platform::MMapArray pkmn_anims; - platform::MMapArray misc_anims; - platform::MMapArray pmd_anims; - - // for sprite sheet hot reload (or custom sprite sheet) - animation_t anim; - - animation_shared_memory_t() = default; - ~animation_shared_memory_t() { - anim_type = config::config_animation_sprite_sheet_layout_t::None; - anim_dm_set = config::config_animation_dm_set_t::None; - anim_custom_set = config::config_animation_custom_set_t::None; - animation_player_result = {}; - anim_index = 0; - movement_offset_x = 0; - anim_direction = 0; - - for (size_t i = 0; i < bongocat_anims.count; i++) { - cleanup_animation(bongocat_anims[i]); - } - platform::release_allocated_mmap_array(bongocat_anims); - - for (size_t i = 0; i < dm_anims.count; i++) { - cleanup_animation(dm_anims[i]); - } - platform::release_allocated_mmap_array(dm_anims); - - for (size_t i = 0; i < dm20_anims.count; i++) { - cleanup_animation(dm20_anims[i]); - } - platform::release_allocated_mmap_array(dm20_anims); - - for (size_t i = 0; i < dmc_anims.count; i++) { - cleanup_animation(dmc_anims[i]); - } - platform::release_allocated_mmap_array(dmc_anims); - - for (size_t i = 0; i < dmx_anims.count; i++) { - cleanup_animation(dmx_anims[i]); - } - platform::release_allocated_mmap_array(dmx_anims); - - for (size_t i = 0; i < pen_anims.count; i++) { - cleanup_animation(pen_anims[i]); - } - platform::release_allocated_mmap_array(pen_anims); - - for (size_t i = 0; i < pen20_anims.count; i++) { - cleanup_animation(pen20_anims[i]); - } - platform::release_allocated_mmap_array(pen20_anims); - - for (size_t i = 0; i < dmall_anims.count; i++) { - cleanup_animation(dmall_anims[i]); - } - platform::release_allocated_mmap_array(dmall_anims); - - for (size_t i = 0; i < min_dm_anims.count; i++) { - cleanup_animation(min_dm_anims[i]); - } - platform::release_allocated_mmap_array(min_dm_anims); - - for (size_t i = 0; i < ms_anims.count; i++) { - cleanup_animation(ms_anims[i]); - } - platform::release_allocated_mmap_array(ms_anims); - - for (size_t i = 0; i < pkmn_anims.count; i++) { - cleanup_animation(pkmn_anims[i]); - } - platform::release_allocated_mmap_array(pkmn_anims); - - for (size_t i = 0; i < misc_anims.count; i++) { - cleanup_animation(misc_anims[i]); - } - platform::release_allocated_mmap_array(misc_anims); - - for (size_t i = 0; i < pmd_anims.count; i++) { - cleanup_animation(pmd_anims[i]); - } - platform::release_allocated_mmap_array(pmd_anims); - - cleanup_animation(anim); - } - animation_shared_memory_t(const animation_shared_memory_t& other) - : animation_player_result(other.animation_player_result), anim_index(other.anim_index), anim_type(other.anim_type), anim_dm_set(other.anim_dm_set) - { - bongocat_anims = other.bongocat_anims; - dm_anims = other.dm_anims; - dm20_anims = other.dm20_anims; - dmc_anims = other.dmc_anims; - dmx_anims = other.dmx_anims; - pen_anims = other.pen_anims; - pen20_anims = other.pen20_anims; - dmall_anims = other.dmall_anims; - min_dm_anims = other.min_dm_anims; - ms_anims = other.ms_anims; - pkmn_anims = other.pkmn_anims; - misc_anims = other.misc_anims; - pmd_anims = other.pmd_anims; - - anim = other.anim; - } - animation_shared_memory_t& operator=(const animation_shared_memory_t& other) { - if (this != &other) { - anim_type = other.anim_type; - anim_dm_set = other.anim_dm_set; - anim_index = other.anim_index; - animation_player_result = other.animation_player_result; - - bongocat_anims = other.bongocat_anims; - dm_anims = other.dm_anims; - dm20_anims = other.dm20_anims; - dmc_anims = other.dmc_anims; - dmx_anims = other.dmx_anims; - pen_anims = other.pen_anims; - pen20_anims = other.pen20_anims; - dmall_anims = other.dmall_anims; - min_dm_anims = other.min_dm_anims; - ms_anims = other.ms_anims; - pkmn_anims = other.pkmn_anims; - misc_anims = other.misc_anims; - pmd_anims = other.pmd_anims; - - anim = other.anim; - } - return *this; - } - - animation_shared_memory_t(animation_shared_memory_t&& other) noexcept - : animation_player_result(other.animation_player_result), anim_index(other.anim_index), anim_type(other.anim_type), anim_dm_set(other.anim_dm_set), anim_custom_set(other.anim_custom_set) - { - bongocat_anims = bongocat::move(other.bongocat_anims); - dm_anims = bongocat::move(other.dm_anims); - dm20_anims = bongocat::move(other.dm20_anims); - dmc_anims = bongocat::move(other.dmc_anims); - dmx_anims = bongocat::move(other.dmx_anims); - pen_anims = bongocat::move(other.pen_anims); - pen20_anims = bongocat::move(other.pen20_anims); - dmall_anims = bongocat::move(other.dmall_anims); - min_dm_anims = bongocat::move(other.min_dm_anims); - ms_anims = bongocat::move(other.ms_anims); - pkmn_anims = bongocat::move(other.pkmn_anims); - misc_anims = bongocat::move(other.misc_anims); - pmd_anims = bongocat::move(other.pmd_anims); - - anim = bongocat::move(other.anim); - - cleanup_animation(other.anim); - platform::release_allocated_mmap_array(other.bongocat_anims); - platform::release_allocated_mmap_array(other.dm_anims); - platform::release_allocated_mmap_array(other.dm20_anims); - platform::release_allocated_mmap_array(other.dmc_anims); - platform::release_allocated_mmap_array(other.dmx_anims); - platform::release_allocated_mmap_array(other.pen_anims); - platform::release_allocated_mmap_array(other.pen20_anims); - platform::release_allocated_mmap_array(other.dmall_anims); - platform::release_allocated_mmap_array(other.min_dm_anims); - platform::release_allocated_mmap_array(other.ms_anims); - platform::release_allocated_mmap_array(other.pkmn_anims); - platform::release_allocated_mmap_array(other.misc_anims); - platform::release_allocated_mmap_array(other.pmd_anims); - - other.anim_type = config::config_animation_sprite_sheet_layout_t::None; - other.anim_dm_set = config::config_animation_dm_set_t::None; - other.anim_custom_set = config::config_animation_custom_set_t::None; - other.anim_index = 0; - other.animation_player_result = {}; - } - animation_shared_memory_t& operator=(animation_shared_memory_t&& other) noexcept { - if (this != &other) { - animation_player_result = other.animation_player_result; - anim_index = other.anim_index; - anim_type = other.anim_type; - anim_dm_set = other.anim_dm_set; - anim_custom_set = other.anim_custom_set; - - bongocat_anims = bongocat::move(other.bongocat_anims); - dm_anims = bongocat::move(other.dm_anims); - dm20_anims = bongocat::move(other.dm20_anims); - dmc_anims = bongocat::move(other.dmc_anims); - dmx_anims = bongocat::move(other.dmx_anims); - pen_anims = bongocat::move(other.pen_anims); - pen20_anims = bongocat::move(other.pen20_anims); - dmall_anims = bongocat::move(other.dmall_anims); - min_dm_anims = bongocat::move(other.min_dm_anims); - ms_anims = bongocat::move(other.ms_anims); - pkmn_anims = bongocat::move(other.pkmn_anims); - misc_anims = bongocat::move(other.misc_anims); - pmd_anims = bongocat::move(other.pmd_anims); - - anim = bongocat::move(other.anim); - - cleanup_animation(other.anim); - platform::release_allocated_mmap_array(other.bongocat_anims); - platform::release_allocated_mmap_array(other.dm_anims); - platform::release_allocated_mmap_array(other.dm20_anims); - platform::release_allocated_mmap_array(other.dmc_anims); - platform::release_allocated_mmap_array(other.dmx_anims); - platform::release_allocated_mmap_array(other.pen_anims); - platform::release_allocated_mmap_array(other.pen20_anims); - platform::release_allocated_mmap_array(other.dmall_anims); - platform::release_allocated_mmap_array(other.min_dm_anims); - platform::release_allocated_mmap_array(other.ms_anims); - platform::release_allocated_mmap_array(other.pkmn_anims); - platform::release_allocated_mmap_array(other.misc_anims); - platform::release_allocated_mmap_array(other.pmd_anims); - - other.anim_type = config::config_animation_sprite_sheet_layout_t::None; - other.anim_dm_set = config::config_animation_dm_set_t::None; - other.anim_custom_set = config::config_animation_custom_set_t::None; - other.anim_index = 0; - other.animation_player_result = {}; - } - return *this; - } - }; -} - -#endif // BONGOCAT_ANIMATION_SHARED_MEMORY_H \ No newline at end of file +struct animation_shared_memory_t { + // animation state + animation_player_result_t animation_player_result{}; + int32_t anim_index{0}; + config::config_animation_sprite_sheet_layout_t anim_type{config::config_animation_sprite_sheet_layout_t::None}; + config::config_animation_dm_set_t anim_dm_set{config::config_animation_dm_set_t::None}; + config::config_animation_custom_set_t anim_custom_set{config::config_animation_custom_set_t::None}; + float movement_offset_x{0.0}; + float anim_direction{0.0}; + + // Animation frame data for sprite sheet preload + platform::MMapArray bongocat_anims; + platform::MMapArray dm_anims; + platform::MMapArray dm20_anims; + platform::MMapArray dmc_anims; + platform::MMapArray dmx_anims; + platform::MMapArray pen_anims; + platform::MMapArray pen20_anims; + platform::MMapArray dmall_anims; + platform::MMapArray min_dm_anims; + platform::MMapArray ms_anims; + platform::MMapArray pkmn_anims; + platform::MMapArray misc_anims; + platform::MMapArray pmd_anims; + + // for sprite sheet hot reload (or custom sprite sheet) + animation_t anim; + + animation_shared_memory_t() = default; + ~animation_shared_memory_t() { + anim_type = config::config_animation_sprite_sheet_layout_t::None; + anim_dm_set = config::config_animation_dm_set_t::None; + anim_custom_set = config::config_animation_custom_set_t::None; + animation_player_result = {}; + anim_index = 0; + movement_offset_x = 0; + anim_direction = 0; + + for (size_t i = 0; i < bongocat_anims.count; i++) { + cleanup_animation(bongocat_anims[i]); + } + platform::release_allocated_mmap_array(bongocat_anims); + + for (size_t i = 0; i < dm_anims.count; i++) { + cleanup_animation(dm_anims[i]); + } + platform::release_allocated_mmap_array(dm_anims); + + for (size_t i = 0; i < dm20_anims.count; i++) { + cleanup_animation(dm20_anims[i]); + } + platform::release_allocated_mmap_array(dm20_anims); + + for (size_t i = 0; i < dmc_anims.count; i++) { + cleanup_animation(dmc_anims[i]); + } + platform::release_allocated_mmap_array(dmc_anims); + + for (size_t i = 0; i < dmx_anims.count; i++) { + cleanup_animation(dmx_anims[i]); + } + platform::release_allocated_mmap_array(dmx_anims); + + for (size_t i = 0; i < pen_anims.count; i++) { + cleanup_animation(pen_anims[i]); + } + platform::release_allocated_mmap_array(pen_anims); + + for (size_t i = 0; i < pen20_anims.count; i++) { + cleanup_animation(pen20_anims[i]); + } + platform::release_allocated_mmap_array(pen20_anims); + + for (size_t i = 0; i < dmall_anims.count; i++) { + cleanup_animation(dmall_anims[i]); + } + platform::release_allocated_mmap_array(dmall_anims); + + for (size_t i = 0; i < min_dm_anims.count; i++) { + cleanup_animation(min_dm_anims[i]); + } + platform::release_allocated_mmap_array(min_dm_anims); + + for (size_t i = 0; i < ms_anims.count; i++) { + cleanup_animation(ms_anims[i]); + } + platform::release_allocated_mmap_array(ms_anims); + + for (size_t i = 0; i < pkmn_anims.count; i++) { + cleanup_animation(pkmn_anims[i]); + } + platform::release_allocated_mmap_array(pkmn_anims); + + for (size_t i = 0; i < misc_anims.count; i++) { + cleanup_animation(misc_anims[i]); + } + platform::release_allocated_mmap_array(misc_anims); + + for (size_t i = 0; i < pmd_anims.count; i++) { + cleanup_animation(pmd_anims[i]); + } + platform::release_allocated_mmap_array(pmd_anims); + + cleanup_animation(anim); + } + animation_shared_memory_t(const animation_shared_memory_t& other) + : animation_player_result(other.animation_player_result) + , anim_index(other.anim_index) + , anim_type(other.anim_type) + , anim_dm_set(other.anim_dm_set) { + bongocat_anims = other.bongocat_anims; + dm_anims = other.dm_anims; + dm20_anims = other.dm20_anims; + dmc_anims = other.dmc_anims; + dmx_anims = other.dmx_anims; + pen_anims = other.pen_anims; + pen20_anims = other.pen20_anims; + dmall_anims = other.dmall_anims; + min_dm_anims = other.min_dm_anims; + ms_anims = other.ms_anims; + pkmn_anims = other.pkmn_anims; + misc_anims = other.misc_anims; + pmd_anims = other.pmd_anims; + + anim = other.anim; + } + animation_shared_memory_t& operator=(const animation_shared_memory_t& other) { + if (this != &other) { + anim_type = other.anim_type; + anim_dm_set = other.anim_dm_set; + anim_index = other.anim_index; + animation_player_result = other.animation_player_result; + + bongocat_anims = other.bongocat_anims; + dm_anims = other.dm_anims; + dm20_anims = other.dm20_anims; + dmc_anims = other.dmc_anims; + dmx_anims = other.dmx_anims; + pen_anims = other.pen_anims; + pen20_anims = other.pen20_anims; + dmall_anims = other.dmall_anims; + min_dm_anims = other.min_dm_anims; + ms_anims = other.ms_anims; + pkmn_anims = other.pkmn_anims; + misc_anims = other.misc_anims; + pmd_anims = other.pmd_anims; + + anim = other.anim; + } + return *this; + } + + animation_shared_memory_t(animation_shared_memory_t&& other) noexcept + : animation_player_result(other.animation_player_result) + , anim_index(other.anim_index) + , anim_type(other.anim_type) + , anim_dm_set(other.anim_dm_set) + , anim_custom_set(other.anim_custom_set) { + bongocat_anims = bongocat::move(other.bongocat_anims); + dm_anims = bongocat::move(other.dm_anims); + dm20_anims = bongocat::move(other.dm20_anims); + dmc_anims = bongocat::move(other.dmc_anims); + dmx_anims = bongocat::move(other.dmx_anims); + pen_anims = bongocat::move(other.pen_anims); + pen20_anims = bongocat::move(other.pen20_anims); + dmall_anims = bongocat::move(other.dmall_anims); + min_dm_anims = bongocat::move(other.min_dm_anims); + ms_anims = bongocat::move(other.ms_anims); + pkmn_anims = bongocat::move(other.pkmn_anims); + misc_anims = bongocat::move(other.misc_anims); + pmd_anims = bongocat::move(other.pmd_anims); + + anim = bongocat::move(other.anim); + + cleanup_animation(other.anim); + platform::release_allocated_mmap_array(other.bongocat_anims); + platform::release_allocated_mmap_array(other.dm_anims); + platform::release_allocated_mmap_array(other.dm20_anims); + platform::release_allocated_mmap_array(other.dmc_anims); + platform::release_allocated_mmap_array(other.dmx_anims); + platform::release_allocated_mmap_array(other.pen_anims); + platform::release_allocated_mmap_array(other.pen20_anims); + platform::release_allocated_mmap_array(other.dmall_anims); + platform::release_allocated_mmap_array(other.min_dm_anims); + platform::release_allocated_mmap_array(other.ms_anims); + platform::release_allocated_mmap_array(other.pkmn_anims); + platform::release_allocated_mmap_array(other.misc_anims); + platform::release_allocated_mmap_array(other.pmd_anims); + + other.anim_type = config::config_animation_sprite_sheet_layout_t::None; + other.anim_dm_set = config::config_animation_dm_set_t::None; + other.anim_custom_set = config::config_animation_custom_set_t::None; + other.anim_index = 0; + other.animation_player_result = {}; + } + animation_shared_memory_t& operator=(animation_shared_memory_t&& other) noexcept { + if (this != &other) { + animation_player_result = other.animation_player_result; + anim_index = other.anim_index; + anim_type = other.anim_type; + anim_dm_set = other.anim_dm_set; + anim_custom_set = other.anim_custom_set; + + bongocat_anims = bongocat::move(other.bongocat_anims); + dm_anims = bongocat::move(other.dm_anims); + dm20_anims = bongocat::move(other.dm20_anims); + dmc_anims = bongocat::move(other.dmc_anims); + dmx_anims = bongocat::move(other.dmx_anims); + pen_anims = bongocat::move(other.pen_anims); + pen20_anims = bongocat::move(other.pen20_anims); + dmall_anims = bongocat::move(other.dmall_anims); + min_dm_anims = bongocat::move(other.min_dm_anims); + ms_anims = bongocat::move(other.ms_anims); + pkmn_anims = bongocat::move(other.pkmn_anims); + misc_anims = bongocat::move(other.misc_anims); + pmd_anims = bongocat::move(other.pmd_anims); + + anim = bongocat::move(other.anim); + + cleanup_animation(other.anim); + platform::release_allocated_mmap_array(other.bongocat_anims); + platform::release_allocated_mmap_array(other.dm_anims); + platform::release_allocated_mmap_array(other.dm20_anims); + platform::release_allocated_mmap_array(other.dmc_anims); + platform::release_allocated_mmap_array(other.dmx_anims); + platform::release_allocated_mmap_array(other.pen_anims); + platform::release_allocated_mmap_array(other.pen20_anims); + platform::release_allocated_mmap_array(other.dmall_anims); + platform::release_allocated_mmap_array(other.min_dm_anims); + platform::release_allocated_mmap_array(other.ms_anims); + platform::release_allocated_mmap_array(other.pkmn_anims); + platform::release_allocated_mmap_array(other.misc_anims); + platform::release_allocated_mmap_array(other.pmd_anims); + + other.anim_type = config::config_animation_sprite_sheet_layout_t::None; + other.anim_dm_set = config::config_animation_dm_set_t::None; + other.anim_custom_set = config::config_animation_custom_set_t::None; + other.anim_index = 0; + other.animation_player_result = {}; + } + return *this; + } +}; +} // namespace bongocat::animation + +#endif // BONGOCAT_ANIMATION_SHARED_MEMORY_H \ No newline at end of file diff --git a/include/graphics/drawing.h b/include/graphics/drawing.h index 7df86520..cc293603 100644 --- a/include/graphics/drawing.h +++ b/include/graphics/drawing.h @@ -5,38 +5,33 @@ #include namespace bongocat::animation { - enum class blit_image_color_option_flags_t : uint32_t { - Invisible = (1u << 0), - Normal = (1u << 1), - Invert = (1u << 2), - MirrorX = (1u << 3), - MirrorY = (1u << 4), - BilinearInterpolation = (1u << 5), - }; - enum class blit_image_color_order_t : uint8_t { - RGBA, - BGRA - }; +enum class blit_image_color_option_flags_t : uint32_t { + Invisible = (1u << 0), + Normal = (1u << 1), + Invert = (1u << 2), + MirrorX = (1u << 3), + MirrorY = (1u << 4), + BilinearInterpolation = (1u << 5), +}; +enum class blit_image_color_order_t : uint8_t { + RGBA, + BGRA +}; // ============================================================================= // RENDERING UTILITIES // ============================================================================= - void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, - const unsigned char *src, int src_channels, int src_idx, - blit_image_color_option_flags_t option, - blit_image_color_order_t dest_order, - blit_image_color_order_t src_order); +void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const unsigned char *src, int src_channels, + int src_idx, blit_image_color_option_flags_t option, blit_image_color_order_t dest_order, + blit_image_color_order_t src_order); - // Blit scaled image to destination buffer - void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int dest_channels, - const unsigned char *src, size_t src_size, int src_w, int src_h, int src_channels, - int src_x, int src_y, - int frame_w, int frame_h, - int offset_x, int offset_y, int target_w, int target_h, - blit_image_color_order_t dest_order, - blit_image_color_order_t src_order, - blit_image_color_option_flags_t options); -} +// Blit scaled image to destination buffer +void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int dest_channels, + const unsigned char *src, size_t src_size, int src_w, int src_h, int src_channels, int src_x, + int src_y, int frame_w, int frame_h, int offset_x, int offset_y, int target_w, int target_h, + blit_image_color_order_t dest_order, blit_image_color_order_t src_order, + blit_image_color_option_flags_t options); +} // namespace bongocat::animation -#endif // BONGOCAT_ANIMATION_DRAWING_IMAGES_H \ No newline at end of file +#endif // BONGOCAT_ANIMATION_DRAWING_IMAGES_H \ No newline at end of file diff --git a/include/graphics/embedded_assets_dms.h b/include/graphics/embedded_assets_dms.h index 504409e9..a520f736 100644 --- a/include/graphics/embedded_assets_dms.h +++ b/include/graphics/embedded_assets_dms.h @@ -3,133 +3,138 @@ #include -#if !defined(FEATURE_DM_EMBEDDED_ASSETS) && !defined(FEATURE_DM20_EMBEDDED_ASSETS) && !defined(FEATURE_DMC_EMBEDDED_ASSETS) && !defined(FEATURE_DMX_EMBEDDED_ASSETS) && !defined(FEATURE_PEN_EMBEDDED_ASSETS) && !defined(FEATURE_PEN20_EMBEDDED_ASSETS) && !defined(FEATURE_DMALL_EMBEDDED_ASSETS) -#ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS +#if !defined(FEATURE_DM_EMBEDDED_ASSETS) && !defined(FEATURE_DM20_EMBEDDED_ASSETS) && \ + !defined(FEATURE_DMC_EMBEDDED_ASSETS) && !defined(FEATURE_DMX_EMBEDDED_ASSETS) && \ + !defined(FEATURE_PEN_EMBEDDED_ASSETS) && !defined(FEATURE_PEN20_EMBEDDED_ASSETS) && \ + !defined(FEATURE_DMALL_EMBEDDED_ASSETS) +# ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS // Fallback dm (minimal set) -#ifndef FEATURE_MIN_DM_EMBEDDED_ASSETS -#define FEATURE_MIN_DM_EMBEDDED_ASSETS -#endif -#include "embedded_assets/min_dm/min_dm.hpp" +# ifndef FEATURE_MIN_DM_EMBEDDED_ASSETS +# define FEATURE_MIN_DM_EMBEDDED_ASSETS +# endif +# include "embedded_assets/min_dm/min_dm.hpp" namespace bongocat::assets { - inline static constexpr size_t DM_ANIM_COUNT = MIN_DM_ANIM_COUNT; +inline static constexpr size_t DM_ANIM_COUNT = MIN_DM_ANIM_COUNT; } -#else +# else namespace bongocat::assets { - inline static constexpr size_t MIN_DM_ANIM_COUNT = 0; - inline static constexpr size_t DM_ANIM_COUNT = 0; -} -#endif +inline static constexpr size_t MIN_DM_ANIM_COUNT = 0; +inline static constexpr size_t DM_ANIM_COUNT = 0; +} // namespace bongocat::assets +# endif namespace bongocat::assets { - inline static constexpr size_t DM20_ANIM_COUNT = 0; - inline static constexpr size_t PEN_ANIM_COUNT = 0; - inline static constexpr size_t PEN20_ANIM_COUNT = 0; - inline static constexpr size_t DMX_ANIM_COUNT = 0; - inline static constexpr size_t DMC_ANIM_COUNT = 0; - inline static constexpr size_t DMALL_ANIM_COUNT = 0; -} +inline static constexpr size_t DM20_ANIM_COUNT = 0; +inline static constexpr size_t PEN_ANIM_COUNT = 0; +inline static constexpr size_t PEN20_ANIM_COUNT = 0; +inline static constexpr size_t DMX_ANIM_COUNT = 0; +inline static constexpr size_t DMC_ANIM_COUNT = 0; +inline static constexpr size_t DMALL_ANIM_COUNT = 0; +} // namespace bongocat::assets // feature full assets #else namespace bongocat::assets { - inline static constexpr size_t MIN_DM_ANIM_COUNT = 0; +inline static constexpr size_t MIN_DM_ANIM_COUNT = 0; } /// dm -#ifdef FEATURE_DM_EMBEDDED_ASSETS -#include "embedded_assets/dm/dm.hpp" -#else +# ifdef FEATURE_DM_EMBEDDED_ASSETS +# include "embedded_assets/dm/dm.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t DM_ANIM_COUNT = 0; +inline static constexpr size_t DM_ANIM_COUNT = 0; } -#endif +# endif /// dm20 -#ifdef FEATURE_DM20_EMBEDDED_ASSETS -#include "embedded_assets/dm20/dm20.hpp" -#else +# ifdef FEATURE_DM20_EMBEDDED_ASSETS +# include "embedded_assets/dm20/dm20.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t DM20_ANIM_COUNT = 0; +inline static constexpr size_t DM20_ANIM_COUNT = 0; } -#endif +# endif /// pen -#ifdef FEATURE_PEN_EMBEDDED_ASSETS -#include "embedded_assets/pen/pen.hpp" -#else +# ifdef FEATURE_PEN_EMBEDDED_ASSETS +# include "embedded_assets/pen/pen.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t PEN_ANIM_COUNT = 0; +inline static constexpr size_t PEN_ANIM_COUNT = 0; } -#endif +# endif /// pen20 -#ifdef FEATURE_PEN20_EMBEDDED_ASSETS -#include "embedded_assets/pen20/pen20.hpp" -#else +# ifdef FEATURE_PEN20_EMBEDDED_ASSETS +# include "embedded_assets/pen20/pen20.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t PEN20_ANIM_COUNT = 0; +inline static constexpr size_t PEN20_ANIM_COUNT = 0; } -#endif +# endif /// dmx -#ifdef FEATURE_DMX_EMBEDDED_ASSETS -#include "embedded_assets/dmx/dmx.hpp" -#else +# ifdef FEATURE_DMX_EMBEDDED_ASSETS +# include "embedded_assets/dmx/dmx.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t DMX_ANIM_COUNT = 0; +inline static constexpr size_t DMX_ANIM_COUNT = 0; } -#endif +# endif /// dmc -#ifdef FEATURE_DMC_EMBEDDED_ASSETS -#include "embedded_assets/dmc/dmc.hpp" -#else +# ifdef FEATURE_DMC_EMBEDDED_ASSETS +# include "embedded_assets/dmc/dmc.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t DMC_ANIM_COUNT = 0; +inline static constexpr size_t DMC_ANIM_COUNT = 0; } -#endif +# endif /// dmall -#ifdef FEATURE_DMALL_EMBEDDED_ASSETS -#include "embedded_assets/dmall/dmall.hpp" -#else +# ifdef FEATURE_DMALL_EMBEDDED_ASSETS +# include "embedded_assets/dmall/dmall.hpp" +# else namespace bongocat::assets { - inline static constexpr size_t DMALL_ANIM_COUNT = 0; +inline static constexpr size_t DMALL_ANIM_COUNT = 0; } -#endif +# endif #endif namespace bongocat::assets { - inline static constexpr size_t DM_ANIMATIONS_COUNT = DM_ANIM_COUNT+DM20_ANIM_COUNT+PEN_ANIM_COUNT+PEN20_ANIM_COUNT+DMX_ANIM_COUNT+DMC_ANIM_COUNT+DMALL_ANIM_COUNT; +inline static constexpr size_t DM_ANIMATIONS_COUNT = DM_ANIM_COUNT + DM20_ANIM_COUNT + PEN_ANIM_COUNT + + PEN20_ANIM_COUNT + DMX_ANIM_COUNT + DMC_ANIM_COUNT + + DMALL_ANIM_COUNT; } namespace bongocat::assets { - static inline constexpr int DM_FRAME_IDLE1 = 0; - static inline constexpr int DM_FRAME_IDLE2 = 1; - static inline constexpr int DM_FRAME_ANGRY = 2; // Angry/Refuse- or Hurt-Fallback, Eat Frame Fallback - static inline constexpr int DM_FRAME_DOWN = 3; // Sleep/Discipline Fallback - static inline constexpr int DM_FRAME_HAPPY = 4; - static inline constexpr int DM_FRAME_EAT1 = 5; - static inline constexpr int DM_FRAME_SLEEP = 6; - static inline constexpr int DM_FRAME_REFUSE = 7; - static inline constexpr int DM_FRAME_SAD = 8; - - // Optional frames - static inline constexpr int DM_FRAME_LOSE1 = 9; - static inline constexpr int DM_FRAME_EAT2 = 10; - static inline constexpr int DM_FRAME_LOSE2 = 11; - static inline constexpr int DM_FRAME_ATTACK = 12; - - static inline constexpr int DM_FRAME_MOVEMENT1 = 13; - static inline constexpr int DM_FRAME_MOVEMENT2 = 14; - - static inline constexpr int DM_FRAME_ATTACK_2 = 15; - - inline static constexpr size_t DM_SPRITE_SHEET_MAX_COLS = 16; - inline static constexpr size_t DM_SPRITE_SHEET_ROWS = 1; - inline static constexpr size_t DM_SPRITE_SHEET_ROW = 0; - - static inline constexpr int DM_HAPPY_CHANCE_PERCENT = 60; -} +static inline constexpr int DM_FRAME_IDLE1 = 0; +static inline constexpr int DM_FRAME_IDLE2 = 1; +static inline constexpr int DM_FRAME_ANGRY = 2; // Angry/Refuse- or Hurt-Fallback, Eat Frame Fallback +static inline constexpr int DM_FRAME_DOWN = 3; // Sleep/Discipline Fallback +static inline constexpr int DM_FRAME_HAPPY = 4; +static inline constexpr int DM_FRAME_EAT1 = 5; +static inline constexpr int DM_FRAME_SLEEP = 6; +static inline constexpr int DM_FRAME_REFUSE = 7; +static inline constexpr int DM_FRAME_SAD = 8; + +// Optional frames +static inline constexpr int DM_FRAME_LOSE1 = 9; +static inline constexpr int DM_FRAME_EAT2 = 10; +static inline constexpr int DM_FRAME_LOSE2 = 11; +static inline constexpr int DM_FRAME_ATTACK = 12; + +static inline constexpr int DM_FRAME_MOVEMENT1 = 13; +static inline constexpr int DM_FRAME_MOVEMENT2 = 14; + +static inline constexpr int DM_FRAME_ATTACK_2 = 15; + +inline static constexpr size_t DM_SPRITE_SHEET_MAX_COLS = 16; +inline static constexpr size_t DM_SPRITE_SHEET_ROWS = 1; +inline static constexpr size_t DM_SPRITE_SHEET_ROW = 0; + +static inline constexpr int DM_HAPPY_CHANCE_PERCENT = 60; +} // namespace bongocat::assets #endif \ No newline at end of file diff --git a/include/graphics/embedded_assets_pkmn.h b/include/graphics/embedded_assets_pkmn.h index c24675d9..c5d05424 100644 --- a/include/graphics/embedded_assets_pkmn.h +++ b/include/graphics/embedded_assets_pkmn.h @@ -5,34 +5,34 @@ /// pkmn #ifdef FEATURE_PKMN_EMBEDDED_ASSETS -#include "embedded_assets/pkmn/pkmn.hpp" +# include "embedded_assets/pkmn/pkmn.hpp" #else namespace bongocat::assets { - inline static constexpr size_t PKMN_ANIM_COUNT = 0; +inline static constexpr size_t PKMN_ANIM_COUNT = 0; } #endif /// pmd (pkmn) #ifdef FEATURE_PMD_EMBEDDED_ASSETS -#include "embedded_assets/pmd/pmd.hpp" +# include "embedded_assets/pmd/pmd.hpp" #else namespace bongocat::assets { - inline static constexpr size_t PMD_ANIM_COUNT = 0; +inline static constexpr size_t PMD_ANIM_COUNT = 0; } #endif namespace bongocat::assets { - inline static constexpr size_t PKMN_ANIMATIONS_COUNT = PKMN_ANIM_COUNT; - inline static constexpr size_t PMD_ANIMATIONS_COUNT = PMD_ANIM_COUNT; -} +inline static constexpr size_t PKMN_ANIMATIONS_COUNT = PKMN_ANIM_COUNT; +inline static constexpr size_t PMD_ANIMATIONS_COUNT = PMD_ANIM_COUNT; +} // namespace bongocat::assets namespace bongocat::assets { - static inline constexpr int PKMN_FRAME_IDLE1 = 0; - static inline constexpr int PKMN_FRAME_IDLE2 = 1; +static inline constexpr int PKMN_FRAME_IDLE1 = 0; +static inline constexpr int PKMN_FRAME_IDLE2 = 1; - inline static constexpr size_t PKMN_SPRITE_SHEET_COLS = 2; - inline static constexpr size_t PKMN_SPRITE_SHEET_ROWS = 1; - inline static constexpr size_t PKMN_SPRITE_SHEET_ROW = 0; -} +inline static constexpr size_t PKMN_SPRITE_SHEET_COLS = 2; +inline static constexpr size_t PKMN_SPRITE_SHEET_ROWS = 1; +inline static constexpr size_t PKMN_SPRITE_SHEET_ROW = 0; +} // namespace bongocat::assets #endif \ No newline at end of file diff --git a/include/graphics/global_animation_session.h b/include/graphics/global_animation_session.h index 04a299e4..c905947e 100644 --- a/include/graphics/global_animation_session.h +++ b/include/graphics/global_animation_session.h @@ -7,62 +7,60 @@ namespace bongocat::animation { - struct animation_session_t; - void stop(animation_session_t& anim_ctx); - void cleanup(animation_session_t& anim_ctx); +struct animation_session_t; +void stop(animation_session_t& anim_ctx); +void cleanup(animation_session_t& anim_ctx); - struct animation_session_t { - animation_context_t anim; +struct animation_session_t { + animation_context_t anim; - // event file descriptor - platform::FileDescriptor trigger_efd; - platform::FileDescriptor render_efd; + // event file descriptor + platform::FileDescriptor trigger_efd; + platform::FileDescriptor render_efd; - // globals (references) - const config::config_t *_config{nullptr}; - platform::CondVariable *_configs_reloaded_cond{nullptr}; - platform::input::input_context_t *_input{nullptr}; - platform::update::update_context_t *_update{nullptr}; - atomic_uint64_t *_config_generation{nullptr}; - atomic_bool ready; - platform::CondVariable init_cond; + // globals (references) + const config::config_t *_config{nullptr}; + platform::CondVariable *_configs_reloaded_cond{nullptr}; + platform::input::input_context_t *_input{nullptr}; + platform::update::update_context_t *_update{nullptr}; + atomic_uint64_t *_config_generation{nullptr}; + atomic_bool ready; + platform::CondVariable init_cond; + animation_session_t() = default; + ~animation_session_t() { + cleanup(*this); + } + animation_session_t(const animation_session_t& other) = delete; + animation_session_t& operator=(const animation_session_t& other) = delete; + animation_session_t(animation_session_t&& other) noexcept = delete; + animation_session_t& operator=(animation_session_t&& other) noexcept = delete; +}; +inline void stop(animation_session_t& anim_ctx) { + stop(anim_ctx.anim); - animation_session_t() = default; - ~animation_session_t() { - cleanup(*this); - } + anim_ctx._config = nullptr; + anim_ctx._configs_reloaded_cond = nullptr; + anim_ctx._config_generation = nullptr; - animation_session_t(const animation_session_t& other) = delete; - animation_session_t& operator=(const animation_session_t& other) = delete; - animation_session_t(animation_session_t&& other) noexcept = delete; - animation_session_t& operator=(animation_session_t&& other) noexcept = delete; - }; - inline void stop(animation_session_t& anim_ctx) { - stop(anim_ctx.anim); - - anim_ctx._config = nullptr; - anim_ctx._configs_reloaded_cond = nullptr; - anim_ctx._config_generation = nullptr; - - anim_ctx.anim.config_updated.notify_all(); - atomic_store(&anim_ctx.ready, false); - anim_ctx.init_cond.notify_all(); - } - inline void cleanup(animation_session_t& anim_ctx) { - cleanup(anim_ctx.anim); + anim_ctx.anim.config_updated.notify_all(); + atomic_store(&anim_ctx.ready, false); + anim_ctx.init_cond.notify_all(); +} +inline void cleanup(animation_session_t& anim_ctx) { + cleanup(anim_ctx.anim); - platform::close_fd(anim_ctx.trigger_efd); - platform::close_fd(anim_ctx.render_efd); + platform::close_fd(anim_ctx.trigger_efd); + platform::close_fd(anim_ctx.render_efd); - anim_ctx._config = nullptr; - anim_ctx._input = nullptr; - anim_ctx._update = nullptr; - anim_ctx._configs_reloaded_cond = nullptr; - atomic_store(&anim_ctx.ready, false); - anim_ctx.init_cond.notify_all(); - } + anim_ctx._config = nullptr; + anim_ctx._input = nullptr; + anim_ctx._update = nullptr; + anim_ctx._configs_reloaded_cond = nullptr; + atomic_store(&anim_ctx.ready, false); + anim_ctx.init_cond.notify_all(); } +} // namespace bongocat::animation #endif diff --git a/include/graphics/sprite_sheet.h b/include/graphics/sprite_sheet.h index dac1c7a6..55d2e910 100644 --- a/include/graphics/sprite_sheet.h +++ b/include/graphics/sprite_sheet.h @@ -2,550 +2,581 @@ #define BONGOCAT_ANIMATION_SPRITE_SHEET_H #include "utils/memory.h" + #include namespace bongocat::animation { - // bongocat: both-up, left-down, right-down, both-down - inline static constexpr size_t BONGOCAT_NUM_FRAMES = 4; - // dm: Idle 1, Idle 2, Angry, Down1, Happy, Eat1, Sleep1, Refuse, Down2 ~~, Eat2, Sleep2, Attack~~ - inline static constexpr size_t MAX_DIGIMON_FRAMES = 16; - // pkmn: Idle 1, Idle 2 - inline static constexpr size_t MAX_PKMN_FRAMES = 2; - // @NOTE: MS agents can have more frames and are more custom - - inline static constexpr size_t MAX_NUM_FRAMES = 16; - - inline static constexpr size_t MAX_ANIMATION_FRAMES = 4; - // @NOTE: MS agents can have more frames per row and are more custom - - struct sprite_sheet_animation_frame_t { - bool valid{false}; - int32_t col{0}; - int32_t row{0}; - }; - - struct generic_sprite_sheet_image_t { - int32_t sprite_sheet_width{0}; - int32_t sprite_sheet_height{0}; - int32_t channels{0}; - AllocatedArray pixels; - }; - - struct dm_sprite_sheet_frames_t { - sprite_sheet_animation_frame_t idle_1; // 0 - sprite_sheet_animation_frame_t idle_2; // 1 - sprite_sheet_animation_frame_t angry; // 2 - sprite_sheet_animation_frame_t down; // 3 - sprite_sheet_animation_frame_t happy; // 4 - sprite_sheet_animation_frame_t eat_1; // 5 - sprite_sheet_animation_frame_t sleep; // 6 - sprite_sheet_animation_frame_t refuse; // 7 - sprite_sheet_animation_frame_t sad; // 8 - - // optional - sprite_sheet_animation_frame_t lose_1; // 9 - sprite_sheet_animation_frame_t eat_2; //10 - sprite_sheet_animation_frame_t lose_2; //11 - sprite_sheet_animation_frame_t attack_1; //12 - - // extra frames - sprite_sheet_animation_frame_t movement_1; //13 - sprite_sheet_animation_frame_t movement_2; //14 - sprite_sheet_animation_frame_t attack_2; //15 - }; - - struct sprite_sheet_animations_t { - int32_t idle[MAX_ANIMATION_FRAMES]{}; - int32_t boring[MAX_ANIMATION_FRAMES]{}; - int32_t writing[MAX_ANIMATION_FRAMES]{}; - int32_t sleep[MAX_ANIMATION_FRAMES]{}; - int32_t wake_up[MAX_ANIMATION_FRAMES]{}; - int32_t working[MAX_ANIMATION_FRAMES]{}; // attack - int32_t moving[MAX_ANIMATION_FRAMES]{}; - int32_t happy[MAX_ANIMATION_FRAMES]{}; - int32_t running[MAX_ANIMATION_FRAMES]{}; - }; - - struct dm_sprite_sheet_t { - generic_sprite_sheet_image_t image; - - int32_t frame_width{0}; - int32_t frame_height{0}; - int32_t total_frames{0}; - - dm_sprite_sheet_frames_t frames; - - sprite_sheet_animations_t animations; - }; - - struct pkmn_sprite_sheet_t { - generic_sprite_sheet_image_t image; - - int32_t frame_width{0}; - int32_t frame_height{0}; - int32_t total_frames{0}; - - sprite_sheet_animation_frame_t idle_1; - sprite_sheet_animation_frame_t idle_2; - - sprite_sheet_animations_t animations; - }; - - struct bongocat_sprite_sheet_animations_t { - int32_t idle[MAX_ANIMATION_FRAMES]{}; - int32_t boring[MAX_ANIMATION_FRAMES]{}; - int32_t writing[MAX_ANIMATION_FRAMES]{}; - int32_t sleep[MAX_ANIMATION_FRAMES]{}; - int32_t wake_up[MAX_ANIMATION_FRAMES]{}; - int32_t working[MAX_ANIMATION_FRAMES]{}; // attack - int32_t moving[MAX_ANIMATION_FRAMES]{}; - int32_t happy[MAX_ANIMATION_FRAMES]{}; - int32_t running[MAX_ANIMATION_FRAMES]{}; - // extras - int32_t left_writing[MAX_ANIMATION_FRAMES]{}; - int32_t right_writing[MAX_ANIMATION_FRAMES]{}; - }; - - struct bongocat_sprite_sheet_t { - generic_sprite_sheet_image_t image; - - int32_t frame_width{0}; - int32_t frame_height{0}; - int32_t total_frames{0}; - - sprite_sheet_animation_frame_t both_up; - sprite_sheet_animation_frame_t left_down; - sprite_sheet_animation_frame_t right_down; - sprite_sheet_animation_frame_t both_down; - - bongocat_sprite_sheet_animations_t animations; - }; - - struct ms_agent_sprite_sheet_animation_section_t { - bool valid{false}; - int32_t start_col{0}; - int32_t end_col{0}; - int32_t row{0}; - }; - struct ms_agent_sprite_sheet_t { - generic_sprite_sheet_image_t image; - - int32_t frame_width{0}; - int32_t frame_height{0}; - - ms_agent_sprite_sheet_animation_section_t idle; - ms_agent_sprite_sheet_animation_section_t boring; - - ms_agent_sprite_sheet_animation_section_t start_writing; - ms_agent_sprite_sheet_animation_section_t writing; - ms_agent_sprite_sheet_animation_section_t end_writing; - - ms_agent_sprite_sheet_animation_section_t sleep; - ms_agent_sprite_sheet_animation_section_t wake_up; - - ms_agent_sprite_sheet_animation_section_t start_working; - ms_agent_sprite_sheet_animation_section_t working; - ms_agent_sprite_sheet_animation_section_t end_working; - - ms_agent_sprite_sheet_animation_section_t start_moving; - ms_agent_sprite_sheet_animation_section_t moving; - ms_agent_sprite_sheet_animation_section_t end_moving; - - ms_agent_sprite_sheet_animation_section_t happy; - - ms_agent_sprite_sheet_animation_section_t start_running; - ms_agent_sprite_sheet_animation_section_t running; - ms_agent_sprite_sheet_animation_section_t end_running; - }; - - struct custom_sprite_sheet_animation_section_t { - bool valid{false}; - int32_t start_col{0}; - int32_t end_col{0}; - int32_t row{0}; - }; - struct custom_sprite_sheet_t { - generic_sprite_sheet_image_t image; - - int32_t frame_width{0}; - int32_t frame_height{0}; - - custom_sprite_sheet_animation_section_t idle; - custom_sprite_sheet_animation_section_t boring; - - custom_sprite_sheet_animation_section_t start_writing; - custom_sprite_sheet_animation_section_t writing; - custom_sprite_sheet_animation_section_t end_writing; - custom_sprite_sheet_animation_section_t happy; - - custom_sprite_sheet_animation_section_t fall_asleep; - custom_sprite_sheet_animation_section_t sleep; - custom_sprite_sheet_animation_section_t wake_up; - - custom_sprite_sheet_animation_section_t start_working; - custom_sprite_sheet_animation_section_t working; - custom_sprite_sheet_animation_section_t end_working; - - custom_sprite_sheet_animation_section_t start_moving; - custom_sprite_sheet_animation_section_t moving; - custom_sprite_sheet_animation_section_t end_moving; - - custom_sprite_sheet_animation_section_t start_running; - custom_sprite_sheet_animation_section_t running; - custom_sprite_sheet_animation_section_t end_running; - - // features - bool feature_idle{false}; - bool feature_boring{false}; - bool feature_writing{false}; - bool feature_writing_happy{false}; - bool feature_sleep{false}; - bool feature_sleep_wake_up{false}; - bool feature_working{false}; - bool feature_moving{false}; - bool feature_running{false}; - bool feature_writing_toggle_frames{false}; - bool feature_writing_toggle_frames_random{false}; - }; - - struct generic_sprite_sheet_t { - generic_sprite_sheet_image_t image{}; - - int32_t frame_width{0}; - int32_t frame_height{0}; - int32_t total_frames{0}; - - sprite_sheet_animation_frame_t frames[MAX_NUM_FRAMES]; - }; - - struct animation_t; - void cleanup_animation(animation_t& anim); - - // sprite_sheet variant - struct animation_t { - union { - bongocat_sprite_sheet_t bongocat; - dm_sprite_sheet_t dm; - ms_agent_sprite_sheet_t ms_agent; - pkmn_sprite_sheet_t pkmn; - custom_sprite_sheet_t custom; - generic_sprite_sheet_t sprite_sheet; - }; - enum class Type : uint8_t { Generic, Bongocat, Dm, MsAgent, Pkmn, Custom } type{Type::Generic}; - - animation_t() { - sprite_sheet.image.pixels.data = nullptr; - sprite_sheet.image.pixels.count = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; - sprite_sheet.total_frames = 0; - for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { - sprite_sheet.frames[i] = {}; - } - type = Type::Generic; - } - ~animation_t() { - cleanup_animation(*this); - } - - animation_t(const animation_t& other) { - type = other.type; - switch (other.type) { - case Type::Bongocat: bongocat = other.bongocat; break; - case Type::Dm: dm = other.dm; break; - case Type::MsAgent: ms_agent = other.ms_agent; break; - case Type::Pkmn: pkmn = other.pkmn; break; - case Type::Custom: custom = other.custom; break; - case Type::Generic: sprite_sheet = other.sprite_sheet; break; - } - } - animation_t& operator=(const animation_t& other) { - if (this != &other) { - cleanup_animation(*this); - type = other.type; - switch (other.type) { - case Type::Bongocat: bongocat = other.bongocat; break; - case Type::Dm: dm = other.dm; break; - case Type::MsAgent: ms_agent = other.ms_agent; break; - case Type::Pkmn: pkmn = other.pkmn; break; - case Type::Custom: custom = other.custom; break; - case Type::Generic: sprite_sheet = other.sprite_sheet; break; - } - } - return *this; - } - - animation_t(animation_t&& other) noexcept { - type = other.type; - switch (other.type) { - case Type::Bongocat: - new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(other.bongocat)); - break; - case Type::Dm: - new (&dm) dm_sprite_sheet_t(bongocat::move(other.dm)); - break; - case Type::MsAgent: - new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(other.ms_agent)); - break; - case Type::Pkmn: - new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(other.pkmn)); - break; - case Type::Custom: - new (&custom) custom_sprite_sheet_t(bongocat::move(other.custom)); - break; - case Type::Generic: - new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(other.sprite_sheet)); - break; - } - other.type = Type::Generic; - new (&other.sprite_sheet) generic_sprite_sheet_t(); - } - animation_t& operator=(animation_t&& other) noexcept { - if (this != &other) { - cleanup_animation(*this); - type = other.type; - switch (other.type) { - case Type::Bongocat: - new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(other.bongocat)); - break; - case Type::Dm: - new (&dm) dm_sprite_sheet_t(bongocat::move(other.dm)); - break; - case Type::MsAgent: - new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(other.ms_agent)); - break; - case Type::Pkmn: - new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(other.pkmn)); - break; - case Type::Custom: - new (&custom) custom_sprite_sheet_t(bongocat::move(other.custom)); - break; - case Type::Generic: - new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(other.sprite_sheet)); - break; - } - other.type = Type::Generic; - new (&other.sprite_sheet) generic_sprite_sheet_t(); - } - return *this; - } - - explicit animation_t(bongocat_sprite_sheet_t&& sheet) noexcept - : bongocat(bongocat::move(sheet)), type(Type::Bongocat) {} - - explicit animation_t(dm_sprite_sheet_t&& sheet) noexcept - : dm(bongocat::move(sheet)), type(Type::Dm) {} - - explicit animation_t(ms_agent_sprite_sheet_t&& sheet) noexcept - : ms_agent(bongocat::move(sheet)), type(Type::MsAgent) {} - - explicit animation_t(pkmn_sprite_sheet_t&& sheet) noexcept - : pkmn(bongocat::move(sheet)), type(Type::Pkmn) {} - - explicit animation_t(custom_sprite_sheet_t&& sheet) noexcept - : custom(bongocat::move(sheet)), type(Type::Custom) {} - - explicit animation_t(generic_sprite_sheet_t&& sheet) noexcept - : sprite_sheet(bongocat::move(sheet)), type(Type::Generic) {} - - - animation_t& operator=(bongocat_sprite_sheet_t&& sheet) noexcept { - cleanup_animation(*this); - new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Bongocat; - return *this; - } - animation_t& operator=(dm_sprite_sheet_t&& sheet) noexcept { - cleanup_animation(*this); - new (&dm) dm_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Dm; - return *this; - } - animation_t& operator=(ms_agent_sprite_sheet_t&& sheet) noexcept { - cleanup_animation(*this); - new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(sheet)); - type = Type::MsAgent; - return *this; - } - animation_t& operator=(pkmn_sprite_sheet_t&& sheet) noexcept { - cleanup_animation(*this); - new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Pkmn; - return *this; - } - animation_t& operator=(custom_sprite_sheet_t&& sheet) noexcept { - cleanup_animation(*this); - new (&custom) custom_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Custom; - return *this; - } - animation_t& operator=(generic_sprite_sheet_t&& sheet) noexcept { - cleanup_animation(*this); - new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Generic; - return *this; - } - }; - inline void cleanup_animation(generic_sprite_sheet_t& sprite_sheet) { - release_allocated_array(sprite_sheet.image.pixels); - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; - sprite_sheet.total_frames = 0; - for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { - sprite_sheet.frames[i] = {}; - } +// bongocat: both-up, left-down, right-down, both-down +inline static constexpr size_t BONGOCAT_NUM_FRAMES = 4; +// dm: Idle 1, Idle 2, Angry, Down1, Happy, Eat1, Sleep1, Refuse, Down2 ~~, Eat2, Sleep2, Attack~~ +inline static constexpr size_t MAX_DIGIMON_FRAMES = 16; +// pkmn: Idle 1, Idle 2 +inline static constexpr size_t MAX_PKMN_FRAMES = 2; +// @NOTE: MS agents can have more frames and are more custom + +inline static constexpr size_t MAX_NUM_FRAMES = 16; + +inline static constexpr size_t MAX_ANIMATION_FRAMES = 4; +// @NOTE: MS agents can have more frames per row and are more custom + +struct sprite_sheet_animation_frame_t { + bool valid{false}; + int32_t col{0}; + int32_t row{0}; +}; + +struct generic_sprite_sheet_image_t { + int32_t sprite_sheet_width{0}; + int32_t sprite_sheet_height{0}; + int32_t channels{0}; + AllocatedArray pixels; +}; + +struct dm_sprite_sheet_frames_t { + sprite_sheet_animation_frame_t idle_1; // 0 + sprite_sheet_animation_frame_t idle_2; // 1 + sprite_sheet_animation_frame_t angry; // 2 + sprite_sheet_animation_frame_t down; // 3 + sprite_sheet_animation_frame_t happy; // 4 + sprite_sheet_animation_frame_t eat_1; // 5 + sprite_sheet_animation_frame_t sleep; // 6 + sprite_sheet_animation_frame_t refuse; // 7 + sprite_sheet_animation_frame_t sad; // 8 + + // optional + sprite_sheet_animation_frame_t lose_1; // 9 + sprite_sheet_animation_frame_t eat_2; // 10 + sprite_sheet_animation_frame_t lose_2; // 11 + sprite_sheet_animation_frame_t attack_1; // 12 + + // extra frames + sprite_sheet_animation_frame_t movement_1; // 13 + sprite_sheet_animation_frame_t movement_2; // 14 + sprite_sheet_animation_frame_t attack_2; // 15 +}; + +struct sprite_sheet_animations_t { + int32_t idle[MAX_ANIMATION_FRAMES]{}; + int32_t boring[MAX_ANIMATION_FRAMES]{}; + int32_t writing[MAX_ANIMATION_FRAMES]{}; + int32_t sleep[MAX_ANIMATION_FRAMES]{}; + int32_t wake_up[MAX_ANIMATION_FRAMES]{}; + int32_t working[MAX_ANIMATION_FRAMES]{}; // attack + int32_t moving[MAX_ANIMATION_FRAMES]{}; + int32_t happy[MAX_ANIMATION_FRAMES]{}; + int32_t running[MAX_ANIMATION_FRAMES]{}; +}; + +struct dm_sprite_sheet_t { + generic_sprite_sheet_image_t image; + + int32_t frame_width{0}; + int32_t frame_height{0}; + int32_t total_frames{0}; + + dm_sprite_sheet_frames_t frames; + + sprite_sheet_animations_t animations; +}; + +struct pkmn_sprite_sheet_t { + generic_sprite_sheet_image_t image; + + int32_t frame_width{0}; + int32_t frame_height{0}; + int32_t total_frames{0}; + + sprite_sheet_animation_frame_t idle_1; + sprite_sheet_animation_frame_t idle_2; + + sprite_sheet_animations_t animations; +}; + +struct bongocat_sprite_sheet_animations_t { + int32_t idle[MAX_ANIMATION_FRAMES]{}; + int32_t boring[MAX_ANIMATION_FRAMES]{}; + int32_t writing[MAX_ANIMATION_FRAMES]{}; + int32_t sleep[MAX_ANIMATION_FRAMES]{}; + int32_t wake_up[MAX_ANIMATION_FRAMES]{}; + int32_t working[MAX_ANIMATION_FRAMES]{}; // attack + int32_t moving[MAX_ANIMATION_FRAMES]{}; + int32_t happy[MAX_ANIMATION_FRAMES]{}; + int32_t running[MAX_ANIMATION_FRAMES]{}; + // extras + int32_t left_writing[MAX_ANIMATION_FRAMES]{}; + int32_t right_writing[MAX_ANIMATION_FRAMES]{}; +}; + +struct bongocat_sprite_sheet_t { + generic_sprite_sheet_image_t image; + + int32_t frame_width{0}; + int32_t frame_height{0}; + int32_t total_frames{0}; + + sprite_sheet_animation_frame_t both_up; + sprite_sheet_animation_frame_t left_down; + sprite_sheet_animation_frame_t right_down; + sprite_sheet_animation_frame_t both_down; + + bongocat_sprite_sheet_animations_t animations; +}; + +struct ms_agent_sprite_sheet_animation_section_t { + bool valid{false}; + int32_t start_col{0}; + int32_t end_col{0}; + int32_t row{0}; +}; +struct ms_agent_sprite_sheet_t { + generic_sprite_sheet_image_t image; + + int32_t frame_width{0}; + int32_t frame_height{0}; + + ms_agent_sprite_sheet_animation_section_t idle; + ms_agent_sprite_sheet_animation_section_t boring; + + ms_agent_sprite_sheet_animation_section_t start_writing; + ms_agent_sprite_sheet_animation_section_t writing; + ms_agent_sprite_sheet_animation_section_t end_writing; + + ms_agent_sprite_sheet_animation_section_t sleep; + ms_agent_sprite_sheet_animation_section_t wake_up; + + ms_agent_sprite_sheet_animation_section_t start_working; + ms_agent_sprite_sheet_animation_section_t working; + ms_agent_sprite_sheet_animation_section_t end_working; + + ms_agent_sprite_sheet_animation_section_t start_moving; + ms_agent_sprite_sheet_animation_section_t moving; + ms_agent_sprite_sheet_animation_section_t end_moving; + + ms_agent_sprite_sheet_animation_section_t happy; + + ms_agent_sprite_sheet_animation_section_t start_running; + ms_agent_sprite_sheet_animation_section_t running; + ms_agent_sprite_sheet_animation_section_t end_running; +}; + +struct custom_sprite_sheet_animation_section_t { + bool valid{false}; + int32_t start_col{0}; + int32_t end_col{0}; + int32_t row{0}; +}; +struct custom_sprite_sheet_t { + generic_sprite_sheet_image_t image; + + int32_t frame_width{0}; + int32_t frame_height{0}; + + custom_sprite_sheet_animation_section_t idle; + custom_sprite_sheet_animation_section_t boring; + + custom_sprite_sheet_animation_section_t start_writing; + custom_sprite_sheet_animation_section_t writing; + custom_sprite_sheet_animation_section_t end_writing; + custom_sprite_sheet_animation_section_t happy; + + custom_sprite_sheet_animation_section_t fall_asleep; + custom_sprite_sheet_animation_section_t sleep; + custom_sprite_sheet_animation_section_t wake_up; + + custom_sprite_sheet_animation_section_t start_working; + custom_sprite_sheet_animation_section_t working; + custom_sprite_sheet_animation_section_t end_working; + + custom_sprite_sheet_animation_section_t start_moving; + custom_sprite_sheet_animation_section_t moving; + custom_sprite_sheet_animation_section_t end_moving; + + custom_sprite_sheet_animation_section_t start_running; + custom_sprite_sheet_animation_section_t running; + custom_sprite_sheet_animation_section_t end_running; + + // features + bool feature_idle{false}; + bool feature_boring{false}; + bool feature_writing{false}; + bool feature_writing_happy{false}; + bool feature_sleep{false}; + bool feature_sleep_wake_up{false}; + bool feature_working{false}; + bool feature_moving{false}; + bool feature_running{false}; + bool feature_writing_toggle_frames{false}; + bool feature_writing_toggle_frames_random{false}; +}; + +struct generic_sprite_sheet_t { + generic_sprite_sheet_image_t image{}; + + int32_t frame_width{0}; + int32_t frame_height{0}; + int32_t total_frames{0}; + + sprite_sheet_animation_frame_t frames[MAX_NUM_FRAMES]; +}; + +struct animation_t; +void cleanup_animation(animation_t& anim); + +// sprite_sheet variant +struct animation_t { + union { + bongocat_sprite_sheet_t bongocat; + dm_sprite_sheet_t dm; + ms_agent_sprite_sheet_t ms_agent; + pkmn_sprite_sheet_t pkmn; + custom_sprite_sheet_t custom; + generic_sprite_sheet_t sprite_sheet; + }; + enum class Type : uint8_t { + Generic, + Bongocat, + Dm, + MsAgent, + Pkmn, + Custom + } type{Type::Generic}; + + animation_t() { + sprite_sheet.image.pixels.data = nullptr; + sprite_sheet.image.pixels.count = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; + sprite_sheet.total_frames = 0; + for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { + sprite_sheet.frames[i] = {}; + } + type = Type::Generic; + } + ~animation_t() { + cleanup_animation(*this); + } + + animation_t(const animation_t& other) { + type = other.type; + switch (other.type) { + case Type::Bongocat: + bongocat = other.bongocat; + break; + case Type::Dm: + dm = other.dm; + break; + case Type::MsAgent: + ms_agent = other.ms_agent; + break; + case Type::Pkmn: + pkmn = other.pkmn; + break; + case Type::Custom: + custom = other.custom; + break; + case Type::Generic: + sprite_sheet = other.sprite_sheet; + break; } - inline void cleanup_animation(dm_sprite_sheet_t& sprite_sheet) { - release_allocated_array(sprite_sheet.image.pixels); - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; - sprite_sheet.total_frames = 0; - sprite_sheet.animations = {}; + } + animation_t& operator=(const animation_t& other) { + if (this != &other) { + cleanup_animation(*this); + type = other.type; + switch (other.type) { + case Type::Bongocat: + bongocat = other.bongocat; + break; + case Type::Dm: + dm = other.dm; + break; + case Type::MsAgent: + ms_agent = other.ms_agent; + break; + case Type::Pkmn: + pkmn = other.pkmn; + break; + case Type::Custom: + custom = other.custom; + break; + case Type::Generic: + sprite_sheet = other.sprite_sheet; + break; + } } - inline void cleanup_animation(pkmn_sprite_sheet_t& sprite_sheet) { - release_allocated_array(sprite_sheet.image.pixels); - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; - sprite_sheet.total_frames = 0; - sprite_sheet.animations = {}; + return *this; + } + + animation_t(animation_t&& other) noexcept { + type = other.type; + switch (other.type) { + case Type::Bongocat: + new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(other.bongocat)); + break; + case Type::Dm: + new (&dm) dm_sprite_sheet_t(bongocat::move(other.dm)); + break; + case Type::MsAgent: + new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(other.ms_agent)); + break; + case Type::Pkmn: + new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(other.pkmn)); + break; + case Type::Custom: + new (&custom) custom_sprite_sheet_t(bongocat::move(other.custom)); + break; + case Type::Generic: + new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(other.sprite_sheet)); + break; } - inline void cleanup_animation(bongocat_sprite_sheet_t& sprite_sheet) { - release_allocated_array(sprite_sheet.image.pixels); - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; - sprite_sheet.total_frames = 0; - sprite_sheet.animations = {}; + other.type = Type::Generic; + new (&other.sprite_sheet) generic_sprite_sheet_t(); + } + animation_t& operator=(animation_t&& other) noexcept { + if (this != &other) { + cleanup_animation(*this); + type = other.type; + switch (other.type) { + case Type::Bongocat: + new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(other.bongocat)); + break; + case Type::Dm: + new (&dm) dm_sprite_sheet_t(bongocat::move(other.dm)); + break; + case Type::MsAgent: + new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(other.ms_agent)); + break; + case Type::Pkmn: + new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(other.pkmn)); + break; + case Type::Custom: + new (&custom) custom_sprite_sheet_t(bongocat::move(other.custom)); + break; + case Type::Generic: + new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(other.sprite_sheet)); + break; + } + other.type = Type::Generic; + new (&other.sprite_sheet) generic_sprite_sheet_t(); } - inline void cleanup_animation(ms_agent_sprite_sheet_t& sprite_sheet) { - release_allocated_array(sprite_sheet.image.pixels); - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; + return *this; + } + + explicit animation_t(bongocat_sprite_sheet_t&& sheet) noexcept + : bongocat(bongocat::move(sheet)) + , type(Type::Bongocat) {} + + explicit animation_t(dm_sprite_sheet_t&& sheet) noexcept : dm(bongocat::move(sheet)), type(Type::Dm) {} + + explicit animation_t(ms_agent_sprite_sheet_t&& sheet) noexcept + : ms_agent(bongocat::move(sheet)) + , type(Type::MsAgent) {} + + explicit animation_t(pkmn_sprite_sheet_t&& sheet) noexcept : pkmn(bongocat::move(sheet)), type(Type::Pkmn) {} + + explicit animation_t(custom_sprite_sheet_t&& sheet) noexcept : custom(bongocat::move(sheet)), type(Type::Custom) {} + + explicit animation_t(generic_sprite_sheet_t&& sheet) noexcept + : sprite_sheet(bongocat::move(sheet)) + , type(Type::Generic) {} + + animation_t& operator=(bongocat_sprite_sheet_t&& sheet) noexcept { + cleanup_animation(*this); + new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(sheet)); + type = Type::Bongocat; + return *this; + } + animation_t& operator=(dm_sprite_sheet_t&& sheet) noexcept { + cleanup_animation(*this); + new (&dm) dm_sprite_sheet_t(bongocat::move(sheet)); + type = Type::Dm; + return *this; + } + animation_t& operator=(ms_agent_sprite_sheet_t&& sheet) noexcept { + cleanup_animation(*this); + new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(sheet)); + type = Type::MsAgent; + return *this; + } + animation_t& operator=(pkmn_sprite_sheet_t&& sheet) noexcept { + cleanup_animation(*this); + new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(sheet)); + type = Type::Pkmn; + return *this; + } + animation_t& operator=(custom_sprite_sheet_t&& sheet) noexcept { + cleanup_animation(*this); + new (&custom) custom_sprite_sheet_t(bongocat::move(sheet)); + type = Type::Custom; + return *this; + } + animation_t& operator=(generic_sprite_sheet_t&& sheet) noexcept { + cleanup_animation(*this); + new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(sheet)); + type = Type::Generic; + return *this; + } +}; +inline void cleanup_animation(generic_sprite_sheet_t& sprite_sheet) { + release_allocated_array(sprite_sheet.image.pixels); + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; + sprite_sheet.total_frames = 0; + for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { + sprite_sheet.frames[i] = {}; + } +} +inline void cleanup_animation(dm_sprite_sheet_t& sprite_sheet) { + release_allocated_array(sprite_sheet.image.pixels); + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; + sprite_sheet.total_frames = 0; + sprite_sheet.animations = {}; +} +inline void cleanup_animation(pkmn_sprite_sheet_t& sprite_sheet) { + release_allocated_array(sprite_sheet.image.pixels); + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; + sprite_sheet.total_frames = 0; + sprite_sheet.animations = {}; +} +inline void cleanup_animation(bongocat_sprite_sheet_t& sprite_sheet) { + release_allocated_array(sprite_sheet.image.pixels); + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; + sprite_sheet.total_frames = 0; + sprite_sheet.animations = {}; +} +inline void cleanup_animation(ms_agent_sprite_sheet_t& sprite_sheet) { + release_allocated_array(sprite_sheet.image.pixels); + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; - sprite_sheet.idle = {}; - sprite_sheet.boring = {}; + sprite_sheet.idle = {}; + sprite_sheet.boring = {}; - sprite_sheet.start_writing = {}; - sprite_sheet.writing = {}; - sprite_sheet.end_writing = {}; + sprite_sheet.start_writing = {}; + sprite_sheet.writing = {}; + sprite_sheet.end_writing = {}; - sprite_sheet.sleep = {}; - sprite_sheet.wake_up = {}; + sprite_sheet.sleep = {}; + sprite_sheet.wake_up = {}; - sprite_sheet.start_working = {}; - sprite_sheet.working = {}; - sprite_sheet.end_working = {}; + sprite_sheet.start_working = {}; + sprite_sheet.working = {}; + sprite_sheet.end_working = {}; - sprite_sheet.start_moving = {}; - sprite_sheet.moving = {}; - sprite_sheet.end_moving = {}; + sprite_sheet.start_moving = {}; + sprite_sheet.moving = {}; + sprite_sheet.end_moving = {}; - sprite_sheet.happy = {}; - } - inline void cleanup_animation(custom_sprite_sheet_t& sprite_sheet) { - release_allocated_array(sprite_sheet.image.pixels); - sprite_sheet.image.sprite_sheet_width = 0; - sprite_sheet.image.sprite_sheet_height = 0; - sprite_sheet.image.channels = 0; - sprite_sheet.frame_width = 0; - sprite_sheet.frame_height = 0; - - sprite_sheet.idle = {}; - sprite_sheet.boring = {}; - - sprite_sheet.start_writing = {}; - sprite_sheet.writing = {}; - sprite_sheet.end_writing = {}; - sprite_sheet.happy = {}; - - sprite_sheet.fall_asleep = {}; - sprite_sheet.sleep = {}; - sprite_sheet.wake_up = {}; - - sprite_sheet.start_working = {}; - sprite_sheet.working = {}; - sprite_sheet.end_working = {}; - - sprite_sheet.start_moving = {}; - sprite_sheet.moving = {}; - sprite_sheet.end_moving = {}; - } - inline void cleanup_animation(animation_t& anim) { - switch (anim.type) { - case animation_t::Type::Bongocat: - release_allocated_array(anim.bongocat.image.pixels); - anim.bongocat.image.sprite_sheet_width = 0; - anim.bongocat.image.sprite_sheet_height = 0; - anim.bongocat.image.channels = 0; - anim.bongocat.frame_width = 0; - anim.bongocat.frame_height = 0; - break; - case animation_t::Type::Dm: - release_allocated_array(anim.dm.image.pixels); - anim.dm.image.sprite_sheet_width = 0; - anim.dm.image.sprite_sheet_height = 0; - anim.dm.image.channels = 0; - anim.dm.frame_width = 0; - anim.dm.frame_height = 0; - - anim.dm.total_frames = 0; - break; - case animation_t::Type::MsAgent: - release_allocated_array(anim.ms_agent.image.pixels); - anim.ms_agent.image.sprite_sheet_width = 0; - anim.ms_agent.image.sprite_sheet_height = 0; - anim.ms_agent.image.channels = 0; - anim.ms_agent.frame_width = 0; - anim.ms_agent.frame_height = 0; - break; - case animation_t::Type::Pkmn: - release_allocated_array(anim.pkmn.image.pixels); - anim.pkmn.image.sprite_sheet_width = 0; - anim.pkmn.image.sprite_sheet_height = 0; - anim.pkmn.image.channels = 0; - anim.pkmn.frame_width = 0; - anim.pkmn.frame_height = 0; - break; - case animation_t::Type::Custom: - release_allocated_array(anim.custom.image.pixels); - anim.custom.image.sprite_sheet_width = 0; - anim.custom.image.sprite_sheet_height = 0; - anim.custom.image.channels = 0; - anim.custom.frame_width = 0; - anim.custom.frame_height = 0; - break; - case animation_t::Type::Generic: - release_allocated_array(anim.sprite_sheet.image.pixels); - anim.sprite_sheet.image.sprite_sheet_width = 0; - anim.sprite_sheet.image.sprite_sheet_height = 0; - anim.sprite_sheet.image.channels = 0; - anim.sprite_sheet.frame_width = 0; - anim.sprite_sheet.frame_height = 0; - - anim.sprite_sheet.total_frames = 0; - for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { - anim.sprite_sheet.frames[i] = {}; - } - break; - } + sprite_sheet.happy = {}; +} +inline void cleanup_animation(custom_sprite_sheet_t& sprite_sheet) { + release_allocated_array(sprite_sheet.image.pixels); + sprite_sheet.image.sprite_sheet_width = 0; + sprite_sheet.image.sprite_sheet_height = 0; + sprite_sheet.image.channels = 0; + sprite_sheet.frame_width = 0; + sprite_sheet.frame_height = 0; + + sprite_sheet.idle = {}; + sprite_sheet.boring = {}; + + sprite_sheet.start_writing = {}; + sprite_sheet.writing = {}; + sprite_sheet.end_writing = {}; + sprite_sheet.happy = {}; + + sprite_sheet.fall_asleep = {}; + sprite_sheet.sleep = {}; + sprite_sheet.wake_up = {}; + + sprite_sheet.start_working = {}; + sprite_sheet.working = {}; + sprite_sheet.end_working = {}; + + sprite_sheet.start_moving = {}; + sprite_sheet.moving = {}; + sprite_sheet.end_moving = {}; +} +inline void cleanup_animation(animation_t& anim) { + switch (anim.type) { + case animation_t::Type::Bongocat: + release_allocated_array(anim.bongocat.image.pixels); + anim.bongocat.image.sprite_sheet_width = 0; + anim.bongocat.image.sprite_sheet_height = 0; + anim.bongocat.image.channels = 0; + anim.bongocat.frame_width = 0; + anim.bongocat.frame_height = 0; + break; + case animation_t::Type::Dm: + release_allocated_array(anim.dm.image.pixels); + anim.dm.image.sprite_sheet_width = 0; + anim.dm.image.sprite_sheet_height = 0; + anim.dm.image.channels = 0; + anim.dm.frame_width = 0; + anim.dm.frame_height = 0; + + anim.dm.total_frames = 0; + break; + case animation_t::Type::MsAgent: + release_allocated_array(anim.ms_agent.image.pixels); + anim.ms_agent.image.sprite_sheet_width = 0; + anim.ms_agent.image.sprite_sheet_height = 0; + anim.ms_agent.image.channels = 0; + anim.ms_agent.frame_width = 0; + anim.ms_agent.frame_height = 0; + break; + case animation_t::Type::Pkmn: + release_allocated_array(anim.pkmn.image.pixels); + anim.pkmn.image.sprite_sheet_width = 0; + anim.pkmn.image.sprite_sheet_height = 0; + anim.pkmn.image.channels = 0; + anim.pkmn.frame_width = 0; + anim.pkmn.frame_height = 0; + break; + case animation_t::Type::Custom: + release_allocated_array(anim.custom.image.pixels); + anim.custom.image.sprite_sheet_width = 0; + anim.custom.image.sprite_sheet_height = 0; + anim.custom.image.channels = 0; + anim.custom.frame_width = 0; + anim.custom.frame_height = 0; + break; + case animation_t::Type::Generic: + release_allocated_array(anim.sprite_sheet.image.pixels); + anim.sprite_sheet.image.sprite_sheet_width = 0; + anim.sprite_sheet.image.sprite_sheet_height = 0; + anim.sprite_sheet.image.channels = 0; + anim.sprite_sheet.frame_width = 0; + anim.sprite_sheet.frame_height = 0; + + anim.sprite_sheet.total_frames = 0; + for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { + anim.sprite_sheet.frames[i] = {}; } + break; + } } +} // namespace bongocat::animation -#endif //BONGOCAT_ANIMATION_SPRITE_SHEET_H +#endif // BONGOCAT_ANIMATION_SPRITE_SHEET_H diff --git a/include/image_loader/base_dm/load_dm.h b/include/image_loader/base_dm/load_dm.h index a6773301..4360feb9 100644 --- a/include/image_loader/base_dm/load_dm.h +++ b/include/image_loader/base_dm/load_dm.h @@ -1,10 +1,12 @@ #pragma once #include "core/bongocat.h" -#include "graphics/sprite_sheet.h" #include "embedded_assets/embedded_image.h" +#include "graphics/sprite_sheet.h" namespace bongocat::animation { - struct animation_context_t; - BONGOCAT_NODISCARD created_result_t load_dm_anim(const animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); -} +struct animation_context_t; +BONGOCAT_NODISCARD created_result_t load_dm_anim(const animation_context_t& ctx, int anim_index, + const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows); +} // namespace bongocat::animation diff --git a/include/image_loader/bongocat/load_images_bongocat.h b/include/image_loader/bongocat/load_images_bongocat.h index e1d68de8..85b298e6 100644 --- a/include/image_loader/bongocat/load_images_bongocat.h +++ b/include/image_loader/bongocat/load_images_bongocat.h @@ -5,9 +5,12 @@ #include "image_loader/load_images.h" namespace bongocat::animation { - struct animation_context_t; - [[nodiscard]] created_result_t load_bongocat_anim(int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); - bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); +struct animation_context_t; +[[nodiscard]] created_result_t +load_bongocat_anim(int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); +bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, + size_t embedded_images_count); - [[nodiscard]] created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, int index); -} +[[nodiscard]] created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, + int index); +} // namespace bongocat::animation diff --git a/include/image_loader/custom/load_custom.h b/include/image_loader/custom/load_custom.h index bd7bbcf0..2e3d1d16 100644 --- a/include/image_loader/custom/load_custom.h +++ b/include/image_loader/custom/load_custom.h @@ -1,55 +1,60 @@ #pragma once #include "core/bongocat.h" -#include "graphics/sprite_sheet.h" -#include "embedded_assets/embedded_image.h" #include "embedded_assets/custom/custom_sprite.h" +#include "embedded_assets/embedded_image.h" +#include "graphics/sprite_sheet.h" #include "utils/system_memory.h" namespace bongocat::assets { - struct custom_image_t; +struct custom_image_t; } namespace bongocat::animation { - void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept; +void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept; } namespace bongocat::assets { - struct custom_image_t { - platform::MMapFileContent data; - char *name{nullptr}; - - custom_image_t() = default; - custom_image_t(const custom_image_t& other) = delete; - custom_image_t(custom_image_t&& other) noexcept - : data(bongocat::move(other.data)), name(other.name) { - other.name = nullptr; - } - ~custom_image_t() { - animation::free_custom_sprite_sheet_file(*this); - } - - custom_image_t& operator=(const custom_image_t& other) = delete; - custom_image_t& operator=(custom_image_t&& other) noexcept { - if (this != &other) { - animation::free_custom_sprite_sheet_file(*this); - - data = bongocat::move(other.data); - - if (name) ::free(name); - name = nullptr; - name = other.name; - - other.name = nullptr; - } - return *this; - } - }; -} +struct custom_image_t { + platform::MMapFileContent data; + char *name{nullptr}; + + custom_image_t() = default; + custom_image_t(const custom_image_t& other) = delete; + custom_image_t(custom_image_t&& other) noexcept : data(bongocat::move(other.data)), name(other.name) { + other.name = nullptr; + } + ~custom_image_t() { + animation::free_custom_sprite_sheet_file(*this); + } + + custom_image_t& operator=(const custom_image_t& other) = delete; + custom_image_t& operator=(custom_image_t&& other) noexcept { + if (this != &other) { + animation::free_custom_sprite_sheet_file(*this); + + data = bongocat::move(other.data); + + if (name) + ::free(name); + name = nullptr; + name = other.name; + + other.name = nullptr; + } + return *this; + } +}; +} // namespace bongocat::assets namespace bongocat::animation { - struct animation_context_t; - BONGOCAT_NODISCARD created_result_t load_custom_sprite_sheet_file(const char* filename); +struct animation_context_t; +BONGOCAT_NODISCARD created_result_t load_custom_sprite_sheet_file(const char *filename); - BONGOCAT_NODISCARD created_result_t load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); - BONGOCAT_NODISCARD created_result_t load_custom_anim(const animation_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); -} +BONGOCAT_NODISCARD created_result_t +load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, + const assets::custom_animation_settings_t& sprite_sheet_settings); + +BONGOCAT_NODISCARD created_result_t +load_custom_anim(const animation_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, + const assets::custom_animation_settings_t& sprite_sheet_settings); +} // namespace bongocat::animation diff --git a/include/image_loader/load_images.h b/include/image_loader/load_images.h index 999768c3..f3e180ef 100644 --- a/include/image_loader/load_images.h +++ b/include/image_loader/load_images.h @@ -1,95 +1,99 @@ #ifndef BONGOCAT_EMBEDDED_LOAD_IMAGES_H #define BONGOCAT_EMBEDDED_LOAD_IMAGES_H -#include "graphics/sprite_sheet.h" -#include "core/bongocat.h" #include "config/config.h" -#include "utils/memory.h" +#include "core/bongocat.h" #include "embedded_assets/embedded_image.h" +#include "graphics/sprite_sheet.h" +#include "utils/memory.h" + #include namespace bongocat::animation { - // ============================================================================= - // IMAGE LOADING MODULE - // ============================================================================= - - struct Image; - created_result_t load_image(const unsigned char *data, size_t size, int desired_channels = RGBA_CHANNELS); - void cleanup_image(Image& image); - void init_image_loader(); - - struct Image { - unsigned char *pixels{nullptr}; - int width{0}; - int height{0}; - int channels{0}; - - Image() = default; - ~Image() { - cleanup_image(*this); - } - - Image(const Image& other) - : width(other.width), height(other.height), channels(other.channels) - { - assert(width >= 0); - assert(height >= 0); - assert(channels >= 0); - const size_t data_size = static_cast(width) * static_cast(height) * static_cast(channels); - pixels = static_cast(::malloc(data_size)); - ::memcpy(pixels, other.pixels, data_size); - } - Image& operator=(const Image& other) { - if (this == &other) return *this; - - cleanup_image(*this); - - width = other.width; - height = other.height; - channels = other.channels; - - assert(width >= 0); - assert(height >= 0); - assert(channels >= 0); - const size_t data_size = static_cast(width) * static_cast(height) * static_cast(channels); - pixels = static_cast(::malloc(data_size)); - ::memcpy(pixels, other.pixels, data_size); - - return *this; - } - - Image(Image&& other) noexcept - : pixels(other.pixels), width(other.width), height(other.height), channels(other.channels) - { - other.pixels = nullptr; - other.width = 0; - other.height = 0; - other.channels = 0; - } - Image& operator=(Image&& other) noexcept { - if (this == &other) return *this; - - cleanup_image(*this); - - pixels = other.pixels; - width = other.width; - height = other.height; - channels = other.channels; - - other.pixels = nullptr; - other.width = 0; - other.height = 0; - other.channels = 0; - - return *this; - } - }; - - using get_sprite_callback_t = assets::embedded_image_t (*)(size_t); - - struct animation_context_t; - BONGOCAT_NODISCARD created_result_t anim_sprite_sheet_from_embedded_images(get_sprite_callback_t get_sprite, size_t embedded_images_count); - BONGOCAT_NODISCARD created_result_t load_sprite_sheet_anim(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); -} +// ============================================================================= +// IMAGE LOADING MODULE +// ============================================================================= + +struct Image; +created_result_t load_image(const unsigned char *data, size_t size, int desired_channels = RGBA_CHANNELS); +void cleanup_image(Image& image); +void init_image_loader(); + +struct Image { + unsigned char *pixels{nullptr}; + int width{0}; + int height{0}; + int channels{0}; + + Image() = default; + ~Image() { + cleanup_image(*this); + } + + Image(const Image& other) : width(other.width), height(other.height), channels(other.channels) { + assert(width >= 0); + assert(height >= 0); + assert(channels >= 0); + const size_t data_size = static_cast(width) * static_cast(height) * static_cast(channels); + pixels = static_cast(::malloc(data_size)); + ::memcpy(pixels, other.pixels, data_size); + } + Image& operator=(const Image& other) { + if (this == &other) + return *this; + + cleanup_image(*this); + + width = other.width; + height = other.height; + channels = other.channels; + + assert(width >= 0); + assert(height >= 0); + assert(channels >= 0); + const size_t data_size = static_cast(width) * static_cast(height) * static_cast(channels); + pixels = static_cast(::malloc(data_size)); + ::memcpy(pixels, other.pixels, data_size); + + return *this; + } + + Image(Image&& other) noexcept + : pixels(other.pixels), width(other.width), height(other.height), channels(other.channels) { + other.pixels = nullptr; + other.width = 0; + other.height = 0; + other.channels = 0; + } + Image& operator=(Image&& other) noexcept { + if (this == &other) + return *this; + + cleanup_image(*this); + + pixels = other.pixels; + width = other.width; + height = other.height; + channels = other.channels; + + other.pixels = nullptr; + other.width = 0; + other.height = 0; + other.channels = 0; + + return *this; + } +}; + +using get_sprite_callback_t = assets::embedded_image_t (*)(size_t); + +struct animation_context_t; +BONGOCAT_NODISCARD created_result_t +anim_sprite_sheet_from_embedded_images(get_sprite_callback_t get_sprite, size_t embedded_images_count); + +BONGOCAT_NODISCARD created_result_t +load_sprite_sheet_anim(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows); +} // namespace bongocat::animation #endif \ No newline at end of file diff --git a/include/image_loader/min_dm/load_images_min_dm.h b/include/image_loader/min_dm/load_images_min_dm.h index fc9f6c22..aebef148 100644 --- a/include/image_loader/min_dm/load_images_min_dm.h +++ b/include/image_loader/min_dm/load_images_min_dm.h @@ -1,12 +1,14 @@ #pragma once #include "core/bongocat.h" -#include "graphics/sprite_sheet.h" #include "embedded_assets/embedded_image.h" +#include "graphics/sprite_sheet.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); +struct animation_context_t; +bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, + const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, + int sprite_sheet_rows); - [[nodiscard]] created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index); -} +[[nodiscard]] created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index); +} // namespace bongocat::animation diff --git a/include/image_loader/misc/load_images_misc.h b/include/image_loader/misc/load_images_misc.h index cc8b13c4..45d5f005 100644 --- a/include/image_loader/misc/load_images_misc.h +++ b/include/image_loader/misc/load_images_misc.h @@ -1,13 +1,15 @@ #pragma once #include "core/bongocat.h" -#include "graphics/sprite_sheet.h" -#include "embedded_assets/embedded_image.h" #include "embedded_assets/custom/custom_sprite.h" +#include "embedded_assets/embedded_image.h" +#include "graphics/sprite_sheet.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); +struct animation_context_t; +bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, + const assets::embedded_image_t& sprite_sheet_image, + const assets::custom_animation_settings_t& sprite_sheet_settings); - [[nodiscard]] created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index); -} +[[nodiscard]] created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index); +} // namespace bongocat::animation diff --git a/include/image_loader/ms_agent/load_images_ms_agent.h b/include/image_loader/ms_agent/load_images_ms_agent.h index 776341b4..07cc4d5e 100644 --- a/include/image_loader/ms_agent/load_images_ms_agent.h +++ b/include/image_loader/ms_agent/load_images_ms_agent.h @@ -1,15 +1,24 @@ #pragma once #include "core/bongocat.h" -#include "graphics/sprite_sheet.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/ms_agent/ms_agent_sprite.h" +#include "graphics/sprite_sheet.h" namespace bongocat::animation { - struct animation_context_t; - [[nodiscard]] created_result_t load_ms_agent_sprite_sheet(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_ms_agent_anim(const animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data); - created_result_t init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data); +struct animation_context_t; +[[nodiscard]] created_result_t +load_ms_agent_sprite_sheet(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows); +[[nodiscard]] created_result_t +load_ms_agent_anim(const animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows, + const assets::ms_agent_animation_indices_t& animation_data); +created_result_t +init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows, + const assets::ms_agent_animation_indices_t& animation_data); - [[nodiscard]] created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, int index); -} +[[nodiscard]] created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, + int index); +} // namespace bongocat::animation diff --git a/include/platform/global_wayland_session.h b/include/platform/global_wayland_session.h index 897569f8..237c4884 100644 --- a/include/platform/global_wayland_session.h +++ b/include/platform/global_wayland_session.h @@ -1,168 +1,168 @@ #ifndef BONGOCAT_GLOBAL_WAYLAND_CONTEXT_CONTEXT_H #define BONGOCAT_GLOBAL_WAYLAND_CONTEXT_CONTEXT_H -#include "platform/wayland-protocols.hpp" - -#include "wayland_context.h" #include "graphics/animation_context.h" #include "graphics/global_animation_session.h" +#include "platform/wayland-protocols.hpp" +#include "wayland_context.h" + #include namespace bongocat::platform::wayland { - // max. windows to track for fullscreen detection - inline static constexpr size_t MAX_TOP_LEVELS = 128; - inline static constexpr size_t MAX_OUTPUTS = 8; // Maximum monitor outputs to store - inline static constexpr size_t OUTPUT_NAME_SIZE = 128; - - // ============================================================================= - // FULLSCREEN DETECTION MODULE - // ============================================================================= - - struct fullscreen_detector_t { - struct zwlr_foreign_toplevel_manager_v1 *manager{nullptr}; - bool has_fullscreen_toplevel{false}; - timeval last_check{}; - }; - struct tracked_toplevel_t { - struct zwlr_foreign_toplevel_handle_v1 *handle{nullptr}; - wl_output *output{nullptr}; - bool is_fullscreen{false}; - }; - - // ============================================================================= - // SCREEN DIMENSION MANAGEMENT - // ============================================================================= - - enum class screen_info_received_flags_t : uint32_t { - None = (1u << 0), - Mode = (1u << 1), - Geometry = (1u << 2), - }; - struct screen_info_t { - struct wl_output *wl_output{nullptr}; // ref of output - int screen_width{0}; - int screen_height{0}; - int transform{0}; - int raw_width{0}; - int raw_height{0}; - screen_info_received_flags_t received{screen_info_received_flags_t::None}; - }; - - struct wayland_session_t; - - enum class output_ref_received_flags_t : uint32_t { - None = (1u << 0), - Name = (1u << 1), - LogicalPosition = (1u << 2), - LogicalSize = (1u << 3), - }; - // Output monitor reference structure - struct output_ref_t { - struct wl_output *wl_output{nullptr}; - zxdg_output_v1 *xdg_output{nullptr}; - uint32_t name{0}; // Registry name - char name_str[OUTPUT_NAME_SIZE]{}; // From xdg-output - int32_t x{0}; - int32_t y{0}; - int32_t width{0}; - int32_t height{0}; - output_ref_received_flags_t received{output_ref_received_flags_t::None}; - // monitor ID in Hyprland - int64_t hypr_id{-1}; - // back reference - wayland_session_t *wayland{nullptr}; - }; - - void cleanup_wayland(wayland_session_t& ctx); - - struct wayland_session_t { - wayland_context_t wayland_context; - animation::animation_session_t *animation_trigger_context{nullptr}; - - tracked_toplevel_t tracked_toplevels[MAX_TOP_LEVELS]; - size_t num_toplevels{0}; - - output_ref_t outputs[MAX_OUTPUTS]; - size_t output_count{0}; - zxdg_output_manager_v1 *xdg_output_manager{nullptr}; - - fullscreen_detector_t fs_detector; - - screen_info_t screen_infos[MAX_OUTPUTS]; - atomic_bool ready{false}; - - // Output reconnection handling - atomic_bool output_lost{false}; // Set when our output disconnects - - - wayland_session_t() { - for (size_t i = 0; i < MAX_TOP_LEVELS; i++) { - tracked_toplevels[i] = {}; - } - for (size_t i = 0; i < MAX_OUTPUTS; i++) { - outputs[i] = {}; - } - } - ~wayland_session_t() { - cleanup_wayland(*this); - } - - wayland_session_t(const wayland_session_t&) = delete; - wayland_session_t& operator=(const wayland_session_t&) = delete; - wayland_session_t(wayland_session_t&& other) noexcept = delete; - wayland_session_t& operator=(wayland_session_t&& other) noexcept = delete; - }; - - inline void cleanup_wayland(wayland_session_t& ctx) { - atomic_store(&ctx.ready, false); - - // First destroy xdg_output objects - for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].xdg_output) { - zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); - ctx.outputs[i].xdg_output = nullptr; - } - } - - // Then destroy the manager - if (ctx.xdg_output_manager) { - zxdg_output_manager_v1_destroy(ctx.xdg_output_manager); - ctx.xdg_output_manager = nullptr; - } - - // Finally destroy wl_output objects - for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].wl_output) { - wl_output_destroy(ctx.outputs[i].wl_output); - ctx.outputs[i].wl_output = nullptr; - } - ctx.outputs[i] = {}; - ctx.outputs[i].wl_output = nullptr; - ctx.outputs[i].wayland = nullptr; - } - ctx.output_count = 0; - - if (ctx.fs_detector.manager) { - zwlr_foreign_toplevel_manager_v1_destroy(ctx.fs_detector.manager); - ctx.fs_detector.manager = nullptr; - } - - for (size_t i = 0; i < ctx.num_toplevels; ++i) { - if (ctx.tracked_toplevels[i].handle) zwlr_foreign_toplevel_handle_v1_destroy(ctx.tracked_toplevels[i].handle); - ctx.tracked_toplevels[i] = {}; - } - ctx.num_toplevels = 0; - - ctx.fs_detector = {}; - for (size_t i = 0; i < MAX_OUTPUTS; ++i) { - ctx.screen_infos[i] = {}; - } - - // clean up wayland context - cleanup_wayland_context(ctx.wayland_context); - - ctx.animation_trigger_context = nullptr; +// max. windows to track for fullscreen detection +inline static constexpr size_t MAX_TOP_LEVELS = 128; +inline static constexpr size_t MAX_OUTPUTS = 8; // Maximum monitor outputs to store +inline static constexpr size_t OUTPUT_NAME_SIZE = 128; + +// ============================================================================= +// FULLSCREEN DETECTION MODULE +// ============================================================================= + +struct fullscreen_detector_t { + struct zwlr_foreign_toplevel_manager_v1 *manager{nullptr}; + bool has_fullscreen_toplevel{false}; + timeval last_check{}; +}; +struct tracked_toplevel_t { + struct zwlr_foreign_toplevel_handle_v1 *handle{nullptr}; + wl_output *output{nullptr}; + bool is_fullscreen{false}; +}; + +// ============================================================================= +// SCREEN DIMENSION MANAGEMENT +// ============================================================================= + +enum class screen_info_received_flags_t : uint32_t { + None = (1u << 0), + Mode = (1u << 1), + Geometry = (1u << 2), +}; +struct screen_info_t { + struct wl_output *wl_output{nullptr}; // ref of output + int screen_width{0}; + int screen_height{0}; + int transform{0}; + int raw_width{0}; + int raw_height{0}; + screen_info_received_flags_t received{screen_info_received_flags_t::None}; +}; + +struct wayland_session_t; + +enum class output_ref_received_flags_t : uint32_t { + None = (1u << 0), + Name = (1u << 1), + LogicalPosition = (1u << 2), + LogicalSize = (1u << 3), +}; +// Output monitor reference structure +struct output_ref_t { + struct wl_output *wl_output{nullptr}; + zxdg_output_v1 *xdg_output{nullptr}; + uint32_t name{0}; // Registry name + char name_str[OUTPUT_NAME_SIZE]{}; // From xdg-output + int32_t x{0}; + int32_t y{0}; + int32_t width{0}; + int32_t height{0}; + output_ref_received_flags_t received{output_ref_received_flags_t::None}; + // monitor ID in Hyprland + int64_t hypr_id{-1}; + // back reference + wayland_session_t *wayland{nullptr}; +}; + +void cleanup_wayland(wayland_session_t& ctx); + +struct wayland_session_t { + wayland_context_t wayland_context; + animation::animation_session_t *animation_trigger_context{nullptr}; + + tracked_toplevel_t tracked_toplevels[MAX_TOP_LEVELS]; + size_t num_toplevels{0}; + + output_ref_t outputs[MAX_OUTPUTS]; + size_t output_count{0}; + zxdg_output_manager_v1 *xdg_output_manager{nullptr}; + + fullscreen_detector_t fs_detector; + + screen_info_t screen_infos[MAX_OUTPUTS]; + atomic_bool ready{false}; + + // Output reconnection handling + atomic_bool output_lost{false}; // Set when our output disconnects + + wayland_session_t() { + for (size_t i = 0; i < MAX_TOP_LEVELS; i++) { + tracked_toplevels[i] = {}; + } + for (size_t i = 0; i < MAX_OUTPUTS; i++) { + outputs[i] = {}; + } + } + ~wayland_session_t() { + cleanup_wayland(*this); + } + + wayland_session_t(const wayland_session_t&) = delete; + wayland_session_t& operator=(const wayland_session_t&) = delete; + wayland_session_t(wayland_session_t&& other) noexcept = delete; + wayland_session_t& operator=(wayland_session_t&& other) noexcept = delete; +}; + +inline void cleanup_wayland(wayland_session_t& ctx) { + atomic_store(&ctx.ready, false); + + // First destroy xdg_output objects + for (size_t i = 0; i < ctx.output_count; ++i) { + if (ctx.outputs[i].xdg_output) { + zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); + ctx.outputs[i].xdg_output = nullptr; + } + } + + // Then destroy the manager + if (ctx.xdg_output_manager) { + zxdg_output_manager_v1_destroy(ctx.xdg_output_manager); + ctx.xdg_output_manager = nullptr; + } + + // Finally destroy wl_output objects + for (size_t i = 0; i < ctx.output_count; ++i) { + if (ctx.outputs[i].wl_output) { + wl_output_destroy(ctx.outputs[i].wl_output); + ctx.outputs[i].wl_output = nullptr; } + ctx.outputs[i] = {}; + ctx.outputs[i].wl_output = nullptr; + ctx.outputs[i].wayland = nullptr; + } + ctx.output_count = 0; + + if (ctx.fs_detector.manager) { + zwlr_foreign_toplevel_manager_v1_destroy(ctx.fs_detector.manager); + ctx.fs_detector.manager = nullptr; + } + + for (size_t i = 0; i < ctx.num_toplevels; ++i) { + if (ctx.tracked_toplevels[i].handle) + zwlr_foreign_toplevel_handle_v1_destroy(ctx.tracked_toplevels[i].handle); + ctx.tracked_toplevels[i] = {}; + } + ctx.num_toplevels = 0; + + ctx.fs_detector = {}; + for (size_t i = 0; i < MAX_OUTPUTS; ++i) { + ctx.screen_infos[i] = {}; + } + + // clean up wayland context + cleanup_wayland_context(ctx.wayland_context); + + ctx.animation_trigger_context = nullptr; } +} // namespace bongocat::platform::wayland #endif diff --git a/include/platform/input.h b/include/platform/input.h index 78ce22d6..52bcaaf9 100644 --- a/include/platform/input.h +++ b/include/platform/input.h @@ -1,9 +1,9 @@ #ifndef BONGOCAT_INPUT_H #define BONGOCAT_INPUT_H -#include "input_context.h" #include "config/config.h" #include "graphics/global_animation_session.h" +#include "input_context.h" #include "utils/error.h" namespace bongocat::platform::input { @@ -12,15 +12,19 @@ namespace bongocat::platform::input { // INPUT MONITORING FUNCTIONS // ============================================================================= - BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); +BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); - // Start input monitoring - must be checked - BONGOCAT_NODISCARD bongocat_error_t start(input_context_t& input, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); +// Start input monitoring - must be checked +BONGOCAT_NODISCARD bongocat_error_t start(input_context_t& input, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation); - // Restart input monitoring with new devices - must be checked - BONGOCAT_NODISCARD bongocat_error_t restart(input_context_t& input, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); - void trigger_update_config(input_context_t& ctx, const config::config_t& config, uint64_t config_generation); - void update_config(input_context_t& ctx, const config::config_t& config, uint64_t new_gen); -} +// Restart input monitoring with new devices - must be checked +BONGOCAT_NODISCARD bongocat_error_t restart(input_context_t& input, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation); +void trigger_update_config(input_context_t& ctx, const config::config_t& config, uint64_t config_generation); +void update_config(input_context_t& ctx, const config::config_t& config, uint64_t new_gen); +} // namespace bongocat::platform::input -#endif // BONGOCAT_INPUT_H \ No newline at end of file +#endif // BONGOCAT_INPUT_H \ No newline at end of file diff --git a/include/platform/input_context.h b/include/platform/input_context.h index d542ef14..d77fee07 100644 --- a/include/platform/input_context.h +++ b/include/platform/input_context.h @@ -5,154 +5,159 @@ #include "input_shared_memory.h" #include "utils/system_memory.h" #include "utils/time.h" + +#include #include #include -#include namespace bongocat::platform::input { - enum class input_unique_file_type_t : uint8_t { - NONE, - File, - Symlink, - }; - struct input_unique_file_t; - void cleanup(input_unique_file_t& file); - struct input_unique_file_t { - const char* _device_path{nullptr}; // original string from config (ref to input_context_t._device_paths[i]) - char* canonical_path{nullptr}; // resolved real path (malloc'd) - FileDescriptor fd; - input_unique_file_type_t type{input_unique_file_type_t::NONE}; - - input_unique_file_t() = default; - ~input_unique_file_t() { - cleanup(*this); - } - - input_unique_file_t(const input_unique_file_t& other) = delete; - input_unique_file_t& operator=(const input_unique_file_t& other) = delete; - - input_unique_file_t(input_unique_file_t&& other) noexcept - : _device_path(other._device_path), - canonical_path(other.canonical_path), - fd(bongocat::move(other.fd)), - type(other.type) - { - other._device_path = nullptr; - other.canonical_path = nullptr; - other.type = input_unique_file_type_t::NONE; - } - input_unique_file_t& operator=(input_unique_file_t&& other) noexcept { - if (this != &other) { - cleanup(*this); - - _device_path = other._device_path; - canonical_path = other.canonical_path; - fd = bongocat::move(other.fd); - type = other.type; - - other._device_path = nullptr; - other.canonical_path = nullptr; - other.type = input_unique_file_type_t::NONE; - } - return *this; - } - }; - inline void cleanup(input_unique_file_t& file) { - close_fd(file.fd); - file._device_path = nullptr; - if (file.canonical_path) ::free(file.canonical_path); - file.canonical_path = nullptr; - file.type = input_unique_file_type_t::NONE; +enum class input_unique_file_type_t : uint8_t { + NONE, + File, + Symlink, +}; +struct input_unique_file_t; +void cleanup(input_unique_file_t& file); +struct input_unique_file_t { + const char *_device_path{nullptr}; // original string from config (ref to input_context_t._device_paths[i]) + char *canonical_path{nullptr}; // resolved real path (malloc'd) + FileDescriptor fd; + input_unique_file_type_t type{input_unique_file_type_t::NONE}; + + input_unique_file_t() = default; + ~input_unique_file_t() { + cleanup(*this); + } + + input_unique_file_t(const input_unique_file_t& other) = delete; + input_unique_file_t& operator=(const input_unique_file_t& other) = delete; + + input_unique_file_t(input_unique_file_t&& other) noexcept + : _device_path(other._device_path) + , canonical_path(other.canonical_path) + , fd(bongocat::move(other.fd)) + , type(other.type) { + other._device_path = nullptr; + other.canonical_path = nullptr; + other.type = input_unique_file_type_t::NONE; + } + input_unique_file_t& operator=(input_unique_file_t&& other) noexcept { + if (this != &other) { + cleanup(*this); + + _device_path = other._device_path; + canonical_path = other.canonical_path; + fd = bongocat::move(other.fd); + type = other.type; + + other._device_path = nullptr; + other.canonical_path = nullptr; + other.type = input_unique_file_type_t::NONE; } + return *this; + } +}; +inline void cleanup(input_unique_file_t& file) { + close_fd(file.fd); + file._device_path = nullptr; + if (file.canonical_path) + ::free(file.canonical_path); + file.canonical_path = nullptr; + file.type = input_unique_file_type_t::NONE; +} - struct input_context_t; - void stop(input_context_t& ctx); - // Cleanup input monitoring resources - void cleanup(input_context_t& ctx); +struct input_context_t; +void stop(input_context_t& ctx); +// Cleanup input monitoring resources +void cleanup(input_context_t& ctx); // ============================================================================= // INPUT STATE // ============================================================================= - struct input_context_t { - // local copy from other thread, update after reload (shared memory) - MMapMemory _local_copy_config; - MMapMemory shm; - - atomic_bool _capture_input_running{false}; - pthread_t _input_thread{0}; - atomic_int _input_kpm_counter{0}; - timestamp_ms_t _latest_kpm_update_ms{0}; - // lock for shm - Mutex input_lock; - - // thread context - AllocatedArray _device_paths; // local copy of devices (from config) - AllocatedArray _unique_paths_indices; - size_t _unique_paths_indices_capacity{0}; // keep real _unique_paths_indices count here, shrink _unique_paths_indices.count to used unique_paths_indices - AllocatedArray _unique_devices; - /// udev monitoring - udev *_udev{nullptr}; - udev_monitor *_udev_mon{nullptr}; - int _udev_fd{-1}; - - // config reload threading - FileDescriptor update_config_efd; // get new_gen from here - atomic_uint64_t config_seen_generation{0}; - platform::CondVariable config_updated; - - // globals (references) - const config::config_t *_config{nullptr}; - platform::CondVariable *_configs_reloaded_cond{nullptr}; - atomic_uint64_t* _config_generation{nullptr}; - atomic_bool ready; - platform::CondVariable init_cond; - - input_context_t() = default; - ~input_context_t() { - cleanup(*this); - } - - input_context_t(const input_context_t&) = delete; - input_context_t& operator=(const input_context_t&) = delete; - input_context_t(input_context_t&& other) noexcept = delete; - input_context_t& operator=(input_context_t&& other) noexcept = delete; - }; - inline void cleanup(input_context_t& ctx) { - if (atomic_load(&ctx._capture_input_running)) { - stop(ctx); - // input_lock should be unlocked - } - atomic_store(&ctx._capture_input_running, false); - ctx._input_thread = 0; - - release_allocated_array(ctx._unique_devices); - ctx._unique_paths_indices_capacity = 0; - release_allocated_array(ctx._unique_paths_indices); - for (size_t i = 0; i < ctx._device_paths.count; i++) { - if (ctx._device_paths[i]) ::free(ctx._device_paths[i]); - ctx._device_paths[i] = nullptr; - } - release_allocated_array(ctx._device_paths); - - if (ctx._udev_mon) udev_monitor_unref(ctx._udev_mon); - if (ctx._udev) udev_unref(ctx._udev); - ctx._udev_mon = nullptr; - ctx._udev = nullptr; - ctx._udev_fd = -1; - - close_fd(ctx.update_config_efd); - atomic_store(&ctx.config_seen_generation, 0); - - release_allocated_mmap_memory(ctx._local_copy_config); - release_allocated_mmap_memory(ctx.shm); - - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; - atomic_store(&ctx.ready, false); - ctx.init_cond.notify_all(); - } +struct input_context_t { + // local copy from other thread, update after reload (shared memory) + MMapMemory _local_copy_config; + MMapMemory shm; + + atomic_bool _capture_input_running{false}; + pthread_t _input_thread{0}; + atomic_int _input_kpm_counter{0}; + timestamp_ms_t _latest_kpm_update_ms{0}; + // lock for shm + Mutex input_lock; + + // thread context + AllocatedArray _device_paths; // local copy of devices (from config) + AllocatedArray _unique_paths_indices; + size_t _unique_paths_indices_capacity{0}; // keep real _unique_paths_indices count here, shrink + // _unique_paths_indices.count to used unique_paths_indices + AllocatedArray _unique_devices; + /// udev monitoring + udev *_udev{nullptr}; + udev_monitor *_udev_mon{nullptr}; + int _udev_fd{-1}; + + // config reload threading + FileDescriptor update_config_efd; // get new_gen from here + atomic_uint64_t config_seen_generation{0}; + platform::CondVariable config_updated; + + // globals (references) + const config::config_t *_config{nullptr}; + platform::CondVariable *_configs_reloaded_cond{nullptr}; + atomic_uint64_t *_config_generation{nullptr}; + atomic_bool ready; + platform::CondVariable init_cond; + + input_context_t() = default; + ~input_context_t() { + cleanup(*this); + } + + input_context_t(const input_context_t&) = delete; + input_context_t& operator=(const input_context_t&) = delete; + input_context_t(input_context_t&& other) noexcept = delete; + input_context_t& operator=(input_context_t&& other) noexcept = delete; +}; +inline void cleanup(input_context_t& ctx) { + if (atomic_load(&ctx._capture_input_running)) { + stop(ctx); + // input_lock should be unlocked + } + atomic_store(&ctx._capture_input_running, false); + ctx._input_thread = 0; + + release_allocated_array(ctx._unique_devices); + ctx._unique_paths_indices_capacity = 0; + release_allocated_array(ctx._unique_paths_indices); + for (size_t i = 0; i < ctx._device_paths.count; i++) { + if (ctx._device_paths[i]) + ::free(ctx._device_paths[i]); + ctx._device_paths[i] = nullptr; + } + release_allocated_array(ctx._device_paths); + + if (ctx._udev_mon) + udev_monitor_unref(ctx._udev_mon); + if (ctx._udev) + udev_unref(ctx._udev); + ctx._udev_mon = nullptr; + ctx._udev = nullptr; + ctx._udev_fd = -1; + + close_fd(ctx.update_config_efd); + atomic_store(&ctx.config_seen_generation, 0); + + release_allocated_mmap_memory(ctx._local_copy_config); + release_allocated_mmap_memory(ctx.shm); + + ctx._config = nullptr; + ctx._configs_reloaded_cond = nullptr; + ctx._config_generation = nullptr; + atomic_store(&ctx.ready, false); + ctx.init_cond.notify_all(); } +} // namespace bongocat::platform::input -#endif // BONGOCAT_INPUT_CONTEXT_H \ No newline at end of file +#endif // BONGOCAT_INPUT_CONTEXT_H \ No newline at end of file diff --git a/include/platform/input_shared_memory.h b/include/platform/input_shared_memory.h index 59a93905..d2976d7f 100644 --- a/include/platform/input_shared_memory.h +++ b/include/platform/input_shared_memory.h @@ -2,6 +2,7 @@ #define BONGOCAT_INPUT_SHARED_MEMORY_H #include "utils/time.h" + #include namespace bongocat::platform::input { @@ -10,70 +11,69 @@ namespace bongocat::platform::input { // INPUT STATE (shared memory) // ============================================================================= - enum class input_hand_mapping_t : int32_t { - None, - Left, - Right - }; - - struct input_shared_memory_t { - /// @DEPRECATED: not really needed anymore, use events - int32_t any_key_pressed{0}; +enum class input_hand_mapping_t : int32_t { + None, + Left, + Right +}; - int32_t kpm{0}; // keystrokes per minute - atomic_int input_counter{0}; - timestamp_ms_t last_key_pressed_timestamp{0}; - input_hand_mapping_t hand_mapping{input_hand_mapping_t::None}; +struct input_shared_memory_t { + /// @DEPRECATED: not really needed anymore, use events + int32_t any_key_pressed{0}; + int32_t kpm{0}; // keystrokes per minute + atomic_int input_counter{0}; + timestamp_ms_t last_key_pressed_timestamp{0}; + input_hand_mapping_t hand_mapping{input_hand_mapping_t::None}; - input_shared_memory_t() = default; - ~input_shared_memory_t() = default; + input_shared_memory_t() = default; + ~input_shared_memory_t() = default; - input_shared_memory_t(const input_shared_memory_t& other) - : any_key_pressed(other.any_key_pressed), - kpm(other.kpm), - input_counter(atomic_load(&other.input_counter)), - last_key_pressed_timestamp(other.last_key_pressed_timestamp), - hand_mapping(other.hand_mapping) {} - input_shared_memory_t& operator=(const input_shared_memory_t& other) { - if (this != &other) { - any_key_pressed = other.any_key_pressed; - kpm = other.kpm; - atomic_store(&input_counter, atomic_load(&other.input_counter)); - last_key_pressed_timestamp = other.last_key_pressed_timestamp; - hand_mapping = other.hand_mapping; - } - return *this; - } + input_shared_memory_t(const input_shared_memory_t& other) + : any_key_pressed(other.any_key_pressed) + , kpm(other.kpm) + , input_counter(atomic_load(&other.input_counter)) + , last_key_pressed_timestamp(other.last_key_pressed_timestamp) + , hand_mapping(other.hand_mapping) {} + input_shared_memory_t& operator=(const input_shared_memory_t& other) { + if (this != &other) { + any_key_pressed = other.any_key_pressed; + kpm = other.kpm; + atomic_store(&input_counter, atomic_load(&other.input_counter)); + last_key_pressed_timestamp = other.last_key_pressed_timestamp; + hand_mapping = other.hand_mapping; + } + return *this; + } - input_shared_memory_t(input_shared_memory_t&& other) noexcept - : any_key_pressed(other.any_key_pressed), - kpm(other.kpm), - last_key_pressed_timestamp(other.last_key_pressed_timestamp), - hand_mapping(other.hand_mapping) { - atomic_store(&input_counter, atomic_load(&other.input_counter)); + input_shared_memory_t(input_shared_memory_t&& other) noexcept + : any_key_pressed(other.any_key_pressed) + , kpm(other.kpm) + , last_key_pressed_timestamp(other.last_key_pressed_timestamp) + , hand_mapping(other.hand_mapping) { + atomic_store(&input_counter, atomic_load(&other.input_counter)); - other.any_key_pressed = 0; - other.kpm = 0; - atomic_store(&other.input_counter, 0); - other.hand_mapping = input_hand_mapping_t::None; - } - input_shared_memory_t& operator=(input_shared_memory_t&& other) noexcept { - if (this != &other) { - any_key_pressed = other.any_key_pressed; - kpm = other.kpm; - atomic_store(&input_counter, atomic_load(&other.input_counter)); - last_key_pressed_timestamp = other.last_key_pressed_timestamp; - hand_mapping = other.hand_mapping; + other.any_key_pressed = 0; + other.kpm = 0; + atomic_store(&other.input_counter, 0); + other.hand_mapping = input_hand_mapping_t::None; + } + input_shared_memory_t& operator=(input_shared_memory_t&& other) noexcept { + if (this != &other) { + any_key_pressed = other.any_key_pressed; + kpm = other.kpm; + atomic_store(&input_counter, atomic_load(&other.input_counter)); + last_key_pressed_timestamp = other.last_key_pressed_timestamp; + hand_mapping = other.hand_mapping; - other.any_key_pressed = 0; - other.kpm = 0; - atomic_store(&other.input_counter, 0); - other.hand_mapping = input_hand_mapping_t::None; - } - return *this; - } - }; -} + other.any_key_pressed = 0; + other.kpm = 0; + atomic_store(&other.input_counter, 0); + other.hand_mapping = input_hand_mapping_t::None; + } + return *this; + } +}; +} // namespace bongocat::platform::input -#endif // BONGOCAT_INPUT_SHARED_MEMORY_H \ No newline at end of file +#endif // BONGOCAT_INPUT_SHARED_MEMORY_H \ No newline at end of file diff --git a/include/platform/update.h b/include/platform/update.h index 59d599eb..031863c4 100644 --- a/include/platform/update.h +++ b/include/platform/update.h @@ -1,18 +1,22 @@ #ifndef BONGOCAT_UPDATE_H #define BONGOCAT_UPDATE_H -#include "update_context.h" #include "config/config.h" #include "graphics/global_animation_session.h" +#include "update_context.h" #include "utils/error.h" namespace bongocat::platform::update { - BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); - BONGOCAT_NODISCARD bongocat_error_t start(update_context_t& input, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); - BONGOCAT_NODISCARD bongocat_error_t restart(update_context_t& input, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); - void trigger_update_config(update_context_t& ctx, const config::config_t& config, uint64_t config_generation); - void update_config(update_context_t& ctx, const config::config_t& config, uint64_t new_gen); - const cpu_snapshot_t& get_latest_snapshot(update_context_t& ctx); -} +BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); +BONGOCAT_NODISCARD bongocat_error_t start(update_context_t& input, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation); +BONGOCAT_NODISCARD bongocat_error_t restart(update_context_t& input, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation); +void trigger_update_config(update_context_t& ctx, const config::config_t& config, uint64_t config_generation); +void update_config(update_context_t& ctx, const config::config_t& config, uint64_t new_gen); +const cpu_snapshot_t& get_latest_snapshot(update_context_t& ctx); +} // namespace bongocat::platform::update -#endif // BONGOCAT_UPDATE_H \ No newline at end of file +#endif // BONGOCAT_UPDATE_H \ No newline at end of file diff --git a/include/platform/update_context.h b/include/platform/update_context.h index b4837487..aab3007f 100644 --- a/include/platform/update_context.h +++ b/include/platform/update_context.h @@ -5,74 +5,75 @@ #include "update_shared_memory.h" #include "utils/system_memory.h" #include "utils/time.h" + #include #include namespace bongocat::platform::update { - struct update_context_t; - void stop(update_context_t& ctx); - void cleanup(update_context_t& ctx); +struct update_context_t; +void stop(update_context_t& ctx); +void cleanup(update_context_t& ctx); - struct update_context_t { - // local copy from other thread, update after reload (shared memory) - MMapMemory _local_copy_config; - MMapMemory shm; +struct update_context_t { + // local copy from other thread, update after reload (shared memory) + MMapMemory _local_copy_config; + MMapMemory shm; - atomic_bool _running{false}; - pthread_t _update_thread{0}; - // lock for shm - Mutex update_lock; - platform::CondVariable update_cond; + atomic_bool _running{false}; + pthread_t _update_thread{0}; + // lock for shm + Mutex update_lock; + platform::CondVariable update_cond; - // thread context - FileDescriptor fd_stat; - FileDescriptor fd_present; + // thread context + FileDescriptor fd_stat; + FileDescriptor fd_present; - // config reload threading - FileDescriptor update_config_efd; // get new_gen from here - atomic_uint64_t config_seen_generation{0}; - platform::CondVariable config_updated; + // config reload threading + FileDescriptor update_config_efd; // get new_gen from here + atomic_uint64_t config_seen_generation{0}; + platform::CondVariable config_updated; - // globals (references) - const config::config_t *_config{nullptr}; - platform::CondVariable *_configs_reloaded_cond{nullptr}; - atomic_uint64_t* _config_generation{nullptr}; - atomic_bool ready; - platform::CondVariable init_cond; + // globals (references) + const config::config_t *_config{nullptr}; + platform::CondVariable *_configs_reloaded_cond{nullptr}; + atomic_uint64_t *_config_generation{nullptr}; + atomic_bool ready; + platform::CondVariable init_cond; - update_context_t() = default; - ~update_context_t() { - cleanup(*this); - } + update_context_t() = default; + ~update_context_t() { + cleanup(*this); + } - update_context_t(const update_context_t&) = delete; - update_context_t& operator=(const update_context_t&) = delete; - update_context_t(update_context_t&& other) noexcept = delete; - update_context_t& operator=(update_context_t&& other) noexcept = delete; - }; - inline void cleanup(update_context_t& ctx) { - if (atomic_load(&ctx._running)) { - stop(ctx); - // input_lock should be unlocked - } - atomic_store(&ctx._running, false); - ctx._update_thread = 0; + update_context_t(const update_context_t&) = delete; + update_context_t& operator=(const update_context_t&) = delete; + update_context_t(update_context_t&& other) noexcept = delete; + update_context_t& operator=(update_context_t&& other) noexcept = delete; +}; +inline void cleanup(update_context_t& ctx) { + if (atomic_load(&ctx._running)) { + stop(ctx); + // input_lock should be unlocked + } + atomic_store(&ctx._running, false); + ctx._update_thread = 0; - close_fd(ctx.fd_present); - close_fd(ctx.fd_stat); + close_fd(ctx.fd_present); + close_fd(ctx.fd_stat); - close_fd(ctx.update_config_efd); - atomic_store(&ctx.config_seen_generation, 0); + close_fd(ctx.update_config_efd); + atomic_store(&ctx.config_seen_generation, 0); - release_allocated_mmap_memory(ctx._local_copy_config); - release_allocated_mmap_memory(ctx.shm); + release_allocated_mmap_memory(ctx._local_copy_config); + release_allocated_mmap_memory(ctx.shm); - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; - atomic_store(&ctx.ready, false); - ctx.init_cond.notify_all(); - } + ctx._config = nullptr; + ctx._configs_reloaded_cond = nullptr; + ctx._config_generation = nullptr; + atomic_store(&ctx.ready, false); + ctx.init_cond.notify_all(); } +} // namespace bongocat::platform::update -#endif // BONGOCAT_INPUT_CONTEXT_H \ No newline at end of file +#endif // BONGOCAT_INPUT_CONTEXT_H \ No newline at end of file diff --git a/include/platform/update_shared_memory.h b/include/platform/update_shared_memory.h index d8bf2965..b67e7d21 100644 --- a/include/platform/update_shared_memory.h +++ b/include/platform/update_shared_memory.h @@ -5,38 +5,39 @@ #include "input_shared_memory.h" #include "utils/system_memory.h" #include "utils/time.h" + #include #include namespace bongocat::platform::update { - inline static constexpr size_t MaxCpus = 256; - inline static constexpr size_t CpuSnapshotRingBufferMaxHistory = 8; +inline static constexpr size_t MaxCpus = 256; +inline static constexpr size_t CpuSnapshotRingBufferMaxHistory = 8; - struct cpu_stat_t { - size_t idle_time{0}; - size_t total_time{0}; - }; - struct cpu_snapshot_t { - cpu_stat_t stats[MaxCpus]{}; - size_t count{0}; // how many CPUs in this snapshot - }; +struct cpu_stat_t { + size_t idle_time{0}; + size_t total_time{0}; +}; +struct cpu_snapshot_t { + cpu_stat_t stats[MaxCpus]{}; + size_t count{0}; // how many CPUs in this snapshot +}; - struct cpu_snapshot_ring_buffer_t { - // ring buffer for snapshots - cpu_snapshot_t history[CpuSnapshotRingBufferMaxHistory]{}; - size_t head{0}; // next write position - size_t stored{0}; // number of valid snapshots - }; +struct cpu_snapshot_ring_buffer_t { + // ring buffer for snapshots + cpu_snapshot_t history[CpuSnapshotRingBufferMaxHistory]{}; + size_t head{0}; // next write position + size_t stored{0}; // number of valid snapshots +}; - struct update_shared_memory_t { - cpu_snapshot_ring_buffer_t cpu_snapshots; - const cpu_snapshot_t* latest_snapshot{nullptr}; - double avg_cpu_usage{0}; - double max_cpu_usage{0}; - double last_avg_cpu_usage{0}; - double last_max_cpu_usage{0}; - bool cpu_active{false}; - }; -} +struct update_shared_memory_t { + cpu_snapshot_ring_buffer_t cpu_snapshots; + const cpu_snapshot_t *latest_snapshot{nullptr}; + double avg_cpu_usage{0}; + double max_cpu_usage{0}; + double last_avg_cpu_usage{0}; + double last_max_cpu_usage{0}; + bool cpu_active{false}; +}; +} // namespace bongocat::platform::update -#endif // BONGOCAT_INPUT_CONTEXT_H \ No newline at end of file +#endif // BONGOCAT_INPUT_CONTEXT_H \ No newline at end of file diff --git a/include/platform/wayland-protocols.hpp b/include/platform/wayland-protocols.hpp index 9477a11c..bbf50f62 100644 --- a/include/platform/wayland-protocols.hpp +++ b/include/platform/wayland-protocols.hpp @@ -1,27 +1,27 @@ #pragma once #ifdef __cplusplus -//#define namespace zwl_namespace -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-function" -#pragma GCC diagnostic ignored "-Wold-style-cast" -//#pragma GCC diagnostic ignored "-Wsign-conversion" -#endif +// #define namespace zwl_namespace +# if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdouble-promotion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wold-style-cast" +// #pragma GCC diagnostic ignored "-Wsign-conversion" +# endif extern "C" { -#include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" -#include "../protocols/xdg-output-unstable-v1-client-protocol.h" -#include "../protocols/xdg-shell-client-protocol.h" -#include "../protocols/zwlr-layer-shell-v1-client-protocol.h" +# include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" +# include "../protocols/xdg-output-unstable-v1-client-protocol.h" +# include "../protocols/xdg-shell-client-protocol.h" +# include "../protocols/zwlr-layer-shell-v1-client-protocol.h" } -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif -//#undef namespace +# if defined(__GNUC__) || defined(__clang__) +# pragma GCC diagnostic pop +# endif +// #undef namespace #else -#include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" -#include "../protocols/xdg-output-unstable-v1-client-protocol.h" -#include "../protocols/xdg-shell-client-protocol.h" -#include "../protocols/zwlr-layer-shell-v1-client-protocol.h" +# include "../protocols/wlr-foreign-toplevel-management-v1-client-protocol.h" +# include "../protocols/xdg-output-unstable-v1-client-protocol.h" +# include "../protocols/xdg-shell-client-protocol.h" +# include "../protocols/zwlr-layer-shell-v1-client-protocol.h" #endif \ No newline at end of file diff --git a/include/platform/wayland.h b/include/platform/wayland.h index 51f13c74..ded4f4bd 100644 --- a/include/platform/wayland.h +++ b/include/platform/wayland.h @@ -1,38 +1,43 @@ #ifndef BONGOCAT_WAYLAND_H #define BONGOCAT_WAYLAND_H +#include "config/config.h" #include "config/config_watcher.h" -#include "wayland_context.h" -#include "graphics/global_animation_session.h" #include "global_wayland_session.h" -#include "config/config.h" +#include "graphics/global_animation_session.h" #include "utils/error.h" +#include "wayland_context.h" + #include namespace bongocat::platform::wayland { - using config_reload_callback_t = void (*)(); +using config_reload_callback_t = void (*)(); // ============================================================================= // WAYLAND LIFECYCLE FUNCTIONS // ============================================================================= - // Initialize Wayland connection - must be checked - BONGOCAT_NODISCARD created_result_t> create(animation::animation_session_t& anim, const config::config_t& config); - BONGOCAT_NODISCARD bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim); +// Initialize Wayland connection - must be checked +BONGOCAT_NODISCARD created_result_t> create(animation::animation_session_t& anim, + const config::config_t& config); +BONGOCAT_NODISCARD bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim); - // Run Wayland event loop - must be checked - BONGOCAT_NODISCARD bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int signal_fd, input::input_context_t& input, const config::config_t& config, const config::config_watcher_t* config_watcher, config_reload_callback_t config_reload_callback); +// Run Wayland event loop - must be checked +BONGOCAT_NODISCARD bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int signal_fd, + input::input_context_t& input, const config::config_t& config, + const config::config_watcher_t *config_watcher, + config_reload_callback_t config_reload_callback); - // Update configuration - void update_config(wayland_session_t& ctx, const config::config_t& config, animation::animation_session_t& trigger_ctx); +// Update configuration +void update_config(wayland_session_t& ctx, const config::config_t& config, animation::animation_session_t& trigger_ctx); - // Get detected screen width - BONGOCAT_NODISCARD int get_screen_width(const wayland_session_t& ctx); +// Get detected screen width +BONGOCAT_NODISCARD int get_screen_width(const wayland_session_t& ctx); - // Get current layer name for logging - BONGOCAT_NODISCARD const char* get_current_layer_name(); +// Get current layer name for logging +BONGOCAT_NODISCARD const char *get_current_layer_name(); - bongocat_error_t request_render(animation::animation_session_t& trigger_ctx); -} +bongocat_error_t request_render(animation::animation_session_t& trigger_ctx); +} // namespace bongocat::platform::wayland -#endif // BONGOCAT_WAYLAND_H \ No newline at end of file +#endif // BONGOCAT_WAYLAND_H \ No newline at end of file diff --git a/include/platform/wayland_callbacks.h b/include/platform/wayland_callbacks.h index 6d266221..25cc6140 100644 --- a/include/platform/wayland_callbacks.h +++ b/include/platform/wayland_callbacks.h @@ -1,167 +1,145 @@ #ifndef BONGOCAT_WAYLAND_CALLBACKS_H #define BONGOCAT_WAYLAND_CALLBACKS_H +#include "global_wayland_session.h" #include "wayland-protocols.hpp" +#include #include #include -#include namespace bongocat::platform::wayland::details { - // ============================================================================= - // ZXDG LISTENER IMPLEMENTATION - // ============================================================================= - - extern void handle_xdg_output_name(void *data, zxdg_output_v1 *xdg_output, const char *name); - - extern void handle_xdg_output_logical_position(void *data, zxdg_output_v1 *xdg_output, int32_t x, int32_t y); - extern void handle_xdg_output_logical_size(void *data, zxdg_output_v1 *xdg_output, int32_t width, int32_t height); - extern void handle_xdg_output_done(void *data, zxdg_output_v1 *xdg_output); - extern void handle_xdg_output_description(void *data, zxdg_output_v1 *xdg_output, const char *description); - - /// @NOTE: xdg_output_listeners MUST pass data as output_ref_t, see zxdg_output_v1_add_listener - inline static constexpr zxdg_output_v1_listener xdg_output_listener = { - .logical_position = handle_xdg_output_logical_position, - .logical_size = handle_xdg_output_logical_size, - .done = handle_xdg_output_done, - .name = handle_xdg_output_name, - .description = handle_xdg_output_description - }; - - - // ============================================================================= - // FULLSCREEN DETECTION IMPLEMENTATION - // ============================================================================= - - // Foreign toplevel protocol event handlers - extern void fs_handle_toplevel_state(void *data, zwlr_foreign_toplevel_handle_v1 *handle, - wl_array *state); - - extern void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *handle); - - // Minimal event handlers for unused events - extern void fs_handle_title(void *data, zwlr_foreign_toplevel_handle_v1 *handle, const char *title); - extern void fs_handle_app_id(void *data, zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id); - extern void fs_handle_output_enter(void *data, zwlr_foreign_toplevel_handle_v1 *handle, wl_output *output); - extern void fs_handle_output_leave(void *data, zwlr_foreign_toplevel_handle_v1 *handle, wl_output *output); - extern void fs_handle_done(void *data, zwlr_foreign_toplevel_handle_v1 *handle); - extern void fs_handle_parent(void *data, zwlr_foreign_toplevel_handle_v1 *handle, zwlr_foreign_toplevel_handle_v1 *parent); - - /// @NOTE: fs_toplevel_listener MUST pass data as output_ref_t, see zwlr_foreign_toplevel_handle_v1_add_listener - inline static constexpr zwlr_foreign_toplevel_handle_v1_listener fs_toplevel_listener = { - .title = fs_handle_title, - .app_id = fs_handle_app_id, - .output_enter = fs_handle_output_enter, - .output_leave = fs_handle_output_leave, - .state = fs_handle_toplevel_state, - .done = fs_handle_done, - .closed = fs_handle_toplevel_closed, - .parent = fs_handle_parent, - }; - - extern void fs_update_state_fallback(wayland_session_t& ctx); - - extern void fs_handle_manager_toplevel(void *data, zwlr_foreign_toplevel_manager_v1 *manager, - zwlr_foreign_toplevel_handle_v1 *toplevel); - extern void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *manager); - - /// @NOTE: fs_manager_listeners MUST pass data as wayland_listeners_context_t, see zwlr_foreign_toplevel_manager_v1_add_listener - inline static constexpr zwlr_foreign_toplevel_manager_v1_listener fs_manager_listener = { - .toplevel = fs_handle_manager_toplevel, - .finished = fs_handle_manager_finished, - }; - - // ============================================================================= - // WAYLAND EVENT HANDLERS - // ============================================================================= - - extern void layer_surface_configure(void *data, - zwlr_layer_surface_v1 *ls, - uint32_t serial, uint32_t w, uint32_t h); - extern void layer_surface_closed(void *data, zwlr_layer_surface_v1 *ls); - - /// @NOTE: layer_listeners MUST pass data as wayland_listeners_context_t, see zwlr_layer_surface_v1_add_listener - inline static constexpr zwlr_layer_surface_v1_listener layer_listener = { - .configure = layer_surface_configure, - .closed = layer_surface_closed, - }; - - - extern void xdg_wm_base_ping(void *data, xdg_wm_base *wm_base, uint32_t serial); - - /// @NOTE: xdg_wm_base_listeners MUST pass data as wayland_listeners_context_t, see xdg_wm_base_add_listener - inline static constexpr xdg_wm_base_listener xdg_wm_base_listener = { - .ping = xdg_wm_base_ping, - }; - - extern void output_geometry(void *data, - wl_output *wl_output, - int32_t x, - int32_t y, - int32_t physical_width, - int32_t physical_height, - int32_t subpixel, - const char *make, - const char *model, - int32_t transform); - extern void output_mode(void *data , - wl_output *wl_output, - uint32_t flags, int32_t width, int32_t height, - int32_t refresh); - extern void output_done(void *data, wl_output *wl_output); - extern void output_scale(void *data, - wl_output *wl_output, - int32_t factor); - extern void output_name(void *data, wl_output *wl_output, const char *name); - extern void output_description(void *data, wl_output *wl_output, const char *name); - - /// @NOTE: output_listeners MUST pass data as wayland_listeners_context_t, see wl_output_add_listener - inline static constexpr wl_output_listener output_listener = { - .geometry = output_geometry, - .mode = output_mode, - .done = output_done, - .scale = output_scale, - .name = output_name, - .description = output_description, - }; - - // ============================================================================= - // WAYLAND PROTOCOL REGISTRY - // ============================================================================= - - extern void registry_global(void *data , wl_registry *reg, - uint32_t name, const char *iface, - uint32_t ver); - extern void registry_remove(void *data, - wl_registry *registry, - uint32_t name); - - /// @NOTE: reg_listeners MUST pass data as wayland_listeners_context_t, see zxdg_output_v1_add_listener - inline static constexpr wl_registry_listener reg_listener = { - .global = registry_global, - .global_remove = registry_remove - }; - - // ============================================================================= - // FRAME DRAWING HANDLER - // ============================================================================= - - extern void buffer_release(void *data, wl_buffer *buffer); - - /// @NOTE: buffer_listeners MUST pass data as wayland_shm_buffer_t, see wl_buffer_add_listener - inline static constexpr wl_buffer_listener buffer_listener = { - .release = buffer_release, - }; - - extern void frame_done(void *data, wl_callback *cb, uint32_t time); - - /// @NOTE: frame_listeners MUST pass data as wayland_listeners_context_t, see wl_callback_add_listener - inline static constexpr wl_callback_listener frame_listener = { - .done = frame_done - }; - - void wayland_handle_output_reconnect(struct wl_output *new_output, uint32_t registry_name, const char *output_name); -} +// ============================================================================= +// ZXDG LISTENER IMPLEMENTATION +// ============================================================================= + +extern void handle_xdg_output_name(void *data, zxdg_output_v1 *xdg_output, const char *name); + +extern void handle_xdg_output_logical_position(void *data, zxdg_output_v1 *xdg_output, int32_t x, int32_t y); +extern void handle_xdg_output_logical_size(void *data, zxdg_output_v1 *xdg_output, int32_t width, int32_t height); +extern void handle_xdg_output_done(void *data, zxdg_output_v1 *xdg_output); +extern void handle_xdg_output_description(void *data, zxdg_output_v1 *xdg_output, const char *description); + +/// @NOTE: xdg_output_listeners MUST pass data as output_ref_t, see zxdg_output_v1_add_listener +inline static constexpr zxdg_output_v1_listener xdg_output_listener = {.logical_position = + handle_xdg_output_logical_position, + .logical_size = handle_xdg_output_logical_size, + .done = handle_xdg_output_done, + .name = handle_xdg_output_name, + .description = handle_xdg_output_description}; + +// ============================================================================= +// FULLSCREEN DETECTION IMPLEMENTATION +// ============================================================================= + +// Foreign toplevel protocol event handlers +extern void fs_handle_toplevel_state(void *data, zwlr_foreign_toplevel_handle_v1 *handle, wl_array *state); + +extern void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *handle); + +// Minimal event handlers for unused events +extern void fs_handle_title(void *data, zwlr_foreign_toplevel_handle_v1 *handle, const char *title); +extern void fs_handle_app_id(void *data, zwlr_foreign_toplevel_handle_v1 *handle, const char *app_id); +extern void fs_handle_output_enter(void *data, zwlr_foreign_toplevel_handle_v1 *handle, wl_output *output); +extern void fs_handle_output_leave(void *data, zwlr_foreign_toplevel_handle_v1 *handle, wl_output *output); +extern void fs_handle_done(void *data, zwlr_foreign_toplevel_handle_v1 *handle); +extern void fs_handle_parent(void *data, zwlr_foreign_toplevel_handle_v1 *handle, + zwlr_foreign_toplevel_handle_v1 *parent); + +/// @NOTE: fs_toplevel_listener MUST pass data as output_ref_t, see zwlr_foreign_toplevel_handle_v1_add_listener +inline static constexpr zwlr_foreign_toplevel_handle_v1_listener fs_toplevel_listener = { + .title = fs_handle_title, + .app_id = fs_handle_app_id, + .output_enter = fs_handle_output_enter, + .output_leave = fs_handle_output_leave, + .state = fs_handle_toplevel_state, + .done = fs_handle_done, + .closed = fs_handle_toplevel_closed, + .parent = fs_handle_parent, +}; + +extern void fs_update_state_fallback(wayland_session_t& ctx); + +extern void fs_handle_manager_toplevel(void *data, zwlr_foreign_toplevel_manager_v1 *manager, + zwlr_foreign_toplevel_handle_v1 *toplevel); +extern void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *manager); + +/// @NOTE: fs_manager_listeners MUST pass data as wayland_listeners_context_t, see +/// zwlr_foreign_toplevel_manager_v1_add_listener +inline static constexpr zwlr_foreign_toplevel_manager_v1_listener fs_manager_listener = { + .toplevel = fs_handle_manager_toplevel, + .finished = fs_handle_manager_finished, +}; + +// ============================================================================= +// WAYLAND EVENT HANDLERS +// ============================================================================= + +extern void layer_surface_configure(void *data, zwlr_layer_surface_v1 *ls, uint32_t serial, uint32_t w, uint32_t h); +extern void layer_surface_closed(void *data, zwlr_layer_surface_v1 *ls); + +/// @NOTE: layer_listeners MUST pass data as wayland_listeners_context_t, see zwlr_layer_surface_v1_add_listener +inline static constexpr zwlr_layer_surface_v1_listener layer_listener = { + .configure = layer_surface_configure, + .closed = layer_surface_closed, +}; + +extern void xdg_wm_base_ping(void *data, xdg_wm_base *wm_base, uint32_t serial); + +/// @NOTE: xdg_wm_base_listeners MUST pass data as wayland_listeners_context_t, see xdg_wm_base_add_listener +inline static constexpr xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + +extern void output_geometry(void *data, wl_output *wl_output, int32_t x, int32_t y, int32_t physical_width, + int32_t physical_height, int32_t subpixel, const char *make, const char *model, + int32_t transform); +extern void output_mode(void *data, wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, + int32_t refresh); +extern void output_done(void *data, wl_output *wl_output); +extern void output_scale(void *data, wl_output *wl_output, int32_t factor); +extern void output_name(void *data, wl_output *wl_output, const char *name); +extern void output_description(void *data, wl_output *wl_output, const char *name); + +/// @NOTE: output_listeners MUST pass data as wayland_listeners_context_t, see wl_output_add_listener +inline static constexpr wl_output_listener output_listener = { + .geometry = output_geometry, + .mode = output_mode, + .done = output_done, + .scale = output_scale, + .name = output_name, + .description = output_description, +}; + +// ============================================================================= +// WAYLAND PROTOCOL REGISTRY +// ============================================================================= + +extern void registry_global(void *data, wl_registry *reg, uint32_t name, const char *iface, uint32_t ver); +extern void registry_remove(void *data, wl_registry *registry, uint32_t name); + +/// @NOTE: reg_listeners MUST pass data as wayland_listeners_context_t, see zxdg_output_v1_add_listener +inline static constexpr wl_registry_listener reg_listener = {.global = registry_global, + .global_remove = registry_remove}; + +// ============================================================================= +// FRAME DRAWING HANDLER +// ============================================================================= + +extern void buffer_release(void *data, wl_buffer *buffer); + +/// @NOTE: buffer_listeners MUST pass data as wayland_shm_buffer_t, see wl_buffer_add_listener +inline static constexpr wl_buffer_listener buffer_listener = { + .release = buffer_release, +}; + +extern void frame_done(void *data, wl_callback *cb, uint32_t time); + +/// @NOTE: frame_listeners MUST pass data as wayland_listeners_context_t, see wl_callback_add_listener +inline static constexpr wl_callback_listener frame_listener = {.done = frame_done}; + +void wayland_handle_output_reconnect(struct wl_output *new_output, uint32_t registry_name, const char *output_name); +} // namespace bongocat::platform::wayland::details #endif \ No newline at end of file diff --git a/include/platform/wayland_context.h b/include/platform/wayland_context.h index f3e6cfe2..8faa3d97 100644 --- a/include/platform/wayland_context.h +++ b/include/platform/wayland_context.h @@ -3,194 +3,193 @@ struct zwlr_layer_shell_v1; struct zwlr_layer_surface_v1; +#include "config/config.h" #include "platform/wayland-protocols.hpp" - #include "wayland_shared_memory.h" -#include "config/config.h" -#include -#include +#include +#include namespace bongocat::platform::wayland { - inline static constexpr int MAX_ATTEMPTS = 4096; - - struct wayland_context_t; - // Cleanup Wayland resources - void cleanup_wayland_context(wayland_context_t& ctx); - - enum class bar_visibility_t : bool { Hide = false, Show = true }; - - struct screen_info_t; - - struct wayland_context_t { - wl_display *display{nullptr}; - wl_compositor *compositor{nullptr}; - wl_shm *shm{nullptr}; - zwlr_layer_shell_v1 *layer_shell{nullptr}; - struct xdg_wm_base *xdg_wm_base{nullptr}; - wl_output *output{nullptr}; - wl_surface *surface{nullptr}; - zwlr_layer_surface_v1 *layer_surface{nullptr}; - - // Output reconnection handling - struct wl_registry *registry{nullptr}; - uint32_t bound_output_name{0}; // Registry name of our bound output - bool using_named_output{false}; // True if user specified an output name - - // local copy from other thread, update after reload (shared memory) - MMapMemory _local_copy_config; - MMapMemory ctx_shm; - bar_visibility_t bar_visibility {bar_visibility_t::Show}; - - int32_t _bar_height{0}; - int32_t _screen_width{0}; - char* _output_name_str{nullptr}; // ref to existing name in output, Will default to automatic one if kept null - bool _fullscreen_detected{false}; - screen_info_t *_screen_info{nullptr}; - - // frame done callback data - wl_callback *_frame_cb{nullptr}; - Mutex _frame_cb_lock; - atomic_bool _frame_pending{false}; - atomic_bool _redraw_after_frame{false}; - timestamp_ms_t _last_frame_timestamp_ms{0}; - - - - wayland_context_t() = default; - ~wayland_context_t() { - cleanup_wayland_context(*this); - } - - wayland_context_t(const wayland_context_t&) = delete; - wayland_context_t& operator=(const wayland_context_t&) = delete; - wayland_context_t(wayland_context_t&& other) noexcept = delete; - wayland_context_t& operator=(wayland_context_t&& other) noexcept = delete; - }; - - inline void cleanup_wayland_context_protocols(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { - atomic_store(&ctx.ctx_shm->configured, false); - } - - if (ctx.registry) { - wl_registry_destroy(ctx.registry); - ctx.registry = nullptr; - } - } - inline void cleanup_wayland_context_surface(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { - atomic_store(&ctx.ctx_shm->configured, false); - } - - if (ctx.layer_surface) { - zwlr_layer_surface_v1_destroy(ctx.layer_surface); - ctx.layer_surface = nullptr; - } - if (ctx.surface) { - wl_surface_destroy(ctx.surface); - ctx.surface = nullptr; - } +inline static constexpr int MAX_ATTEMPTS = 4096; + +struct wayland_context_t; +// Cleanup Wayland resources +void cleanup_wayland_context(wayland_context_t& ctx); + +enum class bar_visibility_t : bool { + Hide = false, + Show = true +}; + +struct screen_info_t; + +struct wayland_context_t { + wl_display *display{nullptr}; + wl_compositor *compositor{nullptr}; + wl_shm *shm{nullptr}; + zwlr_layer_shell_v1 *layer_shell{nullptr}; + struct xdg_wm_base *xdg_wm_base{nullptr}; + wl_output *output{nullptr}; + wl_surface *surface{nullptr}; + zwlr_layer_surface_v1 *layer_surface{nullptr}; + + // Output reconnection handling + struct wl_registry *registry{nullptr}; + uint32_t bound_output_name{0}; // Registry name of our bound output + bool using_named_output{false}; // True if user specified an output name + + // local copy from other thread, update after reload (shared memory) + MMapMemory _local_copy_config; + MMapMemory ctx_shm; + bar_visibility_t bar_visibility{bar_visibility_t::Show}; + + int32_t _bar_height{0}; + int32_t _screen_width{0}; + char *_output_name_str{nullptr}; // ref to existing name in output, Will default to automatic one if kept null + bool _fullscreen_detected{false}; + screen_info_t *_screen_info{nullptr}; + + // frame done callback data + wl_callback *_frame_cb{nullptr}; + Mutex _frame_cb_lock; + atomic_bool _frame_pending{false}; + atomic_bool _redraw_after_frame{false}; + timestamp_ms_t _last_frame_timestamp_ms{0}; + + wayland_context_t() = default; + ~wayland_context_t() { + cleanup_wayland_context(*this); + } + + wayland_context_t(const wayland_context_t&) = delete; + wayland_context_t& operator=(const wayland_context_t&) = delete; + wayland_context_t(wayland_context_t&& other) noexcept = delete; + wayland_context_t& operator=(wayland_context_t&& other) noexcept = delete; +}; + +inline void cleanup_wayland_context_protocols(wayland_context_t& ctx) { + if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + if (ctx.registry) { + wl_registry_destroy(ctx.registry); + ctx.registry = nullptr; + } +} +inline void cleanup_wayland_context_surface(wayland_context_t& ctx) { + if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + if (ctx.layer_surface) { + zwlr_layer_surface_v1_destroy(ctx.layer_surface); + ctx.layer_surface = nullptr; + } + if (ctx.surface) { + wl_surface_destroy(ctx.surface); + ctx.surface = nullptr; + } +} +inline void cleanup_wayland_context_buffer(wayland_context_t& ctx) { + if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { + cleanup_shm_buffer(ctx.ctx_shm->buffers[i]); } - inline void cleanup_wayland_context_buffer(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { - atomic_store(&ctx.ctx_shm->configured, false); - } - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { - for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { - cleanup_shm_buffer(ctx.ctx_shm->buffers[i]); - } - - ctx.ctx_shm->current_buffer_index = 0; - } - - ctx._screen_width = 0; - ctx._bar_height = 0; + ctx.ctx_shm->current_buffer_index = 0; + } + ctx._screen_width = 0; + ctx._bar_height = 0; +} +inline void cleanup_wayland_context(wayland_context_t& ctx) { + if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + // drain pending events + if (ctx.display) { + wl_display_flush(ctx.display); + wl_display_roundtrip(ctx.display); + int attempts = 0; + while (wl_display_dispatch_pending(ctx.display) > 0 && attempts <= MAX_ATTEMPTS) { + attempts++; } - inline void cleanup_wayland_context(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { - atomic_store(&ctx.ctx_shm->configured, false); - } - - // drain pending events - if (ctx.display) { - wl_display_flush(ctx.display); - wl_display_roundtrip(ctx.display); - int attempts = 0; - while (wl_display_dispatch_pending(ctx.display) > 0 && attempts <= MAX_ATTEMPTS) { - attempts++; - } - if (attempts >= MAX_ATTEMPTS && wl_display_dispatch_pending(ctx.display) > 0) { - BONGOCAT_LOG_ERROR("Cant fully drain wayland display, max attempts: %i", attempts); - } - } - - cleanup_wayland_context_protocols(ctx); - - // release frame.done handler - atomic_store(&ctx._frame_pending, false); - atomic_store(&ctx._redraw_after_frame, false); - // ctx._frame_cb_lock should be unlocked - if (ctx._frame_cb) wl_callback_destroy(ctx._frame_cb); - ctx._frame_cb = nullptr; - ctx._last_frame_timestamp_ms = 0; - - // surfaces - cleanup_wayland_context_surface(ctx); - - if (ctx.layer_shell) { - zwlr_layer_shell_v1_destroy(ctx.layer_shell); - ctx.layer_shell = nullptr; - } - if (ctx.xdg_wm_base) { - xdg_wm_base_destroy(ctx.xdg_wm_base); - ctx.xdg_wm_base = nullptr; - } - if (ctx.shm) { - wl_shm_destroy(ctx.shm); - ctx.shm = nullptr; - } - if (ctx.compositor) { - wl_compositor_destroy(ctx.compositor); - ctx.compositor = nullptr; - } - - // release shm - cleanup_wayland_context_buffer(ctx); - release_allocated_mmap_memory(ctx.ctx_shm); - release_allocated_mmap_memory(ctx._local_copy_config); - - if (ctx.display) { - wl_display_disconnect(ctx.display); - ctx.display = nullptr; - } - - - // Note: output is just a reference to one of the outputs[] entries - // It will be destroyed when we destroy the outputs[] array above - ctx.output = nullptr; - ctx.bound_output_name = 0; - ctx.using_named_output = false; - - // Reset state - ctx.display = nullptr; - ctx.compositor = nullptr; - ctx.shm = nullptr; - ctx.layer_shell = nullptr; - ctx.xdg_wm_base = nullptr; - ctx.output = nullptr; - ctx.surface = nullptr; - ctx.layer_surface = nullptr; - ctx._output_name_str = nullptr; - ctx._frame_pending = false; - ctx._redraw_after_frame = false; - ctx._bar_height = 0; - ctx._screen_width = 0; - ctx._fullscreen_detected = false; - ctx._screen_info = nullptr; + if (attempts >= MAX_ATTEMPTS && wl_display_dispatch_pending(ctx.display) > 0) { + BONGOCAT_LOG_ERROR("Cant fully drain wayland display, max attempts: %i", attempts); } + } + + cleanup_wayland_context_protocols(ctx); + + // release frame.done handler + atomic_store(&ctx._frame_pending, false); + atomic_store(&ctx._redraw_after_frame, false); + // ctx._frame_cb_lock should be unlocked + if (ctx._frame_cb) + wl_callback_destroy(ctx._frame_cb); + ctx._frame_cb = nullptr; + ctx._last_frame_timestamp_ms = 0; + + // surfaces + cleanup_wayland_context_surface(ctx); + + if (ctx.layer_shell) { + zwlr_layer_shell_v1_destroy(ctx.layer_shell); + ctx.layer_shell = nullptr; + } + if (ctx.xdg_wm_base) { + xdg_wm_base_destroy(ctx.xdg_wm_base); + ctx.xdg_wm_base = nullptr; + } + if (ctx.shm) { + wl_shm_destroy(ctx.shm); + ctx.shm = nullptr; + } + if (ctx.compositor) { + wl_compositor_destroy(ctx.compositor); + ctx.compositor = nullptr; + } + + // release shm + cleanup_wayland_context_buffer(ctx); + release_allocated_mmap_memory(ctx.ctx_shm); + release_allocated_mmap_memory(ctx._local_copy_config); + + if (ctx.display) { + wl_display_disconnect(ctx.display); + ctx.display = nullptr; + } + + // Note: output is just a reference to one of the outputs[] entries + // It will be destroyed when we destroy the outputs[] array above + ctx.output = nullptr; + ctx.bound_output_name = 0; + ctx.using_named_output = false; + + // Reset state + ctx.display = nullptr; + ctx.compositor = nullptr; + ctx.shm = nullptr; + ctx.layer_shell = nullptr; + ctx.xdg_wm_base = nullptr; + ctx.output = nullptr; + ctx.surface = nullptr; + ctx.layer_surface = nullptr; + ctx._output_name_str = nullptr; + ctx._frame_pending = false; + ctx._redraw_after_frame = false; + ctx._bar_height = 0; + ctx._screen_width = 0; + ctx._fullscreen_detected = false; + ctx._screen_info = nullptr; } +} // namespace bongocat::platform::wayland -#endif // BONGOCAT_WAYLAND_CONTEXT_H \ No newline at end of file +#endif // BONGOCAT_WAYLAND_CONTEXT_H \ No newline at end of file diff --git a/include/platform/wayland_setups.h b/include/platform/wayland_setups.h index 5ee335ef..5dd98d7c 100644 --- a/include/platform/wayland_setups.h +++ b/include/platform/wayland_setups.h @@ -2,18 +2,19 @@ #define BONGOCAT_WAYLAND_SETUPS_H #include "graphics/animation.h" -#include "platform/wayland_shared_memory.h" #include "platform/global_wayland_session.h" +#include "platform/wayland_shared_memory.h" namespace bongocat::platform::wayland::details { - /// @TODO: use created_result_t for shm - // Create shared memory buffer - returns fd or -1 on error - BONGOCAT_NODISCARD FileDescriptor create_shm(off_t size); +/// @TODO: use created_result_t for shm +// Create shared memory buffer - returns fd or -1 on error +BONGOCAT_NODISCARD FileDescriptor create_shm(off_t size); - BONGOCAT_NODISCARD bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx); - BONGOCAT_NODISCARD bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx); - BONGOCAT_NODISCARD bongocat_error_t wayland_setup_surface(wayland_session_t& ctx); - BONGOCAT_NODISCARD bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animation::animation_session_t& anim); -} +BONGOCAT_NODISCARD bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_setup_surface(wayland_session_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, + animation::animation_session_t& anim); +} // namespace bongocat::platform::wayland::details #endif \ No newline at end of file diff --git a/include/platform/wayland_shared_memory.h b/include/platform/wayland_shared_memory.h index d335c2b5..94c2d2e3 100644 --- a/include/platform/wayland_shared_memory.h +++ b/include/platform/wayland_shared_memory.h @@ -2,136 +2,133 @@ #define BONGOCAT_WAYLAND_SHARED_MEMORY_H #include "graphics/global_animation_session.h" -#include + #include +#include namespace bongocat::platform::wayland { - // single-, double- or triple-buffer - // @FIXME: fix for double buffer on (KWin) - inline static constexpr size_t WAYLAND_NUM_BUFFERS = 1; - - struct wayland_shm_buffer_t; - void cleanup_shm_buffer(wayland_shm_buffer_t& buffer); - - struct wayland_context_t; - - struct wayland_shm_buffer_t { - wl_buffer *buffer{nullptr}; - MMapFileBuffer pixels; - atomic_bool busy{false}; // 0: free / 1: busy - atomic_bool pending{false}; // 0/1: a render was requested while busy - size_t index{0}; // index track from wayland_shared_memory_t.buffers - - // extra context for listeners - animation::animation_session_t *_animation_trigger_context{nullptr}; - wayland_context_t *_wayland_context{nullptr}; // parent ref. for buffer_release - - - - wayland_shm_buffer_t() = default; - ~wayland_shm_buffer_t() { - cleanup_shm_buffer(*this); - } - - wayland_shm_buffer_t(const wayland_shm_buffer_t&) = delete; - wayland_shm_buffer_t& operator=(const wayland_shm_buffer_t&) = delete; - - wayland_shm_buffer_t(wayland_shm_buffer_t&& other) noexcept - : buffer(other.buffer), - pixels(bongocat::move(other.pixels)), - index(other.index), - _animation_trigger_context(other._animation_trigger_context), - _wayland_context(other._wayland_context) - { - atomic_store(&busy, atomic_load(&other.busy)); - atomic_store(&pending, atomic_load(&other.pending)); - - other.buffer = nullptr; - other.index = 0; - other._animation_trigger_context = nullptr; - atomic_store(&other.busy, false); - atomic_store(&other.pending, false); - } - wayland_shm_buffer_t& operator=(wayland_shm_buffer_t&& other) noexcept { - if (this != &other) { - buffer = other.buffer; - pixels = bongocat::move(other.pixels); - atomic_store(&busy, atomic_load(&other.busy)); - atomic_store(&pending, atomic_load(&other.pending)); - index = other.index; - _animation_trigger_context = other._animation_trigger_context; - _wayland_context = other._wayland_context; - - other.buffer = nullptr; - other.index = 0; - other._animation_trigger_context = nullptr; - other._wayland_context = nullptr; - atomic_store(&other.busy, false); - atomic_store(&other.pending, false); - } - return *this; - } - }; - - // Wayland globals - struct wayland_shared_memory_t { - wayland_shm_buffer_t buffers[WAYLAND_NUM_BUFFERS]; - size_t current_buffer_index{0}; - atomic_bool configured{false}; - - wayland_shared_memory_t() = default; - ~wayland_shared_memory_t() { - atomic_store(&configured, false); - current_buffer_index = 0; - for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { - cleanup_shm_buffer(buffers[i]); - } - } - - wayland_shared_memory_t(const wayland_shared_memory_t&) = delete; - wayland_shared_memory_t& operator=(const wayland_shared_memory_t&) = delete; - - wayland_shared_memory_t(wayland_shared_memory_t&& other) noexcept - : buffers{} - { - atomic_store(&configured, false); - current_buffer_index = other.current_buffer_index; - // Manually move each buffer - for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { - buffers[i] = bongocat::move(other.buffers[i]); - } - atomic_store(&configured, atomic_load(&other.configured)); - - other.current_buffer_index = 0; - atomic_store(&other.configured, false); - } - wayland_shared_memory_t& operator=(wayland_shared_memory_t&& other) noexcept { - if (this != &other) { - atomic_store(&configured, false); - current_buffer_index = other.current_buffer_index; - for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; ++i) { - buffers[i] = move(other.buffers[i]); - } - atomic_store(&configured, atomic_load(&other.configured)); - - other.current_buffer_index = 0; - atomic_store(&other.configured, false); - } - return *this; - } - }; - - inline void cleanup_shm_buffer(wayland_shm_buffer_t& buffer) { - atomic_store(&buffer.pending, false); - atomic_store(&buffer.busy, true); - if (buffer.buffer) wl_buffer_destroy(buffer.buffer); - buffer.buffer = nullptr; - release_allocated_mmap_file_buffer(buffer.pixels); - atomic_store(&buffer.busy, false); - buffer.index = 0; - buffer._animation_trigger_context = nullptr; - buffer._wayland_context = nullptr; +// single-, double- or triple-buffer +// @FIXME: fix for double buffer on (KWin) +inline static constexpr size_t WAYLAND_NUM_BUFFERS = 1; + +struct wayland_shm_buffer_t; +void cleanup_shm_buffer(wayland_shm_buffer_t& buffer); + +struct wayland_context_t; + +struct wayland_shm_buffer_t { + wl_buffer *buffer{nullptr}; + MMapFileBuffer pixels; + atomic_bool busy{false}; // 0: free / 1: busy + atomic_bool pending{false}; // 0/1: a render was requested while busy + size_t index{0}; // index track from wayland_shared_memory_t.buffers + + // extra context for listeners + animation::animation_session_t *_animation_trigger_context{nullptr}; + wayland_context_t *_wayland_context{nullptr}; // parent ref. for buffer_release + + wayland_shm_buffer_t() = default; + ~wayland_shm_buffer_t() { + cleanup_shm_buffer(*this); + } + + wayland_shm_buffer_t(const wayland_shm_buffer_t&) = delete; + wayland_shm_buffer_t& operator=(const wayland_shm_buffer_t&) = delete; + + wayland_shm_buffer_t(wayland_shm_buffer_t&& other) noexcept + : buffer(other.buffer) + , pixels(bongocat::move(other.pixels)) + , index(other.index) + , _animation_trigger_context(other._animation_trigger_context) + , _wayland_context(other._wayland_context) { + atomic_store(&busy, atomic_load(&other.busy)); + atomic_store(&pending, atomic_load(&other.pending)); + + other.buffer = nullptr; + other.index = 0; + other._animation_trigger_context = nullptr; + atomic_store(&other.busy, false); + atomic_store(&other.pending, false); + } + wayland_shm_buffer_t& operator=(wayland_shm_buffer_t&& other) noexcept { + if (this != &other) { + buffer = other.buffer; + pixels = bongocat::move(other.pixels); + atomic_store(&busy, atomic_load(&other.busy)); + atomic_store(&pending, atomic_load(&other.pending)); + index = other.index; + _animation_trigger_context = other._animation_trigger_context; + _wayland_context = other._wayland_context; + + other.buffer = nullptr; + other.index = 0; + other._animation_trigger_context = nullptr; + other._wayland_context = nullptr; + atomic_store(&other.busy, false); + atomic_store(&other.pending, false); + } + return *this; + } +}; + +// Wayland globals +struct wayland_shared_memory_t { + wayland_shm_buffer_t buffers[WAYLAND_NUM_BUFFERS]; + size_t current_buffer_index{0}; + atomic_bool configured{false}; + + wayland_shared_memory_t() = default; + ~wayland_shared_memory_t() { + atomic_store(&configured, false); + current_buffer_index = 0; + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { + cleanup_shm_buffer(buffers[i]); + } + } + + wayland_shared_memory_t(const wayland_shared_memory_t&) = delete; + wayland_shared_memory_t& operator=(const wayland_shared_memory_t&) = delete; + + wayland_shared_memory_t(wayland_shared_memory_t&& other) noexcept : buffers{} { + atomic_store(&configured, false); + current_buffer_index = other.current_buffer_index; + // Manually move each buffer + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { + buffers[i] = bongocat::move(other.buffers[i]); + } + atomic_store(&configured, atomic_load(&other.configured)); + + other.current_buffer_index = 0; + atomic_store(&other.configured, false); + } + wayland_shared_memory_t& operator=(wayland_shared_memory_t&& other) noexcept { + if (this != &other) { + atomic_store(&configured, false); + current_buffer_index = other.current_buffer_index; + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; ++i) { + buffers[i] = move(other.buffers[i]); + } + atomic_store(&configured, atomic_load(&other.configured)); + + other.current_buffer_index = 0; + atomic_store(&other.configured, false); } + return *this; + } +}; + +inline void cleanup_shm_buffer(wayland_shm_buffer_t& buffer) { + atomic_store(&buffer.pending, false); + atomic_store(&buffer.busy, true); + if (buffer.buffer) + wl_buffer_destroy(buffer.buffer); + buffer.buffer = nullptr; + release_allocated_mmap_file_buffer(buffer.pixels); + atomic_store(&buffer.busy, false); + buffer.index = 0; + buffer._animation_trigger_context = nullptr; + buffer._wayland_context = nullptr; } +} // namespace bongocat::platform::wayland -#endif // BONGOCAT_WAYLAND_SHARED_MEMORY_H \ No newline at end of file +#endif // BONGOCAT_WAYLAND_SHARED_MEMORY_H \ No newline at end of file diff --git a/include/utils/error.h b/include/utils/error.h index 95d35763..c6367567 100644 --- a/include/utils/error.h +++ b/include/utils/error.h @@ -10,25 +10,25 @@ // ============================================================================= // Nodiscard attribute for functions that return values that must be used -#ifdef __cplusplus +#ifdef __cplusplus # define BONGOCAT_NODISCARD [[nodiscard]] #else -#if __STDC_VERSION__ >= 202311L -# define BONGOCAT_NODISCARD [[nodiscard]] -#else -# define BONGOCAT_NODISCARD __attribute__((warn_unused_result)) -#endif +# if __STDC_VERSION__ >= 202311L +# define BONGOCAT_NODISCARD [[nodiscard]] +# else +# define BONGOCAT_NODISCARD __attribute__((warn_unused_result)) +# endif #endif -#ifdef __cplusplus +#ifdef __cplusplus # define BONGOCAT_NULLPTR nullptr #else // Null pointer (C23 nullptr or fallback) -#if __STDC_VERSION__ >= 202311L -# define BONGOCAT_NULLPTR nullptr -#else -# define BONGOCAT_NULLPTR NULL -#endif +# if __STDC_VERSION__ >= 202311L +# define BONGOCAT_NULLPTR nullptr +# else +# define BONGOCAT_NULLPTR NULL +# endif #endif // Unreachable code hint for optimizer @@ -44,32 +44,31 @@ namespace bongocat { // ERROR CODES // ============================================================================= - // Error codes - enum class bongocat_error_t : uint8_t { - BONGOCAT_SUCCESS = 0, - BONGOCAT_ERROR_MEMORY, - BONGOCAT_ERROR_FILE_IO, - BONGOCAT_ERROR_WAYLAND, - BONGOCAT_ERROR_CONFIG, - BONGOCAT_ERROR_INPUT, - BONGOCAT_ERROR_ANIMATION, - BONGOCAT_ERROR_THREAD, - BONGOCAT_ERROR_INVALID_PARAM, - BONGOCAT_ERROR_IMAGE, - }; +// Error codes +enum class bongocat_error_t : uint8_t { + BONGOCAT_SUCCESS = 0, + BONGOCAT_ERROR_MEMORY, + BONGOCAT_ERROR_FILE_IO, + BONGOCAT_ERROR_WAYLAND, + BONGOCAT_ERROR_CONFIG, + BONGOCAT_ERROR_INPUT, + BONGOCAT_ERROR_ANIMATION, + BONGOCAT_ERROR_THREAD, + BONGOCAT_ERROR_INVALID_PARAM, + BONGOCAT_ERROR_IMAGE, +}; // ============================================================================= // GUARD CLAUSE MACROS // ============================================================================= // Guard clause for null pointer - returns early with error -#define BONGOCAT_CHECK_NULL(ptr, error_code) \ - do { \ - if ((ptr) == BONGOCAT_NULLPTR) { \ - BONGOCAT_LOG_ERROR("NULL pointer: %s at %s:%d", #ptr, __FILE__, \ - __LINE__); \ - return (error_code); \ - } \ +#define BONGOCAT_CHECK_NULL(ptr, error_code) \ + do { \ + if ((ptr) == BONGOCAT_NULLPTR) { \ + BONGOCAT_LOG_ERROR("NULL pointer: %s at %s:%d", #ptr, __FILE__, __LINE__); \ + return (error_code); \ + } \ } while (0) // Guard clause for error conditions - returns early with error @@ -94,83 +93,82 @@ namespace bongocat { // ============================================================================= #if !defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER) - namespace details { - // Logging functions - void log_error(const char *format, ...); - void log_warning(const char *format, ...); - void log_info(const char *format, ...); - void log_debug(const char *format, ...); - void log_verbose(const char *format, ...); - } +namespace details { + // Logging functions + void log_error(const char *format, ...); + void log_warning(const char *format, ...); + void log_info(const char *format, ...); + void log_debug(const char *format, ...); + void log_verbose(const char *format, ...); +} // namespace details #endif // ============================================================================= // ERROR HANDLING // ============================================================================= - // Error handling initialization - void error_init(bool enable_debug); - BONGOCAT_NODISCARD const char* error_string(bongocat_error_t error); +// Error handling initialization +void error_init(bool enable_debug); +BONGOCAT_NODISCARD const char *error_string(bongocat_error_t error); - // 1 = Error - // 2 = Warning - // 3 = Info - // 4 = Debug - // 5 = Verbose +// 1 = Error +// 2 = Warning +// 3 = Info +// 4 = Debug +// 5 = Verbose #ifndef BONGOCAT_LOG_LEVEL -#define BONGOCAT_LOG_LEVEL 3 +# define BONGOCAT_LOG_LEVEL 3 #endif - namespace features { +namespace features { #if defined(BONGOCAT_LOG_LEVEL) - inline static constexpr int LogLevel = BONGOCAT_LOG_LEVEL; + inline static constexpr int LogLevel = BONGOCAT_LOG_LEVEL; #endif - } +} // namespace features #if (!defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER)) && BONGOCAT_LOG_LEVEL >= 1 -#define BONGOCAT_LOG_ERROR(format, ...) ::bongocat::details::log_error(format __VA_OPT__(,) __VA_ARGS__) +# define BONGOCAT_LOG_ERROR(format, ...) ::bongocat::details::log_error(format __VA_OPT__(, ) __VA_ARGS__) #else -#define BONGOCAT_LOG_ERROR(format, ...) +# define BONGOCAT_LOG_ERROR(format, ...) #endif #if (!defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER)) && BONGOCAT_LOG_LEVEL >= 2 -#define BONGOCAT_LOG_WARNING(format, ...) ::bongocat::details::log_warning(format __VA_OPT__(,) __VA_ARGS__) +# define BONGOCAT_LOG_WARNING(format, ...) ::bongocat::details::log_warning(format __VA_OPT__(, ) __VA_ARGS__) #else -#define BONGOCAT_LOG_WARNING(format, ...) +# define BONGOCAT_LOG_WARNING(format, ...) #endif #if (!defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER)) && BONGOCAT_LOG_LEVEL >= 3 -#define BONGOCAT_LOG_INFO(format, ...) ::bongocat::details::log_info(format __VA_OPT__(,) __VA_ARGS__) +# define BONGOCAT_LOG_INFO(format, ...) ::bongocat::details::log_info(format __VA_OPT__(, ) __VA_ARGS__) #else -#define BONGOCAT_LOG_INFO(format, ...) +# define BONGOCAT_LOG_INFO(format, ...) #endif #if (!defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER)) && BONGOCAT_LOG_LEVEL >= 4 -#define BONGOCAT_LOG_DEBUG(format, ...) ::bongocat::details::log_debug(format __VA_OPT__(,) __VA_ARGS__) +# define BONGOCAT_LOG_DEBUG(format, ...) ::bongocat::details::log_debug(format __VA_OPT__(, ) __VA_ARGS__) #else -#define BONGOCAT_LOG_DEBUG(format, ...) +# define BONGOCAT_LOG_DEBUG(format, ...) #endif #if (!defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER)) && BONGOCAT_LOG_LEVEL >= 5 -#define BONGOCAT_LOG_VERBOSE(format, ...) ::bongocat::details::log_verbose(format __VA_OPT__(,) __VA_ARGS__) +# define BONGOCAT_LOG_VERBOSE(format, ...) ::bongocat::details::log_verbose(format __VA_OPT__(, ) __VA_ARGS__) #else -#define BONGOCAT_LOG_VERBOSE(format, ...) +# define BONGOCAT_LOG_VERBOSE(format, ...) #endif - - inline int check_errno([[maybe_unused]] const char* fd_name) { - int err = errno; - // supress compiler warning +inline int check_errno([[maybe_unused]] const char *fd_name) { + int err = errno; + // supress compiler warning #if EAGAIN == EWOULDBLOCK - if (err != EAGAIN && err != -1) { - BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(err)); - } + if (err != EAGAIN && err != -1) { + BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(err)); + } #else - if (err != EAGAIN && err != EWOULDBLOCK && err != -1) { - BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(err)); - } + if (err != EAGAIN && err != EWOULDBLOCK && err != -1) { + BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(err)); + } #endif - return err; - } + return err; } +} // namespace bongocat -#endif // ERROR_H \ No newline at end of file +#endif // ERROR_H \ No newline at end of file diff --git a/include/utils/memory.h b/include/utils/memory.h index f2f072a4..e04e43e9 100644 --- a/include/utils/memory.h +++ b/include/utils/memory.h @@ -3,11 +3,12 @@ #include "./time.h" #include "utils/error.h" -#include + #include #include +#include #if defined(__GNUC__) || defined(__GNUG__) -#include +# include #endif namespace bongocat { @@ -16,477 +17,459 @@ namespace bongocat { // MEMORY POOL // ============================================================================= - // Memory pool for efficient allocation - struct memory_pool_t { - void *data{nullptr}; - size_t size{0}; - size_t used{0}; - size_t alignment{0}; - }; +// Memory pool for efficient allocation +struct memory_pool_t { + void *data{nullptr}; + size_t size{0}; + size_t used{0}; + size_t alignment{0}; +}; // ============================================================================= // MEMORY ALLOCATION FUNCTIONS // ============================================================================= - // Safe memory allocation functions - BONGOCAT_NODISCARD void* malloc(size_t size); - BONGOCAT_NODISCARD void* calloc(size_t count, size_t size); - BONGOCAT_NODISCARD void* realloc(void *ptr, size_t size); - void free(void *ptr); +// Safe memory allocation functions +BONGOCAT_NODISCARD void *malloc(size_t size); +BONGOCAT_NODISCARD void *calloc(size_t count, size_t size); +BONGOCAT_NODISCARD void *realloc(void *ptr, size_t size); +void free(void *ptr); // ============================================================================= // MEMORY POOL FUNCTIONS // ============================================================================= - // Memory pool functions - BONGOCAT_NODISCARD memory_pool_t* memory_pool_create(size_t size, size_t alignment); - BONGOCAT_NODISCARD void* memory_pool_alloc(memory_pool_t& pool, size_t size); - void memory_pool_reset(memory_pool_t& pool); - void memory_pool_destroy(memory_pool_t& pool); +// Memory pool functions +BONGOCAT_NODISCARD memory_pool_t *memory_pool_create(size_t size, size_t alignment); +BONGOCAT_NODISCARD void *memory_pool_alloc(memory_pool_t& pool, size_t size); +void memory_pool_reset(memory_pool_t& pool); +void memory_pool_destroy(memory_pool_t& pool); // ============================================================================= // MEMORY STATISTICS // ============================================================================= #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - // Memory statistics - struct memory_stats_t { - atomic_size_t total_allocated; - atomic_size_t current_allocated; - atomic_size_t peak_allocated; - atomic_size_t allocation_count; - atomic_size_t free_count; - }; - - void memory_get_stats(memory_stats_t& stats); +// Memory statistics +struct memory_stats_t { + atomic_size_t total_allocated; + atomic_size_t current_allocated; + atomic_size_t peak_allocated; + atomic_size_t allocation_count; + atomic_size_t free_count; +}; + +void memory_get_stats(memory_stats_t& stats); #endif - void memory_print_stats(); +void memory_print_stats(); // ============================================================================= // DEBUG BUILD FEATURES // ============================================================================= - // Memory leak detection (debug builds) +// Memory leak detection (debug builds) #ifndef NDEBUG -#define BONGOCAT_MALLOC(size) ::bongocat::malloc_debug(size, __FILE__, __LINE__) -#define BONGOCAT_FREE(ptr) ::bongocat::free_debug(ptr, __FILE__, __LINE__) - void* malloc_debug(size_t size, const char *file, int line); - void free_debug(void *ptr, const char *file, int line); - void memory_leak_check(); +# define BONGOCAT_MALLOC(size) ::bongocat::malloc_debug(size, __FILE__, __LINE__) +# define BONGOCAT_FREE(ptr) ::bongocat::free_debug(ptr, __FILE__, __LINE__) +void *malloc_debug(size_t size, const char *file, int line); +void free_debug(void *ptr, const char *file, int line); +void memory_leak_check(); #else -#define BONGOCAT_MALLOC(size) ::bongocat::malloc(size) -#define BONGOCAT_FREE(ptr) ::bongocat::free(ptr) +# define BONGOCAT_MALLOC(size) ::bongocat::malloc(size) +# define BONGOCAT_FREE(ptr) ::bongocat::free(ptr) #endif // ============================================================================= // RAII CLEANUP MACROS // ============================================================================= - // Cleanup function for auto-freeing malloc'd memory - /* - inline void auto_free_impl(void *ptr) { - void **p = static_cast(ptr); - if (*p) { - free(*p); - *p = BONGOCAT_NULLPTR; - } +// Cleanup function for auto-freeing malloc'd memory +/* +inline void auto_free_impl(void *ptr) { + void **p = static_cast(ptr); + if (*p) { + free(*p); + *p = BONGOCAT_NULLPTR; } - */ +} +*/ // Auto-free heap allocations when variable goes out of scope -//#define BONGOCAT_AUTO_FREE __attribute__((cleanup(bongocat::auto_free_impl))) - -#define BONGOCAT_SAFE_FREE(ptr) \ - do { \ - if (ptr) { \ - free(reinterpret_cast(ptr)); \ - (ptr) = BONGOCAT_NULLPTR; \ - } \ - } while (0) - - template - constexpr std::size_t LEN_ARRAY(const T (&)[N]) noexcept { - return N; - } +// #define BONGOCAT_AUTO_FREE __attribute__((cleanup(bongocat::auto_free_impl))) + +#define BONGOCAT_SAFE_FREE(ptr) \ + do { \ + if (ptr) { \ + free(reinterpret_cast(ptr)); \ + (ptr) = BONGOCAT_NULLPTR; \ + } \ + } while (0) + +template constexpr std::size_t LEN_ARRAY(const T (&)[N]) noexcept { + return N; +} - // Cleanup implementation for auto pool (must be after memory_pool_t definition) - /* - inline void bongocat_auto_pool_impl(struct memory_pool **pool) { - if (*pool) { - memory_pool_destroy(*pool); - *pool = BONGOCAT_NULLPTR; - } - } - */ +// Cleanup implementation for auto pool (must be after memory_pool_t definition) +/* +inline void bongocat_auto_pool_impl(struct memory_pool **pool) { + if (*pool) { + memory_pool_destroy(*pool); + *pool = BONGOCAT_NULLPTR; + } +} +*/ - // Auto-destroy memory pool when variable goes out of scope - //#define BONGOCAT_AUTO_POOL __attribute__((cleanup(bongocat::auto_pool_impl))) +// Auto-destroy memory pool when variable goes out of scope +// #define BONGOCAT_AUTO_POOL __attribute__((cleanup(bongocat::auto_pool_impl))) - template - struct is_trivially_copyable { +template struct is_trivially_copyable { #if defined(__clang__) - inline static constexpr bool value = __is_trivially_copyable(T); + inline static constexpr bool value = __is_trivially_copyable(T); #elif defined(__GNUC__) || defined(__GNUG__) - inline static constexpr bool value = __is_trivially_copyable(T); + inline static constexpr bool value = __is_trivially_copyable(T); #elif defined(_MSC_VER) - inline static constexpr bool value = __is_trivially_copyable(T); + inline static constexpr bool value = __is_trivially_copyable(T); #else - inline static constexpr bool value = false; + inline static constexpr bool value = false; #endif - }; +}; - template - struct is_trivially_destructible { +template struct is_trivially_destructible { #if defined(__clang__) - inline static constexpr bool value = __is_trivially_destructible(T); + inline static constexpr bool value = __is_trivially_destructible(T); #elif defined(__GNUC__) || defined(__GNUG__) - // GCC requires `typename T` to be fully resolved - //static constexpr bool value = __is_trivially_destructible(T); - /// @FIXME: expected nested-name-specifier before »T« [-Wtemplate-body] - /// Fallback: use STL - inline static constexpr bool value = std::is_trivially_destructible_v; + // GCC requires `typename T` to be fully resolved + // static constexpr bool value = __is_trivially_destructible(T); + /// @FIXME: expected nested-name-specifier before »T« [-Wtemplate-body] + /// Fallback: use STL + inline static constexpr bool value = std::is_trivially_destructible_v; #elif defined(_MSC_VER) - inline static constexpr bool value = __is_trivially_destructible(T); + inline static constexpr bool value = __is_trivially_destructible(T); #else - inline static constexpr bool value = false; + inline static constexpr bool value = false; #endif - }; +}; // ============================================================================= // RAII CLEANUP // ============================================================================= - template - struct AllocatedMemory; - template - void release_allocated_memory(AllocatedMemory& memory) noexcept; +template struct AllocatedMemory; +template void release_allocated_memory(AllocatedMemory& memory) noexcept; - template - struct AllocatedMemory { - T* ptr{nullptr}; - size_t _size_bytes{0}; +template struct AllocatedMemory { + T *ptr{nullptr}; + size_t _size_bytes{0}; - constexpr AllocatedMemory() = default; - ~AllocatedMemory() noexcept { - release_allocated_memory(*this); - } + constexpr AllocatedMemory() = default; + ~AllocatedMemory() noexcept { + release_allocated_memory(*this); + } - explicit AllocatedMemory(decltype(nullptr)) noexcept {} - AllocatedMemory& operator=(decltype(nullptr)) noexcept { - release_allocated_memory(*this); - return *this; - } + explicit AllocatedMemory(decltype(nullptr)) noexcept {} + AllocatedMemory& operator=(decltype(nullptr)) noexcept { + release_allocated_memory(*this); + return *this; + } - AllocatedMemory(const AllocatedMemory& other) - : _size_bytes(other._size_bytes) - { - _size_bytes = sizeof(T); - if (other.ptr != nullptr && _size_bytes > 0) { - ptr = static_cast(BONGOCAT_MALLOC(_size_bytes)); - if (ptr != nullptr) { - if constexpr (is_trivially_copyable::value) { - memcpy(ptr, other.ptr, _size_bytes); - } else { - new (ptr) T(*other.ptr); - } - } else { - _size_bytes = 0; - ptr = nullptr; - BONGOCAT_LOG_ERROR("memory allocation failed"); - } - } else { - _size_bytes = 0; - } - } - AllocatedMemory& operator=(const AllocatedMemory& other) { - if (this != &other) { - release_allocated_memory(*this); - _size_bytes = sizeof(T); - if (other.ptr != nullptr && _size_bytes > 0) { - ptr = static_cast(BONGOCAT_MALLOC(_size_bytes)); - if (ptr) { - if constexpr (is_trivially_copyable::value) { - memcpy(ptr, other.ptr, _size_bytes); - } else { - new (ptr) T(*other.ptr); - } - } else { - _size_bytes = 0; - ptr = nullptr; - BONGOCAT_LOG_ERROR("memory allocation failed"); - } - } else { - _size_bytes = 0; - } - } - return *this; - } + AllocatedMemory(const AllocatedMemory& other) : _size_bytes(other._size_bytes) { + _size_bytes = sizeof(T); + if (other.ptr != nullptr && _size_bytes > 0) { + ptr = static_cast(BONGOCAT_MALLOC(_size_bytes)); + if (ptr != nullptr) { + if constexpr (is_trivially_copyable::value) { + memcpy(ptr, other.ptr, _size_bytes); + } else { + new (ptr) T(*other.ptr); + } + } else { + _size_bytes = 0; + ptr = nullptr; + BONGOCAT_LOG_ERROR("memory allocation failed"); + } + } else { + _size_bytes = 0; + } + } + AllocatedMemory& operator=(const AllocatedMemory& other) { + if (this != &other) { + release_allocated_memory(*this); + _size_bytes = sizeof(T); + if (other.ptr != nullptr && _size_bytes > 0) { + ptr = static_cast(BONGOCAT_MALLOC(_size_bytes)); + if (ptr) { + if constexpr (is_trivially_copyable::value) { + memcpy(ptr, other.ptr, _size_bytes); + } else { + new (ptr) T(*other.ptr); + } + } else { + _size_bytes = 0; + ptr = nullptr; + BONGOCAT_LOG_ERROR("memory allocation failed"); + } + } else { + _size_bytes = 0; + } + } + return *this; + } - AllocatedMemory(AllocatedMemory&& other) noexcept - : ptr(other.ptr), _size_bytes(other._size_bytes) - { - other.ptr = nullptr; - other._size_bytes = 0; - } - AllocatedMemory& operator=(AllocatedMemory&& other) noexcept { - if (this != &other) { - release_allocated_memory(*this); - ptr = other.ptr; - _size_bytes = other._size_bytes; - other.ptr = nullptr; - other._size_bytes = 0; - } - return *this; - } + AllocatedMemory(AllocatedMemory&& other) noexcept : ptr(other.ptr), _size_bytes(other._size_bytes) { + other.ptr = nullptr; + other._size_bytes = 0; + } + AllocatedMemory& operator=(AllocatedMemory&& other) noexcept { + if (this != &other) { + release_allocated_memory(*this); + ptr = other.ptr; + _size_bytes = other._size_bytes; + other.ptr = nullptr; + other._size_bytes = 0; + } + return *this; + } - constexpr operator bool() const noexcept { - return ptr != nullptr; - } + constexpr operator bool() const noexcept { + return ptr != nullptr; + } - T& operator*() { - assert(ptr); - return *ptr; - } - constexpr const T& operator*() const { - assert(ptr); - return *ptr; - } - T* operator->() { - assert(ptr); - return ptr; - } - constexpr const T* operator->() const { - assert(ptr); - return ptr; - } - explicit operator T*() noexcept { - return ptr; - } - constexpr explicit operator const T*() const noexcept { - return ptr; - } + T& operator*() { + assert(ptr); + return *ptr; + } + constexpr const T& operator*() const { + assert(ptr); + return *ptr; + } + T *operator->() { + assert(ptr); + return ptr; + } + constexpr const T *operator->() const { + assert(ptr); + return ptr; + } + explicit operator T *() noexcept { + return ptr; + } + constexpr explicit operator const T *() const noexcept { + return ptr; + } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return ptr == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return ptr != nullptr; - } - }; - template - void release_allocated_memory(AllocatedMemory& memory) noexcept { - if (memory.ptr != nullptr) { - if constexpr (!is_trivially_destructible::value) { - memory.ptr->~T(); - } - BONGOCAT_SAFE_FREE(memory.ptr); - memory.ptr = nullptr; - memory._size_bytes = 0; - } - } - template - BONGOCAT_NODISCARD inline static AllocatedMemory make_null_memory() noexcept { - return AllocatedMemory(); + constexpr bool operator==(decltype(nullptr)) const noexcept { + return ptr == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return ptr != nullptr; + } +}; +template void release_allocated_memory(AllocatedMemory& memory) noexcept { + if (memory.ptr != nullptr) { + if constexpr (!is_trivially_destructible::value) { + memory.ptr->~T(); } - template - BONGOCAT_NODISCARD inline static AllocatedMemory make_allocated_memory() { - AllocatedMemory ret; - ret._size_bytes = sizeof(T); - if (ret._size_bytes > 0) { - ret.ptr = static_cast(BONGOCAT_MALLOC(ret._size_bytes)); - if (ret.ptr != nullptr) { - // default ctor - new (ret.ptr) T(); - return ret; - } else { - BONGOCAT_LOG_ERROR("memory allocation failed"); - } - } - ret._size_bytes = 0; - ret.ptr = nullptr; - return ret; + BONGOCAT_SAFE_FREE(memory.ptr); + memory.ptr = nullptr; + memory._size_bytes = 0; + } +} +template BONGOCAT_NODISCARD inline static AllocatedMemory make_null_memory() noexcept { + return AllocatedMemory(); +} +template BONGOCAT_NODISCARD inline static AllocatedMemory make_allocated_memory() { + AllocatedMemory ret; + ret._size_bytes = sizeof(T); + if (ret._size_bytes > 0) { + ret.ptr = static_cast(BONGOCAT_MALLOC(ret._size_bytes)); + if (ret.ptr != nullptr) { + // default ctor + new (ret.ptr) T(); + return ret; + } else { + BONGOCAT_LOG_ERROR("memory allocation failed"); } + } + ret._size_bytes = 0; + ret.ptr = nullptr; + return ret; +} +template struct AllocatedArray; +template void release_allocated_array(AllocatedArray& memory) noexcept; - template - struct AllocatedArray; - template - void release_allocated_array(AllocatedArray& memory) noexcept; - - template - struct AllocatedArray { - T* data{nullptr}; - size_t count{0}; - size_t _size_bytes{0}; +template struct AllocatedArray { + T *data{nullptr}; + size_t count{0}; + size_t _size_bytes{0}; - constexpr AllocatedArray() = default; - ~AllocatedArray() noexcept { - release_allocated_array(*this); - } + constexpr AllocatedArray() = default; + ~AllocatedArray() noexcept { + release_allocated_array(*this); + } - explicit AllocatedArray(decltype(nullptr)) noexcept {} - AllocatedArray& operator=(decltype(nullptr)) noexcept { - release_allocated_array(*this); - return *this; - } + explicit AllocatedArray(decltype(nullptr)) noexcept {} + AllocatedArray& operator=(decltype(nullptr)) noexcept { + release_allocated_array(*this); + return *this; + } - explicit AllocatedArray(size_t p_count) - : count(p_count), _size_bytes(sizeof(T) * count) - { - if (_size_bytes > 0) { - data = static_cast(BONGOCAT_MALLOC(_size_bytes)); - if (data) { - return; - } else { - BONGOCAT_LOG_ERROR("malloc array failed: %zu bytes", _size_bytes); - } - } - count = 0; - _size_bytes = 0; - } + explicit AllocatedArray(size_t p_count) : count(p_count), _size_bytes(sizeof(T) * count) { + if (_size_bytes > 0) { + data = static_cast(BONGOCAT_MALLOC(_size_bytes)); + if (data) { + return; + } else { + BONGOCAT_LOG_ERROR("malloc array failed: %zu bytes", _size_bytes); + } + } + count = 0; + _size_bytes = 0; + } - AllocatedArray(const AllocatedArray& other) - : count(other.count), _size_bytes(other._size_bytes) - { - if (other.data && _size_bytes > 0) { - data = static_cast(BONGOCAT_MALLOC(_size_bytes)); - if (data) { - if constexpr (is_trivially_copyable::value) { - memcpy(data, other.data, _size_bytes); - } else { - for (size_t i = 0; i < other.count; i++) { - *data[i] = *other.data[i]; - } - } - return; - } else { - BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); - } - } + AllocatedArray(const AllocatedArray& other) : count(other.count), _size_bytes(other._size_bytes) { + if (other.data && _size_bytes > 0) { + data = static_cast(BONGOCAT_MALLOC(_size_bytes)); + if (data) { + if constexpr (is_trivially_copyable::value) { + memcpy(data, other.data, _size_bytes); + } else { + for (size_t i = 0; i < other.count; i++) { + *data[i] = *other.data[i]; + } + } + return; + } else { + BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); + } + } - count = 0; - _size_bytes = 0; - data = nullptr; - } - AllocatedArray& operator=(const AllocatedArray& other) { - if (this != &other) { - release_allocated_array(*this); - count = other.count; - _size_bytes = other._size_bytes; - if (other.data && _size_bytes > 0) { - data = static_cast(BONGOCAT_MALLOC(_size_bytes)); - if (data) { - if constexpr (is_trivially_copyable::value) { - memcpy(data, other.data, _size_bytes); - } else { - for (size_t i = 0; i < other.count; i++) { - *data[i] = *other.data[i]; - } - } - return *this; - } else { - BONGOCAT_LOG_ERROR("mmap buffer failed in copy assignment"); - } - } - - count = 0; - _size_bytes = 0; - data = nullptr; + count = 0; + _size_bytes = 0; + data = nullptr; + } + AllocatedArray& operator=(const AllocatedArray& other) { + if (this != &other) { + release_allocated_array(*this); + count = other.count; + _size_bytes = other._size_bytes; + if (other.data && _size_bytes > 0) { + data = static_cast(BONGOCAT_MALLOC(_size_bytes)); + if (data) { + if constexpr (is_trivially_copyable::value) { + memcpy(data, other.data, _size_bytes); + } else { + for (size_t i = 0; i < other.count; i++) { + *data[i] = *other.data[i]; } - return *this; + } + return *this; + } else { + BONGOCAT_LOG_ERROR("mmap buffer failed in copy assignment"); } + } - AllocatedArray(AllocatedArray&& other) noexcept - : data(other.data), count(other.count), _size_bytes(other._size_bytes) - { - other.data = nullptr; - other.count = 0; - other._size_bytes = 0; - } - AllocatedArray& operator=(AllocatedArray&& other) noexcept { - if (this != &other) { - release_allocated_array(*this); - data = other.data; - count = other.count; - _size_bytes = other._size_bytes; - other.data = nullptr; - other.count = 0; - other._size_bytes = 0; - } - return *this; - } + count = 0; + _size_bytes = 0; + data = nullptr; + } + return *this; + } - T& operator[](size_t index) { - assert(index < count); - return data[index]; - } - constexpr const T& operator[](size_t index) const { - assert(index < count); - return data[index]; - } + AllocatedArray(AllocatedArray&& other) noexcept + : data(other.data) + , count(other.count) + , _size_bytes(other._size_bytes) { + other.data = nullptr; + other.count = 0; + other._size_bytes = 0; + } + AllocatedArray& operator=(AllocatedArray&& other) noexcept { + if (this != &other) { + release_allocated_array(*this); + data = other.data; + count = other.count; + _size_bytes = other._size_bytes; + other.data = nullptr; + other.count = 0; + other._size_bytes = 0; + } + return *this; + } - constexpr explicit operator bool() const noexcept { - return data != nullptr; - } + T& operator[](size_t index) { + assert(index < count); + return data[index]; + } + constexpr const T& operator[](size_t index) const { + assert(index < count); + return data[index]; + } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; - } - }; - template - void release_allocated_array(AllocatedArray& memory) noexcept { - if (memory.data != nullptr) { - if constexpr (!is_trivially_destructible::value) { - for (size_t i = 0; i < memory.count; i++) { - memory.data[i].~T(); - } - } - BONGOCAT_SAFE_FREE(memory.data); - memory.data = nullptr; - memory.count = 0; - memory._size_bytes = 0; - } - } + constexpr explicit operator bool() const noexcept { + return data != nullptr; + } - template - BONGOCAT_NODISCARD inline static AllocatedArray make_unallocated_array() noexcept { - return AllocatedArray(); - } - template - BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array_uninitialized(size_t count) { - return count > 0 ? AllocatedArray(count) : AllocatedArray(); - } - template - BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array(size_t count) { - auto ret= count > 0 ? AllocatedArray(count) : AllocatedArray(); - for (size_t i = 0;i < ret.count;i++) { - new (&ret.data[i]) T(); - } - return ret; - } - template - BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array_with_value(size_t count, const T& value) { - auto ret= count > 0 ? AllocatedArray(count) : AllocatedArray(); - for (size_t i = 0;i < ret.count;i++) { - ret.data[i] = value; - } - return ret; + constexpr bool operator==(decltype(nullptr)) const noexcept { + return data == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return data != nullptr; + } +}; +template void release_allocated_array(AllocatedArray& memory) noexcept { + if (memory.data != nullptr) { + if constexpr (!is_trivially_destructible::value) { + for (size_t i = 0; i < memory.count; i++) { + memory.data[i].~T(); + } } + BONGOCAT_SAFE_FREE(memory.data); + memory.data = nullptr; + memory.count = 0; + memory._size_bytes = 0; + } +} - // remove_reference implementation (no STL) - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; - template struct remove_reference { typedef T type; }; +template BONGOCAT_NODISCARD inline static AllocatedArray make_unallocated_array() noexcept { + return AllocatedArray(); +} +template +BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array_uninitialized(size_t count) { + return count > 0 ? AllocatedArray(count) : AllocatedArray(); +} +template BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array(size_t count) { + auto ret = count > 0 ? AllocatedArray(count) : AllocatedArray(); + for (size_t i = 0; i < ret.count; i++) { + new (&ret.data[i]) T(); + } + return ret; +} +template +BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array_with_value(size_t count, const T& value) { + auto ret = count > 0 ? AllocatedArray(count) : AllocatedArray(); + for (size_t i = 0; i < ret.count; i++) { + ret.data[i] = value; + } + return ret; +} - // move implementation (no STL) - template - inline typename remove_reference::type&& move(T&& t) { - typedef typename remove_reference::type U; - return static_cast(t); - } +// remove_reference implementation (no STL) +template struct remove_reference { + typedef T type; +}; +template struct remove_reference { + typedef T type; +}; +template struct remove_reference { + typedef T type; +}; + +// move implementation (no STL) +template inline typename remove_reference::type&& move(T&& t) { + typedef typename remove_reference::type U; + return static_cast(t); } +} // namespace bongocat -#endif // BONGOCAT_MEMORY_H \ No newline at end of file +#endif // BONGOCAT_MEMORY_H \ No newline at end of file diff --git a/include/utils/random.h b/include/utils/random.h index 4b36188c..cffd16ce 100644 --- a/include/utils/random.h +++ b/include/utils/random.h @@ -1,9 +1,9 @@ #ifndef BONGOCAT_RANDOM_H_ #define BONGOCAT_RANDOM_H_ -#include -#include #include +#include +#include namespace bongocat::platform { @@ -64,7 +64,9 @@ class random_xoshiro128 { /// Constructor with seed value. ///\param seed The new seed value. //*************************************************************************** - constexpr explicit random_xoshiro128(uint32_t seed) { initialise(seed); } + constexpr explicit random_xoshiro128(uint32_t seed) { + initialise(seed); + } //*************************************************************************** /// Initialises the sequence with a new seed value. @@ -81,7 +83,9 @@ class random_xoshiro128 { //*************************************************************************** /// Get the next random_xoshiro128 number. //*************************************************************************** - [[nodiscard]] constexpr uint32_t operator()() { return next(); } + [[nodiscard]] constexpr uint32_t operator()() { + return next(); + } //*************************************************************************** /// Get the next random_xoshiro128 number in a specified inclusive range. @@ -95,7 +99,9 @@ class random_xoshiro128 { return range(0, UINT32_MAX); } - [[nodiscard]] constexpr inline uint32_t range_min(uint32_t min) { return range(min, UINT32_MAX); } + [[nodiscard]] constexpr inline uint32_t range_min(uint32_t min) { + return range(min, UINT32_MAX); + } private: uint32_t state[4]{}; @@ -113,7 +119,9 @@ class random_xoshiro128 { The state must be seeded so that it is not everywhere zero. */ - static inline constexpr uint32_t rotl(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); } + static inline constexpr uint32_t rotl(const uint32_t x, int k) { + return (x << k) | (x >> (32 - k)); + } constexpr uint32_t next() { const uint32_t result = rotl(state[1] * 5, 7) * 9; @@ -132,7 +140,6 @@ class random_xoshiro128 { return result; } - /* This is the jump function for the generator. It is equivalent to 2^64 calls to next(); it can be used to generate 2^64 non-overlapping subsequences for parallel computations. */ @@ -163,7 +170,6 @@ class random_xoshiro128 { } */ - /* This is the long-jump function for the generator. It is equivalent to 2^96 calls to next(); it can be used to generate 2^32 starting points, from each of which jump() will generate 2^32 non-overlapping @@ -199,6 +205,6 @@ class random_xoshiro128 { // for seeding extern uint32_t slow_rand(); -} +} // namespace bongocat::platform #endif \ No newline at end of file diff --git a/include/utils/system_memory.h b/include/utils/system_memory.h index fba98662..9dbad3d2 100644 --- a/include/utils/system_memory.h +++ b/include/utils/system_memory.h @@ -5,1206 +5,1142 @@ #include "./time.h" #include "core/bongocat.h" #include "utils/error.h" -#include -#include -#include -#include + #include #include +#include +#include +#include #include #include - +#include namespace bongocat::platform { - int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms); - int stop_thread_graceful_or_cancel(pthread_t& thread, atomic_bool& running_flag); - - struct Mutex { - pthread_mutex_t pt_mutex{}; - - Mutex() { - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); - if (pthread_mutex_init(&pt_mutex, &attr) != 0) { - BONGOCAT_LOG_ERROR("Failed to initialize mutex"); - } - pthread_mutexattr_destroy(&attr); - } - ~Mutex() { - int rc = pthread_mutex_destroy(&pt_mutex); - if (rc == EBUSY) { - // still locked → try to unlock first - rc = pthread_mutex_unlock(&pt_mutex); - if (rc != 0) { - BONGOCAT_LOG_ERROR("pthread_mutex_unlock in destructor failed"); - } - pthread_mutex_destroy(&pt_mutex); - } else if (rc != 0) { - BONGOCAT_LOG_ERROR("pthread_mutex_destroy failed"); - } - } - - Mutex(const Mutex&) = delete; - Mutex& operator=(const Mutex&) = delete; - Mutex(Mutex&&) = delete; - Mutex& operator=(Mutex&&) = delete; - - void _lock() { - if (const int rc = pthread_mutex_lock(&pt_mutex); rc != 0) { - BONGOCAT_LOG_ERROR("pthread_mutex_lock failed"); - } - } - void _unlock() { - if (const int rc = pthread_mutex_unlock(&pt_mutex); rc != 0) { - if (rc != EPERM) { // ignore "not owner" - BONGOCAT_LOG_ERROR("pthread_mutex_unlock failed"); - } - } - } - - /* - explicit operator pthread_mutex_t() const noexcept { - return pt_mutex; - } - */ - }; - struct LockGuard { - explicit LockGuard(Mutex& m) : pt_mutex(&m.pt_mutex) { - if (const int rc = pthread_mutex_lock(pt_mutex); rc != 0) { - BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_lock failed"); - } - } - explicit LockGuard(pthread_mutex_t& m) : pt_mutex(&m) { - if (const int rc = pthread_mutex_lock(pt_mutex); rc != 0) { - BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_lock failed"); - } - } - ~LockGuard() { - if (const int rc = pthread_mutex_unlock(pt_mutex); rc != 0) { - BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_unlock failed"); - } - } - - // No copying, no move - LockGuard(const LockGuard&) = delete; - LockGuard& operator=(const LockGuard&) = delete; - LockGuard(const LockGuard&&) = delete; - LockGuard&& operator=(const LockGuard&&) = delete; - - pthread_mutex_t *pt_mutex{nullptr}; - }; - - struct SingleCondVariable; - void cond_destroy(SingleCondVariable& cond); - struct SingleCondVariable { - Mutex mutex; - pthread_cond_t cond; - atomic_bool _predicate{false}; - bool _inited{false}; - - SingleCondVariable() { - pthread_cond_init(&cond, nullptr); - _inited = true; - } - - ~SingleCondVariable() { - cond_destroy(*this); - } - - // No copying, no move - SingleCondVariable(const SingleCondVariable&) = delete; - SingleCondVariable& operator=(const SingleCondVariable&) = delete; - SingleCondVariable(const SingleCondVariable&&) = delete; - SingleCondVariable&& operator=(const SingleCondVariable&&) = delete; - }; - inline void cond_destroy(SingleCondVariable& cond) { - atomic_store(&cond._predicate, true); - if (cond._inited) pthread_cond_broadcast(&cond.cond); - if (cond._inited) pthread_cond_destroy(&cond.cond); - cond._inited = false; +int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms); +int stop_thread_graceful_or_cancel(pthread_t& thread, atomic_bool& running_flag); + +struct Mutex { + pthread_mutex_t pt_mutex{}; + + Mutex() { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + if (pthread_mutex_init(&pt_mutex, &attr) != 0) { + BONGOCAT_LOG_ERROR("Failed to initialize mutex"); } + pthread_mutexattr_destroy(&attr); + } + ~Mutex() { + int rc = pthread_mutex_destroy(&pt_mutex); + if (rc == EBUSY) { + // still locked → try to unlock first + rc = pthread_mutex_unlock(&pt_mutex); + if (rc != 0) { + BONGOCAT_LOG_ERROR("pthread_mutex_unlock in destructor failed"); + } + pthread_mutex_destroy(&pt_mutex); + } else if (rc != 0) { + BONGOCAT_LOG_ERROR("pthread_mutex_destroy failed"); + } + } - struct CondVariable { - CondVariable() { - pthread_mutex_init(&_mutex, nullptr); - pthread_cond_init(&_cond, nullptr); - } - - // No copying, no move - CondVariable(const CondVariable&) = delete; - CondVariable& operator=(const CondVariable&) = delete; - CondVariable(const CondVariable&&) = delete; - CondVariable&& operator=(const CondVariable&&) = delete; - - ~CondVariable() { - pthread_cond_broadcast(&_cond); - pthread_cond_destroy(&_cond); - pthread_mutex_destroy(&_mutex); - } - - template - [[deprecated("better use timedwait")]] int wait(Predicate&& pred) { - int ret = 0; - pthread_mutex_lock(&_mutex); - while (!pred()) { - ret = pthread_cond_wait(&_cond, &_mutex); - } - pthread_mutex_unlock(&_mutex); - return ret; - } - - template - int timedwait(Predicate&& pred, time_ms_t timeout_ms) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += timeout_ms / 1000; - ts.tv_nsec += (timeout_ms % 1000) * 1000000LL; - // normalize time - if (ts.tv_nsec >= 1000000000LL) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000LL; - } - - int ret = 0; - pthread_mutex_lock(&_mutex); - while (!pred()) { - ret = pthread_cond_timedwait(&_cond, &_mutex, &ts); - if (ret == ETIMEDOUT) { - pthread_mutex_unlock(&_mutex); - return ret; - } - } - pthread_mutex_unlock(&_mutex); - return ret; - } - - void notify_all() { - pthread_mutex_lock(&_mutex); - pthread_cond_broadcast(&_cond); - pthread_mutex_unlock(&_mutex); - } - - pthread_mutex_t _mutex; - pthread_cond_t _cond; - }; - - struct CondVarGuard { - explicit CondVarGuard(pthread_mutex_t& m, pthread_cond_t& c, atomic_bool& pred) - : _mutex(m), _cond(c), _predicate(pred) - { - pthread_mutex_lock(&_mutex); - } - explicit CondVarGuard(SingleCondVariable& cond) - : _mutex(cond.mutex.pt_mutex), _cond(cond.cond), _predicate(cond._predicate) - { - pthread_mutex_lock(&_mutex); - } - - ~CondVarGuard() { - pthread_mutex_unlock(&_mutex); - } - - // No copying, no move - CondVarGuard(const CondVarGuard&) = delete; - CondVariable& operator=(const CondVarGuard&) = delete; - CondVarGuard(const CondVarGuard&&) = delete; - CondVarGuard&& operator=(const CondVarGuard&&) = delete; - - // Wait until predicate becomes true - [[deprecated("better use timedwait")]] int wait() { - int ret = 0; - while (!atomic_load(&_predicate)) { - ret = pthread_cond_wait(&_cond, &_mutex); - } - return ret; - } - - int timedwait(time_ms_t timeout) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - ts.tv_sec += timeout / 1000; - ts.tv_nsec += (timeout % 1000) * 1000000LL; - // normalize time - if (ts.tv_nsec >= 1000000000LL) { - ts.tv_sec++; - ts.tv_nsec -= 1000000000LL; - } - - int ret = 0; - while (!atomic_load(&_predicate)) { - ret = pthread_cond_timedwait(&_cond, &_mutex, &ts); - } - return ret; - } - - // Set predicate and signal all waiting threads - void notify() { - atomic_store(&_predicate, true); - pthread_cond_broadcast(&_cond); - } - - pthread_mutex_t& _mutex; - pthread_cond_t& _cond; - atomic_bool& _predicate; - }; - - - template - struct MMapMemory; - template - void release_allocated_mmap_memory(MMapMemory& memory) noexcept; - - template - struct MMapMemory { - T* ptr{nullptr}; - size_t _size_bytes{0}; - - constexpr MMapMemory() = default; - ~MMapMemory() noexcept { - release_allocated_mmap_memory(*this); - } - - explicit MMapMemory(decltype(nullptr)) noexcept {} - MMapMemory& operator=(decltype(nullptr)) noexcept { - release_allocated_mmap_memory(*this); - return *this; - } - - MMapMemory(const MMapMemory& other) - : _size_bytes(other._size_bytes) - { - if (other.ptr && _size_bytes > 0) { - ptr = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, - -1, 0)); - if (ptr != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(ptr, other.ptr, _size_bytes); - } else { - // assign/copy - new (ptr) T(*other.ptr); - } - return; - } else { - BONGOCAT_LOG_ERROR("mmap failed in copy constructor"); - } - } - _size_bytes = 0; - ptr = nullptr; - } - MMapMemory& operator=(const MMapMemory& other) { - if (this != &other) { - release_allocated_mmap_memory(*this); - _size_bytes = other._size_bytes; - if (_size_bytes > 0) { - ptr = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, - -1, 0)); - if (ptr != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(ptr, other.ptr, _size_bytes); - } else { - // assign/copy - new (ptr) T(*other.ptr); - } - return *this; - } else { - BONGOCAT_LOG_ERROR("mmap failed in copy assignment"); - } - } - _size_bytes = 0; - ptr = nullptr; - } - return *this; - } - - MMapMemory(MMapMemory&& other) noexcept - : ptr(other.ptr), _size_bytes(other._size_bytes) - { - other.ptr = nullptr; - other._size_bytes = 0; - } - MMapMemory& operator=(MMapMemory&& other) noexcept { - if (this != &other) { - release_allocated_mmap_memory(*this); - ptr = other.ptr; - _size_bytes = other._size_bytes; - other.ptr = nullptr; - other._size_bytes = 0; - } - return *this; - } - - T& operator*() { - assert(ptr && ptr != MAP_FAILED); - return *ptr; - } - constexpr const T& operator*() const { - assert(ptr && ptr != MAP_FAILED); - return *ptr; - } - T* operator->() { - assert(ptr && ptr != MAP_FAILED); - return ptr; - } - constexpr const T* operator->() const { - assert(ptr && ptr != MAP_FAILED); - return ptr; - } - explicit operator T*() noexcept { - return ptr; - } - constexpr explicit operator const T*() const noexcept { - return ptr; - } - - constexpr explicit operator bool() const noexcept { - return ptr != nullptr && ptr != MAP_FAILED; - } + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + Mutex(Mutex&&) = delete; + Mutex& operator=(Mutex&&) = delete; - constexpr bool operator==(decltype(nullptr)) const noexcept { - return ptr == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return ptr != nullptr; - } - }; - template - void release_allocated_mmap_memory(MMapMemory& memory) noexcept { - if (memory.ptr != nullptr) { - if constexpr (!is_trivially_destructible::value) { - memory.ptr->~T(); - } - munmap(memory.ptr, memory._size_bytes); - memory.ptr = nullptr; - memory._size_bytes = 0; - } + void _lock() { + if (const int rc = pthread_mutex_lock(&pt_mutex); rc != 0) { + BONGOCAT_LOG_ERROR("pthread_mutex_lock failed"); } - template - BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap() noexcept { - return MMapMemory(); + } + void _unlock() { + if (const int rc = pthread_mutex_unlock(&pt_mutex); rc != 0) { + if (rc != EPERM) { // ignore "not owner" + BONGOCAT_LOG_ERROR("pthread_mutex_unlock failed"); + } } - // Allocate shared memory using mmap - template - BONGOCAT_NODISCARD inline static MMapMemory make_allocated_mmap() { - MMapMemory ret; - ret._size_bytes = sizeof(T); - if (ret._size_bytes > 0) { - ret.ptr = static_cast(mmap(nullptr, ret._size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, - -1, 0)); - if (ret.ptr && ret.ptr != MAP_FAILED) { - // default ctor - new (ret.ptr) T(); - return ret; - } else { - BONGOCAT_LOG_ERROR("mmap failed"); - } - } - ret.ptr = nullptr; - ret._size_bytes = 0; - return ret; + } + + /* + explicit operator pthread_mutex_t() const noexcept { + return pt_mutex; + } + */ +}; +struct LockGuard { + explicit LockGuard(Mutex& m) : pt_mutex(&m.pt_mutex) { + if (const int rc = pthread_mutex_lock(pt_mutex); rc != 0) { + BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_lock failed"); } - template - BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap_value(const T& value) { - auto ret = make_allocated_mmap(); - if (ret.ptr != nullptr) { - *ret.ptr = value; - } - return ret; + } + explicit LockGuard(pthread_mutex_t& m) : pt_mutex(&m) { + if (const int rc = pthread_mutex_lock(pt_mutex); rc != 0) { + BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_lock failed"); } - - - template - struct MMapArray; - template - void release_allocated_mmap_array(MMapArray& memory) noexcept; - - template - struct MMapArray { - T* data{nullptr}; - size_t count{0}; - size_t _size_bytes{0}; - - constexpr MMapArray() = default; - ~MMapArray() noexcept { - release_allocated_mmap_array(*this); - } - - explicit MMapArray(decltype(nullptr)) noexcept {} - MMapArray& operator=(decltype(nullptr)) noexcept { - release_allocated_mmap_array(*this); - return *this; - } - - // Allocate shared memory using mmap and count - explicit MMapArray(size_t p_count) - : count(p_count), _size_bytes(sizeof(T) * count) - { - if (_size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, - -1, 0)); - if (data != MAP_FAILED) { - return; - } else { - BONGOCAT_LOG_ERROR("mmap buffer failed"); - } - } - data = nullptr; - count = 0; - _size_bytes = 0; - } - - MMapArray(const MMapArray& other) - : count(other.count), _size_bytes(other._size_bytes) - { - if (other.data && _size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, - -1, 0)); - if (data != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(data, other.data, _size_bytes); - } else { - for (size_t i = 0; i < other.count; i++) { - *data[i] = *other.data[i]; - } - } - return; - } else { - BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); - } - } - data = nullptr; - count = 0; - _size_bytes = 0; - } - MMapArray& operator=(const MMapArray& other) { - if (this != &other) { - release_allocated_mmap_array(*this); - count = other.count; - _size_bytes = other._size_bytes; - if (other.data && _size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED | MAP_ANONYMOUS, - -1, 0)); - if (data != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(data, other.data, _size_bytes); - } else { - for (size_t i = 0; i < other.count; i++) { - data[i] = other.data[i]; - } - } - return *this; - } else { - BONGOCAT_LOG_ERROR("mmap buffer failed in copy assignment"); - } - } - data = nullptr; - count = 0; - _size_bytes = 0; - } - return *this; - } - - MMapArray(MMapArray&& other) noexcept - : data(other.data), count(other.count), _size_bytes(other._size_bytes) - { - other.data = nullptr; - other.count = 0; - other._size_bytes = 0; - } - MMapArray& operator=(MMapArray&& other) noexcept { - if (this != &other) { - release_allocated_mmap_array(*this); - data = other.data; - count = other.count; - _size_bytes = other._size_bytes; - other.data = nullptr; - other.count = 0; - other._size_bytes = 0; - } - return *this; - } - - - T& operator[](size_t index) { - assert(index < count); - return data[index]; - } - constexpr const T& operator[](size_t index) const { - assert(index < count); - return data[index]; - } - - constexpr explicit operator bool() const noexcept { - return data != nullptr && data != MAP_FAILED; - } - - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; - } - }; - template - void release_allocated_mmap_array(MMapArray& memory) noexcept { - if (memory.data) { - if constexpr (!is_trivially_destructible::value) { - for (size_t i = 0; i < memory.count; i++) { - memory.data[i].~T(); - } - } - munmap(memory.data, memory._size_bytes); - memory.data = nullptr; - memory.count = 0; - memory._size_bytes = 0; - } + } + ~LockGuard() { + if (const int rc = pthread_mutex_unlock(pt_mutex); rc != 0) { + BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_unlock failed"); } - template - BONGOCAT_NODISCARD inline static MMapArray make_unallocated_mmap_array() noexcept { - return MMapArray(); + } + + // No copying, no move + LockGuard(const LockGuard&) = delete; + LockGuard& operator=(const LockGuard&) = delete; + LockGuard(const LockGuard&&) = delete; + LockGuard&& operator=(const LockGuard&&) = delete; + + pthread_mutex_t *pt_mutex{nullptr}; +}; + +struct SingleCondVariable; +void cond_destroy(SingleCondVariable& cond); +struct SingleCondVariable { + Mutex mutex; + pthread_cond_t cond; + atomic_bool _predicate{false}; + bool _inited{false}; + + SingleCondVariable() { + pthread_cond_init(&cond, nullptr); + _inited = true; + } + + ~SingleCondVariable() { + cond_destroy(*this); + } + + // No copying, no move + SingleCondVariable(const SingleCondVariable&) = delete; + SingleCondVariable& operator=(const SingleCondVariable&) = delete; + SingleCondVariable(const SingleCondVariable&&) = delete; + SingleCondVariable&& operator=(const SingleCondVariable&&) = delete; +}; +inline void cond_destroy(SingleCondVariable& cond) { + atomic_store(&cond._predicate, true); + if (cond._inited) + pthread_cond_broadcast(&cond.cond); + if (cond._inited) + pthread_cond_destroy(&cond.cond); + cond._inited = false; +} + +struct CondVariable { + CondVariable() { + pthread_mutex_init(&_mutex, nullptr); + pthread_cond_init(&_cond, nullptr); + } + + // No copying, no move + CondVariable(const CondVariable&) = delete; + CondVariable& operator=(const CondVariable&) = delete; + CondVariable(const CondVariable&&) = delete; + CondVariable&& operator=(const CondVariable&&) = delete; + + ~CondVariable() { + pthread_cond_broadcast(&_cond); + pthread_cond_destroy(&_cond); + pthread_mutex_destroy(&_mutex); + } + + template [[deprecated("better use timedwait")]] int wait(Predicate&& pred) { + int ret = 0; + pthread_mutex_lock(&_mutex); + while (!pred()) { + ret = pthread_cond_wait(&_cond, &_mutex); } - template - BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array_uninitialized(size_t count) { - return count > 0? MMapArray(count) : MMapArray(); + pthread_mutex_unlock(&_mutex); + return ret; + } + + template int timedwait(Predicate&& pred, time_ms_t timeout_ms) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout_ms / 1000; + ts.tv_nsec += (timeout_ms % 1000) * 1000000LL; + // normalize time + if (ts.tv_nsec >= 1000000000LL) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000LL; } - template - BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array(size_t count) { - auto ret= count > 0 ? MMapArray(count) : MMapArray(); - for (size_t i = 0;i < ret.count;i++) { - new (&ret.data[i]) T(); - } + + int ret = 0; + pthread_mutex_lock(&_mutex); + while (!pred()) { + ret = pthread_cond_timedwait(&_cond, &_mutex, &ts); + if (ret == ETIMEDOUT) { + pthread_mutex_unlock(&_mutex); return ret; + } + } + pthread_mutex_unlock(&_mutex); + return ret; + } + + void notify_all() { + pthread_mutex_lock(&_mutex); + pthread_cond_broadcast(&_cond); + pthread_mutex_unlock(&_mutex); + } + + pthread_mutex_t _mutex; + pthread_cond_t _cond; +}; + +struct CondVarGuard { + explicit CondVarGuard(pthread_mutex_t& m, pthread_cond_t& c, atomic_bool& pred) + : _mutex(m) + , _cond(c) + , _predicate(pred) { + pthread_mutex_lock(&_mutex); + } + explicit CondVarGuard(SingleCondVariable& cond) + : _mutex(cond.mutex.pt_mutex) + , _cond(cond.cond) + , _predicate(cond._predicate) { + pthread_mutex_lock(&_mutex); + } + + ~CondVarGuard() { + pthread_mutex_unlock(&_mutex); + } + + // No copying, no move + CondVarGuard(const CondVarGuard&) = delete; + CondVariable& operator=(const CondVarGuard&) = delete; + CondVarGuard(const CondVarGuard&&) = delete; + CondVarGuard&& operator=(const CondVarGuard&&) = delete; + + // Wait until predicate becomes true + [[deprecated("better use timedwait")]] int wait() { + int ret = 0; + while (!atomic_load(&_predicate)) { + ret = pthread_cond_wait(&_cond, &_mutex); + } + return ret; + } + + int timedwait(time_ms_t timeout) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += timeout / 1000; + ts.tv_nsec += (timeout % 1000) * 1000000LL; + // normalize time + if (ts.tv_nsec >= 1000000000LL) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000LL; } - template - struct MMapFile; - template - void release_allocated_mmap_file(MMapFile& memory) noexcept; - - template - struct MMapFile { - T* ptr{nullptr}; - size_t _size_bytes{0}; - int _fd{-1}; - off_t _offset{0}; - - constexpr MMapFile() = default; - ~MMapFile() noexcept { - release_allocated_mmap_file(*this); - } - - explicit MMapFile(decltype(nullptr)) noexcept {} - MMapFile& operator=(decltype(nullptr)) noexcept { - release_allocated_mmap_file(*this); - return *this; - } - - explicit MMapFile(int fd, off_t offset = 0) - : _size_bytes(sizeof(T)), _fd(fd), _offset(offset) - { - if (_size_bytes > 0) { - ptr = mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED, - _fd, _offset); - if (ptr != MAP_FAILED) { - return; - } else { - BONGOCAT_LOG_ERROR("mmap failed to map file"); - } - } - ptr = nullptr; - _size_bytes = 0; - } - - MMapFile(const MMapFile& other) - : _size_bytes(other._size_bytes), _fd(other._fd), _offset(other._offset) - { - if (other.ptr && _size_bytes > 0) { - ptr = mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED, - _fd, _offset); - if (ptr != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(ptr, other.ptr, _size_bytes); - } else { - new (ptr) T(*other.ptr); - } - return; - } else { - BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); - } - } - ptr = nullptr; - _size_bytes = 0; - } - MMapFile& operator=(const MMapFile& other) { - if (this != &other) { - release_allocated_mmap_file(*this); - _size_bytes = other._size_bytes; - _fd = other._fd; - _offset = other._offset; - if (other.ptr && _size_bytes > 0) { - ptr = mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED, - _fd, _offset); - if (ptr) { - if constexpr (is_trivially_copyable::value) { - memcpy(ptr, other.ptr, _size_bytes); - } else { - new (ptr) T(*other.ptr); - } - return *this; - } else { - BONGOCAT_LOG_ERROR("file mmap failed in copy assignment"); - } - } - ptr = nullptr; - _size_bytes = 0; - } - return *this; - } - - MMapFile(MMapFile&& other) noexcept - : ptr(other.ptr), _size_bytes(other._size_bytes), _fd(other._fd), _offset(other._offset) - { - other.ptr = nullptr; - other._size_bytes = 0; - other._fd = -1; - other._offset = 0; - } - MMapFile& operator=(MMapFile&& other) noexcept { - if (this != &other) { - release_allocated_mmap_file(*this); - ptr = other.ptr; - _size_bytes = other._size_bytes; - _fd = other._fd; - _offset = other._offset; - other.ptr = nullptr; - other._size_bytes = 0; - other._fd = -1; - other._offset = 0; - } - return *this; - } - - T& operator*() { - assert(ptr); - return *ptr; - } - - T* operator->() { - return ptr; - } - - constexpr explicit operator bool() const noexcept { - return ptr != nullptr && ptr != MAP_FAILED; - } - - constexpr bool operator==(decltype(nullptr)) const noexcept { - return ptr == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return ptr != nullptr; - } - }; - template - void release_allocated_mmap_file(MMapFile& memory) noexcept { - if (memory.ptr) { - if constexpr (!is_trivially_destructible::value) { - memory.ptr->~T(); - } - munmap(memory.ptr, memory._size_bytes); - memory.ptr = nullptr; - memory._size_bytes = 0; - memory._fd = -1; - memory._offset = 0; - } + int ret = 0; + while (!atomic_load(&_predicate)) { + ret = pthread_cond_timedwait(&_cond, &_mutex, &ts); } - template - BONGOCAT_NODISCARD inline static MMapFile make_unallocated_mmap_file() noexcept { - return MMapFile(); + return ret; + } + + // Set predicate and signal all waiting threads + void notify() { + atomic_store(&_predicate, true); + pthread_cond_broadcast(&_cond); + } + + pthread_mutex_t& _mutex; + pthread_cond_t& _cond; + atomic_bool& _predicate; +}; + +template struct MMapMemory; +template void release_allocated_mmap_memory(MMapMemory& memory) noexcept; + +template struct MMapMemory { + T *ptr{nullptr}; + size_t _size_bytes{0}; + + constexpr MMapMemory() = default; + ~MMapMemory() noexcept { + release_allocated_mmap_memory(*this); + } + + explicit MMapMemory(decltype(nullptr)) noexcept {} + MMapMemory& operator=(decltype(nullptr)) noexcept { + release_allocated_mmap_memory(*this); + return *this; + } + + MMapMemory(const MMapMemory& other) : _size_bytes(other._size_bytes) { + if (other.ptr && _size_bytes > 0) { + ptr = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if (ptr != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(ptr, other.ptr, _size_bytes); + } else { + // assign/copy + new (ptr) T(*other.ptr); + } + return; + } else { + BONGOCAT_LOG_ERROR("mmap failed in copy constructor"); + } } - template - BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_uninitialized(int fd, off_t offset = 0) { - return MMapFile(fd, offset); + _size_bytes = 0; + ptr = nullptr; + } + MMapMemory& operator=(const MMapMemory& other) { + if (this != &other) { + release_allocated_mmap_memory(*this); + _size_bytes = other._size_bytes; + if (_size_bytes > 0) { + ptr = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if (ptr != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(ptr, other.ptr, _size_bytes); + } else { + // assign/copy + new (ptr) T(*other.ptr); + } + return *this; + } else { + BONGOCAT_LOG_ERROR("mmap failed in copy assignment"); + } + } + _size_bytes = 0; + ptr = nullptr; } - template - BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_defaulted(int fd, off_t offset = 0) { - auto ret = MMapFile(fd, offset); - if (ret.ptr) { - new (ret.ptr) T(); - } - return ret; + return *this; + } + + MMapMemory(MMapMemory&& other) noexcept : ptr(other.ptr), _size_bytes(other._size_bytes) { + other.ptr = nullptr; + other._size_bytes = 0; + } + MMapMemory& operator=(MMapMemory&& other) noexcept { + if (this != &other) { + release_allocated_mmap_memory(*this); + ptr = other.ptr; + _size_bytes = other._size_bytes; + other.ptr = nullptr; + other._size_bytes = 0; } - template - BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_value(const T& value, int fd, off_t offset = 0) { - auto ret = MMapFile(fd, offset); - for (size_t i = 0;i < ret.size;i++) { - *ret.ptr = value; - } - return ret; + return *this; + } + + T& operator*() { + assert(ptr && ptr != MAP_FAILED); + return *ptr; + } + constexpr const T& operator*() const { + assert(ptr && ptr != MAP_FAILED); + return *ptr; + } + T *operator->() { + assert(ptr && ptr != MAP_FAILED); + return ptr; + } + constexpr const T *operator->() const { + assert(ptr && ptr != MAP_FAILED); + return ptr; + } + explicit operator T *() noexcept { + return ptr; + } + constexpr explicit operator const T *() const noexcept { + return ptr; + } + + constexpr explicit operator bool() const noexcept { + return ptr != nullptr && ptr != MAP_FAILED; + } + + constexpr bool operator==(decltype(nullptr)) const noexcept { + return ptr == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return ptr != nullptr; + } +}; +template void release_allocated_mmap_memory(MMapMemory& memory) noexcept { + if (memory.ptr != nullptr) { + if constexpr (!is_trivially_destructible::value) { + memory.ptr->~T(); } + munmap(memory.ptr, memory._size_bytes); + memory.ptr = nullptr; + memory._size_bytes = 0; + } +} +template BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap() noexcept { + return MMapMemory(); +} +// Allocate shared memory using mmap +template BONGOCAT_NODISCARD inline static MMapMemory make_allocated_mmap() { + MMapMemory ret; + ret._size_bytes = sizeof(T); + if (ret._size_bytes > 0) { + ret.ptr = + static_cast(mmap(nullptr, ret._size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if (ret.ptr && ret.ptr != MAP_FAILED) { + // default ctor + new (ret.ptr) T(); + return ret; + } else { + BONGOCAT_LOG_ERROR("mmap failed"); + } + } + ret.ptr = nullptr; + ret._size_bytes = 0; + return ret; +} +template BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap_value(const T& value) { + auto ret = make_allocated_mmap(); + if (ret.ptr != nullptr) { + *ret.ptr = value; + } + return ret; +} - - template - struct MMapFileBuffer; - template - void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept; - - template - struct MMapFileBuffer { - T* data{nullptr}; - size_t count{0}; - size_t _size_bytes{0}; - int _fd{-1}; - off_t _offset{0}; - - constexpr MMapFileBuffer() = default; - ~MMapFileBuffer() noexcept { - release_allocated_mmap_file_buffer(*this); - } - - explicit MMapFileBuffer(decltype(nullptr)) noexcept {} - MMapFileBuffer& operator=(decltype(nullptr)) noexcept { - release_allocated_mmap_file_buffer(*this); - return *this; - } - - // Allocate shared memory using mmap - MMapFileBuffer(size_t p_count, int fd, off_t offset = 0) - : count(p_count), _size_bytes(sizeof(T) * count), _fd(fd), _offset(offset) - { - if (_size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED, - _fd, _offset)); - if (data != MAP_FAILED) { - return; - } else { - BONGOCAT_LOG_ERROR("mmap buffer failed to map file"); - } - } - data = nullptr; - count = 0; - _size_bytes = 0; - } - - MMapFileBuffer(const MMapFileBuffer& other) - : count(other.count), _size_bytes(other._size_bytes), _fd(other._fd), _offset(other._offset) - { - if (other.data && _size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED, - _fd, _offset)); - if (data != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(data, other.data, _size_bytes); - } else { - for (size_t i = 0; i < other.count; i++) { - *data[i] = *other.data[i]; - } - } - return; - } else { - BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); - } - } - data = nullptr; - count = 0; - _size_bytes = 0; - } - MMapFileBuffer& operator=(const MMapFileBuffer& other) { - if (this != &other) { - release_allocated_mmap_file(*this); - count = other.count; - _size_bytes = other._size_bytes; - _fd = other._fd; - _offset = other._offset; - if (_size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, - PROT_READ | PROT_WRITE, - MAP_SHARED, - _fd, _offset)); - if (data != MAP_FAILED) { - if constexpr (is_trivially_copyable::value) { - memcpy(data, other.data, _size_bytes); - } else { - for (size_t i = 0; i < other.count; i++) { - *data[i] = *other.data[i]; - } - } - return *this; - } else { - BONGOCAT_LOG_ERROR("file mmap buffer failed in copy assignment"); - } - } - data = nullptr; - count = 0; - _size_bytes = 0; - } - return *this; - } - - MMapFileBuffer(MMapFileBuffer&& other) noexcept - : data(other.data), count(other.count), _size_bytes(other._size_bytes), _fd(other._fd), _offset(other._offset) - { - other.data = nullptr; - other.count = 0; - other._size_bytes = 0; - other._fd = -1; - other._offset = 0; - } - MMapFileBuffer& operator=(MMapFileBuffer&& other) noexcept { - if (this != &other) { - release_allocated_mmap_file_buffer(*this); - data = other.data; - count = other.count; - _size_bytes = other._size_bytes; - _fd = other._fd; - _offset = other._offset; - other.data = nullptr; - other.count = 0; - other._size_bytes = 0; - other._fd = -1; - other._offset = 0; +template struct MMapArray; +template void release_allocated_mmap_array(MMapArray& memory) noexcept; + +template struct MMapArray { + T *data{nullptr}; + size_t count{0}; + size_t _size_bytes{0}; + + constexpr MMapArray() = default; + ~MMapArray() noexcept { + release_allocated_mmap_array(*this); + } + + explicit MMapArray(decltype(nullptr)) noexcept {} + MMapArray& operator=(decltype(nullptr)) noexcept { + release_allocated_mmap_array(*this); + return *this; + } + + // Allocate shared memory using mmap and count + explicit MMapArray(size_t p_count) : count(p_count), _size_bytes(sizeof(T) * count) { + if (_size_bytes > 0) { + data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if (data != MAP_FAILED) { + return; + } else { + BONGOCAT_LOG_ERROR("mmap buffer failed"); + } + } + data = nullptr; + count = 0; + _size_bytes = 0; + } + + MMapArray(const MMapArray& other) : count(other.count), _size_bytes(other._size_bytes) { + if (other.data && _size_bytes > 0) { + data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if (data != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(data, other.data, _size_bytes); + } else { + for (size_t i = 0; i < other.count; i++) { + *data[i] = *other.data[i]; + } + } + return; + } else { + BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); + } + } + data = nullptr; + count = 0; + _size_bytes = 0; + } + MMapArray& operator=(const MMapArray& other) { + if (this != &other) { + release_allocated_mmap_array(*this); + count = other.count; + _size_bytes = other._size_bytes; + if (other.data && _size_bytes > 0) { + data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + if (data != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(data, other.data, _size_bytes); + } else { + for (size_t i = 0; i < other.count; i++) { + data[i] = other.data[i]; } - return *this; - } - - - T& operator[](size_t index) { - assert(index < count); - return data[index]; - } - const T& operator[](size_t index) const { - assert(index < count); - return data[index]; - } - - constexpr explicit operator bool() const noexcept { - return data != nullptr && data != MAP_FAILED; - } + } + return *this; + } else { + BONGOCAT_LOG_ERROR("mmap buffer failed in copy assignment"); + } + } + data = nullptr; + count = 0; + _size_bytes = 0; + } + return *this; + } + + MMapArray(MMapArray&& other) noexcept : data(other.data), count(other.count), _size_bytes(other._size_bytes) { + other.data = nullptr; + other.count = 0; + other._size_bytes = 0; + } + MMapArray& operator=(MMapArray&& other) noexcept { + if (this != &other) { + release_allocated_mmap_array(*this); + data = other.data; + count = other.count; + _size_bytes = other._size_bytes; + other.data = nullptr; + other.count = 0; + other._size_bytes = 0; + } + return *this; + } + + T& operator[](size_t index) { + assert(index < count); + return data[index]; + } + constexpr const T& operator[](size_t index) const { + assert(index < count); + return data[index]; + } + + constexpr explicit operator bool() const noexcept { + return data != nullptr && data != MAP_FAILED; + } + + constexpr bool operator==(decltype(nullptr)) const noexcept { + return data == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return data != nullptr; + } +}; +template void release_allocated_mmap_array(MMapArray& memory) noexcept { + if (memory.data) { + if constexpr (!is_trivially_destructible::value) { + for (size_t i = 0; i < memory.count; i++) { + memory.data[i].~T(); + } + } + munmap(memory.data, memory._size_bytes); + memory.data = nullptr; + memory.count = 0; + memory._size_bytes = 0; + } +} +template BONGOCAT_NODISCARD inline static MMapArray make_unallocated_mmap_array() noexcept { + return MMapArray(); +} +template +BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array_uninitialized(size_t count) { + return count > 0 ? MMapArray(count) : MMapArray(); +} +template BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array(size_t count) { + auto ret = count > 0 ? MMapArray(count) : MMapArray(); + for (size_t i = 0; i < ret.count; i++) { + new (&ret.data[i]) T(); + } + return ret; +} - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; - } - }; - template - void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept { - if (memory.data) { - if constexpr (!is_trivially_destructible::value) { - for (size_t i = 0; i < memory.count; i++) { - memory.data[i].~T(); - } - } - munmap(memory.data, memory._size_bytes); - memory.data = nullptr; - memory.count = 0; - memory._size_bytes = 0; - memory._fd = -1; - memory._offset = 0; - } +template struct MMapFile; +template void release_allocated_mmap_file(MMapFile& memory) noexcept; + +template struct MMapFile { + T *ptr{nullptr}; + size_t _size_bytes{0}; + int _fd{-1}; + off_t _offset{0}; + + constexpr MMapFile() = default; + ~MMapFile() noexcept { + release_allocated_mmap_file(*this); + } + + explicit MMapFile(decltype(nullptr)) noexcept {} + MMapFile& operator=(decltype(nullptr)) noexcept { + release_allocated_mmap_file(*this); + return *this; + } + + explicit MMapFile(int fd, off_t offset = 0) : _size_bytes(sizeof(T)), _fd(fd), _offset(offset) { + if (_size_bytes > 0) { + ptr = mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); + if (ptr != MAP_FAILED) { + return; + } else { + BONGOCAT_LOG_ERROR("mmap failed to map file"); + } } - template - BONGOCAT_NODISCARD inline static MMapFileBuffer make_unallocated_mmap_file_buffer() { - return MMapFileBuffer(); + ptr = nullptr; + _size_bytes = 0; + } + + MMapFile(const MMapFile& other) : _size_bytes(other._size_bytes), _fd(other._fd), _offset(other._offset) { + if (other.ptr && _size_bytes > 0) { + ptr = mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); + if (ptr != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(ptr, other.ptr, _size_bytes); + } else { + new (ptr) T(*other.ptr); + } + return; + } else { + BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); + } } - template - BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buffer_uninitialized(size_t count, int fd, off_t offset = 0) { - return MMapFileBuffer(count, fd, offset); + ptr = nullptr; + _size_bytes = 0; + } + MMapFile& operator=(const MMapFile& other) { + if (this != &other) { + release_allocated_mmap_file(*this); + _size_bytes = other._size_bytes; + _fd = other._fd; + _offset = other._offset; + if (other.ptr && _size_bytes > 0) { + ptr = mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); + if (ptr) { + if constexpr (is_trivially_copyable::value) { + memcpy(ptr, other.ptr, _size_bytes); + } else { + new (ptr) T(*other.ptr); + } + return *this; + } else { + BONGOCAT_LOG_ERROR("file mmap failed in copy assignment"); + } + } + ptr = nullptr; + _size_bytes = 0; } - template - BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buffer_defaulted(size_t count, int fd, off_t offset = 0) { - auto ret = count > 0 ? MMapFileBuffer(count, fd, offset) : MMapFileBuffer(); - for (size_t i = 0;i < ret.count;i++) { - new (&ret.data[i]) T(); - } - return ret; + return *this; + } + + MMapFile(MMapFile&& other) noexcept + : ptr(other.ptr) + , _size_bytes(other._size_bytes) + , _fd(other._fd) + , _offset(other._offset) { + other.ptr = nullptr; + other._size_bytes = 0; + other._fd = -1; + other._offset = 0; + } + MMapFile& operator=(MMapFile&& other) noexcept { + if (this != &other) { + release_allocated_mmap_file(*this); + ptr = other.ptr; + _size_bytes = other._size_bytes; + _fd = other._fd; + _offset = other._offset; + other.ptr = nullptr; + other._size_bytes = 0; + other._fd = -1; + other._offset = 0; } - template - BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buffer_value(const T& value, size_t count, int fd, off_t offset = 0) { - auto ret = count > 0 ? MMapFileBuffer(count, fd, offset) : MMapFileBuffer(); - for (size_t i = 0;i < ret.count;i++) { - ret.data[i] = value; - } - return ret; + return *this; + } + + T& operator*() { + assert(ptr); + return *ptr; + } + + T *operator->() { + return ptr; + } + + constexpr explicit operator bool() const noexcept { + return ptr != nullptr && ptr != MAP_FAILED; + } + + constexpr bool operator==(decltype(nullptr)) const noexcept { + return ptr == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return ptr != nullptr; + } +}; +template void release_allocated_mmap_file(MMapFile& memory) noexcept { + if (memory.ptr) { + if constexpr (!is_trivially_destructible::value) { + memory.ptr->~T(); } + munmap(memory.ptr, memory._size_bytes); + memory.ptr = nullptr; + memory._size_bytes = 0; + memory._fd = -1; + memory._offset = 0; + } +} +template BONGOCAT_NODISCARD inline static MMapFile make_unallocated_mmap_file() noexcept { + return MMapFile(); +} +template +BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_uninitialized(int fd, off_t offset = 0) { + return MMapFile(fd, offset); +} +template +BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_defaulted(int fd, off_t offset = 0) { + auto ret = MMapFile(fd, offset); + if (ret.ptr) { + new (ret.ptr) T(); + } + return ret; +} +template +BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_value(const T& value, int fd, off_t offset = 0) { + auto ret = MMapFile(fd, offset); + for (size_t i = 0; i < ret.size; i++) { + *ret.ptr = value; + } + return ret; +} - struct FileDescriptor; - void close_fd(FileDescriptor& fd) noexcept; - - struct FileDescriptor { - int _fd{-1}; - - constexpr FileDescriptor() = default; - explicit FileDescriptor(int fd) noexcept : _fd(fd) {} - ~FileDescriptor() noexcept { - close_fd(*this); - } - - explicit FileDescriptor(decltype(nullptr)) noexcept {} - FileDescriptor& operator=(decltype(nullptr)) noexcept { - close_fd(*this); - return *this; - } - - FileDescriptor(const FileDescriptor&) = delete; - FileDescriptor& operator=(const FileDescriptor&) = delete; - - FileDescriptor(FileDescriptor&& other) noexcept - : _fd(other._fd) - { - other._fd = -1; - } - FileDescriptor& operator=(FileDescriptor&& other) noexcept { - if (this != &other) { - close_fd(*this); - _fd = other._fd; - other._fd = -1; - } - return *this; - } - - // Check if valid - /* - explicit(false) operator bool() const noexcept { - return _fd >= 0; - } - */ - - // conversion to int - /* - explicit operator int() const noexcept { - return _fd; - } - */ - }; - inline void close_fd(FileDescriptor& fd) noexcept { - if (fd._fd >= 0) { - ::close(fd._fd); - fd._fd = -1; - } +template struct MMapFileBuffer; +template void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept; + +template struct MMapFileBuffer { + T *data{nullptr}; + size_t count{0}; + size_t _size_bytes{0}; + int _fd{-1}; + off_t _offset{0}; + + constexpr MMapFileBuffer() = default; + ~MMapFileBuffer() noexcept { + release_allocated_mmap_file_buffer(*this); + } + + explicit MMapFileBuffer(decltype(nullptr)) noexcept {} + MMapFileBuffer& operator=(decltype(nullptr)) noexcept { + release_allocated_mmap_file_buffer(*this); + return *this; + } + + // Allocate shared memory using mmap + MMapFileBuffer(size_t p_count, int fd, off_t offset = 0) + : count(p_count) + , _size_bytes(sizeof(T) * count) + , _fd(fd) + , _offset(offset) { + if (_size_bytes > 0) { + data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); + if (data != MAP_FAILED) { + return; + } else { + BONGOCAT_LOG_ERROR("mmap buffer failed to map file"); + } } - - - - struct MMapFileContent; - void release_allocated_mmap_file_content(MMapFileContent& memory) noexcept; - - struct MMapFileContent { - /// @NOTE: memory is private (not sharable within threads) - unsigned char* data{nullptr}; - off_t _size_bytes{0}; - FileDescriptor _fd{-1}; - off_t _offset{0}; - - constexpr MMapFileContent() = default; - ~MMapFileContent() noexcept { - release_allocated_mmap_file_content(*this); - } - - explicit MMapFileContent(decltype(nullptr)) noexcept {} - MMapFileContent& operator=(decltype(nullptr)) noexcept { - release_allocated_mmap_file_content(*this); - return *this; - } - - // Allocate shared memory using mmap - explicit MMapFileContent(FileDescriptor&& fd, off_t offset = 0) - : _fd(bongocat::move(fd)), _offset(offset) - { - // Get file size - struct stat st {}; - if (::fstat(_fd._fd, &st) < 0) { - close_fd(fd); - return; - } - if (st.st_size <= 0) { - close_fd(fd); - return; - } - if (st.st_size <= offset) { - close_fd(fd); - return; - } - _size_bytes = st.st_size - _offset; - - long page_size = sysconf(_SC_PAGE_SIZE); - assert(page_size > 0); - off_t aligned_offset = (_offset / page_size) * page_size; - off_t delta = _offset - aligned_offset; - _size_bytes = st.st_size - _offset; - size_t map_length = static_cast(_size_bytes + delta); - - if (_size_bytes > 0) { - void* mapped = mmap(nullptr, map_length, - PROT_READ, - MAP_PRIVATE, - _fd._fd, aligned_offset); - if (mapped != MAP_FAILED) { - data = static_cast(mapped) + delta; - return; - } else { - BONGOCAT_LOG_ERROR("mmap file content failed to map file"); - } - } - - data = nullptr; - _size_bytes = 0; - close_fd(fd); - } - - MMapFileContent(MMapFileContent&& other) noexcept - : data(other.data), _size_bytes(other._size_bytes), _fd(bongocat::move(other._fd)), _offset(other._offset) - { - other.data = nullptr; - other._size_bytes = 0; - other._fd._fd = -1; - other._offset = 0; - } - MMapFileContent& operator=(MMapFileContent&& other) noexcept { - if (this != &other) { - release_allocated_mmap_file_content(*this); - data = other.data; - _size_bytes = other._size_bytes; - _fd = bongocat::move(other._fd); - _offset = other._offset; - other.data = nullptr; - other._size_bytes = 0; - other._fd._fd = -1; - other._offset = 0; + data = nullptr; + count = 0; + _size_bytes = 0; + } + + MMapFileBuffer(const MMapFileBuffer& other) + : count(other.count) + , _size_bytes(other._size_bytes) + , _fd(other._fd) + , _offset(other._offset) { + if (other.data && _size_bytes > 0) { + data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); + if (data != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(data, other.data, _size_bytes); + } else { + for (size_t i = 0; i < other.count; i++) { + *data[i] = *other.data[i]; + } + } + return; + } else { + BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); + } + } + data = nullptr; + count = 0; + _size_bytes = 0; + } + MMapFileBuffer& operator=(const MMapFileBuffer& other) { + if (this != &other) { + release_allocated_mmap_file(*this); + count = other.count; + _size_bytes = other._size_bytes; + _fd = other._fd; + _offset = other._offset; + if (_size_bytes > 0) { + data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); + if (data != MAP_FAILED) { + if constexpr (is_trivially_copyable::value) { + memcpy(data, other.data, _size_bytes); + } else { + for (size_t i = 0; i < other.count; i++) { + *data[i] = *other.data[i]; } - return *this; - } - - const unsigned char& operator[](size_t index) const { - assert(_size_bytes >= 0); - assert(index < static_cast(_size_bytes)); - return data[index]; - } + } + return *this; + } else { + BONGOCAT_LOG_ERROR("file mmap buffer failed in copy assignment"); + } + } + data = nullptr; + count = 0; + _size_bytes = 0; + } + return *this; + } + + MMapFileBuffer(MMapFileBuffer&& other) noexcept + : data(other.data) + , count(other.count) + , _size_bytes(other._size_bytes) + , _fd(other._fd) + , _offset(other._offset) { + other.data = nullptr; + other.count = 0; + other._size_bytes = 0; + other._fd = -1; + other._offset = 0; + } + MMapFileBuffer& operator=(MMapFileBuffer&& other) noexcept { + if (this != &other) { + release_allocated_mmap_file_buffer(*this); + data = other.data; + count = other.count; + _size_bytes = other._size_bytes; + _fd = other._fd; + _offset = other._offset; + other.data = nullptr; + other.count = 0; + other._size_bytes = 0; + other._fd = -1; + other._offset = 0; + } + return *this; + } + + T& operator[](size_t index) { + assert(index < count); + return data[index]; + } + const T& operator[](size_t index) const { + assert(index < count); + return data[index]; + } + + constexpr explicit operator bool() const noexcept { + return data != nullptr && data != MAP_FAILED; + } + + constexpr bool operator==(decltype(nullptr)) const noexcept { + return data == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return data != nullptr; + } +}; +template void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept { + if (memory.data) { + if constexpr (!is_trivially_destructible::value) { + for (size_t i = 0; i < memory.count; i++) { + memory.data[i].~T(); + } + } + munmap(memory.data, memory._size_bytes); + memory.data = nullptr; + memory.count = 0; + memory._size_bytes = 0; + memory._fd = -1; + memory._offset = 0; + } +} +template BONGOCAT_NODISCARD inline static MMapFileBuffer make_unallocated_mmap_file_buffer() { + return MMapFileBuffer(); +} +template +BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buffer_uninitialized(size_t count, int fd, + off_t offset = 0) { + return MMapFileBuffer(count, fd, offset); +} +template +BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buffer_defaulted(size_t count, int fd, + off_t offset = 0) { + auto ret = count > 0 ? MMapFileBuffer(count, fd, offset) : MMapFileBuffer(); + for (size_t i = 0; i < ret.count; i++) { + new (&ret.data[i]) T(); + } + return ret; +} +template +BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buffer_value(const T& value, size_t count, + int fd, off_t offset = 0) { + auto ret = count > 0 ? MMapFileBuffer(count, fd, offset) : MMapFileBuffer(); + for (size_t i = 0; i < ret.count; i++) { + ret.data[i] = value; + } + return ret; +} - constexpr explicit operator bool() const noexcept { - return data != nullptr && data != MAP_FAILED && _fd._fd >= 0; - } +struct FileDescriptor; +void close_fd(FileDescriptor& fd) noexcept; + +struct FileDescriptor { + int _fd{-1}; + + constexpr FileDescriptor() = default; + explicit FileDescriptor(int fd) noexcept : _fd(fd) {} + ~FileDescriptor() noexcept { + close_fd(*this); + } + + explicit FileDescriptor(decltype(nullptr)) noexcept {} + FileDescriptor& operator=(decltype(nullptr)) noexcept { + close_fd(*this); + return *this; + } + + FileDescriptor(const FileDescriptor&) = delete; + FileDescriptor& operator=(const FileDescriptor&) = delete; + + FileDescriptor(FileDescriptor&& other) noexcept : _fd(other._fd) { + other._fd = -1; + } + FileDescriptor& operator=(FileDescriptor&& other) noexcept { + if (this != &other) { + close_fd(*this); + _fd = other._fd; + other._fd = -1; + } + return *this; + } + + // Check if valid + /* + explicit(false) operator bool() const noexcept { + return _fd >= 0; + } + */ + + // conversion to int + /* + explicit operator int() const noexcept { + return _fd; + } + */ +}; +inline void close_fd(FileDescriptor& fd) noexcept { + if (fd._fd >= 0) { + ::close(fd._fd); + fd._fd = -1; + } +} - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; - } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; - } - }; - inline void release_allocated_mmap_file_content(MMapFileContent& memory) noexcept { - if (memory.data) { - assert(memory._size_bytes >= 0); - munmap(memory.data, static_cast(memory._size_bytes)); - close_fd(memory._fd); - memory.data = nullptr; - memory._size_bytes = 0; - memory._offset = 0; - } +struct MMapFileContent; +void release_allocated_mmap_file_content(MMapFileContent& memory) noexcept; + +struct MMapFileContent { + /// @NOTE: memory is private (not sharable within threads) + unsigned char *data{nullptr}; + off_t _size_bytes{0}; + FileDescriptor _fd{-1}; + off_t _offset{0}; + + constexpr MMapFileContent() = default; + ~MMapFileContent() noexcept { + release_allocated_mmap_file_content(*this); + } + + explicit MMapFileContent(decltype(nullptr)) noexcept {} + MMapFileContent& operator=(decltype(nullptr)) noexcept { + release_allocated_mmap_file_content(*this); + return *this; + } + + // Allocate shared memory using mmap + explicit MMapFileContent(FileDescriptor&& fd, off_t offset = 0) : _fd(bongocat::move(fd)), _offset(offset) { + // Get file size + struct stat st{}; + if (::fstat(_fd._fd, &st) < 0) { + close_fd(fd); + return; } - BONGOCAT_NODISCARD inline static MMapFileContent make_unallocated_mmap_file_content() { - return {}; + if (st.st_size <= 0) { + close_fd(fd); + return; } - BONGOCAT_NODISCARD inline static created_result_t make_allocated_mmap_file_content(FileDescriptor&& fd, off_t offset = 0) { - // Get file size - struct stat st {}; - if (::fstat(fd._fd, &st) < 0) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap: fd=%d", fd._fd); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - if (st.st_size <= 0) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap, fstat failed on file descriptor: fd=%d", fd._fd); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - if (st.st_size <= offset) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap, Invalid mmap offset (beyond EOF): fd=%d", fd._fd); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - - return MMapFileContent(bongocat::move(fd), offset); + if (st.st_size <= offset) { + close_fd(fd); + return; + } + _size_bytes = st.st_size - _offset; + + long page_size = sysconf(_SC_PAGE_SIZE); + assert(page_size > 0); + off_t aligned_offset = (_offset / page_size) * page_size; + off_t delta = _offset - aligned_offset; + _size_bytes = st.st_size - _offset; + size_t map_length = static_cast(_size_bytes + delta); + + if (_size_bytes > 0) { + void *mapped = mmap(nullptr, map_length, PROT_READ, MAP_PRIVATE, _fd._fd, aligned_offset); + if (mapped != MAP_FAILED) { + data = static_cast(mapped) + delta; + return; + } else { + BONGOCAT_LOG_ERROR("mmap file content failed to map file"); + } } - BONGOCAT_NODISCARD inline static created_result_t make_allocated_mmap_file_content_open(const char* filename, off_t offset = 0) { - int fd = ::open(filename, O_RDONLY); - if (fd < 0) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap: %s", filename); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - // Get file size - struct stat st {}; - if (::fstat(fd, &st) < 0) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap: %s", filename); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - if (st.st_size <= 0) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap, fstat failed on file descriptor: %s", filename); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - if (st.st_size <= offset) { - BONGOCAT_LOG_ERROR("Failed to open file for mmap, Invalid mmap offset (beyond EOF): %s", filename); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - return MMapFileContent(FileDescriptor(fd), offset); + data = nullptr; + _size_bytes = 0; + close_fd(fd); + } + + MMapFileContent(MMapFileContent&& other) noexcept + : data(other.data) + , _size_bytes(other._size_bytes) + , _fd(bongocat::move(other._fd)) + , _offset(other._offset) { + other.data = nullptr; + other._size_bytes = 0; + other._fd._fd = -1; + other._offset = 0; + } + MMapFileContent& operator=(MMapFileContent&& other) noexcept { + if (this != &other) { + release_allocated_mmap_file_content(*this); + data = other.data; + _size_bytes = other._size_bytes; + _fd = bongocat::move(other._fd); + _offset = other._offset; + other.data = nullptr; + other._size_bytes = 0; + other._fd._fd = -1; + other._offset = 0; } + return *this; + } + + const unsigned char& operator[](size_t index) const { + assert(_size_bytes >= 0); + assert(index < static_cast(_size_bytes)); + return data[index]; + } + + constexpr explicit operator bool() const noexcept { + return data != nullptr && data != MAP_FAILED && _fd._fd >= 0; + } + + constexpr bool operator==(decltype(nullptr)) const noexcept { + return data == nullptr; + } + constexpr bool operator!=(decltype(nullptr)) const noexcept { + return data != nullptr; + } +}; +inline void release_allocated_mmap_file_content(MMapFileContent& memory) noexcept { + if (memory.data) { + assert(memory._size_bytes >= 0); + munmap(memory.data, static_cast(memory._size_bytes)); + close_fd(memory._fd); + memory.data = nullptr; + memory._size_bytes = 0; + memory._offset = 0; + } +} +BONGOCAT_NODISCARD inline static MMapFileContent make_unallocated_mmap_file_content() { + return {}; +} +BONGOCAT_NODISCARD inline static created_result_t make_allocated_mmap_file_content(FileDescriptor&& fd, + off_t offset = 0) { + // Get file size + struct stat st{}; + if (::fstat(fd._fd, &st) < 0) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap: fd=%d", fd._fd); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + if (st.st_size <= 0) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap, fstat failed on file descriptor: fd=%d", fd._fd); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + if (st.st_size <= offset) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap, Invalid mmap offset (beyond EOF): fd=%d", fd._fd); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + return MMapFileContent(bongocat::move(fd), offset); +} +BONGOCAT_NODISCARD inline static created_result_t +make_allocated_mmap_file_content_open(const char *filename, off_t offset = 0) { + int fd = ::open(filename, O_RDONLY); + if (fd < 0) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap: %s", filename); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + // Get file size + struct stat st{}; + if (::fstat(fd, &st) < 0) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap: %s", filename); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + if (st.st_size <= 0) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap, fstat failed on file descriptor: %s", filename); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + if (st.st_size <= offset) { + BONGOCAT_LOG_ERROR("Failed to open file for mmap, Invalid mmap offset (beyond EOF): %s", filename); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + return MMapFileContent(FileDescriptor(fd), offset); +} - struct drain_event_result_t { uint64_t result{0}; int err{0}; }; - inline drain_event_result_t drain_event(pollfd& pfd, int max_attempts, [[maybe_unused]] const char* fd_name = nullptr) noexcept { - drain_event_result_t ret; - if (pfd.revents & POLLIN) { - ssize_t rc{0}; - uint64_t u{0}; - int err; - int attempts = 0; - do { - rc = read(pfd.fd, &u, sizeof(u)); - err = errno; - if (rc == sizeof(u)) { - ret.result = u; - } - attempts++; - } while (rc == sizeof(u) && attempts < max_attempts); - if (max_attempts > 1 && rc < 0) { - ret.err = err; - // supress compiler warning +struct drain_event_result_t { + uint64_t result{0}; + int err{0}; +}; +inline drain_event_result_t drain_event(pollfd& pfd, int max_attempts, + [[maybe_unused]] const char *fd_name = nullptr) noexcept { + drain_event_result_t ret; + if (pfd.revents & POLLIN) { + ssize_t rc{0}; + uint64_t u{0}; + int err; + int attempts = 0; + do { + rc = read(pfd.fd, &u, sizeof(u)); + err = errno; + if (rc == sizeof(u)) { + ret.result = u; + } + attempts++; + } while (rc == sizeof(u) && attempts < max_attempts); + if (max_attempts > 1 && rc < 0) { + ret.err = err; + // supress compiler warning #if EAGAIN == EWOULDBLOCK - if (ret.err != EAGAIN && ret.err != -1) { - if (fd_name) { - BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(ret.err)); - } - } + if (ret.err != EAGAIN && ret.err != -1) { + if (fd_name) { + BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(ret.err)); + } + } #else - if (ret.err != EAGAIN && ret.err != EWOULDBLOCK && ret.err != -1) { - if (fd_name) { - BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(ret.err)); - } - } -#endif - } + if (ret.err != EAGAIN && ret.err != EWOULDBLOCK && ret.err != -1) { + if (fd_name) { + BONGOCAT_LOG_ERROR("Error reading %s: %s", fd_name, strerror(ret.err)); } - return ret; + } +#endif } + } + return ret; } +} // namespace bongocat::platform -#endif // BONGOCAT_SYSTEM_MEMORY_H \ No newline at end of file +#endif // BONGOCAT_SYSTEM_MEMORY_H \ No newline at end of file diff --git a/include/utils/time.h b/include/utils/time.h index b70def6f..b5434747 100644 --- a/include/utils/time.h +++ b/include/utils/time.h @@ -4,18 +4,18 @@ #include namespace bongocat::platform { - using timestamp_us_t = int64_t; - using timestamp_ms_t = int64_t; - using time_us_t = int64_t; - using time_ms_t = int64_t; - using time_ns_t = int64_t; - using time_sec_t = int64_t; +using timestamp_us_t = int64_t; +using timestamp_ms_t = int64_t; +using time_us_t = int64_t; +using time_ms_t = int64_t; +using time_ns_t = int64_t; +using time_sec_t = int64_t; - [[nodiscard]] timestamp_us_t get_current_time_us(); - [[nodiscard]] timestamp_ms_t get_current_time_ms(); +[[nodiscard]] timestamp_us_t get_current_time_us(); +[[nodiscard]] timestamp_ms_t get_current_time_ms(); - [[nodiscard]] time_us_t get_uptime_us(); - [[nodiscard]] time_ms_t get_uptime_ms(); -} +[[nodiscard]] time_us_t get_uptime_us(); +[[nodiscard]] time_ms_t get_uptime_ms(); +} // namespace bongocat::platform -#endif // BONGOCAT_TIME_H \ No newline at end of file +#endif // BONGOCAT_TIME_H \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 73849981..cd24e983 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,67 +4,59 @@ target_link_libraries(assets_custom_sprite_sheet_feature INTERFACE assets_custom # base options for all execs add_library(bongocat_options INTERFACE) -if (FEATURE_DISABLE_LOGGER) - target_compile_definitions(bongocat_options INTERFACE BONGOCAT_DISABLE_MEMORY_STATISTICS BONGOCAT_DISABLE_LOGGER) +if(FEATURE_DISABLE_LOGGER) + target_compile_definitions(bongocat_options INTERFACE BONGOCAT_DISABLE_MEMORY_STATISTICS BONGOCAT_DISABLE_LOGGER) endif() -if (FEATURE_PRELOAD_ASSETS) - target_compile_definitions(bongocat_options INTERFACE FEATURE_PRELOAD_ASSETS) +if(FEATURE_PRELOAD_ASSETS) + target_compile_definitions(bongocat_options INTERFACE FEATURE_PRELOAD_ASSETS) endif() -if (FEATURE_LAZY_LOAD_ASSETS) - target_compile_definitions(bongocat_options INTERFACE FEATURE_LAZY_LOAD_ASSETS) +if(FEATURE_LAZY_LOAD_ASSETS) + target_compile_definitions(bongocat_options INTERFACE FEATURE_LAZY_LOAD_ASSETS) endif() -if (FEATURE_CUSTOM_SPRITE_SHEETS) - target_compile_definitions(bongocat_options INTERFACE FEATURE_CUSTOM_SPRITE_SHEETS) - target_link_libraries(bongocat_options INTERFACE assets_custom_sprite_sheet_feature assets_custom_loader) +if(FEATURE_CUSTOM_SPRITE_SHEETS) + target_compile_definitions(bongocat_options INTERFACE FEATURE_CUSTOM_SPRITE_SHEETS) + target_link_libraries(bongocat_options INTERFACE assets_custom_sprite_sheet_feature assets_custom_loader) endif() target_link_libraries(bongocat_options INTERFACE project_warnings project_options project_sanitizers) -target_compile_options(bongocat_options INTERFACE - $<$:-fstack-protector-strong> - $<$:-fstack-protector-strong> -) +target_compile_options(bongocat_options INTERFACE $<$:-fstack-protector-strong> + $<$:-fstack-protector-strong>) # supress warning: embed is a Clang extension [-Wc23-extensions] -target_compile_options(bongocat_options INTERFACE - $<$,$>:-std=gnu23> - $<$,$>:-std=gnu++26> - $<$,$>:-Wno-c23-extensions> - $<$,$>:-Wno-c23-extensions> -) +target_compile_options( + bongocat_options + INTERFACE $<$,$>:-std=gnu23> + $<$,$>:-std=gnu++26> + $<$,$>:-Wno-c23-extensions> + $<$,$>:-Wno-c23-extensions>) # no exceptions -target_compile_options(bongocat_options INTERFACE - $<$: - -fno-exceptions -fno-unwind-tables -fno-rtti - -nostdlib++ - > -) +target_compile_options(bongocat_options INTERFACE $<$: -fno-exceptions -fno-unwind-tables -fno-rtti -nostdlib++ >) # optimization -target_compile_options(bongocat_options INTERFACE - $<$: - -march=native - -fomit-frame-pointer - -funroll-loops - -finline-functions - > - $<$: - -fno-inline-functions - -fomit-frame-pointer - -fno-asynchronous-unwind-tables - -fno-unroll-loops - -fmerge-all-constants - -fno-math-errno - -fno-stack-protector - > -) -target_compile_definitions(bongocat_options INTERFACE - $<$:BONGOCAT_LOG_LEVEL=5> # Verbose - $<$:BONGOCAT_LOG_LEVEL=4> # Debug - $<$:BONGOCAT_LOG_LEVEL=3> # Info - #$<$:BONGOCAT_LOG_LEVEL=0> # NONE - $<$:BONGOCAT_DISABLE_MEMORY_STATISTICS> - $<$:BONGOCAT_DISABLE_LOGGER> -) +target_compile_options( + bongocat_options + INTERFACE $<$: + -march=native + -fomit-frame-pointer + -funroll-loops + -finline-functions + > + $<$: + -fno-inline-functions + -fomit-frame-pointer + -fno-asynchronous-unwind-tables + -fno-unroll-loops + -fmerge-all-constants + -fno-math-errno + -fno-stack-protector + >) +target_compile_definitions( + bongocat_options + INTERFACE $<$:BONGOCAT_LOG_LEVEL=5> # Verbose + $<$:BONGOCAT_LOG_LEVEL=4> # Debug + $<$:BONGOCAT_LOG_LEVEL=3> # Info + # $<$:BONGOCAT_LOG_LEVEL=0> # NONE + $<$:BONGOCAT_DISABLE_MEMORY_STATISTICS> + $<$:BONGOCAT_DISABLE_LOGGER>) target_link_options(bongocat_options INTERFACE $<$:-Wl,--gc-sections -s>) - add_library(bongocat_libs INTERFACE) # wayland-client dependency find_package(PkgConfig REQUIRED) @@ -85,26 +77,25 @@ target_link_options(bongocat_libs INTERFACE -Wl,--gc-sections) # base for assembling exec(s) set(SOURCES - utils/error.cpp - utils/memory.cpp - utils/random.cpp - utils/system_memory.cpp - utils/time.cpp - config/config.cpp - config/config_watcher.cpp - core/main.cpp - graphics/animation.cpp - graphics/animation_init.cpp - graphics/bar.cpp - graphics/drawing_images.cpp - platform/input.cpp - platform/update.cpp - platform/wayland.cpp - platform/wayland_callbacks.cpp - platform/wayland_hyprland.cpp - platform/wayland_setups.cpp - platform/wayland_sway.cpp -) + utils/error.cpp + utils/memory.cpp + utils/random.cpp + utils/system_memory.cpp + utils/time.cpp + config/config.cpp + config/config_watcher.cpp + core/main.cpp + graphics/animation.cpp + graphics/animation_init.cpp + graphics/bar.cpp + graphics/drawing_images.cpp + platform/input.cpp + platform/update.cpp + platform/wayland.cpp + platform/wayland_callbacks.cpp + platform/wayland_hyprland.cpp + platform/wayland_setups.cpp + platform/wayland_sway.cpp) add_library(bongocat_base INTERFACE) target_sources(bongocat_base INTERFACE ${SOURCES}) target_include_directories(bongocat_base INTERFACE ${INCLUDE_DIR}) @@ -125,4 +116,4 @@ add_subdirectory(embedded_assets/ms_agent) add_subdirectory(embedded_assets/pkmn) add_subdirectory(embedded_assets/misc) add_subdirectory(embedded_assets/pmd) -add_subdirectory(image_loader) \ No newline at end of file +add_subdirectory(image_loader) diff --git a/src/config/config.cpp b/src/config/config.cpp index 9a329cdc..12e1fe67 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -1,1845 +1,1949 @@ #include "config/config.h" -#include "core/bongocat.h" -#include "utils/error.h" -#include "graphics/animation_context.h" -#include -#include -#include -#include -#include "graphics/embedded_assets_dms.h" -#include "graphics/embedded_assets_pkmn.h" +#include "core/bongocat.h" #include "embedded_assets/bongocat/bongocat.h" #include "embedded_assets/bongocat/bongocat.hpp" +#include "embedded_assets/misc/misc.hpp" +#include "embedded_assets/misc/misc_sprite.h" #include "embedded_assets/ms_agent/ms_agent.hpp" #include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "embedded_assets/pkmn/pkmn_sprite.h" -#include "embedded_assets/misc/misc.hpp" -#include "embedded_assets/misc/misc_sprite.h" #include "embedded_assets/pmd/pmd_sprite.h" +#include "graphics/animation_context.h" +#include "graphics/embedded_assets_dms.h" +#include "graphics/embedded_assets_pkmn.h" +#include "utils/error.h" + +#include +#include +#include +#include #ifdef FEATURE_DM_EMBEDDED_ASSETS -#include "dm_config_parse_animation_name.h" +# include "dm_config_parse_animation_name.h" #endif #ifdef FEATURE_DM20_EMBEDDED_ASSETS -#include "dm20_config_parse_animation_name.h" +# include "dm20_config_parse_animation_name.h" #endif #ifdef FEATURE_DMX_EMBEDDED_ASSETS -#include "dmx_config_parse_animation_name.h" +# include "dmx_config_parse_animation_name.h" #endif #ifdef FEATURE_DMC_EMBEDDED_ASSETS -#include "dmc_config_parse_animation_name.h" +# include "dmc_config_parse_animation_name.h" #endif #ifdef FEATURE_PEN_EMBEDDED_ASSETS -#include "pen_config_parse_animation_name.h" +# include "pen_config_parse_animation_name.h" #endif #ifdef FEATURE_PEN20_EMBEDDED_ASSETS -#include "pen20_config_parse_animation_name.h" +# include "pen20_config_parse_animation_name.h" #endif #ifdef FEATURE_DMALL_EMBEDDED_ASSETS -#include "dmall_config_parse_animation_name.h" +# include "dmall_config_parse_animation_name.h" #endif #ifdef FEATURE_PKMN_EMBEDDED_ASSETS -#include "pkmn_config_parse_animation_name.h" +# include "pkmn_config_parse_animation_name.h" #endif #ifdef FEATURE_PMD_EMBEDDED_ASSETS -#include "pmd_config_parse_animation_name.h" +# include "pmd_config_parse_animation_name.h" #endif - // ============================================================================= // CONFIGURATION CONSTANTS AND VALIDATION RANGES // ============================================================================= namespace bongocat::config { - static inline constexpr int MIN_CAT_HEIGHT = 10; - static inline constexpr int MAX_CAT_HEIGHT = 200; - static inline constexpr int MIN_OVERLAY_HEIGHT = 20; - static inline constexpr int MAX_OVERLAY_HEIGHT = 300; - static inline constexpr int MIN_FPS = 1; - static inline constexpr int MAX_FPS = 144; - static inline constexpr int MIN_DURATION_MS = 10; - static inline constexpr int MAX_DURATION_MS = 5000; - static inline constexpr int MAX_INTERVAL_SEC = 3600; - static inline constexpr int MIN_KPM = 0; - static inline constexpr int MAX_KPM = 10000; - static inline constexpr double MAX_CPU_THRESHOLD = 100.0; - static inline constexpr double MAX_CPU_RUNNING_FACTOR = 50.0; - static inline constexpr int MAX_UPDATE_RATE_MS = 60 * 60 * 1000; - static inline constexpr int MAX_SLEEP_TIMEOUT_SEC = 30 * 24 * 60 * 60; - static inline constexpr int MIN_OFFSET = -16000; - static inline constexpr int MAX_OFFSET = 16000; - static inline constexpr int MIN_MOVEMENT_RADIUS = 0; - static inline constexpr int MAX_MOVEMENT_RADIUS = MAX_OFFSET/2; - - static inline constexpr int MIN_CUSTOM_FRAMES = 0; - static inline constexpr int MAX_CUSTOM_FRAMES = 512; - static inline constexpr int MIN_CUSTOM_ROWS = 0; - static_assert(assets::CUSTOM_SPRITE_SHEET_MAX_ROWS > 0); - static inline constexpr int MAX_CUSTOM_ROWS = assets::CUSTOM_SPRITE_SHEET_MAX_ROWS-1; - - static_assert(MIN_FPS > 0, "FPS cannot be zero, for math reasons"); - - // Default settings - static inline constexpr auto DEFAULT_DEVICE = "/dev/input/event4"; - static inline constexpr auto DEFAULT_CONFIG_FILE_PATH = "bongocat.conf"; - - static inline constexpr int32_t DEFAULT_CAT_X_OFFSET = 100; - static inline constexpr int32_t DEFAULT_CAT_Y_OFFSET = 10; - static inline constexpr int32_t DEFAULT_CAT_HEIGHT = 40; - static inline constexpr int32_t DEFAULT_OVERLAY_HEIGHT = 80; - static inline constexpr int32_t DEFAULT_IDLE_FRAME = 0; - static inline constexpr platform::time_ms_t DEFAULT_KEYPRESS_DURATION_MS = 100; - static inline constexpr int32_t DEFAULT_OVERLAY_OPACITY = 0; - static inline constexpr int32_t DEFAULT_ANIMATION_INDEX = 0; - static inline constexpr layer_type_t DEFAULT_LAYER = layer_type_t::LAYER_TOP; - static inline constexpr overlay_position_t DEFAULT_OVERLAY_POSITION = overlay_position_t::POSITION_TOP; - static inline constexpr int32_t DEFAULT_HAPPY_KPM = 0; - static inline constexpr platform::time_sec_t DEFAULT_IDLE_SLEEP_TIMEOUT_SEC = 0; - static inline constexpr align_type_t DEFAULT_CAT_ALIGN = align_type_t::ALIGN_CENTER; - static inline constexpr platform::time_ms_t DEFAULT_TEST_ANIMATION_DURATION_MS = 0; - static inline constexpr platform::time_sec_t DEFAULT_TEST_ANIMATION_INTERVAL_SEC = 0; - static inline constexpr int32_t DEFAULT_ENABLE_ANTIALIASING = 1; - static inline constexpr double DEFAULT_MOVEMENT_WAIT_FACTOR = 5.1; - static inline constexpr int32_t DEFAULT_ENABLE_HAND_MAPPING = 1; - - // Debug-specific defaults +static inline constexpr int MIN_CAT_HEIGHT = 10; +static inline constexpr int MAX_CAT_HEIGHT = 200; +static inline constexpr int MIN_OVERLAY_HEIGHT = 20; +static inline constexpr int MAX_OVERLAY_HEIGHT = 300; +static inline constexpr int MIN_FPS = 1; +static inline constexpr int MAX_FPS = 144; +static inline constexpr int MIN_DURATION_MS = 10; +static inline constexpr int MAX_DURATION_MS = 5000; +static inline constexpr int MAX_INTERVAL_SEC = 3600; +static inline constexpr int MIN_KPM = 0; +static inline constexpr int MAX_KPM = 10000; +static inline constexpr double MAX_CPU_THRESHOLD = 100.0; +static inline constexpr double MAX_CPU_RUNNING_FACTOR = 50.0; +static inline constexpr int MAX_UPDATE_RATE_MS = 60 * 60 * 1000; +static inline constexpr int MAX_SLEEP_TIMEOUT_SEC = 30 * 24 * 60 * 60; +static inline constexpr int MIN_OFFSET = -16000; +static inline constexpr int MAX_OFFSET = 16000; +static inline constexpr int MIN_MOVEMENT_RADIUS = 0; +static inline constexpr int MAX_MOVEMENT_RADIUS = MAX_OFFSET / 2; + +static inline constexpr int MIN_CUSTOM_FRAMES = 0; +static inline constexpr int MAX_CUSTOM_FRAMES = 512; +static inline constexpr int MIN_CUSTOM_ROWS = 0; +static_assert(assets::CUSTOM_SPRITE_SHEET_MAX_ROWS > 0); +static inline constexpr int MAX_CUSTOM_ROWS = assets::CUSTOM_SPRITE_SHEET_MAX_ROWS - 1; + +static_assert(MIN_FPS > 0, "FPS cannot be zero, for math reasons"); + +// Default settings +static inline constexpr auto DEFAULT_DEVICE = "/dev/input/event4"; +static inline constexpr auto DEFAULT_CONFIG_FILE_PATH = "bongocat.conf"; + +static inline constexpr int32_t DEFAULT_CAT_X_OFFSET = 100; +static inline constexpr int32_t DEFAULT_CAT_Y_OFFSET = 10; +static inline constexpr int32_t DEFAULT_CAT_HEIGHT = 40; +static inline constexpr int32_t DEFAULT_OVERLAY_HEIGHT = 80; +static inline constexpr int32_t DEFAULT_IDLE_FRAME = 0; +static inline constexpr platform::time_ms_t DEFAULT_KEYPRESS_DURATION_MS = 100; +static inline constexpr int32_t DEFAULT_OVERLAY_OPACITY = 0; +static inline constexpr int32_t DEFAULT_ANIMATION_INDEX = 0; +static inline constexpr layer_type_t DEFAULT_LAYER = layer_type_t::LAYER_TOP; +static inline constexpr overlay_position_t DEFAULT_OVERLAY_POSITION = overlay_position_t::POSITION_TOP; +static inline constexpr int32_t DEFAULT_HAPPY_KPM = 0; +static inline constexpr platform::time_sec_t DEFAULT_IDLE_SLEEP_TIMEOUT_SEC = 0; +static inline constexpr align_type_t DEFAULT_CAT_ALIGN = align_type_t::ALIGN_CENTER; +static inline constexpr platform::time_ms_t DEFAULT_TEST_ANIMATION_DURATION_MS = 0; +static inline constexpr platform::time_sec_t DEFAULT_TEST_ANIMATION_INTERVAL_SEC = 0; +static inline constexpr int32_t DEFAULT_ENABLE_ANTIALIASING = 1; +static inline constexpr double DEFAULT_MOVEMENT_WAIT_FACTOR = 5.1; +static inline constexpr int32_t DEFAULT_ENABLE_HAND_MAPPING = 1; + +// Debug-specific defaults #ifndef NDEBUG - static inline constexpr int32_t DEFAULT_ENABLE_DEBUG = 1; +static inline constexpr int32_t DEFAULT_ENABLE_DEBUG = 1; #else - static inline constexpr int32_t DEFAULT_ENABLE_DEBUG = 0; +static inline constexpr int32_t DEFAULT_ENABLE_DEBUG = 0; #endif +static inline constexpr auto CAT_X_OFFSET_KEY = "cat_x_offset"; +static inline constexpr auto CAT_Y_OFFSET_KEY = "cat_y_offset"; +static inline constexpr auto CAT_HEIGHT_KEY = "cat_height"; +static inline constexpr auto OVERLAY_HEIGHT_KEY = "overlay_height"; +static inline constexpr auto OVERLAY_POSITION_KEY = "overlay_position"; +static inline constexpr auto ANIMATION_NAME_KEY = "animation_name"; +static inline constexpr auto INVERT_COLOR_KEY = "invert_color"; +static inline constexpr auto PADDING_X_KEY = "padding_x"; +static inline constexpr auto PADDING_Y_KEY = "padding_y"; +static inline constexpr auto IDLE_FRAME_KEY = "idle_frame"; +static inline constexpr auto ENABLE_SCHEDULED_SLEEP_KEY = "enable_scheduled_sleep"; +static inline constexpr auto SLEEP_BEGIN_KEY = "sleep_begin"; +static inline constexpr auto SLEEP_END_KEY = "sleep_end"; +static inline constexpr auto IDLE_SLEEP_TIMEOUT_KEY = "idle_sleep_timeout"; +static inline constexpr auto HAPPY_KPM_KEY = "happy_kpm"; +static inline constexpr auto KEYPRESS_DURATION_KEY = "keypress_duration"; +static inline constexpr auto TEST_ANIMATION_DURATION_KEY = "test_animation_duration"; +static inline constexpr auto TEST_ANIMATION_INTERVAL_KEY = "test_animation_interval"; +static inline constexpr auto ANIMATION_SPEED_KEY = "animation_speed"; +static inline constexpr auto FPS_KEY = "fps"; +static inline constexpr auto OVERLAY_OPACITY_KEY = "overlay_opacity"; +static inline constexpr auto ENABLE_DEBUG_KEY = "enable_debug"; +static inline constexpr auto KEYBOARD_DEVICE_KEY = "keyboard_device"; +static inline constexpr auto KEYBOARD_DEVICES_KEY = "keyboard_devices"; +static inline constexpr auto ANIMATION_INDEX_KEY = "animation_index"; +static inline constexpr auto LAYER_KEY = "layer"; ///< DEPRECATED: use overlay_layer +static inline constexpr auto OVERLAY_LAYER_KEY = "overlay_layer"; +static inline constexpr auto CAT_ALIGN_KEY = "cat_align"; +static inline constexpr auto IDLE_ANIMATION_KEY = "idle_animation"; +static inline constexpr auto INPUT_FPS_KEY = "input_fps"; +static inline constexpr auto MIRROR_X_KEY = "mirror_x"; +static inline constexpr auto MIRROR_Y_KEY = "mirror_y"; +static inline constexpr auto RANDOM_KEY = "random"; +static inline constexpr auto RANDOM_ON_RELOAD_KEY = "random_on_reload"; +static inline constexpr auto ENABLE_ANTIALIASING_KEY = "enable_antialiasing"; +static inline constexpr auto UPDATE_RATE_KEY = "update_rate"; +static inline constexpr auto CPU_THRESHOLD_KEY = "cpu_threshold"; +static inline constexpr auto CPU_RUNNING_FACTOR_KEY = "cpu_running_factor"; +static inline constexpr auto MOVEMENT_RADIUS_KEY = "movement_radius"; +static inline constexpr auto ENABLE_MOVEMENT_DEBUG_KEY = "enable_movement_debug"; +static inline constexpr auto MOVEMENT_SPEED_KEY = "movement_speed"; +static inline constexpr auto MOVEMENT_WAIT_FACTOR_KEY = "movement_wait_factor"; +static inline constexpr auto SCREEN_WIDTH_KEY = "screen_width"; +static inline constexpr auto MONITOR_KEY = "monitor"; +static inline constexpr auto OUTPUT_NAME_KEY = "output_name"; // monitor alt key +static inline constexpr auto ENABLE_HAND_MAPPING_KEY = "enable_hand_mapping"; + +static inline constexpr auto CUSTOM_SPRITE_SHEET_FILENAME_KEY = "custom_sprite_sheet_filename"; +static inline constexpr auto CUSTOM_IDLE_FRAMES_KEY = "custom_idle_frames"; +static inline constexpr auto CUSTOM_BORING_FRAMES_KEY = "custom_boring_frames"; +static inline constexpr auto CUSTOM_START_WRITING_FRAMES_KEY = "custom_start_writing_frames"; +static inline constexpr auto CUSTOM_WRITING_FRAMES_KEY = "custom_writing_frames"; +static inline constexpr auto CUSTOM_END_WRITING_FRAMES_KEY = "custom_end_writing_frames"; +static inline constexpr auto CUSTOM_HAPPY_FRAMES_KEY = "custom_happy_frames"; +static inline constexpr auto CUSTOM_ASLEEP_FRAMES_KEY = "custom_asleep_frames"; +static inline constexpr auto CUSTOM_SLEEP_FRAMES_KEY = "custom_sleep_frames"; +static inline constexpr auto CUSTOM_WAKE_UP_FRAMES_KEY = "custom_wake_up_frames"; +static inline constexpr auto CUSTOM_START_WORKING_FRAMES_KEY = "custom_start_working_frames"; +static inline constexpr auto CUSTOM_WORKING_FRAMES_KEY = "custom_working_frames"; +static inline constexpr auto CUSTOM_END_WORKING_FRAMES_KEY = "custom_end_working_frames"; +static inline constexpr auto CUSTOM_START_MOVING_FRAMES_KEY = "custom_start_moving_frames"; +static inline constexpr auto CUSTOM_MOVING_FRAMES_KEY = "custom_moving_frames"; +static inline constexpr auto CUSTOM_END_MOVING_FRAMES_KEY = "custom_end_moving_frames"; +static inline constexpr auto CUSTOM_START_RUNNING_FRAMES_KEY = "custom_start_running_frames"; +static inline constexpr auto CUSTOM_RUNNING_FRAMES_KEY = "custom_running_frames"; +static inline constexpr auto CUSTOM_END_RUNNING_FRAMES_KEY = "custom_end_running_frames"; + +static inline constexpr auto CUSTOM_TOGGLE_WRITING_FRAMES_KEY = "custom_toggle_writing_frames"; +static inline constexpr auto CUSTOM_TOGGLE_WRITING_FRAMES_RANDOM_KEY = "custom_toggle_writing_frames_random"; +static inline constexpr auto CUSTOM_MIRROR_X_MOVING_KEY = "custom_mirror_x_moving"; + +static inline constexpr auto CUSTOM_IDLE_ROW_KEY = "custom_idle_row"; +static inline constexpr auto CUSTOM_BORING_ROW_KEY = "custom_boring_row"; +static inline constexpr auto CUSTOM_START_WRITING_ROW_KEY = "custom_start_writing_row"; +static inline constexpr auto CUSTOM_WRITING_ROW_KEY = "custom_writing_row"; +static inline constexpr auto CUSTOM_END_WRITING_ROW_KEY = "custom_end_writing_row"; +static inline constexpr auto CUSTOM_HAPPY_ROW_KEY = "custom_happy_row"; +static inline constexpr auto CUSTOM_ASLEEP_ROW_KEY = "custom_asleep_row"; +static inline constexpr auto CUSTOM_SLEEP_ROW_KEY = "custom_sleep_row"; +static inline constexpr auto CUSTOM_WAKE_UP_ROW_KEY = "custom_wake_up_row"; +static inline constexpr auto CUSTOM_START_WORKING_ROW_KEY = "custom_start_working_row"; +static inline constexpr auto CUSTOM_WORKING_ROW_KEY = "custom_working_row"; +static inline constexpr auto CUSTOM_END_WORKING_ROW_KEY = "custom_end_working_row"; +static inline constexpr auto CUSTOM_START_MOVING_ROW_KEY = "custom_start_moving_row"; +static inline constexpr auto CUSTOM_MOVING_ROW_KEY = "custom_moving_row"; +static inline constexpr auto CUSTOM_END_MOVING_ROW_KEY = "custom_end_moving_row"; +static inline constexpr auto CUSTOM_START_RUNNING_ROW_KEY = "custom_start_running_row"; +static inline constexpr auto CUSTOM_RUNNING_ROW_KEY = "custom_running_row"; +static inline constexpr auto CUSTOM_END_RUNNING_ROW_KEY = "custom_end_running_row"; +static inline constexpr auto CUSTOM_ROWS_KEY = "custom_rows"; + +static inline constexpr size_t KEY_BUF = 256; +static inline constexpr size_t VALUE_BUF = PATH_MAX + 256; // max value + comment +static inline constexpr size_t LINE_BUF = KEY_BUF - 1 + VALUE_BUF - 1 + 1 + 1; // key + '=' + value + '\0' - static inline constexpr auto CAT_X_OFFSET_KEY = "cat_x_offset"; - static inline constexpr auto CAT_Y_OFFSET_KEY = "cat_y_offset"; - static inline constexpr auto CAT_HEIGHT_KEY = "cat_height"; - static inline constexpr auto OVERLAY_HEIGHT_KEY = "overlay_height"; - static inline constexpr auto OVERLAY_POSITION_KEY = "overlay_position"; - static inline constexpr auto ANIMATION_NAME_KEY = "animation_name"; - static inline constexpr auto INVERT_COLOR_KEY = "invert_color"; - static inline constexpr auto PADDING_X_KEY = "padding_x"; - static inline constexpr auto PADDING_Y_KEY = "padding_y"; - static inline constexpr auto IDLE_FRAME_KEY = "idle_frame"; - static inline constexpr auto ENABLE_SCHEDULED_SLEEP_KEY = "enable_scheduled_sleep"; - static inline constexpr auto SLEEP_BEGIN_KEY = "sleep_begin"; - static inline constexpr auto SLEEP_END_KEY = "sleep_end"; - static inline constexpr auto IDLE_SLEEP_TIMEOUT_KEY = "idle_sleep_timeout"; - static inline constexpr auto HAPPY_KPM_KEY = "happy_kpm"; - static inline constexpr auto KEYPRESS_DURATION_KEY = "keypress_duration"; - static inline constexpr auto TEST_ANIMATION_DURATION_KEY = "test_animation_duration"; - static inline constexpr auto TEST_ANIMATION_INTERVAL_KEY = "test_animation_interval"; - static inline constexpr auto ANIMATION_SPEED_KEY = "animation_speed"; - static inline constexpr auto FPS_KEY = "fps"; - static inline constexpr auto OVERLAY_OPACITY_KEY = "overlay_opacity"; - static inline constexpr auto ENABLE_DEBUG_KEY = "enable_debug"; - static inline constexpr auto KEYBOARD_DEVICE_KEY = "keyboard_device"; - static inline constexpr auto KEYBOARD_DEVICES_KEY = "keyboard_devices"; - static inline constexpr auto ANIMATION_INDEX_KEY = "animation_index"; - static inline constexpr auto LAYER_KEY = "layer"; ///< DEPRECATED: use overlay_layer - static inline constexpr auto OVERLAY_LAYER_KEY = "overlay_layer"; - static inline constexpr auto CAT_ALIGN_KEY = "cat_align"; - static inline constexpr auto IDLE_ANIMATION_KEY = "idle_animation"; - static inline constexpr auto INPUT_FPS_KEY = "input_fps"; - static inline constexpr auto MIRROR_X_KEY = "mirror_x"; - static inline constexpr auto MIRROR_Y_KEY = "mirror_y"; - static inline constexpr auto RANDOM_KEY = "random"; - static inline constexpr auto RANDOM_ON_RELOAD_KEY = "random_on_reload"; - static inline constexpr auto ENABLE_ANTIALIASING_KEY = "enable_antialiasing"; - static inline constexpr auto UPDATE_RATE_KEY = "update_rate"; - static inline constexpr auto CPU_THRESHOLD_KEY = "cpu_threshold"; - static inline constexpr auto CPU_RUNNING_FACTOR_KEY = "cpu_running_factor"; - static inline constexpr auto MOVEMENT_RADIUS_KEY = "movement_radius"; - static inline constexpr auto ENABLE_MOVEMENT_DEBUG_KEY = "enable_movement_debug"; - static inline constexpr auto MOVEMENT_SPEED_KEY = "movement_speed"; - static inline constexpr auto MOVEMENT_WAIT_FACTOR_KEY = "movement_wait_factor"; - static inline constexpr auto SCREEN_WIDTH_KEY = "screen_width"; - static inline constexpr auto MONITOR_KEY = "monitor"; - static inline constexpr auto OUTPUT_NAME_KEY = "output_name"; // monitor alt key - static inline constexpr auto ENABLE_HAND_MAPPING_KEY = "enable_hand_mapping"; - - static inline constexpr auto CUSTOM_SPRITE_SHEET_FILENAME_KEY = "custom_sprite_sheet_filename"; - static inline constexpr auto CUSTOM_IDLE_FRAMES_KEY = "custom_idle_frames"; - static inline constexpr auto CUSTOM_BORING_FRAMES_KEY = "custom_boring_frames"; - static inline constexpr auto CUSTOM_START_WRITING_FRAMES_KEY = "custom_start_writing_frames"; - static inline constexpr auto CUSTOM_WRITING_FRAMES_KEY = "custom_writing_frames"; - static inline constexpr auto CUSTOM_END_WRITING_FRAMES_KEY = "custom_end_writing_frames"; - static inline constexpr auto CUSTOM_HAPPY_FRAMES_KEY = "custom_happy_frames"; - static inline constexpr auto CUSTOM_ASLEEP_FRAMES_KEY = "custom_asleep_frames"; - static inline constexpr auto CUSTOM_SLEEP_FRAMES_KEY = "custom_sleep_frames"; - static inline constexpr auto CUSTOM_WAKE_UP_FRAMES_KEY = "custom_wake_up_frames"; - static inline constexpr auto CUSTOM_START_WORKING_FRAMES_KEY = "custom_start_working_frames"; - static inline constexpr auto CUSTOM_WORKING_FRAMES_KEY = "custom_working_frames"; - static inline constexpr auto CUSTOM_END_WORKING_FRAMES_KEY = "custom_end_working_frames"; - static inline constexpr auto CUSTOM_START_MOVING_FRAMES_KEY = "custom_start_moving_frames"; - static inline constexpr auto CUSTOM_MOVING_FRAMES_KEY = "custom_moving_frames"; - static inline constexpr auto CUSTOM_END_MOVING_FRAMES_KEY = "custom_end_moving_frames"; - static inline constexpr auto CUSTOM_START_RUNNING_FRAMES_KEY = "custom_start_running_frames"; - static inline constexpr auto CUSTOM_RUNNING_FRAMES_KEY = "custom_running_frames"; - static inline constexpr auto CUSTOM_END_RUNNING_FRAMES_KEY = "custom_end_running_frames"; - - static inline constexpr auto CUSTOM_TOGGLE_WRITING_FRAMES_KEY = "custom_toggle_writing_frames"; - static inline constexpr auto CUSTOM_TOGGLE_WRITING_FRAMES_RANDOM_KEY = "custom_toggle_writing_frames_random"; - static inline constexpr auto CUSTOM_MIRROR_X_MOVING_KEY = "custom_mirror_x_moving"; - - static inline constexpr auto CUSTOM_IDLE_ROW_KEY = "custom_idle_row"; - static inline constexpr auto CUSTOM_BORING_ROW_KEY = "custom_boring_row"; - static inline constexpr auto CUSTOM_START_WRITING_ROW_KEY = "custom_start_writing_row"; - static inline constexpr auto CUSTOM_WRITING_ROW_KEY = "custom_writing_row"; - static inline constexpr auto CUSTOM_END_WRITING_ROW_KEY = "custom_end_writing_row"; - static inline constexpr auto CUSTOM_HAPPY_ROW_KEY = "custom_happy_row"; - static inline constexpr auto CUSTOM_ASLEEP_ROW_KEY = "custom_asleep_row"; - static inline constexpr auto CUSTOM_SLEEP_ROW_KEY = "custom_sleep_row"; - static inline constexpr auto CUSTOM_WAKE_UP_ROW_KEY = "custom_wake_up_row"; - static inline constexpr auto CUSTOM_START_WORKING_ROW_KEY = "custom_start_working_row"; - static inline constexpr auto CUSTOM_WORKING_ROW_KEY = "custom_working_row"; - static inline constexpr auto CUSTOM_END_WORKING_ROW_KEY = "custom_end_working_row"; - static inline constexpr auto CUSTOM_START_MOVING_ROW_KEY = "custom_start_moving_row"; - static inline constexpr auto CUSTOM_MOVING_ROW_KEY = "custom_moving_row"; - static inline constexpr auto CUSTOM_END_MOVING_ROW_KEY = "custom_end_moving_row"; - static inline constexpr auto CUSTOM_START_RUNNING_ROW_KEY = "custom_start_running_row"; - static inline constexpr auto CUSTOM_RUNNING_ROW_KEY = "custom_running_row"; - static inline constexpr auto CUSTOM_END_RUNNING_ROW_KEY = "custom_end_running_row"; - static inline constexpr auto CUSTOM_ROWS_KEY = "custom_rows"; - - static inline constexpr size_t KEY_BUF = 256; - static inline constexpr size_t VALUE_BUF = PATH_MAX + 256; // max value + comment - static inline constexpr size_t LINE_BUF = KEY_BUF-1 + VALUE_BUF-1 + 1 + 1; // key + '=' + value + '\0' - - // ============================================================================= - // CONFIGURATION VALIDATION MODULE - // ============================================================================= - - static constexpr uint64_t config_clamp_int(int& value, int min, int max, [[maybe_unused]] const char *name) { - if (value < min || value > max) { - BONGOCAT_LOG_WARNING("%s %d out of range [%d-%d], clamping", name, value, min, max); - value = (value < min) ? min : max; - return (1u << 0); - } - return 0; +// ============================================================================= +// CONFIGURATION VALIDATION MODULE +// ============================================================================= + +static constexpr uint64_t config_clamp_int(int& value, int min, int max, [[maybe_unused]] const char *name) { + if (value < min || value > max) { + BONGOCAT_LOG_WARNING("%s %d out of range [%d-%d], clamping", name, value, min, max); + value = (value < min) ? min : max; + return (1u << 0); + } + return 0; +} +static constexpr uint64_t config_clamp_double(double& value, double min, double max, + [[maybe_unused]] const char *name) { + if (value < min || value > max) { + BONGOCAT_LOG_WARNING("%s %.2f out of range [%.0f-%.0f], clamping", name, value, min, max); + value = (value < min) ? min : max; + return (1u << 0); + } + return 0; +} + +static constexpr uint64_t config_validate_max_int(const int& value, int max, [[maybe_unused]] const char *name) { + if (value > max) { + BONGOCAT_LOG_WARNING("%s %d out of range [%d], clamping", name, value, max); + return (1u << 0); + } + return 0; +} + +static uint64_t config_validate_dimensions(config_t& config) { + uint64_t ret{0}; + ret |= config_clamp_int(config.cat_height, MIN_CAT_HEIGHT, MAX_CAT_HEIGHT, CAT_HEIGHT_KEY); + ret |= config_clamp_int(config.overlay_height, MIN_OVERLAY_HEIGHT, MAX_OVERLAY_HEIGHT, OVERLAY_HEIGHT_KEY); + ret |= config_clamp_int(config.cat_x_offset, MIN_OFFSET, MAX_OFFSET, CAT_X_OFFSET_KEY); + ret |= config_clamp_int(config.cat_y_offset, MIN_OFFSET, MAX_OFFSET, CAT_Y_OFFSET_KEY); + ret |= config_clamp_int(config.movement_radius, MIN_MOVEMENT_RADIUS, MAX_MOVEMENT_RADIUS, CAT_Y_OFFSET_KEY); + ret |= config_clamp_int(config.padding_x, 0, MAX_OFFSET, PADDING_X_KEY); + ret |= config_clamp_int(config.padding_y, 0, MAX_OFFSET, PADDING_Y_KEY); + ret |= config_clamp_int(config.screen_width, 0, MAX_OFFSET, SCREEN_WIDTH_KEY); + return ret; +} + +static uint64_t config_validate_timing(config_t& config) { + uint64_t ret{0}; + ret |= config_clamp_int(config.fps, MIN_FPS, MAX_FPS, FPS_KEY); + ret |= config_clamp_int(config.keypress_duration_ms, MIN_DURATION_MS, MAX_DURATION_MS, KEYPRESS_DURATION_KEY); + ret |= config_clamp_int(config.test_animation_duration_ms, 0, MAX_DURATION_MS, TEST_ANIMATION_DURATION_KEY); + ret |= config_clamp_int(config.animation_speed_ms, 0, MAX_DURATION_MS, ANIMATION_SPEED_KEY); + ret |= config_clamp_int(config.idle_sleep_timeout_sec, 0, MAX_SLEEP_TIMEOUT_SEC, IDLE_SLEEP_TIMEOUT_KEY); + ret |= config_clamp_int(config.input_fps, 0, MAX_FPS, INPUT_FPS_KEY); + ret |= config_clamp_int(config.movement_speed, 0, MAX_DURATION_MS, MOVEMENT_SPEED_KEY); + + // Validate interval (0 is allowed to disable) + if (config.test_animation_interval_sec < 0 || config.test_animation_interval_sec > MAX_INTERVAL_SEC) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%dsec], clamping", TEST_ANIMATION_INTERVAL_KEY, + config.test_animation_interval_sec, MAX_INTERVAL_SEC); + config.test_animation_interval_sec = (config.test_animation_interval_sec < 0) ? 0 : MAX_INTERVAL_SEC; + ret = (1u << 1); + } + if (config.animation_speed_ms < 0 || config.animation_speed_ms > MAX_INTERVAL_SEC * 1000) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%dms], clamping", ANIMATION_SPEED_KEY, + config.test_animation_interval_sec, MAX_INTERVAL_SEC * 1000); + config.animation_speed_ms = (config.animation_speed_ms < 0) ? 0 : MAX_INTERVAL_SEC * 1000; + ret = (1u << 2); + } + return ret; +} + +static uint64_t config_validate_kpm(config_t& config) { + return config_clamp_int(config.happy_kpm, MIN_KPM, MAX_KPM, HAPPY_KPM_KEY); +} + +static uint64_t config_validate_update(config_t& config) { + uint64_t ret{0}; + + ret |= config_clamp_int(config.update_rate_ms, 0, MAX_UPDATE_RATE_MS, UPDATE_RATE_KEY); + ret |= config_clamp_double(config.cpu_threshold, 0, MAX_CPU_THRESHOLD, CPU_THRESHOLD_KEY); + ret |= config_clamp_double(config.cpu_running_factor, 0, MAX_CPU_RUNNING_FACTOR, CPU_RUNNING_FACTOR_KEY); + + return ret; +} + +static uint64_t config_validate_custom(config_t& config) { + using namespace assets; + uint64_t ret{0}; + + if (config._custom) { + if (config.custom_sprite_sheet_settings.feature_toggle_writing_frames >= 0) { + config.custom_sprite_sheet_settings.feature_toggle_writing_frames = + config.custom_sprite_sheet_settings.feature_toggle_writing_frames ? 1 : 0; } - static constexpr uint64_t config_clamp_double(double& value, double min, double max, [[maybe_unused]] const char *name) { - if (value < min || value > max) { - BONGOCAT_LOG_WARNING("%s %.2f out of range [%.0f-%.0f], clamping", name, value, min, max); - value = (value < min) ? min : max; - return (1u << 0); - } - return 0; + if (config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random >= 0) { + config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random = + config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random ? 1 : 0; } - - static constexpr uint64_t config_validate_max_int(const int& value, int max, [[maybe_unused]] const char *name) { - if (value > max) { - BONGOCAT_LOG_WARNING("%s %d out of range [%d], clamping", name, value, max); - return (1u << 0); - } - return 0; - } - - static uint64_t config_validate_dimensions(config_t& config) { - uint64_t ret{0}; - ret |= config_clamp_int(config.cat_height, MIN_CAT_HEIGHT, MAX_CAT_HEIGHT, CAT_HEIGHT_KEY); - ret |= config_clamp_int(config.overlay_height, MIN_OVERLAY_HEIGHT, MAX_OVERLAY_HEIGHT, OVERLAY_HEIGHT_KEY); - ret |= config_clamp_int(config.cat_x_offset, MIN_OFFSET, MAX_OFFSET, CAT_X_OFFSET_KEY); - ret |= config_clamp_int(config.cat_y_offset, MIN_OFFSET, MAX_OFFSET, CAT_Y_OFFSET_KEY); - ret |= config_clamp_int(config.movement_radius, MIN_MOVEMENT_RADIUS, MAX_MOVEMENT_RADIUS, CAT_Y_OFFSET_KEY); - ret |= config_clamp_int(config.padding_x, 0, MAX_OFFSET, PADDING_X_KEY); - ret |= config_clamp_int(config.padding_y, 0, MAX_OFFSET, PADDING_Y_KEY); - ret |= config_clamp_int(config.screen_width, 0, MAX_OFFSET, SCREEN_WIDTH_KEY); - return ret; + if (config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { + config.custom_sprite_sheet_settings.feature_mirror_x_moving = + config.custom_sprite_sheet_settings.feature_mirror_x_moving ? 1 : 0; } - static uint64_t config_validate_timing(config_t& config) { - uint64_t ret{0}; - ret |= config_clamp_int(config.fps, MIN_FPS, MAX_FPS, FPS_KEY); - ret |= config_clamp_int(config.keypress_duration_ms, MIN_DURATION_MS, MAX_DURATION_MS, KEYPRESS_DURATION_KEY); - ret |= config_clamp_int(config.test_animation_duration_ms, 0, MAX_DURATION_MS, TEST_ANIMATION_DURATION_KEY); - ret |= config_clamp_int(config.animation_speed_ms, 0, MAX_DURATION_MS, ANIMATION_SPEED_KEY); - ret |= config_clamp_int(config.idle_sleep_timeout_sec, 0, MAX_SLEEP_TIMEOUT_SEC, IDLE_SLEEP_TIMEOUT_KEY); - ret |= config_clamp_int(config.input_fps, 0, MAX_FPS, INPUT_FPS_KEY); - ret |= config_clamp_int(config.movement_speed, 0, MAX_DURATION_MS, MOVEMENT_SPEED_KEY); - - // Validate interval (0 is allowed to disable) - if (config.test_animation_interval_sec < 0 || config.test_animation_interval_sec > MAX_INTERVAL_SEC) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%dsec], clamping", - TEST_ANIMATION_INTERVAL_KEY, config.test_animation_interval_sec, MAX_INTERVAL_SEC); - config.test_animation_interval_sec = (config.test_animation_interval_sec < 0) ? 0 : MAX_INTERVAL_SEC; - ret = (1u << 1); - } - if (config.animation_speed_ms < 0 || config.animation_speed_ms > MAX_INTERVAL_SEC*1000) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%dms], clamping", - ANIMATION_SPEED_KEY, config.test_animation_interval_sec, MAX_INTERVAL_SEC*1000); - config.animation_speed_ms = (config.animation_speed_ms < 0) ? 0 : MAX_INTERVAL_SEC*1000; - ret = (1u << 2); - } - return ret; + // clamp cols + ret |= config_clamp_int(config.custom_sprite_sheet_settings.idle_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_IDLE_FRAMES_KEY); + + ret |= config_clamp_int(config.custom_sprite_sheet_settings.boring_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_BORING_FRAMES_KEY); + + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_writing_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_START_WRITING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.writing_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_WRITING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_writing_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_END_WRITING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.happy_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_HAPPY_FRAMES_KEY); + + ret |= config_clamp_int(config.custom_sprite_sheet_settings.asleep_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_ASLEEP_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.sleep_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_SLEEP_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.wake_up_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_WAKE_UP_FRAMES_KEY); + + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_working_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_START_WORKING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.working_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_WORKING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_working_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_END_WORKING_FRAMES_KEY); + + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_moving_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_START_MOVING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.moving_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_MOVING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_moving_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_END_MOVING_FRAMES_KEY); + + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_running_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_START_RUNNING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.running_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, + CUSTOM_RUNNING_FRAMES_KEY); + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_running_frames, MIN_CUSTOM_FRAMES, + MAX_CUSTOM_FRAMES, CUSTOM_END_RUNNING_FRAMES_KEY); + + // clamp rows + if (config.custom_sprite_sheet_settings.idle_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.idle_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_IDLE_ROW_KEY); } - - static uint64_t config_validate_kpm(config_t& config) { - return config_clamp_int(config.happy_kpm, MIN_KPM, MAX_KPM, HAPPY_KPM_KEY); + if (config.custom_sprite_sheet_settings.boring_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.boring_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_BORING_ROW_KEY); } - - static uint64_t config_validate_update(config_t& config) { - uint64_t ret{0}; - - ret |= config_clamp_int(config.update_rate_ms, 0, MAX_UPDATE_RATE_MS, UPDATE_RATE_KEY); - ret |= config_clamp_double(config.cpu_threshold, 0, MAX_CPU_THRESHOLD, CPU_THRESHOLD_KEY); - ret |= config_clamp_double(config.cpu_running_factor, 0, MAX_CPU_RUNNING_FACTOR, CPU_RUNNING_FACTOR_KEY); - - return ret; + if (config.custom_sprite_sheet_settings.start_writing_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_writing_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_START_WRITING_ROW_KEY); } - - static uint64_t config_validate_custom(config_t& config) { - using namespace assets; - uint64_t ret{0}; - - if (config._custom) { - if (config.custom_sprite_sheet_settings.feature_toggle_writing_frames >= 0) { - config.custom_sprite_sheet_settings.feature_toggle_writing_frames = config.custom_sprite_sheet_settings.feature_toggle_writing_frames ? 1 : 0; - } - if (config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random >= 0) { - config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random = config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random ? 1 : 0; - } - if (config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { - config.custom_sprite_sheet_settings.feature_mirror_x_moving = config.custom_sprite_sheet_settings.feature_mirror_x_moving ? 1 : 0; - } - - // clamp cols - ret |= config_clamp_int(config.custom_sprite_sheet_settings.idle_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_IDLE_FRAMES_KEY); - - ret |= config_clamp_int(config.custom_sprite_sheet_settings.boring_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_BORING_FRAMES_KEY); - - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_writing_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_START_WRITING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.writing_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_WRITING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_writing_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_END_WRITING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.happy_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_HAPPY_FRAMES_KEY); - - ret |= config_clamp_int(config.custom_sprite_sheet_settings.asleep_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_ASLEEP_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.sleep_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_SLEEP_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.wake_up_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_WAKE_UP_FRAMES_KEY); - - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_working_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_START_WORKING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.working_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_WORKING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_working_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_END_WORKING_FRAMES_KEY); - - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_moving_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_START_MOVING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.moving_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_MOVING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_moving_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_END_MOVING_FRAMES_KEY); - - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_running_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_START_RUNNING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.running_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_RUNNING_FRAMES_KEY); - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_running_frames, MIN_CUSTOM_FRAMES, MAX_CUSTOM_FRAMES, CUSTOM_END_RUNNING_FRAMES_KEY); - - // clamp rows - if (config.custom_sprite_sheet_settings.idle_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.idle_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_IDLE_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.boring_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.boring_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_BORING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_writing_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_writing_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_START_WRITING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.writing_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.writing_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_WRITING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_writing_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_writing_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_END_WRITING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.happy_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.happy_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_HAPPY_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.asleep_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.asleep_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_ASLEEP_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.sleep_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.sleep_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_SLEEP_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.wake_up_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.wake_up_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_WAKE_UP_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_working_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_working_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_START_WORKING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.working_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.working_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_WORKING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_working_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_working_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_END_WORKING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_moving_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_moving_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_START_MOVING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.moving_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.moving_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_MOVING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_moving_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_moving_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_END_MOVING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_running_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_running_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_START_RUNNING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.running_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.running_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_RUNNING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_running_row_index >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_running_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, CUSTOM_END_RUNNING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.rows >= 0) { - ret |= config_clamp_int(config.custom_sprite_sheet_settings.rows, 1, MAX_CUSTOM_ROWS, CUSTOM_ROWS_KEY); - } - - - const int sprite_sheet_cols = get_custom_animation_settings_max_cols(config.custom_sprite_sheet_settings); - const int sprite_sheet_rows = get_custom_animation_settings_rows_count(config.custom_sprite_sheet_settings); - - if (sprite_sheet_cols <= 0) { - BONGOCAT_LOG_WARNING("custom sprite sheet has no columns"); - ret |= (1u << 3); - } - - // validate cols - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.idle_frames, sprite_sheet_cols, CUSTOM_IDLE_FRAMES_KEY); - - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.boring_frames, sprite_sheet_cols, CUSTOM_BORING_FRAMES_KEY); - - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_writing_frames, sprite_sheet_cols, CUSTOM_START_WRITING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.writing_frames, sprite_sheet_cols, CUSTOM_WRITING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_writing_frames, sprite_sheet_cols, CUSTOM_END_WRITING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.happy_frames, sprite_sheet_cols, CUSTOM_HAPPY_FRAMES_KEY); - - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.asleep_frames, sprite_sheet_cols, CUSTOM_ASLEEP_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.sleep_frames, sprite_sheet_cols, CUSTOM_SLEEP_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.wake_up_frames, sprite_sheet_cols, CUSTOM_WAKE_UP_FRAMES_KEY); - - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_working_frames, sprite_sheet_cols, CUSTOM_START_WORKING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.working_frames, sprite_sheet_cols, CUSTOM_WORKING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_working_frames, sprite_sheet_cols, CUSTOM_END_WORKING_FRAMES_KEY); - - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_moving_frames, sprite_sheet_cols, CUSTOM_START_MOVING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.moving_frames, sprite_sheet_cols, CUSTOM_MOVING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_moving_frames, sprite_sheet_cols, CUSTOM_END_MOVING_FRAMES_KEY); - - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_running_frames, sprite_sheet_cols, CUSTOM_START_RUNNING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.running_frames, sprite_sheet_cols, CUSTOM_RUNNING_FRAMES_KEY); - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_running_frames, sprite_sheet_cols, CUSTOM_END_RUNNING_FRAMES_KEY); - - // validate rows - if (sprite_sheet_rows > 0) { - if (config.custom_sprite_sheet_settings.idle_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.idle_row_index, sprite_sheet_rows-1, CUSTOM_IDLE_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.boring_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.boring_row_index, sprite_sheet_rows-1, CUSTOM_BORING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_writing_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_writing_row_index, sprite_sheet_rows-1, CUSTOM_START_WRITING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.writing_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.writing_row_index, sprite_sheet_rows-1, CUSTOM_WRITING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_writing_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_writing_row_index, sprite_sheet_rows-1, CUSTOM_END_WRITING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.happy_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.happy_row_index, sprite_sheet_rows-1, CUSTOM_HAPPY_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.asleep_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.asleep_row_index, sprite_sheet_rows-1, CUSTOM_ASLEEP_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.sleep_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.sleep_row_index, sprite_sheet_rows-1, CUSTOM_SLEEP_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.wake_up_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.wake_up_row_index, sprite_sheet_rows-1, CUSTOM_WAKE_UP_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_working_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_working_row_index, sprite_sheet_rows-1, CUSTOM_START_WORKING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.working_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.working_row_index, sprite_sheet_rows-1, CUSTOM_WORKING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_working_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_working_row_index, sprite_sheet_rows-1, CUSTOM_END_WORKING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_moving_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_moving_row_index, sprite_sheet_rows-1, CUSTOM_START_MOVING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.moving_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.moving_row_index, sprite_sheet_rows-1, CUSTOM_MOVING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_moving_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_moving_row_index, sprite_sheet_rows-1, CUSTOM_END_MOVING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.start_running_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_running_row_index, sprite_sheet_rows-1, CUSTOM_START_RUNNING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.running_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.running_row_index, sprite_sheet_rows-1, CUSTOM_RUNNING_ROW_KEY); - } - if (config.custom_sprite_sheet_settings.end_running_row_index >= 0) { - ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_running_row_index, sprite_sheet_rows-1, CUSTOM_END_RUNNING_ROW_KEY); - } - } else { - if (config.custom_sprite_sheet_settings.rows <= 0) { - BONGOCAT_LOG_WARNING("custom sprite sheet has no rows"); - ret |= (1u << 4); - } - } - - - // validate sprite sheet file - if (config.custom_sprite_sheet_filename != nullptr && strlen(config.custom_sprite_sheet_filename) != 0) { - constexpr size_t PNG_SIGNATURE_SIZE = 8; - constexpr unsigned char PNG_SIGNATURE[PNG_SIGNATURE_SIZE] = { - 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' - }; - - // Try to open the file - platform::FileDescriptor fd (open(config.custom_sprite_sheet_filename, O_RDONLY)); - if (fd._fd < 0) { - BONGOCAT_LOG_ERROR("Custom Sprite Sheet doesn't exist or can't be opened: %s", config.custom_sprite_sheet_filename); - ret |= (1u << 6); - return ret; - } - - struct stat st; - if (fstat(fd._fd, &st) < 0) { - BONGOCAT_LOG_ERROR("Custom Sprite Sheet can't be opened: %s", config.custom_sprite_sheet_filename); - ret |= (1u << 7); - return ret; - } - if (st.st_size == 0) { - BONGOCAT_LOG_ERROR("Custom Sprite Sheet is an empty file: %s", config.custom_sprite_sheet_filename); - ret |= (1u << 8); - return ret; - } - - unsigned char header[PNG_SIGNATURE_SIZE] = {0}; - const ssize_t n = read(fd._fd, header, PNG_SIGNATURE_SIZE); - if (n < static_cast(PNG_SIGNATURE_SIZE)) { - BONGOCAT_LOG_ERROR("Failed to read PNG header: %s", config.custom_sprite_sheet_filename); - ret |= (1u << 9); - return ret; - } - if (memcmp(header, PNG_SIGNATURE, PNG_SIGNATURE_SIZE) != 0) { - BONGOCAT_LOG_ERROR("Invalid PNG signature: %s", config.custom_sprite_sheet_filename); - ret |= (1u << 10); - return ret; - } - } else { - // empty custom_sprite_sheet_filename - BONGOCAT_LOG_WARNING("custom_sprite_sheet_filename is empty"); - ret |= (1u << 5); - } - - // validate frames - if (config.custom_sprite_sheet_settings.idle_frames <= 0) { - BONGOCAT_LOG_WARNING("custom sprite sheet needs at least an idle animation"); - ret |= (1u << 11); - } - - if (config.custom_sprite_sheet_settings.wake_up_frames > 0 && - (config.custom_sprite_sheet_settings.asleep_frames <= 0 || config.custom_sprite_sheet_settings.sleep_frames <= 0)) { - BONGOCAT_LOG_WARNING("custom sprite sheet has a wake up animation, but no sleep animation"); - //ret |= (1u << 12); - // to hard of an error in strict mode, just print warning - } - - if (config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0 && - (config.custom_sprite_sheet_settings.start_moving_frames <= 0 && config.custom_sprite_sheet_settings.moving_frames <= 0 && config.custom_sprite_sheet_settings.end_moving_frames <= 0)) { - BONGOCAT_LOG_WARNING("feature_mirror_x_moving for custom sprite sheet is used, but has no moving animation"); - //ret |= (1u << 13); - } - - if ((config.custom_sprite_sheet_settings.feature_toggle_writing_frames >= 0 || config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random >= 0) && - (config.custom_sprite_sheet_settings.start_writing_frames <= 0 && config.custom_sprite_sheet_settings.writing_frames <= 0 && config.custom_sprite_sheet_settings.end_writing_frames <= 0)) { - BONGOCAT_LOG_WARNING("feature_toggle_writing_frames for custom sprite sheet is used, but has no writing animation"); - //ret |= (1u << 14); - } - - if (config.enable_scheduled_sleep >= 0 && - (config.custom_sprite_sheet_settings.asleep_frames <= 0 && config.custom_sprite_sheet_settings.sleep_frames <= 0)) { - BONGOCAT_LOG_WARNING("enable_scheduled_sleep is enabled, but custom sprite sheet has no sleep animation"); - //ret |= (1u << 15); - } - if (config.happy_kpm >= 0 && - (config.custom_sprite_sheet_settings.happy_frames <= 0)) { - BONGOCAT_LOG_WARNING("happy_kpm is used, but custom sprite sheet has no happy animation"); - //ret |= (1u << 16); - } - } - - return ret; + if (config.custom_sprite_sheet_settings.writing_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.writing_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_WRITING_ROW_KEY); } - - static uint64_t config_validate_appearance(config_t& config) { - using namespace assets; - using namespace animation; - uint64_t ret{0}; - // Validate opacity - ret |= config_clamp_int(config.overlay_opacity, 0, 255, OVERLAY_OPACITY_KEY); - - switch (config.animation_sprite_sheet_layout) { - case config_animation_sprite_sheet_layout_t::None: - BONGOCAT_LOG_WARNING("Cant determine sprite sheet layout"); - /// @TODO: move validation error codes (1 << ..) into constants - ret |= (1u << 17); - break; - case config_animation_sprite_sheet_layout_t::Bongocat: - if constexpr (features::EnableBongocatEmbeddedAssets) { - // Validate animation index - assert(BONGOCAT_ANIMATIONS_COUNT <= INT_MAX); - if (config.animation_index < 0 || config.animation_index >= static_cast(BONGOCAT_ANIMATIONS_COUNT)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, BONGOCAT_ANIMATIONS_COUNT - 1); - config.animation_index = 0; - ret |= (1u << 18); - } - // Validate idle frame - assert(animation::BONGOCAT_NUM_FRAMES <= INT_MAX); - if (config.idle_frame < 0 || config.idle_frame >= static_cast(BONGOCAT_NUM_FRAMES)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - IDLE_FRAME_KEY, config.idle_frame, BONGOCAT_NUM_FRAMES - 1); - config.idle_frame = 0; - ret |= (1u << 19); - } - } - break; - case config_animation_sprite_sheet_layout_t::Dm: - if constexpr (features::EnableDmEmbeddedAssets) { - // Validate animation index - assert(DM_ANIMATIONS_COUNT <= INT_MAX); - if (config.animation_index < 0 || config.animation_index >= static_cast(DM_ANIMATIONS_COUNT)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, assets::DM_ANIMATIONS_COUNT - 1); - config.animation_index = 0; - ret |= (1u << 20); - } - // Validate idle frame - assert(animation::MAX_DIGIMON_FRAMES <= INT_MAX); - if (config.idle_frame < 0 || config.idle_frame >= static_cast(animation::MAX_DIGIMON_FRAMES)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - IDLE_FRAME_KEY, config.idle_frame, animation::MAX_DIGIMON_FRAMES - 1); - config.idle_frame = 0; - ret |= (1u << 21); - } - } - break; - case config_animation_sprite_sheet_layout_t::Pkmn: - if constexpr (features::EnablePkmnEmbeddedAssets) { - // Validate animation index - assert(DM_ANIMATIONS_COUNT <= INT_MAX); - if (config.animation_index < 0 || config.animation_index >= static_cast(PKMN_ANIMATIONS_COUNT)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, assets::PKMN_ANIMATIONS_COUNT - 1); - config.animation_index = 0; - ret |= (1u << 22); - } - // Validate idle frame - assert(animation::MAX_DIGIMON_FRAMES <= INT_MAX); - if (config.idle_frame < 0 || config.idle_frame >= static_cast(MAX_PKMN_FRAMES)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - IDLE_FRAME_KEY, config.idle_frame, MAX_PKMN_FRAMES - 1); - config.idle_frame = 0; - ret |= (1u << 23); - } - } - break; - case config_animation_sprite_sheet_layout_t::MsAgent: - if constexpr (features::EnableMsAgentEmbeddedAssets) { - // Validate animation index - assert(assets::MS_AGENTS_ANIMATIONS_COUNT <= INT_MAX); - if (config.animation_index < 0 || config.animation_index >= static_cast(MS_AGENTS_ANIMATIONS_COUNT)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, MS_AGENTS_ANIMATIONS_COUNT - 1); - config.animation_index = 0; - ret |= (1u << 24); - } - // Validate idle frame - assert(assets::MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES <= INT_MAX); - if (config.idle_frame < 0 || config.idle_frame >= static_cast(MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - IDLE_FRAME_KEY, config.idle_frame, MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES - 1); - config.idle_frame = 0; - ret |= (1u << 25); - } - } - break; - case config_animation_sprite_sheet_layout_t::Custom: - assert(CUSTOM_ANIM_INDEX <= INT_MAX); - assert(MAX_MISC_ANIM_INDEX <= INT_MAX); - if constexpr (features::EnableCustomSpriteSheetsAssets) { - if (config._custom) { - assert(config.animation_custom_set == config_animation_custom_set_t::custom); - // Validate animation index - if (config.animation_index < 0 || config.animation_index > static_cast(CUSTOM_ANIM_INDEX)) { - BONGOCAT_LOG_WARNING("%s %d out of range [%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, CUSTOM_ANIM_INDEX); - config.animation_index = 0; - ret |= (1u << 26); - } - /// @TODO: validate max (idle) frames - } - } - if (!config._custom) { - switch (config.animation_custom_set) { - case config_animation_custom_set_t::None: - break; - case config_animation_custom_set_t::misc: - if constexpr (features::EnableMiscEmbeddedAssets) { - // Validate animation index - if (config.animation_index < 0 || config.animation_index > static_cast(MAX_MISC_ANIM_INDEX)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, MAX_MISC_ANIM_INDEX); - config.animation_index = 0; - ret |= (1u << 27); - } - // Validate idle frame - assert(assets::MISC_MAX_SPRITE_SHEET_COL_FRAMES <= INT_MAX); - if (config.idle_frame < 0 || config.idle_frame >= static_cast(MISC_MAX_SPRITE_SHEET_COL_FRAMES)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - IDLE_FRAME_KEY, config.idle_frame, assets::MISC_MAX_SPRITE_SHEET_COL_FRAMES - 1); - config.idle_frame = 0; - ret |= (1u << 28); - } - } - break; - case config_animation_custom_set_t::pmd: - if constexpr (features::EnablePmdEmbeddedAssets) { - assert(assets::PMD_ANIM_COUNT <= INT_MAX); - // Validate animation index - if (config.animation_index < 0 || config.animation_index >= static_cast(PMD_ANIM_COUNT)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - ANIMATION_INDEX_KEY, config.animation_index, PMD_ANIM_COUNT - 1); - config.animation_index = 0; - ret |= (1uz << 29); - } - // Validate idle frame - if (config.idle_frame < 0 || config.idle_frame >= static_cast(PMD_ANIM_COUNT)) { - BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", - IDLE_FRAME_KEY, config.idle_frame, assets::PMD_ANIM_COUNT - 1); - config.idle_frame = 0; - ret |= (1uz << 30); - } - } - break; - case config_animation_custom_set_t::custom: - break; - } - } - break; - /// @NOTE(assets): 5. add animation_index validation - } - return ret; + if (config.custom_sprite_sheet_settings.end_writing_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_writing_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_END_WRITING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.happy_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.happy_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_HAPPY_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.asleep_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.asleep_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_ASLEEP_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.sleep_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.sleep_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_SLEEP_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.wake_up_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.wake_up_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_WAKE_UP_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_working_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_working_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_START_WORKING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.working_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.working_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_WORKING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_working_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_working_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_END_WORKING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_moving_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_moving_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_START_MOVING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.moving_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.moving_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_MOVING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_moving_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_moving_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_END_MOVING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_running_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.start_running_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_START_RUNNING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.running_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.running_row_index, MIN_CUSTOM_ROWS, MAX_CUSTOM_ROWS, + CUSTOM_RUNNING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_running_row_index >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.end_running_row_index, MIN_CUSTOM_ROWS, + MAX_CUSTOM_ROWS, CUSTOM_END_RUNNING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.rows >= 0) { + ret |= config_clamp_int(config.custom_sprite_sheet_settings.rows, 1, MAX_CUSTOM_ROWS, CUSTOM_ROWS_KEY); } - static uint64_t config_validate_enums(config_t& config) { - uint64_t ret{0}; - // Validate layer - if (config.layer != layer_type_t::LAYER_BACKGROUND && - config.layer != layer_type_t::LAYER_BOTTOM && - config.layer != layer_type_t::LAYER_TOP && - config.layer != layer_type_t::LAYER_OVERLAY) { - BONGOCAT_LOG_WARNING("Invalid layer %d, resetting to top", config.layer); - config.layer = layer_type_t::LAYER_TOP; - ret |= (1uz << 31); - } - - // Validate overlay_position - if (config.overlay_position != overlay_position_t::POSITION_TOP && config.overlay_position != overlay_position_t::POSITION_BOTTOM) { - BONGOCAT_LOG_WARNING("Invalid %s %d, resetting to top", OVERLAY_OPACITY_KEY, config.overlay_position); - config.overlay_position = overlay_position_t::POSITION_TOP; - ret |= (1uz << 32); - } + const int sprite_sheet_cols = get_custom_animation_settings_max_cols(config.custom_sprite_sheet_settings); + const int sprite_sheet_rows = get_custom_animation_settings_rows_count(config.custom_sprite_sheet_settings); - // Validate cat_align - if (config.cat_align != align_type_t::ALIGN_CENTER && config.cat_align != align_type_t::ALIGN_LEFT && config.cat_align != align_type_t::ALIGN_RIGHT) { - BONGOCAT_LOG_WARNING("Invalid %s %d, resetting to center", CAT_ALIGN_KEY, config.cat_align); - config.cat_align = align_type_t::ALIGN_CENTER; - ret |= (1uz << 33); - } + if (sprite_sheet_cols <= 0) { + BONGOCAT_LOG_WARNING("custom sprite sheet has no columns"); + ret |= (1u << 3); + } - return ret; + // validate cols + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.idle_frames, sprite_sheet_cols, + CUSTOM_IDLE_FRAMES_KEY); + + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.boring_frames, sprite_sheet_cols, + CUSTOM_BORING_FRAMES_KEY); + + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_writing_frames, sprite_sheet_cols, + CUSTOM_START_WRITING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.writing_frames, sprite_sheet_cols, + CUSTOM_WRITING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_writing_frames, sprite_sheet_cols, + CUSTOM_END_WRITING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.happy_frames, sprite_sheet_cols, + CUSTOM_HAPPY_FRAMES_KEY); + + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.asleep_frames, sprite_sheet_cols, + CUSTOM_ASLEEP_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.sleep_frames, sprite_sheet_cols, + CUSTOM_SLEEP_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.wake_up_frames, sprite_sheet_cols, + CUSTOM_WAKE_UP_FRAMES_KEY); + + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_working_frames, sprite_sheet_cols, + CUSTOM_START_WORKING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.working_frames, sprite_sheet_cols, + CUSTOM_WORKING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_working_frames, sprite_sheet_cols, + CUSTOM_END_WORKING_FRAMES_KEY); + + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_moving_frames, sprite_sheet_cols, + CUSTOM_START_MOVING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.moving_frames, sprite_sheet_cols, + CUSTOM_MOVING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_moving_frames, sprite_sheet_cols, + CUSTOM_END_MOVING_FRAMES_KEY); + + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_running_frames, sprite_sheet_cols, + CUSTOM_START_RUNNING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.running_frames, sprite_sheet_cols, + CUSTOM_RUNNING_FRAMES_KEY); + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_running_frames, sprite_sheet_cols, + CUSTOM_END_RUNNING_FRAMES_KEY); + + // validate rows + if (sprite_sheet_rows > 0) { + if (config.custom_sprite_sheet_settings.idle_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.idle_row_index, sprite_sheet_rows - 1, + CUSTOM_IDLE_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.boring_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.boring_row_index, sprite_sheet_rows - 1, + CUSTOM_BORING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_writing_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_writing_row_index, + sprite_sheet_rows - 1, CUSTOM_START_WRITING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.writing_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.writing_row_index, sprite_sheet_rows - 1, + CUSTOM_WRITING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_writing_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_writing_row_index, sprite_sheet_rows - 1, + CUSTOM_END_WRITING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.happy_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.happy_row_index, sprite_sheet_rows - 1, + CUSTOM_HAPPY_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.asleep_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.asleep_row_index, sprite_sheet_rows - 1, + CUSTOM_ASLEEP_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.sleep_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.sleep_row_index, sprite_sheet_rows - 1, + CUSTOM_SLEEP_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.wake_up_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.wake_up_row_index, sprite_sheet_rows - 1, + CUSTOM_WAKE_UP_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_working_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_working_row_index, + sprite_sheet_rows - 1, CUSTOM_START_WORKING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.working_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.working_row_index, sprite_sheet_rows - 1, + CUSTOM_WORKING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_working_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_working_row_index, sprite_sheet_rows - 1, + CUSTOM_END_WORKING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_moving_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_moving_row_index, + sprite_sheet_rows - 1, CUSTOM_START_MOVING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.moving_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.moving_row_index, sprite_sheet_rows - 1, + CUSTOM_MOVING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_moving_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_moving_row_index, sprite_sheet_rows - 1, + CUSTOM_END_MOVING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.start_running_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.start_running_row_index, + sprite_sheet_rows - 1, CUSTOM_START_RUNNING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.running_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.running_row_index, sprite_sheet_rows - 1, + CUSTOM_RUNNING_ROW_KEY); + } + if (config.custom_sprite_sheet_settings.end_running_row_index >= 0) { + ret |= config_validate_max_int(config.custom_sprite_sheet_settings.end_running_row_index, sprite_sheet_rows - 1, + CUSTOM_END_RUNNING_ROW_KEY); + } + } else { + if (config.custom_sprite_sheet_settings.rows <= 0) { + BONGOCAT_LOG_WARNING("custom sprite sheet has no rows"); + ret |= (1u << 4); + } } - static uint64_t config_validate_time(config_t& config) { - uint64_t ret{0}; - if (config.enable_scheduled_sleep) { - const int begin_minutes = config.sleep_begin.hour * 60 + config.sleep_begin.min; - const int end_minutes = config.sleep_end.hour * 60 + config.sleep_end.min; + // validate sprite sheet file + if (config.custom_sprite_sheet_filename != nullptr && strlen(config.custom_sprite_sheet_filename) != 0) { + constexpr size_t PNG_SIGNATURE_SIZE = 8; + constexpr unsigned char PNG_SIGNATURE[PNG_SIGNATURE_SIZE] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; + + // Try to open the file + platform::FileDescriptor fd(open(config.custom_sprite_sheet_filename, O_RDONLY)); + if (fd._fd < 0) { + BONGOCAT_LOG_ERROR("Custom Sprite Sheet doesn't exist or can't be opened: %s", + config.custom_sprite_sheet_filename); + ret |= (1u << 6); + return ret; + } - if (begin_minutes == end_minutes) { - BONGOCAT_LOG_WARNING("Sleep mode is enabled, but time is equal: %02d:%02d, disable sleep mode", config.sleep_begin.hour, config.sleep_begin.min); + struct stat st; + if (fstat(fd._fd, &st) < 0) { + BONGOCAT_LOG_ERROR("Custom Sprite Sheet can't be opened: %s", config.custom_sprite_sheet_filename); + ret |= (1u << 7); + return ret; + } + if (st.st_size == 0) { + BONGOCAT_LOG_ERROR("Custom Sprite Sheet is an empty file: %s", config.custom_sprite_sheet_filename); + ret |= (1u << 8); + return ret; + } - config.enable_scheduled_sleep = 0; - //config.sleep_begin.hour = 0; - //config.sleep_begin.min = 0; - //config.sleep_end.hour = 0; - //config.sleep_end.min = 0; - ret |= (1uz << 34); - } - } + unsigned char header[PNG_SIGNATURE_SIZE] = {0}; + const ssize_t n = read(fd._fd, header, PNG_SIGNATURE_SIZE); + if (n < static_cast(PNG_SIGNATURE_SIZE)) { + BONGOCAT_LOG_ERROR("Failed to read PNG header: %s", config.custom_sprite_sheet_filename); + ret |= (1u << 9); + return ret; + } + if (memcmp(header, PNG_SIGNATURE, PNG_SIGNATURE_SIZE) != 0) { + BONGOCAT_LOG_ERROR("Invalid PNG signature: %s", config.custom_sprite_sheet_filename); + ret |= (1u << 10); return ret; + } + } else { + // empty custom_sprite_sheet_filename + BONGOCAT_LOG_WARNING("custom_sprite_sheet_filename is empty"); + ret |= (1u << 5); } - static bongocat_error_t config_validate(config_t& config) { - uint64_t ret{0}; - // Normalize boolean values - config.enable_debug = config.enable_debug ? 1 : 0; - config.invert_color = config.invert_color ? 1 : 0; - config.idle_animation = config.idle_animation ? 1 : 0; - config.enable_scheduled_sleep = config.enable_scheduled_sleep ? 1 : 0; - config.mirror_x = config.mirror_x ? 1 : 0; - config.mirror_y = config.mirror_y ? 1 : 0; - config.randomize_index = config.randomize_index ? 1 : 0; - config.randomize_on_reload = config.randomize_on_reload ? 1 : 0; - config.enable_antialiasing = config.enable_antialiasing ? 1 : 0; - config.enable_movement_debug = config.enable_movement_debug ? 1 : 0; - config.enable_hand_mapping = config.enable_hand_mapping ? 1 : 0; - - ret |= config_validate_dimensions(config); - ret |= config_validate_timing(config); - ret |= config_validate_appearance(config); - ret |= config_validate_enums(config); - ret |= config_validate_time(config); - ret |= config_validate_kpm(config); - ret |= config_validate_update(config); - ret |= config_validate_custom(config); - - if (config._strict) { - if (ret != 0) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to load configuration in strict mode: %x", ret); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - } - return bongocat_error_t::BONGOCAT_SUCCESS; + // validate frames + if (config.custom_sprite_sheet_settings.idle_frames <= 0) { + BONGOCAT_LOG_WARNING("custom sprite sheet needs at least an idle animation"); + ret |= (1u << 11); } - // ============================================================================= - // DEVICE MANAGEMENT MODULE - // ============================================================================= - - static bongocat_error_t config_add_keyboard_device(config_t& config, const char *device_path) { - BONGOCAT_CHECK_NULL(device_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - assert(config.num_keyboard_devices >= 0 && config.num_keyboard_devices < INT_MAX-1); - - const int old_num_keyboard_devices = config.num_keyboard_devices; - - assert(input::MAX_INPUT_DEVICES <= INT_MAX); - if (old_num_keyboard_devices >= static_cast(input::MAX_INPUT_DEVICES)) { - BONGOCAT_LOG_WARNING("Can not add more devices from config, max. reach: %d", input::MAX_INPUT_DEVICES); - return config._strict ? bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM : bongocat_error_t::BONGOCAT_SUCCESS; - } - const int new_num_keyboard_devices = old_num_keyboard_devices + 1; - assert(new_num_keyboard_devices >= 0); - assert(static_cast(new_num_keyboard_devices) <= input::MAX_INPUT_DEVICES); - - // Add new device path - config.keyboard_devices[old_num_keyboard_devices] = strdup(device_path); - if (!config.keyboard_devices[old_num_keyboard_devices]) { - // free new copied strings - for (int i = 0; i < old_num_keyboard_devices; i++) { - if (config.keyboard_devices[i]) ::free(config.keyboard_devices[i]); - config.keyboard_devices[i] = nullptr; - } - config.num_keyboard_devices = old_num_keyboard_devices; - BONGOCAT_LOG_ERROR("Failed to copy new keyboard device path"); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - - // update new size - config.num_keyboard_devices = new_num_keyboard_devices; - - return bongocat_error_t::BONGOCAT_SUCCESS; + if (config.custom_sprite_sheet_settings.wake_up_frames > 0 && + (config.custom_sprite_sheet_settings.asleep_frames <= 0 || + config.custom_sprite_sheet_settings.sleep_frames <= 0)) { + BONGOCAT_LOG_WARNING("custom sprite sheet has a wake up animation, but no sleep animation"); + // ret |= (1u << 12); + // to hard of an error in strict mode, just print warning } - static void config_cleanup_devices(config_t& config) { - assert(config.num_keyboard_devices >= 0); - for (size_t i = 0; i < input::MAX_INPUT_DEVICES; i++) { - if (i < static_cast(config.num_keyboard_devices)) { - if (config.keyboard_devices[i]) ::free(config.keyboard_devices[i]); - } - config.keyboard_devices[i] = nullptr; - } - config.num_keyboard_devices = 0; + if (config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0 && + (config.custom_sprite_sheet_settings.start_moving_frames <= 0 && + config.custom_sprite_sheet_settings.moving_frames <= 0 && + config.custom_sprite_sheet_settings.end_moving_frames <= 0)) { + BONGOCAT_LOG_WARNING("feature_mirror_x_moving for custom sprite sheet is used, but has no moving animation"); + // ret |= (1u << 13); } - // ============================================================================= - // CONFIGURATION PARSING MODULE - // ============================================================================= - - static char* config_trim_str(char *key) { - char *key_start = key; - while (*key_start == ' ' || *key_start == '\t') key_start++; - - char *key_end = key_start + strlen(key_start) - 1; - while (key_end > key_start && (*key_end == ' ' || *key_end == '\t')) { - *key_end = '\0'; - key_end--; - } - - return key_start; + if ((config.custom_sprite_sheet_settings.feature_toggle_writing_frames >= 0 || + config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random >= 0) && + (config.custom_sprite_sheet_settings.start_writing_frames <= 0 && + config.custom_sprite_sheet_settings.writing_frames <= 0 && + config.custom_sprite_sheet_settings.end_writing_frames <= 0)) { + BONGOCAT_LOG_WARNING( + "feature_toggle_writing_frames for custom sprite sheet is used, but has no writing animation"); + // ret |= (1u << 14); } - static bongocat_error_t config_parse_integer_key(config_t& config, const char *key, const char *value) { - errno = 0; - char* endptr_int = nullptr; - const int int_value = static_cast(strtol(value, &endptr_int, 10)); - if (errno != 0 || endptr_int == value || *endptr_int != '\0') { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - - if (strcmp(key, CAT_X_OFFSET_KEY) == 0) { - config.cat_x_offset = int_value; - } else if (strcmp(key, CAT_Y_OFFSET_KEY) == 0) { - config.cat_y_offset = int_value; - } else if (strcmp(key, CAT_HEIGHT_KEY) == 0) { - config.cat_height = int_value; - } else if (strcmp(key, OVERLAY_HEIGHT_KEY) == 0) { - config.overlay_height = int_value; - } else if (strcmp(key, IDLE_FRAME_KEY) == 0) { - config.idle_frame = int_value; - } else if (strcmp(key, KEYPRESS_DURATION_KEY) == 0) { - config.keypress_duration_ms = int_value; - } else if (strcmp(key, TEST_ANIMATION_DURATION_KEY) == 0) { - config.test_animation_duration_ms = int_value; - } else if (strcmp(key, TEST_ANIMATION_INTERVAL_KEY) == 0) { - config.test_animation_interval_sec = int_value; - } else if (strcmp(key, FPS_KEY) == 0) { - config.fps = int_value; - } else if (strcmp(key, OVERLAY_OPACITY_KEY) == 0) { - config.overlay_opacity = int_value; - } else if (strcmp(key, MIRROR_X_KEY) == 0) { - config.mirror_x = int_value; - } else if (strcmp(key, MIRROR_Y_KEY) == 0) { - config.mirror_y = int_value; - } else if (strcmp(key, ENABLE_ANTIALIASING_KEY) == 0) { - config.enable_antialiasing = int_value; - } else if (strcmp(key, ENABLE_DEBUG_KEY) == 0) { - config.enable_debug = int_value; - } else if (strcmp(key, ANIMATION_INDEX_KEY) == 0) { - config.animation_index = int_value; - } else if (strcmp(key, INVERT_COLOR_KEY) == 0) { - config.invert_color = int_value; - } else if (strcmp(key, PADDING_X_KEY) == 0) { - config.padding_x = int_value; - } else if (strcmp(key, PADDING_Y_KEY) == 0) { - config.padding_y = int_value; - } else if (strcmp(key, ENABLE_SCHEDULED_SLEEP_KEY) == 0) { - config.enable_scheduled_sleep = int_value; - } else if (strcmp(key, IDLE_SLEEP_TIMEOUT_KEY) == 0) { - config.idle_sleep_timeout_sec = int_value; - } else if (strcmp(key, HAPPY_KPM_KEY) == 0) { - config.happy_kpm = int_value; - } else if (strcmp(key, ANIMATION_SPEED_KEY) == 0) { - config.animation_speed_ms = int_value; - } else if (strcmp(key, IDLE_ANIMATION_KEY) == 0) { - config.idle_animation = int_value; - } else if (strcmp(key, INPUT_FPS_KEY) == 0) { - config.input_fps = int_value; - } else if (strcmp(key, RANDOM_KEY) == 0) { - config.randomize_index = int_value; - } else if (strcmp(key, RANDOM_ON_RELOAD_KEY) == 0) { - config.randomize_on_reload = int_value; - } else if (strcmp(key, UPDATE_RATE_KEY) == 0) { - config.update_rate_ms = int_value; - } else if (strcmp(key, CPU_THRESHOLD_KEY) == 0) { - config.cpu_threshold = int_value; - } else if (strcmp(key, CPU_RUNNING_FACTOR_KEY) == 0) { - config.cpu_running_factor = int_value; - } else if (strcmp(key, MOVEMENT_RADIUS_KEY) == 0) { - config.movement_radius = int_value; - } else if (strcmp(key, ENABLE_MOVEMENT_DEBUG_KEY) == 0) { - config.enable_movement_debug = int_value; - } else if (strcmp(key, MOVEMENT_SPEED_KEY) == 0) { - config.movement_speed = int_value; - } else if (strcmp(key, MOVEMENT_WAIT_FACTOR_KEY) == 0) { - config.movement_wait_factor = int_value; - } else if (strcmp(key, SCREEN_WIDTH_KEY) == 0) { - config.screen_width = int_value; - } else if (strcmp(key, ENABLE_HAND_MAPPING_KEY) == 0) { - config.enable_hand_mapping = int_value; - } else if (strcmp(key, CUSTOM_IDLE_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.idle_frames = int_value; - } else if (strcmp(key, CUSTOM_BORING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.boring_frames = int_value; - } else if (strcmp(key, CUSTOM_START_WRITING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.start_writing_frames = int_value; - } else if (strcmp(key, CUSTOM_WRITING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.writing_frames = int_value; - } else if (strcmp(key, CUSTOM_END_WRITING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.end_writing_frames = int_value; - } else if (strcmp(key, CUSTOM_HAPPY_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.happy_frames = int_value; - } else if (strcmp(key, CUSTOM_ASLEEP_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.asleep_frames = int_value; - } else if (strcmp(key, CUSTOM_SLEEP_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.sleep_frames = int_value; - } else if (strcmp(key, CUSTOM_WAKE_UP_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.wake_up_frames = int_value; - } else if (strcmp(key, CUSTOM_START_WORKING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.start_working_frames = int_value; - } else if (strcmp(key, CUSTOM_WORKING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.working_frames = int_value; - } else if (strcmp(key, CUSTOM_END_WORKING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.end_working_frames = int_value; - } else if (strcmp(key, CUSTOM_START_MOVING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.start_moving_frames = int_value; - } else if (strcmp(key, CUSTOM_MOVING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.moving_frames = int_value; - } else if (strcmp(key, CUSTOM_END_MOVING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.end_moving_frames = int_value; - } else if (strcmp(key, CUSTOM_START_RUNNING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.start_running_frames = int_value; - } else if (strcmp(key, CUSTOM_RUNNING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.running_frames = int_value; - } else if (strcmp(key, CUSTOM_END_RUNNING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.end_running_frames = int_value; - } else if (strcmp(key, CUSTOM_TOGGLE_WRITING_FRAMES_KEY) == 0) { - config.custom_sprite_sheet_settings.feature_toggle_writing_frames = int_value; - } else if (strcmp(key, CUSTOM_TOGGLE_WRITING_FRAMES_RANDOM_KEY) == 0) { - config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random = int_value; - } else if (strcmp(key, CUSTOM_MIRROR_X_MOVING_KEY) == 0) { - config.custom_sprite_sheet_settings.feature_mirror_x_moving = int_value; - } else if (strcmp(key, CUSTOM_IDLE_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.idle_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_BORING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.boring_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_START_WRITING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.start_writing_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_WRITING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.writing_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_END_WRITING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.end_writing_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_HAPPY_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.happy_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_ASLEEP_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.asleep_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_SLEEP_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.sleep_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_WAKE_UP_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.wake_up_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_START_WORKING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.start_working_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_WORKING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.working_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_END_WORKING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.end_working_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_START_MOVING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.start_moving_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_MOVING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.moving_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_END_MOVING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.end_moving_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_START_RUNNING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.start_running_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_RUNNING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.running_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_END_RUNNING_ROW_KEY) == 0) { - config.custom_sprite_sheet_settings.end_running_row_index = int_value - 1; - } else if (strcmp(key, CUSTOM_ROWS_KEY) == 0) { - config.custom_sprite_sheet_settings.rows = int_value; - } else { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + if (config.enable_scheduled_sleep >= 0 && (config.custom_sprite_sheet_settings.asleep_frames <= 0 && + config.custom_sprite_sheet_settings.sleep_frames <= 0)) { + BONGOCAT_LOG_WARNING("enable_scheduled_sleep is enabled, but custom sprite sheet has no sleep animation"); + // ret |= (1u << 15); + } + if (config.happy_kpm >= 0 && (config.custom_sprite_sheet_settings.happy_frames <= 0)) { + BONGOCAT_LOG_WARNING("happy_kpm is used, but custom sprite sheet has no happy animation"); + // ret |= (1u << 16); + } + } + + return ret; +} + +static uint64_t config_validate_appearance(config_t& config) { + using namespace assets; + using namespace animation; + uint64_t ret{0}; + // Validate opacity + ret |= config_clamp_int(config.overlay_opacity, 0, 255, OVERLAY_OPACITY_KEY); + + switch (config.animation_sprite_sheet_layout) { + case config_animation_sprite_sheet_layout_t::None: + BONGOCAT_LOG_WARNING("Cant determine sprite sheet layout"); + /// @TODO: move validation error codes (1 << ..) into constants + ret |= (1u << 17); + break; + case config_animation_sprite_sheet_layout_t::Bongocat: + if constexpr (features::EnableBongocatEmbeddedAssets) { + // Validate animation index + assert(BONGOCAT_ANIMATIONS_COUNT <= INT_MAX); + if (config.animation_index < 0 || config.animation_index >= static_cast(BONGOCAT_ANIMATIONS_COUNT)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", ANIMATION_INDEX_KEY, config.animation_index, + BONGOCAT_ANIMATIONS_COUNT - 1); + config.animation_index = 0; + ret |= (1u << 18); + } + // Validate idle frame + assert(animation::BONGOCAT_NUM_FRAMES <= INT_MAX); + if (config.idle_frame < 0 || config.idle_frame >= static_cast(BONGOCAT_NUM_FRAMES)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", IDLE_FRAME_KEY, config.idle_frame, + BONGOCAT_NUM_FRAMES - 1); + config.idle_frame = 0; + ret |= (1u << 19); + } + } + break; + case config_animation_sprite_sheet_layout_t::Dm: + if constexpr (features::EnableDmEmbeddedAssets) { + // Validate animation index + assert(DM_ANIMATIONS_COUNT <= INT_MAX); + if (config.animation_index < 0 || config.animation_index >= static_cast(DM_ANIMATIONS_COUNT)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", ANIMATION_INDEX_KEY, config.animation_index, + assets::DM_ANIMATIONS_COUNT - 1); + config.animation_index = 0; + ret |= (1u << 20); + } + // Validate idle frame + assert(animation::MAX_DIGIMON_FRAMES <= INT_MAX); + if (config.idle_frame < 0 || config.idle_frame >= static_cast(animation::MAX_DIGIMON_FRAMES)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", IDLE_FRAME_KEY, config.idle_frame, + animation::MAX_DIGIMON_FRAMES - 1); + config.idle_frame = 0; + ret |= (1u << 21); + } + } + break; + case config_animation_sprite_sheet_layout_t::Pkmn: + if constexpr (features::EnablePkmnEmbeddedAssets) { + // Validate animation index + assert(DM_ANIMATIONS_COUNT <= INT_MAX); + if (config.animation_index < 0 || config.animation_index >= static_cast(PKMN_ANIMATIONS_COUNT)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", ANIMATION_INDEX_KEY, config.animation_index, + assets::PKMN_ANIMATIONS_COUNT - 1); + config.animation_index = 0; + ret |= (1u << 22); + } + // Validate idle frame + assert(animation::MAX_DIGIMON_FRAMES <= INT_MAX); + if (config.idle_frame < 0 || config.idle_frame >= static_cast(MAX_PKMN_FRAMES)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", IDLE_FRAME_KEY, config.idle_frame, + MAX_PKMN_FRAMES - 1); + config.idle_frame = 0; + ret |= (1u << 23); + } + } + break; + case config_animation_sprite_sheet_layout_t::MsAgent: + if constexpr (features::EnableMsAgentEmbeddedAssets) { + // Validate animation index + assert(assets::MS_AGENTS_ANIMATIONS_COUNT <= INT_MAX); + if (config.animation_index < 0 || config.animation_index >= static_cast(MS_AGENTS_ANIMATIONS_COUNT)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", ANIMATION_INDEX_KEY, config.animation_index, + MS_AGENTS_ANIMATIONS_COUNT - 1); + config.animation_index = 0; + ret |= (1u << 24); + } + // Validate idle frame + assert(assets::MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES <= INT_MAX); + if (config.idle_frame < 0 || config.idle_frame >= static_cast(MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", IDLE_FRAME_KEY, config.idle_frame, + MS_AGENT_MAX_SPRITE_SHEET_COL_FRAMES - 1); + config.idle_frame = 0; + ret |= (1u << 25); + } + } + break; + case config_animation_sprite_sheet_layout_t::Custom: + assert(CUSTOM_ANIM_INDEX <= INT_MAX); + assert(MAX_MISC_ANIM_INDEX <= INT_MAX); + if constexpr (features::EnableCustomSpriteSheetsAssets) { + if (config._custom) { + assert(config.animation_custom_set == config_animation_custom_set_t::custom); + // Validate animation index + if (config.animation_index < 0 || config.animation_index > static_cast(CUSTOM_ANIM_INDEX)) { + BONGOCAT_LOG_WARNING("%s %d out of range [%d], resetting to 0", ANIMATION_INDEX_KEY, config.animation_index, + CUSTOM_ANIM_INDEX); + config.animation_index = 0; + ret |= (1u << 26); } - - return bongocat_error_t::BONGOCAT_SUCCESS; + /// @TODO: validate max (idle) frames + } } - - static bongocat_error_t config_parse_double_key(config_t& config, const char *key, const char *value) { - errno = 0; - char* endptr_double = nullptr; - const double double_value = strtod(value, &endptr_double); - if (errno != 0 || endptr_double == value || *endptr_double != '\0') { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + if (!config._custom) { + switch (config.animation_custom_set) { + case config_animation_custom_set_t::None: + break; + case config_animation_custom_set_t::misc: + if constexpr (features::EnableMiscEmbeddedAssets) { + // Validate animation index + if (config.animation_index < 0 || config.animation_index > static_cast(MAX_MISC_ANIM_INDEX)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", ANIMATION_INDEX_KEY, + config.animation_index, MAX_MISC_ANIM_INDEX); + config.animation_index = 0; + ret |= (1u << 27); + } + // Validate idle frame + assert(assets::MISC_MAX_SPRITE_SHEET_COL_FRAMES <= INT_MAX); + if (config.idle_frame < 0 || config.idle_frame >= static_cast(MISC_MAX_SPRITE_SHEET_COL_FRAMES)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", IDLE_FRAME_KEY, config.idle_frame, + assets::MISC_MAX_SPRITE_SHEET_COL_FRAMES - 1); + config.idle_frame = 0; + ret |= (1u << 28); + } } - - if (strcmp(key, CPU_THRESHOLD_KEY) == 0) { - config.cpu_threshold = double_value; - } else if (strcmp(key, CPU_RUNNING_FACTOR_KEY) == 0) { - config.cpu_running_factor = double_value; - } else if (strcmp(key, MOVEMENT_WAIT_FACTOR_KEY) == 0) { - config.movement_wait_factor = double_value; - } else { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + break; + case config_animation_custom_set_t::pmd: + if constexpr (features::EnablePmdEmbeddedAssets) { + assert(assets::PMD_ANIM_COUNT <= INT_MAX); + // Validate animation index + if (config.animation_index < 0 || config.animation_index >= static_cast(PMD_ANIM_COUNT)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", ANIMATION_INDEX_KEY, + config.animation_index, PMD_ANIM_COUNT - 1); + config.animation_index = 0; + ret |= (1uz << 29); + } + // Validate idle frame + if (config.idle_frame < 0 || config.idle_frame >= static_cast(PMD_ANIM_COUNT)) { + BONGOCAT_LOG_WARNING("%s %d out of range [0-%d], resetting to 0", IDLE_FRAME_KEY, config.idle_frame, + assets::PMD_ANIM_COUNT - 1); + config.idle_frame = 0; + ret |= (1uz << 30); + } } + break; + case config_animation_custom_set_t::custom: + break; + } + } + break; + /// @NOTE(assets): 5. add animation_index validation + } + return ret; +} + +static uint64_t config_validate_enums(config_t& config) { + uint64_t ret{0}; + // Validate layer + if (config.layer != layer_type_t::LAYER_BACKGROUND && config.layer != layer_type_t::LAYER_BOTTOM && + config.layer != layer_type_t::LAYER_TOP && config.layer != layer_type_t::LAYER_OVERLAY) { + BONGOCAT_LOG_WARNING("Invalid layer %d, resetting to top", config.layer); + config.layer = layer_type_t::LAYER_TOP; + ret |= (1uz << 31); + } + + // Validate overlay_position + if (config.overlay_position != overlay_position_t::POSITION_TOP && + config.overlay_position != overlay_position_t::POSITION_BOTTOM) { + BONGOCAT_LOG_WARNING("Invalid %s %d, resetting to top", OVERLAY_OPACITY_KEY, config.overlay_position); + config.overlay_position = overlay_position_t::POSITION_TOP; + ret |= (1uz << 32); + } + + // Validate cat_align + if (config.cat_align != align_type_t::ALIGN_CENTER && config.cat_align != align_type_t::ALIGN_LEFT && + config.cat_align != align_type_t::ALIGN_RIGHT) { + BONGOCAT_LOG_WARNING("Invalid %s %d, resetting to center", CAT_ALIGN_KEY, config.cat_align); + config.cat_align = align_type_t::ALIGN_CENTER; + ret |= (1uz << 33); + } + + return ret; +} + +static uint64_t config_validate_time(config_t& config) { + uint64_t ret{0}; + if (config.enable_scheduled_sleep) { + const int begin_minutes = config.sleep_begin.hour * 60 + config.sleep_begin.min; + const int end_minutes = config.sleep_end.hour * 60 + config.sleep_end.min; + + if (begin_minutes == end_minutes) { + BONGOCAT_LOG_WARNING("Sleep mode is enabled, but time is equal: %02d:%02d, disable sleep mode", + config.sleep_begin.hour, config.sleep_begin.min); + + config.enable_scheduled_sleep = 0; + // config.sleep_begin.hour = 0; + // config.sleep_begin.min = 0; + // config.sleep_end.hour = 0; + // config.sleep_end.min = 0; + ret |= (1uz << 34); + } + } + return ret; +} + +static bongocat_error_t config_validate(config_t& config) { + uint64_t ret{0}; + // Normalize boolean values + config.enable_debug = config.enable_debug ? 1 : 0; + config.invert_color = config.invert_color ? 1 : 0; + config.idle_animation = config.idle_animation ? 1 : 0; + config.enable_scheduled_sleep = config.enable_scheduled_sleep ? 1 : 0; + config.mirror_x = config.mirror_x ? 1 : 0; + config.mirror_y = config.mirror_y ? 1 : 0; + config.randomize_index = config.randomize_index ? 1 : 0; + config.randomize_on_reload = config.randomize_on_reload ? 1 : 0; + config.enable_antialiasing = config.enable_antialiasing ? 1 : 0; + config.enable_movement_debug = config.enable_movement_debug ? 1 : 0; + config.enable_hand_mapping = config.enable_hand_mapping ? 1 : 0; + + ret |= config_validate_dimensions(config); + ret |= config_validate_timing(config); + ret |= config_validate_appearance(config); + ret |= config_validate_enums(config); + ret |= config_validate_time(config); + ret |= config_validate_kpm(config); + ret |= config_validate_update(config); + ret |= config_validate_custom(config); + + if (config._strict) { + if (ret != 0) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to load configuration in strict mode: %x", ret); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + } + return bongocat_error_t::BONGOCAT_SUCCESS; +} - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - static bongocat_error_t config_parse_enum_key(config_t& config, const char *key, const char *value) { - if (strcmp(key, LAYER_KEY) == 0 || strcmp(key, OVERLAY_LAYER_KEY) == 0) { - if (strcmp(value, LAYER_TOP_STR) == 0) { - config.layer = layer_type_t::LAYER_TOP; - } else if (strcmp(value, LAYER_OVERLAY_STR) == 0) { - config.layer = layer_type_t::LAYER_OVERLAY; - } else if (strcmp(value, LAYER_BOTTOM_STR) == 0) { - config.layer = layer_type_t::LAYER_BOTTOM; - } else if (strcmp(value, LAYER_BACKGROUND_STR) == 0) { - config.layer = layer_type_t::LAYER_BACKGROUND; - } else { - BONGOCAT_LOG_WARNING("Invalid %s '%s', using 'top'", LAYER_KEY, value); - config.layer = layer_type_t::LAYER_TOP; - } - } else if (strcmp(key, OVERLAY_POSITION_KEY) == 0) { - if (strcmp(value, POSITION_TOP_STR) == 0) { - config.overlay_position = overlay_position_t::POSITION_TOP; - } else if (strcmp(value, POSITION_BOTTOM_STR) == 0) { - config.overlay_position = overlay_position_t::POSITION_BOTTOM; - } else { - BONGOCAT_LOG_WARNING("Invalid %s '%s', using 'top'", OVERLAY_POSITION_KEY, value); - config.overlay_position = overlay_position_t::POSITION_TOP; - } - } else if (strcmp(key, CAT_ALIGN_KEY) == 0) { - if (strcmp(value, ALIGN_CENTER_STR) == 0) { - config.cat_align = align_type_t::ALIGN_CENTER; - } else if (strcmp(value, ALIGN_LEFT_STR) == 0) { - config.cat_align = align_type_t::ALIGN_LEFT; - } else if (strcmp(value, ALIGN_RIGHT_STR) == 0) { - config.cat_align = align_type_t::ALIGN_RIGHT; - } else { - BONGOCAT_LOG_WARNING("Invalid %s '%s', using 'center'", CAT_ALIGN_KEY, value); - config.cat_align = align_type_t::ALIGN_CENTER; - } - } else { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key - } +// ============================================================================= +// DEVICE MANAGEMENT MODULE +// ============================================================================= - return bongocat_error_t::BONGOCAT_SUCCESS; +static bongocat_error_t config_add_keyboard_device(config_t& config, const char *device_path) { + BONGOCAT_CHECK_NULL(device_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + assert(config.num_keyboard_devices >= 0 && config.num_keyboard_devices < INT_MAX - 1); + + const int old_num_keyboard_devices = config.num_keyboard_devices; + + assert(input::MAX_INPUT_DEVICES <= INT_MAX); + if (old_num_keyboard_devices >= static_cast(input::MAX_INPUT_DEVICES)) { + BONGOCAT_LOG_WARNING("Can not add more devices from config, max. reach: %d", input::MAX_INPUT_DEVICES); + return config._strict ? bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM : bongocat_error_t::BONGOCAT_SUCCESS; + } + const int new_num_keyboard_devices = old_num_keyboard_devices + 1; + assert(new_num_keyboard_devices >= 0); + assert(static_cast(new_num_keyboard_devices) <= input::MAX_INPUT_DEVICES); + + // Add new device path + config.keyboard_devices[old_num_keyboard_devices] = strdup(device_path); + if (!config.keyboard_devices[old_num_keyboard_devices]) { + // free new copied strings + for (int i = 0; i < old_num_keyboard_devices; i++) { + if (config.keyboard_devices[i]) + ::free(config.keyboard_devices[i]); + config.keyboard_devices[i] = nullptr; } + config.num_keyboard_devices = old_num_keyboard_devices; + BONGOCAT_LOG_ERROR("Failed to copy new keyboard device path"); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // update new size + config.num_keyboard_devices = new_num_keyboard_devices; + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +static void config_cleanup_devices(config_t& config) { + assert(config.num_keyboard_devices >= 0); + for (size_t i = 0; i < input::MAX_INPUT_DEVICES; i++) { + if (i < static_cast(config.num_keyboard_devices)) { + if (config.keyboard_devices[i]) + ::free(config.keyboard_devices[i]); + } + config.keyboard_devices[i] = nullptr; + } + config.num_keyboard_devices = 0; +} +// ============================================================================= +// CONFIGURATION PARSING MODULE +// ============================================================================= - bongocat_error_t config_parse_time(const char *value, int& hour, int& min) { - char *endptr = nullptr; - errno = 0; - - // Parse hour - const long h = strtol(value, &endptr, 10); - if (endptr == value || *endptr != ':' || errno == ERANGE || h < 0 || h > 23) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } +static char *config_trim_str(char *key) { + char *key_start = key; + while (*key_start == ' ' || *key_start == '\t') + key_start++; + + char *key_end = key_start + strlen(key_start) - 1; + while (key_end > key_start && (*key_end == ' ' || *key_end == '\t')) { + *key_end = '\0'; + key_end--; + } + + return key_start; +} + +static bongocat_error_t config_parse_integer_key(config_t& config, const char *key, const char *value) { + errno = 0; + char *endptr_int = nullptr; + const int int_value = static_cast(strtol(value, &endptr_int, 10)); + if (errno != 0 || endptr_int == value || *endptr_int != '\0') { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + if (strcmp(key, CAT_X_OFFSET_KEY) == 0) { + config.cat_x_offset = int_value; + } else if (strcmp(key, CAT_Y_OFFSET_KEY) == 0) { + config.cat_y_offset = int_value; + } else if (strcmp(key, CAT_HEIGHT_KEY) == 0) { + config.cat_height = int_value; + } else if (strcmp(key, OVERLAY_HEIGHT_KEY) == 0) { + config.overlay_height = int_value; + } else if (strcmp(key, IDLE_FRAME_KEY) == 0) { + config.idle_frame = int_value; + } else if (strcmp(key, KEYPRESS_DURATION_KEY) == 0) { + config.keypress_duration_ms = int_value; + } else if (strcmp(key, TEST_ANIMATION_DURATION_KEY) == 0) { + config.test_animation_duration_ms = int_value; + } else if (strcmp(key, TEST_ANIMATION_INTERVAL_KEY) == 0) { + config.test_animation_interval_sec = int_value; + } else if (strcmp(key, FPS_KEY) == 0) { + config.fps = int_value; + } else if (strcmp(key, OVERLAY_OPACITY_KEY) == 0) { + config.overlay_opacity = int_value; + } else if (strcmp(key, MIRROR_X_KEY) == 0) { + config.mirror_x = int_value; + } else if (strcmp(key, MIRROR_Y_KEY) == 0) { + config.mirror_y = int_value; + } else if (strcmp(key, ENABLE_ANTIALIASING_KEY) == 0) { + config.enable_antialiasing = int_value; + } else if (strcmp(key, ENABLE_DEBUG_KEY) == 0) { + config.enable_debug = int_value; + } else if (strcmp(key, ANIMATION_INDEX_KEY) == 0) { + config.animation_index = int_value; + } else if (strcmp(key, INVERT_COLOR_KEY) == 0) { + config.invert_color = int_value; + } else if (strcmp(key, PADDING_X_KEY) == 0) { + config.padding_x = int_value; + } else if (strcmp(key, PADDING_Y_KEY) == 0) { + config.padding_y = int_value; + } else if (strcmp(key, ENABLE_SCHEDULED_SLEEP_KEY) == 0) { + config.enable_scheduled_sleep = int_value; + } else if (strcmp(key, IDLE_SLEEP_TIMEOUT_KEY) == 0) { + config.idle_sleep_timeout_sec = int_value; + } else if (strcmp(key, HAPPY_KPM_KEY) == 0) { + config.happy_kpm = int_value; + } else if (strcmp(key, ANIMATION_SPEED_KEY) == 0) { + config.animation_speed_ms = int_value; + } else if (strcmp(key, IDLE_ANIMATION_KEY) == 0) { + config.idle_animation = int_value; + } else if (strcmp(key, INPUT_FPS_KEY) == 0) { + config.input_fps = int_value; + } else if (strcmp(key, RANDOM_KEY) == 0) { + config.randomize_index = int_value; + } else if (strcmp(key, RANDOM_ON_RELOAD_KEY) == 0) { + config.randomize_on_reload = int_value; + } else if (strcmp(key, UPDATE_RATE_KEY) == 0) { + config.update_rate_ms = int_value; + } else if (strcmp(key, CPU_THRESHOLD_KEY) == 0) { + config.cpu_threshold = int_value; + } else if (strcmp(key, CPU_RUNNING_FACTOR_KEY) == 0) { + config.cpu_running_factor = int_value; + } else if (strcmp(key, MOVEMENT_RADIUS_KEY) == 0) { + config.movement_radius = int_value; + } else if (strcmp(key, ENABLE_MOVEMENT_DEBUG_KEY) == 0) { + config.enable_movement_debug = int_value; + } else if (strcmp(key, MOVEMENT_SPEED_KEY) == 0) { + config.movement_speed = int_value; + } else if (strcmp(key, MOVEMENT_WAIT_FACTOR_KEY) == 0) { + config.movement_wait_factor = int_value; + } else if (strcmp(key, SCREEN_WIDTH_KEY) == 0) { + config.screen_width = int_value; + } else if (strcmp(key, ENABLE_HAND_MAPPING_KEY) == 0) { + config.enable_hand_mapping = int_value; + } else if (strcmp(key, CUSTOM_IDLE_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.idle_frames = int_value; + } else if (strcmp(key, CUSTOM_BORING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.boring_frames = int_value; + } else if (strcmp(key, CUSTOM_START_WRITING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.start_writing_frames = int_value; + } else if (strcmp(key, CUSTOM_WRITING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.writing_frames = int_value; + } else if (strcmp(key, CUSTOM_END_WRITING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.end_writing_frames = int_value; + } else if (strcmp(key, CUSTOM_HAPPY_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.happy_frames = int_value; + } else if (strcmp(key, CUSTOM_ASLEEP_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.asleep_frames = int_value; + } else if (strcmp(key, CUSTOM_SLEEP_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.sleep_frames = int_value; + } else if (strcmp(key, CUSTOM_WAKE_UP_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.wake_up_frames = int_value; + } else if (strcmp(key, CUSTOM_START_WORKING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.start_working_frames = int_value; + } else if (strcmp(key, CUSTOM_WORKING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.working_frames = int_value; + } else if (strcmp(key, CUSTOM_END_WORKING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.end_working_frames = int_value; + } else if (strcmp(key, CUSTOM_START_MOVING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.start_moving_frames = int_value; + } else if (strcmp(key, CUSTOM_MOVING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.moving_frames = int_value; + } else if (strcmp(key, CUSTOM_END_MOVING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.end_moving_frames = int_value; + } else if (strcmp(key, CUSTOM_START_RUNNING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.start_running_frames = int_value; + } else if (strcmp(key, CUSTOM_RUNNING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.running_frames = int_value; + } else if (strcmp(key, CUSTOM_END_RUNNING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.end_running_frames = int_value; + } else if (strcmp(key, CUSTOM_TOGGLE_WRITING_FRAMES_KEY) == 0) { + config.custom_sprite_sheet_settings.feature_toggle_writing_frames = int_value; + } else if (strcmp(key, CUSTOM_TOGGLE_WRITING_FRAMES_RANDOM_KEY) == 0) { + config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random = int_value; + } else if (strcmp(key, CUSTOM_MIRROR_X_MOVING_KEY) == 0) { + config.custom_sprite_sheet_settings.feature_mirror_x_moving = int_value; + } else if (strcmp(key, CUSTOM_IDLE_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.idle_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_BORING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.boring_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_START_WRITING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.start_writing_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_WRITING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.writing_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_END_WRITING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.end_writing_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_HAPPY_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.happy_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_ASLEEP_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.asleep_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_SLEEP_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.sleep_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_WAKE_UP_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.wake_up_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_START_WORKING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.start_working_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_WORKING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.working_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_END_WORKING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.end_working_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_START_MOVING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.start_moving_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_MOVING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.moving_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_END_MOVING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.end_moving_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_START_RUNNING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.start_running_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_RUNNING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.running_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_END_RUNNING_ROW_KEY) == 0) { + config.custom_sprite_sheet_settings.end_running_row_index = int_value - 1; + } else if (strcmp(key, CUSTOM_ROWS_KEY) == 0) { + config.custom_sprite_sheet_settings.rows = int_value; + } else { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +static bongocat_error_t config_parse_double_key(config_t& config, const char *key, const char *value) { + errno = 0; + char *endptr_double = nullptr; + const double double_value = strtod(value, &endptr_double); + if (errno != 0 || endptr_double == value || *endptr_double != '\0') { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + if (strcmp(key, CPU_THRESHOLD_KEY) == 0) { + config.cpu_threshold = double_value; + } else if (strcmp(key, CPU_RUNNING_FACTOR_KEY) == 0) { + config.cpu_running_factor = double_value; + } else if (strcmp(key, MOVEMENT_WAIT_FACTOR_KEY) == 0) { + config.movement_wait_factor = double_value; + } else { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +static bongocat_error_t config_parse_enum_key(config_t& config, const char *key, const char *value) { + if (strcmp(key, LAYER_KEY) == 0 || strcmp(key, OVERLAY_LAYER_KEY) == 0) { + if (strcmp(value, LAYER_TOP_STR) == 0) { + config.layer = layer_type_t::LAYER_TOP; + } else if (strcmp(value, LAYER_OVERLAY_STR) == 0) { + config.layer = layer_type_t::LAYER_OVERLAY; + } else if (strcmp(value, LAYER_BOTTOM_STR) == 0) { + config.layer = layer_type_t::LAYER_BOTTOM; + } else if (strcmp(value, LAYER_BACKGROUND_STR) == 0) { + config.layer = layer_type_t::LAYER_BACKGROUND; + } else { + BONGOCAT_LOG_WARNING("Invalid %s '%s', using 'top'", LAYER_KEY, value); + config.layer = layer_type_t::LAYER_TOP; + } + } else if (strcmp(key, OVERLAY_POSITION_KEY) == 0) { + if (strcmp(value, POSITION_TOP_STR) == 0) { + config.overlay_position = overlay_position_t::POSITION_TOP; + } else if (strcmp(value, POSITION_BOTTOM_STR) == 0) { + config.overlay_position = overlay_position_t::POSITION_BOTTOM; + } else { + BONGOCAT_LOG_WARNING("Invalid %s '%s', using 'top'", OVERLAY_POSITION_KEY, value); + config.overlay_position = overlay_position_t::POSITION_TOP; + } + } else if (strcmp(key, CAT_ALIGN_KEY) == 0) { + if (strcmp(value, ALIGN_CENTER_STR) == 0) { + config.cat_align = align_type_t::ALIGN_CENTER; + } else if (strcmp(value, ALIGN_LEFT_STR) == 0) { + config.cat_align = align_type_t::ALIGN_LEFT; + } else if (strcmp(value, ALIGN_RIGHT_STR) == 0) { + config.cat_align = align_type_t::ALIGN_RIGHT; + } else { + BONGOCAT_LOG_WARNING("Invalid %s '%s', using 'center'", CAT_ALIGN_KEY, value); + config.cat_align = align_type_t::ALIGN_CENTER; + } + } else { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +bongocat_error_t config_parse_time(const char *value, int& hour, int& min) { + char *endptr = nullptr; + errno = 0; + + // Parse hour + const long h = strtol(value, &endptr, 10); + if (endptr == value || *endptr != ':' || errno == ERANGE || h < 0 || h > 23) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + // Parse minute + value = endptr + 1; // skip ':' + errno = 0; + const long m = strtol(value, &endptr, 10); + if (endptr == value || *endptr != '\0' || errno == ERANGE || m < 0 || m > 59) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + hour = static_cast(h); + min = static_cast(m); + return bongocat_error_t::BONGOCAT_SUCCESS; +} +static bongocat_error_t config_parse_string(config_t& config, const char *key, const char *value, + const load_config_overwrite_parameters_t& overwrite_parameters) { + using namespace assets; + if (strcmp(key, MONITOR_KEY) == 0 || strcmp(key, OUTPUT_NAME_KEY) == 0) { + if (config.output_name) { + ::free(config.output_name); + config.output_name = nullptr; + } + if (value && value[0] != '\0') { + config.output_name = strdup(value); + if (!config.output_name) { + BONGOCAT_LOG_ERROR("Failed to allocate memory for interface output"); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + } else { + config.output_name = nullptr; + } + } else if (strcmp(key, CUSTOM_SPRITE_SHEET_FILENAME_KEY) == 0) { + if (config.custom_sprite_sheet_filename) { + ::free(config.custom_sprite_sheet_filename); + config.custom_sprite_sheet_filename = nullptr; + } + if (value && value[0] != '\0') { + config.custom_sprite_sheet_filename = strdup(value); + if (!config.custom_sprite_sheet_filename) { + BONGOCAT_LOG_ERROR("Failed to allocate memory for custom sprite sheet filename"); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + } else { + config.custom_sprite_sheet_filename = nullptr; + } + } else if (strcmp(key, SLEEP_BEGIN_KEY) == 0) { + if (value && value[0] != '\0') { + int hour{0}; + int min{0}; + if (config_parse_time(value, hour, min) != bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format + } + if (hour < 0 || hour > 23 || min < 0 || min > 59) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format + } + + config.sleep_begin.hour = hour; + config.sleep_begin.min = min; + } else { + config.sleep_begin.hour = 0; + config.sleep_begin.min = 0; + } + } else if (strcmp(key, SLEEP_END_KEY) == 0) { + if (value && value[0] != '\0') { + int hour{0}; + int min{0}; + if (config_parse_time(value, hour, min) != bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format + } + if (hour < 0 || hour > 23 || min < 0 || min > 59) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format + } + + config.sleep_end.hour = hour; + config.sleep_end.min = min; + } else { + config.sleep_end.hour = 0; + config.sleep_end.min = 0; + } + } else if (strcmp(key, ANIMATION_NAME_KEY) == 0) { + using namespace assets; + if (overwrite_parameters.animation_name) { + value = overwrite_parameters.animation_name; + } - // Parse minute - value = endptr + 1; // skip ':' - errno = 0; - const long m = strtol(value, &endptr, 10); - if (endptr == value || *endptr != '\0' || errno == ERANGE || m < 0 || m > 59) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } + // set config._animation_name + if (config._animation_name) + ::free(config._animation_name); + config._animation_name = nullptr; + config._animation_name = value ? strdup(value) : nullptr; + + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + + // reset state + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::None; + config.animation_dm_set = config_animation_dm_set_t::None; + config.animation_custom_set = config_animation_custom_set_t::None; + config.animation_index = -1; + + // is fully name like dm:..., dm20:..., dmc:... + [[maybe_unused]] const bool is_fqn = strchr(value, ':') != nullptr; + bool animation_found = false; + + if constexpr (features::EnableBongocatEmbeddedAssets) { + // check for bongocat + if (strcmp(value, BONGOCAT_NAME) == 0 || strcmp(value, BONGOCAT_ID) == 0 || strcmp(value, BONGOCAT_FQID) == 0 || + strcmp(value, BONGOCAT_FQNAME) == 0) { + config.animation_index = BONGOCAT_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; + config._loaded_animation_fqname = strdup(BONGOCAT_FQNAME); + } + + animation_found = config.animation_index >= 0; + } - hour = static_cast(h); - min = static_cast(m); - return bongocat_error_t::BONGOCAT_SUCCESS; - } - static bongocat_error_t config_parse_string(config_t& config, const char *key, const char *value, const load_config_overwrite_parameters_t& overwrite_parameters) { - using namespace assets; - if (strcmp(key, MONITOR_KEY) == 0 || strcmp(key, OUTPUT_NAME_KEY) == 0) { - if (config.output_name) { - ::free(config.output_name); - config.output_name = nullptr; - } - if (value && value[0] != '\0') { - config.output_name = strdup(value); - if (!config.output_name) { - BONGOCAT_LOG_ERROR("Failed to allocate memory for interface output"); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - } else { - config.output_name = nullptr; - } - } else if (strcmp(key, CUSTOM_SPRITE_SHEET_FILENAME_KEY) == 0) { - if (config.custom_sprite_sheet_filename) { - ::free(config.custom_sprite_sheet_filename); - config.custom_sprite_sheet_filename = nullptr; - } - if (value && value[0] != '\0') { - config.custom_sprite_sheet_filename = strdup(value); - if (!config.custom_sprite_sheet_filename) { - BONGOCAT_LOG_ERROR("Failed to allocate memory for custom sprite sheet filename"); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - } else { - config.custom_sprite_sheet_filename = nullptr; - } - } else if (strcmp(key, SLEEP_BEGIN_KEY) == 0) { - if (value && value[0] != '\0') { - int hour{0}; - int min{0}; - if (config_parse_time(value, hour, min) != bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format - } - if (hour < 0 || hour > 23 || min < 0 || min > 59) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format - } - - config.sleep_begin.hour = hour; - config.sleep_begin.min = min; - } else { - config.sleep_begin.hour = 0; - config.sleep_begin.min = 0; - } - } else if (strcmp(key, SLEEP_END_KEY) == 0) { - if (value && value[0] != '\0') { - int hour{0}; - int min{0}; - if (config_parse_time(value, hour, min) != bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format - } - if (hour < 0 || hour > 23 || min < 0 || min > 59) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Invalid time format - } - - config.sleep_end.hour = hour; - config.sleep_end.min = min; - } else { - config.sleep_end.hour = 0; - config.sleep_end.min = 0; - } - } else if (strcmp(key, ANIMATION_NAME_KEY) == 0) { - using namespace assets; - if (overwrite_parameters.animation_name) { - value = overwrite_parameters.animation_name; - } - - // set config._animation_name - if (config._animation_name) ::free(config._animation_name); - config._animation_name = nullptr; - config._animation_name = value ? strdup(value) : nullptr; - - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - - // reset state - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::None; - config.animation_dm_set = config_animation_dm_set_t::None; - config.animation_custom_set = config_animation_custom_set_t::None; - config.animation_index = -1; - - - // is fully name like dm:..., dm20:..., dmc:... - [[maybe_unused]] const bool is_fqn = strchr(value, ':') != nullptr; - bool animation_found = false; - - if constexpr (features::EnableBongocatEmbeddedAssets) { - // check for bongocat - if (strcmp(value, BONGOCAT_NAME) == 0 || - strcmp(value, BONGOCAT_ID) == 0 || - strcmp(value, BONGOCAT_FQID) == 0 || - strcmp(value, BONGOCAT_FQNAME) == 0) { - config.animation_index = BONGOCAT_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; - config._loaded_animation_fqname = strdup(BONGOCAT_FQNAME); - } - - animation_found = config.animation_index >= 0; - } - - // check for dm - if constexpr (features::EnableDmEmbeddedAssets) { - using namespace assets; + // check for dm + if constexpr (features::EnableDmEmbeddedAssets) { + using namespace assets; #ifdef FEATURE_MIN_DM_EMBEDDED_ASSETS - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { // overwrite animation when needed, priorities the fq names -#include "min_dm_config_parse_enum_key.cpp.inl" - if (config.animation_index >= 0) { - assert(found_index >= 0); - /// @TODO: get fqname of min_dm - //config._loaded_animation_fqname = strdup(get_config_animation_name_min_dm(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s", value); - } - } - animation_found = config.animation_index >= 0; + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || + (!is_fqn && !animation_found)) { // overwrite animation when needed, priorities the fq names +# include "min_dm_config_parse_enum_key.cpp.inl" + if (config.animation_index >= 0) { + assert(found_index >= 0); + /// @TODO: get fqname of min_dm + // config._loaded_animation_fqname = + // strdup(get_config_animation_name_min_dm(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s", value); + } + } + animation_found = config.animation_index >= 0; #endif - /// @TODO: use macros (or templates) to reduce copy&paste code + /// @TODO: use macros (or templates) to reduce copy&paste code - /// @NOTE(assets): 3. add more dm versions here, config animation_name parsing + /// @NOTE(assets): 3. add more dm versions here, config animation_name parsing #ifdef FEATURE_DM_EMBEDDED_ASSETS - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { // overwrite animation when needed, priorities the fq names - const int found_index = config_parse_animation_name_dm(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_dm(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || + (!is_fqn && !animation_found)) { // overwrite animation when needed, priorities the fq names + const int found_index = config_parse_animation_name_dm(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_dm(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif #ifdef FEATURE_DM20_EMBEDDED_ASSETS - // overwrite animation when not found or full name - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_dm20(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_dm20(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + // overwrite animation when not found or full name + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_dm20(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_dm20(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif #ifdef FEATURE_DMX_EMBEDDED_ASSETS - // overwrite animation when not found or full name - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_dmx(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_dmx(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + // overwrite animation when not found or full name + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_dmx(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_dmx(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif #ifdef FEATURE_DMC_EMBEDDED_ASSETS - // overwrite animation when not found or full name - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_dmc(config, value); - if (found_index >= 0) { - assert(config.animation_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_dmc(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + // overwrite animation when not found or full name + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_dmc(config, value); + if (found_index >= 0) { + assert(config.animation_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_dmc(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif #ifdef FEATURE_PEN_EMBEDDED_ASSETS - // overwrite animation when not found or full name - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_pen(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_pen(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + // overwrite animation when not found or full name + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_pen(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_pen(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif #ifdef FEATURE_PEN20_EMBEDDED_ASSETS - // overwrite animation when not found or full name - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_pen20(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_pen20(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + // overwrite animation when not found or full name + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_pen20(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_pen20(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif #ifdef FEATURE_DMALL_EMBEDDED_ASSETS - // overwrite animation when not found or full name - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_dmall(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_dmall(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + // overwrite animation when not found or full name + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_dmall(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_dmall(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif - } - - // check for MS agent - if constexpr (features::EnableMsAgentEmbeddedAssets) { - // check for ms pets (clippy) - if (strcmp(value, CLIPPY_NAME) == 0 || - strcmp(value, CLIPPY_ID) == 0 || - strcmp(value, CLIPPY_FQID) == 0 || - strcmp(value, CLIPPY_FQNAME) == 0) { - config.animation_index = CLIPPY_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(CLIPPY_FQNAME); - } + } + + // check for MS agent + if constexpr (features::EnableMsAgentEmbeddedAssets) { + // check for ms pets (clippy) + if (strcmp(value, CLIPPY_NAME) == 0 || strcmp(value, CLIPPY_ID) == 0 || strcmp(value, CLIPPY_FQID) == 0 || + strcmp(value, CLIPPY_FQNAME) == 0) { + config.animation_index = CLIPPY_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = strdup(CLIPPY_FQNAME); + } #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - /// @NOTE(assets): 4. add more MS Agents here - // Links - if (strcmp(value, LINKS_NAME) == 0 || - strcmp(value, LINKS_ID) == 0 || - strcmp(value, LINKS_FQID) == 0 || - strcmp(value, LINKS_FQNAME) == 0) { - config.animation_index = LINKS_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(LINKS_FQNAME); - } - // Rover - if (strcmp(value, ROVER_NAME) == 0 || - strcmp(value, ROVER_ID) == 0 || - strcmp(value, ROVER_FQID) == 0 || - strcmp(value, ROVER_FQNAME) == 0) { - config.animation_index = ROVER_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(ROVER_FQNAME); - } - // Merlin - if (strcmp(value, MERLIN_NAME) == 0 || - strcmp(value, MERLIN_ID) == 0 || - strcmp(value, MERLIN_FQID) == 0 || - strcmp(value, MERLIN_FQNAME) == 0) { - config.animation_index = MERLIN_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(MERLIN_FQNAME); - } + /// @NOTE(assets): 4. add more MS Agents here + // Links + if (strcmp(value, LINKS_NAME) == 0 || strcmp(value, LINKS_ID) == 0 || strcmp(value, LINKS_FQID) == 0 || + strcmp(value, LINKS_FQNAME) == 0) { + config.animation_index = LINKS_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = strdup(LINKS_FQNAME); + } + // Rover + if (strcmp(value, ROVER_NAME) == 0 || strcmp(value, ROVER_ID) == 0 || strcmp(value, ROVER_FQID) == 0 || + strcmp(value, ROVER_FQNAME) == 0) { + config.animation_index = ROVER_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = strdup(ROVER_FQNAME); + } + // Merlin + if (strcmp(value, MERLIN_NAME) == 0 || strcmp(value, MERLIN_ID) == 0 || strcmp(value, MERLIN_FQID) == 0 || + strcmp(value, MERLIN_FQNAME) == 0) { + config.animation_index = MERLIN_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = strdup(MERLIN_FQNAME); + } #endif - animation_found = config.animation_index >= 0; - } + animation_found = config.animation_index >= 0; + } - // check for pkmn - if constexpr (features::EnablePkmnEmbeddedAssets) { - using namespace assets; + // check for pkmn + if constexpr (features::EnablePkmnEmbeddedAssets) { + using namespace assets; #ifdef FEATURE_PKMN_EMBEDDED_ASSETS - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_pkmn(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_pkmn(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_pkmn(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_pkmn(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); + } + animation_found = config.animation_index >= 0; + } #endif - } - // check for pmd (pkmn) - if constexpr (features::EnablePmdEmbeddedAssets) { - using namespace assets; + } + // check for pmd (pkmn) + if constexpr (features::EnablePmdEmbeddedAssets) { + using namespace assets; #ifdef FEATURE_PMD_EMBEDDED_ASSETS - if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { - const int found_index = config_parse_animation_name_pmd(config, value); - if (found_index >= 0) { - assert(found_index >= 0); - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(get_config_animation_name_pmd(static_cast(found_index)).fqname); - BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); - } - animation_found = config.animation_index >= 0; - } -#endif - } - - // check for Misc (neko) - if constexpr (features::EnableMiscEmbeddedAssets) { - // check neko - if (strcmp(value, MISC_NEKO_NAME) == 0 || - strcmp(value, MISC_NEKO_ID) == 0 || - strcmp(value, MISC_NEKO_FQID) == 0 || - strcmp(value, MISC_NEKO_FQNAME) == 0) { - config.animation_index = MISC_NEKO_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Custom; - config.animation_custom_set = config_animation_custom_set_t::misc; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(MISC_NEKO_FQNAME); - animation_found = config.animation_index >= 0; - } - } - /// @NOTE(assets): 4. add more config animation_name parsring here - - // check for custom sprite sheet - if constexpr (features::EnableCustomSpriteSheetsAssets) { - // check custom - if (strcmp(value, CUSTOM_NAME) == 0 || - strcmp(value, CUSTOM_ID) == 0) { - config.animation_index = CUSTOM_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Custom; - config.animation_custom_set = config_animation_custom_set_t::custom; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = config.custom_sprite_sheet_filename ? strdup(config.custom_sprite_sheet_filename) : nullptr; - animation_found = config.animation_index >= 0; - config._custom = config.animation_index == CUSTOM_ANIM_INDEX; - - if (config.custom_sprite_sheet_filename == nullptr || strlen(config.custom_sprite_sheet_filename) <= 0) { - BONGOCAT_LOG_WARNING("custom_sprite_sheet_filename required for custom sprite sheet"); - animation_found = false; - } - } - } - - animation_found = config.animation_index >= 0 && config.animation_sprite_sheet_layout != config_animation_sprite_sheet_layout_t::None; - if (!animation_found) { - if (config.animation_index >= 0 && config.animation_sprite_sheet_layout == config_animation_sprite_sheet_layout_t::None) { - BONGOCAT_LOG_WARNING("animation_index is set, but not animation_type (unknown type for index=%i and value='%s')", config.animation_index, value); - } - if (config._strict) { - BONGOCAT_LOG_ERROR("Invalid %s '%s'", ANIMATION_NAME_KEY, value, BONGOCAT_NAME); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - BONGOCAT_LOG_WARNING("Invalid %s '%s', using '%s'", ANIMATION_NAME_KEY, value, BONGOCAT_NAME); - config.animation_index = BONGOCAT_ANIM_INDEX; - config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; - config.animation_dm_set = config_animation_dm_set_t::None; - config.animation_custom_set = config_animation_custom_set_t::None; - if (config._loaded_animation_fqname) ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = strdup(BONGOCAT_FQNAME); - } - } else { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + if ((!is_fqn && animation_found) || (is_fqn && !animation_found) || (!is_fqn && !animation_found)) { + const int found_index = config_parse_animation_name_pmd(config, value); + if (found_index >= 0) { + assert(found_index >= 0); + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + strdup(get_config_animation_name_pmd(static_cast(found_index)).fqname); + BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); } - - return bongocat_error_t::BONGOCAT_SUCCESS; + animation_found = config.animation_index >= 0; + } +#endif } - static bongocat_error_t config_parse_key_value(config_t& config, const char *key, const char *value, const load_config_overwrite_parameters_t& overwrite_parameters) { - // Try integer keys first - if (config_parse_integer_key(config, key, value) == bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_SUCCESS; - } - // Try double keys first - if (config_parse_double_key(config, key, value) == bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - // Try enum keys - if (config_parse_enum_key(config, key, value) == bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - // Try string - if (config_parse_string(config, key, value, overwrite_parameters) == bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - // Handle device keys - if (strcmp(key, KEYBOARD_DEVICE_KEY) == 0 || strcmp(key, KEYBOARD_DEVICES_KEY) == 0) { - return config_add_keyboard_device(config, value); + // check for Misc (neko) + if constexpr (features::EnableMiscEmbeddedAssets) { + // check neko + if (strcmp(value, MISC_NEKO_NAME) == 0 || strcmp(value, MISC_NEKO_ID) == 0 || + strcmp(value, MISC_NEKO_FQID) == 0 || strcmp(value, MISC_NEKO_FQNAME) == 0) { + config.animation_index = MISC_NEKO_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Custom; + config.animation_custom_set = config_animation_custom_set_t::misc; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = strdup(MISC_NEKO_FQNAME); + animation_found = config.animation_index >= 0; + } + } + /// @NOTE(assets): 4. add more config animation_name parsring here + + // check for custom sprite sheet + if constexpr (features::EnableCustomSpriteSheetsAssets) { + // check custom + if (strcmp(value, CUSTOM_NAME) == 0 || strcmp(value, CUSTOM_ID) == 0) { + config.animation_index = CUSTOM_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Custom; + config.animation_custom_set = config_animation_custom_set_t::custom; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = + config.custom_sprite_sheet_filename ? strdup(config.custom_sprite_sheet_filename) : nullptr; + animation_found = config.animation_index >= 0; + config._custom = config.animation_index == CUSTOM_ANIM_INDEX; + + if (config.custom_sprite_sheet_filename == nullptr || strlen(config.custom_sprite_sheet_filename) <= 0) { + BONGOCAT_LOG_WARNING("custom_sprite_sheet_filename required for custom sprite sheet"); + animation_found = false; } + } + } - // Unknown key + animation_found = config.animation_index >= 0 && + config.animation_sprite_sheet_layout != config_animation_sprite_sheet_layout_t::None; + if (!animation_found) { + if (config.animation_index >= 0 && + config.animation_sprite_sheet_layout == config_animation_sprite_sheet_layout_t::None) { + BONGOCAT_LOG_WARNING( + "animation_index is set, but not animation_type (unknown type for index=%i and value='%s')", + config.animation_index, value); + } + if (config._strict) { + BONGOCAT_LOG_ERROR("Invalid %s '%s'", ANIMATION_NAME_KEY, value, BONGOCAT_NAME); return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + BONGOCAT_LOG_WARNING("Invalid %s '%s', using '%s'", ANIMATION_NAME_KEY, value, BONGOCAT_NAME); + config.animation_index = BONGOCAT_ANIM_INDEX; + config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; + config.animation_dm_set = config_animation_dm_set_t::None; + config.animation_custom_set = config_animation_custom_set_t::None; + if (config._loaded_animation_fqname) + ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = strdup(BONGOCAT_FQNAME); } - - static bool config_is_comment_or_empty(const char *line) { - return (line[0] == '#' || line[0] == '\0' || strspn(line, " \t") == strlen(line)); - } - - - static bongocat_error_t config_parse_file(FILE *file, config_t& config, const load_config_overwrite_parameters_t& overwrite_parameters) { - char line[LINE_BUF] = {0}; - char key[KEY_BUF] = {0}; - char value[VALUE_BUF] = {0}; - int line_number = 0; - bongocat_error_t result = bongocat_error_t::BONGOCAT_SUCCESS; - - while (fgets(line, sizeof(line), file)) { - line_number++; - - // Remove trailing newline - size_t len = strlen(line); - if (len > 0 && line[len - 1] == '\n') { - line[len - 1] = '\0'; - } - - // Skip comments and empty lines - if (config_is_comment_or_empty(line)) { - continue; - } - - // Parse key=value pairs - static_assert(VALUE_BUF >= PATH_MAX); - static_assert(255 < KEY_BUF); - static_assert(4351 < VALUE_BUF); - if (sscanf(line, " %255[^=]=%4351[^\n]", key, value) == 2) { - // Cut off trailing comment in value - char *comment = strchr(value, '#'); - if (comment) { - *comment = '\0'; // terminate string before '#' - } - - char *trimmed_key = config_trim_str(key); - char *trimmed_value = config_trim_str(value); - - bongocat_error_t parse_result = config_parse_key_value(config, trimmed_key, trimmed_value, overwrite_parameters); - if (parse_result == bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM) { - BONGOCAT_LOG_WARNING("Unknown configuration key '%s' at line %d", trimmed_key, line_number); - } else if (parse_result != bongocat_error_t::BONGOCAT_SUCCESS) { - result = parse_result; - break; - } - } else if (strlen(line) > 0) { - BONGOCAT_LOG_WARNING("Invalid configuration line %d: %s", line_number, line); - } - } - - return result; + } else { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; // Unknown key + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +static bongocat_error_t config_parse_key_value(config_t& config, const char *key, const char *value, + const load_config_overwrite_parameters_t& overwrite_parameters) { + // Try integer keys first + if (config_parse_integer_key(config, key, value) == bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_SUCCESS; + } + // Try double keys first + if (config_parse_double_key(config, key, value) == bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_SUCCESS; + } + + // Try enum keys + if (config_parse_enum_key(config, key, value) == bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_SUCCESS; + } + + // Try string + if (config_parse_string(config, key, value, overwrite_parameters) == bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_SUCCESS; + } + + // Handle device keys + if (strcmp(key, KEYBOARD_DEVICE_KEY) == 0 || strcmp(key, KEYBOARD_DEVICES_KEY) == 0) { + return config_add_keyboard_device(config, value); + } + + // Unknown key + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; +} + +static bool config_is_comment_or_empty(const char *line) { + return (line[0] == '#' || line[0] == '\0' || strspn(line, " \t") == strlen(line)); +} + +static bongocat_error_t config_parse_file(FILE *file, config_t& config, + const load_config_overwrite_parameters_t& overwrite_parameters) { + char line[LINE_BUF] = {0}; + char key[KEY_BUF] = {0}; + char value[VALUE_BUF] = {0}; + int line_number = 0; + bongocat_error_t result = bongocat_error_t::BONGOCAT_SUCCESS; + + while (fgets(line, sizeof(line), file)) { + line_number++; + + // Remove trailing newline + size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; } - static bongocat_error_t config_parse_file(config_t& config, const char *config_file_path, load_config_overwrite_parameters_t overwrite_parameters) { - const char *file_path = config_file_path ? config_file_path : DEFAULT_CONFIG_FILE_PATH; - - FILE *file = fopen(file_path, "r"); - if (!file) { - if (overwrite_parameters.strict >= 0) { - BONGOCAT_LOG_INFO("Config file '%s' not found", file_path); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - BONGOCAT_LOG_INFO("Config file '%s' not found, using defaults", file_path); - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); - - fclose(file); - - if (result == bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_INFO("Loaded configuration from %s", file_path); - } + // Skip comments and empty lines + if (config_is_comment_or_empty(line)) { + continue; + } - return result; + // Parse key=value pairs + static_assert(VALUE_BUF >= PATH_MAX); + static_assert(255 < KEY_BUF); + static_assert(4351 < VALUE_BUF); + if (sscanf(line, " %255[^=]=%4351[^\n]", key, value) == 2) { + // Cut off trailing comment in value + char *comment = strchr(value, '#'); + if (comment) { + *comment = '\0'; // terminate string before '#' + } + + char *trimmed_key = config_trim_str(key); + char *trimmed_value = config_trim_str(value); + + bongocat_error_t parse_result = config_parse_key_value(config, trimmed_key, trimmed_value, overwrite_parameters); + if (parse_result == bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM) { + BONGOCAT_LOG_WARNING("Unknown configuration key '%s' at line %d", trimmed_key, line_number); + } else if (parse_result != bongocat_error_t::BONGOCAT_SUCCESS) { + result = parse_result; + break; + } + } else if (strlen(line) > 0) { + BONGOCAT_LOG_WARNING("Invalid configuration line %d: %s", line_number, line); } + } - static bongocat_error_t config_parse_stdin(config_t& config, const load_config_overwrite_parameters_t& overwrite_parameters) { - FILE *file = stdin; + return result; +} - bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); - if (result == bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_INFO("Loaded configuration from stdin"); - } +static bongocat_error_t config_parse_file(config_t& config, const char *config_file_path, + load_config_overwrite_parameters_t overwrite_parameters) { + const char *file_path = config_file_path ? config_file_path : DEFAULT_CONFIG_FILE_PATH; - return result; + FILE *file = fopen(file_path, "r"); + if (!file) { + if (overwrite_parameters.strict >= 0) { + BONGOCAT_LOG_INFO("Config file '%s' not found", file_path); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; } + BONGOCAT_LOG_INFO("Config file '%s' not found, using defaults", file_path); + return bongocat_error_t::BONGOCAT_SUCCESS; + } - // ============================================================================= - // DEFAULT CONFIGURATION MODULE - // ============================================================================= + bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); - void set_defaults(config_t& config) { - config_t cfg{}; + fclose(file); - cfg.output_name = nullptr; - assert(input::MAX_INPUT_DEVICES <= INT_MAX); - for (int i = 0; i < static_cast(input::MAX_INPUT_DEVICES); i++) { - cfg.keyboard_devices[i] = nullptr; - } - cfg.num_keyboard_devices = 0; - cfg.cat_x_offset = DEFAULT_CAT_X_OFFSET; - cfg.cat_y_offset = DEFAULT_CAT_Y_OFFSET; - cfg.cat_height = DEFAULT_CAT_HEIGHT; - cfg.overlay_height = DEFAULT_OVERLAY_HEIGHT; - cfg.idle_frame = DEFAULT_IDLE_FRAME; - cfg.keypress_duration_ms = DEFAULT_KEYPRESS_DURATION_MS; - cfg.test_animation_duration_ms = DEFAULT_TEST_ANIMATION_DURATION_MS; - cfg.test_animation_interval_sec = DEFAULT_TEST_ANIMATION_INTERVAL_SEC; - cfg.fps = DEFAULT_FPS; - cfg.overlay_opacity = DEFAULT_OVERLAY_OPACITY; - cfg.mirror_x = 0; - cfg.mirror_y = 0; - cfg.enable_antialiasing = DEFAULT_ENABLE_ANTIALIASING; - cfg.enable_debug = DEFAULT_ENABLE_DEBUG; - cfg.enable_hand_mapping = DEFAULT_ENABLE_HAND_MAPPING; - cfg.layer = DEFAULT_LAYER; - cfg.overlay_position = DEFAULT_OVERLAY_POSITION; - cfg.animation_index = DEFAULT_ANIMATION_INDEX; - cfg.invert_color = 0; - cfg.padding_x = 0; - cfg.padding_y = 0; - cfg.enable_scheduled_sleep = 0; - cfg.sleep_begin = {}; - cfg.sleep_end = {}; - cfg.idle_sleep_timeout_sec = DEFAULT_IDLE_SLEEP_TIMEOUT_SEC; - cfg.happy_kpm = DEFAULT_HAPPY_KPM; - cfg.cat_align = DEFAULT_CAT_ALIGN; - cfg.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; - cfg.animation_dm_set = config_animation_dm_set_t::None; - cfg.animation_custom_set = config_animation_custom_set_t::None; - cfg.idle_animation = 0; - cfg.input_fps = 0; // when 0 fallback to fps - cfg.randomize_index = 0; - cfg.randomize_on_reload = 0; - cfg.movement_wait_factor = DEFAULT_MOVEMENT_WAIT_FACTOR; - cfg.screen_width = 0; - cfg.custom_sprite_sheet_filename = nullptr; - cfg.custom_sprite_sheet_settings = {}; - cfg._keep_old_animation_index = false; - cfg._strict = false; - cfg._custom = false; - cfg._animation_name = nullptr; - cfg._loaded_animation_fqname = nullptr; - - config = bongocat::move(cfg); - } - - static bongocat_error_t config_set_default_devices(config_t& config) { - if (config.num_keyboard_devices == 0) { - return config_add_keyboard_device(config, DEFAULT_DEVICE); - } + if (result == bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_INFO("Loaded configuration from %s", file_path); + } - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - static void config_log_summary(const config_t& config) { - using namespace assets; - BONGOCAT_LOG_DEBUG("Configuration loaded successfully"); - BONGOCAT_LOG_DEBUG(" Overlay Height: %dpx", config.overlay_height); - switch (config.animation_sprite_sheet_layout) { - case config_animation_sprite_sheet_layout_t::None: - break; - case config_animation_sprite_sheet_layout_t::Bongocat: - assert(config._loaded_animation_fqname); - BONGOCAT_LOG_DEBUG(" Cat: '%s' %dx%d at offset (%d,%d)", config._loaded_animation_fqname, - config.cat_height, (config.cat_height * BONGOCAT_FRAME_WIDTH) / BONGOCAT_FRAME_HEIGHT, - config.cat_x_offset, config.cat_y_offset); - break; - case config_animation_sprite_sheet_layout_t::Dm: - assert(config._loaded_animation_fqname); - BONGOCAT_LOG_DEBUG(" dm: '%s' %03d/%03d (set=%d) at offset (%d,%d)", config._loaded_animation_fqname, - config.animation_index, DM_ANIMATIONS_COUNT, config.animation_dm_set, - config.cat_x_offset, config.cat_y_offset); - break; - case config_animation_sprite_sheet_layout_t::Pkmn: - assert(config._loaded_animation_fqname); - assert(PKMN_ANIMATIONS_COUNT <= INT32_MAX); - BONGOCAT_LOG_DEBUG(" pkmn: '%s' %03d/%03d at offset (%d,%d)", config._loaded_animation_fqname, - config.animation_index, PKMN_ANIMATIONS_COUNT, - config.cat_x_offset, config.cat_y_offset); - break; - case config_animation_sprite_sheet_layout_t::MsAgent: - assert(config._loaded_animation_fqname); - assert(MS_AGENTS_ANIMATIONS_COUNT <= INT32_MAX); - BONGOCAT_LOG_DEBUG(" MS Agent: '%s' %02d/%02d at offset (%d,%d)", config._loaded_animation_fqname, - config.animation_index, MS_AGENTS_ANIMATIONS_COUNT, - config.cat_x_offset, config.cat_y_offset); - break; - case config_animation_sprite_sheet_layout_t::Custom: - switch (config.animation_custom_set) { - case config_animation_custom_set_t::None: - break; - case config_animation_custom_set_t::misc: - assert(config._loaded_animation_fqname); - assert(MISC_ANIM_COUNT <= INT32_MAX); - BONGOCAT_LOG_DEBUG(" Misc: '%s' %03d/%03d at offset (%d,%d)", config._loaded_animation_fqname, - config.animation_index, MISC_ANIMATIONS_COUNT, - config.cat_x_offset, config.cat_y_offset); - break; - case config_animation_custom_set_t::pmd: - assert(config._loaded_animation_fqname); - assert(PMD_ANIM_COUNT <= INT32_MAX); - BONGOCAT_LOG_DEBUG(" pkmn pmd: '%s' %04d/%04d at offset (%d,%d)", config._loaded_animation_fqname, - config.animation_index, PMD_ANIMATIONS_COUNT, - config.cat_x_offset, config.cat_y_offset); - break; - case config_animation_custom_set_t::custom: - assert(config.custom_sprite_sheet_filename); - assert(config._custom); - BONGOCAT_LOG_DEBUG(" Custom: %s at offset (%d,%d)", config.custom_sprite_sheet_filename, - config.cat_x_offset, config.cat_y_offset); - break; - } - break; - } - BONGOCAT_LOG_DEBUG(" FPS: %d, Opacity: %d, Random: %d", config.fps, config.overlay_opacity, config.randomize_index); - BONGOCAT_LOG_DEBUG(" Mirror: X=%d, Y=%d", config.mirror_x, config.mirror_y); - BONGOCAT_LOG_DEBUG(" Anti-aliasing: %s", config.enable_antialiasing ? "enabled" : "disabled"); - BONGOCAT_LOG_DEBUG(" Position: %s", config.overlay_position == overlay_position_t::POSITION_TOP ? "top" : "bottom"); - BONGOCAT_LOG_DEBUG(" Alignment: %d", config.cat_align, config.cat_align == align_type_t::ALIGN_CENTER ? "(center)" : ""); - BONGOCAT_LOG_DEBUG(" Layer: %s", config.layer == layer_type_t::LAYER_TOP ? "top" : "overlay"); - BONGOCAT_LOG_DEBUG(" Output Screen: %s", config.output_name); - } - - // ============================================================================= - // PUBLIC API IMPLEMENTATION - // ============================================================================= - - created_result_t load(const char *config_file_path, load_config_overwrite_parameters_t overwrite_parameters) { - BONGOCAT_CHECK_NULL(config_file_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - config_t ret; - set_defaults(ret); - - // Parse config file and override defaults - bongocat_error_t result = bongocat_error_t::BONGOCAT_ERROR_CONFIG; - if (strcmp(config_file_path, "-") == 0) { - result = config_parse_stdin(ret, overwrite_parameters); - } else { - result = config_parse_file(ret, config_file_path, overwrite_parameters); - } - if (result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to parse configuration file: %s", bongocat::error_string(result)); - return result; - } + return result; +} - if (overwrite_parameters.output_name) { - if (ret.output_name) ::free(ret.output_name); - ret.output_name = strdup(overwrite_parameters.output_name); - } - if (overwrite_parameters.randomize_index >= 0) { - ret.randomize_index = overwrite_parameters.randomize_index ? 1 : 0; - } - if (overwrite_parameters.strict >= 0) { - ret._strict = overwrite_parameters.strict >= 1; - } +static bongocat_error_t config_parse_stdin(config_t& config, + const load_config_overwrite_parameters_t& overwrite_parameters) { + FILE *file = stdin; - if (ret.input_fps <= 0) { - ret.input_fps = ret.fps; - } + bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); + if (result == bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_INFO("Loaded configuration from stdin"); + } - // Set default keyboard device if none specified - if (ret.num_keyboard_devices == 0) { - if (!ret._strict) { - result = config_set_default_devices(ret); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to set default keyboard devices: %s", bongocat::error_string(result)); - return result; - } - } - } + return result; +} - // Validate and sanitize configuration - result = config_validate(ret); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Configuration validation failed: %s", bongocat::error_string(result)); - return result; - } +// ============================================================================= +// DEFAULT CONFIGURATION MODULE +// ============================================================================= - if (ret.num_keyboard_devices == 0) { - if (!ret._strict) { - // Set default keyboard device if none specified - result = config_set_default_devices(ret); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to set default keyboard devices: %s", bongocat::error_string(result)); - return result; - } else { - BONGOCAT_LOG_INFO("No device loaded, use default keyboard device: %s", DEFAULT_DEVICE); - } - } else { - BONGOCAT_LOG_INFO("No device loaded"); - } - } +void set_defaults(config_t& config) { + config_t cfg{}; + + cfg.output_name = nullptr; + assert(input::MAX_INPUT_DEVICES <= INT_MAX); + for (int i = 0; i < static_cast(input::MAX_INPUT_DEVICES); i++) { + cfg.keyboard_devices[i] = nullptr; + } + cfg.num_keyboard_devices = 0; + cfg.cat_x_offset = DEFAULT_CAT_X_OFFSET; + cfg.cat_y_offset = DEFAULT_CAT_Y_OFFSET; + cfg.cat_height = DEFAULT_CAT_HEIGHT; + cfg.overlay_height = DEFAULT_OVERLAY_HEIGHT; + cfg.idle_frame = DEFAULT_IDLE_FRAME; + cfg.keypress_duration_ms = DEFAULT_KEYPRESS_DURATION_MS; + cfg.test_animation_duration_ms = DEFAULT_TEST_ANIMATION_DURATION_MS; + cfg.test_animation_interval_sec = DEFAULT_TEST_ANIMATION_INTERVAL_SEC; + cfg.fps = DEFAULT_FPS; + cfg.overlay_opacity = DEFAULT_OVERLAY_OPACITY; + cfg.mirror_x = 0; + cfg.mirror_y = 0; + cfg.enable_antialiasing = DEFAULT_ENABLE_ANTIALIASING; + cfg.enable_debug = DEFAULT_ENABLE_DEBUG; + cfg.enable_hand_mapping = DEFAULT_ENABLE_HAND_MAPPING; + cfg.layer = DEFAULT_LAYER; + cfg.overlay_position = DEFAULT_OVERLAY_POSITION; + cfg.animation_index = DEFAULT_ANIMATION_INDEX; + cfg.invert_color = 0; + cfg.padding_x = 0; + cfg.padding_y = 0; + cfg.enable_scheduled_sleep = 0; + cfg.sleep_begin = {}; + cfg.sleep_end = {}; + cfg.idle_sleep_timeout_sec = DEFAULT_IDLE_SLEEP_TIMEOUT_SEC; + cfg.happy_kpm = DEFAULT_HAPPY_KPM; + cfg.cat_align = DEFAULT_CAT_ALIGN; + cfg.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; + cfg.animation_dm_set = config_animation_dm_set_t::None; + cfg.animation_custom_set = config_animation_custom_set_t::None; + cfg.idle_animation = 0; + cfg.input_fps = 0; // when 0 fallback to fps + cfg.randomize_index = 0; + cfg.randomize_on_reload = 0; + cfg.movement_wait_factor = DEFAULT_MOVEMENT_WAIT_FACTOR; + cfg.screen_width = 0; + cfg.custom_sprite_sheet_filename = nullptr; + cfg.custom_sprite_sheet_settings = {}; + cfg._keep_old_animation_index = false; + cfg._strict = false; + cfg._custom = false; + cfg._animation_name = nullptr; + cfg._loaded_animation_fqname = nullptr; + + config = bongocat::move(cfg); +} + +static bongocat_error_t config_set_default_devices(config_t& config) { + if (config.num_keyboard_devices == 0) { + return config_add_keyboard_device(config, DEFAULT_DEVICE); + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +static void config_log_summary(const config_t& config) { + using namespace assets; + BONGOCAT_LOG_DEBUG("Configuration loaded successfully"); + BONGOCAT_LOG_DEBUG(" Overlay Height: %dpx", config.overlay_height); + switch (config.animation_sprite_sheet_layout) { + case config_animation_sprite_sheet_layout_t::None: + break; + case config_animation_sprite_sheet_layout_t::Bongocat: + assert(config._loaded_animation_fqname); + BONGOCAT_LOG_DEBUG(" Cat: '%s' %dx%d at offset (%d,%d)", config._loaded_animation_fqname, config.cat_height, + (config.cat_height * BONGOCAT_FRAME_WIDTH) / BONGOCAT_FRAME_HEIGHT, config.cat_x_offset, + config.cat_y_offset); + break; + case config_animation_sprite_sheet_layout_t::Dm: + assert(config._loaded_animation_fqname); + BONGOCAT_LOG_DEBUG(" dm: '%s' %03d/%03d (set=%d) at offset (%d,%d)", config._loaded_animation_fqname, + config.animation_index, DM_ANIMATIONS_COUNT, config.animation_dm_set, config.cat_x_offset, + config.cat_y_offset); + break; + case config_animation_sprite_sheet_layout_t::Pkmn: + assert(config._loaded_animation_fqname); + assert(PKMN_ANIMATIONS_COUNT <= INT32_MAX); + BONGOCAT_LOG_DEBUG(" pkmn: '%s' %03d/%03d at offset (%d,%d)", config._loaded_animation_fqname, + config.animation_index, PKMN_ANIMATIONS_COUNT, config.cat_x_offset, config.cat_y_offset); + break; + case config_animation_sprite_sheet_layout_t::MsAgent: + assert(config._loaded_animation_fqname); + assert(MS_AGENTS_ANIMATIONS_COUNT <= INT32_MAX); + BONGOCAT_LOG_DEBUG(" MS Agent: '%s' %02d/%02d at offset (%d,%d)", config._loaded_animation_fqname, + config.animation_index, MS_AGENTS_ANIMATIONS_COUNT, config.cat_x_offset, config.cat_y_offset); + break; + case config_animation_sprite_sheet_layout_t::Custom: + switch (config.animation_custom_set) { + case config_animation_custom_set_t::None: + break; + case config_animation_custom_set_t::misc: + assert(config._loaded_animation_fqname); + assert(MISC_ANIM_COUNT <= INT32_MAX); + BONGOCAT_LOG_DEBUG(" Misc: '%s' %03d/%03d at offset (%d,%d)", config._loaded_animation_fqname, + config.animation_index, MISC_ANIMATIONS_COUNT, config.cat_x_offset, config.cat_y_offset); + break; + case config_animation_custom_set_t::pmd: + assert(config._loaded_animation_fqname); + assert(PMD_ANIM_COUNT <= INT32_MAX); + BONGOCAT_LOG_DEBUG(" pkmn pmd: '%s' %04d/%04d at offset (%d,%d)", config._loaded_animation_fqname, + config.animation_index, PMD_ANIMATIONS_COUNT, config.cat_x_offset, config.cat_y_offset); + break; + case config_animation_custom_set_t::custom: + assert(config.custom_sprite_sheet_filename); + assert(config._custom); + BONGOCAT_LOG_DEBUG(" Custom: %s at offset (%d,%d)", config.custom_sprite_sheet_filename, config.cat_x_offset, + config.cat_y_offset); + break; + } + break; + } + BONGOCAT_LOG_DEBUG(" FPS: %d, Opacity: %d, Random: %d", config.fps, config.overlay_opacity, config.randomize_index); + BONGOCAT_LOG_DEBUG(" Mirror: X=%d, Y=%d", config.mirror_x, config.mirror_y); + BONGOCAT_LOG_DEBUG(" Anti-aliasing: %s", config.enable_antialiasing ? "enabled" : "disabled"); + BONGOCAT_LOG_DEBUG(" Position: %s", config.overlay_position == overlay_position_t::POSITION_TOP ? "top" : "bottom"); + BONGOCAT_LOG_DEBUG(" Alignment: %d", config.cat_align, + config.cat_align == align_type_t::ALIGN_CENTER ? "(center)" : ""); + BONGOCAT_LOG_DEBUG(" Layer: %s", config.layer == layer_type_t::LAYER_TOP ? "top" : "overlay"); + BONGOCAT_LOG_DEBUG(" Output Screen: %s", config.output_name); +} - // Log configuration summary - config_log_summary(ret); +// ============================================================================= +// PUBLIC API IMPLEMENTATION +// ============================================================================= - return ret; +created_result_t load(const char *config_file_path, load_config_overwrite_parameters_t overwrite_parameters) { + BONGOCAT_CHECK_NULL(config_file_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + config_t ret; + set_defaults(ret); + + // Parse config file and override defaults + bongocat_error_t result = bongocat_error_t::BONGOCAT_ERROR_CONFIG; + if (strcmp(config_file_path, "-") == 0) { + result = config_parse_stdin(ret, overwrite_parameters); + } else { + result = config_parse_file(ret, config_file_path, overwrite_parameters); + } + if (result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to parse configuration file: %s", bongocat::error_string(result)); + return result; + } + + if (overwrite_parameters.output_name) { + if (ret.output_name) + ::free(ret.output_name); + ret.output_name = strdup(overwrite_parameters.output_name); + } + if (overwrite_parameters.randomize_index >= 0) { + ret.randomize_index = overwrite_parameters.randomize_index ? 1 : 0; + } + if (overwrite_parameters.strict >= 0) { + ret._strict = overwrite_parameters.strict >= 1; + } + + if (ret.input_fps <= 0) { + ret.input_fps = ret.fps; + } + + // Set default keyboard device if none specified + if (ret.num_keyboard_devices == 0) { + if (!ret._strict) { + result = config_set_default_devices(ret); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to set default keyboard devices: %s", bongocat::error_string(result)); + return result; + } } - - void reset(config_t& config) { - config_cleanup_devices(config); - set_defaults(config); + } + + // Validate and sanitize configuration + result = config_validate(ret); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Configuration validation failed: %s", bongocat::error_string(result)); + return result; + } + + if (ret.num_keyboard_devices == 0) { + if (!ret._strict) { + // Set default keyboard device if none specified + result = config_set_default_devices(ret); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to set default keyboard devices: %s", bongocat::error_string(result)); + return result; + } else { + BONGOCAT_LOG_INFO("No device loaded, use default keyboard device: %s", DEFAULT_DEVICE); + } + } else { + BONGOCAT_LOG_INFO("No device loaded"); } -} \ No newline at end of file + } + + // Log configuration summary + config_log_summary(ret); + + return ret; +} + +void reset(config_t& config) { + config_cleanup_devices(config); + set_defaults(config); +} +} // namespace bongocat::config \ No newline at end of file diff --git a/src/config/config_watcher.cpp b/src/config/config_watcher.cpp index 501592f5..1f991920 100644 --- a/src/config/config_watcher.cpp +++ b/src/config/config_watcher.cpp @@ -1,285 +1,286 @@ #include "config/config_watcher.h" -#include "utils/time.h" + +#include "platform/wayland_context.h" #include "utils/error.h" #include "utils/system_memory.h" -#include -#include +#include "utils/time.h" + #include -#include -#include +#include +#include #include -#include +#include #include -#include - -#include "platform/wayland_context.h" +#include +#include +#include namespace bongocat::config { - static inline constexpr int MAX_ATTEMPTS = 2048; - static inline constexpr int RECREATE_MAX_ATTEMPTS = 10; - static inline constexpr platform::time_ms_t RELOAD_DEBOUNCE_MS = 1000; - static inline constexpr platform::time_ms_t RELOAD_DELAY_MS = 100; - static inline constexpr platform::time_ms_t RECREATE_SLEEP_ATTEMPT_MS = 100; - static inline constexpr platform::time_ms_t TIMEOUT_MS = 100; - - static constexpr uint32_t FILE_MASK = IN_MODIFY | IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVE_SELF | IN_DELETE_SELF; - static constexpr uint32_t DIR_MASK = IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM; - - static bongocat_error_t reinitialize_inotify(config_watcher_t& watcher) { - if (watcher.inotify_fd._fd >= 0) { - platform::close_fd(watcher.inotify_fd); - } - - watcher.inotify_fd = platform::FileDescriptor(inotify_init1(IN_NONBLOCK | IN_CLOEXEC)); - if (watcher.inotify_fd._fd < 0) { - BONGOCAT_LOG_ERROR("config_watcher: Failed to reinit inotify: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - - watcher.wd_dir = platform::FileDescriptor(inotify_add_watch(watcher.inotify_fd._fd, dirname(watcher.config_path), DIR_MASK)); - watcher.wd_file = platform::FileDescriptor(inotify_add_watch(watcher.inotify_fd._fd, watcher.config_path, FILE_MASK)); - - if (watcher.wd_dir._fd < 0 || watcher.wd_file._fd < 0) { - BONGOCAT_LOG_WARNING("config_watcher: partial reinit of inotify watches"); - } +static inline constexpr int MAX_ATTEMPTS = 2048; +static inline constexpr int RECREATE_MAX_ATTEMPTS = 10; +static inline constexpr platform::time_ms_t RELOAD_DEBOUNCE_MS = 1000; +static inline constexpr platform::time_ms_t RELOAD_DELAY_MS = 100; +static inline constexpr platform::time_ms_t RECREATE_SLEEP_ATTEMPT_MS = 100; +static inline constexpr platform::time_ms_t TIMEOUT_MS = 100; + +static constexpr uint32_t FILE_MASK = IN_MODIFY | IN_CLOSE_WRITE | IN_ATTRIB | IN_MOVE_SELF | IN_DELETE_SELF; +static constexpr uint32_t DIR_MASK = IN_CREATE | IN_MOVED_TO | IN_DELETE | IN_MOVED_FROM; + +static bongocat_error_t reinitialize_inotify(config_watcher_t& watcher) { + if (watcher.inotify_fd._fd >= 0) { + platform::close_fd(watcher.inotify_fd); + } + + watcher.inotify_fd = platform::FileDescriptor(inotify_init1(IN_NONBLOCK | IN_CLOEXEC)); + if (watcher.inotify_fd._fd < 0) { + BONGOCAT_LOG_ERROR("config_watcher: Failed to reinit inotify: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + watcher.wd_dir = + platform::FileDescriptor(inotify_add_watch(watcher.inotify_fd._fd, dirname(watcher.config_path), DIR_MASK)); + watcher.wd_file = platform::FileDescriptor(inotify_add_watch(watcher.inotify_fd._fd, watcher.config_path, FILE_MASK)); + + if (watcher.wd_dir._fd < 0 || watcher.wd_file._fd < 0) { + BONGOCAT_LOG_WARNING("config_watcher: partial reinit of inotify watches"); + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} - return bongocat_error_t::BONGOCAT_SUCCESS; +static void *config_watcher_thread(void *arg) { + assert(arg); + auto& watcher = *static_cast(arg); + + char buffer[INOTIFY_BUF_LEN] = {}; + platform::timestamp_ms_t last_reload_timestamp = platform::get_current_time_ms(); + + constexpr size_t fds_inotify_index = 0; + constexpr nfds_t fds_count = 1; + pollfd fds[fds_count] = { + {.fd = watcher.inotify_fd._fd, .events = POLLIN, .revents = 0}, + }; + constexpr int timeout_ms = TIMEOUT_MS; + + BONGOCAT_LOG_INFO("config_watcher: Config watcher started for: %s", watcher.config_path); + + atomic_store(&watcher._running, true); + while (atomic_load(&watcher._running)) { + int ret = poll(fds, fds_count, timeout_ms); + if (ret < 0) { + if (errno == EINTR) + continue; + BONGOCAT_LOG_ERROR("config_watcher: Config watcher poll failed: %s", strerror(errno)); + break; } - - static void *config_watcher_thread(void *arg) { - assert(arg); - auto& watcher = *static_cast(arg); - - char buffer[INOTIFY_BUF_LEN] = {}; - platform::timestamp_ms_t last_reload_timestamp = platform::get_current_time_ms(); - - constexpr size_t fds_inotify_index = 0; - constexpr nfds_t fds_count = 1; - pollfd fds[fds_count] = { - { .fd = watcher.inotify_fd._fd, .events = POLLIN, .revents = 0 }, - }; - constexpr int timeout_ms = TIMEOUT_MS; - - BONGOCAT_LOG_INFO("config_watcher: Config watcher started for: %s", watcher.config_path); - - atomic_store(&watcher._running, true); - while (atomic_load(&watcher._running)) { - int ret = poll(fds, fds_count, timeout_ms); - if (ret < 0) { - if (errno == EINTR) continue; - BONGOCAT_LOG_ERROR("config_watcher: Config watcher poll failed: %s", strerror(errno)); - break; - } - if (!atomic_load(&watcher._running)) { - // draining pools - for (size_t i = 0; i < fds_count; i++) { - if (fds[i].revents & POLLIN) { - int attempts = 0; - uint64_t u; - while (read(fds[i].fd, &u, sizeof(uint64_t)) == sizeof(uint64_t) && attempts < MAX_ATTEMPTS) { - attempts++; - } - } - } - break; - } - - if (fds[fds_inotify_index].revents & POLLIN) { - const ssize_t length = read(watcher.inotify_fd._fd, buffer, config::INOTIFY_BUF_LEN); - if (length <= 0) { - if (errno == EINVAL || errno == EBADF) { - BONGOCAT_LOG_WARNING("config_watcher: inotify fd invalidated (likely after suspend). Reinitializing..."); - reinitialize_inotify(watcher); - continue; - } - BONGOCAT_LOG_ERROR("config_watcher: Config watcher read failed: %s", strerror(errno)); - continue; - } - - bool should_reload = false; - bool file_went_away = false; - bool file_recreated = false; - - ssize_t i = 0; - int attempts = 0; - while (i < length && attempts < MAX_ATTEMPTS) { - const auto *event = reinterpret_cast(&buffer[i]); - assert(event); - - BONGOCAT_LOG_VERBOSE("config_watcher: inotify event: wd=%d mask=0x%08X name=%s", - event->wd, event->mask, - event->len ? event->name : "(none)"); - - // File events - if (event->wd == watcher.wd_file._fd) { - should_reload |= event->mask & (IN_MODIFY | IN_CLOSE_WRITE | IN_ATTRIB); - file_went_away |= event->mask & (IN_MOVE_SELF | IN_DELETE_SELF); - } - // Directory events (watch for recreate) - else if (event->wd == watcher.wd_dir._fd) { - if ((event->mask & (IN_CREATE | IN_MOVED_TO)) && event->len > 0) { - file_recreated |= strcmp(event->name, basename(watcher.config_path)) == 0; - } - } - // Inotify queue overflow (critical) - else if (event->mask & IN_Q_OVERFLOW) { - BONGOCAT_LOG_WARNING("config_watcher: inotify event queue overflow, forcing full reload"); - should_reload = true; - } - - - assert(config::INOTIFY_EVENT_SIZE <= SSIZE_MAX); - i += static_cast(config::INOTIFY_EVENT_SIZE) + event->len; - attempts++; - } - - // Handle file disappearance - if (file_went_away && watcher.wd_file._fd >= 0) { - BONGOCAT_LOG_VERBOSE("config_watcher: Config file went away; removing file watch"); - inotify_rm_watch(watcher.inotify_fd._fd, watcher.wd_file._fd); - watcher.wd_file._fd = -1; - // the file is gone, wait for recreation - should_reload = false; - } - - // Handle recreation - if (file_recreated) { - BONGOCAT_LOG_VERBOSE("config_watcher: Config file recreated; re-adding file watch"); - int new_wd = inotify_add_watch(watcher.inotify_fd._fd, watcher.config_path, FILE_MASK); - if (new_wd >= 0) { - watcher.wd_file = platform::FileDescriptor(new_wd); - new_wd = -1; - should_reload = true; - } else { - BONGOCAT_LOG_ERROR("config_watcher: Failed to re-add file watch: %s", strerror(errno)); - should_reload = false; - } - } - - // ensure file exists (from IN_CLOSE_WRITE/IN_MODIFY) - if (should_reload) { - // If we don't currently have a file watch (it was removed), try stat the file - bool file_exists = false; - if (watcher.wd_file._fd >= 0) { - file_exists = true; - } else { - // small retry loop to handle race where create races with our handling - struct stat st{}; - for (int attempt = 0; attempt < RECREATE_MAX_ATTEMPTS; attempt++) { - if (stat(watcher.config_path, &st) == 0) { - file_exists = true; - break; - } - // small sleep before next stat - usleep(RECREATE_SLEEP_ATTEMPT_MS*1000); - } - } - - if (!file_exists) { - BONGOCAT_LOG_VERBOSE("config_watcher: Reload skipped: config file not present yet (will wait for recreate)"); - should_reload = false; - } - } - - // Debounce reloads - if (should_reload) { - // Debounce: only reload if at least some time have passed since last reload - const platform::timestamp_ms_t now = platform::get_current_time_ms(); - if (now - last_reload_timestamp >= RELOAD_DEBOUNCE_MS) { - // Small delay to ensure file write is complete - usleep(RELOAD_DELAY_MS*1000); - last_reload_timestamp = now; - - BONGOCAT_LOG_INFO("config_watcher: Config file changed, trigger reload"); - uint64_t u = 1; - if (write(watcher.reload_efd._fd, &u, sizeof(uint64_t)) >= 0) { - BONGOCAT_LOG_DEBUG("config_watcher: Write reload event in watcher"); - } else { - BONGOCAT_LOG_ERROR("config_watcher: Failed to write to notify pipe in watcher: %s", strerror(errno)); - } - } - } - } + if (!atomic_load(&watcher._running)) { + // draining pools + for (size_t i = 0; i < fds_count; i++) { + if (fds[i].revents & POLLIN) { + int attempts = 0; + uint64_t u; + while (read(fds[i].fd, &u, sizeof(uint64_t)) == sizeof(uint64_t) && attempts < MAX_ATTEMPTS) { + attempts++; + } } - atomic_store(&watcher._running, false); - - BONGOCAT_LOG_INFO("config_watcher: Config watcher stopped"); - return nullptr; + } + break; } - created_result_t> create_watcher(const char *config_path) { - BONGOCAT_CHECK_NULL(config_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) [[unlikely]] { - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + if (fds[fds_inotify_index].revents & POLLIN) { + const ssize_t length = read(watcher.inotify_fd._fd, buffer, config::INOTIFY_BUF_LEN); + if (length <= 0) { + if (errno == EINVAL || errno == EBADF) { + BONGOCAT_LOG_WARNING("config_watcher: inotify fd invalidated (likely after suspend). Reinitializing..."); + reinitialize_inotify(watcher); + continue; } - - // Store config path - ret->config_path = strdup(config_path); - if (!ret->config_path) [[unlikely]] { - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + BONGOCAT_LOG_ERROR("config_watcher: Config watcher read failed: %s", strerror(errno)); + continue; + } + + bool should_reload = false; + bool file_went_away = false; + bool file_recreated = false; + + ssize_t i = 0; + int attempts = 0; + while (i < length && attempts < MAX_ATTEMPTS) { + const auto *event = reinterpret_cast(&buffer[i]); + assert(event); + + BONGOCAT_LOG_VERBOSE("config_watcher: inotify event: wd=%d mask=0x%08X name=%s", event->wd, event->mask, + event->len ? event->name : "(none)"); + + // File events + if (event->wd == watcher.wd_file._fd) { + should_reload |= event->mask & (IN_MODIFY | IN_CLOSE_WRITE | IN_ATTRIB); + file_went_away |= event->mask & (IN_MOVE_SELF | IN_DELETE_SELF); } - - // Initialize inotify - ret->inotify_fd = platform::FileDescriptor(inotify_init1(IN_NONBLOCK)); - if (ret->inotify_fd._fd < 0) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to initialize inotify: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + // Directory events (watch for recreate) + else if (event->wd == watcher.wd_dir._fd) { + if ((event->mask & (IN_CREATE | IN_MOVED_TO)) && event->len > 0) { + file_recreated |= strcmp(event->name, basename(watcher.config_path)) == 0; + } } - - constexpr size_t dirbuf_size = PATH_MAX; - char dirbuf[dirbuf_size] = {0}; - strncpy(dirbuf, config_path, dirbuf_size-1); - dirbuf[dirbuf_size-1] = '\0'; - const char* dir = dirname(dirbuf); - if (!dir) [[unlikely]] { - BONGOCAT_LOG_ERROR("dirname() failed for path %s", ret->config_path); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + // Inotify queue overflow (critical) + else if (event->mask & IN_Q_OVERFLOW) { + BONGOCAT_LOG_WARNING("config_watcher: inotify event queue overflow, forcing full reload"); + should_reload = true; } - ret->wd_file = platform::FileDescriptor(inotify_add_watch(ret->inotify_fd._fd, ret->config_path, FILE_MASK)); - if (ret->wd_file._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to add inotify watch for file %s: %s", ret->config_path, strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + assert(config::INOTIFY_EVENT_SIZE <= SSIZE_MAX); + i += static_cast(config::INOTIFY_EVENT_SIZE) + event->len; + attempts++; + } + + // Handle file disappearance + if (file_went_away && watcher.wd_file._fd >= 0) { + BONGOCAT_LOG_VERBOSE("config_watcher: Config file went away; removing file watch"); + inotify_rm_watch(watcher.inotify_fd._fd, watcher.wd_file._fd); + watcher.wd_file._fd = -1; + // the file is gone, wait for recreation + should_reload = false; + } + + // Handle recreation + if (file_recreated) { + BONGOCAT_LOG_VERBOSE("config_watcher: Config file recreated; re-adding file watch"); + int new_wd = inotify_add_watch(watcher.inotify_fd._fd, watcher.config_path, FILE_MASK); + if (new_wd >= 0) { + watcher.wd_file = platform::FileDescriptor(new_wd); + new_wd = -1; + should_reload = true; + } else { + BONGOCAT_LOG_ERROR("config_watcher: Failed to re-add file watch: %s", strerror(errno)); + should_reload = false; } - - ret->wd_dir = platform::FileDescriptor(inotify_add_watch(ret->inotify_fd._fd, dir, DIR_MASK)); - if (ret->wd_dir._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to add inotify watch for dir %s: %s", dir, strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + // ensure file exists (from IN_CLOSE_WRITE/IN_MODIFY) + if (should_reload) { + // If we don't currently have a file watch (it was removed), try stat the file + bool file_exists = false; + if (watcher.wd_file._fd >= 0) { + file_exists = true; + } else { + // small retry loop to handle race where create races with our handling + struct stat st{}; + for (int attempt = 0; attempt < RECREATE_MAX_ATTEMPTS; attempt++) { + if (stat(watcher.config_path, &st) == 0) { + file_exists = true; + break; + } + // small sleep before next stat + usleep(RECREATE_SLEEP_ATTEMPT_MS * 1000); + } } - ret->reload_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->reload_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for config reload: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + if (!file_exists) { + BONGOCAT_LOG_VERBOSE("config_watcher: Reload skipped: config file not present yet (will wait for recreate)"); + should_reload = false; } - - return ret; + } + + // Debounce reloads + if (should_reload) { + // Debounce: only reload if at least some time have passed since last reload + const platform::timestamp_ms_t now = platform::get_current_time_ms(); + if (now - last_reload_timestamp >= RELOAD_DEBOUNCE_MS) { + // Small delay to ensure file write is complete + usleep(RELOAD_DELAY_MS * 1000); + last_reload_timestamp = now; + + BONGOCAT_LOG_INFO("config_watcher: Config file changed, trigger reload"); + uint64_t u = 1; + if (write(watcher.reload_efd._fd, &u, sizeof(uint64_t)) >= 0) { + BONGOCAT_LOG_DEBUG("config_watcher: Write reload event in watcher"); + } else { + BONGOCAT_LOG_ERROR("config_watcher: Failed to write to notify pipe in watcher: %s", strerror(errno)); + } + } + } } + } + atomic_store(&watcher._running, false); - void start_watcher(config_watcher_t& watcher) { - if (pthread_create(&watcher._watcher_thread, nullptr, config_watcher_thread, &watcher) != 0) { - atomic_store(&watcher._running, false); - BONGOCAT_LOG_ERROR("Failed to create config watcher thread: %s", strerror(errno)); - return; - } + BONGOCAT_LOG_INFO("config_watcher: Config watcher stopped"); + return nullptr; +} - BONGOCAT_LOG_INFO("Config watcher thread started"); - } +created_result_t> create_watcher(const char *config_path) { + BONGOCAT_CHECK_NULL(config_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + AllocatedMemory ret = make_allocated_memory(); + assert(ret != nullptr); + if (ret == nullptr) [[unlikely]] { + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // Store config path + ret->config_path = strdup(config_path); + if (!ret->config_path) [[unlikely]] { + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // Initialize inotify + ret->inotify_fd = platform::FileDescriptor(inotify_init1(IN_NONBLOCK)); + if (ret->inotify_fd._fd < 0) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to initialize inotify: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + constexpr size_t dirbuf_size = PATH_MAX; + char dirbuf[dirbuf_size] = {0}; + strncpy(dirbuf, config_path, dirbuf_size - 1); + dirbuf[dirbuf_size - 1] = '\0'; + const char *dir = dirname(dirbuf); + if (!dir) [[unlikely]] { + BONGOCAT_LOG_ERROR("dirname() failed for path %s", ret->config_path); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + ret->wd_file = platform::FileDescriptor(inotify_add_watch(ret->inotify_fd._fd, ret->config_path, FILE_MASK)); + if (ret->wd_file._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to add inotify watch for file %s: %s", ret->config_path, strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + ret->wd_dir = platform::FileDescriptor(inotify_add_watch(ret->inotify_fd._fd, dir, DIR_MASK)); + if (ret->wd_dir._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to add inotify watch for dir %s: %s", dir, strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + ret->reload_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->reload_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for config reload: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + return ret; +} - void stop_watcher(config_watcher_t& watcher) { - atomic_store(&watcher._running, false); - if (watcher._watcher_thread) { - BONGOCAT_LOG_DEBUG("Stopping config watcher thread"); - //pthread_cancel(watcher->_watcher_thread); - // Wait for thread to finish - if (platform::stop_thread_graceful_or_cancel(watcher._watcher_thread, watcher._running) != 0) { - BONGOCAT_LOG_ERROR("Failed to join config watcher thread: %s", strerror(errno)); - } - BONGOCAT_LOG_DEBUG("config watcher thread terminated"); - } - watcher._watcher_thread = 0; +void start_watcher(config_watcher_t& watcher) { + if (pthread_create(&watcher._watcher_thread, nullptr, config_watcher_thread, &watcher) != 0) { + atomic_store(&watcher._running, false); + BONGOCAT_LOG_ERROR("Failed to create config watcher thread: %s", strerror(errno)); + return; + } + + BONGOCAT_LOG_INFO("Config watcher thread started"); +} + +void stop_watcher(config_watcher_t& watcher) { + atomic_store(&watcher._running, false); + if (watcher._watcher_thread) { + BONGOCAT_LOG_DEBUG("Stopping config watcher thread"); + // pthread_cancel(watcher->_watcher_thread); + // Wait for thread to finish + if (platform::stop_thread_graceful_or_cancel(watcher._watcher_thread, watcher._running) != 0) { + BONGOCAT_LOG_ERROR("Failed to join config watcher thread: %s", strerror(errno)); } + BONGOCAT_LOG_DEBUG("config watcher thread terminated"); + } + watcher._watcher_thread = 0; } +} // namespace bongocat::config diff --git a/src/core/main.cpp b/src/core/main.cpp index d58393c9..213cc2aa 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -1,1016 +1,1083 @@ +#include "config/config.h" #include "core/bongocat.h" -#include "platform/wayland.h" #include "graphics/animation.h" +#include "image_loader/load_images.h" #include "platform/input.h" #include "platform/update.h" -#include "config/config.h" +#include "platform/wayland.h" #include "utils/error.h" #include "utils/memory.h" + +#include +#include #include -#include -#include -#include -#include #include -#include +#include #include -#include #include -#include +#include +#include +#include #include -#include "image_loader/load_images.h" - // ============================================================================= // GLOBAL STATE AND CONFIGURATION // ============================================================================= namespace bongocat { - static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_MS = 5000; - static inline constexpr platform::time_ms_t SLEEP_WAIT_FOR_SHUTDOWN_MS = 100; - static_assert(SLEEP_WAIT_FOR_SHUTDOWN_MS > 0); - - static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS = 5000; - static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_INPUT_THREAD_MS = 2000; - static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_UPDATE_THREAD_MS = 5000; - static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_CONFIG_WATCHER_THREAD_MS = 1000; - inline static constexpr platform::time_ms_t COND_RELOAD_CONFIG_TIMEOUT_MS = 5000; - inline static constexpr platform::time_ms_t COND_INIT_TIMEOUT_MS = 5000; - - inline static constexpr platform::time_ms_t WAIT_FOR_FLUSH_BEFORE_EXIT_MS = 100; - - struct main_context_t; - void stop_threads(main_context_t& context); - void cleanup(main_context_t& context); - - struct main_context_t { - volatile sig_atomic_t running {0}; - platform::FileDescriptor signal_fd {-1}; - - config::config_t config; - config::load_config_overwrite_parameters_t overwrite_config_parameters; - - AllocatedMemory config_watcher; - AllocatedMemory input; - AllocatedMemory update; - AllocatedMemory animation; - AllocatedMemory wayland; - - const char *signal_watch_path{nullptr}; - atomic_uint64_t config_generation{0}; - platform::CondVariable configs_reloaded_cond{}; - platform::Mutex sync_configs; - - char* pid_filename{nullptr}; - char* default_config_filename{nullptr}; - - main_context_t() = default; - ~main_context_t() { - cleanup(*this); - } - main_context_t(const main_context_t&) = delete; - main_context_t& operator=(const main_context_t&) = delete; - main_context_t(main_context_t&& other) noexcept = delete; - main_context_t& operator=(main_context_t&& other) noexcept = delete; - }; - inline void stop_threads(main_context_t& context) { - context.running = 0; - // stop threads - if (context.animation != nullptr) atomic_store(&context.animation->anim._animation_running, false); - if (context.input != nullptr) atomic_store(&context.input->_capture_input_running, false); - if (context.update != nullptr) atomic_store(&context.update->_running, false); - if (context.config_watcher != nullptr) atomic_store(&context.config_watcher->_running, false); - - // wait for threads - if (context.animation != nullptr) platform::join_thread_with_timeout(context.animation->anim._anim_thread, WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS); - if (context.input != nullptr) platform::join_thread_with_timeout(context.input->_input_thread, WAIT_FOR_SHUTDOWN_INPUT_THREAD_MS); - if (context.update != nullptr) platform::join_thread_with_timeout(context.update->_update_thread, WAIT_FOR_SHUTDOWN_UPDATE_THREAD_MS); - if (context.config_watcher != nullptr) platform::join_thread_with_timeout(context.config_watcher->_watcher_thread, WAIT_FOR_SHUTDOWN_CONFIG_WATCHER_THREAD_MS); - - // stop threads - if (context.animation != nullptr) animation::stop(*context.animation); - if (context.input != nullptr) platform::input::stop(*context.input); - if (context.update != nullptr) platform::update::stop(*context.update); - if (context.config_watcher != nullptr) config::stop_watcher(*context.config_watcher); - - context.config_generation = 0; - } - void cleanup(main_context_t& context) { - stop_threads(context); - - // Cleanup Wayland - if (context.wayland != nullptr) cleanup_wayland(*context.wayland); - - // remove references (avoid dangling pointers) - if (context.wayland != nullptr) context.wayland->animation_trigger_context = nullptr; - if (context.animation != nullptr) context.animation->_input = nullptr; - - // Cleanup systems - if (context.animation != nullptr) cleanup(*context.animation); - if (context.input != nullptr) cleanup(*context.input); - if (context.update != nullptr) cleanup(*context.update); - if (context.signal_fd._fd >= 0) close_fd(context.signal_fd); - if (context.config_watcher != nullptr) cleanup_watcher(*context.config_watcher); - context.signal_watch_path = nullptr; - - release_allocated_memory(context.config_watcher); - release_allocated_memory(context.input); - release_allocated_memory(context.update); - release_allocated_memory(context.animation); - release_allocated_memory(context.wayland); - - // Cleanup configuration - cleanup(context.config); - context.overwrite_config_parameters.output_name = nullptr; - - // cleanup signals handler - platform::close_fd(context.signal_fd); - - if (context.pid_filename) ::free(context.pid_filename); - context.pid_filename = nullptr; - - if (context.default_config_filename) ::free(context.default_config_filename); - context.default_config_filename = nullptr; - } - - inline main_context_t& get_main_context() { - static main_context_t g_instance; - return g_instance; - } - - // ============================================================================= - // COMMAND LINE ARGUMENTS STRUCTURE - // ============================================================================= - - struct cli_args_t { - const char *config_file{nullptr}; - bool watch_config{false}; - bool toggle_mode{false}; - bool show_help{false}; - bool show_version{false}; - const char *output_name{nullptr}; - int32_t randomize_index{-1}; - int32_t strict{-1}; - bool ignore_running{false}; - int64_t nr{-1}; - - bool nr_set{false}; - bool output_name_set{false}; - bool config_file_set{false}; - }; - - // ============================================================================= - // PROCESS MANAGEMENT MODULE - // ============================================================================= - - inline static constexpr size_t PID_STR_BUF = 64; - - inline static constexpr auto DEFAULT_PID_FILE = "/tmp/bongocat.pid"; - inline static constexpr auto PID_FILE_WITH_SUFFIX_TEMPLATE = "/tmp/bongocat-%s.pid"; - inline static constexpr auto PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE = "/tmp/bongocat-%s.%" PRIu32 ".pid"; - inline static constexpr auto PID_FILE_WITH_SUFFIX_NR_TEMPLATE = "/tmp/bongocat-%" PRId64 ".pid"; - - inline static constexpr auto DEFAULT_CONF_FILENAME = "bongocat.conf"; - - static platform::FileDescriptor process_create_pid_file(const char *pid_filename) { - platform::FileDescriptor fd = platform::FileDescriptor(open(pid_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)); - if (fd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create PID file: %s", strerror(errno)); - return platform::FileDescriptor(-1); - } - - if (flock(fd._fd, LOCK_EX | LOCK_NB) < 0) { - if (errno == EWOULDBLOCK) { - BONGOCAT_LOG_INFO("Another instance is already running"); - return platform::FileDescriptor(-2); // Already running - } - BONGOCAT_LOG_ERROR("Failed to lock PID file: %s", strerror(errno)); - return platform::FileDescriptor(-1); - } - - char pid_str[PID_STR_BUF] = {}; - snprintf(pid_str, sizeof(pid_str), "%d\n", getpid()); - if (write(fd._fd, pid_str, strlen(pid_str)) < 0) { - BONGOCAT_LOG_ERROR("Failed to write PID to file: %s", strerror(errno)); - return platform::FileDescriptor(-1); - } - - return fd; // Keep file descriptor open to maintain lock - } - - static void process_remove_pid_file(const char* pid_filename) { - assert(pid_filename); - unlink(pid_filename); - } +static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_MS = 5000; +static inline constexpr platform::time_ms_t SLEEP_WAIT_FOR_SHUTDOWN_MS = 100; +static_assert(SLEEP_WAIT_FOR_SHUTDOWN_MS > 0); + +static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS = 5000; +static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_INPUT_THREAD_MS = 2000; +static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_UPDATE_THREAD_MS = 5000; +static inline constexpr platform::time_ms_t WAIT_FOR_SHUTDOWN_CONFIG_WATCHER_THREAD_MS = 1000; +inline static constexpr platform::time_ms_t COND_RELOAD_CONFIG_TIMEOUT_MS = 5000; +inline static constexpr platform::time_ms_t COND_INIT_TIMEOUT_MS = 5000; + +inline static constexpr platform::time_ms_t WAIT_FOR_FLUSH_BEFORE_EXIT_MS = 100; + +struct main_context_t; +void stop_threads(main_context_t& context); +void cleanup(main_context_t& context); + +struct main_context_t { + volatile sig_atomic_t running{0}; + platform::FileDescriptor signal_fd{-1}; + + config::config_t config; + config::load_config_overwrite_parameters_t overwrite_config_parameters; + + AllocatedMemory config_watcher; + AllocatedMemory input; + AllocatedMemory update; + AllocatedMemory animation; + AllocatedMemory wayland; + + const char *signal_watch_path{nullptr}; + atomic_uint64_t config_generation{0}; + platform::CondVariable configs_reloaded_cond{}; + platform::Mutex sync_configs; + + char *pid_filename{nullptr}; + char *default_config_filename{nullptr}; + + main_context_t() = default; + ~main_context_t() { + cleanup(*this); + } + main_context_t(const main_context_t&) = delete; + main_context_t& operator=(const main_context_t&) = delete; + main_context_t(main_context_t&& other) noexcept = delete; + main_context_t& operator=(main_context_t&& other) noexcept = delete; +}; +inline void stop_threads(main_context_t& context) { + context.running = 0; + // stop threads + if (context.animation != nullptr) + atomic_store(&context.animation->anim._animation_running, false); + if (context.input != nullptr) + atomic_store(&context.input->_capture_input_running, false); + if (context.update != nullptr) + atomic_store(&context.update->_running, false); + if (context.config_watcher != nullptr) + atomic_store(&context.config_watcher->_running, false); + + // wait for threads + if (context.animation != nullptr) + platform::join_thread_with_timeout(context.animation->anim._anim_thread, WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS); + if (context.input != nullptr) + platform::join_thread_with_timeout(context.input->_input_thread, WAIT_FOR_SHUTDOWN_INPUT_THREAD_MS); + if (context.update != nullptr) + platform::join_thread_with_timeout(context.update->_update_thread, WAIT_FOR_SHUTDOWN_UPDATE_THREAD_MS); + if (context.config_watcher != nullptr) + platform::join_thread_with_timeout(context.config_watcher->_watcher_thread, + WAIT_FOR_SHUTDOWN_CONFIG_WATCHER_THREAD_MS); + + // stop threads + if (context.animation != nullptr) + animation::stop(*context.animation); + if (context.input != nullptr) + platform::input::stop(*context.input); + if (context.update != nullptr) + platform::update::stop(*context.update); + if (context.config_watcher != nullptr) + config::stop_watcher(*context.config_watcher); + + context.config_generation = 0; +} +void cleanup(main_context_t& context) { + stop_threads(context); + + // Cleanup Wayland + if (context.wayland != nullptr) + cleanup_wayland(*context.wayland); + + // remove references (avoid dangling pointers) + if (context.wayland != nullptr) + context.wayland->animation_trigger_context = nullptr; + if (context.animation != nullptr) + context.animation->_input = nullptr; + + // Cleanup systems + if (context.animation != nullptr) + cleanup(*context.animation); + if (context.input != nullptr) + cleanup(*context.input); + if (context.update != nullptr) + cleanup(*context.update); + if (context.signal_fd._fd >= 0) + close_fd(context.signal_fd); + if (context.config_watcher != nullptr) + cleanup_watcher(*context.config_watcher); + context.signal_watch_path = nullptr; + + release_allocated_memory(context.config_watcher); + release_allocated_memory(context.input); + release_allocated_memory(context.update); + release_allocated_memory(context.animation); + release_allocated_memory(context.wayland); + + // Cleanup configuration + cleanup(context.config); + context.overwrite_config_parameters.output_name = nullptr; + + // cleanup signals handler + platform::close_fd(context.signal_fd); + + if (context.pid_filename) + ::free(context.pid_filename); + context.pid_filename = nullptr; + + if (context.default_config_filename) + ::free(context.default_config_filename); + context.default_config_filename = nullptr; +} - static pid_t process_get_running_pid(const char* program_name, const char* pid_filename) { - assert(program_name); - assert(pid_filename); - platform::FileDescriptor fd = platform::FileDescriptor(::open(pid_filename, O_RDONLY)); - if (fd._fd < 0) { - return -1; // No PID file exists - } +inline main_context_t& get_main_context() { + static main_context_t g_instance; + return g_instance; +} - // Try to get a shared lock to read the file - if (flock(fd._fd, LOCK_SH | LOCK_NB) < 0) { - if (errno == EWOULDBLOCK) { - // File is locked by another process, so it's running - // We need to read the PID anyway, so let's try without lock - fd = platform::FileDescriptor(::open(pid_filename, O_RDONLY)); - if (fd._fd < 0) return -1; - } else { - return -1; - } - } +// ============================================================================= +// COMMAND LINE ARGUMENTS STRUCTURE +// ============================================================================= - char pid_str[PID_STR_BUF] = {0}; - const ssize_t bytes_read = read(fd._fd, pid_str, sizeof(pid_str) - 1); - platform::close_fd(fd); - if (bytes_read <= 0) { - return -1; - } - pid_str[bytes_read] = '\0'; - pid_str[strcspn(pid_str, "\r\n")] = '\0'; - for (char* p = pid_str; *p; ++p) { - if (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t') { - *p = '\0'; - break; - } - } +struct cli_args_t { + const char *config_file{nullptr}; + bool watch_config{false}; + bool toggle_mode{false}; + bool show_help{false}; + bool show_version{false}; + const char *output_name{nullptr}; + int32_t randomize_index{-1}; + int32_t strict{-1}; + bool ignore_running{false}; + int64_t nr{-1}; + + bool nr_set{false}; + bool output_name_set{false}; + bool config_file_set{false}; +}; +// ============================================================================= +// PROCESS MANAGEMENT MODULE +// ============================================================================= - char *endptr = nullptr; - errno = 0; // Reset errno before call - const auto pid = static_cast(strtol(pid_str, &endptr, 10)); - if (endptr == pid_str) { - return -1; // no digits at all - } - if ((errno == ERANGE) || pid < 0) { - BONGOCAT_LOG_ERROR("'%s' out of range for pid_t", pid_str); - return -1; - } +inline static constexpr size_t PID_STR_BUF = 64; - char exe_path[PATH_MAX] = {0}; - snprintf(exe_path, sizeof(exe_path), "/proc/%d/exe", pid); - char buf[PATH_MAX] = {0}; - ssize_t len = readlink(exe_path, buf, sizeof(buf) - 1); - if (len > 0) { - buf[len] = '\0'; +inline static constexpr auto DEFAULT_PID_FILE = "/tmp/bongocat.pid"; +inline static constexpr auto PID_FILE_WITH_SUFFIX_TEMPLATE = "/tmp/bongocat-%s.pid"; +inline static constexpr auto PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE = "/tmp/bongocat-%s.%" PRIu32 ".pid"; +inline static constexpr auto PID_FILE_WITH_SUFFIX_NR_TEMPLATE = "/tmp/bongocat-%" PRId64 ".pid"; - const char* exe_basename = strrchr(buf, '/'); - exe_basename = exe_basename ? exe_basename + 1 : buf; +inline static constexpr auto DEFAULT_CONF_FILENAME = "bongocat.conf"; - const char* prog_basename = strrchr(program_name, '/'); - prog_basename = prog_basename ? prog_basename + 1 : program_name; +static platform::FileDescriptor process_create_pid_file(const char *pid_filename) { + platform::FileDescriptor fd = platform::FileDescriptor(open(pid_filename, O_CREAT | O_WRONLY | O_TRUNC, 0644)); + if (fd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create PID file: %s", strerror(errno)); + return platform::FileDescriptor(-1); + } - if (strcmp(exe_basename, prog_basename) != 0) { - return -1; - } - } + if (flock(fd._fd, LOCK_EX | LOCK_NB) < 0) { + if (errno == EWOULDBLOCK) { + BONGOCAT_LOG_INFO("Another instance is already running"); + return platform::FileDescriptor(-2); // Already running + } + BONGOCAT_LOG_ERROR("Failed to lock PID file: %s", strerror(errno)); + return platform::FileDescriptor(-1); + } + + char pid_str[PID_STR_BUF] = {}; + snprintf(pid_str, sizeof(pid_str), "%d\n", getpid()); + if (write(fd._fd, pid_str, strlen(pid_str)) < 0) { + BONGOCAT_LOG_ERROR("Failed to write PID to file: %s", strerror(errno)); + return platform::FileDescriptor(-1); + } + + return fd; // Keep file descriptor open to maintain lock +} - // Check if process is actually running - if (kill(pid, 0) == 0) { - return pid; // Process is running - } +static void process_remove_pid_file(const char *pid_filename) { + assert(pid_filename); + unlink(pid_filename); +} +static pid_t process_get_running_pid(const char *program_name, const char *pid_filename) { + assert(program_name); + assert(pid_filename); + platform::FileDescriptor fd = platform::FileDescriptor(::open(pid_filename, O_RDONLY)); + if (fd._fd < 0) { + return -1; // No PID file exists + } + + // Try to get a shared lock to read the file + if (flock(fd._fd, LOCK_SH | LOCK_NB) < 0) { + if (errno == EWOULDBLOCK) { + // File is locked by another process, so it's running + // We need to read the PID anyway, so let's try without lock + fd = platform::FileDescriptor(::open(pid_filename, O_RDONLY)); + if (fd._fd < 0) return -1; + } else { + return -1; + } + } + + char pid_str[PID_STR_BUF] = {0}; + const ssize_t bytes_read = read(fd._fd, pid_str, sizeof(pid_str) - 1); + platform::close_fd(fd); + if (bytes_read <= 0) { + return -1; + } + pid_str[bytes_read] = '\0'; + pid_str[strcspn(pid_str, "\r\n")] = '\0'; + for (char *p = pid_str; *p; ++p) { + if (*p == '\n' || *p == '\r' || *p == ' ' || *p == '\t') { + *p = '\0'; + break; + } + } + + char *endptr = nullptr; + errno = 0; // Reset errno before call + const auto pid = static_cast(strtol(pid_str, &endptr, 10)); + if (endptr == pid_str) { + return -1; // no digits at all + } + if ((errno == ERANGE) || pid < 0) { + BONGOCAT_LOG_ERROR("'%s' out of range for pid_t", pid_str); + return -1; + } + + char exe_path[PATH_MAX] = {0}; + snprintf(exe_path, sizeof(exe_path), "/proc/%d/exe", pid); + char buf[PATH_MAX] = {0}; + ssize_t len = readlink(exe_path, buf, sizeof(buf) - 1); + if (len > 0) { + buf[len] = '\0'; + + const char *exe_basename = strrchr(buf, '/'); + exe_basename = exe_basename ? exe_basename + 1 : buf; + + const char *prog_basename = strrchr(program_name, '/'); + prog_basename = prog_basename ? prog_basename + 1 : program_name; + + if (strcmp(exe_basename, prog_basename) != 0) { + return -1; } + } - static int process_handle_toggle(const char* program_name, const char* pid_filename) { - const pid_t running_pid = process_get_running_pid(program_name, pid_filename); - if (running_pid < 0) { - // Process is not running, remove stale PID file - process_remove_pid_file(pid_filename); - } + // Check if process is actually running + if (kill(pid, 0) == 0) { + return pid; // Process is running + } + + return -1; +} - if (running_pid > 0) { - // Process is running, kill it - BONGOCAT_LOG_INFO("Stopping bongocat (PID: %d)", running_pid); - if (kill(running_pid, SIGTERM) == 0) { - // Wait a bit for graceful shutdown - for (int i = 0; i < WAIT_FOR_SHUTDOWN_MS/SLEEP_WAIT_FOR_SHUTDOWN_MS; i++) { - if (kill(running_pid, 0) != 0) { - BONGOCAT_LOG_INFO("Bongocat stopped successfully"); - return 0; - } - usleep(SLEEP_WAIT_FOR_SHUTDOWN_MS*1000); // 100ms - } - - // Force kill if still running - BONGOCAT_LOG_WARNING("Force killing bongocat"); - kill(running_pid, SIGKILL); - BONGOCAT_LOG_INFO("Bongocat force stopped"); - } else { - BONGOCAT_LOG_ERROR("Failed to stop bongocat: %s", strerror(errno)); - return 1; - } - } else { - BONGOCAT_LOG_INFO("Bongocat is not running, starting it now"); - return -1; // Signal to continue with normal startup +static int process_handle_toggle(const char *program_name, const char *pid_filename) { + const pid_t running_pid = process_get_running_pid(program_name, pid_filename); + if (running_pid < 0) { + // Process is not running, remove stale PID file + process_remove_pid_file(pid_filename); + } + + if (running_pid > 0) { + // Process is running, kill it + BONGOCAT_LOG_INFO("Stopping bongocat (PID: %d)", running_pid); + if (kill(running_pid, SIGTERM) == 0) { + // Wait a bit for graceful shutdown + for (int i = 0; i < WAIT_FOR_SHUTDOWN_MS / SLEEP_WAIT_FOR_SHUTDOWN_MS; i++) { + if (kill(running_pid, 0) != 0) { + BONGOCAT_LOG_INFO("Bongocat stopped successfully"); + return 0; } + usleep(SLEEP_WAIT_FOR_SHUTDOWN_MS * 1000); // 100ms + } - return 0; + // Force kill if still running + BONGOCAT_LOG_WARNING("Force killing bongocat"); + kill(running_pid, SIGKILL); + BONGOCAT_LOG_INFO("Bongocat force stopped"); + } else { + BONGOCAT_LOG_ERROR("Failed to stop bongocat: %s", strerror(errno)); + return 1; } + } else { + BONGOCAT_LOG_INFO("Bongocat is not running, starting it now"); + return -1; // Signal to continue with normal startup + } - // ============================================================================= - // CONFIGURATION MANAGEMENT MODULE - // ============================================================================= - - static bool config_devices_changed(const config::config_t& old_config, const config::config_t& new_config) { - if (old_config.num_keyboard_devices != new_config.num_keyboard_devices) { - return true; - } + return 0; +} - // Check if any device paths changed - for (int i = 0; i < new_config.num_keyboard_devices; i++) { - bool found = false; - for (int j = 0; j < old_config.num_keyboard_devices; j++) { - if (strcmp(new_config.keyboard_devices[i], old_config.keyboard_devices[j]) == 0) { - found = true; - break; - } - } - if (!found) { - return true; - } - } +// ============================================================================= +// CONFIGURATION MANAGEMENT MODULE +// ============================================================================= - return false; +static bool config_devices_changed(const config::config_t& old_config, const config::config_t& new_config) { + if (old_config.num_keyboard_devices != new_config.num_keyboard_devices) { + return true; + } + + // Check if any device paths changed + for (int i = 0; i < new_config.num_keyboard_devices; i++) { + bool found = false; + for (int j = 0; j < old_config.num_keyboard_devices; j++) { + if (strcmp(new_config.keyboard_devices[i], old_config.keyboard_devices[j]) == 0) { + found = true; + break; + } + } + if (!found) { + return true; } + } - static void config_reload_callback() { - assert(get_main_context().input != nullptr); - assert(get_main_context().animation != nullptr); - assert(get_main_context().signal_watch_path != nullptr); - BONGOCAT_LOG_INFO("Reloading configuration from: %s (config_watcher=%s)", get_main_context().signal_watch_path, (get_main_context().config_watcher) ? get_main_context().config_watcher->config_path : "OFF"); - assert(get_main_context().config_watcher == nullptr || strcmp(get_main_context().config_watcher->config_path, get_main_context().signal_watch_path) == 0); + return false; +} - if (strcmp(get_main_context().signal_watch_path, "-") == 0) { - BONGOCAT_LOG_WARNING("No reload config for stdin"); - BONGOCAT_LOG_INFO("Keeping current configuration"); - return; - } +static void config_reload_callback() { + assert(get_main_context().input != nullptr); + assert(get_main_context().animation != nullptr); + assert(get_main_context().signal_watch_path != nullptr); + BONGOCAT_LOG_INFO("Reloading configuration from: %s (config_watcher=%s)", get_main_context().signal_watch_path, + (get_main_context().config_watcher) ? get_main_context().config_watcher->config_path : "OFF"); + assert(get_main_context().config_watcher == nullptr || + strcmp(get_main_context().config_watcher->config_path, get_main_context().signal_watch_path) == 0); + + if (strcmp(get_main_context().signal_watch_path, "-") == 0) { + BONGOCAT_LOG_WARNING("No reload config for stdin"); + BONGOCAT_LOG_INFO("Keeping current configuration"); + return; + } + + // Create a temporary config to test loading + auto [new_config, error] = + config::load(get_main_context().signal_watch_path, get_main_context().overwrite_config_parameters); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to reload config: %s", bongocat::error_string(error)); + BONGOCAT_LOG_INFO("Keeping current configuration"); + return; + } + + // If successful, update the global config + bool devices_changed = false; + bool update_needed = false; + { + platform::LockGuard guard(get_main_context().sync_configs); + config::config_t old_config = get_main_context().config; + // keep old animation, don't randomize + if (old_config.randomize_index && new_config.randomize_index && + old_config.animation_sprite_sheet_layout == new_config.animation_sprite_sheet_layout && + old_config.animation_dm_set == new_config.animation_dm_set) { + new_config._keep_old_animation_index = !new_config.randomize_on_reload; + } + // If successful, check if input devices changed before updating config + devices_changed = config_devices_changed(old_config, new_config); + // update features had been enabled + update_needed = (new_config.cpu_threshold > old_config.cpu_threshold && + old_config.cpu_threshold < platform::ENABLED_MIN_CPU_PERCENT && + new_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT) || + (new_config.update_rate_ms > 0 && old_config.update_rate_ms <= 0); + get_main_context().config = bongocat::move(new_config); + /// @NOTE: don't use new_config after move + new_config = {}; + // Initialize error system with debug setting + bongocat::error_init(get_main_context().config.enable_debug); + + // Increment generation atomically + // Update the running systems with new config + assert(get_main_context().wayland); + assert(get_main_context().animation); + assert(get_main_context().input); + assert(get_main_context().update); + update_config(*get_main_context().wayland, get_main_context().config, *get_main_context().animation); + atomic_fetch_add(&get_main_context().config_generation, 1); + uint64_t new_gen{atomic_load(&get_main_context().config_generation)}; + platform::input::trigger_update_config(*get_main_context().input, get_main_context().config, new_gen); + platform::update::trigger_update_config(*get_main_context().update, get_main_context().config, new_gen); + animation::trigger_update_config(*get_main_context().animation, get_main_context().config, new_gen); + + // Wait for both workers to catch up + int timedwait_result{0}; + timedwait_result |= get_main_context().input->config_updated.timedwait( + [&] { + return !atomic_load(&get_main_context().input->_capture_input_running) || + atomic_load(&get_main_context().input->config_seen_generation) >= new_gen; + }, + COND_RELOAD_CONFIG_TIMEOUT_MS); + timedwait_result |= get_main_context().update->config_updated.timedwait( + [&] { + return !atomic_load(&get_main_context().update->_running) || + atomic_load(&get_main_context().update->config_seen_generation) >= new_gen; + }, + COND_RELOAD_CONFIG_TIMEOUT_MS); + timedwait_result |= get_main_context().animation->anim.config_updated.timedwait( + [&] { + return !atomic_load(&get_main_context().animation->anim._animation_running) || + atomic_load(&get_main_context().animation->anim.config_seen_generation) >= new_gen; + }, + COND_RELOAD_CONFIG_TIMEOUT_MS); + + // reset config internal state + get_main_context().config._keep_old_animation_index = false; + if (timedwait_result != 0) { + // fallback when cond hits timeout (sync config generations) + if (atomic_load(&get_main_context().input->_capture_input_running)) { + atomic_store(&get_main_context().input->config_seen_generation, new_gen); + } + if (atomic_load(&get_main_context().update->_running)) { + atomic_store(&get_main_context().update->config_seen_generation, new_gen); + } + if (atomic_load(&get_main_context().animation->anim._animation_running)) { + atomic_store(&get_main_context().animation->anim.config_seen_generation, new_gen); + } + BONGOCAT_LOG_VERBOSE("timedwait timeouted, sync all config gen: %d", timedwait_result); + } + atomic_store(&get_main_context().config_generation, new_gen); + + BONGOCAT_LOG_VERBOSE("Input: config gen: %d", atomic_load(&get_main_context().input->config_seen_generation)); + BONGOCAT_LOG_VERBOSE("Update: config gen: %d", atomic_load(&get_main_context().update->config_seen_generation)); + BONGOCAT_LOG_VERBOSE("Animation: config gen: %d", + atomic_load(&get_main_context().animation->anim.config_seen_generation)); + BONGOCAT_LOG_VERBOSE("Main: config gen: %d", atomic_load(&get_main_context().config_generation)); + } + // Tell workers they can continue + get_main_context().configs_reloaded_cond.notify_all(); + + BONGOCAT_LOG_INFO("Configuration reloaded successfully!"); + BONGOCAT_LOG_INFO("New screen dimensions: %dx%d", get_main_context().wayland->wayland_context._screen_width, + get_main_context().wayland->wayland_context._bar_height); + + assert(get_main_context().animation != nullptr); + animation::trigger(*get_main_context().animation, animation::trigger_animation_cause_mask_t::UpdateConfig); + + // Check if input devices changed and restart monitoring if needed + if (devices_changed) { + BONGOCAT_LOG_INFO("Input devices changed, restarting input monitoring"); + const bongocat_error_t input_result = + platform::input::restart(*get_main_context().input, *get_main_context().animation, get_main_context().config, + get_main_context().configs_reloaded_cond, get_main_context().config_generation); + if (input_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to restart input monitoring: %s", bongocat::error_string(input_result)); + } else { + BONGOCAT_LOG_INFO("Input monitoring restarted successfully"); + } + } + + // Check if update features are enabled and restart update if needed + if (update_needed) { + BONGOCAT_LOG_INFO("Update features enabled, restarting update thread"); + const bongocat_error_t update_result = + platform::update::restart(*get_main_context().update, *get_main_context().animation, get_main_context().config, + get_main_context().configs_reloaded_cond, get_main_context().config_generation); + if (update_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to restart update thread: %s", bongocat::error_string(update_result)); + } else { + BONGOCAT_LOG_INFO("Update thread restarted successfully"); + } + } + + // Wait for (new) threads to be ready + // wait for context + if (atomic_load(&get_main_context().animation->anim._animation_running)) { + get_main_context().animation->init_cond.timedwait( + [&]() { + return !atomic_load(&get_main_context().input->_capture_input_running) || + atomic_load(&get_main_context().animation->ready); + }, + COND_INIT_TIMEOUT_MS); + } + if (atomic_load(&get_main_context().input->_capture_input_running)) { + get_main_context().input->init_cond.timedwait( + [&]() { + return !atomic_load(&get_main_context().input->_capture_input_running) || + atomic_load(&get_main_context().input->ready); + }, + COND_INIT_TIMEOUT_MS); + } + if (atomic_load(&get_main_context().update->_running)) { + get_main_context().update->init_cond.timedwait( + [&]() { + return !atomic_load(&get_main_context().update->_running) || atomic_load(&get_main_context().update->ready); + }, + COND_INIT_TIMEOUT_MS); + } + BONGOCAT_LOG_VERBOSE("Animation: running %d (ready=%d)", + atomic_load(&get_main_context().animation->anim._animation_running), + atomic_load(&get_main_context().animation->ready)); + BONGOCAT_LOG_VERBOSE("Input: running %d (ready=%d)", atomic_load(&get_main_context().input->_capture_input_running), + atomic_load(&get_main_context().input->ready)); + BONGOCAT_LOG_VERBOSE("Update: running %d (ready=%d)", atomic_load(&get_main_context().update->_running), + atomic_load(&get_main_context().update->ready)); +} - // Create a temporary config to test loading - auto [new_config, error] = config::load(get_main_context().signal_watch_path, get_main_context().overwrite_config_parameters); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to reload config: %s", bongocat::error_string(error)); - BONGOCAT_LOG_INFO("Keeping current configuration"); - return; - } +static bongocat_error_t start_config_watcher(main_context_t& ctx, const char *config_file) { + auto [config_watcher, error] = config::create_watcher(config_file); + if (error == bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + ctx.config_watcher = bongocat::move(config_watcher); + config::start_watcher(*ctx.config_watcher); + BONGOCAT_LOG_INFO("Config file watching enabled for: %s", config_file); + } else { + BONGOCAT_LOG_WARNING("Failed to initialize config watcher, continuing without hot-reload"); + } + return error; +} - // If successful, update the global config - bool devices_changed = false; - bool update_needed = false; - { - platform::LockGuard guard (get_main_context().sync_configs); - config::config_t old_config = get_main_context().config; - // keep old animation, don't randomize - if (old_config.randomize_index && new_config.randomize_index && - old_config.animation_sprite_sheet_layout == new_config.animation_sprite_sheet_layout && - old_config.animation_dm_set == new_config.animation_dm_set) { - new_config._keep_old_animation_index = !new_config.randomize_on_reload; - } - // If successful, check if input devices changed before updating config - devices_changed = config_devices_changed(old_config, new_config); - // update features had been enabled - update_needed = (new_config.cpu_threshold > old_config.cpu_threshold && old_config.cpu_threshold < platform::ENABLED_MIN_CPU_PERCENT && new_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT) || - (new_config.update_rate_ms > 0 && old_config.update_rate_ms <= 0); - get_main_context().config = bongocat::move(new_config); - /// @NOTE: don't use new_config after move - new_config = {}; - // Initialize error system with debug setting - bongocat::error_init(get_main_context().config.enable_debug); - - // Increment generation atomically - // Update the running systems with new config - assert(get_main_context().wayland); - assert(get_main_context().animation); - assert(get_main_context().input); - assert(get_main_context().update); - update_config(*get_main_context().wayland, get_main_context().config, *get_main_context().animation); - atomic_fetch_add(&get_main_context().config_generation, 1); - uint64_t new_gen{ atomic_load(&get_main_context().config_generation)}; - platform::input::trigger_update_config(*get_main_context().input, get_main_context().config, new_gen); - platform::update::trigger_update_config(*get_main_context().update, get_main_context().config, new_gen); - animation::trigger_update_config(*get_main_context().animation, get_main_context().config, new_gen); - - // Wait for both workers to catch up - int timedwait_result{0}; - timedwait_result |= get_main_context().input->config_updated.timedwait([&] { - return !atomic_load(&get_main_context().input->_capture_input_running) || atomic_load(&get_main_context().input->config_seen_generation) >= new_gen; - }, COND_RELOAD_CONFIG_TIMEOUT_MS); - timedwait_result |= get_main_context().update->config_updated.timedwait([&] { - return !atomic_load(&get_main_context().update->_running) || atomic_load(&get_main_context().update->config_seen_generation) >= new_gen; - }, COND_RELOAD_CONFIG_TIMEOUT_MS); - timedwait_result |= get_main_context().animation->anim.config_updated.timedwait([&] { - return !atomic_load(&get_main_context().animation->anim._animation_running) || atomic_load(&get_main_context().animation->anim.config_seen_generation) >= new_gen; - }, COND_RELOAD_CONFIG_TIMEOUT_MS); - - // reset config internal state - get_main_context().config._keep_old_animation_index = false; - if (timedwait_result != 0) { - // fallback when cond hits timeout (sync config generations) - if (atomic_load(&get_main_context().input->_capture_input_running)) { - atomic_store(&get_main_context().input->config_seen_generation, new_gen); - } - if (atomic_load(&get_main_context().update->_running)) { - atomic_store(&get_main_context().update->config_seen_generation, new_gen); - } - if (atomic_load(&get_main_context().animation->anim._animation_running)) { - atomic_store(&get_main_context().animation->anim.config_seen_generation, new_gen); - } - BONGOCAT_LOG_VERBOSE("timedwait timeouted, sync all config gen: %d", timedwait_result); - } - atomic_store(&get_main_context().config_generation, new_gen); - - BONGOCAT_LOG_VERBOSE("Input: config gen: %d", atomic_load(&get_main_context().input->config_seen_generation)); - BONGOCAT_LOG_VERBOSE("Update: config gen: %d", atomic_load(&get_main_context().update->config_seen_generation)); - BONGOCAT_LOG_VERBOSE("Animation: config gen: %d", atomic_load(&get_main_context().animation->anim.config_seen_generation)); - BONGOCAT_LOG_VERBOSE("Main: config gen: %d", atomic_load(&get_main_context().config_generation)); - } - // Tell workers they can continue - get_main_context().configs_reloaded_cond.notify_all(); - - BONGOCAT_LOG_INFO("Configuration reloaded successfully!"); - BONGOCAT_LOG_INFO("New screen dimensions: %dx%d", get_main_context().wayland->wayland_context._screen_width, get_main_context().wayland->wayland_context._bar_height); - - assert(get_main_context().animation != nullptr); - animation::trigger(*get_main_context().animation, animation::trigger_animation_cause_mask_t::UpdateConfig); - - // Check if input devices changed and restart monitoring if needed - if (devices_changed) { - BONGOCAT_LOG_INFO("Input devices changed, restarting input monitoring"); - const bongocat_error_t input_result = platform::input::restart(*get_main_context().input, *get_main_context().animation, get_main_context().config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); - if (input_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to restart input monitoring: %s", bongocat::error_string(input_result)); - } else { - BONGOCAT_LOG_INFO("Input monitoring restarted successfully"); - } - } +static char *default_config_file_path() { + const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + const char *home = getenv("HOME"); - // Check if update features are enabled and restart update if needed - if (update_needed) { - BONGOCAT_LOG_INFO("Update features enabled, restarting update thread"); - const bongocat_error_t update_result = platform::update::restart(*get_main_context().update, *get_main_context().animation, get_main_context().config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); - if (update_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to restart update thread: %s", bongocat::error_string(update_result)); - } else { - BONGOCAT_LOG_INFO("Update thread restarted successfully"); - } - } + if (xdg_config_home != nullptr) { + size_t len = strlen(xdg_config_home) + 1 + strlen(DEFAULT_CONF_FILENAME) + 1; + char *path = static_cast(::malloc(len)); + if (!path) + return nullptr; + snprintf(path, len, "%s/%s", xdg_config_home, DEFAULT_CONF_FILENAME); - // Wait for (new) threads to be ready - // wait for context - if (atomic_load(&get_main_context().animation->anim._animation_running)) { - get_main_context().animation->init_cond.timedwait([&]() { - return !atomic_load(&get_main_context().input->_capture_input_running) || atomic_load(&get_main_context().animation->ready); - }, COND_INIT_TIMEOUT_MS); - } - if (atomic_load(&get_main_context().input->_capture_input_running)) { - get_main_context().input->init_cond.timedwait([&]() { - return !atomic_load(&get_main_context().input->_capture_input_running) || atomic_load(&get_main_context().input->ready); - }, COND_INIT_TIMEOUT_MS); - } - if (atomic_load(&get_main_context().update->_running)) { - get_main_context().update->init_cond.timedwait([&]() { - return !atomic_load(&get_main_context().update->_running) || atomic_load(&get_main_context().update->ready); - }, COND_INIT_TIMEOUT_MS); - } - BONGOCAT_LOG_VERBOSE("Animation: running %d (ready=%d)", atomic_load(&get_main_context().animation->anim._animation_running), atomic_load(&get_main_context().animation->ready)); - BONGOCAT_LOG_VERBOSE("Input: running %d (ready=%d)", atomic_load(&get_main_context().input->_capture_input_running), atomic_load(&get_main_context().input->ready)); - BONGOCAT_LOG_VERBOSE("Update: running %d (ready=%d)", atomic_load(&get_main_context().update->_running), atomic_load(&get_main_context().update->ready)); - } - - static bongocat_error_t start_config_watcher(main_context_t& ctx, const char *config_file) { - auto [config_watcher, error] = config::create_watcher(config_file); - if (error == bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - ctx.config_watcher = bongocat::move(config_watcher); - config::start_watcher(*ctx.config_watcher); - BONGOCAT_LOG_INFO("Config file watching enabled for: %s", config_file); - } else { - BONGOCAT_LOG_WARNING("Failed to initialize config watcher, continuing without hot-reload"); - } - return error; + if (access(path, F_OK) == 0) { + return path; // file exists } - static char *default_config_file_path() { - const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); - const char *home = getenv("HOME"); + free(path); + path = nullptr; + } - if (xdg_config_home != nullptr) { - size_t len = strlen(xdg_config_home) + 1 + strlen(DEFAULT_CONF_FILENAME) + 1; - char *path = static_cast(::malloc(len)); - if (!path) return nullptr; - snprintf(path, len, "%s/%s", xdg_config_home, DEFAULT_CONF_FILENAME); + if (home != nullptr) { + size_t len = strlen(home) + strlen("/.config/") + strlen(DEFAULT_CONF_FILENAME) + 1; + char *path = static_cast(::malloc(len)); + if (!path) + return nullptr; + snprintf(path, len, "%s/.config/%s", home, DEFAULT_CONF_FILENAME); - if (access(path, F_OK) == 0) { - return path; // file exists - } + if (access(path, F_OK) == 0) { + return path; // file exists + } - free(path); - path = nullptr; - } + free(path); + path = nullptr; + } - if (home != nullptr) { - size_t len = strlen(home) + strlen("/.config/") + strlen(DEFAULT_CONF_FILENAME) + 1; - char *path = static_cast(::malloc(len)); - if (!path) return nullptr; - snprintf(path, len, "%s/.config/%s", home, DEFAULT_CONF_FILENAME); + // If neither env var is set, fallback to just filename in current dir + return strdup(DEFAULT_CONF_FILENAME); +} - if (access(path, F_OK) == 0) { - return path; // file exists - } +// ============================================================================= +// SIGNAL HANDLING MODULE +// ============================================================================= - free(path); - path = nullptr; - } +static bongocat_error_t signal_setup_handlers(main_context_t& ctx) { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigaddset(&mask, SIGQUIT); + sigaddset(&mask, SIGHUP); + + // Block signals globally so they are only delivered via signalfd + if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to block signals: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + ctx.signal_fd = platform::FileDescriptor(signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)); + if (ctx.signal_fd._fd == -1) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to create signalfd: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} - // If neither env var is set, fallback to just filename in current dir - return strdup(DEFAULT_CONF_FILENAME); - } - - // ============================================================================= - // SIGNAL HANDLING MODULE - // ============================================================================= - - static bongocat_error_t signal_setup_handlers(main_context_t& ctx) { - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - sigaddset(&mask, SIGCHLD); - sigaddset(&mask, SIGUSR1); - sigaddset(&mask, SIGUSR2); - sigaddset(&mask, SIGQUIT); - sigaddset(&mask, SIGHUP); - - // Block signals globally so they are only delivered via signalfd - if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to block signals: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; - } +// ============================================================================= +// SYSTEM INITIALIZATION AND CLEANUP MODULE +// ============================================================================= - ctx.signal_fd = platform::FileDescriptor(signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC)); - if (ctx.signal_fd._fd == -1) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to create signalfd: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; - } +static bongocat_error_t system_initialize_components(main_context_t& ctx) { + // Initialize input system + { + auto [input, input_error] = platform::input::create(ctx.config); + if (input_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to initialize input system: %s", bongocat::error_string(input_error)); + return input_error; + } + ctx.input = bongocat::move(input); + } + + // Initialize update system + { + auto [update, update_error] = platform::update::create(ctx.config); + if (update_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to initialize updfate system: %s", bongocat::error_string(update_error)); + return update_error; + } + ctx.update = bongocat::move(update); + } + + // Initialize animation system + { + animation::init_image_loader(); + auto [animation, animation_error] = animation::create(ctx.config); + if (animation_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to initialize animation system: %s", bongocat::error_string(animation_error)); + return animation_error; + } + ctx.animation = bongocat::move(animation); + } - return bongocat_error_t::BONGOCAT_SUCCESS; + // Initialize Wayland + { + assert(ctx.animation != nullptr); + /// @NOTE: animation needed only for reference + auto [wayland, wayland_error] = platform::wayland::create(*ctx.animation, ctx.config); + if (wayland_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to initialize Wayland: %s", bongocat::error_string(wayland_error)); + return wayland_error; } + ctx.wayland = bongocat::move(wayland); + } - // ============================================================================= - // SYSTEM INITIALIZATION AND CLEANUP MODULE - // ============================================================================= + // Setup wayland + { + assert(ctx.wayland != nullptr); + assert(ctx.animation != nullptr); + bongocat_error_t setup_wayland_result = setup(*ctx.wayland, *ctx.animation); + if (setup_wayland_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to setup wayland: %s", bongocat::error_string(setup_wayland_result)); + return setup_wayland_result; + } + } - static bongocat_error_t system_initialize_components(main_context_t& ctx) { - // Initialize input system - { - auto [input, input_error] = platform::input::create(ctx.config); - if (input_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to initialize input system: %s", bongocat::error_string(input_error)); - return input_error; - } - ctx.input = bongocat::move(input); - } + // Start animation thread + { + assert(ctx.animation != nullptr); + assert(ctx.input != nullptr); + assert(ctx.update != nullptr); + bongocat_error_t start_animation_result = + animation::start(*ctx.animation, *ctx.input, *ctx.update, ctx.config, get_main_context().configs_reloaded_cond, + get_main_context().config_generation); + if (start_animation_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to start animation thread: %s", bongocat::error_string(start_animation_result)); + return start_animation_result; + } + } - // Initialize update system - { - auto [update, update_error] = platform::update::create(ctx.config); - if (update_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to initialize updfate system: %s", bongocat::error_string(update_error)); - return update_error; - } - ctx.update = bongocat::move(update); - } + // Start input monitoring + { + assert(ctx.animation != nullptr); + assert(ctx.input != nullptr); + bongocat_error_t start_input_result = + platform::input::start(*ctx.input, *ctx.animation, ctx.config, get_main_context().configs_reloaded_cond, + get_main_context().config_generation); + if (start_input_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to start input monitoring: %s", bongocat::error_string(start_input_result)); + return start_input_result; + } + } - // Initialize animation system - { - animation::init_image_loader(); - auto [animation, animation_error] = animation::create(ctx.config); - if (animation_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to initialize animation system: %s", bongocat::error_string(animation_error)); - return animation_error; - } - ctx.animation = bongocat::move(animation); - } + // Start update monitoring + { + assert(ctx.animation != nullptr); + assert(ctx.update != nullptr); + bongocat_error_t start_update_result = + platform::update::start(*ctx.update, *ctx.animation, ctx.config, get_main_context().configs_reloaded_cond, + get_main_context().config_generation); + if (start_update_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to start update thread: %s", bongocat::error_string(start_update_result)); + return start_update_result; + } + } - // Initialize Wayland - { - assert(ctx.animation != nullptr); - /// @NOTE: animation needed only for reference - auto [wayland, wayland_error] = platform::wayland::create(*ctx.animation, ctx.config); - if (wayland_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to initialize Wayland: %s", bongocat::error_string(wayland_error)); - return wayland_error; - } - ctx.wayland = bongocat::move(wayland); - } + return bongocat_error_t::BONGOCAT_SUCCESS; +} - // Setup wayland - { - assert(ctx.wayland != nullptr); - assert(ctx.animation != nullptr); - bongocat_error_t setup_wayland_result = setup(*ctx.wayland, *ctx.animation); - if (setup_wayland_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to setup wayland: %s", bongocat::error_string(setup_wayland_result)); - return setup_wayland_result; - } - } +[[noreturn]] static void system_cleanup_and_exit(main_context_t& ctx, int exit_code) { + BONGOCAT_LOG_INFO("Stop threads..."); + ctx.running = 0; + stop_threads(ctx); - // Start animation thread - { - assert(ctx.animation != nullptr); - assert(ctx.input != nullptr); - assert(ctx.update != nullptr); - bongocat_error_t start_animation_result = animation::start(*ctx.animation, *ctx.input, *ctx.update, ctx.config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); - if (start_animation_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to start animation thread: %s", bongocat::error_string(start_animation_result)); - return start_animation_result; - } - } + BONGOCAT_LOG_INFO("Performing cleanup..."); + process_remove_pid_file(ctx.pid_filename); + // clean up context before global cleanup (log mutex, etc.) + cleanup(ctx); - // Start input monitoring - { - assert(ctx.animation != nullptr); - assert(ctx.input != nullptr); - bongocat_error_t start_input_result = platform::input::start(*ctx.input, *ctx.animation, ctx.config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); - if (start_input_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to start input monitoring: %s", bongocat::error_string(start_input_result)); - return start_input_result; - } - } + BONGOCAT_LOG_INFO("Cleanup complete, exiting with code %d", exit_code); + usleep(WAIT_FOR_FLUSH_BEFORE_EXIT_MS * 1000); + exit(exit_code); +} - // Start update monitoring - { - assert(ctx.animation != nullptr); - assert(ctx.update != nullptr); - bongocat_error_t start_update_result = platform::update::start(*ctx.update, *ctx.animation, ctx.config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); - if (start_update_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to start update thread: %s", bongocat::error_string(start_update_result)); - return start_update_result; - } - } +// ============================================================================= +// COMMAND LINE PROCESSING MODULE +// ============================================================================= - return bongocat_error_t::BONGOCAT_SUCCESS; +static void cli_show_help(const char *program_name) { + char *base_program_name = strdup(program_name); + if (!base_program_name) { + perror("strdup"); + return; + } + + printf("Bongo Cat Wayland Overlay.\n\n"); + printf("Usage: %s [OPTIONS]\n\n", basename(base_program_name)); + printf("Options:\n"); + printf(" -h, --help Show this help message\n"); + printf(" -v, --version Show version information\n"); + printf(" -c, --config Specify config file (default: ~/.config/bongocat.conf)\n"); + printf(" -w, --watch-config Watch config file for changes and reload automatically\n"); + printf(" -t, --toggle Toggle bongocat on/off (start if not running, stop if running)\n"); + printf(" -o, --output-name NAME Specify output name (overwrite output_name from config)\n"); + printf( + " --random Enable random animation_index, at start (overwrite random_index from config)\n"); + printf(" --strict Enable strict mode, only start up with a valid config and valid parameter\n"); + printf(" --nr NR Specify Nr. for PID file to avoid conflicting ruinning instances\n"); + printf(" --ignore-running Ignore current running instance\n"); + printf("\n"); + printf("Included sets:\n"); + if constexpr (features::EnableBongocatEmbeddedAssets) { + printf(" %8s - Classic Bongo cat\n", "bongocat"); + } + if constexpr (features::EnableDmEmbeddedAssets) { + if constexpr (features::EnableDmEmbeddedAssets) { + printf(" %8s - Digital Monster Original\n", "dm"); } - - [[ noreturn ]] static void system_cleanup_and_exit(main_context_t& ctx, int exit_code) { - BONGOCAT_LOG_INFO("Stop threads..."); - ctx.running = 0; - stop_threads(ctx); - - BONGOCAT_LOG_INFO("Performing cleanup..."); - process_remove_pid_file(ctx.pid_filename); - // clean up context before global cleanup (log mutex, etc.) - cleanup(ctx); - - BONGOCAT_LOG_INFO("Cleanup complete, exiting with code %d", exit_code); - usleep(WAIT_FOR_FLUSH_BEFORE_EXIT_MS*1000); - exit(exit_code); + if constexpr (features::EnableDm20EmbeddedAssets) { + printf(" %8s - Digital Monster Ver.20th\n", "dm20"); } + if constexpr (features::EnableDmxEmbeddedAssets) { + printf(" %8s - Digital Monster X\n", "dmx"); + } + if constexpr (features::EnablePenEmbeddedAssets) { + printf(" %8s - Digimon Pendulum\n", "pen"); + } + if constexpr (features::EnablePen20EmbeddedAssets) { + printf(" %8s - Digimon Pendulum Ver.20th\n", "pen20"); + } + if constexpr (features::EnableDmcEmbeddedAssets) { + printf(" %8s - Digital Monster Color\n", "dmc"); + } + if constexpr (features::EnableDmAllEmbeddedAssets) { + printf(" %8s - Custom Digital Monster Colored (fan sprites)\n", "dmall"); + } + } + if constexpr (features::EnablePkmnEmbeddedAssets) { + printf(" %8s - Pokemon, up to Gen 5\n", "pkmn"); + } + if constexpr (features::EnablePmdEmbeddedAssets) { + printf(" %8s - Pokemon Mystery Dungeon, up to Gen 8 (fan sprites)\n", "pmd"); + } + if constexpr (features::EnableMsAgentEmbeddedAssets) { + printf(" %8s - MS Agent\n", "ms_agent"); + } + printf("\n"); + + ::free(base_program_name); +} - // ============================================================================= - // COMMAND LINE PROCESSING MODULE - // ============================================================================= - - static void cli_show_help(const char *program_name) { - char *base_program_name = strdup(program_name); - if (!base_program_name) { - perror("strdup"); - return; - } +static void cli_show_version() { + printf("bongocat version %s\n", BONGOCAT_VERSION); +} - printf("Bongo Cat Wayland Overlay.\n\n"); - printf("Usage: %s [OPTIONS]\n\n", basename(base_program_name)); - printf("Options:\n"); - printf(" -h, --help Show this help message\n"); - printf(" -v, --version Show version information\n"); - printf(" -c, --config Specify config file (default: ~/.config/bongocat.conf)\n"); - printf(" -w, --watch-config Watch config file for changes and reload automatically\n"); - printf(" -t, --toggle Toggle bongocat on/off (start if not running, stop if running)\n"); - printf(" -o, --output-name NAME Specify output name (overwrite output_name from config)\n"); - printf(" --random Enable random animation_index, at start (overwrite random_index from config)\n"); - printf(" --strict Enable strict mode, only start up with a valid config and valid parameter\n"); - printf(" --nr NR Specify Nr. for PID file to avoid conflicting ruinning instances\n"); - printf(" --ignore-running Ignore current running instance\n"); - printf("\n"); - printf("Included sets:\n"); - if constexpr (features::EnableBongocatEmbeddedAssets) { - printf(" %8s - Classic Bongo cat\n", "bongocat"); - } - if constexpr (features::EnableDmEmbeddedAssets) { - if constexpr (features::EnableDmEmbeddedAssets) { - printf(" %8s - Digital Monster Original\n", "dm"); - } - if constexpr (features::EnableDm20EmbeddedAssets) { - printf(" %8s - Digital Monster Ver.20th\n", "dm20"); - } - if constexpr (features::EnableDmxEmbeddedAssets) { - printf(" %8s - Digital Monster X\n", "dmx"); - } - if constexpr (features::EnablePenEmbeddedAssets) { - printf(" %8s - Digimon Pendulum\n", "pen"); - } - if constexpr (features::EnablePen20EmbeddedAssets) { - printf(" %8s - Digimon Pendulum Ver.20th\n", "pen20"); - } - if constexpr (features::EnableDmcEmbeddedAssets) { - printf(" %8s - Digital Monster Color\n", "dmc"); - } - if constexpr (features::EnableDmAllEmbeddedAssets) { - printf(" %8s - Custom Digital Monster Colored (fan sprites)\n", "dmall"); - } - } - if constexpr (features::EnablePkmnEmbeddedAssets) { - printf(" %8s - Pokemon, up to Gen 5\n", "pkmn"); - } - if constexpr (features::EnablePmdEmbeddedAssets) { - printf(" %8s - Pokemon Mystery Dungeon, up to Gen 8 (fan sprites)\n", "pmd"); - } - if constexpr (features::EnableMsAgentEmbeddedAssets) { - printf(" %8s - MS Agent\n", "ms_agent"); +static created_result_t cli_parse_arguments(int argc, char *argv[]) { + cli_args_t args{}; + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + args.show_help = true; + } else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { + args.show_version = true; + } else if (strcmp(argv[i], "--config") == 0 || strcmp(argv[i], "-c") == 0) { + args.config_file_set = true; + if (i + 1 < argc) { + args.config_file = argv[i + 1]; + i++; // Skip the next argument since it's the config file path + } else { + BONGOCAT_LOG_ERROR("--config option requires a file path"); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + } else if (strcmp(argv[i], "--watch-config") == 0 || strcmp(argv[i], "-w") == 0) { + args.watch_config = true; + } else if (strcmp(argv[i], "--toggle") == 0 || strcmp(argv[i], "-t") == 0) { + args.toggle_mode = true; + } else if (strcmp(argv[i], "--random") == 0) { + args.randomize_index = 1; + } else if (strcmp(argv[i], "--strict") == 0) { + args.strict = 1; + } else if (strcmp(argv[i], "--ignore-running") == 0) { + args.ignore_running = true; + } else if (strcmp(argv[i], "--nr") == 0) { + args.nr_set = true; + if (i + 1 < argc) { + char *endptr{nullptr}; + args.nr = strtoll(argv[i + 1], &endptr, 10); + if (*endptr != '\0' || errno == ERANGE) { + BONGOCAT_LOG_ERROR("--nr option requires a valid number"); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; } - printf("\n"); - - ::free(base_program_name); - } - - static void cli_show_version() { - printf("bongocat version %s\n", BONGOCAT_VERSION); - } - - static created_result_t cli_parse_arguments(int argc, char *argv[]) { - cli_args_t args{}; - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - args.show_help = true; - } else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { - args.show_version = true; - } else if (strcmp(argv[i], "--config") == 0 || strcmp(argv[i], "-c") == 0) { - args.config_file_set = true; - if (i + 1 < argc) { - args.config_file = argv[i + 1]; - i++; // Skip the next argument since it's the config file path - } else { - BONGOCAT_LOG_ERROR("--config option requires a file path"); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - } else if (strcmp(argv[i], "--watch-config") == 0 || strcmp(argv[i], "-w") == 0) { - args.watch_config = true; - } else if (strcmp(argv[i], "--toggle") == 0 || strcmp(argv[i], "-t") == 0) { - args.toggle_mode = true; - } else if (strcmp(argv[i], "--random") == 0) { - args.randomize_index = 1; - } else if (strcmp(argv[i], "--strict") == 0) { - args.strict = 1; - } else if (strcmp(argv[i], "--ignore-running") == 0) { - args.ignore_running = true; - } else if (strcmp(argv[i], "--nr") == 0) { - args.nr_set = true; - if (i + 1 < argc) { - char *endptr{nullptr}; - args.nr = strtoll(argv[i + 1], &endptr, 10); - if (*endptr != '\0' || errno == ERANGE) { - BONGOCAT_LOG_ERROR("--nr option requires a valid number"); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - i++; // Skip the next argument since it's the nr value - } else { - BONGOCAT_LOG_ERROR("--nr option requires a number"); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - } else if (strcmp(argv[i], "--output-name") == 0 || strcmp(argv[i], "-o") == 0) { - args.output_name_set = true; - if (i + 1 < argc) { - args.output_name = argv[i + 1]; - i++; // Skip the next argument since it's the output name - } else { - BONGOCAT_LOG_ERROR("--output-name option requires a output name"); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - } else { - BONGOCAT_LOG_WARNING("Unknown argument: %s", argv[i]); - } - } - - return args; + i++; // Skip the next argument since it's the nr value + } else { + BONGOCAT_LOG_ERROR("--nr option requires a number"); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + } else if (strcmp(argv[i], "--output-name") == 0 || strcmp(argv[i], "-o") == 0) { + args.output_name_set = true; + if (i + 1 < argc) { + args.output_name = argv[i + 1]; + i++; // Skip the next argument since it's the output name + } else { + BONGOCAT_LOG_ERROR("--output-name option requires a output name"); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + } else { + BONGOCAT_LOG_WARNING("Unknown argument: %s", argv[i]); } + } + + return args; } +} // namespace bongocat // ============================================================================= // MAIN APPLICATION ENTRY POINT // ============================================================================= int main(int argc, char *argv[]) { - using namespace bongocat; - // Initialize error system early - bongocat::error_init(true); // Enable debug initially - - // Parse command line arguments - const auto [args, args_result] = cli_parse_arguments(argc, argv); - if (args_result != bongocat_error_t::BONGOCAT_SUCCESS) { - return EXIT_FAILURE; - } - - // Handle help and version requests - if (args.show_help) { - cli_show_help(argv[0]); - return EXIT_SUCCESS; - } - if (args.show_version) { - cli_show_version(); - return EXIT_SUCCESS; - } - - BONGOCAT_LOG_INFO("Starting Bongo Cat Overlay v%s", BONGOCAT_VERSION); - - main_context_t& ctx = get_main_context(); - - // Load configuration - ctx.overwrite_config_parameters = { - .output_name = args.output_name, - .randomize_index = args.randomize_index, - .strict = args.strict, - }; - if (args.config_file == nullptr) { - /// @TODO: RAII default_config_filename string - get_main_context().default_config_filename = default_config_file_path(); - } - const char* config_file = args.config_file == nullptr ? get_main_context().default_config_filename : args.config_file; - if (args.strict) { - if (strcmp(config_file, "-") != 0 && access(config_file, F_OK) != 0) { - BONGOCAT_LOG_ERROR("Configuration file required: %s", config_file); - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - return EXIT_FAILURE; - } + using namespace bongocat; + // Initialize error system early + bongocat::error_init(true); // Enable debug initially + + // Parse command line arguments + const auto [args, args_result] = cli_parse_arguments(argc, argv); + if (args_result != bongocat_error_t::BONGOCAT_SUCCESS) { + return EXIT_FAILURE; + } + + // Handle help and version requests + if (args.show_help) { + cli_show_help(argv[0]); + return EXIT_SUCCESS; + } + if (args.show_version) { + cli_show_version(); + return EXIT_SUCCESS; + } + + BONGOCAT_LOG_INFO("Starting Bongo Cat Overlay v%s", BONGOCAT_VERSION); + + main_context_t& ctx = get_main_context(); + + // Load configuration + ctx.overwrite_config_parameters = { + .output_name = args.output_name, + .randomize_index = args.randomize_index, + .strict = args.strict, + }; + if (args.config_file == nullptr) { + /// @TODO: RAII default_config_filename string + get_main_context().default_config_filename = default_config_file_path(); + } + const char *config_file = args.config_file == nullptr ? get_main_context().default_config_filename : args.config_file; + if (args.strict) { + if (strcmp(config_file, "-") != 0 && access(config_file, F_OK) != 0) { + BONGOCAT_LOG_ERROR("Configuration file required: %s", config_file); + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + return EXIT_FAILURE; } - - auto [config, config_error] = config::load(config_file, ctx.overwrite_config_parameters); - if (config_error != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to load configuration: %s", bongocat::error_string(config_error)); - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - return EXIT_FAILURE; - } - ctx.config = bongocat::move(config); - bongocat::error_init(ctx.config.enable_debug); - - // validate args - if (config._strict) { - if (args.nr_set && args.nr < 0) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - BONGOCAT_LOG_ERROR("--nr needs to be a positive number"); - return EXIT_FAILURE; - } - if (args.output_name_set && (!args.output_name || strlen(args.output_name) <= 0)) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - BONGOCAT_LOG_ERROR("--output_name value is missing"); - return EXIT_FAILURE; - } + } + + auto [config, config_error] = config::load(config_file, ctx.overwrite_config_parameters); + if (config_error != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to load configuration: %s", bongocat::error_string(config_error)); + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; } - - if (args.nr >= 0) { - // set pid file, based on nr - const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr) + 1; - assert(needed_size >= 0); - ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); - if (ctx.pid_filename != nullptr) { - snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr); - } - } else if (ctx.config.output_name && ctx.config.output_name[0] != '\0') { - // set pid file, based on output_name - if (!args.ignore_running) { - const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_TEMPLATE, ctx.config.output_name) + 1; - assert(needed_size >= 0); - ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); - if (ctx.pid_filename != nullptr) { - snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_TEMPLATE, ctx.config.output_name); - } - } else { - const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, ctx.config.output_name, platform::slow_rand()) + 1; - assert(needed_size >= 0); - ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); - if (ctx.pid_filename != nullptr) { - snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, ctx.config.output_name, platform::slow_rand()); - } - } - - if (ctx.pid_filename == nullptr) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - BONGOCAT_LOG_ERROR("Failed to allocate PID filename"); - return EXIT_FAILURE; - } - } else { - ctx.pid_filename = strdup(DEFAULT_PID_FILE); + return EXIT_FAILURE; + } + ctx.config = bongocat::move(config); + bongocat::error_init(ctx.config.enable_debug); + + // validate args + if (config._strict) { + if (args.nr_set && args.nr < 0) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + BONGOCAT_LOG_ERROR("--nr needs to be a positive number"); + return EXIT_FAILURE; } - - // Handle toggle mode - if (args.toggle_mode) { - BONGOCAT_LOG_INFO("Toggle... (pid=%s)", ctx.pid_filename); - if (const int toggle_result = process_handle_toggle(argv[0], ctx.pid_filename); toggle_result >= 0) { - return toggle_result; // Either successfully toggled off or error - } - // toggle_result == -1 means continue with startup + if (args.output_name_set && (!args.output_name || strlen(args.output_name) <= 0)) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + BONGOCAT_LOG_ERROR("--output_name value is missing"); + return EXIT_FAILURE; } - - // Create PID file to track this instance - const platform::FileDescriptor pid_fd = process_create_pid_file(ctx.pid_filename); - if (pid_fd._fd < 0) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - BONGOCAT_LOG_ERROR("Failed to create PID file"); - return EXIT_FAILURE; + } + + if (args.nr >= 0) { + // set pid file, based on nr + const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr) + 1; + assert(needed_size >= 0); + ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); + if (ctx.pid_filename != nullptr) { + snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr); } + } else if (ctx.config.output_name && ctx.config.output_name[0] != '\0') { + // set pid file, based on output_name if (!args.ignore_running) { - if (pid_fd._fd == -2) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - BONGOCAT_LOG_ERROR("Another instance of bongocat is already running"); - return EXIT_FAILURE; - } + const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_TEMPLATE, ctx.config.output_name) + 1; + assert(needed_size >= 0); + ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); + if (ctx.pid_filename != nullptr) { + snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_TEMPLATE, + ctx.config.output_name); + } + } else { + const int needed_size = + snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, ctx.config.output_name, platform::slow_rand()) + 1; + assert(needed_size >= 0); + ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); + if (ctx.pid_filename != nullptr) { + snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, + ctx.config.output_name, platform::slow_rand()); + } } - BONGOCAT_LOG_INFO("PID file created: %s", ctx.pid_filename); - BONGOCAT_LOG_INFO("bongocat PID: %d", getpid()); - // Setup signal handlers - ctx.signal_watch_path = config_file; - bongocat_error_t signal_result = signal_setup_handlers(ctx); - if (signal_result != bongocat_error_t::BONGOCAT_SUCCESS) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { - ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; - } - BONGOCAT_LOG_ERROR("Failed to setup signal handlers: %s", bongocat::error_string(signal_result)); - return EXIT_FAILURE; - } - BONGOCAT_LOG_INFO("Signal handler configure (fd=%i)", ctx.signal_fd._fd); - - // Initialize config watcher if requested - if (args.watch_config) { - if (strcmp(config_file, "-") != 0) { - start_config_watcher(ctx, config_file); - } else { - BONGOCAT_LOG_INFO("Skip config watcher, no config watcher for stdin"); - } - } else { - BONGOCAT_LOG_INFO("No config watcher, continuing without hot-reload"); + if (ctx.pid_filename == nullptr) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + BONGOCAT_LOG_ERROR("Failed to allocate PID filename"); + return EXIT_FAILURE; } - - // Initialize all system components - bongocat_error_t result = system_initialize_components(ctx); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - system_cleanup_and_exit(ctx, EXIT_FAILURE); + } else { + ctx.pid_filename = strdup(DEFAULT_PID_FILE); + } + + // Handle toggle mode + if (args.toggle_mode) { + BONGOCAT_LOG_INFO("Toggle... (pid=%s)", ctx.pid_filename); + if (const int toggle_result = process_handle_toggle(argv[0], ctx.pid_filename); toggle_result >= 0) { + return toggle_result; // Either successfully toggled off or error } - - assert(ctx.input != nullptr); - assert(ctx.animation != nullptr); - assert(ctx.wayland != nullptr); - - if (abs(ctx.config.cat_x_offset) > ctx.wayland->wayland_context._screen_width) { - BONGOCAT_LOG_WARNING("cat_x_offset %d may position cat off-screen (screen width: %d)", - ctx.config.cat_x_offset, ctx.wayland->wayland_context._screen_width); + // toggle_result == -1 means continue with startup + } + + // Create PID file to track this instance + const platform::FileDescriptor pid_fd = process_create_pid_file(ctx.pid_filename); + if (pid_fd._fd < 0) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; } - - BONGOCAT_LOG_INFO("Bar dimensions: %dx%d", ctx.wayland->wayland_context._screen_width, ctx.config.overlay_height); - - BONGOCAT_LOG_INFO("Bongo Cat Overlay configured successfully"); - - // trigger initial rendering - platform::wayland::request_render(*ctx.animation); - // Main Wayland event loop with graceful shutdown - assert(ctx.wayland != nullptr); - assert(ctx.input != nullptr); - /// @NOTE: config_watcher os optional - result = run(*ctx.wayland, ctx.running, ctx.signal_fd._fd, *ctx.input, ctx.config, ctx.config_watcher.ptr, config_reload_callback); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Wayland event loop error: %s", bongocat::error_string(result)); - system_cleanup_and_exit(ctx, EXIT_FAILURE); - } - - BONGOCAT_LOG_INFO("Main loop exited, shutting down"); - system_cleanup_and_exit(ctx, EXIT_SUCCESS); - - // Never reached - //return EXIT_SUCCESS; + BONGOCAT_LOG_ERROR("Failed to create PID file"); + return EXIT_FAILURE; + } + if (!args.ignore_running) { + if (pid_fd._fd == -2) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + BONGOCAT_LOG_ERROR("Another instance of bongocat is already running"); + return EXIT_FAILURE; + } + } + BONGOCAT_LOG_INFO("PID file created: %s", ctx.pid_filename); + BONGOCAT_LOG_INFO("bongocat PID: %d", getpid()); + + // Setup signal handlers + ctx.signal_watch_path = config_file; + bongocat_error_t signal_result = signal_setup_handlers(ctx); + if (signal_result != bongocat_error_t::BONGOCAT_SUCCESS) { + if (args.config_file == nullptr && get_main_context().default_config_filename) { + ::free(get_main_context().default_config_filename); + get_main_context().default_config_filename = nullptr; + } + BONGOCAT_LOG_ERROR("Failed to setup signal handlers: %s", bongocat::error_string(signal_result)); + return EXIT_FAILURE; + } + BONGOCAT_LOG_INFO("Signal handler configure (fd=%i)", ctx.signal_fd._fd); + + // Initialize config watcher if requested + if (args.watch_config) { + if (strcmp(config_file, "-") != 0) { + start_config_watcher(ctx, config_file); + } else { + BONGOCAT_LOG_INFO("Skip config watcher, no config watcher for stdin"); + } + } else { + BONGOCAT_LOG_INFO("No config watcher, continuing without hot-reload"); + } + + // Initialize all system components + bongocat_error_t result = system_initialize_components(ctx); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + system_cleanup_and_exit(ctx, EXIT_FAILURE); + } + + assert(ctx.input != nullptr); + assert(ctx.animation != nullptr); + assert(ctx.wayland != nullptr); + + if (abs(ctx.config.cat_x_offset) > ctx.wayland->wayland_context._screen_width) { + BONGOCAT_LOG_WARNING("cat_x_offset %d may position cat off-screen (screen width: %d)", ctx.config.cat_x_offset, + ctx.wayland->wayland_context._screen_width); + } + + BONGOCAT_LOG_INFO("Bar dimensions: %dx%d", ctx.wayland->wayland_context._screen_width, ctx.config.overlay_height); + + BONGOCAT_LOG_INFO("Bongo Cat Overlay configured successfully"); + + // trigger initial rendering + platform::wayland::request_render(*ctx.animation); + // Main Wayland event loop with graceful shutdown + assert(ctx.wayland != nullptr); + assert(ctx.input != nullptr); + /// @NOTE: config_watcher os optional + result = run(*ctx.wayland, ctx.running, ctx.signal_fd._fd, *ctx.input, ctx.config, ctx.config_watcher.ptr, + config_reload_callback); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Wayland event loop error: %s", bongocat::error_string(result)); + system_cleanup_and_exit(ctx, EXIT_FAILURE); + } + + BONGOCAT_LOG_INFO("Main loop exited, shutting down"); + system_cleanup_and_exit(ctx, EXIT_SUCCESS); + + // Never reached + // return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/embedded_assets/bongocat/bongocat_get_sprite_sheet.cpp b/src/embedded_assets/bongocat/bongocat_get_sprite_sheet.cpp index be25d90b..5aff3b8c 100644 --- a/src/embedded_assets/bongocat/bongocat_get_sprite_sheet.cpp +++ b/src/embedded_assets/bongocat/bongocat_get_sprite_sheet.cpp @@ -1,17 +1,22 @@ -#include "embedded_assets/embedded_image.h" #include "embedded_assets/bongocat/bongocat.h" #include "embedded_assets/bongocat/bongocat_images.h" +#include "embedded_assets/embedded_image.h" namespace bongocat::assets { - embedded_image_t get_bongocat_sprite(size_t i) { - using namespace assets; - switch (i) { - case BONGOCAT_FRAME_BOTH_UP: return {bongo_cat_both_up_png, bongo_cat_both_up_png_size, "bongo-cat-both-up"}; - case BONGOCAT_FRAME_LEFT_DOWN: return {bongo_cat_left_down_png, bongo_cat_left_down_png_size, "bongo-cat-left-down"}; - case BONGOCAT_FRAME_RIGHT_DOWN: return {bongo_cat_right_down_png, bongo_cat_right_down_png_size, "bongo-cat-right-down"}; - case BONGOCAT_FRAME_BOTH_DOWN: return {bongo_cat_both_down_png, bongo_cat_both_down_png_size, "bongo-cat-both-down"}; - default: return {}; - } - return { }; - } -} \ No newline at end of file +embedded_image_t get_bongocat_sprite(size_t i) { + using namespace assets; + switch (i) { + case BONGOCAT_FRAME_BOTH_UP: + return {bongo_cat_both_up_png, bongo_cat_both_up_png_size, "bongo-cat-both-up"}; + case BONGOCAT_FRAME_LEFT_DOWN: + return {bongo_cat_left_down_png, bongo_cat_left_down_png_size, "bongo-cat-left-down"}; + case BONGOCAT_FRAME_RIGHT_DOWN: + return {bongo_cat_right_down_png, bongo_cat_right_down_png_size, "bongo-cat-right-down"}; + case BONGOCAT_FRAME_BOTH_DOWN: + return {bongo_cat_both_down_png, bongo_cat_both_down_png_size, "bongo-cat-both-down"}; + default: + return {}; + } + return {}; +} +} // namespace bongocat::assets \ No newline at end of file diff --git a/src/embedded_assets/bongocat/bongocat_images.c b/src/embedded_assets/bongocat/bongocat_images.c index b83e1dd8..a5768bb6 100644 --- a/src/embedded_assets/bongocat/bongocat_images.c +++ b/src/embedded_assets/bongocat/bongocat_images.c @@ -1,6 +1,7 @@ -#include #include "embedded_assets/bongocat/bongocat_images.h" +#include + // Embedded asset data const unsigned char bongo_cat_both_up_png[] = { #embed "../../../assets/bongo-cat-both-up.png" diff --git a/src/embedded_assets/min_dm/min_dm_get_sprite_sheet.cpp b/src/embedded_assets/min_dm/min_dm_get_sprite_sheet.cpp index a585255d..be7d4955 100644 --- a/src/embedded_assets/min_dm/min_dm_get_sprite_sheet.cpp +++ b/src/embedded_assets/min_dm/min_dm_get_sprite_sheet.cpp @@ -4,25 +4,40 @@ #include "embedded_assets/min_dm/min_dm_sprite.h" namespace bongocat::assets { - embedded_image_t get_min_dm_sprite_sheet(size_t i) { - using namespace assets; - switch (i) { - case DM_BOTAMON_ANIM_INDEX: return {dm_botamon_png, dm_botamon_png_size, "botamon"}; - case DM_KOROMON_ANIM_INDEX: return {dm_koromon_png, dm_koromon_png_size, "koromon"}; - case DM_AGUMON_ANIM_INDEX: return {dm_agumon_png, dm_agumon_png_size, "agumon"}; - case DM_BETAMON_ANIM_INDEX: return {dm_betamon_png, dm_betamon_png_size, "betamon"}; - case DM_GREYMON_ANIM_INDEX: return {dm_greymon_png, dm_greymon_png_size, "greymon"}; - case DM_TYRANOMON_ANIM_INDEX: return {dm_tyranomon_png, dm_tyranomon_png_size, "tyranomon"}; - case DM_DEVIMON_ANIM_INDEX: return {dm_devimon_png, dm_devimon_png_size, "devimon"}; - case DM_MERAMON_ANIM_INDEX: return {dm_meramon_png, dm_meramon_png_size, "meramon"}; - case DM_AIRDRAMON_ANIM_INDEX: return {dm_airdramon_png, dm_airdramon_png_size, "airdramon"}; - case DM_SEADRAMON_ANIM_INDEX: return {dm_seadramon_png, dm_seadramon_png_size, "seadramon"}; - case DM_NUMEMON_ANIM_INDEX: return {dm_numemon_png, dm_numemon_png_size, "numemon"}; - case DM_METAL_GREYMON_ANIM_INDEX: return {dm_metal_greymon_png, dm_metal_greymon_png_size, "metal_greymon"}; - case DM_MAMEMON_ANIM_INDEX: return {dm_mamemon_png, dm_mamemon_png_size, "mamemon"}; - case DM_MONZAEMON_ANIM_INDEX: return {dm_monzaemon_png, dm_monzaemon_png_size, "monzaemon"}; - default: return { nullptr, 0, "" }; - } - return { nullptr, 0, "" }; - } -} \ No newline at end of file +embedded_image_t get_min_dm_sprite_sheet(size_t i) { + using namespace assets; + switch (i) { + case DM_BOTAMON_ANIM_INDEX: + return {dm_botamon_png, dm_botamon_png_size, "botamon"}; + case DM_KOROMON_ANIM_INDEX: + return {dm_koromon_png, dm_koromon_png_size, "koromon"}; + case DM_AGUMON_ANIM_INDEX: + return {dm_agumon_png, dm_agumon_png_size, "agumon"}; + case DM_BETAMON_ANIM_INDEX: + return {dm_betamon_png, dm_betamon_png_size, "betamon"}; + case DM_GREYMON_ANIM_INDEX: + return {dm_greymon_png, dm_greymon_png_size, "greymon"}; + case DM_TYRANOMON_ANIM_INDEX: + return {dm_tyranomon_png, dm_tyranomon_png_size, "tyranomon"}; + case DM_DEVIMON_ANIM_INDEX: + return {dm_devimon_png, dm_devimon_png_size, "devimon"}; + case DM_MERAMON_ANIM_INDEX: + return {dm_meramon_png, dm_meramon_png_size, "meramon"}; + case DM_AIRDRAMON_ANIM_INDEX: + return {dm_airdramon_png, dm_airdramon_png_size, "airdramon"}; + case DM_SEADRAMON_ANIM_INDEX: + return {dm_seadramon_png, dm_seadramon_png_size, "seadramon"}; + case DM_NUMEMON_ANIM_INDEX: + return {dm_numemon_png, dm_numemon_png_size, "numemon"}; + case DM_METAL_GREYMON_ANIM_INDEX: + return {dm_metal_greymon_png, dm_metal_greymon_png_size, "metal_greymon"}; + case DM_MAMEMON_ANIM_INDEX: + return {dm_mamemon_png, dm_mamemon_png_size, "mamemon"}; + case DM_MONZAEMON_ANIM_INDEX: + return {dm_monzaemon_png, dm_monzaemon_png_size, "monzaemon"}; + default: + return {nullptr, 0, ""}; + } + return {nullptr, 0, ""}; +} +} // namespace bongocat::assets \ No newline at end of file diff --git a/src/embedded_assets/min_dm/min_dm_images.c b/src/embedded_assets/min_dm/min_dm_images.c index d536406e..b2d1cc45 100644 --- a/src/embedded_assets/min_dm/min_dm_images.c +++ b/src/embedded_assets/min_dm/min_dm_images.c @@ -1,4 +1,5 @@ #include "embedded_assets/min_dm/min_dm_images.h" + #include // Botamon diff --git a/src/embedded_assets/misc/misc_get_sprite_sheet.cpp b/src/embedded_assets/misc/misc_get_sprite_sheet.cpp index d795f1ea..d44df681 100644 --- a/src/embedded_assets/misc/misc_get_sprite_sheet.cpp +++ b/src/embedded_assets/misc/misc_get_sprite_sheet.cpp @@ -4,21 +4,25 @@ #include "embedded_assets/misc/misc_sprite.h" namespace bongocat::assets { - embedded_image_t get_misc_sprite_sheet(size_t i) { - using namespace assets; - switch (i) { - case MISC_NEKO_ANIM_INDEX: return {misc_neko_png, misc_neko_png_size, "neko"}; - default: return { nullptr, 0, "" }; - } - return { nullptr, 0, "" }; - } +embedded_image_t get_misc_sprite_sheet(size_t i) { + using namespace assets; + switch (i) { + case MISC_NEKO_ANIM_INDEX: + return {misc_neko_png, misc_neko_png_size, "neko"}; + default: + return {nullptr, 0, ""}; + } + return {nullptr, 0, ""}; +} - custom_animation_settings_t get_misc_sprite_sheet_columns(size_t i) { - using namespace assets; - switch (i) { - case MISC_NEKO_ANIM_INDEX: return MISC_NEKO_SPRITE_SHEET_SETTINGS; - default: return {}; - } - return {}; - } -} \ No newline at end of file +custom_animation_settings_t get_misc_sprite_sheet_columns(size_t i) { + using namespace assets; + switch (i) { + case MISC_NEKO_ANIM_INDEX: + return MISC_NEKO_SPRITE_SHEET_SETTINGS; + default: + return {}; + } + return {}; +} +} // namespace bongocat::assets \ No newline at end of file diff --git a/src/embedded_assets/misc/misc_images.c b/src/embedded_assets/misc/misc_images.c index b0d78d65..05ebfd71 100644 --- a/src/embedded_assets/misc/misc_images.c +++ b/src/embedded_assets/misc/misc_images.c @@ -1,4 +1,5 @@ #include "embedded_assets/misc/misc_images.h" + #include // neko @@ -6,4 +7,3 @@ const unsigned char misc_neko_png[] = { #embed "../../../assets/misc/neko.png" }; const size_t misc_neko_png_size = sizeof(misc_neko_png); - diff --git a/src/embedded_assets/ms_agent/embedded_assets_ms_agent.cpp b/src/embedded_assets/ms_agent/embedded_assets_ms_agent.cpp index d2cd8626..afca7952 100644 --- a/src/embedded_assets/ms_agent/embedded_assets_ms_agent.cpp +++ b/src/embedded_assets/ms_agent/embedded_assets_ms_agent.cpp @@ -1,116 +1,126 @@ #include "embedded_assets/embedded_image.h" #include "embedded_assets/ms_agent/ms_agent.hpp" -#include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "embedded_assets/ms_agent/ms_agent_images.h" +#include "embedded_assets/ms_agent/ms_agent_sprite.h" namespace bongocat::assets { - embedded_image_t get_ms_agent_sprite_sheet(size_t i) { - switch (i) { - case CLIPPY_ANIM_INDEX: return {clippy_png, clippy_png_size, "clippy"}; +embedded_image_t get_ms_agent_sprite_sheet(size_t i) { + switch (i) { + case CLIPPY_ANIM_INDEX: + return {clippy_png, clippy_png_size, "clippy"}; #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - case LINKS_ANIM_INDEX: return {links_png, links_png_size, "links"}; - case ROVER_ANIM_INDEX: return {rover_png, rover_png_size, "rover"}; - case MERLIN_ANIM_INDEX: return {merlin_png, merlin_png_size, "merlin"}; + case LINKS_ANIM_INDEX: + return {links_png, links_png_size, "links"}; + case ROVER_ANIM_INDEX: + return {rover_png, rover_png_size, "rover"}; + case MERLIN_ANIM_INDEX: + return {merlin_png, merlin_png_size, "merlin"}; #endif - default: return {}; - } - return {}; - } - - ms_agent_animation_indices_t get_ms_agent_animation_indices(size_t i) { - switch (i) { - case CLIPPY_ANIM_INDEX: return { - .start_index_frame_idle = 0, - .end_index_frame_idle = CLIPPY_FRAMES_IDLE-1, - - .start_index_frame_boring = 0, - .end_index_frame_boring = CLIPPY_FRAMES_BORING-1, - - .start_index_frame_start_writing = 0, - .end_index_frame_start_writing = CLIPPY_FRAMES_START_WRITING-1, - - .start_index_frame_writing = 0, - .end_index_frame_writing = CLIPPY_FRAMES_WRITING-1, - - .start_index_frame_end_writing = 0, - .end_index_frame_end_writing = CLIPPY_FRAMES_END_WRITING-1, - - .start_index_frame_sleep = 0, - .end_index_frame_sleep = CLIPPY_FRAMES_SLEEP-1, - - .start_index_frame_wake_up = 0, - .end_index_frame_wake_up = CLIPPY_FRAMES_WAKE_UP-1, - }; + default: + return {}; + } + return {}; +} + +ms_agent_animation_indices_t get_ms_agent_animation_indices(size_t i) { + switch (i) { + case CLIPPY_ANIM_INDEX: + return { + .start_index_frame_idle = 0, + .end_index_frame_idle = CLIPPY_FRAMES_IDLE - 1, + + .start_index_frame_boring = 0, + .end_index_frame_boring = CLIPPY_FRAMES_BORING - 1, + + .start_index_frame_start_writing = 0, + .end_index_frame_start_writing = CLIPPY_FRAMES_START_WRITING - 1, + + .start_index_frame_writing = 0, + .end_index_frame_writing = CLIPPY_FRAMES_WRITING - 1, + + .start_index_frame_end_writing = 0, + .end_index_frame_end_writing = CLIPPY_FRAMES_END_WRITING - 1, + + .start_index_frame_sleep = 0, + .end_index_frame_sleep = CLIPPY_FRAMES_SLEEP - 1, + + .start_index_frame_wake_up = 0, + .end_index_frame_wake_up = CLIPPY_FRAMES_WAKE_UP - 1, + }; #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - case LINKS_ANIM_INDEX: return { - .start_index_frame_idle = 0, - .end_index_frame_idle = LINKS_FRAMES_IDLE-1, - - .start_index_frame_boring = 0, - .end_index_frame_boring = LINKS_FRAMES_BORING-1, - - .start_index_frame_start_writing = 0, - .end_index_frame_start_writing = LINKS_FRAMES_START_WRITING-1, - - .start_index_frame_writing = 0, - .end_index_frame_writing = LINKS_FRAMES_WRITING-1, - - .start_index_frame_end_writing = 0, - .end_index_frame_end_writing = LINKS_FRAMES_END_WRITING-1, - - .start_index_frame_sleep = 0, - .end_index_frame_sleep = LINKS_FRAMES_SLEEP-1, - - .start_index_frame_wake_up = 0, - .end_index_frame_wake_up = LINKS_FRAMES_WAKE_UP-1, - }; - case ROVER_ANIM_INDEX: return { - .start_index_frame_idle = 0, - .end_index_frame_idle = ROVER_FRAMES_IDLE-1, - - .start_index_frame_boring = 0, - .end_index_frame_boring = ROVER_FRAMES_BORING-1, - - .start_index_frame_start_writing = 0, - .end_index_frame_start_writing = ROVER_FRAMES_START_WRITING-1, - - .start_index_frame_writing = 0, - .end_index_frame_writing = ROVER_FRAMES_WRITING-1, - - .start_index_frame_end_writing = 0, - .end_index_frame_end_writing = ROVER_FRAMES_END_WRITING-1, - - .start_index_frame_sleep = 0, - .end_index_frame_sleep = ROVER_FRAMES_SLEEP-1, - - .start_index_frame_wake_up = 0, - .end_index_frame_wake_up = ROVER_FRAMES_WAKE_UP-1, - }; - case MERLIN_ANIM_INDEX: return { - .start_index_frame_idle = 0, - .end_index_frame_idle = MERLIN_FRAMES_IDLE-1, - - .start_index_frame_boring = 0, - .end_index_frame_boring = MERLIN_FRAMES_BORING-1, - - .start_index_frame_start_writing = 0, - .end_index_frame_start_writing = MERLIN_FRAMES_START_WRITING-1, - - .start_index_frame_writing = 0, - .end_index_frame_writing = MERLIN_FRAMES_WRITING-1, - - .start_index_frame_end_writing = 0, - .end_index_frame_end_writing = MERLIN_FRAMES_END_WRITING-1, - - .start_index_frame_sleep = 0, - .end_index_frame_sleep = MERLIN_FRAMES_SLEEP-1, - - .start_index_frame_wake_up = 0, - .end_index_frame_wake_up = MERLIN_FRAMES_WAKE_UP-1, - }; + case LINKS_ANIM_INDEX: + return { + .start_index_frame_idle = 0, + .end_index_frame_idle = LINKS_FRAMES_IDLE - 1, + + .start_index_frame_boring = 0, + .end_index_frame_boring = LINKS_FRAMES_BORING - 1, + + .start_index_frame_start_writing = 0, + .end_index_frame_start_writing = LINKS_FRAMES_START_WRITING - 1, + + .start_index_frame_writing = 0, + .end_index_frame_writing = LINKS_FRAMES_WRITING - 1, + + .start_index_frame_end_writing = 0, + .end_index_frame_end_writing = LINKS_FRAMES_END_WRITING - 1, + + .start_index_frame_sleep = 0, + .end_index_frame_sleep = LINKS_FRAMES_SLEEP - 1, + + .start_index_frame_wake_up = 0, + .end_index_frame_wake_up = LINKS_FRAMES_WAKE_UP - 1, + }; + case ROVER_ANIM_INDEX: + return { + .start_index_frame_idle = 0, + .end_index_frame_idle = ROVER_FRAMES_IDLE - 1, + + .start_index_frame_boring = 0, + .end_index_frame_boring = ROVER_FRAMES_BORING - 1, + + .start_index_frame_start_writing = 0, + .end_index_frame_start_writing = ROVER_FRAMES_START_WRITING - 1, + + .start_index_frame_writing = 0, + .end_index_frame_writing = ROVER_FRAMES_WRITING - 1, + + .start_index_frame_end_writing = 0, + .end_index_frame_end_writing = ROVER_FRAMES_END_WRITING - 1, + + .start_index_frame_sleep = 0, + .end_index_frame_sleep = ROVER_FRAMES_SLEEP - 1, + + .start_index_frame_wake_up = 0, + .end_index_frame_wake_up = ROVER_FRAMES_WAKE_UP - 1, + }; + case MERLIN_ANIM_INDEX: + return { + .start_index_frame_idle = 0, + .end_index_frame_idle = MERLIN_FRAMES_IDLE - 1, + + .start_index_frame_boring = 0, + .end_index_frame_boring = MERLIN_FRAMES_BORING - 1, + + .start_index_frame_start_writing = 0, + .end_index_frame_start_writing = MERLIN_FRAMES_START_WRITING - 1, + + .start_index_frame_writing = 0, + .end_index_frame_writing = MERLIN_FRAMES_WRITING - 1, + + .start_index_frame_end_writing = 0, + .end_index_frame_end_writing = MERLIN_FRAMES_END_WRITING - 1, + + .start_index_frame_sleep = 0, + .end_index_frame_sleep = MERLIN_FRAMES_SLEEP - 1, + + .start_index_frame_wake_up = 0, + .end_index_frame_wake_up = MERLIN_FRAMES_WAKE_UP - 1, + }; #endif - default: return {}; - } - return {}; - } -} \ No newline at end of file + default: + return {}; + } + return {}; +} +} // namespace bongocat::assets \ No newline at end of file diff --git a/src/embedded_assets/ms_agent/ms_agent_images.c b/src/embedded_assets/ms_agent/ms_agent_images.c index 168aa572..65b1f68f 100644 --- a/src/embedded_assets/ms_agent/ms_agent_images.c +++ b/src/embedded_assets/ms_agent/ms_agent_images.c @@ -1,4 +1,5 @@ #include "embedded_assets/ms_agent/ms_agent_images.h" + #include // Embedded asset data @@ -7,20 +8,19 @@ const unsigned char clippy_png[] = { }; const size_t clippy_png_size = sizeof(clippy_png); - #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS const unsigned char links_png[] = { -#embed "../../../assets/ms_agent/links.png" +# embed "../../../assets/ms_agent/links.png" }; const size_t links_png_size = sizeof(links_png); const unsigned char rover_png[] = { -#embed "../../../assets/ms_agent/rover.png" +# embed "../../../assets/ms_agent/rover.png" }; const size_t rover_png_size = sizeof(rover_png); const unsigned char merlin_png[] = { -#embed "../../../assets/ms_agent/merlin.png" +# embed "../../../assets/ms_agent/merlin.png" }; const size_t merlin_png_size = sizeof(merlin_png); #endif diff --git a/src/graphics/animation.cpp b/src/graphics/animation.cpp index c1902da2..1c9e2f8b 100644 --- a/src/graphics/animation.cpp +++ b/src/graphics/animation.cpp @@ -1,5081 +1,5217 @@ -#include "graphics/animation_context.h" #include "graphics/animation.h" -#include "platform/wayland.h" -#include "utils/time.h" -#include -#include -#include -#include -#include -#include -#include +#include "embedded_assets/bongocat/bongocat.h" #include "embedded_assets/bongocat/bongocat.hpp" +#include "embedded_assets/custom/custom_sprite.h" +#include "embedded_assets/min_dm/min_dm_sprite.h" +#include "embedded_assets/misc/misc.hpp" +#include "embedded_assets/misc/misc_sprite.h" +#include "embedded_assets/ms_agent/ms_agent.hpp" +#include "embedded_assets/ms_agent/ms_agent_sprite.h" +#include "embedded_assets/pkmn/pkmn_sprite.h" +#include "graphics/animation_context.h" #include "graphics/embedded_assets_dms.h" +#include "graphics/embedded_assets_pkmn.h" #include "image_loader/bongocat/load_images_bongocat.h" -#include "image_loader/ms_agent/load_images_ms_agent.h" #include "image_loader/dm/load_images_dm.h" #include "image_loader/dm20/load_images_dm20.h" +#include "image_loader/dmc/load_images_dmc.h" #include "image_loader/dmx/load_images_dmx.h" +#include "image_loader/ms_agent/load_images_ms_agent.h" #include "image_loader/pen/load_images_pen.h" #include "image_loader/pen20/load_images_pen20.h" -#include "image_loader/dmc/load_images_dmc.h" -#include "embedded_assets/bongocat/bongocat.h" -#include "embedded_assets/custom/custom_sprite.h" -#include "embedded_assets/ms_agent/ms_agent.hpp" -#include "embedded_assets/ms_agent/ms_agent_sprite.h" -#include "embedded_assets/min_dm/min_dm_sprite.h" -#include "embedded_assets/misc/misc.hpp" -#include "graphics/embedded_assets_pkmn.h" -#include "embedded_assets/pkmn/pkmn_sprite.h" -#include "embedded_assets/misc/misc.hpp" -#include "embedded_assets/misc/misc_sprite.h" +#include "platform/wayland.h" +#include "utils/time.h" + +#include +#include +#include +#include +#include +#include +#include #if defined(FEATURE_CUSTOM_SPRITE_SHEETS) || defined(FEATURE_MISC_EMBEDDED_ASSETS) -#define FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION +# define FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION #endif namespace bongocat::animation { - // ============================================================================= - // GLOBAL STATE AND CONFIGURATION - // ============================================================================= - - inline static constexpr platform::time_ms_t POOL_MIN_TIMEOUT_MS = 5; - inline static constexpr platform::time_ms_t POOL_MAX_TIMEOUT_MS = 1000; - inline static constexpr int MAX_ATTEMPTS = 2048; - static_assert(POOL_MAX_TIMEOUT_MS >= POOL_MIN_TIMEOUT_MS); - - inline static constexpr platform::time_ms_t COND_RELOAD_CONFIGS_TIMEOUT_MS = 5000; - - inline static constexpr int CHANCE_FOR_SKIPPING_MOVEMENT_PERCENT = 30; // in percent - inline static constexpr int SMALL_MAX_DISTANCE_PER_MOVEMENT_PART = 3; // 1/3 of movement_radius (for small areas) - inline static constexpr int MAX_DISTANCE_PER_MOVEMENT_PART = 5; // 1/5 of movement_radius - static_assert(SMALL_MAX_DISTANCE_PER_MOVEMENT_PART > 0); - static_assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); - inline static constexpr int MAX_MOVEMENT_RADIUS_SMALL = 100; - inline static constexpr int FLIP_DIRECTION_NEAR_WALL_PERCENT = 70; - inline static constexpr int SMALL_FLIP_DIRECTION_NEAR_WALL_PERCENT = 40; - - inline static constexpr int SLEEP_BORING_PART = 2; // 1/2 of sleep time for triggering boring animation - - // ============================================================================= - // ANIMATION STATE MANAGEMENT MODULE - // ============================================================================= - - static bool is_sleep_time(const config::config_t& config) { - time_t raw_time; - tm time_info{}; - time(&raw_time); - localtime_r(&raw_time, &time_info); - - const int now_minutes = time_info.tm_hour * 60 + time_info.tm_min; - const int begin = config.sleep_begin.hour * 60 + config.sleep_begin.min; - const int end = config.sleep_end.hour * 60 + config.sleep_end.min; - - // Normal range (e.g., 10:00–22:00): begin < end && (now_minutes >= begin && now_minutes < end) - // Overnight range (e.g., 22:00–06:00): begin > end && (now_minutes >= begin || now_minutes < end) - - return (begin == end) || - (begin < end ? (now_minutes >= begin && now_minutes < end) - : (now_minutes >= begin || now_minutes < end)); - } - - enum class animation_state_row_t : uint8_t { - Idle, - StartWriting, - Writing, - EndWriting, - Happy, - FallASleep, - Sleep, - WakeUp, - Boring, - Test, - StartWorking, - Working, - EndWorking, - StartMoving, - Moving, - EndMoving, - StartRunning, - Running, - EndRunning, - }; - - struct animation_state_t { - // animation timing - platform::time_ms_t frame_delta_ms_counter{0}; - platform::time_ms_t update_delta_ms_counter{0}; - platform::time_ns_t frame_time_ns{0}; - platform::time_ms_t frame_time_ms{0}; - platform::time_ms_t hold_frame_ms{0}; - platform::timestamp_ms_t last_frame_update_ms{0}; - platform::timestamp_ms_t time_until_next_frame_ms{0}; - - // state - bool hold_frame_after_release{false}; - bool show_boring_animation_once{false}; - bool is_idle_sleep{false}; - - // moving - float anim_velocity{0.0}; - float anim_distance{0.0}; - float anim_last_direction{0.0}; - int32_t anim_pause_after_movement_ms{0}; - - // animation player data - animation_state_row_t row_state{animation_state_row_t::Idle}; - // for ms agent and sprite sheets - int32_t start_col_index{0}; - int32_t end_col_index{0}; - // for am/bongocat/pkmn (cached animation frames) - int32_t animations_index{0}; // for sprite_sheet.frames (col indices) array - }; - - struct anim_next_frame_result_t { - bool moved{false}; - bool frame_changed{false}; - bool rerender{false}; - int32_t new_col{0}; - int32_t new_row{0}; - }; - - - struct animation_trigger_t { - trigger_animation_cause_mask_t anim_cause{trigger_animation_cause_mask_t::NONE}; - int any_key_press_counter{0}; - }; - struct anim_handle_key_press_result_t { - animation_trigger_t trigger; - anim_next_frame_result_t update_frame_result; - }; - - struct anim_conditions_t { - platform::timestamp_ms_t last_key_pressed_timestamp{0}; - - // trigger (start animation) - bool any_key_pressed{false}; - bool trigger_test_animation{false}; - bool check_for_idle_sleep{false}; - bool process_movement{false}; - - // trigger (back to idle) - bool release_test_frame{false}; - bool release_frame_after_press{false}; - bool release_frame_after_update{false}; - - // for continues animations - bool process_idle_animation{false}; - bool process_movement_animation{false}; - bool process_working_animation{false}; - bool process_running_animation{false}; - bool go_next_frame{false}; - bool go_next_frame_running{false}; - bool release_frame_for_non_idle{false}; - - // current animation - bool is_writing{false}; - bool is_moving{false}; - bool is_working{false}; - bool is_running{false}; - bool continue_writing{false}; - }; - static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_context_t& ctx, - const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - const animation_state_t& current_state, - const animation_trigger_t& trigger, - const config::config_t& current_config) { - assert(input.shm != nullptr); - const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - const platform::timestamp_ms_t fps_ms = 1000/current_config.fps; - - const bool any_key_pressed = has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress) && trigger.any_key_press_counter > 0; - - const bool process_idle_animation_by_animation_speed = current_config.idle_animation && current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms; - const bool process_idle_animation_by_fps = current_config.idle_animation && current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms; - const bool process_idle_animation = process_idle_animation_by_animation_speed || process_idle_animation_by_fps; - - const bool release_frame_by_animation_speed = !current_config.idle_animation && current_config.animation_speed_ms > 0 && current_state.hold_frame_ms > current_config.animation_speed_ms; - const bool release_frame_animation_by_fps = !current_config.idle_animation && current_config.animation_speed_ms <= 0 && current_state.hold_frame_ms > fps_ms; - const bool release_frame_for_non_idle = release_frame_by_animation_speed || release_frame_animation_by_fps; - const bool go_next_frame = (current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms) || (current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms); - - const bool process_movement_animation_by_animation_speed = current_config.movement_speed > 0 && current_config.movement_radius > 0 && current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms; - const bool process_movement_animation_by_fps = current_config.movement_speed > 0 && current_config.movement_radius > 0 && current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms; - const bool process_movement_animation = process_movement_animation_by_animation_speed || process_movement_animation_by_fps; - - const bool process_working_animation_by_animation_speed = current_config.cpu_running_factor < 1.0 && current_config.cpu_threshold > 0 && current_config.update_rate_ms > 0 && current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms; - const bool process_working_animation_by_fps = current_config.cpu_running_factor < 1.0 && current_config.cpu_threshold > 0 && current_config.update_rate_ms > 0 && current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms; - const bool process_working_animation = process_working_animation_by_animation_speed || process_working_animation_by_fps; - - const bool process_running_animation_by_animation_speed = current_config.cpu_running_factor >= 1.0 && current_config.cpu_threshold > 0 && current_config.update_rate_ms > 0 && current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms; - const bool process_running_animation_by_fps = current_config.cpu_running_factor >= 1.0 && current_config.cpu_threshold > 0 && current_config.update_rate_ms > 0 && current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms; - const bool process_running_animation = process_running_animation_by_animation_speed || process_running_animation_by_fps; - - const bool process_movement = current_config.movement_speed > 0 && current_config.movement_radius > 0 && current_state.update_delta_ms_counter > current_config.animation_speed_ms; - - const bool is_writing = current_state.row_state == animation_state_row_t::StartWriting || current_state.row_state == animation_state_row_t::Writing || current_state.row_state == animation_state_row_t::EndWriting; - const bool release_frame_after_press = !any_key_pressed && current_state.hold_frame_ms > current_config.keypress_duration_ms; - - const bool is_running = current_state.row_state == animation_state_row_t::StartRunning || current_state.row_state == animation_state_row_t::Running; - double running_animation_speed_factor = 1.0; - if (current_config.cpu_running_factor >= 1.0 && update_shm.cpu_active) { - running_animation_speed_factor = update_shm.avg_cpu_usage > 0 ? 1.0 / ((update_shm.avg_cpu_usage/100.0) * (current_config.cpu_running_factor)) : 0; - } - const double running_animation_speed_ms = running_animation_speed_factor * current_config.animation_speed_ms; - const bool go_next_frame_running = current_config.cpu_running_factor >= 1.0 && ((running_animation_speed_ms > 0 && static_cast(current_state.frame_delta_ms_counter) > running_animation_speed_ms) || (running_animation_speed_ms <= 0 && (running_animation_speed_factor*static_cast(current_state.frame_delta_ms_counter)) > static_cast(fps_ms))); - +// ============================================================================= +// GLOBAL STATE AND CONFIGURATION +// ============================================================================= + +inline static constexpr platform::time_ms_t POOL_MIN_TIMEOUT_MS = 5; +inline static constexpr platform::time_ms_t POOL_MAX_TIMEOUT_MS = 1000; +inline static constexpr int MAX_ATTEMPTS = 2048; +static_assert(POOL_MAX_TIMEOUT_MS >= POOL_MIN_TIMEOUT_MS); + +inline static constexpr platform::time_ms_t COND_RELOAD_CONFIGS_TIMEOUT_MS = 5000; + +inline static constexpr int CHANCE_FOR_SKIPPING_MOVEMENT_PERCENT = 30; // in percent +inline static constexpr int SMALL_MAX_DISTANCE_PER_MOVEMENT_PART = 3; // 1/3 of movement_radius (for small areas) +inline static constexpr int MAX_DISTANCE_PER_MOVEMENT_PART = 5; // 1/5 of movement_radius +static_assert(SMALL_MAX_DISTANCE_PER_MOVEMENT_PART > 0); +static_assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); +inline static constexpr int MAX_MOVEMENT_RADIUS_SMALL = 100; +inline static constexpr int FLIP_DIRECTION_NEAR_WALL_PERCENT = 70; +inline static constexpr int SMALL_FLIP_DIRECTION_NEAR_WALL_PERCENT = 40; + +inline static constexpr int SLEEP_BORING_PART = 2; // 1/2 of sleep time for triggering boring animation + +// ============================================================================= +// ANIMATION STATE MANAGEMENT MODULE +// ============================================================================= + +static bool is_sleep_time(const config::config_t& config) { + time_t raw_time; + tm time_info{}; + time(&raw_time); + localtime_r(&raw_time, &time_info); + + const int now_minutes = time_info.tm_hour * 60 + time_info.tm_min; + const int begin = config.sleep_begin.hour * 60 + config.sleep_begin.min; + const int end = config.sleep_end.hour * 60 + config.sleep_end.min; + + // Normal range (e.g., 10:00–22:00): begin < end && (now_minutes >= begin && now_minutes < end) + // Overnight range (e.g., 22:00–06:00): begin > end && (now_minutes >= begin || now_minutes < end) + + return (begin == end) || + (begin < end ? (now_minutes >= begin && now_minutes < end) : (now_minutes >= begin || now_minutes < end)); +} - assert(SMALL_MAX_DISTANCE_PER_MOVEMENT_PART > 0); - assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); +enum class animation_state_row_t : uint8_t { + Idle, + StartWriting, + Writing, + EndWriting, + Happy, + FallASleep, + Sleep, + WakeUp, + Boring, + Test, + StartWorking, + Working, + EndWorking, + StartMoving, + Moving, + EndMoving, + StartRunning, + Running, + EndRunning, +}; + +struct animation_state_t { + // animation timing + platform::time_ms_t frame_delta_ms_counter{0}; + platform::time_ms_t update_delta_ms_counter{0}; + platform::time_ns_t frame_time_ns{0}; + platform::time_ms_t frame_time_ms{0}; + platform::time_ms_t hold_frame_ms{0}; + platform::timestamp_ms_t last_frame_update_ms{0}; + platform::timestamp_ms_t time_until_next_frame_ms{0}; + + // state + bool hold_frame_after_release{false}; + bool show_boring_animation_once{false}; + bool is_idle_sleep{false}; + + // moving + float anim_velocity{0.0}; + float anim_distance{0.0}; + float anim_last_direction{0.0}; + int32_t anim_pause_after_movement_ms{0}; + + // animation player data + animation_state_row_t row_state{animation_state_row_t::Idle}; + // for ms agent and sprite sheets + int32_t start_col_index{0}; + int32_t end_col_index{0}; + // for am/bongocat/pkmn (cached animation frames) + int32_t animations_index{0}; // for sprite_sheet.frames (col indices) array +}; + +struct anim_next_frame_result_t { + bool moved{false}; + bool frame_changed{false}; + bool rerender{false}; + int32_t new_col{0}; + int32_t new_row{0}; +}; + +struct animation_trigger_t { + trigger_animation_cause_mask_t anim_cause{trigger_animation_cause_mask_t::NONE}; + int any_key_press_counter{0}; +}; +struct anim_handle_key_press_result_t { + animation_trigger_t trigger; + anim_next_frame_result_t update_frame_result; +}; + +struct anim_conditions_t { + platform::timestamp_ms_t last_key_pressed_timestamp{0}; + + // trigger (start animation) + bool any_key_pressed{false}; + bool trigger_test_animation{false}; + bool check_for_idle_sleep{false}; + bool process_movement{false}; + + // trigger (back to idle) + bool release_test_frame{false}; + bool release_frame_after_press{false}; + bool release_frame_after_update{false}; + + // for continues animations + bool process_idle_animation{false}; + bool process_movement_animation{false}; + bool process_working_animation{false}; + bool process_running_animation{false}; + bool go_next_frame{false}; + bool go_next_frame_running{false}; + bool release_frame_for_non_idle{false}; + + // current animation + bool is_writing{false}; + bool is_moving{false}; + bool is_working{false}; + bool is_running{false}; + bool continue_writing{false}; +}; +static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_context_t& ctx, + const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + const animation_state_t& current_state, const animation_trigger_t& trigger, + const config::config_t& current_config) { + assert(input.shm != nullptr); + const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + const platform::timestamp_ms_t fps_ms = 1000 / current_config.fps; + + const bool any_key_pressed = + has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress) && trigger.any_key_press_counter > 0; + + const bool process_idle_animation_by_animation_speed = + current_config.idle_animation && current_config.animation_speed_ms > 0 && + current_state.frame_delta_ms_counter > current_config.animation_speed_ms; + const bool process_idle_animation_by_fps = current_config.idle_animation && current_config.animation_speed_ms <= 0 && + current_state.frame_delta_ms_counter > fps_ms; + const bool process_idle_animation = process_idle_animation_by_animation_speed || process_idle_animation_by_fps; + + const bool release_frame_by_animation_speed = !current_config.idle_animation && + current_config.animation_speed_ms > 0 && + current_state.hold_frame_ms > current_config.animation_speed_ms; + const bool release_frame_animation_by_fps = + !current_config.idle_animation && current_config.animation_speed_ms <= 0 && current_state.hold_frame_ms > fps_ms; + const bool release_frame_for_non_idle = release_frame_by_animation_speed || release_frame_animation_by_fps; + const bool go_next_frame = (current_config.animation_speed_ms > 0 && + current_state.frame_delta_ms_counter > current_config.animation_speed_ms) || + (current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms); + + const bool process_movement_animation_by_animation_speed = + current_config.movement_speed > 0 && current_config.movement_radius > 0 && + current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms; + const bool process_movement_animation_by_fps = + current_config.movement_speed > 0 && current_config.movement_radius > 0 && + current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms; + const bool process_movement_animation = + process_movement_animation_by_animation_speed || process_movement_animation_by_fps; + + const bool process_working_animation_by_animation_speed = + current_config.cpu_running_factor < 1.0 && current_config.cpu_threshold > 0 && + current_config.update_rate_ms > 0 && current_config.animation_speed_ms > 0 && + current_state.frame_delta_ms_counter > current_config.animation_speed_ms; + const bool process_working_animation_by_fps = current_config.cpu_running_factor < 1.0 && + current_config.cpu_threshold > 0 && current_config.update_rate_ms > 0 && + current_config.animation_speed_ms <= 0 && + current_state.frame_delta_ms_counter > fps_ms; + const bool process_working_animation = + process_working_animation_by_animation_speed || process_working_animation_by_fps; + + const bool process_running_animation_by_animation_speed = + current_config.cpu_running_factor >= 1.0 && current_config.cpu_threshold > 0 && + current_config.update_rate_ms > 0 && current_config.animation_speed_ms > 0 && + current_state.frame_delta_ms_counter > current_config.animation_speed_ms; + const bool process_running_animation_by_fps = current_config.cpu_running_factor >= 1.0 && + current_config.cpu_threshold > 0 && current_config.update_rate_ms > 0 && + current_config.animation_speed_ms <= 0 && + current_state.frame_delta_ms_counter > fps_ms; + const bool process_running_animation = + process_running_animation_by_animation_speed || process_running_animation_by_fps; + + const bool process_movement = current_config.movement_speed > 0 && current_config.movement_radius > 0 && + current_state.update_delta_ms_counter > current_config.animation_speed_ms; + + const bool is_writing = current_state.row_state == animation_state_row_t::StartWriting || + current_state.row_state == animation_state_row_t::Writing || + current_state.row_state == animation_state_row_t::EndWriting; + const bool release_frame_after_press = + !any_key_pressed && current_state.hold_frame_ms > current_config.keypress_duration_ms; + + const bool is_running = current_state.row_state == animation_state_row_t::StartRunning || + current_state.row_state == animation_state_row_t::Running; + double running_animation_speed_factor = 1.0; + if (current_config.cpu_running_factor >= 1.0 && update_shm.cpu_active) { + running_animation_speed_factor = + update_shm.avg_cpu_usage > 0 ? 1.0 / ((update_shm.avg_cpu_usage / 100.0) * (current_config.cpu_running_factor)) + : 0; + } + const double running_animation_speed_ms = running_animation_speed_factor * current_config.animation_speed_ms; + const bool go_next_frame_running = + current_config.cpu_running_factor >= 1.0 && + ((running_animation_speed_ms > 0 && + static_cast(current_state.frame_delta_ms_counter) > running_animation_speed_ms) || + (running_animation_speed_ms <= 0 && + (running_animation_speed_factor * static_cast(current_state.frame_delta_ms_counter)) > + static_cast(fps_ms))); + + assert(SMALL_MAX_DISTANCE_PER_MOVEMENT_PART > 0); + assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); + + // @TODO: reduce duplicated condition for when updating frames vs. movement vs. working animation + + return { + .last_key_pressed_timestamp = last_key_pressed_timestamp, + + .any_key_pressed = any_key_pressed, + .trigger_test_animation = + current_config.test_animation_interval_sec > 0 && + current_state.frame_delta_ms_counter > current_config.test_animation_interval_sec * 1000, + .check_for_idle_sleep = + current_config.idle_sleep_timeout_sec > 0 && + ((SLEEP_BORING_PART > 0 && + current_state.frame_delta_ms_counter > current_config.idle_sleep_timeout_sec * 1000 / SLEEP_BORING_PART && + last_key_pressed_timestamp > 0) || + process_idle_animation || process_movement), + .process_movement = process_movement, + + .release_test_frame = current_config.test_animation_duration_ms > 0 && + current_state.frame_delta_ms_counter > current_config.test_animation_duration_ms, + .release_frame_after_press = release_frame_after_press, + .release_frame_after_update = + (!any_key_pressed && has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::CpuUpdate)) && + current_state.hold_frame_ms > current_config.keypress_duration_ms, + + .process_idle_animation = process_idle_animation, + .process_movement_animation = process_movement_animation, + .process_working_animation = process_working_animation, + .process_running_animation = process_running_animation, + .go_next_frame = go_next_frame, + .go_next_frame_running = go_next_frame_running, + .release_frame_for_non_idle = + release_frame_for_non_idle || go_next_frame || (is_running && go_next_frame_running), + + .is_writing = is_writing, + .is_moving = current_state.row_state == animation_state_row_t::StartMoving || + current_state.row_state == animation_state_row_t::Moving || + current_state.row_state == animation_state_row_t::EndMoving, + .is_working = current_state.row_state == animation_state_row_t::StartWorking || + current_state.row_state == animation_state_row_t::Working, + .is_running = is_running, + .continue_writing = ((!any_key_pressed && current_state.hold_frame_ms < current_config.keypress_duration_ms) || + !release_frame_after_press) && + is_writing, + }; +} - // @TODO: reduce duplicated condition for when updating frames vs. movement vs. working animation - - return { - .last_key_pressed_timestamp = last_key_pressed_timestamp, - - .any_key_pressed = any_key_pressed, - .trigger_test_animation = current_config.test_animation_interval_sec > 0 && current_state.frame_delta_ms_counter > current_config.test_animation_interval_sec*1000, - .check_for_idle_sleep = current_config.idle_sleep_timeout_sec > 0 && ((SLEEP_BORING_PART > 0 && current_state.frame_delta_ms_counter > current_config.idle_sleep_timeout_sec*1000/SLEEP_BORING_PART && last_key_pressed_timestamp > 0) || process_idle_animation || process_movement), - .process_movement = process_movement, - - .release_test_frame = current_config.test_animation_duration_ms > 0 && current_state.frame_delta_ms_counter > current_config.test_animation_duration_ms, - .release_frame_after_press = release_frame_after_press, - .release_frame_after_update = (!any_key_pressed && has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::CpuUpdate)) && current_state.hold_frame_ms > current_config.keypress_duration_ms, - - .process_idle_animation = process_idle_animation, - .process_movement_animation = process_movement_animation, - .process_working_animation = process_working_animation, - .process_running_animation = process_running_animation, - .go_next_frame = go_next_frame, - .go_next_frame_running = go_next_frame_running, - .release_frame_for_non_idle = release_frame_for_non_idle || go_next_frame || (is_running && go_next_frame_running), - - .is_writing = is_writing, - .is_moving = current_state.row_state == animation_state_row_t::StartMoving || current_state.row_state == animation_state_row_t::Moving || current_state.row_state == animation_state_row_t::EndMoving, - .is_working = current_state.row_state == animation_state_row_t::StartWorking || current_state.row_state == animation_state_row_t::Working, - .is_running = is_running, - .continue_writing = ((!any_key_pressed && current_state.hold_frame_ms < current_config.keypress_duration_ms) || !release_frame_after_press) && is_writing, - }; +static anim_next_frame_result_t +anim_update_animation_state(animation_shared_memory_t& anim_shm, animation_state_t& state, + const animation_player_result_t& new_animation_result, const animation_state_t& new_state, + const animation_player_result_t& current_animation_result, + const animation_state_t& current_state, const config::config_t& current_config) { + const bool moved = static_cast(current_state.anim_distance) != static_cast(new_state.anim_distance); + const bool frame_changed = new_animation_result.sprite_sheet_col != current_animation_result.sprite_sheet_col || + new_animation_result.sprite_sheet_row != current_animation_result.sprite_sheet_row; + const bool rerender = frame_changed || current_state.row_state != new_state.row_state || moved; + state = new_state; + if (new_state.animations_index != current_state.animations_index || rerender || moved) { + anim_shm.animation_player_result = new_animation_result; + if (current_config.enable_debug) { + BONGOCAT_LOG_VERBOSE("Animation frame change: %d", new_animation_result.sprite_sheet_col); } - - static anim_next_frame_result_t anim_update_animation_state(animation_shared_memory_t& anim_shm, - animation_state_t& state, - const animation_player_result_t& new_animation_result, - const animation_state_t& new_state, - const animation_player_result_t& current_animation_result, - const animation_state_t& current_state, - const config::config_t& current_config) { - const bool moved = static_cast(current_state.anim_distance) != static_cast(new_state.anim_distance); - const bool frame_changed = new_animation_result.sprite_sheet_col != current_animation_result.sprite_sheet_col || new_animation_result.sprite_sheet_row != current_animation_result.sprite_sheet_row; - const bool rerender = frame_changed || current_state.row_state != new_state.row_state || moved; - state = new_state; - if (new_state.animations_index != current_state.animations_index || rerender || moved) { - anim_shm.animation_player_result = new_animation_result; - if (current_config.enable_debug) { - BONGOCAT_LOG_VERBOSE("Animation frame change: %d", new_animation_result.sprite_sheet_col); - } - if (frame_changed) { - state.frame_delta_ms_counter = 0; - } - state.update_delta_ms_counter = 0; - } - - return { .moved = moved, .frame_changed = frame_changed, .rerender = rerender, .new_col = new_animation_result.sprite_sheet_col, .new_row = new_animation_result.sprite_sheet_row }; + if (frame_changed) { + state.frame_delta_ms_counter = 0; } + state.update_delta_ms_counter = 0; + } + + return {.moved = moved, + .frame_changed = frame_changed, + .rerender = rerender, + .new_col = new_animation_result.sprite_sheet_col, + .new_row = new_animation_result.sprite_sheet_row}; +} - /// @TODO: move anim_..._animation into own cpp files per asset set, make it more modular +/// @TODO: move anim_..._animation into own cpp files per asset set, make it more modular #ifdef FEATURE_BONGOCAT_EMBEDDED_ASSETS - enum class anim_bongocat_process_animation_result_status_t : uint8_t { None, Started, Updated, End, Looped, NextAnimationStarted }; - struct anim_bongocat_process_animation_result_t { - animation_state_row_t row_state; - anim_bongocat_process_animation_result_status_t status{anim_bongocat_process_animation_result_status_t::None}; - }; - static anim_bongocat_process_animation_result_t anim_bongocat_process_animation(const platform::input::input_context_t& input, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const bongocat_sprite_sheet_t& current_frames) { - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - // read-only config - assert(input._local_copy_config != nullptr); - const config::config_t& current_config = *input._local_copy_config; - - anim_bongocat_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_bongocat_process_animation_result_status_t::Updated}; - // forward animation - new_state.animations_index = current_state.animations_index + 1; - if (new_state.animations_index > static_cast(MAX_ANIMATION_FRAMES-1)) { - ret.status = anim_bongocat_process_animation_result_status_t::Looped; - new_state.animations_index = 0; - } - /* - // backwards animation - else if (new_state.animations_index < 0) { - ret = anim_bongocat_process_animation_result_t::End; - new_state.animations_index = MAX_ANIMATION_FRAMES-1; - } - */ - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - if (current_config.enable_hand_mapping) { - switch (input.shm->hand_mapping) { - case platform::input::input_hand_mapping_t::None: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case platform::input::input_hand_mapping_t::Left: - new_animation_result.sprite_sheet_col = (current_config.mirror_x) ? current_frames.animations.right_writing[new_state.animations_index] : current_frames.animations.left_writing[new_state.animations_index]; - break; - case platform::input::input_hand_mapping_t::Right: - new_animation_result.sprite_sheet_col = (current_config.mirror_x) ? current_frames.animations.left_writing[new_state.animations_index] : current_frames.animations.right_writing[new_state.animations_index]; - break; - } - } else { - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - } - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; - break; - case animation_state_row_t::FallASleep: - // not supported - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; - break; - case animation_state_row_t::Test: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; - break; - } - return ret; +enum class anim_bongocat_process_animation_result_status_t : uint8_t { + None, + Started, + Updated, + End, + Looped, + NextAnimationStarted +}; +struct anim_bongocat_process_animation_result_t { + animation_state_row_t row_state; + anim_bongocat_process_animation_result_status_t status{anim_bongocat_process_animation_result_status_t::None}; +}; +static anim_bongocat_process_animation_result_t +anim_bongocat_process_animation(const platform::input::input_context_t& input, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, const bongocat_sprite_sheet_t& current_frames) { + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + // read-only config + assert(input._local_copy_config != nullptr); + const config::config_t& current_config = *input._local_copy_config; + + anim_bongocat_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_bongocat_process_animation_result_status_t::Updated}; + // forward animation + new_state.animations_index = current_state.animations_index + 1; + if (new_state.animations_index > static_cast(MAX_ANIMATION_FRAMES - 1)) { + ret.status = anim_bongocat_process_animation_result_status_t::Looped; + new_state.animations_index = 0; + } + /* + // backwards animation + else if (new_state.animations_index < 0) { + ret = anim_bongocat_process_animation_result_t::End; + new_state.animations_index = MAX_ANIMATION_FRAMES-1; + } + */ + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + if (current_config.enable_hand_mapping) { + switch (input.shm->hand_mapping) { + case platform::input::input_hand_mapping_t::None: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case platform::input::input_hand_mapping_t::Left: + new_animation_result.sprite_sheet_col = + (current_config.mirror_x) ? current_frames.animations.right_writing[new_state.animations_index] + : current_frames.animations.left_writing[new_state.animations_index]; + break; + case platform::input::input_hand_mapping_t::Right: + new_animation_result.sprite_sheet_col = + (current_config.mirror_x) ? current_frames.animations.left_writing[new_state.animations_index] + : current_frames.animations.right_writing[new_state.animations_index]; + break; + } + } else { + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; } - static anim_bongocat_process_animation_result_t anim_bongocat_restart_animation(animation_context_t& ctx, - const platform::input::input_context_t& input, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const bongocat_sprite_sheet_t& current_frames) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - // read-only config - assert(input._local_copy_config != nullptr); - const config::config_t& current_config = *input._local_copy_config; - - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = BONGOCAT_SPRITE_SHEET_ROW; - new_state.animations_index = 0; - - anim_bongocat_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_bongocat_process_animation_result_status_t::Started}; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - break; - case animation_state_row_t::StartWriting: { - if (current_config.enable_hand_mapping) { - switch (input.shm->hand_mapping) { - case platform::input::input_hand_mapping_t::None: - new_state.animations_index = static_cast(ctx._rng.range(0, (MAX_ANIMATION_FRAMES-1) / 2)); - break; - case platform::input::input_hand_mapping_t::Left: - case platform::input::input_hand_mapping_t::Right: - new_state.animations_index = 0; - break; - } - } else { - new_state.animations_index = static_cast(ctx._rng.range(0, (MAX_ANIMATION_FRAMES-1) / 2)); - } - [[fallthrough]]; - }case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - if (current_config.enable_hand_mapping) { - switch (input.shm->hand_mapping) { - case platform::input::input_hand_mapping_t::None: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case platform::input::input_hand_mapping_t::Left: - new_animation_result.sprite_sheet_col = (current_config.mirror_x) ? current_frames.animations.right_writing[new_state.animations_index] : current_frames.animations.left_writing[new_state.animations_index]; - break; - case platform::input::input_hand_mapping_t::Right: - new_animation_result.sprite_sheet_col = (current_config.mirror_x) ? current_frames.animations.left_writing[new_state.animations_index] : current_frames.animations.right_writing[new_state.animations_index]; - break; - } - } else { - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - } - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; - break; - case animation_state_row_t::FallASleep: - // not supported - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; - break; - case animation_state_row_t::Test: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; - break; - } - return ret; + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; + break; + case animation_state_row_t::FallASleep: + // not supported + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; + break; + case animation_state_row_t::Test: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; + break; + } + return ret; +} +static anim_bongocat_process_animation_result_t +anim_bongocat_restart_animation(animation_context_t& ctx, const platform::input::input_context_t& input, + animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, + animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, + const bongocat_sprite_sheet_t& current_frames) { + using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + // read-only config + assert(input._local_copy_config != nullptr); + const config::config_t& current_config = *input._local_copy_config; + + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = BONGOCAT_SPRITE_SHEET_ROW; + new_state.animations_index = 0; + + anim_bongocat_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_bongocat_process_animation_result_status_t::Started}; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; } - /* - static anim_bongocat_process_animation_result_t anim_bongocat_show_single_frame(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - [[maybe_unused]] const bongocat_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - anim_bongocat_process_animation_result_t ret = anim_bongocat_process_animation_result_t::Started; - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = BONGOCAT_SPRITE_SHEET_ROW; + break; + case animation_state_row_t::StartWriting: { + if (current_config.enable_hand_mapping) { + switch (input.shm->hand_mapping) { + case platform::input::input_hand_mapping_t::None: + new_state.animations_index = static_cast(ctx._rng.range(0, (MAX_ANIMATION_FRAMES - 1) / 2)); + break; + case platform::input::input_hand_mapping_t::Left: + case platform::input::input_hand_mapping_t::Right: new_state.animations_index = 0; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_UP; - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - break; - case animation_state_row_t::StartWriting: - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : BONGOCAT_FRAME_RIGHT_DOWN; - [[fallthrough]]; - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - // toggle frame - if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_LEFT_DOWN) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_RIGHT_DOWN; - } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_RIGHT_DOWN) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_LEFT_DOWN; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : BONGOCAT_FRAME_RIGHT_DOWN; - } - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_UP; - break; - case animation_state_row_t::Sleep: + break; + } + } else { + new_state.animations_index = static_cast(ctx._rng.range(0, (MAX_ANIMATION_FRAMES - 1) / 2)); + } + [[fallthrough]]; + } + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + if (current_config.enable_hand_mapping) { + switch (input.shm->hand_mapping) { + case platform::input::input_hand_mapping_t::None: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case platform::input::input_hand_mapping_t::Left: + new_animation_result.sprite_sheet_col = + (current_config.mirror_x) ? current_frames.animations.right_writing[new_state.animations_index] + : current_frames.animations.left_writing[new_state.animations_index]; + break; + case platform::input::input_hand_mapping_t::Right: + new_animation_result.sprite_sheet_col = + (current_config.mirror_x) ? current_frames.animations.left_writing[new_state.animations_index] + : current_frames.animations.right_writing[new_state.animations_index]; + break; + } + } else { + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + } + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; + break; + case animation_state_row_t::FallASleep: + // not supported + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; + break; + case animation_state_row_t::Test: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; + break; + } + return ret; +} +/* +static anim_bongocat_process_animation_result_t anim_bongocat_show_single_frame(animation_context_t& ctx, + animation_state_row_t new_row_state, + animation_player_result_t& +new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const +animation_state_t& current_state, + [[maybe_unused]] const +bongocat_sprite_sheet_t& current_frames, const config::config_t& current_config) { using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + anim_bongocat_process_animation_result_t ret = anim_bongocat_process_animation_result_t::Started; + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = BONGOCAT_SPRITE_SHEET_ROW; + new_state.animations_index = 0; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_UP; + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; + } + break; + case animation_state_row_t::StartWriting: + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : +BONGOCAT_FRAME_RIGHT_DOWN; + [[fallthrough]]; + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + // toggle frame + if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_LEFT_DOWN) { + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_RIGHT_DOWN; + } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_RIGHT_DOWN) { + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_LEFT_DOWN; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : +BONGOCAT_FRAME_RIGHT_DOWN; + } + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_UP; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_DOWN; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_UP; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_DOWN; + break; + case animation_state_row_t::Test: + // toggle frame (same as writing?) + if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_LEFT_DOWN) { + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_RIGHT_DOWN; + } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_RIGHT_DOWN) { + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_LEFT_DOWN; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : +BONGOCAT_FRAME_RIGHT_DOWN; + } + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + // toggle frame (unused) + if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_LEFT_DOWN) { + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_RIGHT_DOWN; + } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_RIGHT_DOWN) { + new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_LEFT_DOWN; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : +BONGOCAT_FRAME_RIGHT_DOWN; + } + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + // toggle frame (unused) + if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_BOTH_UP) { new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_DOWN; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_UP; - break; - case animation_state_row_t::Boring: + } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_BOTH_UP) { new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_DOWN; - break; - case animation_state_row_t::Test: - // toggle frame (same as writing?) - if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_LEFT_DOWN) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_RIGHT_DOWN; - } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_RIGHT_DOWN) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_LEFT_DOWN; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : BONGOCAT_FRAME_RIGHT_DOWN; - } - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - // toggle frame (unused) - if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_LEFT_DOWN) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_RIGHT_DOWN; - } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_RIGHT_DOWN) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_LEFT_DOWN; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_LEFT_DOWN : BONGOCAT_FRAME_RIGHT_DOWN; - } - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - // toggle frame (unused) - if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_BOTH_UP) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_DOWN; - } else if (new_animation_result.sprite_sheet_col == BONGOCAT_FRAME_BOTH_UP) { - new_animation_result.sprite_sheet_col = BONGOCAT_FRAME_BOTH_DOWN; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_BOTH_UP : BONGOCAT_FRAME_BOTH_DOWN; - } - break; - } - return ret; - } - */ - - static anim_bongocat_process_animation_result_t anim_bongocat_start_or_process_animation(animation_context_t& ctx, const platform::input::input_context_t& input, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const bongocat_sprite_sheet_t& current_frames) { - if (current_state.row_state != new_row_state) { - auto result = anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); - if (result.status == anim_bongocat_process_animation_result_status_t::Looped || result.status == anim_bongocat_process_animation_result_status_t::End) { - result = anim_bongocat_restart_animation(ctx, input, new_row_state, new_animation_result, new_state, current_state, current_frames); - result.status = anim_bongocat_process_animation_result_status_t::NextAnimationStarted; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? BONGOCAT_FRAME_BOTH_UP : +BONGOCAT_FRAME_BOTH_DOWN; } - return result; - } - - return anim_bongocat_restart_animation(ctx, input, new_row_state, new_animation_result, new_state, + break; + } + return ret; +} +*/ + +static anim_bongocat_process_animation_result_t anim_bongocat_start_or_process_animation( + animation_context_t& ctx, const platform::input::input_context_t& input, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, const bongocat_sprite_sheet_t& current_frames) { + if (current_state.row_state != new_row_state) { + auto result = + anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); + if (result.status == anim_bongocat_process_animation_result_status_t::Looped || + result.status == anim_bongocat_process_animation_result_status_t::End) { + result = anim_bongocat_restart_animation(ctx, input, new_row_state, new_animation_result, new_state, current_state, current_frames); + result.status = anim_bongocat_process_animation_result_status_t::NextAnimationStarted; } + return result; + } - static anim_next_frame_result_t anim_bongocat_idle_next_frame(animation_context_t& ctx, - const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { - using namespace assets; - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(anim_shm.anim_type == config::config_animation_sprite_sheet_layout_t::Bongocat); - assert(get_current_animation(ctx).type == animation_t::Type::Bongocat); - const auto& current_frames = get_current_animation(ctx).bongocat; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + return anim_bongocat_restart_animation(ctx, input, new_row_state, new_animation_result, new_state, current_state, + current_frames); +} - // Idle Animation - const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; - const bool stop_test_animation = conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test && conditions.release_test_frame; - if (stop_writing || stop_test_animation) { - // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, +static anim_next_frame_result_t +anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, + const anim_handle_key_press_result_t& trigger_result) { + using namespace assets; + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(anim_shm.anim_type == config::config_animation_sprite_sheet_layout_t::Bongocat); + assert(get_current_animation(ctx).type == animation_t::Type::Bongocat); + const auto& current_frames = get_current_animation(ctx).bongocat; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + // Idle Animation + const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; + const bool stop_test_animation = conditions.trigger_test_animation && + current_state.row_state == animation_state_row_t::Test && + conditions.release_test_frame; + if (stop_writing || stop_test_animation) { + // back to idle + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames); + } + + if constexpr (features::BongocatIdleAnimation) { + if (!stop_writing && !stop_test_animation && conditions.process_idle_animation) { + if (current_state.row_state == animation_state_row_t::Idle) { + // loop idle animation + anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); + } else if (current_state.row_state == animation_state_row_t::WakeUp) { + // process wake up animation + anim_bongocat_start_or_process_animation(ctx, input, + animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames); + } + } + } else { + if (current_state.row_state == animation_state_row_t::WakeUp && conditions.release_frame_for_non_idle) { + // back to idle + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames); + } + } + + // Start Test animation + if (!conditions.any_key_pressed && conditions.trigger_test_animation && + current_state.row_state == animation_state_row_t::Idle) { + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Test, new_animation_result, new_state, + current_state, current_frames); + } else if (!conditions.any_key_pressed && conditions.trigger_test_animation && + current_state.row_state == animation_state_row_t::Test) { + // loop test animation + anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); + } + + const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + + // Idle Sleep + if (conditions.check_for_idle_sleep) { + if (!is_sleeping_time) { + const platform::timestamp_ms_t now = platform::get_current_time_ms(); + const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000; + assert(now >= last_key_pressed_timestamp); + const auto sleep_timeout = now - last_key_pressed_timestamp; + + if constexpr (features::BongocatBoringAnimation) { + const bool start_boring = SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms / SLEEP_BORING_PART; + if (current_state.row_state == animation_state_row_t::Idle) { + // start boring animation + if (start_boring && !current_state.show_boring_animation_once) { + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Boring, new_animation_result, new_state, current_state, current_frames); - } - - if constexpr (features::BongocatIdleAnimation) { - if (!stop_writing && !stop_test_animation && conditions.process_idle_animation) { - if (current_state.row_state == animation_state_row_t::Idle) { - // loop idle animation - anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); - } else if (current_state.row_state == animation_state_row_t::WakeUp) { - // process wake up animation - anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames); - } + new_state.show_boring_animation_once = true; + } + } else if (current_state.row_state == animation_state_row_t::Boring) { + if constexpr (features::BongocatIdleAnimation) { + if (conditions.process_idle_animation) { + const auto animation_result = anim_bongocat_start_or_process_animation( + ctx, input, animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames); + if (!start_boring && animation_result.row_state == animation_state_row_t::Idle) { + new_state.show_boring_animation_once = false; + } } - } else { - if (current_state.row_state == animation_state_row_t::WakeUp && conditions.release_frame_for_non_idle) { + } else { + if (!start_boring || (start_boring && current_state.show_boring_animation_once)) { + if (conditions.release_frame_for_non_idle) { // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames); - } - } - - // Start Test animation - if (!conditions.any_key_pressed && conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Idle) { - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Test, - new_animation_result, new_state, - current_state, current_frames); - } else if (!conditions.any_key_pressed && conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test) { - // loop test animation - anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); - } - - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); - - // Idle Sleep - if (conditions.check_for_idle_sleep) { - if (!is_sleeping_time) { - const platform::timestamp_ms_t now = platform::get_current_time_ms(); - const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec*1000; - assert(now >= last_key_pressed_timestamp); - const auto sleep_timeout = now - last_key_pressed_timestamp; - - if constexpr (features::BongocatBoringAnimation) { - const bool start_boring = SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms/SLEEP_BORING_PART; - if (current_state.row_state == animation_state_row_t::Idle) { - // start boring animation - if (start_boring && !current_state.show_boring_animation_once) { - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Boring, - new_animation_result, new_state, - current_state, current_frames); - new_state.show_boring_animation_once = true; - } - } else if (current_state.row_state == animation_state_row_t::Boring) { - if constexpr (features::BongocatIdleAnimation) { - if (conditions.process_idle_animation) { - const auto animation_result = anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames); - if (!start_boring && animation_result.row_state == animation_state_row_t::Idle) { - new_state.show_boring_animation_once = false; - } - } - } else { - if (!start_boring || (start_boring && current_state.show_boring_animation_once)) { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames); - if (!start_boring) { - new_state.show_boring_animation_once = false; - } - } - } - } - } - } - - // idle sleep - if (sleep_timeout >= idle_sleep_timeout_ms) { - if (current_state.row_state == animation_state_row_t::Idle) { - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames); - new_state.is_idle_sleep = true; - new_state.show_boring_animation_once = false; - } else if (current_state.is_idle_sleep) { - if constexpr (features::BongocatIdleAnimation) { - if (current_state.row_state == animation_state_row_t::Sleep) { - if (conditions.process_idle_animation) { - // loop sleep animation - anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); - } - } - } - } - } else if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - if constexpr (features::BongocatIdleAnimation) { - if (conditions.process_idle_animation) { - const auto animation_result = anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames); - if (animation_result.row_state == animation_state_row_t::Idle) { - new_state.is_idle_sleep = false; - } - } - } else { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames); - new_state.is_idle_sleep = false; - } - } - } - } - } - - // Sleep Mode - if (current_config.enable_scheduled_sleep) { - if (is_sleeping_time) { - if (current_state.row_state == animation_state_row_t::Idle) { - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames); - new_state.is_idle_sleep = false; - } else { - if constexpr (features::BongocatIdleAnimation) { - if (current_state.row_state == animation_state_row_t::Sleep && conditions.process_idle_animation) { - // loop sleep animation - anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); - } - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames); - } - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames); + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, + new_state, current_state, current_frames); + if (!start_boring) { + new_state.show_boring_animation_once = false; } + } } + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); - } - - static anim_next_frame_result_t anim_bongocat_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& state, - const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Bongocat); - const auto& current_frames = get_current_animation(ctx).bongocat; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - /// @TODO: use state machine for animation (states) - - // in Writing mode/start writing - if (!conditions.is_writing) { - if (state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - // wake up - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames); - } else if (state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - // start writing - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::StartWriting, - new_animation_result, new_state, - current_state, current_frames); + } + + // idle sleep + if (sleep_timeout >= idle_sleep_timeout_ms) { + if (current_state.row_state == animation_state_row_t::Idle) { + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Sleep, new_animation_result, new_state, + current_state, current_frames); + new_state.is_idle_sleep = true; + new_state.show_boring_animation_once = false; + } else if (current_state.is_idle_sleep) { + if constexpr (features::BongocatIdleAnimation) { + if (current_state.row_state == animation_state_row_t::Sleep) { + if (conditions.process_idle_animation) { + // loop sleep animation + anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); + } } - } else if (state.row_state == animation_state_row_t::StartWriting) { - anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Writing, - new_animation_result, new_state, - current_state, current_frames); + } } - + } else if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { if constexpr (features::BongocatIdleAnimation) { - if (conditions.is_writing && conditions.process_idle_animation) { - if (conditions.release_frame_after_press && current_state.row_state == animation_state_row_t::Writing) { - anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::EndWriting, - new_animation_result, new_state, - current_state, current_frames); - } else if (conditions.release_frame_after_press && (current_state.row_state == animation_state_row_t::EndWriting || current_state.row_state == animation_state_row_t::WakeUp)) { - anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames); - } + if (conditions.process_idle_animation) { + const auto animation_result = anim_bongocat_start_or_process_animation( + ctx, input, animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames); + if (animation_result.row_state == animation_state_row_t::Idle) { + new_state.is_idle_sleep = false; } + } } else { - if (conditions.is_writing) { - if (conditions.continue_writing) { - // keep writing - anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); - } - } else if (((!conditions.is_writing && state.row_state == animation_state_row_t::Idle) || state.row_state == animation_state_row_t::WakeUp) && conditions.release_frame_after_press) { - // back to idle - anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames); - } + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames); + new_state.is_idle_sleep = false; + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + } } -#endif - -#ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS - enum class anim_dm_process_animation_result_status_t : uint8_t { None, Started, Updated, End, Looped, NextAnimationStarted, SkipMovement, Moved, Stop }; - struct anim_dm_process_animation_result_t { - animation_state_row_t row_state; - anim_dm_process_animation_result_status_t status{anim_dm_process_animation_result_status_t::None}; - }; - static anim_dm_process_animation_result_t anim_dm_process_animation(animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const dm_sprite_sheet_t& current_frames) { - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - anim_dm_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_dm_process_animation_result_status_t::Updated}; - // forward animation - new_state.animations_index = current_state.animations_index + 1; - if (new_state.animations_index > static_cast(MAX_ANIMATION_FRAMES-1)) { - ret.status = anim_dm_process_animation_result_status_t::Looped; - new_state.animations_index = 0; - } - /* - // backwards animation - else if (new_state.animations_index < 0) { - ret = anim_bongocat_process_animation_result_t::End; - new_state.animations_index = MAX_ANIMATION_FRAMES-1; + } + + // Sleep Mode + if (current_config.enable_scheduled_sleep) { + if (is_sleeping_time) { + if (current_state.row_state == animation_state_row_t::Idle) { + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Sleep, new_animation_result, new_state, + current_state, current_frames); + new_state.is_idle_sleep = false; + } else { + if constexpr (features::BongocatIdleAnimation) { + if (current_state.row_state == animation_state_row_t::Sleep && conditions.process_idle_animation) { + // loop sleep animation + anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); + } } - */ - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; - break; - case animation_state_row_t::FallASleep: - // not supported - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; - break; - case animation_state_row_t::Test: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; - break; + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames); } - return ret; + } } - static anim_dm_process_animation_result_t anim_dm_restart_animation([[maybe_unused]] animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const dm_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = DM_SPRITE_SHEET_ROW; - new_state.animations_index = 0; - - anim_dm_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_dm_process_animation_result_status_t::Started}; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; - break; - case animation_state_row_t::FallASleep: - // not supported - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; - break; - case animation_state_row_t::Test: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; - break; - } - return ret; + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames); + } } - static anim_dm_process_animation_result_t anim_dm_show_single_frame([[maybe_unused]] animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - [[maybe_unused]] const dm_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = DM_SPRITE_SHEET_ROW; - new_state.animations_index = 0; + } - anim_dm_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_dm_process_animation_result_status_t::Started}; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - break; - case animation_state_row_t::Happy: - if (current_frames.frames.happy.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.happy.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - case animation_state_row_t::FallASleep: - // not fully supported - if (current_frames.frames.sleep.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.sleep.col; - } else if (current_frames.frames.down.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.down.col; - } else { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } - break; - case animation_state_row_t::Sleep: - if (current_frames.frames.sleep.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.sleep.col; - } else if (current_frames.frames.down.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.down.col; - } else { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } - break; - case animation_state_row_t::WakeUp: - if (current_frames.frames.happy.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.sleep.col; - } else { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } - break; - case animation_state_row_t::Boring: - if (current_frames.frames.sad.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.sad.col; - } else if (current_frames.frames.angry.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.angry.col; - } else if (current_frames.frames.down.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.down.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - case animation_state_row_t::Test: - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - if (current_frames.frames.attack_1.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.attack_1.col; - } else if (current_frames.frames.angry.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.angry.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - if (current_frames.frames.movement_1.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.movement_1.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - case animation_state_row_t::EndMoving: - if (current_frames.frames.movement_2.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.movement_2.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - if (current_frames.frames.movement_1.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.movement_1.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - case animation_state_row_t::EndRunning: - if (current_frames.frames.movement_2.valid) { - new_animation_result.sprite_sheet_col = current_frames.frames.movement_2.col; - } else { - // toggle frame - if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; - } - } - break; - } - return ret; - } - - static anim_dm_process_animation_result_t anim_dm_start_or_process_animation(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const dm_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - - if (current_state.row_state != new_row_state) { - auto result = anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - if (result.status == anim_dm_process_animation_result_status_t::End || result.status == anim_dm_process_animation_result_status_t::Looped) { - result = anim_dm_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); - result.status = anim_dm_process_animation_result_status_t::NextAnimationStarted; - } - return result; - } + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} - return anim_dm_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); +static anim_next_frame_result_t anim_bongocat_key_pressed_next_frame( + animation_context_t& ctx, animation_state_t& state, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Bongocat); + const auto& current_frames = get_current_animation(ctx).bongocat; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + /// @TODO: use state machine for animation (states) + + // in Writing mode/start writing + if (!conditions.is_writing) { + if (state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { + // wake up + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames); + } else if (state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + // start writing + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::StartWriting, new_animation_result, new_state, + current_state, current_frames); } - - - static anim_dm_process_animation_result_t anim_dm_handle_movement(animation_context_t& ctx, - const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const anim_handle_key_press_result_t& trigger_result, - const animation_state_t& current_state, - const dm_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); - - anim_dm_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_dm_process_animation_result_status_t::None}; - if (!conditions.is_writing && current_config.movement_speed > 0 && current_config.movement_radius > 0 && current_config.fps > 0) { - if (conditions.process_idle_animation || conditions.process_movement || conditions.process_movement_animation) { - const float delta = 1.0f / static_cast(current_config.fps); - const auto delta_ms = static_cast(delta * 1000); - const auto frame_height = current_config.cat_height; - const auto frame_width = static_cast(static_cast(frame_height) * (static_cast(current_frames.frame_width) / static_cast(current_frames.frame_height))); - const auto fmovement_radius = static_cast(current_config.movement_radius); - - assert(current_config.movement_radius > 0); - float max_movement_offset_x_left = 0.0f; - float max_movement_offset_x_right = 0.0f; - float wall_distance = 0.0f; - switch (current_config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - // range: [-r, +r] - max_movement_offset_x_left = -static_cast(current_config.movement_radius) + static_cast(frame_width) / 2.0f; - max_movement_offset_x_right = static_cast(current_config.movement_radius - frame_width); - wall_distance = anim_shm.movement_offset_x / fmovement_radius; - break; - case config::align_type_t::ALIGN_LEFT: - // range: [0, +2r] - max_movement_offset_x_left = 0.0f; - max_movement_offset_x_right = static_cast(current_config.movement_radius) * 2.0f - static_cast(frame_width); - wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f - 1.0f; - break; - case config::align_type_t::ALIGN_RIGHT: - // range: [-2r, 0] - max_movement_offset_x_left = -(static_cast(current_config.movement_radius) * 2.0f) + static_cast(frame_width); - max_movement_offset_x_right = 0.0f; - wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f + 1.0f; // normalize to -1 → 1 - break; - } - - if (current_state.row_state == animation_state_row_t::Idle && (current_state.anim_pause_after_movement_ms > 0 || (ctx._rng.range(0, 100) <= CHANCE_FOR_SKIPPING_MOVEMENT_PERCENT))) { - // skip movement - new_state.anim_velocity = 0.0f; - new_state.anim_distance = 0.0f; - if (new_state.anim_pause_after_movement_ms > 0) { - new_state.anim_pause_after_movement_ms -= delta_ms; - if (new_state.anim_pause_after_movement_ms <= 0) { - new_state.anim_pause_after_movement_ms = 0; - } - } - ret.status = anim_dm_process_animation_result_status_t::SkipMovement; - } else { - // moving animation - constexpr float DIR_EPSILON = 1e-3f; - assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); - const int movement_part = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL ? SMALL_MAX_DISTANCE_PER_MOVEMENT_PART : MAX_DISTANCE_PER_MOVEMENT_PART; - const float fmovement_part = static_cast(movement_part); - if (current_state.row_state == animation_state_row_t::Idle) { - // start movement - const auto min_movement = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL ? (fmovement_radius / fmovement_part / 2) + 1 : (fmovement_radius / fmovement_part / fmovement_part) + 1; - float max_move_distance = fmovement_radius / fmovement_part; - max_move_distance = max_move_distance <= min_movement ? min_movement : max_move_distance; - - assert(max_move_distance >= 0); - assert(min_movement >= 0); - new_state.anim_distance = static_cast(ctx._rng.range(static_cast(min_movement), static_cast(max_move_distance))); - - if (anim_shm.movement_offset_x >= max_movement_offset_x_right) { - // run against wall, change direction - anim_shm.anim_direction = -1.0; - anim_shm.movement_offset_x = max_movement_offset_x_right; - } else if (anim_shm.movement_offset_x <= max_movement_offset_x_left) { - // run against wall, change direction - anim_shm.anim_direction = 1.0; - anim_shm.movement_offset_x = max_movement_offset_x_left; - } else { - float toward_wall_bias = fabs(wall_distance); - toward_wall_bias = toward_wall_bias >= 1.0f ? 1.0f : toward_wall_bias; - toward_wall_bias = toward_wall_bias <= 0.0f ? 0.0f : toward_wall_bias; - - const int flip_direction_chance = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL ? SMALL_FLIP_DIRECTION_NEAR_WALL_PERCENT : FLIP_DIRECTION_NEAR_WALL_PERCENT; - - assert(toward_wall_bias >= 0.0f); - // change direction: chance drops at center, changes falloff steeper near walls - const auto dir_chance = static_cast(static_cast(100 - flip_direction_chance + 10) * (1.0f - pow(toward_wall_bias, 1.5f))); - - if (fabs(new_state.anim_last_direction) >= DIR_EPSILON) { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -new_state.anim_last_direction : new_state.anim_last_direction; - } else if (fabs(anim_shm.anim_direction) < DIR_EPSILON) { - // idle: choose random start direction - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; - } else { - if (wall_distance > (100.0f / static_cast(flip_direction_chance))) { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -1.0f : 1.0f; - } else if (wall_distance < -(100.0f / static_cast(flip_direction_chance))) { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; - } else { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? anim_shm.anim_direction : -anim_shm.anim_direction; - } - } - } - // start moving animation - new_state.anim_velocity = anim_shm.anim_direction * static_cast(current_config.movement_speed); - ret = anim_dm_restart_animation(ctx, animation_state_row_t::StartMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (current_state.row_state == animation_state_row_t::StartMoving) { - if (conditions.process_idle_animation) { - ret = anim_dm_start_or_process_animation(ctx, animation_state_row_t::Moving, // continue with moving - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_dm_process_animation_result_status_t::Updated; - } else if (conditions.process_movement_animation) { - ret = anim_dm_restart_animation(ctx, animation_state_row_t::Moving, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_dm_process_animation_result_status_t::Updated; - } - } else if (current_state.row_state == animation_state_row_t::Moving) { - if (conditions.process_movement) { - ret = anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - ret.status = anim_dm_process_animation_result_status_t::Moved; - - new_state.anim_distance -= fabs(new_state.anim_velocity); - anim_shm.movement_offset_x += new_state.anim_velocity; - // clamp walking/movement area - if (anim_shm.movement_offset_x > max_movement_offset_x_right) { - anim_shm.movement_offset_x = max_movement_offset_x_right; - ret = anim_dm_restart_animation(ctx, animation_state_row_t::EndMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_dm_process_animation_result_status_t::Stop; - } else if (anim_shm.movement_offset_x < max_movement_offset_x_left) { - anim_shm.movement_offset_x = max_movement_offset_x_left; - ret = anim_dm_restart_animation(ctx, animation_state_row_t::EndMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_dm_process_animation_result_status_t::Stop; - } - - if (new_state.anim_distance <= 0 || (fabs(new_state.anim_distance) < DIR_EPSILON || fabs(new_state.anim_velocity) < DIR_EPSILON)) { - ret = anim_dm_restart_animation(ctx, animation_state_row_t::EndMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_dm_process_animation_result_status_t::Stop; - } - } - } else if (current_state.row_state == animation_state_row_t::EndMoving) { - if (conditions.process_idle_animation) { - ret = anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - ret = anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - new_state.anim_last_direction = anim_shm.anim_direction; - new_state.anim_velocity = 0.0f; - anim_shm.anim_direction = 0.0; - if (ret.row_state == animation_state_row_t::Idle && new_state.anim_distance <= 0) { - assert(current_config.animation_speed_ms >= 0); - assert(movement_part >= 0); - const auto min_wait = current_config.animation_speed_ms * (current_config.movement_wait_factor / 2); - const auto max_wait = current_config.animation_speed_ms * current_config.movement_wait_factor; - assert(min_wait >= 0); - assert(max_wait >= 0); - new_state.anim_pause_after_movement_ms = static_cast(ctx._rng.range(static_cast(min_wait), static_cast(max_wait))); - ret.status = anim_dm_process_animation_result_status_t::End; - } - } - } - } - } else { - // movement got disabled, back to idle - if ((current_config.movement_speed <= 0 || current_config.movement_radius <= 0) && conditions.is_moving) { - // back to idle - ret = anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.anim_velocity = 0.0f; - ret.status = anim_dm_process_animation_result_status_t::Stop; - } - } - - return ret; + } else if (state.row_state == animation_state_row_t::StartWriting) { + anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Writing, new_animation_result, + new_state, current_state, current_frames); + } + + if constexpr (features::BongocatIdleAnimation) { + if (conditions.is_writing && conditions.process_idle_animation) { + if (conditions.release_frame_after_press && current_state.row_state == animation_state_row_t::Writing) { + anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::EndWriting, new_animation_result, + new_state, current_state, current_frames); + } else if (conditions.release_frame_after_press && + (current_state.row_state == animation_state_row_t::EndWriting || + current_state.row_state == animation_state_row_t::WakeUp)) { + anim_bongocat_start_or_process_animation(ctx, input, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames); + } } + } else { + if (conditions.is_writing) { + if (conditions.continue_writing) { + // keep writing + anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); + } + } else if (((!conditions.is_writing && state.row_state == animation_state_row_t::Idle) || + state.row_state == animation_state_row_t::WakeUp) && + conditions.release_frame_after_press) { + // back to idle + anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames); + } + } - static anim_next_frame_result_t anim_dm_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - assert(upd.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Dm); - const auto& current_frames = get_current_animation(ctx).dm; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); - - /// @TODO: make animation state machine ? + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} +#endif - if (!conditions.is_moving) { - // Idle Animation - const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; - const bool stop_happy_kpm = current_state.row_state == animation_state_row_t::Happy && conditions.release_frame_after_press && !conditions.process_idle_animation; - const bool stop_test_animation = conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test && conditions.release_test_frame; - if (stop_writing || stop_test_animation || stop_happy_kpm) { - // back to idle - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - // Test Animation - if (!conditions.any_key_pressed && conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test) { - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } else { - // continues animations - if (current_state.row_state == animation_state_row_t::Idle) { - if (conditions.process_idle_animation) { - // Idle Animation - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - // finish animations, back to idle - if (current_state.row_state == animation_state_row_t::Happy) { - if (conditions.release_frame_after_press) { - if (conditions.process_idle_animation) { - // finish last happy animation - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - if (conditions.release_frame_for_non_idle) { - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } - // Finsh wakeup - if (current_state.row_state == animation_state_row_t::WakeUp) { - // back to idle - if (conditions.process_idle_animation) { - // end current sleep animation - const auto animation_result = anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::Idle) { - new_state.is_idle_sleep = false; - } - } else { - if (conditions.release_frame_for_non_idle) { - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.is_idle_sleep = false; - } - } - } - // Finish Working - if (current_state.row_state == animation_state_row_t::EndWorking && (conditions.release_frame_after_update || conditions.release_frame_for_non_idle) && !update_shm.cpu_active) { - if (conditions.process_idle_animation) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } else if (conditions.is_working && current_state.row_state != animation_state_row_t::EndWorking && !update_shm.cpu_active) { - // Cancel Working - if (conditions.process_idle_animation) { - // back to idle - anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - if (conditions.release_frame_for_non_idle) { - anim_dm_show_single_frame(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } - } - } +#ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS +enum class anim_dm_process_animation_result_status_t : uint8_t { + None, + Started, + Updated, + End, + Looped, + NextAnimationStarted, + SkipMovement, + Moved, + Stop +}; +struct anim_dm_process_animation_result_t { + animation_state_row_t row_state; + anim_dm_process_animation_result_status_t status{anim_dm_process_animation_result_status_t::None}; +}; +static anim_dm_process_animation_result_t anim_dm_process_animation(animation_player_result_t& new_animation_result, + animation_state_t& new_state, + const animation_state_t& current_state, + const dm_sprite_sheet_t& current_frames) { + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + anim_dm_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_dm_process_animation_result_status_t::Updated}; + // forward animation + new_state.animations_index = current_state.animations_index + 1; + if (new_state.animations_index > static_cast(MAX_ANIMATION_FRAMES - 1)) { + ret.status = anim_dm_process_animation_result_status_t::Looped; + new_state.animations_index = 0; + } + /* + // backwards animation + else if (new_state.animations_index < 0) { + ret = anim_bongocat_process_animation_result_t::End; + new_state.animations_index = MAX_ANIMATION_FRAMES-1; + } + */ + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; + break; + case animation_state_row_t::FallASleep: + // not supported + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; + break; + case animation_state_row_t::Test: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; + break; + } + return ret; +} +static anim_dm_process_animation_result_t +anim_dm_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + const dm_sprite_sheet_t& current_frames, const config::config_t& current_config) { + using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = DM_SPRITE_SHEET_ROW; + new_state.animations_index = 0; + + anim_dm_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_dm_process_animation_result_status_t::Started}; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; + } + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; + break; + case animation_state_row_t::FallASleep: + // not supported + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; + break; + case animation_state_row_t::Test: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; + break; + } + return ret; +} +static anim_dm_process_animation_result_t +anim_dm_show_single_frame([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + [[maybe_unused]] const dm_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = DM_SPRITE_SHEET_ROW; + new_state.animations_index = 0; + + anim_dm_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_dm_process_animation_result_status_t::Started}; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; + } + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + break; + case animation_state_row_t::Happy: + if (current_frames.frames.happy.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.happy.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + case animation_state_row_t::FallASleep: + // not fully supported + if (current_frames.frames.sleep.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.sleep.col; + } else if (current_frames.frames.down.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.down.col; + } else { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } + break; + case animation_state_row_t::Sleep: + if (current_frames.frames.sleep.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.sleep.col; + } else if (current_frames.frames.down.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.down.col; + } else { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } + break; + case animation_state_row_t::WakeUp: + if (current_frames.frames.happy.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.sleep.col; + } else { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } + break; + case animation_state_row_t::Boring: + if (current_frames.frames.sad.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.sad.col; + } else if (current_frames.frames.angry.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.angry.col; + } else if (current_frames.frames.down.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.down.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + case animation_state_row_t::Test: + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + if (current_frames.frames.attack_1.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.attack_1.col; + } else if (current_frames.frames.angry.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.angry.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + if (current_frames.frames.movement_1.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.movement_1.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + case animation_state_row_t::EndMoving: + if (current_frames.frames.movement_2.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.movement_2.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + if (current_frames.frames.movement_1.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.movement_1.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + case animation_state_row_t::EndRunning: + if (current_frames.frames.movement_2.valid) { + new_animation_result.sprite_sheet_col = current_frames.frames.movement_2.col; + } else { + // toggle frame + if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == DM_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = DM_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? DM_FRAME_IDLE1 : DM_FRAME_IDLE2; + } + } + break; + } + return ret; +} +static anim_dm_process_animation_result_t +anim_dm_start_or_process_animation(animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, const dm_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + if (current_state.row_state != new_row_state) { + auto result = anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + if (result.status == anim_dm_process_animation_result_status_t::End || + result.status == anim_dm_process_animation_result_status_t::Looped) { + result = anim_dm_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); + result.status = anim_dm_process_animation_result_status_t::NextAnimationStarted; + } + return result; + } - /* - // Start Test animation - if (!conditions.any_key_pressed && conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Idle) { - anim_dm_restart_animation(ctx, animation_state_row_t::Test, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!conditions.any_key_pressed && conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test) { - // loop test animation - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - */ - - /// @TODO: move moving handle into own anim_handle_moving_animation - // Move Animation - anim_dm_handle_movement(ctx, input, upd, new_animation_result, new_state, trigger_result, current_state, current_frames, current_config); - - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); - - // Idle Sleep - if (conditions.check_for_idle_sleep) { - if (!is_sleeping_time) { - const platform::timestamp_ms_t now = platform::get_current_time_ms(); - const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec*1000; - assert(now >= last_key_pressed_timestamp); - const auto sleep_timeout = now - last_key_pressed_timestamp; - - const bool start_boring = SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms/SLEEP_BORING_PART; - if (current_state.row_state == animation_state_row_t::Idle) { - // start boring animation - if (start_boring && !current_state.show_boring_animation_once) { - if (conditions.process_idle_animation) { - anim_dm_restart_animation(ctx, animation_state_row_t::Boring, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - anim_dm_show_single_frame(ctx, animation_state_row_t::Boring, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - new_state.show_boring_animation_once = true; - new_state.anim_last_direction = 0.0f; - } - } else if (current_state.row_state == animation_state_row_t::Boring) { - if (conditions.process_idle_animation) { - const auto animation_result = anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames, current_config); - if (!start_boring && animation_result.row_state == animation_state_row_t::Idle) { - new_state.show_boring_animation_once = false; - } - } else { - if (!start_boring || (start_boring && current_state.show_boring_animation_once)) { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (!start_boring) { - new_state.show_boring_animation_once = false; - } - } - } - } - } + return anim_dm_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, current_frames, + current_config); +} - // idle sleep - if (sleep_timeout >= idle_sleep_timeout_ms) { - if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - if (conditions.process_idle_animation && conditions.is_moving) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Sleep, // wait for moving to end - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - anim_dm_restart_animation(ctx, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - new_state.anim_last_direction = 0.0f; - new_state.is_idle_sleep = true; - } else if (current_state.row_state == animation_state_row_t::Sleep) { - if (conditions.process_idle_animation) { - // loop sleep animation - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - // wake up - if (conditions.process_idle_animation) { - // end current sleep animation - const auto animation_result = anim_dm_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::WakeUp) { - new_state.is_idle_sleep = false; - new_state.show_boring_animation_once = false; - } - } else { - if (conditions.release_frame_for_non_idle) { - anim_dm_show_single_frame(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.is_idle_sleep = false; - new_state.show_boring_animation_once = false; - } - } - } - } - } +static anim_dm_process_animation_result_t +anim_dm_handle_movement(animation_context_t& ctx, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const anim_handle_key_press_result_t& trigger_result, const animation_state_t& current_state, + const dm_sprite_sheet_t& current_frames, const config::config_t& current_config) { + using namespace assets; + + assert(ctx.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + anim_dm_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_dm_process_animation_result_status_t::None}; + if (!conditions.is_writing && current_config.movement_speed > 0 && current_config.movement_radius > 0 && + current_config.fps > 0) { + if (conditions.process_idle_animation || conditions.process_movement || conditions.process_movement_animation) { + const float delta = 1.0f / static_cast(current_config.fps); + const auto delta_ms = static_cast(delta * 1000); + const auto frame_height = current_config.cat_height; + const auto frame_width = + static_cast(static_cast(frame_height) * (static_cast(current_frames.frame_width) / + static_cast(current_frames.frame_height))); + const auto fmovement_radius = static_cast(current_config.movement_radius); + + assert(current_config.movement_radius > 0); + float max_movement_offset_x_left = 0.0f; + float max_movement_offset_x_right = 0.0f; + float wall_distance = 0.0f; + switch (current_config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + // range: [-r, +r] + max_movement_offset_x_left = + -static_cast(current_config.movement_radius) + static_cast(frame_width) / 2.0f; + max_movement_offset_x_right = static_cast(current_config.movement_radius - frame_width); + wall_distance = anim_shm.movement_offset_x / fmovement_radius; + break; + case config::align_type_t::ALIGN_LEFT: + // range: [0, +2r] + max_movement_offset_x_left = 0.0f; + max_movement_offset_x_right = + static_cast(current_config.movement_radius) * 2.0f - static_cast(frame_width); + wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f - 1.0f; + break; + case config::align_type_t::ALIGN_RIGHT: + // range: [-2r, 0] + max_movement_offset_x_left = + -(static_cast(current_config.movement_radius) * 2.0f) + static_cast(frame_width); + max_movement_offset_x_right = 0.0f; + wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f + 1.0f; // normalize to -1 → 1 + break; + } + + if (current_state.row_state == animation_state_row_t::Idle && + (current_state.anim_pause_after_movement_ms > 0 || + (ctx._rng.range(0, 100) <= CHANCE_FOR_SKIPPING_MOVEMENT_PERCENT))) { + // skip movement + new_state.anim_velocity = 0.0f; + new_state.anim_distance = 0.0f; + if (new_state.anim_pause_after_movement_ms > 0) { + new_state.anim_pause_after_movement_ms -= delta_ms; + if (new_state.anim_pause_after_movement_ms <= 0) { + new_state.anim_pause_after_movement_ms = 0; + } } - - // Sleep Mode - if (current_config.enable_scheduled_sleep) { - if (is_sleeping_time) { - if (new_state.row_state == animation_state_row_t::Idle) { - anim_dm_restart_animation(ctx, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.anim_last_direction = 0.0f; - new_state.is_idle_sleep = false; - } else if (state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - if (conditions.process_idle_animation) { - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } + ret.status = anim_dm_process_animation_result_status_t::SkipMovement; + } else { + // moving animation + constexpr float DIR_EPSILON = 1e-3f; + assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); + const int movement_part = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL + ? SMALL_MAX_DISTANCE_PER_MOVEMENT_PART + : MAX_DISTANCE_PER_MOVEMENT_PART; + const float fmovement_part = static_cast(movement_part); + if (current_state.row_state == animation_state_row_t::Idle) { + // start movement + const auto min_movement = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL + ? (fmovement_radius / fmovement_part / 2) + 1 + : (fmovement_radius / fmovement_part / fmovement_part) + 1; + float max_move_distance = fmovement_radius / fmovement_part; + max_move_distance = max_move_distance <= min_movement ? min_movement : max_move_distance; + + assert(max_move_distance >= 0); + assert(min_movement >= 0); + new_state.anim_distance = static_cast( + ctx._rng.range(static_cast(min_movement), static_cast(max_move_distance))); + + if (anim_shm.movement_offset_x >= max_movement_offset_x_right) { + // run against wall, change direction + anim_shm.anim_direction = -1.0; + anim_shm.movement_offset_x = max_movement_offset_x_right; + } else if (anim_shm.movement_offset_x <= max_movement_offset_x_left) { + // run against wall, change direction + anim_shm.anim_direction = 1.0; + anim_shm.movement_offset_x = max_movement_offset_x_left; + } else { + float toward_wall_bias = fabs(wall_distance); + toward_wall_bias = toward_wall_bias >= 1.0f ? 1.0f : toward_wall_bias; + toward_wall_bias = toward_wall_bias <= 0.0f ? 0.0f : toward_wall_bias; + + const int flip_direction_chance = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL + ? SMALL_FLIP_DIRECTION_NEAR_WALL_PERCENT + : FLIP_DIRECTION_NEAR_WALL_PERCENT; + + assert(toward_wall_bias >= 0.0f); + // change direction: chance drops at center, changes falloff steeper near walls + const auto dir_chance = static_cast(static_cast(100 - flip_direction_chance + 10) * + (1.0f - pow(toward_wall_bias, 1.5f))); + + if (fabs(new_state.anim_last_direction) >= DIR_EPSILON) { + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -new_state.anim_last_direction + : new_state.anim_last_direction; + } else if (fabs(anim_shm.anim_direction) < DIR_EPSILON) { + // idle: choose random start direction + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; } else { - if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - // wake up - if (conditions.process_idle_animation) { - // end current sleep animation - const auto animation_result = anim_dm_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::WakeUp) { - new_state.is_idle_sleep = false; - new_state.show_boring_animation_once = false; - } - } else { - if (conditions.release_frame_for_non_idle) { - anim_dm_show_single_frame(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.is_idle_sleep = false; - new_state.show_boring_animation_once = false; - } - } - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - // wake up - if (conditions.process_idle_animation) { - // end current sleep animation - const auto animation_result = anim_dm_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::WakeUp) { - new_state.is_idle_sleep = false; - new_state.show_boring_animation_once = false; - } - } else { - if (conditions.release_frame_for_non_idle) { - anim_dm_show_single_frame(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.is_idle_sleep = false; - new_state.show_boring_animation_once = false; - } - } - } - } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); - } - - static anim_next_frame_result_t anim_dm_key_pressed_next_frame(animation_context_t& ctx, - const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, - const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::Dm); - const auto& current_frames = get_current_animation(ctx).dm; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - bool show_happy = false; - if (input_shm.kpm > 0) { - if (current_config.happy_kpm > 0 && input_shm.kpm >= current_config.happy_kpm) { - show_happy = DM_HAPPY_CHANCE_PERCENT >= 100 || ctx._rng.range(0, 99) < DM_HAPPY_CHANCE_PERCENT; + if (wall_distance > (100.0f / static_cast(flip_direction_chance))) { + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -1.0f : 1.0f; + } else if (wall_distance < -(100.0f / static_cast(flip_direction_chance))) { + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; + } else { + anim_shm.anim_direction = + (ctx._rng.range(0, 100) < dir_chance) ? anim_shm.anim_direction : -anim_shm.anim_direction; + } } - } - - /// @TODO: use state machine for animation (states) - - if (show_happy && (conditions.is_writing || current_state.row_state == animation_state_row_t::Idle)) { - // show happy animation (KPM) - anim_dm_restart_animation(ctx, animation_state_row_t::Happy, - new_animation_result, new_state, + } + // start moving animation + new_state.anim_velocity = anim_shm.anim_direction * static_cast(current_config.movement_speed); + ret = anim_dm_restart_animation(ctx, animation_state_row_t::StartMoving, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (current_state.row_state == animation_state_row_t::StartMoving) { + if (conditions.process_idle_animation) { + ret = anim_dm_start_or_process_animation(ctx, animation_state_row_t::Moving, // continue with moving + new_animation_result, new_state, current_state, current_frames, + current_config); + ret.status = anim_dm_process_animation_result_status_t::Updated; + } else if (conditions.process_movement_animation) { + ret = anim_dm_restart_animation(ctx, animation_state_row_t::Moving, new_animation_result, new_state, current_state, current_frames, current_config); - } else if (conditions.is_writing || conditions.is_moving || current_state.row_state == animation_state_row_t::Idle) { - const bool end_writing = !conditions.release_frame_after_press && conditions.is_writing; - // in Writing mode/start writing - if (!conditions.is_writing || conditions.is_moving) { - anim_dm_restart_animation(ctx, animation_state_row_t::StartWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.show_boring_animation_once = false; - } else { - if (conditions.process_idle_animation) { - // transition writing animations - if (current_state.row_state == animation_state_row_t::StartWriting) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Writing, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (conditions.release_frame_after_press && current_state.row_state == animation_state_row_t::Writing) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (conditions.release_frame_after_press && current_state.row_state == animation_state_row_t::EndWriting) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (end_writing) { - // keep writing - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } else { - if (current_state.row_state == animation_state_row_t::StartWriting) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Writing, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (end_writing) { - // back to idle - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (conditions.continue_writing) { - // keep writing - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } - } else if (current_state.row_state == animation_state_row_t::Happy) { - if (conditions.release_frame_after_press) { - if (conditions.process_idle_animation) { - // finish last happy animation - anim_dm_start_or_process_animation(ctx, conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - // back to idle - anim_dm_restart_animation(ctx, conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - if (!conditions.process_idle_animation) { - // back to idle - anim_dm_restart_animation(ctx, conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - // finish last happy animation - anim_dm_start_or_process_animation(ctx, conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } + ret.status = anim_dm_process_animation_result_status_t::Updated; + } + } else if (current_state.row_state == animation_state_row_t::Moving) { + if (conditions.process_movement) { + ret = anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + ret.status = anim_dm_process_animation_result_status_t::Moved; + + new_state.anim_distance -= fabs(new_state.anim_velocity); + anim_shm.movement_offset_x += new_state.anim_velocity; + // clamp walking/movement area + if (anim_shm.movement_offset_x > max_movement_offset_x_right) { + anim_shm.movement_offset_x = max_movement_offset_x_right; + ret = anim_dm_restart_animation(ctx, animation_state_row_t::EndMoving, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_dm_process_animation_result_status_t::Stop; + } else if (anim_shm.movement_offset_x < max_movement_offset_x_left) { + anim_shm.movement_offset_x = max_movement_offset_x_left; + ret = anim_dm_restart_animation(ctx, animation_state_row_t::EndMoving, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_dm_process_animation_result_status_t::Stop; } - } else if (state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - // wake up - anim_dm_restart_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, + + if (new_state.anim_distance <= 0 || + (fabs(new_state.anim_distance) < DIR_EPSILON || fabs(new_state.anim_velocity) < DIR_EPSILON)) { + ret = anim_dm_restart_animation(ctx, animation_state_row_t::EndMoving, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_dm_process_animation_result_status_t::Stop; + } + } + } else if (current_state.row_state == animation_state_row_t::EndMoving) { + if (conditions.process_idle_animation) { + ret = anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } else { + ret = anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, current_frames, current_config); + } + new_state.anim_last_direction = anim_shm.anim_direction; + new_state.anim_velocity = 0.0f; + anim_shm.anim_direction = 0.0; + if (ret.row_state == animation_state_row_t::Idle && new_state.anim_distance <= 0) { + assert(current_config.animation_speed_ms >= 0); + assert(movement_part >= 0); + const auto min_wait = current_config.animation_speed_ms * (current_config.movement_wait_factor / 2); + const auto max_wait = current_config.animation_speed_ms * current_config.movement_wait_factor; + assert(min_wait >= 0); + assert(max_wait >= 0); + new_state.anim_pause_after_movement_ms = + static_cast(ctx._rng.range(static_cast(min_wait), static_cast(max_wait))); + ret.status = anim_dm_process_animation_result_status_t::End; + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + } } + } else { + // movement got disabled, back to idle + if ((current_config.movement_speed <= 0 || current_config.movement_radius <= 0) && conditions.is_moving) { + // back to idle + ret = anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + new_state.anim_velocity = 0.0f; + ret.status = anim_dm_process_animation_result_status_t::Stop; + } + } - static anim_next_frame_result_t anim_dm_working_next_frame(animation_context_t& ctx, - const platform::input::input_context_t& input, - const platform::update::update_context_t& upd, - animation_state_t& state, - const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - //assert(input.shm != nullptr); - assert(upd.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Dm); - const auto& current_frames = get_current_animation(ctx).dm; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - /// @TODO: use state machine for animation (states) - - if (conditions.process_working_animation) { - // toggle frame, show attack animation - const bool above_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage >= current_config.cpu_threshold || update_shm.max_cpu_usage >= current_config.cpu_threshold); - const bool lower_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage < current_config.cpu_threshold && update_shm.max_cpu_usage < current_config.cpu_threshold); - - if (above_threshold) { - if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - anim_dm_restart_animation(ctx, animation_state_row_t::StartWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Start Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } else if (conditions.is_working) { - // continues animations - if (conditions.process_idle_animation) { - if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Working, // continue working - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartWorking || state.row_state == animation_state_row_t::Working)) { - // end working, cool down - anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndWorking) { - // back to idle - anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::Working) { - // keep working - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } else { - if (update_shm.cpu_active) { - if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { - anim_dm_show_single_frame(ctx, animation_state_row_t::Working, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartWorking || state.row_state == animation_state_row_t::Working)) { - // end working - anim_dm_show_single_frame(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndWorking) { - // back to idle - anim_dm_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::Working) { - // keep working - anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } - } - } else if (lower_threshold) { - // End Working - if (conditions.is_working && current_state.row_state != animation_state_row_t::EndMoving) { - if (conditions.process_idle_animation) { - // end working, cool down - anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - anim_dm_show_single_frame(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } + return ret; +} + +static anim_next_frame_result_t anim_dm_idle_next_frame(animation_context_t& ctx, + const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + animation_state_t& state, + const anim_handle_key_press_result_t& trigger_result) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + assert(upd.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Dm); + const auto& current_frames = get_current_animation(ctx).dm; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + /// @TODO: make animation state machine ? + + if (!conditions.is_moving) { + // Idle Animation + const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; + const bool stop_happy_kpm = current_state.row_state == animation_state_row_t::Happy && + conditions.release_frame_after_press && !conditions.process_idle_animation; + const bool stop_test_animation = conditions.trigger_test_animation && + current_state.row_state == animation_state_row_t::Test && + conditions.release_test_frame; + if (stop_writing || stop_test_animation || stop_happy_kpm) { + // back to idle + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } else { + // Test Animation + if (!conditions.any_key_pressed && conditions.trigger_test_animation && + current_state.row_state == animation_state_row_t::Test) { + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } else { + // continues animations + if (current_state.row_state == animation_state_row_t::Idle) { + if (conditions.process_idle_animation) { + // Idle Animation + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } + // finish animations, back to idle + if (current_state.row_state == animation_state_row_t::Happy) { + if (conditions.release_frame_after_press) { + if (conditions.process_idle_animation) { + // finish last happy animation + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, + current_config); } else { - // Cancel Working - if (conditions.is_working && current_state.row_state != animation_state_row_t::EndWorking && !update_shm.cpu_active) { - if (conditions.process_idle_animation) { - // back to idle - anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - anim_dm_show_single_frame(ctx, animation_state_row_t::EndMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } + if (conditions.release_frame_for_non_idle) { + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } } + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + // Finsh wakeup + if (current_state.row_state == animation_state_row_t::WakeUp) { + // back to idle + if (conditions.process_idle_animation) { + // end current sleep animation + const auto animation_result = + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::Idle) { + new_state.is_idle_sleep = false; + } + } else { + if (conditions.release_frame_for_non_idle) { + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.is_idle_sleep = false; + } + } + } + // Finish Working + if (current_state.row_state == animation_state_row_t::EndWorking && + (conditions.release_frame_after_update || conditions.release_frame_for_non_idle) && + !update_shm.cpu_active) { + if (conditions.process_idle_animation) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames, + current_config); + } else { + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } + } else if (conditions.is_working && current_state.row_state != animation_state_row_t::EndWorking && + !update_shm.cpu_active) { + // Cancel Working + if (conditions.process_idle_animation) { + // back to idle + anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } else { + if (conditions.release_frame_for_non_idle) { + anim_dm_show_single_frame(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } + } + } } -#endif - - -#ifdef FEATURE_PKMN_EMBEDDED_ASSETS - enum class anim_pkmn_process_animation_result_status_t : uint8_t { None, Started, Updated, End, Looped, NextAnimationStarted }; - struct anim_pkmn_process_animation_result_t { - animation_state_row_t row_state; - anim_pkmn_process_animation_result_status_t status{anim_pkmn_process_animation_result_status_t::None}; - }; - static anim_pkmn_process_animation_result_t anim_pkmn_process_animation(animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const pkmn_sprite_sheet_t& current_frames) { - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - anim_pkmn_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_pkmn_process_animation_result_status_t::Updated}; - // forward animation - new_state.animations_index = current_state.animations_index + 1; - if (new_state.animations_index > static_cast(MAX_ANIMATION_FRAMES-1)) { - ret.status = anim_pkmn_process_animation_result_status_t::Looped; - new_state.animations_index = 0; + } + + /* + // Start Test animation + if (!conditions.any_key_pressed && conditions.trigger_test_animation && current_state.row_state == + animation_state_row_t::Idle) { anim_dm_restart_animation(ctx, animation_state_row_t::Test, new_animation_result, + new_state, current_state, current_frames, current_config); } else if (!conditions.any_key_pressed && + conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test) { + // loop test animation + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } + */ + + /// @TODO: move moving handle into own anim_handle_moving_animation + // Move Animation + anim_dm_handle_movement(ctx, input, upd, new_animation_result, new_state, trigger_result, current_state, + current_frames, current_config); + + const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + + // Idle Sleep + if (conditions.check_for_idle_sleep) { + if (!is_sleeping_time) { + const platform::timestamp_ms_t now = platform::get_current_time_ms(); + const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000; + assert(now >= last_key_pressed_timestamp); + const auto sleep_timeout = now - last_key_pressed_timestamp; + + const bool start_boring = SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms / SLEEP_BORING_PART; + if (current_state.row_state == animation_state_row_t::Idle) { + // start boring animation + if (start_boring && !current_state.show_boring_animation_once) { + if (conditions.process_idle_animation) { + anim_dm_restart_animation(ctx, animation_state_row_t::Boring, new_animation_result, new_state, + current_state, current_frames, current_config); + } else { + anim_dm_show_single_frame(ctx, animation_state_row_t::Boring, new_animation_result, new_state, + current_state, current_frames, current_config); + } + new_state.show_boring_animation_once = true; + new_state.anim_last_direction = 0.0f; } - /* - // backwards animation - else if (new_state.animations_index < 0) { - ret = anim_bongocat_process_animation_result_t::End; - new_state.animations_index = MAX_ANIMATION_FRAMES-1; + } else if (current_state.row_state == animation_state_row_t::Boring) { + if (conditions.process_idle_animation) { + const auto animation_result = anim_dm_start_or_process_animation( + ctx, animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames, current_config); + if (!start_boring && animation_result.row_state == animation_state_row_t::Idle) { + new_state.show_boring_animation_once = false; + } + } else { + if (!start_boring || (start_boring && current_state.show_boring_animation_once)) { + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + if (!start_boring) { + new_state.show_boring_animation_once = false; + } + } + } } - */ - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; - break; - case animation_state_row_t::FallASleep: - // not supported - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; - break; - case animation_state_row_t::Test: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; - break; + } + + // idle sleep + if (sleep_timeout >= idle_sleep_timeout_ms) { + if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + if (conditions.process_idle_animation && conditions.is_moving) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Sleep, // wait for moving to end + new_animation_result, new_state, current_state, current_frames, + current_config); + } else { + anim_dm_restart_animation(ctx, animation_state_row_t::Sleep, new_animation_result, new_state, current_state, + current_frames, current_config); + } + new_state.anim_last_direction = 0.0f; + new_state.is_idle_sleep = true; + } else if (current_state.row_state == animation_state_row_t::Sleep) { + if (conditions.process_idle_animation) { + // loop sleep animation + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } } - return ret; - } - static anim_pkmn_process_animation_result_t anim_pkmn_restart_animation([[maybe_unused]] animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const pkmn_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = PKMN_SPRITE_SHEET_ROW; - new_state.animations_index = 0; - - anim_pkmn_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_pkmn_process_animation_result_status_t::Started}; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::Happy: - new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; - break; - case animation_state_row_t::FallASleep: - // not supported - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; - break; - case animation_state_row_t::Test: - new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; - break; + } else { + if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { + // wake up + if (conditions.process_idle_animation) { + // end current sleep animation + const auto animation_result = + anim_dm_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::WakeUp) { + new_state.is_idle_sleep = false; + new_state.show_boring_animation_once = false; + } + } else { + if (conditions.release_frame_for_non_idle) { + anim_dm_show_single_frame(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.is_idle_sleep = false; + new_state.show_boring_animation_once = false; + } + } } - return ret; + } } - /* - static anim_pkmn_process_animation_result_t anim_pkmn_show_single_frame(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - [[maybe_unused]] const pkmn_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = PKMN_SPRITE_SHEET_ROW; - new_state.animations_index = 0; - - anim_pkmn_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_pkmn_process_animation_result_status_t::Started}; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - break; - case animation_state_row_t::StartWriting: - case animation_state_row_t::Writing: - case animation_state_row_t::EndWriting: - // toggle frame - if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : PKMN_FRAME_IDLE2; - } - break; - case animation_state_row_t::Happy: - // toggle frame - if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : PKMN_FRAME_IDLE2; - } - break; - case animation_state_row_t::Sleep: - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - break; - case animation_state_row_t::WakeUp: - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - break; - case animation_state_row_t::Boring: - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - break; - case animation_state_row_t::Test: - // toggle frame (same as writing?) - if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : PKMN_FRAME_IDLE2; - } - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - // toggle frame (unused) - if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : PKMN_FRAME_IDLE2; - } - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - // toggle frame (unused) - if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; - } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { - new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; - } else { - new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : PKMN_FRAME_IDLE2; - } - break; + } + + // Sleep Mode + if (current_config.enable_scheduled_sleep) { + if (is_sleeping_time) { + if (new_state.row_state == animation_state_row_t::Idle) { + anim_dm_restart_animation(ctx, animation_state_row_t::Sleep, new_animation_result, new_state, current_state, + current_frames, current_config); + new_state.anim_last_direction = 0.0f; + new_state.is_idle_sleep = false; + } else if (state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + if (conditions.process_idle_animation) { + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); } - return ret; - } - */ - - static anim_pkmn_process_animation_result_t anim_pkmn_start_or_process_animation(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const pkmn_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - if (current_state.row_state != new_row_state) { - auto result = anim_pkmn_process_animation(new_animation_result, new_state, current_state, current_frames); - if (result.status == anim_pkmn_process_animation_result_status_t::Looped || result.status == anim_pkmn_process_animation_result_status_t::End) { - result = anim_pkmn_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, current_frames, current_config); - result.status = anim_pkmn_process_animation_result_status_t::NextAnimationStarted; - } - return result; + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + // wake up + if (conditions.process_idle_animation) { + // end current sleep animation + const auto animation_result = + anim_dm_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::WakeUp) { + new_state.is_idle_sleep = false; + new_state.show_boring_animation_once = false; + } + } else { + if (conditions.release_frame_for_non_idle) { + anim_dm_show_single_frame(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.is_idle_sleep = false; + new_state.show_boring_animation_once = false; + } } - - return anim_pkmn_restart_animation(ctx, new_row_state, new_animation_result, new_state, + } + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + // wake up + if (conditions.process_idle_animation) { + // end current sleep animation + const auto animation_result = + anim_dm_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::WakeUp) { + new_state.is_idle_sleep = false; + new_state.show_boring_animation_once = false; + } + } else { + if (conditions.release_frame_for_non_idle) { + anim_dm_show_single_frame(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, current_state, + current_frames, current_config); + new_state.is_idle_sleep = false; + new_state.show_boring_animation_once = false; + } + } } + } - static anim_next_frame_result_t anim_pkmn_idle_next_frame(animation_context_t& ctx, - [[maybe_unused]] const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { - using namespace assets; - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::Pkmn); - const auto& current_frames = get_current_animation(ctx).pkmn; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} - // Idle Animation - const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; - if (stop_writing) { +static anim_next_frame_result_t +anim_dm_key_pressed_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, + const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + assert(get_current_animation(ctx).type == animation_t::Type::Dm); + const auto& current_frames = get_current_animation(ctx).dm; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + bool show_happy = false; + if (input_shm.kpm > 0) { + if (current_config.happy_kpm > 0 && input_shm.kpm >= current_config.happy_kpm) { + show_happy = DM_HAPPY_CHANCE_PERCENT >= 100 || ctx._rng.range(0, 99) < DM_HAPPY_CHANCE_PERCENT; + } + } + + /// @TODO: use state machine for animation (states) + + if (show_happy && (conditions.is_writing || current_state.row_state == animation_state_row_t::Idle)) { + // show happy animation (KPM) + anim_dm_restart_animation(ctx, animation_state_row_t::Happy, new_animation_result, new_state, current_state, + current_frames, current_config); + } else if (conditions.is_writing || conditions.is_moving || current_state.row_state == animation_state_row_t::Idle) { + const bool end_writing = !conditions.release_frame_after_press && conditions.is_writing; + // in Writing mode/start writing + if (!conditions.is_writing || conditions.is_moving) { + anim_dm_restart_animation(ctx, animation_state_row_t::StartWriting, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.show_boring_animation_once = false; + } else { + if (conditions.process_idle_animation) { + // transition writing animations + if (current_state.row_state == animation_state_row_t::StartWriting) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Writing, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (conditions.release_frame_after_press && current_state.row_state == animation_state_row_t::Writing) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWriting, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (conditions.release_frame_after_press && + current_state.row_state == animation_state_row_t::EndWriting) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, + current_config); + } else if (end_writing) { + // keep writing + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } else { + if (current_state.row_state == animation_state_row_t::StartWriting) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Writing, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (end_writing) { + // back to idle + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } else if (conditions.continue_writing) { + // keep writing + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } + } + } else if (current_state.row_state == animation_state_row_t::Happy) { + if (conditions.release_frame_after_press) { + if (conditions.process_idle_animation) { + // finish last happy animation + anim_dm_start_or_process_animation( + ctx, + conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, current_config); + } else { + // back to idle + anim_dm_restart_animation( + ctx, conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, current_config); + } + } else { + if (!conditions.process_idle_animation) { + // back to idle + anim_dm_restart_animation( + ctx, conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, current_config); + } else { + // finish last happy animation + anim_dm_start_or_process_animation( + ctx, + conditions.continue_writing ? animation_state_row_t::Writing : animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, current_config); + } + } + } else if (state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { + // wake up + anim_dm_restart_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, current_state, + current_frames, current_config); + } + + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} + +static anim_next_frame_result_t anim_dm_working_next_frame(animation_context_t& ctx, + const platform::input::input_context_t& input, + const platform::update::update_context_t& upd, + animation_state_t& state, + const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + // assert(input.shm != nullptr); + assert(upd.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Dm); + const auto& current_frames = get_current_animation(ctx).dm; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + /// @TODO: use state machine for animation (states) + + if (conditions.process_working_animation) { + // toggle frame, show attack animation + const bool above_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage >= current_config.cpu_threshold || + update_shm.max_cpu_usage >= current_config.cpu_threshold); + const bool lower_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage < current_config.cpu_threshold && + update_shm.max_cpu_usage < current_config.cpu_threshold); + + if (above_threshold) { + if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + anim_dm_restart_animation(ctx, animation_state_row_t::StartWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + BONGOCAT_LOG_VERBOSE("Start Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); + } else if (conditions.is_working) { + // continues animations + if (conditions.process_idle_animation) { + if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Working, // continue working + new_animation_result, new_state, current_state, current_frames, + current_config); + } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartWorking || + state.row_state == animation_state_row_t::Working)) { + // end working, cool down + anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndWorking) { // back to idle - anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); + anim_dm_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::Working) { + // keep working + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); + } } else { - // continues animations - if (current_state.row_state == animation_state_row_t::Idle) { - if (conditions.process_idle_animation) { - // Idle Animation - anim_pkmn_process_animation(new_animation_result, new_state, current_state, current_frames); - } + if (update_shm.cpu_active) { + if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { + anim_dm_show_single_frame(ctx, animation_state_row_t::Working, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartWorking || + state.row_state == animation_state_row_t::Working)) { + // end working + anim_dm_show_single_frame(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndWorking) { + // back to idle + anim_dm_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::Working) { + // keep working + anim_dm_process_animation(new_animation_result, new_state, current_state, current_frames); } + } + } + } + } else if (lower_threshold) { + // End Working + if (conditions.is_working && current_state.row_state != animation_state_row_t::EndMoving) { + if (conditions.process_idle_animation) { + // end working, cool down + anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } else { + anim_dm_show_single_frame(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } + BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); + } + } else { + // Cancel Working + if (conditions.is_working && current_state.row_state != animation_state_row_t::EndWorking && + !update_shm.cpu_active) { + if (conditions.process_idle_animation) { + // back to idle + anim_dm_start_or_process_animation(ctx, animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } else { + anim_dm_show_single_frame(ctx, animation_state_row_t::EndMoving, new_animation_result, new_state, + current_state, current_frames, current_config); } + BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); + } + } + } - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} +#endif - // Sleep Mode - if (current_config.enable_scheduled_sleep) { - if (is_sleeping_time) { - if (current_state.row_state == animation_state_row_t::Idle) { - anim_pkmn_restart_animation(ctx, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.is_idle_sleep = false; - } +#ifdef FEATURE_PKMN_EMBEDDED_ASSETS +enum class anim_pkmn_process_animation_result_status_t : uint8_t { + None, + Started, + Updated, + End, + Looped, + NextAnimationStarted +}; +struct anim_pkmn_process_animation_result_t { + animation_state_row_t row_state; + anim_pkmn_process_animation_result_status_t status{anim_pkmn_process_animation_result_status_t::None}; +}; +static anim_pkmn_process_animation_result_t anim_pkmn_process_animation(animation_player_result_t& new_animation_result, + animation_state_t& new_state, + const animation_state_t& current_state, + const pkmn_sprite_sheet_t& current_frames) { + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + anim_pkmn_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_pkmn_process_animation_result_status_t::Updated}; + // forward animation + new_state.animations_index = current_state.animations_index + 1; + if (new_state.animations_index > static_cast(MAX_ANIMATION_FRAMES - 1)) { + ret.status = anim_pkmn_process_animation_result_status_t::Looped; + new_state.animations_index = 0; + } + /* + // backwards animation + else if (new_state.animations_index < 0) { + ret = anim_bongocat_process_animation_result_t::End; + new_state.animations_index = MAX_ANIMATION_FRAMES-1; + } + */ + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; + break; + case animation_state_row_t::FallASleep: + // not supported + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; + break; + case animation_state_row_t::Test: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + new_animation_result.sprite_sheet_col = current_frames.animations.moving[new_state.animations_index]; + break; + } + return ret; +} +static anim_pkmn_process_animation_result_t +anim_pkmn_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + const pkmn_sprite_sheet_t& current_frames, const config::config_t& current_config) { + using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = PKMN_SPRITE_SHEET_ROW; + new_state.animations_index = 0; + + anim_pkmn_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_pkmn_process_animation_result_status_t::Started}; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; + } + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::Happy: + new_animation_result.sprite_sheet_col = current_frames.animations.happy[new_state.animations_index]; + break; + case animation_state_row_t::FallASleep: + // not supported + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = current_frames.animations.sleep[new_state.animations_index]; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = current_frames.animations.wake_up[new_state.animations_index]; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = current_frames.animations.boring[new_state.animations_index]; + break; + case animation_state_row_t::Test: + new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + new_animation_result.sprite_sheet_col = current_frames.animations.working[new_state.animations_index]; + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + new_animation_result.sprite_sheet_col = current_frames.animations.running[new_state.animations_index]; + break; + } + return ret; +} +/* +static anim_pkmn_process_animation_result_t anim_pkmn_show_single_frame(animation_context_t& ctx, + animation_state_row_t new_row_state, + animation_player_result_t& +new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const +animation_state_t& current_state, + [[maybe_unused]] const +pkmn_sprite_sheet_t& current_frames, const config::config_t& current_config) { using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = PKMN_SPRITE_SHEET_ROW; + new_state.animations_index = 0; + + anim_pkmn_process_animation_result_t ret {.row_state = new_state.row_state, .status = +anim_pkmn_process_animation_result_status_t::Started}; switch (new_state.row_state) { case animation_state_row_t::Idle: + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; + } + break; + case animation_state_row_t::StartWriting: + case animation_state_row_t::Writing: + case animation_state_row_t::EndWriting: + // toggle frame + if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; } else { - if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : +PKMN_FRAME_IDLE2; } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { - if (conditions.release_frame_for_non_idle) { - // back to idle - anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } + break; + case animation_state_row_t::Happy: + // toggle frame + if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : +PKMN_FRAME_IDLE2; } - } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + break; + case animation_state_row_t::Sleep: + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + break; + case animation_state_row_t::WakeUp: + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; + break; + case animation_state_row_t::Boring: + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + break; + case animation_state_row_t::Test: + // toggle frame (same as writing?) + if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : +PKMN_FRAME_IDLE2; + } + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + // toggle frame (unused) + if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : +PKMN_FRAME_IDLE2; + } + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + // toggle frame (unused) + if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE1) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE2; + } else if (new_animation_result.sprite_sheet_col == PKMN_FRAME_IDLE2) { + new_animation_result.sprite_sheet_col = PKMN_FRAME_IDLE1; + } else { + new_animation_result.sprite_sheet_col = ctx._rng.range(0, 100) <= 50 ? PKMN_FRAME_IDLE1 : +PKMN_FRAME_IDLE2; + } + break; + } + return ret; +} +*/ + +static anim_pkmn_process_animation_result_t +anim_pkmn_start_or_process_animation(animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, const pkmn_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + if (current_state.row_state != new_row_state) { + auto result = anim_pkmn_process_animation(new_animation_result, new_state, current_state, current_frames); + if (result.status == anim_pkmn_process_animation_result_status_t::Looped || + result.status == anim_pkmn_process_animation_result_status_t::End) { + result = anim_pkmn_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); + result.status = anim_pkmn_process_animation_result_status_t::NextAnimationStarted; } + return result; + } - static anim_next_frame_result_t anim_pkmn_key_pressed_next_frame(animation_context_t& ctx, - [[maybe_unused]] const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, - const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Pkmn); - const auto& current_frames = get_current_animation(ctx).pkmn; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - // in Writing mode/start writing - if (!conditions.is_writing) { - if (state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - // wake up - anim_pkmn_restart_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - // start writing - anim_pkmn_restart_animation(ctx, animation_state_row_t::StartWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else if (state.row_state == animation_state_row_t::StartWriting) { - anim_pkmn_start_or_process_animation(ctx, animation_state_row_t::Writing, - new_animation_result, new_state, - current_state, current_frames, current_config); - } + return anim_pkmn_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, current_frames, + current_config); +} - if (conditions.is_writing) { - if (conditions.continue_writing) { - // keep writing - anim_pkmn_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } else if (((!conditions.is_writing && state.row_state == animation_state_row_t::Idle) || state.row_state == animation_state_row_t::WakeUp) && conditions.release_frame_after_press) { - // back to idle - anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); +static anim_next_frame_result_t +anim_pkmn_idle_next_frame(animation_context_t& ctx, [[maybe_unused]] const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, + const anim_handle_key_press_result_t& trigger_result) { + using namespace assets; + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + assert(get_current_animation(ctx).type == animation_t::Type::Pkmn); + const auto& current_frames = get_current_animation(ctx).pkmn; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + // Idle Animation + const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; + if (stop_writing) { + // back to idle + anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } else { + // continues animations + if (current_state.row_state == animation_state_row_t::Idle) { + if (conditions.process_idle_animation) { + // Idle Animation + anim_pkmn_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } + } + + const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + + // Sleep Mode + if (current_config.enable_scheduled_sleep) { + if (is_sleeping_time) { + if (current_state.row_state == animation_state_row_t::Idle) { + anim_pkmn_restart_animation(ctx, animation_state_row_t::Sleep, new_animation_result, new_state, current_state, + current_frames, current_config); + new_state.is_idle_sleep = false; + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); } + } + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !current_state.is_idle_sleep) { + if (conditions.release_frame_for_non_idle) { + // back to idle + anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + } + } + + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} + +static anim_next_frame_result_t +anim_pkmn_key_pressed_next_frame(animation_context_t& ctx, + [[maybe_unused]] const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + animation_state_t& state, const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Pkmn); + const auto& current_frames = get_current_animation(ctx).pkmn; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + // in Writing mode/start writing + if (!conditions.is_writing) { + if (state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { + // wake up + anim_pkmn_restart_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, current_state, + current_frames, current_config); + } else if (state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + // start writing + anim_pkmn_restart_animation(ctx, animation_state_row_t::StartWriting, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } else if (state.row_state == animation_state_row_t::StartWriting) { + anim_pkmn_start_or_process_animation(ctx, animation_state_row_t::Writing, new_animation_result, new_state, + current_state, current_frames, current_config); + } - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + if (conditions.is_writing) { + if (conditions.continue_writing) { + // keep writing + anim_pkmn_process_animation(new_animation_result, new_state, current_state, current_frames); } + } else if (((!conditions.is_writing && state.row_state == animation_state_row_t::Idle) || + state.row_state == animation_state_row_t::WakeUp) && + conditions.release_frame_after_press) { + // back to idle + anim_pkmn_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} #endif #ifdef FEATURE_MS_AGENT_EMBEDDED_ASSETS - enum class anim_ms_agent_process_animation_result_status_t : uint8_t { None, Started, Updated, End, Looped, NextAnimationStarted }; - struct anim_ms_agent_process_animation_result_t { - animation_state_row_t row_state; - anim_ms_agent_process_animation_result_status_t status{anim_ms_agent_process_animation_result_status_t::None}; - }; - static anim_ms_agent_process_animation_result_t anim_ms_agent_process_animation(animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const ms_agent_sprite_sheet_t& current_frames) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - const ms_agent_sprite_sheet_animation_section_t *section = nullptr; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; - break; - case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; - break; - case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; - break; - case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; - break; - case animation_state_row_t::FallASleep: - // use sleep animation for ms agent - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; - break; - case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; - break; - case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; - break; - case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; - break; - case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; - break; - case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; - break; - case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; - break; - case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; - break; - case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; - break; - case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; - break; - case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; - break; - } - - anim_ms_agent_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_ms_agent_process_animation_result_status_t::None}; - if (section && section->valid) { - new_animation_result.sprite_sheet_row = section->row; - new_animation_result.sprite_sheet_col = new_animation_result.sprite_sheet_col + 1; - ret.status = anim_ms_agent_process_animation_result_status_t::Updated; - - if (new_animation_result.sprite_sheet_col <= section->start_col) { - // start animation - new_animation_result.sprite_sheet_col = section->start_col; - ret.status = anim_ms_agent_process_animation_result_status_t::Started; - } else if (new_animation_result.sprite_sheet_col == section->end_col) { - // last frame - new_animation_result.sprite_sheet_col = section->end_col; - ret.status = anim_ms_agent_process_animation_result_status_t::Updated; - } else if (new_animation_result.sprite_sheet_col > section->end_col) { - // don't loop at sleep, show last frame - if (new_state.row_state == animation_state_row_t::Sleep) { - // end animation - new_animation_result.sprite_sheet_col = section->end_col; - ret.status = anim_ms_agent_process_animation_result_status_t::End; - } else { - // loop animation - new_animation_result.sprite_sheet_col = section->start_col; - ret.status = anim_ms_agent_process_animation_result_status_t::Looped; - } - } - - } +enum class anim_ms_agent_process_animation_result_status_t : uint8_t { + None, + Started, + Updated, + End, + Looped, + NextAnimationStarted +}; +struct anim_ms_agent_process_animation_result_t { + animation_state_row_t row_state; + anim_ms_agent_process_animation_result_status_t status{anim_ms_agent_process_animation_result_status_t::None}; +}; +static anim_ms_agent_process_animation_result_t +anim_ms_agent_process_animation(animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + const ms_agent_sprite_sheet_t& current_frames) { + using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + const ms_agent_sprite_sheet_animation_section_t *section = nullptr; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + break; + case animation_state_row_t::StartWriting: + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + break; + case animation_state_row_t::Writing: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::EndWriting: + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + break; + case animation_state_row_t::Happy: + section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + break; + case animation_state_row_t::FallASleep: + // use sleep animation for ms agent + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::Sleep: + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::WakeUp: + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + break; + case animation_state_row_t::Boring: + section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + break; + case animation_state_row_t::Test: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::StartWorking: + section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + break; + case animation_state_row_t::Working: + section = current_frames.working.valid ? ¤t_frames.working : nullptr; + break; + case animation_state_row_t::EndWorking: + section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + break; + case animation_state_row_t::StartMoving: + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + break; + case animation_state_row_t::Moving: + section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + break; + case animation_state_row_t::EndMoving: + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + break; + case animation_state_row_t::StartRunning: + section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + break; + case animation_state_row_t::Running: + section = current_frames.running.valid ? ¤t_frames.running : nullptr; + break; + case animation_state_row_t::EndRunning: + section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + break; + } + + anim_ms_agent_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_ms_agent_process_animation_result_status_t::None}; + if (section && section->valid) { + new_animation_result.sprite_sheet_row = section->row; + new_animation_result.sprite_sheet_col = new_animation_result.sprite_sheet_col + 1; + ret.status = anim_ms_agent_process_animation_result_status_t::Updated; + + if (new_animation_result.sprite_sheet_col <= section->start_col) { + // start animation + new_animation_result.sprite_sheet_col = section->start_col; + ret.status = anim_ms_agent_process_animation_result_status_t::Started; + } else if (new_animation_result.sprite_sheet_col == section->end_col) { + // last frame + new_animation_result.sprite_sheet_col = section->end_col; + ret.status = anim_ms_agent_process_animation_result_status_t::Updated; + } else if (new_animation_result.sprite_sheet_col > section->end_col) { + // don't loop at sleep, show last frame + if (new_state.row_state == animation_state_row_t::Sleep) { + // end animation + new_animation_result.sprite_sheet_col = section->end_col; + ret.status = anim_ms_agent_process_animation_result_status_t::End; + } else { + // loop animation + new_animation_result.sprite_sheet_col = section->start_col; + ret.status = anim_ms_agent_process_animation_result_status_t::Looped; + } + } + } - return ret; + return ret; +} +static anim_ms_agent_process_animation_result_t +anim_ms_agent_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + const ms_agent_sprite_sheet_t& current_frames, + [[maybe_unused]] const config::config_t& current_config) { + using namespace assets; + assert(MAX_ANIMATION_FRAMES > 0); + assert(MAX_ANIMATION_FRAMES <= INT_MAX); + + const ms_agent_sprite_sheet_animation_section_t *section = nullptr; + switch (new_row_state) { + case animation_state_row_t::Idle: + section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + break; + case animation_state_row_t::StartWriting: + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + break; + case animation_state_row_t::Writing: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::EndWriting: + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + break; + case animation_state_row_t::Happy: + section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + break; + case animation_state_row_t::FallASleep: + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::Sleep: + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::WakeUp: + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + break; + case animation_state_row_t::Boring: + section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + break; + case animation_state_row_t::Test: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::StartWorking: + section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + break; + case animation_state_row_t::Working: + section = current_frames.working.valid ? ¤t_frames.working : nullptr; + break; + case animation_state_row_t::EndWorking: + section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + break; + case animation_state_row_t::StartMoving: + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + break; + case animation_state_row_t::Moving: + section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + break; + case animation_state_row_t::EndMoving: + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + break; + case animation_state_row_t::StartRunning: + section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + break; + case animation_state_row_t::Running: + section = current_frames.running.valid ? ¤t_frames.running : nullptr; + break; + case animation_state_row_t::EndRunning: + section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + break; + } + + anim_ms_agent_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_ms_agent_process_animation_result_status_t::None}; + if (section && section->valid) { + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = section->row; + new_animation_result.sprite_sheet_col = section->start_col; + if (new_state.row_state == animation_state_row_t::Idle) { + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame; + } } - static anim_ms_agent_process_animation_result_t anim_ms_agent_restart_animation([[maybe_unused]] animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const ms_agent_sprite_sheet_t& current_frames, - [[maybe_unused]] const config::config_t& current_config) { - using namespace assets; - assert(MAX_ANIMATION_FRAMES > 0); - assert(MAX_ANIMATION_FRAMES <= INT_MAX); - - const ms_agent_sprite_sheet_animation_section_t *section = nullptr; - switch (new_row_state) { - case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; - break; - case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; - break; - case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; - break; - case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; - break; - case animation_state_row_t::FallASleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; - break; - case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; - break; - case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; - break; - case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; - break; - case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; - break; - case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; - break; - case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; - break; - case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; - break; - case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; - break; - case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; - break; - case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; - break; - } + ret.row_state = new_state.row_state; + ret.status = anim_ms_agent_process_animation_result_status_t::Started; + } - anim_ms_agent_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_ms_agent_process_animation_result_status_t::None}; - if (section && section->valid) { - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = section->row; - new_animation_result.sprite_sheet_col = section->start_col; - if (new_state.row_state == animation_state_row_t::Idle) { - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame; - } - } - ret.row_state = new_state.row_state; - ret.status = anim_ms_agent_process_animation_result_status_t::Started; - } + return ret; +} - return ret; +static anim_ms_agent_process_animation_result_t anim_ms_agent_start_or_process_animation( + animation_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, + animation_state_t& new_state, const animation_state_t& current_state, const ms_agent_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + if (current_state.row_state != new_row_state) { + auto result = anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); + if (result.status == anim_ms_agent_process_animation_result_status_t::Looped || + result.status == anim_ms_agent_process_animation_result_status_t::End) { + result = anim_ms_agent_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); + result.status = anim_ms_agent_process_animation_result_status_t::NextAnimationStarted; } + return result; + } - static anim_ms_agent_process_animation_result_t anim_ms_agent_start_or_process_animation(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const ms_agent_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - - if (current_state.row_state != new_row_state) { - auto result = anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); - if (result.status == anim_ms_agent_process_animation_result_status_t::Looped || result.status == anim_ms_agent_process_animation_result_status_t::End) { - result = anim_ms_agent_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); - result.status = anim_ms_agent_process_animation_result_status_t::NextAnimationStarted; - } - return result; - } + return anim_ms_agent_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); +} - return anim_ms_agent_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); +static anim_next_frame_result_t +anim_ms_agent_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, + const anim_handle_key_press_result_t& trigger_result) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + // assert(upd.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const auto& input_shm = *input.shm; + // const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); + const auto& current_frames = get_current_animation(ctx).ms_agent; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + /// @TODO: make animation fsm + + const platform::timestamp_ms_t now = platform::get_current_time_ms(); + const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000; + assert(now >= last_key_pressed_timestamp); + const auto sleep_timeout = now - last_key_pressed_timestamp; + + const bool start_boring = SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms / SLEEP_BORING_PART; + + switch (current_state.row_state) { + case animation_state_row_t::Test: + case animation_state_row_t::Happy: + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + // not supported, same as idle + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + // @TODO: add working (CPU state) animation + break; + case animation_state_row_t::Idle: { + if (current_config.idle_animation && conditions.go_next_frame) { + anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); } - static anim_next_frame_result_t anim_ms_agent_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - //assert(upd.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const auto& input_shm = *input.shm; - //const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); - const auto& current_frames = get_current_animation(ctx).ms_agent; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); - - /// @TODO: make animation fsm - - const platform::timestamp_ms_t now = platform::get_current_time_ms(); - const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec*1000; - assert(now >= last_key_pressed_timestamp); - const auto sleep_timeout = now - last_key_pressed_timestamp; - - const bool start_boring = SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms/SLEEP_BORING_PART; - - switch (current_state.row_state) { - case animation_state_row_t::Test: - case animation_state_row_t::Happy: - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - // not supported, same as idle - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - // @TODO: add working (CPU state) animation - break; - case animation_state_row_t::Idle: { - if (current_config.idle_animation && conditions.go_next_frame) { - anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); - } - - // handle sleep - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); - - // Idle Sleep - if (conditions.check_for_idle_sleep) { - if (!is_sleeping_time) { - if (current_state.row_state == animation_state_row_t::Idle) { - // start boring animation - if (start_boring && !current_state.show_boring_animation_once) { - anim_ms_agent_restart_animation(ctx, animation_state_row_t::Boring, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.show_boring_animation_once = true; - new_state.anim_last_direction = 0.0f; - } - } - - // idle sleep - if (sleep_timeout >= idle_sleep_timeout_ms) { - if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - anim_ms_agent_process_animation_result_t animation_result { .row_state = current_state.row_state }; - if (conditions.is_moving) { - animation_result = anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Sleep, // wait for moving to end - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - animation_result = anim_ms_agent_restart_animation(ctx, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - if (animation_result.row_state == animation_state_row_t::Sleep) { - new_state.anim_last_direction = 0.0f; - new_state.is_idle_sleep = true; - new_state.show_boring_animation_once = false; - } - } else if (current_state.row_state == animation_state_row_t::Sleep) { - if (conditions.go_next_frame) { - // process sleep animation - anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - // wake up - if (conditions.go_next_frame) { - // end current sleep animation - const auto animation_result = anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::WakeUp) { - new_state.is_idle_sleep = false; - } - } - } - } - } - } + // handle sleep + const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); - // Sleep Mode - if (current_config.enable_scheduled_sleep) { - if (is_sleeping_time) { - if (new_state.row_state == animation_state_row_t::Idle) { - anim_ms_agent_restart_animation(ctx, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.anim_last_direction = 0.0f; - new_state.is_idle_sleep = false; - } else if (state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - if (conditions.go_next_frame) { - anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - // wake up - if (conditions.go_next_frame) { - // end current sleep animation - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - // wake up - if (conditions.go_next_frame) { - // end current sleep animation - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - }break; - case animation_state_row_t::Writing: { - if (conditions.continue_writing) { - if (conditions.go_next_frame) { - // loop writing animation - anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } else { - if (conditions.release_frame_after_press) { - // cancel writing - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::EndWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - }break; - case animation_state_row_t::StartWriting: - if (conditions.go_next_frame) { - const auto animation_result = anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Writing, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::Writing) { - // reset release counter after writing is started (for real) - new_state.hold_frame_after_release = true; - new_state.hold_frame_ms = 0; - } - } - break; - case animation_state_row_t::EndWriting: - if (conditions.go_next_frame) { - // finish animation - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::FallASleep: - if (conditions.go_next_frame) { - // finish animation - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::Sleep: - if (conditions.go_next_frame) { - anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); - } - break; - case animation_state_row_t::WakeUp: - if (conditions.go_next_frame) { - // finish animation - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::Boring: - if (conditions.go_next_frame) { - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames, current_config); - if (!start_boring) { - new_state.show_boring_animation_once = false; - } - } - break; + // Idle Sleep + if (conditions.check_for_idle_sleep) { + if (!is_sleeping_time) { + if (current_state.row_state == animation_state_row_t::Idle) { + // start boring animation + if (start_boring && !current_state.show_boring_animation_once) { + anim_ms_agent_restart_animation(ctx, animation_state_row_t::Boring, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.show_boring_animation_once = true; + new_state.anim_last_direction = 0.0f; + } } - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + // idle sleep + if (sleep_timeout >= idle_sleep_timeout_ms) { + if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + anim_ms_agent_process_animation_result_t animation_result{.row_state = current_state.row_state}; + if (conditions.is_moving) { + animation_result = anim_ms_agent_start_or_process_animation( + ctx, animation_state_row_t::Sleep, // wait for moving to end + new_animation_result, new_state, current_state, current_frames, current_config); + } else { + animation_result = + anim_ms_agent_restart_animation(ctx, animation_state_row_t::Sleep, new_animation_result, new_state, + current_state, current_frames, current_config); + } + if (animation_result.row_state == animation_state_row_t::Sleep) { + new_state.anim_last_direction = 0.0f; + new_state.is_idle_sleep = true; + new_state.show_boring_animation_once = false; + } + } else if (current_state.row_state == animation_state_row_t::Sleep) { + if (conditions.go_next_frame) { + // process sleep animation + anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { + // wake up + if (conditions.go_next_frame) { + // end current sleep animation + const auto animation_result = + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, + new_state, current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::WakeUp) { + new_state.is_idle_sleep = false; + } + } + } + } + } } - static anim_next_frame_result_t anim_ms_agent_key_pressed_next_frame(animation_context_t& ctx, - animation_state_t& state, - [[maybe_unused]] const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - [[maybe_unused]] const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); - const auto& current_frames = get_current_animation(ctx).ms_agent; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - //const auto conditions = get_anim_conditions(ctx, input, current_state, trigger, current_config); - - // in Writing mode/start writing - switch (state.row_state) { - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - // moving not supported - case animation_state_row_t::Test: - case animation_state_row_t::Happy: - case animation_state_row_t::Idle: - anim_ms_agent_restart_animation(ctx, animation_state_row_t::StartWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - break; - case animation_state_row_t::StartWriting: - // start end writing and process animation in anim_ms_agent_idle_next_frame - break; - case animation_state_row_t::Writing: - // reset hold frame so we can continue writing - new_state.hold_frame_ms = 0; - // start end writing and process animation in anim_ms_agent_idle_next_frame - break; - case animation_state_row_t::EndWriting: - // start end writing and process animation in anim_ms_agent_idle_next_frame - break; - case animation_state_row_t::FallASleep: - // process animation in anim_ms_agent_idle_next_frame - break; - case animation_state_row_t::Sleep: - if (current_state.is_idle_sleep) { - // wake up, end current sleep animation - anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.is_idle_sleep = false; - } - case animation_state_row_t::WakeUp: - // process animation in anim_ms_agent_idle_next_frame - break; - case animation_state_row_t::Boring: - // process animation in anim_ms_agent_idle_next_frame - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - // start end writing and process animation in anim_ms_agent_idle_next_frame - break; + // Sleep Mode + if (current_config.enable_scheduled_sleep) { + if (is_sleeping_time) { + if (new_state.row_state == animation_state_row_t::Idle) { + anim_ms_agent_restart_animation(ctx, animation_state_row_t::Sleep, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.anim_last_direction = 0.0f; + new_state.is_idle_sleep = false; + } else if (state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + if (conditions.go_next_frame) { + anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + // wake up + if (conditions.go_next_frame) { + // end current sleep animation + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, + new_state, current_state, current_frames, current_config); + } + } + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + // wake up + if (conditions.go_next_frame) { + // end current sleep animation + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } } + } break; + case animation_state_row_t::Writing: { + if (conditions.continue_writing) { + if (conditions.go_next_frame) { + // loop writing animation + anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } else { + if (conditions.release_frame_after_press) { + // cancel writing + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::EndWriting, new_animation_result, + new_state, current_state, current_frames, current_config); + } + } + } break; + case animation_state_row_t::StartWriting: + if (conditions.go_next_frame) { + const auto animation_result = + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Writing, new_animation_result, new_state, + current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::Writing) { + // reset release counter after writing is started (for real) + new_state.hold_frame_after_release = true; + new_state.hold_frame_ms = 0; + } + } + break; + case animation_state_row_t::EndWriting: + if (conditions.go_next_frame) { + // finish animation + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, + current_config); + } + break; + case animation_state_row_t::FallASleep: + if (conditions.go_next_frame) { + // finish animation + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Sleep, new_animation_result, new_state, + current_state, current_frames, current_config); + } + break; + case animation_state_row_t::Sleep: + if (conditions.go_next_frame) { + anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); + } + break; + case animation_state_row_t::WakeUp: + if (conditions.go_next_frame) { + // finish animation + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, + current_config); + } + break; + case animation_state_row_t::Boring: + if (conditions.go_next_frame) { + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames, + current_config); + if (!start_boring) { + new_state.show_boring_animation_once = false; + } + } + break; + } + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} + +static anim_next_frame_result_t +anim_ms_agent_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& state, + [[maybe_unused]] const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + [[maybe_unused]] const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); + const auto& current_frames = get_current_animation(ctx).ms_agent; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + // const auto conditions = get_anim_conditions(ctx, input, current_state, trigger, current_config); + + // in Writing mode/start writing + switch (state.row_state) { + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + // moving not supported + case animation_state_row_t::Test: + case animation_state_row_t::Happy: + case animation_state_row_t::Idle: + anim_ms_agent_restart_animation(ctx, animation_state_row_t::StartWriting, new_animation_result, new_state, + current_state, current_frames, current_config); + break; + case animation_state_row_t::StartWriting: + // start end writing and process animation in anim_ms_agent_idle_next_frame + break; + case animation_state_row_t::Writing: + // reset hold frame so we can continue writing + new_state.hold_frame_ms = 0; + // start end writing and process animation in anim_ms_agent_idle_next_frame + break; + case animation_state_row_t::EndWriting: + // start end writing and process animation in anim_ms_agent_idle_next_frame + break; + case animation_state_row_t::FallASleep: + // process animation in anim_ms_agent_idle_next_frame + break; + case animation_state_row_t::Sleep: + if (current_state.is_idle_sleep) { + // wake up, end current sleep animation + anim_ms_agent_start_or_process_animation(ctx, animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.is_idle_sleep = false; + } + case animation_state_row_t::WakeUp: + // process animation in anim_ms_agent_idle_next_frame + break; + case animation_state_row_t::Boring: + // process animation in anim_ms_agent_idle_next_frame + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + // start end writing and process animation in anim_ms_agent_idle_next_frame + break; + } + + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} - /* - static anim_next_frame_result_t anim_ms_agent_working_next_frame(animation_context_t& ctx, const platform::update::update_context_t& upd, - animation_state_t& state) { - using namespace assets; +/* +static anim_next_frame_result_t anim_ms_agent_working_next_frame(animation_context_t& ctx, const +platform::update::update_context_t& upd, animation_state_t& state) { using namespace assets; - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(upd.shm != nullptr); - //assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); - const auto& current_frames = get_current_animation(ctx).ms_agent; + assert(ctx.shm != nullptr); + assert(upd.shm != nullptr); + //assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + //const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); + const auto& current_frames = get_current_animation(ctx).ms_agent; - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; - assert(anim_shm.anim_type == config::config_animation_sprite_sheet_layout_t::MsAgent); + assert(anim_shm.anim_type == config::config_animation_sprite_sheet_layout_t::MsAgent); - /// @TODO: use state machine for animation (states) + /// @TODO: use state machine for animation (states) - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); - } - */ + return anim_update_animation_state(anim_shm, state, + new_animation_result, new_state, + current_animation_result, current_state, + current_config); +} +*/ #endif #ifdef FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION - enum class anim_custom_process_animation_result_status_t : uint8_t { None, Started, Updated, End, Looped, NextAnimationStarted, SkipMovement, Moved, Stop }; - struct anim_custom_process_animation_result_t { - animation_state_row_t row_state; - anim_custom_process_animation_result_status_t status{anim_custom_process_animation_result_status_t::None}; - }; - static anim_custom_process_animation_result_t anim_custom_process_animation(animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const custom_sprite_sheet_t& current_frames) { - using namespace assets; - - const custom_sprite_sheet_animation_section_t *section = nullptr; - switch (new_state.row_state) { - case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; - break; - case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; - break; - case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; - break; - case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; - break; - case animation_state_row_t::FallASleep: - section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; - break; - case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; - break; - case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; - break; - case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; - break; - case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; - break; - case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; - break; - case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; - break; - case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; - break; - case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; - break; - case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; - break; - case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; - break; - case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; - break; - } - - anim_custom_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (section && section->valid) { - new_animation_result.sprite_sheet_row = section->row; - new_animation_result.sprite_sheet_col = new_animation_result.sprite_sheet_col + 1; - ret.status = anim_custom_process_animation_result_status_t::Updated; +enum class anim_custom_process_animation_result_status_t : uint8_t { + None, + Started, + Updated, + End, + Looped, + NextAnimationStarted, + SkipMovement, + Moved, + Stop +}; +struct anim_custom_process_animation_result_t { + animation_state_row_t row_state; + anim_custom_process_animation_result_status_t status{anim_custom_process_animation_result_status_t::None}; +}; +static anim_custom_process_animation_result_t +anim_custom_process_animation(animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + const custom_sprite_sheet_t& current_frames) { + using namespace assets; + + const custom_sprite_sheet_animation_section_t *section = nullptr; + switch (new_state.row_state) { + case animation_state_row_t::Idle: + section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + break; + case animation_state_row_t::StartWriting: + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + break; + case animation_state_row_t::Writing: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::EndWriting: + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + break; + case animation_state_row_t::Happy: + section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + break; + case animation_state_row_t::FallASleep: + section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; + break; + case animation_state_row_t::Sleep: + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::WakeUp: + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + break; + case animation_state_row_t::Boring: + section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + break; + case animation_state_row_t::Test: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::StartWorking: + section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + break; + case animation_state_row_t::Working: + section = current_frames.working.valid ? ¤t_frames.working : nullptr; + break; + case animation_state_row_t::EndWorking: + section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + break; + case animation_state_row_t::StartMoving: + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + break; + case animation_state_row_t::Moving: + section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + break; + case animation_state_row_t::EndMoving: + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + break; + case animation_state_row_t::StartRunning: + section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + break; + case animation_state_row_t::Running: + section = current_frames.running.valid ? ¤t_frames.running : nullptr; + break; + case animation_state_row_t::EndRunning: + section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + break; + } + + anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_custom_process_animation_result_status_t::None}; + if (section && section->valid) { + new_animation_result.sprite_sheet_row = section->row; + new_animation_result.sprite_sheet_col = new_animation_result.sprite_sheet_col + 1; + ret.status = anim_custom_process_animation_result_status_t::Updated; + + if (new_animation_result.sprite_sheet_col <= section->start_col) { + // start animation + new_animation_result.sprite_sheet_col = section->start_col; + ret.status = anim_custom_process_animation_result_status_t::Started; + } else if (new_animation_result.sprite_sheet_col == section->end_col) { + // last frame + new_animation_result.sprite_sheet_col = section->end_col; + ret.status = anim_custom_process_animation_result_status_t::Updated; + } else if (new_animation_result.sprite_sheet_col > section->end_col) { + // loop animation + new_animation_result.sprite_sheet_col = section->start_col; + ret.status = anim_custom_process_animation_result_status_t::Looped; + } + } - if (new_animation_result.sprite_sheet_col <= section->start_col) { - // start animation - new_animation_result.sprite_sheet_col = section->start_col; - ret.status = anim_custom_process_animation_result_status_t::Started; - } else if (new_animation_result.sprite_sheet_col == section->end_col) { - // last frame - new_animation_result.sprite_sheet_col = section->end_col; - ret.status = anim_custom_process_animation_result_status_t::Updated; - } else if (new_animation_result.sprite_sheet_col > section->end_col) { - // loop animation - new_animation_result.sprite_sheet_col = section->start_col; - ret.status = anim_custom_process_animation_result_status_t::Looped; - } + return ret; +} +static anim_custom_process_animation_result_t +anim_custom_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, + const custom_sprite_sheet_t& current_frames, + [[maybe_unused]] const config::config_t& current_config) { + using namespace assets; + + const custom_sprite_sheet_animation_section_t *section = nullptr; + switch (new_row_state) { + case animation_state_row_t::Idle: + section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + break; + case animation_state_row_t::StartWriting: + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + break; + case animation_state_row_t::Writing: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::EndWriting: + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + break; + case animation_state_row_t::Happy: + section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + break; + case animation_state_row_t::FallASleep: + section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; + break; + case animation_state_row_t::Sleep: + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::WakeUp: + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + break; + case animation_state_row_t::Boring: + section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + break; + case animation_state_row_t::Test: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::StartWorking: + section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + break; + case animation_state_row_t::Working: + section = current_frames.working.valid ? ¤t_frames.working : nullptr; + break; + case animation_state_row_t::EndWorking: + section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + break; + case animation_state_row_t::StartMoving: + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + break; + case animation_state_row_t::Moving: + section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + break; + case animation_state_row_t::EndMoving: + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + break; + case animation_state_row_t::StartRunning: + section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + break; + case animation_state_row_t::Running: + section = current_frames.running.valid ? ¤t_frames.running : nullptr; + break; + case animation_state_row_t::EndRunning: + section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + break; + } + + anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_custom_process_animation_result_status_t::None}; + if (section && section->valid) { + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = section->row; + new_animation_result.sprite_sheet_col = section->start_col; + new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::None; + if (new_state.row_state == animation_state_row_t::Idle) { + assert(current_frames.idle.end_col >= 0); + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame % (current_frames.idle.end_col + 1); + } + } else if (new_state.row_state == animation_state_row_t::StartMoving || + new_state.row_state == animation_state_row_t::Moving || + new_state.row_state == animation_state_row_t::EndMoving) { + // flip movement frame when configured + if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { + if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 1) { + new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::Mirror; + } else if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 0) { + new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::NoMirror; } + } + } - return ret; + ret.row_state = new_state.row_state; + ret.status = anim_custom_process_animation_result_status_t::Started; + } + + return ret; +} +static anim_custom_process_animation_result_t anim_custom_restart_animation( + [[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + const animation_state_row_t fallback_row_state, const animation_state_row_t end_fallback_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + [[maybe_unused]] const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, + [[maybe_unused]] const config::config_t& current_config) { + using namespace assets; + + constexpr size_t new_row_states_count = 3; + const animation_state_row_t new_row_states[new_row_states_count] = {new_row_state, fallback_row_state, + end_fallback_row_state}; + + const custom_sprite_sheet_animation_section_t *section = nullptr; + for (size_t i = 0; i < new_row_states_count && section == nullptr; i++) { + new_row_state = new_row_states[i]; + switch (new_row_state) { + case animation_state_row_t::Idle: + section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + break; + case animation_state_row_t::StartWriting: + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + break; + case animation_state_row_t::Writing: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::EndWriting: + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + break; + case animation_state_row_t::Happy: + section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + break; + case animation_state_row_t::FallASleep: + section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; + break; + case animation_state_row_t::Sleep: + section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + break; + case animation_state_row_t::WakeUp: + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + break; + case animation_state_row_t::Boring: + section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + break; + case animation_state_row_t::Test: + section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + break; + case animation_state_row_t::StartWorking: + section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + break; + case animation_state_row_t::Working: + section = current_frames.working.valid ? ¤t_frames.working : nullptr; + break; + case animation_state_row_t::EndWorking: + section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + break; + case animation_state_row_t::StartMoving: + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + break; + case animation_state_row_t::Moving: + section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + break; + case animation_state_row_t::EndMoving: + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + break; + case animation_state_row_t::StartRunning: + section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + break; + case animation_state_row_t::Running: + section = current_frames.running.valid ? ¤t_frames.running : nullptr; + break; + case animation_state_row_t::EndRunning: + section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + break; } - static anim_custom_process_animation_result_t anim_custom_restart_animation([[maybe_unused]] animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const custom_sprite_sheet_t& current_frames, - [[maybe_unused]] const config::config_t& current_config) { - using namespace assets; - - const custom_sprite_sheet_animation_section_t *section = nullptr; - switch (new_row_state) { - case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; - break; - case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; - break; - case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; - break; - case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; - break; - case animation_state_row_t::FallASleep: - section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; - break; - case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; - break; - case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; - break; - case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; - break; - case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; - break; - case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; - break; - case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; - break; - case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; - break; - case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; - break; - case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; - break; - case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; - break; - case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; - break; + } + + anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_custom_process_animation_result_status_t::None}; + if (section && section->valid) { + new_state.row_state = new_row_state; + new_animation_result.sprite_sheet_row = section->row; + new_animation_result.sprite_sheet_col = section->start_col; + new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::None; + if (new_state.row_state == animation_state_row_t::Idle) { + assert(current_frames.idle.end_col >= 0); + if (current_config.idle_frame) { + new_animation_result.sprite_sheet_col = current_config.idle_frame % (current_frames.idle.end_col + 1); + } + } else if (new_state.row_state == animation_state_row_t::StartMoving || + new_state.row_state == animation_state_row_t::Moving || + new_state.row_state == animation_state_row_t::EndMoving) { + // flip movement frame when configured + if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { + if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 1) { + new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::Mirror; + } else if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 0) { + new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::NoMirror; } + } + } + ret.row_state = new_state.row_state; + ret.status = anim_custom_process_animation_result_status_t::Started; + } - anim_custom_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (section && section->valid) { - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = section->row; - new_animation_result.sprite_sheet_col = section->start_col; - new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::None; - if (new_state.row_state == animation_state_row_t::Idle) { - assert(current_frames.idle.end_col >= 0); - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame % (current_frames.idle.end_col+1); - } - } else if (new_state.row_state == animation_state_row_t::StartMoving || new_state.row_state == animation_state_row_t::Moving || new_state.row_state == animation_state_row_t::EndMoving) { - // flip movement frame when configured - if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { - if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 1) { - new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::Mirror; - } else if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 0) { - new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::NoMirror; - } - } - } + return ret; +} - ret.row_state = new_state.row_state; - ret.status = anim_custom_process_animation_result_status_t::Started; - } +static anim_custom_process_animation_result_t anim_custom_start_or_process_animation( + animation_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, + animation_state_t& new_state, const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + if (current_state.row_state != new_row_state) { + auto result = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + if (result.status == anim_custom_process_animation_result_status_t::Looped || + result.status == anim_custom_process_animation_result_status_t::End) { + result = anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); + result.status = result.status != anim_custom_process_animation_result_status_t::None + ? anim_custom_process_animation_result_status_t::NextAnimationStarted + : anim_custom_process_animation_result_status_t::None; + } + return result; + } - return ret; + return anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); +} +static anim_custom_process_animation_result_t anim_custom_start_or_process_animation( + animation_context_t& ctx, animation_state_row_t new_row_state, animation_state_row_t fallback_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + if (current_state.row_state != new_row_state) { + auto result = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + if (result.status == anim_custom_process_animation_result_status_t::Looped || + result.status == anim_custom_process_animation_result_status_t::End) { + result = anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); + if (result.status == anim_custom_process_animation_result_status_t::None) { + result = anim_custom_restart_animation(ctx, fallback_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); + result.status = result.status != anim_custom_process_animation_result_status_t::None + ? anim_custom_process_animation_result_status_t::NextAnimationStarted + : anim_custom_process_animation_result_status_t::None; + } else { + result.status = anim_custom_process_animation_result_status_t::NextAnimationStarted; + } } - static anim_custom_process_animation_result_t anim_custom_restart_animation([[maybe_unused]] animation_context_t& ctx, - animation_state_row_t new_row_state, - const animation_state_row_t fallback_row_state, - const animation_state_row_t end_fallback_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - [[maybe_unused]] const animation_state_t& current_state, - const custom_sprite_sheet_t& current_frames, - [[maybe_unused]] const config::config_t& current_config) { - using namespace assets; - - constexpr size_t new_row_states_count = 3; - const animation_state_row_t new_row_states[new_row_states_count] = { new_row_state, fallback_row_state, end_fallback_row_state}; - - const custom_sprite_sheet_animation_section_t *section = nullptr; - for (size_t i = 0;i < new_row_states_count && section == nullptr;i++) { - new_row_state = new_row_states[i]; - switch (new_row_state) { - case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; - break; - case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; - break; - case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; - break; - case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; - break; - case animation_state_row_t::FallASleep: - section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; - break; - case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; - break; - case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; - break; - case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; - break; - case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; - break; - case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; - break; - case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; - break; - case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; - break; - case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; - break; - case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; - break; - case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; - break; - case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; - break; - case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; - break; - case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; - break; + return result; + } + + return anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, current_state, + current_frames, current_config); +} + +static anim_custom_process_animation_result_t +anim_custom_handle_movement(animation_context_t& ctx, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const anim_handle_key_press_result_t& trigger_result, + const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, + const config::config_t& current_config) { + using namespace assets; + + assert(ctx.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, + .status = anim_custom_process_animation_result_status_t::None}; + if (!conditions.is_writing && current_config.movement_speed > 0 && current_config.movement_radius > 0 && + current_config.fps > 0) { + if ((conditions.go_next_frame || conditions.process_idle_animation) || conditions.process_movement || + conditions.process_movement_animation) { + const float delta = 1.0f / static_cast(current_config.fps); + const auto delta_ms = static_cast(delta * 1000); + const auto frame_height = current_config.cat_height; + const auto frame_width = + static_cast(static_cast(frame_height) * (static_cast(current_frames.frame_width) / + static_cast(current_frames.frame_height))); + const auto fmovement_radius = static_cast(current_config.movement_radius); + + assert(current_config.movement_radius > 0); + float max_movement_offset_x_left = 0.0f; + float max_movement_offset_x_right = 0.0f; + float wall_distance = 0.0f; + switch (current_config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + // range: [-r, +r] + max_movement_offset_x_left = + -static_cast(current_config.movement_radius) + static_cast(frame_width) / 2.0f; + max_movement_offset_x_right = static_cast(current_config.movement_radius - frame_width); + wall_distance = anim_shm.movement_offset_x / fmovement_radius; + break; + case config::align_type_t::ALIGN_LEFT: + // range: [0, +2r] + max_movement_offset_x_left = 0.0f; + max_movement_offset_x_right = + static_cast(current_config.movement_radius) * 2.0f - static_cast(frame_width); + wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f - 1.0f; + break; + case config::align_type_t::ALIGN_RIGHT: + // range: [-2r, 0] + max_movement_offset_x_left = + -(static_cast(current_config.movement_radius) * 2.0f) + static_cast(frame_width); + max_movement_offset_x_right = 0.0f; + wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f + 1.0f; // normalize to -1 → 1 + break; + } + + if (current_state.row_state == animation_state_row_t::Idle && + (current_state.anim_pause_after_movement_ms > 0 || + (ctx._rng.range(0, 100) <= CHANCE_FOR_SKIPPING_MOVEMENT_PERCENT))) { + // skip movement + new_state.anim_velocity = 0.0f; + new_state.anim_distance = 0.0f; + if (new_state.anim_pause_after_movement_ms > 0) { + new_state.anim_pause_after_movement_ms -= delta_ms; + if (new_state.anim_pause_after_movement_ms <= 0) { + new_state.anim_pause_after_movement_ms = 0; + } + } + ret.status = anim_custom_process_animation_result_status_t::SkipMovement; + } else { + // moving animation + constexpr float DIR_EPSILON = 1e-3f; + assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); + const int movement_part = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL + ? SMALL_MAX_DISTANCE_PER_MOVEMENT_PART + : MAX_DISTANCE_PER_MOVEMENT_PART; + const float fmovement_part = static_cast(movement_part); + bool end_movement = false; + if (current_state.row_state == animation_state_row_t::Idle) { + // start movement + const auto min_movement = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL + ? (fmovement_radius / fmovement_part / 2) + 1 + : (fmovement_radius / fmovement_part / fmovement_part) + 1; + float max_move_distance = fmovement_radius / fmovement_part; + max_move_distance = max_move_distance <= min_movement ? min_movement : max_move_distance; + + assert(max_move_distance >= 0); + assert(min_movement >= 0); + new_state.anim_distance = static_cast( + ctx._rng.range(static_cast(min_movement), static_cast(max_move_distance))); + + if (anim_shm.movement_offset_x >= max_movement_offset_x_right) { + // run against wall, change direction + anim_shm.anim_direction = -1.0; + anim_shm.movement_offset_x = max_movement_offset_x_right; + } else if (anim_shm.movement_offset_x <= max_movement_offset_x_left) { + // run against wall, change direction + anim_shm.anim_direction = 1.0; + anim_shm.movement_offset_x = max_movement_offset_x_left; + } else { + float toward_wall_bias = fabs(wall_distance); + toward_wall_bias = toward_wall_bias >= 1.0f ? 1.0f : toward_wall_bias; + toward_wall_bias = toward_wall_bias <= 0.0f ? 0.0f : toward_wall_bias; + + const int flip_direction_chance = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL + ? SMALL_FLIP_DIRECTION_NEAR_WALL_PERCENT + : FLIP_DIRECTION_NEAR_WALL_PERCENT; + + assert(toward_wall_bias >= 0.0f); + // change direction: chance drops at center, changes falloff steeper near walls + const auto dir_chance = static_cast(static_cast(100 - flip_direction_chance + 10) * + (1.0f - pow(toward_wall_bias, 1.5f))); + + if (fabs(new_state.anim_last_direction) >= DIR_EPSILON) { + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -new_state.anim_last_direction + : new_state.anim_last_direction; + } else if (fabs(anim_shm.anim_direction) < DIR_EPSILON) { + // idle: choose random start direction + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; + } else { + if (wall_distance > (100.0f / static_cast(flip_direction_chance))) { + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -1.0f : 1.0f; + } else if (wall_distance < -(100.0f / static_cast(flip_direction_chance))) { + anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; + } else { + anim_shm.anim_direction = + (ctx._rng.range(0, 100) < dir_chance) ? anim_shm.anim_direction : -anim_shm.anim_direction; + } + } + } + // start moving animation + new_state.anim_velocity = anim_shm.anim_direction * static_cast(current_config.movement_speed); + ret = anim_custom_restart_animation(ctx, animation_state_row_t::StartMoving, animation_state_row_t::Moving, + animation_state_row_t::EndMoving, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (current_state.row_state == animation_state_row_t::StartMoving) { + if (conditions.process_idle_animation) { + ret = anim_custom_start_or_process_animation(ctx, animation_state_row_t::Moving, + animation_state_row_t::EndMoving, new_animation_result, + new_state, current_state, current_frames, current_config); + ret.status = anim_custom_process_animation_result_status_t::Updated; + } else if (conditions.process_movement_animation) { + ret = anim_custom_restart_animation(ctx, animation_state_row_t::Moving, animation_state_row_t::EndMoving, + animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_custom_process_animation_result_status_t::Updated; + } + } else if (current_state.row_state == animation_state_row_t::Moving) { + if (conditions.process_movement) { + ret = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + ret.status = anim_custom_process_animation_result_status_t::Moved; + + new_state.anim_distance -= fabs(new_state.anim_velocity); + anim_shm.movement_offset_x += new_state.anim_velocity; + // clamp walking/movement area + if (anim_shm.movement_offset_x > max_movement_offset_x_right) { + anim_shm.movement_offset_x = max_movement_offset_x_right; + ret = anim_custom_restart_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, + animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_custom_process_animation_result_status_t::Stop; + } else if (anim_shm.movement_offset_x < max_movement_offset_x_left) { + anim_shm.movement_offset_x = max_movement_offset_x_left; + ret = anim_custom_restart_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, + animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_custom_process_animation_result_status_t::Stop; } - } - anim_custom_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (section && section->valid) { - new_state.row_state = new_row_state; - new_animation_result.sprite_sheet_row = section->row; - new_animation_result.sprite_sheet_col = section->start_col; - new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::None; - if (new_state.row_state == animation_state_row_t::Idle) { - assert(current_frames.idle.end_col >= 0); - if (current_config.idle_frame) { - new_animation_result.sprite_sheet_col = current_config.idle_frame % (current_frames.idle.end_col+1); - } - } else if (new_state.row_state == animation_state_row_t::StartMoving || new_state.row_state == animation_state_row_t::Moving || new_state.row_state == animation_state_row_t::EndMoving) { - // flip movement frame when configured - if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { - if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 1) { - new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::Mirror; - } else if (current_config.custom_sprite_sheet_settings.feature_mirror_x_moving == 0) { - new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::NoMirror; - } - } + if (new_state.anim_distance <= 0 || + (fabs(new_state.anim_distance) < DIR_EPSILON || fabs(new_state.anim_velocity) < DIR_EPSILON)) { + ret = anim_custom_restart_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, + animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + ret.status = anim_custom_process_animation_result_status_t::Stop; } - ret.row_state = new_state.row_state; - ret.status = anim_custom_process_animation_result_status_t::Started; + + end_movement = ret.status == anim_custom_process_animation_result_status_t::Stop; + } + } else if (current_state.row_state == animation_state_row_t::EndMoving) { + end_movement = true; } - return ret; + if (end_movement) { + if (conditions.process_idle_animation) { + ret = anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, + new_state, current_state, current_frames, current_config); + } else { + ret = anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } + new_state.anim_last_direction = anim_shm.anim_direction; + new_state.anim_velocity = 0.0f; + anim_shm.anim_direction = 0.0; + if (ret.row_state == animation_state_row_t::Idle && new_state.anim_distance <= 0) { + assert(current_config.animation_speed_ms >= 0); + assert(movement_part >= 0); + const auto min_wait = current_config.animation_speed_ms * (current_config.movement_wait_factor / 2); + const auto max_wait = current_config.animation_speed_ms * current_config.movement_wait_factor; + assert(min_wait >= 0); + assert(max_wait >= 0); + new_state.anim_pause_after_movement_ms = + static_cast(ctx._rng.range(static_cast(min_wait), static_cast(max_wait))); + ret.status = anim_custom_process_animation_result_status_t::End; + } + } + } } + } else { + // movement got disabled, back to idle + if ((current_config.movement_speed <= 0 || current_config.movement_radius <= 0) && conditions.is_moving) { + // back to idle + ret = anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.anim_velocity = 0.0f; + ret.status = anim_custom_process_animation_result_status_t::Stop; + } + } - static anim_custom_process_animation_result_t anim_custom_start_or_process_animation(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const custom_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - - if (current_state.row_state != new_row_state) { - auto result = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - if (result.status == anim_custom_process_animation_result_status_t::Looped || result.status == anim_custom_process_animation_result_status_t::End) { - result = anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); - result.status = result.status != anim_custom_process_animation_result_status_t::None ? anim_custom_process_animation_result_status_t::NextAnimationStarted : anim_custom_process_animation_result_status_t::None; - } - return result; + return ret; +} +static anim_next_frame_result_t +anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, + const anim_handle_key_press_result_t& trigger_result) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + assert(upd.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Custom); + const auto& current_frames = get_current_animation(ctx).custom; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); + + /// @TODO: make animation fsm + + const platform::timestamp_ms_t now = platform::get_current_time_ms(); + const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000; + assert(now >= last_key_pressed_timestamp); + const auto sleep_timeout = now - last_key_pressed_timestamp; + + const bool start_boring = current_frames.feature_boring && SLEEP_BORING_PART > 0 && + sleep_timeout >= idle_sleep_timeout_ms / SLEEP_BORING_PART; + + switch (current_state.row_state) { + case animation_state_row_t::Happy: + if (current_frames.feature_writing_happy) { + if (current_frames.feature_writing_toggle_frames && current_frames.happy.end_col == 0) { + // animation for + const bool stop_happy_kpm = current_state.row_state == animation_state_row_t::Happy && + conditions.release_frame_after_press && !conditions.process_idle_animation; + if (stop_happy_kpm) { + // back to idle + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); } - - return anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); - } - static anim_custom_process_animation_result_t anim_custom_start_or_process_animation(animation_context_t& ctx, - animation_state_row_t new_row_state, - animation_state_row_t fallback_row_state, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const animation_state_t& current_state, - const custom_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - - if (current_state.row_state != new_row_state) { - auto result = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - if (result.status == anim_custom_process_animation_result_status_t::Looped || result.status == anim_custom_process_animation_result_status_t::End) { - result = anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); - if (result.status == anim_custom_process_animation_result_status_t::None) { - result = anim_custom_restart_animation(ctx, fallback_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); - result.status = result.status != anim_custom_process_animation_result_status_t::None ? anim_custom_process_animation_result_status_t::NextAnimationStarted : anim_custom_process_animation_result_status_t::None; - } else { - result.status = anim_custom_process_animation_result_status_t::NextAnimationStarted; - } - } - return result; + } + if (new_state.row_state == animation_state_row_t::Happy) { + if (conditions.go_next_frame) { + if (conditions.continue_writing) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::StartWorking, + animation_state_row_t::Working, new_animation_result, new_state, + current_state, current_frames, current_config); + } else { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } } - - return anim_custom_restart_animation(ctx, new_row_state, new_animation_result, new_state, - current_state, current_frames, current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); } - - - static anim_custom_process_animation_result_t anim_custom_handle_movement(animation_context_t& ctx, - const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_player_result_t& new_animation_result, - animation_state_t& new_state, - const anim_handle_key_press_result_t& trigger_result, - const animation_state_t& current_state, - const custom_sprite_sheet_t& current_frames, - const config::config_t& current_config) { - using namespace assets; - - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); - - anim_custom_process_animation_result_t ret {.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (!conditions.is_writing && current_config.movement_speed > 0 && current_config.movement_radius > 0 && current_config.fps > 0) { - if ((conditions.go_next_frame || conditions.process_idle_animation) || conditions.process_movement || conditions.process_movement_animation) { - const float delta = 1.0f / static_cast(current_config.fps); - const auto delta_ms = static_cast(delta * 1000); - const auto frame_height = current_config.cat_height; - const auto frame_width = static_cast(static_cast(frame_height) * (static_cast(current_frames.frame_width) / static_cast(current_frames.frame_height))); - const auto fmovement_radius = static_cast(current_config.movement_radius); - - assert(current_config.movement_radius > 0); - float max_movement_offset_x_left = 0.0f; - float max_movement_offset_x_right = 0.0f; - float wall_distance = 0.0f; - switch (current_config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - // range: [-r, +r] - max_movement_offset_x_left = -static_cast(current_config.movement_radius) + static_cast(frame_width) / 2.0f; - max_movement_offset_x_right = static_cast(current_config.movement_radius - frame_width); - wall_distance = anim_shm.movement_offset_x / fmovement_radius; - break; - case config::align_type_t::ALIGN_LEFT: - // range: [0, +2r] - max_movement_offset_x_left = 0.0f; - max_movement_offset_x_right = static_cast(current_config.movement_radius) * 2.0f - static_cast(frame_width); - wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f - 1.0f; - break; - case config::align_type_t::ALIGN_RIGHT: - // range: [-2r, 0] - max_movement_offset_x_left = -(static_cast(current_config.movement_radius) * 2.0f) + static_cast(frame_width); - max_movement_offset_x_right = 0.0f; - wall_distance = (anim_shm.movement_offset_x / (fmovement_radius * 2.0f)) * 2.0f + 1.0f; // normalize to -1 → 1 - break; - } - - if (current_state.row_state == animation_state_row_t::Idle && (current_state.anim_pause_after_movement_ms > 0 || (ctx._rng.range(0, 100) <= CHANCE_FOR_SKIPPING_MOVEMENT_PERCENT))) { - // skip movement - new_state.anim_velocity = 0.0f; - new_state.anim_distance = 0.0f; - if (new_state.anim_pause_after_movement_ms > 0) { - new_state.anim_pause_after_movement_ms -= delta_ms; - if (new_state.anim_pause_after_movement_ms <= 0) { - new_state.anim_pause_after_movement_ms = 0; - } - } - ret.status = anim_custom_process_animation_result_status_t::SkipMovement; - } else { - // moving animation - constexpr float DIR_EPSILON = 1e-3f; - assert(MAX_DISTANCE_PER_MOVEMENT_PART > 0); - const int movement_part = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL ? SMALL_MAX_DISTANCE_PER_MOVEMENT_PART : MAX_DISTANCE_PER_MOVEMENT_PART; - const float fmovement_part = static_cast(movement_part); - bool end_movement = false; - if (current_state.row_state == animation_state_row_t::Idle) { - // start movement - const auto min_movement = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL ? (fmovement_radius / fmovement_part / 2) + 1 : (fmovement_radius / fmovement_part / fmovement_part) + 1; - float max_move_distance = fmovement_radius / fmovement_part; - max_move_distance = max_move_distance <= min_movement ? min_movement : max_move_distance; - - assert(max_move_distance >= 0); - assert(min_movement >= 0); - new_state.anim_distance = static_cast(ctx._rng.range(static_cast(min_movement), static_cast(max_move_distance))); - - if (anim_shm.movement_offset_x >= max_movement_offset_x_right) { - // run against wall, change direction - anim_shm.anim_direction = -1.0; - anim_shm.movement_offset_x = max_movement_offset_x_right; - } else if (anim_shm.movement_offset_x <= max_movement_offset_x_left) { - // run against wall, change direction - anim_shm.anim_direction = 1.0; - anim_shm.movement_offset_x = max_movement_offset_x_left; - } else { - float toward_wall_bias = fabs(wall_distance); - toward_wall_bias = toward_wall_bias >= 1.0f ? 1.0f : toward_wall_bias; - toward_wall_bias = toward_wall_bias <= 0.0f ? 0.0f : toward_wall_bias; - - const int flip_direction_chance = current_config.movement_radius <= MAX_MOVEMENT_RADIUS_SMALL ? SMALL_FLIP_DIRECTION_NEAR_WALL_PERCENT : FLIP_DIRECTION_NEAR_WALL_PERCENT; - - assert(toward_wall_bias >= 0.0f); - // change direction: chance drops at center, changes falloff steeper near walls - const auto dir_chance = static_cast(static_cast(100 - flip_direction_chance + 10) * (1.0f - pow(toward_wall_bias, 1.5f))); - - if (fabs(new_state.anim_last_direction) >= DIR_EPSILON) { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -new_state.anim_last_direction : new_state.anim_last_direction; - } else if (fabs(anim_shm.anim_direction) < DIR_EPSILON) { - // idle: choose random start direction - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; - } else { - if (wall_distance > (100.0f / static_cast(flip_direction_chance))) { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? -1.0f : 1.0f; - } else if (wall_distance < -(100.0f / static_cast(flip_direction_chance))) { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? 1.0f : -1.0f; - } else { - anim_shm.anim_direction = (ctx._rng.range(0, 100) < dir_chance) ? anim_shm.anim_direction : -anim_shm.anim_direction; - } - } - } - // start moving animation - new_state.anim_velocity = anim_shm.anim_direction * static_cast(current_config.movement_speed); - ret = anim_custom_restart_animation(ctx, animation_state_row_t::StartMoving, animation_state_row_t::Moving, animation_state_row_t::EndMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (current_state.row_state == animation_state_row_t::StartMoving) { - if (conditions.process_idle_animation) { - ret = anim_custom_start_or_process_animation(ctx, animation_state_row_t::Moving, animation_state_row_t::EndMoving, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_custom_process_animation_result_status_t::Updated; - } else if (conditions.process_movement_animation) { - ret = anim_custom_restart_animation(ctx, animation_state_row_t::Moving, animation_state_row_t::EndMoving, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_custom_process_animation_result_status_t::Updated; - } - } else if (current_state.row_state == animation_state_row_t::Moving) { - if (conditions.process_movement) { - ret = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - ret.status = anim_custom_process_animation_result_status_t::Moved; - - new_state.anim_distance -= fabs(new_state.anim_velocity); - anim_shm.movement_offset_x += new_state.anim_velocity; - // clamp walking/movement area - if (anim_shm.movement_offset_x > max_movement_offset_x_right) { - anim_shm.movement_offset_x = max_movement_offset_x_right; - ret = anim_custom_restart_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_custom_process_animation_result_status_t::Stop; - } else if (anim_shm.movement_offset_x < max_movement_offset_x_left) { - anim_shm.movement_offset_x = max_movement_offset_x_left; - ret = anim_custom_restart_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_custom_process_animation_result_status_t::Stop; - } - - if (new_state.anim_distance <= 0 || (fabs(new_state.anim_distance) < DIR_EPSILON || fabs(new_state.anim_velocity) < DIR_EPSILON)) { - ret = anim_custom_restart_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - ret.status = anim_custom_process_animation_result_status_t::Stop; - } - - end_movement = ret.status == anim_custom_process_animation_result_status_t::Stop; - } - } else if (current_state.row_state == animation_state_row_t::EndMoving) { - end_movement = true; - } - - if (end_movement) { - if (conditions.process_idle_animation) { - ret = anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - ret = anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - new_state.anim_last_direction = anim_shm.anim_direction; - new_state.anim_velocity = 0.0f; - anim_shm.anim_direction = 0.0; - if (ret.row_state == animation_state_row_t::Idle && new_state.anim_distance <= 0) { - assert(current_config.animation_speed_ms >= 0); - assert(movement_part >= 0); - const auto min_wait = current_config.animation_speed_ms * (current_config.movement_wait_factor / 2); - const auto max_wait = current_config.animation_speed_ms * current_config.movement_wait_factor; - assert(min_wait >= 0); - assert(max_wait >= 0); - new_state.anim_pause_after_movement_ms = static_cast(ctx._rng.range(static_cast(min_wait), static_cast(max_wait))); - ret.status = anim_custom_process_animation_result_status_t::End; - } - } - } - } + break; + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + if (current_frames.feature_moving) { + anim_custom_handle_movement(ctx, input, upd, new_animation_result, new_state, trigger_result, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::StartWorking: + if (current_frames.feature_working) { + if (conditions.go_next_frame) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Working, animation_state_row_t::EndWorking, + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::Working: + if (current_frames.feature_working) { + if (conditions.go_next_frame) { + if (update_shm.cpu_active) { + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); } else { - // movement got disabled, back to idle - if ((current_config.movement_speed <= 0 || current_config.movement_radius <= 0) && conditions.is_moving) { - // back to idle - ret = anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.anim_velocity = 0.0f; - ret.status = anim_custom_process_animation_result_status_t::Stop; - } + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWorking, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); } - - - return ret; + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::EndWorking: + if (current_frames.feature_working) { + if (conditions.go_next_frame) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::Test: + if (current_frames.feature_writing_toggle_frames) { + const bool stop_test_animation = conditions.trigger_test_animation && + current_state.row_state == animation_state_row_t::Test && + conditions.release_test_frame; + if (stop_test_animation) { + // back to idle + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + } else { + if (current_config.idle_animation && conditions.go_next_frame) { + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } + break; + case animation_state_row_t::Idle: { + if (current_config.idle_animation && conditions.go_next_frame) { + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); } - static anim_next_frame_result_t anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - assert(upd.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); - const auto& current_frames = get_current_animation(ctx).custom; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); - - /// @TODO: make animation fsm - - const platform::timestamp_ms_t now = platform::get_current_time_ms(); - const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec*1000; - assert(now >= last_key_pressed_timestamp); - const auto sleep_timeout = now - last_key_pressed_timestamp; - - const bool start_boring = current_frames.feature_boring && SLEEP_BORING_PART > 0 && sleep_timeout >= idle_sleep_timeout_ms/SLEEP_BORING_PART; - - switch (current_state.row_state) { - case animation_state_row_t::Happy: - if (current_frames.feature_writing_happy) { - if (current_frames.feature_writing_toggle_frames && current_frames.happy.end_col == 0) { - // animation for - const bool stop_happy_kpm = current_state.row_state == animation_state_row_t::Happy && conditions.release_frame_after_press && !conditions.process_idle_animation; - if (stop_happy_kpm) { - // back to idle - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - if (new_state.row_state == animation_state_row_t::Happy) { - if (conditions.go_next_frame) { - if (conditions.continue_writing) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::StartWorking, animation_state_row_t::Working, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - if (current_frames.feature_moving) { - anim_custom_handle_movement(ctx, input, upd, new_animation_result, new_state, trigger_result, current_state, current_frames, current_config); - } - break; - case animation_state_row_t::StartWorking: - if (current_frames.feature_working) { - if (conditions.go_next_frame) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Working, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::Working: - if (current_frames.feature_working) { - if (conditions.go_next_frame) { - if (update_shm.cpu_active) { - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } else { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWorking, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::EndWorking: - if (current_frames.feature_working) { - if (conditions.go_next_frame) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::Test: - if (current_frames.feature_writing_toggle_frames) { - const bool stop_test_animation = conditions.trigger_test_animation && current_state.row_state == animation_state_row_t::Test && conditions.release_test_frame; - if (stop_test_animation) { - // back to idle - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - if (current_config.idle_animation && conditions.go_next_frame) { - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - break; - case animation_state_row_t::Idle: { - if (current_config.idle_animation && conditions.go_next_frame) { - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } - if (current_frames.feature_moving) { - // Move Animation - anim_custom_handle_movement(ctx, input, upd, new_animation_result, new_state, trigger_result, current_state, current_frames, current_config); - } + if (current_frames.feature_moving) { + // Move Animation + anim_custom_handle_movement(ctx, input, upd, new_animation_result, new_state, trigger_result, current_state, + current_frames, current_config); + } - if (current_frames.feature_sleep || current_frames.feature_boring) { - // handle sleep - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); - - // Idle Sleep - if (conditions.check_for_idle_sleep) { - if (!is_sleeping_time) { - if (current_state.row_state == animation_state_row_t::Idle) { - // start boring animation - if (current_frames.feature_boring) { - if (start_boring && !current_state.show_boring_animation_once) { - anim_custom_restart_animation(ctx, animation_state_row_t::Boring, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.show_boring_animation_once = true; - new_state.anim_last_direction = 0.0f; - } - } - } - - // idle sleep - if (current_frames.feature_sleep) { - if (sleep_timeout >= idle_sleep_timeout_ms) { - if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - anim_custom_process_animation_result_t animation_result{ .row_state = current_state.row_state }; - if (conditions.is_moving) { - if (conditions.go_next_frame) { - animation_result = anim_custom_start_or_process_animation(ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - animation_result = anim_custom_restart_animation(ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - if (animation_result.row_state == animation_state_row_t::FallASleep || animation_result.row_state == animation_state_row_t::Sleep || animation_result.row_state == animation_state_row_t::WakeUp) { - new_state.anim_last_direction = 0.0f; - new_state.is_idle_sleep = true; - new_state.show_boring_animation_once = false; - } - } else if (current_state.row_state == animation_state_row_t::Sleep) { - if (conditions.go_next_frame) { - // process sleep animation - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { - // wake up - if (conditions.go_next_frame) { - // end current sleep animation - anim_custom_process_animation_result_t animation_result{ .row_state = current_state.row_state }; - if (current_frames.feature_sleep_wake_up) { - animation_result = anim_custom_start_or_process_animation(ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - // no wake up animation - animation_result = anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - if (animation_result.row_state == animation_state_row_t::WakeUp || animation_result.row_state == animation_state_row_t::Idle) { - new_state.is_idle_sleep = false; - } - } - } - } - } - } - } - - // Sleep Mode - if (current_frames.feature_sleep) { - if (current_config.enable_scheduled_sleep) { - if (is_sleeping_time) { - if (new_state.row_state == animation_state_row_t::Idle) { - anim_custom_restart_animation(ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.anim_last_direction = 0.0f; - new_state.is_idle_sleep = false; - } else if (state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - if (conditions.go_next_frame) { - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - // wake up - if (conditions.go_next_frame) { - // end current sleep animation - anim_custom_start_or_process_animation(ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } else { - if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { - // wake up - if (conditions.go_next_frame) { - if (current_frames.feature_sleep_wake_up) { - // end current sleep animation - anim_custom_start_or_process_animation(ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - // no wake up animation - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } - } - } - }break; - case animation_state_row_t::Writing: { - if (current_frames.feature_writing) { - if (conditions.continue_writing) { - if (current_frames.feature_writing_toggle_frames) { - // back to Idle Animation (after writing) - const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; - const bool stop_happy_kpm = current_state.row_state == animation_state_row_t::Happy && conditions.release_frame_after_press && !conditions.process_idle_animation; - if (stop_writing || stop_happy_kpm) { - // back to idle - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - if (conditions.go_next_frame) { - // loop writing animation - const auto animation_result = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - if ((animation_result.status == anim_custom_process_animation_result_status_t::End || animation_result.status == anim_custom_process_animation_result_status_t::Looped) && !conditions.any_key_pressed) { - // end writing - anim_custom_restart_animation(ctx, animation_state_row_t::EndWriting, animation_state_row_t::Idle, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } else { - // cancel writing - if (current_frames.feature_writing_toggle_frames) { - if (conditions.release_frame_after_press) { - anim_custom_restart_animation(ctx, animation_state_row_t::EndWriting, animation_state_row_t::Idle, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - if (conditions.go_next_frame) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWriting, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - }break; - case animation_state_row_t::StartWriting: - if (current_frames.feature_writing) { - if (conditions.go_next_frame) { - const auto animation_result = anim_custom_start_or_process_animation(ctx, animation_state_row_t::Writing, animation_state_row_t::EndWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (animation_result.row_state == animation_state_row_t::Writing || animation_result.row_state == animation_state_row_t::EndWriting) { - // reset release counter after writing is started (for real) - new_state.hold_frame_after_release = true; - new_state.hold_frame_ms = 0; - } - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::EndWriting: - if (current_frames.feature_writing) { - if (conditions.go_next_frame) { - // finish animation - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::Sleep: - if (current_frames.feature_sleep) { - if (conditions.go_next_frame) { - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::FallASleep: - if (current_frames.feature_sleep) { - if (conditions.go_next_frame) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Sleep, animation_state_row_t::WakeUp, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::WakeUp: - if (current_frames.feature_sleep_wake_up) { - if (conditions.go_next_frame) { - // finish animation - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle - new_animation_result, new_state, - current_state, current_frames, current_config); - } + if (current_frames.feature_sleep || current_frames.feature_boring) { + // handle sleep + const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + + // Idle Sleep + if (conditions.check_for_idle_sleep) { + if (!is_sleeping_time) { + if (current_state.row_state == animation_state_row_t::Idle) { + // start boring animation + if (current_frames.feature_boring) { + if (start_boring && !current_state.show_boring_animation_once) { + anim_custom_restart_animation(ctx, animation_state_row_t::Boring, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.show_boring_animation_once = true; + new_state.anim_last_direction = 0.0f; + } + } + } + + // idle sleep + if (current_frames.feature_sleep) { + if (sleep_timeout >= idle_sleep_timeout_ms) { + if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + anim_custom_process_animation_result_t animation_result{.row_state = current_state.row_state}; + if (conditions.is_moving) { + if (conditions.go_next_frame) { + animation_result = anim_custom_start_or_process_animation( + ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, new_animation_result, + new_state, current_state, current_frames, current_config); + } } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); + animation_result = anim_custom_restart_animation( + ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, + animation_state_row_t::WakeUp, new_animation_result, new_state, current_state, current_frames, + current_config); } - break; - case animation_state_row_t::Boring: - if (current_frames.feature_boring) { - if (conditions.go_next_frame) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended - new_animation_result, new_state, - current_state, current_frames, current_config); - if (!start_boring) { - new_state.show_boring_animation_once = false; - } - } + if (animation_result.row_state == animation_state_row_t::FallASleep || + animation_result.row_state == animation_state_row_t::Sleep || + animation_result.row_state == animation_state_row_t::WakeUp) { + new_state.anim_last_direction = 0.0f; + new_state.is_idle_sleep = true; + new_state.show_boring_animation_once = false; } - break; - case animation_state_row_t::StartRunning: - if (current_frames.feature_running) { - if (conditions.go_next_frame_running) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Running, animation_state_row_t::EndRunning, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - break; - case animation_state_row_t::Running: - if (current_frames.feature_running) { - if (conditions.go_next_frame_running) { - if (update_shm.cpu_active) { - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } else { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); + } else if (current_state.row_state == animation_state_row_t::Sleep) { + if (conditions.go_next_frame) { + // process sleep animation + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); } - break; - case animation_state_row_t::EndRunning: - if (current_frames.feature_running) { - if (conditions.go_next_frame_running) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } else { - anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && current_state.is_idle_sleep) { + // wake up + if (conditions.go_next_frame) { + // end current sleep animation + anim_custom_process_animation_result_t animation_result{.row_state = current_state.row_state}; + if (current_frames.feature_sleep_wake_up) { + animation_result = anim_custom_start_or_process_animation( + ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, new_animation_result, + new_state, current_state, current_frames, current_config); + } else { + // no wake up animation + animation_result = anim_custom_start_or_process_animation( + ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + if (animation_result.row_state == animation_state_row_t::WakeUp || + animation_result.row_state == animation_state_row_t::Idle) { + new_state.is_idle_sleep = false; + } } - break; + } + } + } } + } - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + // Sleep Mode + if (current_frames.feature_sleep) { + if (current_config.enable_scheduled_sleep) { + if (is_sleeping_time) { + if (new_state.row_state == animation_state_row_t::Idle) { + anim_custom_restart_animation(ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, + animation_state_row_t::WakeUp, new_animation_result, new_state, + current_state, current_frames, current_config); + new_state.anim_last_direction = 0.0f; + new_state.is_idle_sleep = false; + } else if (state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + if (conditions.go_next_frame) { + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + // wake up + if (conditions.go_next_frame) { + // end current sleep animation + anim_custom_start_or_process_animation(ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } + } + } else { + if (current_state.row_state == animation_state_row_t::Sleep && !new_state.is_idle_sleep) { + // wake up + if (conditions.go_next_frame) { + if (current_frames.feature_sleep_wake_up) { + // end current sleep animation + anim_custom_start_or_process_animation(ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + } else { + // no wake up animation + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, + new_state, current_state, current_frames, current_config); + } + } + } + } + } } - static anim_next_frame_result_t anim_custom_key_pressed_next_frame(animation_context_t& ctx, - animation_state_t& state, - [[maybe_unused]] const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - [[maybe_unused]] const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const auto& input_shm = *input.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); - const auto& current_frames = get_current_animation(ctx).custom; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - bool show_happy = false; - if (current_frames.feature_writing_happy) { - if (input_shm.kpm > 0) { - if (current_config.happy_kpm > 0 && input_shm.kpm >= current_config.happy_kpm) { - show_happy = CUSTOM_HAPPY_CHANCE_PERCENT >= 100 || ctx._rng.range(0, 99) < CUSTOM_HAPPY_CHANCE_PERCENT; - } + } break; + case animation_state_row_t::Writing: { + if (current_frames.feature_writing) { + if (conditions.continue_writing) { + if (current_frames.feature_writing_toggle_frames) { + // back to Idle Animation (after writing) + const bool stop_writing = conditions.is_writing && conditions.release_frame_after_press; + const bool stop_happy_kpm = current_state.row_state == animation_state_row_t::Happy && + conditions.release_frame_after_press && !conditions.process_idle_animation; + if (stop_writing || stop_happy_kpm) { + // back to idle + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } else { + if (conditions.go_next_frame) { + // loop writing animation + const auto animation_result = + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + if ((animation_result.status == anim_custom_process_animation_result_status_t::End || + animation_result.status == anim_custom_process_animation_result_status_t::Looped) && + !conditions.any_key_pressed) { + // end writing + anim_custom_restart_animation(ctx, animation_state_row_t::EndWriting, animation_state_row_t::Idle, + animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); } + } } - - // in Writing mode/start writing - switch (current_state.row_state) { - case animation_state_row_t::StartMoving: - case animation_state_row_t::Moving: - case animation_state_row_t::EndMoving: - // moving is handle in anim_custom_handle_movement - break; - case animation_state_row_t::Happy: - // end happy animation in idle - break; - case animation_state_row_t::Test: - case animation_state_row_t::Idle: - if (current_frames.feature_writing_happy && show_happy) { - // show happy animation (KPM) - anim_custom_restart_animation(ctx, animation_state_row_t::Happy, animation_state_row_t::Writing, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (current_frames.feature_writing) { - // start writing - const auto animation_result = anim_custom_restart_animation(ctx, animation_state_row_t::StartWriting, animation_state_row_t::Writing, animation_state_row_t::EndWriting, - new_animation_result, new_state, - current_state, current_frames, current_config); - if (current_frames.feature_writing_toggle_frames_random && - animation_result.row_state == animation_state_row_t::Writing && (animation_result.status == anim_custom_process_animation_result_status_t::Started || animation_result.status == anim_custom_process_animation_result_status_t::NextAnimationStarted || animation_result.status == anim_custom_process_animation_result_status_t::Looped) && - current_frames.writing.valid && current_frames.writing.start_col >= 0 && current_frames.writing.end_col >= 0) { - new_animation_result.sprite_sheet_col = static_cast(ctx._rng.range(static_cast(current_frames.writing.start_col), static_cast(current_frames.writing.end_col))); - } - if (animation_result.row_state == animation_state_row_t::Writing || animation_result.row_state == animation_state_row_t::EndWriting) { - // reset release counter after writing is started (for real) - new_state.hold_frame_after_release = true; - new_state.hold_frame_ms = 0; - } - } - break; - case animation_state_row_t::StartWriting: - // start end writing and process animation in anim_custom_idle_next_frame - break; - case animation_state_row_t::Writing: - if (current_frames.feature_writing) { - if (current_frames.feature_writing_happy && show_happy && conditions.is_writing) { - // show happy animation (KPM) - anim_custom_restart_animation(ctx, animation_state_row_t::Happy, animation_state_row_t::Writing, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - new_state.hold_frame_ms = 0; - } else if (current_frames.feature_writing_happy && current_state.row_state == animation_state_row_t::Happy) { - // wait for happy animation to end - new_state.hold_frame_ms = 0; - } else if (current_frames.feature_writing_toggle_frames) { - // keep writing - anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); - } else { - // reset hold frame so we can continue writing - new_state.hold_frame_ms = 0; - // start end writing and process animation in anim_custom_idle_next_frame - } - } - break; - case animation_state_row_t::EndWriting: - // start end writing and process animation in anim_custom_idle_next_frame - break; - case animation_state_row_t::FallASleep: - case animation_state_row_t::Sleep: - if (current_state.is_idle_sleep) { - anim_custom_process_animation_result_t animation_result{.row_state = current_state.row_state}; - if (current_frames.feature_sleep_wake_up) { - // wake up, end current sleep animation - animation_result = anim_custom_restart_animation(ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else { - // no wake up animation - animation_result = anim_custom_restart_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - if (animation_result.row_state != animation_state_row_t::Sleep) { - new_state.is_idle_sleep = false; - } - } - case animation_state_row_t::WakeUp: - // process animation in anim_custom_idle_next_frame - break; - case animation_state_row_t::Boring: - // process animation in anim_custom_idle_next_frame - break; - case animation_state_row_t::StartWorking: - case animation_state_row_t::Working: - case animation_state_row_t::EndWorking: - // start end working and process animation in anim_custom_idle_next_frame - break; - case animation_state_row_t::StartRunning: - case animation_state_row_t::Running: - case animation_state_row_t::EndRunning: - // start end running and process animation in anim_custom_idle_next_frame - break; + } else { + // cancel writing + if (current_frames.feature_writing_toggle_frames) { + if (conditions.release_frame_after_press) { + anim_custom_restart_animation(ctx, animation_state_row_t::EndWriting, animation_state_row_t::Idle, + animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + } else { + if (conditions.go_next_frame) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWriting, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + } break; + case animation_state_row_t::StartWriting: + if (current_frames.feature_writing) { + if (conditions.go_next_frame) { + const auto animation_result = anim_custom_start_or_process_animation( + ctx, animation_state_row_t::Writing, animation_state_row_t::EndWriting, new_animation_result, new_state, + current_state, current_frames, current_config); + if (animation_result.row_state == animation_state_row_t::Writing || + animation_result.row_state == animation_state_row_t::EndWriting) { + // reset release counter after writing is started (for real) + new_state.hold_frame_after_release = true; + new_state.hold_frame_ms = 0; + } + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::EndWriting: + if (current_frames.feature_writing) { + if (conditions.go_next_frame) { + // finish animation + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::Sleep: + if (current_frames.feature_sleep) { + if (conditions.go_next_frame) { + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::FallASleep: + if (current_frames.feature_sleep) { + if (conditions.go_next_frame) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Sleep, animation_state_row_t::WakeUp, + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::WakeUp: + if (current_frames.feature_sleep_wake_up) { + if (conditions.go_next_frame) { + // finish animation + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::Boring: + if (current_frames.feature_boring) { + if (conditions.go_next_frame) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, // back to idle, when animation ended + new_animation_result, new_state, current_state, current_frames, + current_config); + if (!start_boring) { + new_state.show_boring_animation_once = false; + } + } + } + break; + case animation_state_row_t::StartRunning: + if (current_frames.feature_running) { + if (conditions.go_next_frame_running) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Running, animation_state_row_t::EndRunning, + new_animation_result, new_state, current_state, current_frames, + current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::Running: + if (current_frames.feature_running) { + if (conditions.go_next_frame_running) { + if (update_shm.cpu_active) { + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + } else { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); } + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + case animation_state_row_t::EndRunning: + if (current_frames.feature_running) { + if (conditions.go_next_frame_running) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } else { + anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } + break; + } - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} +static anim_next_frame_result_t +anim_custom_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& state, + [[maybe_unused]] const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, + [[maybe_unused]] const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const auto& input_shm = *input.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + assert(get_current_animation(ctx).type == animation_t::Type::Custom); + const auto& current_frames = get_current_animation(ctx).custom; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + bool show_happy = false; + if (current_frames.feature_writing_happy) { + if (input_shm.kpm > 0) { + if (current_config.happy_kpm > 0 && input_shm.kpm >= current_config.happy_kpm) { + show_happy = CUSTOM_HAPPY_CHANCE_PERCENT >= 100 || ctx._rng.range(0, 99) < CUSTOM_HAPPY_CHANCE_PERCENT; + } + } + } + + // in Writing mode/start writing + switch (current_state.row_state) { + case animation_state_row_t::StartMoving: + case animation_state_row_t::Moving: + case animation_state_row_t::EndMoving: + // moving is handle in anim_custom_handle_movement + break; + case animation_state_row_t::Happy: + // end happy animation in idle + break; + case animation_state_row_t::Test: + case animation_state_row_t::Idle: + if (current_frames.feature_writing_happy && show_happy) { + // show happy animation (KPM) + anim_custom_restart_animation(ctx, animation_state_row_t::Happy, animation_state_row_t::Writing, + animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + } else if (current_frames.feature_writing) { + // start writing + const auto animation_result = anim_custom_restart_animation( + ctx, animation_state_row_t::StartWriting, animation_state_row_t::Writing, animation_state_row_t::EndWriting, + new_animation_result, new_state, current_state, current_frames, current_config); + if (current_frames.feature_writing_toggle_frames_random && + animation_result.row_state == animation_state_row_t::Writing && + (animation_result.status == anim_custom_process_animation_result_status_t::Started || + animation_result.status == anim_custom_process_animation_result_status_t::NextAnimationStarted || + animation_result.status == anim_custom_process_animation_result_status_t::Looped) && + current_frames.writing.valid && current_frames.writing.start_col >= 0 && + current_frames.writing.end_col >= 0) { + new_animation_result.sprite_sheet_col = + static_cast(ctx._rng.range(static_cast(current_frames.writing.start_col), + static_cast(current_frames.writing.end_col))); + } + if (animation_result.row_state == animation_state_row_t::Writing || + animation_result.row_state == animation_state_row_t::EndWriting) { + // reset release counter after writing is started (for real) + new_state.hold_frame_after_release = true; + new_state.hold_frame_ms = 0; + } + } + break; + case animation_state_row_t::StartWriting: + // start end writing and process animation in anim_custom_idle_next_frame + break; + case animation_state_row_t::Writing: + if (current_frames.feature_writing) { + if (current_frames.feature_writing_happy && show_happy && conditions.is_writing) { + // show happy animation (KPM) + anim_custom_restart_animation(ctx, animation_state_row_t::Happy, animation_state_row_t::Writing, + animation_state_row_t::Idle, new_animation_result, new_state, current_state, + current_frames, current_config); + new_state.hold_frame_ms = 0; + } else if (current_frames.feature_writing_happy && current_state.row_state == animation_state_row_t::Happy) { + // wait for happy animation to end + new_state.hold_frame_ms = 0; + } else if (current_frames.feature_writing_toggle_frames) { + // keep writing + anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); + } else { + // reset hold frame so we can continue writing + new_state.hold_frame_ms = 0; + // start end writing and process animation in anim_custom_idle_next_frame + } } + break; + case animation_state_row_t::EndWriting: + // start end writing and process animation in anim_custom_idle_next_frame + break; + case animation_state_row_t::FallASleep: + case animation_state_row_t::Sleep: + if (current_state.is_idle_sleep) { + anim_custom_process_animation_result_t animation_result{.row_state = current_state.row_state}; + if (current_frames.feature_sleep_wake_up) { + // wake up, end current sleep animation + animation_result = anim_custom_restart_animation( + ctx, animation_state_row_t::WakeUp, animation_state_row_t::Idle, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, current_config); + } else { + // no wake up animation + animation_result = anim_custom_restart_animation(ctx, animation_state_row_t::Idle, new_animation_result, + new_state, current_state, current_frames, current_config); + } + if (animation_result.row_state != animation_state_row_t::Sleep) { + new_state.is_idle_sleep = false; + } + } + case animation_state_row_t::WakeUp: + // process animation in anim_custom_idle_next_frame + break; + case animation_state_row_t::Boring: + // process animation in anim_custom_idle_next_frame + break; + case animation_state_row_t::StartWorking: + case animation_state_row_t::Working: + case animation_state_row_t::EndWorking: + // start end working and process animation in anim_custom_idle_next_frame + break; + case animation_state_row_t::StartRunning: + case animation_state_row_t::Running: + case animation_state_row_t::EndRunning: + // start end running and process animation in anim_custom_idle_next_frame + break; + } + + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} - static anim_next_frame_result_t anim_custom_working_next_frame(animation_context_t& ctx, +static anim_next_frame_result_t anim_custom_working_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, const platform::update::update_context_t& upd, animation_state_t& state, const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - //assert(input.shm != nullptr); - assert(upd.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); - const auto& current_frames = get_current_animation(ctx).custom; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - /// @TODO: use state machine for animation (states) - - if (current_frames.feature_working) { - if (conditions.process_working_animation) { - // toggle frame, show attack animation - const bool above_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage >= current_config.cpu_threshold || update_shm.max_cpu_usage >= current_config.cpu_threshold); - const bool lower_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage < current_config.cpu_threshold && update_shm.max_cpu_usage < current_config.cpu_threshold); - - if (above_threshold) { - if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - anim_custom_restart_animation(ctx, animation_state_row_t::StartWorking, animation_state_row_t::Working, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Start Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } else if (conditions.is_working) { - if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Working, animation_state_row_t::EndWorking, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartWorking || state.row_state == animation_state_row_t::Working)) { - // end working, cool down - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWorking, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndWorking) { - // back to idle - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } else if (lower_threshold) { - // End Working - if (conditions.is_working && current_state.row_state != animation_state_row_t::EndMoving) { - if (conditions.go_next_frame) { - // end working, cool down - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWorking, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } - } - } else { - // Cancel Working - if (conditions.is_working && current_state.row_state != animation_state_row_t::EndMoving && !update_shm.cpu_active) { - // back to idle - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } - } - } - } else { - // Cancel Working - if (conditions.is_working && !update_shm.cpu_active) { - // back to idle - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + // assert(input.shm != nullptr); + assert(upd.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Custom); + const auto& current_frames = get_current_animation(ctx).custom; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + /// @TODO: use state machine for animation (states) + + if (current_frames.feature_working) { + if (conditions.process_working_animation) { + // toggle frame, show attack animation + const bool above_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage >= current_config.cpu_threshold || + update_shm.max_cpu_usage >= current_config.cpu_threshold); + const bool lower_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage < current_config.cpu_threshold && + update_shm.max_cpu_usage < current_config.cpu_threshold); + + if (above_threshold) { + if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + anim_custom_restart_animation(ctx, animation_state_row_t::StartWorking, animation_state_row_t::Working, + animation_state_row_t::EndWorking, new_animation_result, new_state, current_state, current_frames, current_config); - } + BONGOCAT_LOG_VERBOSE("Start Working: %d %d; %d%%", above_threshold, lower_threshold, + update_shm.avg_cpu_usage); + } else if (conditions.is_working) { + if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Working, + animation_state_row_t::EndWorking, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartWorking || + state.row_state == animation_state_row_t::Working)) { + // end working, cool down + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWorking, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndWorking) { + // back to idle + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + } else if (lower_threshold) { + // End Working + if (conditions.is_working && current_state.row_state != animation_state_row_t::EndMoving) { + if (conditions.go_next_frame) { + // end working, cool down + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndWorking, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, + update_shm.avg_cpu_usage); + } + } + } else { + // Cancel Working + if (conditions.is_working && current_state.row_state != animation_state_row_t::EndMoving && + !update_shm.cpu_active) { + // back to idle + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndMoving, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + BONGOCAT_LOG_VERBOSE("Stop Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); + } + } } + } else { + // Cancel Working + if (conditions.is_working && !update_shm.cpu_active) { + // back to idle + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } + } - static anim_next_frame_result_t anim_custom_running_next_frame(animation_context_t& ctx, - const platform::input::input_context_t& input, - const platform::update::update_context_t& upd, - animation_state_t& state, - const animation_trigger_t& trigger) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - assert(ctx.shm != nullptr); - //assert(input.shm != nullptr); - assert(upd.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - const auto& update_shm = *upd.shm; - const auto current_state = state; - const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); - const auto& current_frames = get_current_animation(ctx).custom; - - auto new_animation_result = anim_shm.animation_player_result; - auto new_state = state; - - const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); - - /// @TODO: use state machine for animation (states) - - if (current_frames.feature_running) { - if (conditions.process_running_animation) { - // toggle frame, show running animation - const bool above_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage >= current_config.cpu_threshold || update_shm.max_cpu_usage >= current_config.cpu_threshold); - const bool lower_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage < current_config.cpu_threshold && update_shm.max_cpu_usage < current_config.cpu_threshold); - - if (above_threshold) { - if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { - anim_custom_restart_animation(ctx, animation_state_row_t::StartRunning, animation_state_row_t::Running, animation_state_row_t::EndRunning, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Start Running: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } else if (conditions.is_running) { - if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartRunning) { - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Running, animation_state_row_t::EndRunning, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartRunning || state.row_state == animation_state_row_t::Running)) { - // end running, cool down - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Running, - new_animation_result, new_state, - current_state, current_frames, current_config); - } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndRunning) { - // back to idle - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - } - } - } else if (lower_threshold) { - // End Running - if (conditions.is_running && current_state.row_state != animation_state_row_t::EndRunning) { - if (conditions.go_next_frame_running) { - // end running, cool down - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Stop Running: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } - } - } else { - // Cancel Running - if (conditions.is_running && current_state.row_state != animation_state_row_t::EndRunning && !update_shm.cpu_active) { - // back to idle - anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Idle, - new_animation_result, new_state, - current_state, current_frames, current_config); - BONGOCAT_LOG_VERBOSE("Stop Running: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); - } - } - } - } else { - // Cancel Running - if (conditions.is_running && !update_shm.cpu_active) { - // back to idle - anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, - new_animation_result, new_state, + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} + +static anim_next_frame_result_t anim_custom_running_next_frame(animation_context_t& ctx, + const platform::input::input_context_t& input, + const platform::update::update_context_t& upd, + animation_state_t& state, + const animation_trigger_t& trigger) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + assert(ctx.shm != nullptr); + // assert(input.shm != nullptr); + assert(upd.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + const auto& update_shm = *upd.shm; + const auto current_state = state; + const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; + assert(get_current_animation(ctx).type == animation_t::Type::Custom); + const auto& current_frames = get_current_animation(ctx).custom; + + auto new_animation_result = anim_shm.animation_player_result; + auto new_state = state; + + const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger, current_config); + + /// @TODO: use state machine for animation (states) + + if (current_frames.feature_running) { + if (conditions.process_running_animation) { + // toggle frame, show running animation + const bool above_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage >= current_config.cpu_threshold || + update_shm.max_cpu_usage >= current_config.cpu_threshold); + const bool lower_threshold = current_config.cpu_threshold >= platform::ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage < current_config.cpu_threshold && + update_shm.max_cpu_usage < current_config.cpu_threshold); + + if (above_threshold) { + if (current_state.row_state == animation_state_row_t::Idle || conditions.is_moving) { + anim_custom_restart_animation(ctx, animation_state_row_t::StartRunning, animation_state_row_t::Running, + animation_state_row_t::EndRunning, new_animation_result, new_state, current_state, current_frames, current_config); - } + BONGOCAT_LOG_VERBOSE("Start Running: %d %d; %d%%", above_threshold, lower_threshold, + update_shm.avg_cpu_usage); + } else if (conditions.is_running) { + if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartRunning) { + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Running, + animation_state_row_t::EndRunning, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (!update_shm.cpu_active && (current_state.row_state == animation_state_row_t::StartRunning || + state.row_state == animation_state_row_t::Running)) { + // end running, cool down + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, + animation_state_row_t::Running, new_animation_result, new_state, + current_state, current_frames, current_config); + } else if (!update_shm.cpu_active && current_state.row_state == animation_state_row_t::EndRunning) { + // back to idle + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); + } } - - return anim_update_animation_state(anim_shm, state, - new_animation_result, new_state, - current_animation_result, current_state, - current_config); + } else if (lower_threshold) { + // End Running + if (conditions.is_running && current_state.row_state != animation_state_row_t::EndRunning) { + if (conditions.go_next_frame_running) { + // end running, cool down + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + BONGOCAT_LOG_VERBOSE("Stop Running: %d %d; %d%%", above_threshold, lower_threshold, + update_shm.avg_cpu_usage); + } + } + } else { + // Cancel Running + if (conditions.is_running && current_state.row_state != animation_state_row_t::EndRunning && + !update_shm.cpu_active) { + // back to idle + anim_custom_start_or_process_animation(ctx, animation_state_row_t::EndRunning, animation_state_row_t::Idle, + new_animation_result, new_state, current_state, current_frames, + current_config); + BONGOCAT_LOG_VERBOSE("Stop Running: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); + } + } + } + } else { + // Cancel Running + if (conditions.is_running && !update_shm.cpu_active) { + // back to idle + anim_custom_start_or_process_animation(ctx, animation_state_row_t::Idle, new_animation_result, new_state, + current_state, current_frames, current_config); } + } + + return anim_update_animation_state(anim_shm, state, new_animation_result, new_state, current_animation_result, + current_state, current_config); +} #endif - static anim_next_frame_result_t anim_handle_idle_animation(animation_context_t& ctx, - [[maybe_unused]] const platform::input::input_context_t& input, - [[maybe_unused]] const platform::update::update_context_t& upd, - animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { - using namespace assets; - - // read-only config - assert(ctx._local_copy_config != nullptr); - //const config::config_t& current_config = *ctx._local_copy_config; - - assert(input.shm != nullptr); - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - //auto& animation_player_data = anim_shm.animation_player_data; - //const int current_frame = animation_player_data.frame_index; - //const int current_row = animation_player_data.sprite_sheet_row; - //const animation_state_row_t current_row_state = state.row_state; - //const int anim_index = anim_shm.anim_index; - - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: { +static anim_next_frame_result_t +anim_handle_idle_animation(animation_context_t& ctx, [[maybe_unused]] const platform::input::input_context_t& input, + [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, + const anim_handle_key_press_result_t& trigger_result) { + using namespace assets; + + // read-only config + assert(ctx._local_copy_config != nullptr); + // const config::config_t& current_config = *ctx._local_copy_config; + + assert(input.shm != nullptr); + assert(ctx.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + // auto& animation_player_data = anim_shm.animation_player_data; + // const int current_frame = animation_player_data.frame_index; + // const int current_row = animation_player_data.sprite_sheet_row; + // const animation_state_row_t current_row_state = state.row_state; + // const int anim_index = anim_shm.anim_index; + + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: { #ifdef FEATURE_BONGOCAT_EMBEDDED_ASSETS - return anim_bongocat_idle_next_frame(ctx, input, upd, state, trigger_result); + return anim_bongocat_idle_next_frame(ctx, input, upd, state, trigger_result); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Dm: { + } break; + case config::config_animation_sprite_sheet_layout_t::Dm: { #ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS - return anim_dm_idle_next_frame(ctx, input, upd, state, trigger_result); + return anim_dm_idle_next_frame(ctx, input, upd, state, trigger_result); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: { + } break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: { #ifdef FEATURE_PKMN_EMBEDDED_ASSETS - return anim_pkmn_idle_next_frame(ctx, input, upd, state, trigger_result); + return anim_pkmn_idle_next_frame(ctx, input, upd, state, trigger_result); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::MsAgent: { + } break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: { #ifdef FEATURE_MS_AGENT_EMBEDDED_ASSETS - return anim_ms_agent_idle_next_frame(ctx, input, upd, state, trigger_result); + return anim_ms_agent_idle_next_frame(ctx, input, upd, state, trigger_result); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Custom: { + } break; + case config::config_animation_sprite_sheet_layout_t::Custom: { #ifdef FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION - return anim_custom_idle_next_frame(ctx, input, upd, state, trigger_result); + return anim_custom_idle_next_frame(ctx, input, upd, state, trigger_result); #endif - }break; - } - return {}; - } + } break; + } + return {}; +} - static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_session_t& animation_trigger_ctx, animation_state_t& state, const animation_trigger_t& trigger) { - using namespace assets; - - assert(animation_trigger_ctx._input != nullptr); - assert(animation_trigger_ctx._input->shm != nullptr); - assert(animation_trigger_ctx._update != nullptr); - assert(animation_trigger_ctx._update->shm != nullptr); - animation_context_t& ctx = animation_trigger_ctx.anim; - [[maybe_unused]] const platform::input::input_context_t& input = *animation_trigger_ctx._input; - [[maybe_unused]] const platform::update::update_context_t& upd = *animation_trigger_ctx._update; - - // read-only config - //assert(ctx._local_copy_config != nullptr); - //assert(ctx.shm != nullptr); - //assert(animation_trigger_ctx._config != nullptr); - //const config::config_t& current_config = *ctx._local_copy_config; - - assert(input.shm != nullptr); - assert(upd.shm != nullptr); - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - //const auto current_state = state; - //const auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - - anim_next_frame_result_t update_frame_result{}; - - if (has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::CpuUpdate)) { - // handle working animation - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: - break; - case config::config_animation_sprite_sheet_layout_t::Dm: { +static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_session_t& animation_trigger_ctx, + animation_state_t& state, + const animation_trigger_t& trigger) { + using namespace assets; + + assert(animation_trigger_ctx._input != nullptr); + assert(animation_trigger_ctx._input->shm != nullptr); + assert(animation_trigger_ctx._update != nullptr); + assert(animation_trigger_ctx._update->shm != nullptr); + animation_context_t& ctx = animation_trigger_ctx.anim; + [[maybe_unused]] const platform::input::input_context_t& input = *animation_trigger_ctx._input; + [[maybe_unused]] const platform::update::update_context_t& upd = *animation_trigger_ctx._update; + + // read-only config + // assert(ctx._local_copy_config != nullptr); + // assert(ctx.shm != nullptr); + // assert(animation_trigger_ctx._config != nullptr); + // const config::config_t& current_config = *ctx._local_copy_config; + + assert(input.shm != nullptr); + assert(upd.shm != nullptr); + assert(ctx.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + // const auto current_state = state; + // const auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + + anim_next_frame_result_t update_frame_result{}; + + if (has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::CpuUpdate)) { + // handle working animation + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: + break; + case config::config_animation_sprite_sheet_layout_t::Dm: { #ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS - update_frame_result = anim_dm_working_next_frame(ctx, input, upd, state, trigger); + update_frame_result = anim_dm_working_next_frame(ctx, input, upd, state, trigger); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: - break; - case config::config_animation_sprite_sheet_layout_t::MsAgent: { + } break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: + break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: { #ifdef FEATURE_MS_AGENT_EMBEDDED_ASSETS - /// @TODO: add working animation for MS agents - //update_frame_result = anim_ms_agent_working_next_frame(ctx, input, upd, state, trigger); + /// @TODO: add working animation for MS agents + // update_frame_result = anim_ms_agent_working_next_frame(ctx, input, upd, state, trigger); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Custom: { + } break; + case config::config_animation_sprite_sheet_layout_t::Custom: { #ifdef FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION - update_frame_result = anim_custom_working_next_frame(ctx, input, upd, state, trigger); + update_frame_result = anim_custom_working_next_frame(ctx, input, upd, state, trigger); #endif - }break; - } - // handle running animation - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: - break; - case config::config_animation_sprite_sheet_layout_t::Dm: - break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: - break; - case config::config_animation_sprite_sheet_layout_t::MsAgent: - break; - case config::config_animation_sprite_sheet_layout_t::Custom: { + } break; + } + // handle running animation + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: + break; + case config::config_animation_sprite_sheet_layout_t::Dm: + break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: + break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: + break; + case config::config_animation_sprite_sheet_layout_t::Custom: { #ifdef FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION - update_frame_result = anim_custom_running_next_frame(ctx, input, upd, state, trigger); + update_frame_result = anim_custom_running_next_frame(ctx, input, upd, state, trigger); #endif - }break; - } - BONGOCAT_LOG_VERBOSE("CPU update detected - switching to frame %d (%zu)", update_frame_result.new_col, trigger.anim_cause); - } - - // handle key press animation - if (has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress)) { - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: { + } break; + } + BONGOCAT_LOG_VERBOSE("CPU update detected - switching to frame %d (%zu)", update_frame_result.new_col, + trigger.anim_cause); + } + + // handle key press animation + if (has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress)) { + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: { #ifdef FEATURE_BONGOCAT_EMBEDDED_ASSETS - update_frame_result = anim_bongocat_key_pressed_next_frame(ctx, state, input, upd, trigger); + update_frame_result = anim_bongocat_key_pressed_next_frame(ctx, state, input, upd, trigger); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Dm: { + } break; + case config::config_animation_sprite_sheet_layout_t::Dm: { #ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS - update_frame_result = anim_dm_key_pressed_next_frame(ctx, input, upd, state, trigger); + update_frame_result = anim_dm_key_pressed_next_frame(ctx, input, upd, state, trigger); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: { + } break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: { #ifdef FEATURE_PKMN_EMBEDDED_ASSETS - update_frame_result = anim_pkmn_key_pressed_next_frame(ctx, input, upd, state, trigger); + update_frame_result = anim_pkmn_key_pressed_next_frame(ctx, input, upd, state, trigger); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::MsAgent: { + } break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: { #ifdef FEATURE_MS_AGENT_EMBEDDED_ASSETS - update_frame_result = anim_ms_agent_key_pressed_next_frame(ctx, state, input, upd, trigger); + update_frame_result = anim_ms_agent_key_pressed_next_frame(ctx, state, input, upd, trigger); #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Custom: { + } break; + case config::config_animation_sprite_sheet_layout_t::Custom: { #ifdef FEATURE_CUSTOM_SPRITE_SHEETS_ANIMATION - update_frame_result = anim_custom_key_pressed_next_frame(ctx, state, input, upd, trigger); + update_frame_result = anim_custom_key_pressed_next_frame(ctx, state, input, upd, trigger); #endif - }break; - } - BONGOCAT_LOG_VERBOSE("Key press detected - switching to frame %d (%zu)", update_frame_result.new_col, trigger.anim_cause); - } - - return { .trigger = trigger, .update_frame_result = update_frame_result}; + } break; } + BONGOCAT_LOG_VERBOSE("Key press detected - switching to frame %d (%zu)", update_frame_result.new_col, + trigger.anim_cause); + } - static bool anim_update_state(animation_session_t& animation_trigger_ctx, animation_state_t& state, const animation_trigger_t& trigger) { - assert(animation_trigger_ctx._input); - platform::input::input_context_t& input = *animation_trigger_ctx._input; - platform::update::update_context_t& upd = *animation_trigger_ctx._update; - animation_context_t& ctx = animation_trigger_ctx.anim; - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - bool ret = false; - { - state.frame_delta_ms_counter += state.frame_time_ms; - state.update_delta_ms_counter += state.frame_time_ms; - - anim_handle_key_press_result_t trigger_result; - anim_next_frame_result_t idle_update_result; - bool hold_frame = false; - bool key_pressed = false; - { - platform::LockGuard input_guard (input.input_lock); - platform::LockGuard update_guard (upd.update_lock); - trigger_result = anim_handle_animation_trigger(animation_trigger_ctx, state, trigger); - idle_update_result = anim_handle_idle_animation(ctx, input, upd, state, trigger_result); - key_pressed = has_flag(trigger_result.trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress) && trigger_result.trigger.any_key_press_counter > 0; - hold_frame = key_pressed || idle_update_result.frame_changed || idle_update_result.rerender; - } - ret = idle_update_result.rerender || trigger_result.update_frame_result.rerender; - if (key_pressed) { - BONGOCAT_LOG_VERBOSE("Trigger key press animation"); - } - if (idle_update_result.frame_changed) { - BONGOCAT_LOG_VERBOSE("Trigger frame changed"); - } - if (idle_update_result.moved) { - BONGOCAT_LOG_VERBOSE("Trigger movement animation"); - } - if (idle_update_result.rerender) { - BONGOCAT_LOG_VERBOSE("Trigger rerender"); - } - - if (!state.hold_frame_after_release && hold_frame) { - state.hold_frame_after_release = true; - } - if (state.hold_frame_after_release && (!key_pressed && !hold_frame) && state.hold_frame_ms > current_config.keypress_duration_ms) { - state.hold_frame_after_release = false; - state.hold_frame_ms = 0; - } - if (state.hold_frame_after_release) { - state.hold_frame_ms += state.frame_time_ms; - } - } + return {.trigger = trigger, .update_frame_result = update_frame_result}; +} - return ret; +static bool anim_update_state(animation_session_t& animation_trigger_ctx, animation_state_t& state, + const animation_trigger_t& trigger) { + assert(animation_trigger_ctx._input); + platform::input::input_context_t& input = *animation_trigger_ctx._input; + platform::update::update_context_t& upd = *animation_trigger_ctx._update; + animation_context_t& ctx = animation_trigger_ctx.anim; + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + bool ret = false; + { + state.frame_delta_ms_counter += state.frame_time_ms; + state.update_delta_ms_counter += state.frame_time_ms; + + anim_handle_key_press_result_t trigger_result; + anim_next_frame_result_t idle_update_result; + bool hold_frame = false; + bool key_pressed = false; + { + platform::LockGuard input_guard(input.input_lock); + platform::LockGuard update_guard(upd.update_lock); + trigger_result = anim_handle_animation_trigger(animation_trigger_ctx, state, trigger); + idle_update_result = anim_handle_idle_animation(ctx, input, upd, state, trigger_result); + key_pressed = has_flag(trigger_result.trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress) && + trigger_result.trigger.any_key_press_counter > 0; + hold_frame = key_pressed || idle_update_result.frame_changed || idle_update_result.rerender; + } + ret = idle_update_result.rerender || trigger_result.update_frame_result.rerender; + if (key_pressed) { + BONGOCAT_LOG_VERBOSE("Trigger key press animation"); + } + if (idle_update_result.frame_changed) { + BONGOCAT_LOG_VERBOSE("Trigger frame changed"); + } + if (idle_update_result.moved) { + BONGOCAT_LOG_VERBOSE("Trigger movement animation"); + } + if (idle_update_result.rerender) { + BONGOCAT_LOG_VERBOSE("Trigger rerender"); } - // ============================================================================= - // ANIMATION THREAD MANAGEMENT MODULE - // ============================================================================= - - static void anim_init_state(animation_context_t& ctx, animation_state_t& state) { - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - assert(current_config.fps > 0); - - state.hold_frame_ms = 0; - state.frame_delta_ms_counter = 0; - state.update_delta_ms_counter = 0; - state.frame_time_ns = 1000000000LL / current_config.fps; - state.frame_time_ms = state.frame_time_ns/1000000LL; - state.last_frame_update_ms = platform::get_current_time_ms(); - state.row_state = animation_state_row_t::Idle; + if (!state.hold_frame_after_release && hold_frame) { + state.hold_frame_after_release = true; } + if (state.hold_frame_after_release && (!key_pressed && !hold_frame) && + state.hold_frame_ms > current_config.keypress_duration_ms) { + state.hold_frame_after_release = false; + state.hold_frame_ms = 0; + } + if (state.hold_frame_after_release) { + state.hold_frame_ms += state.frame_time_ms; + } + } + + return ret; +} - static void cleanup_anim_thread(void* arg) { - assert(arg); - animation_session_t& trigger_ctx = *static_cast(arg); +// ============================================================================= +// ANIMATION THREAD MANAGEMENT MODULE +// ============================================================================= + +static void anim_init_state(animation_context_t& ctx, animation_state_t& state) { + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + assert(current_config.fps > 0); + + state.hold_frame_ms = 0; + state.frame_delta_ms_counter = 0; + state.update_delta_ms_counter = 0; + state.frame_time_ns = 1000000000LL / current_config.fps; + state.frame_time_ms = state.frame_time_ns / 1000000LL; + state.last_frame_update_ms = platform::get_current_time_ms(); + state.row_state = animation_state_row_t::Idle; +} - atomic_store(&trigger_ctx.anim._animation_running, false); +static void cleanup_anim_thread(void *arg) { + assert(arg); + animation_session_t& trigger_ctx = *static_cast(arg); - trigger_ctx.anim.config_updated.notify_all(); + atomic_store(&trigger_ctx.anim._animation_running, false); - BONGOCAT_LOG_INFO("Animation thread cleanup completed (via pthread_cancel)"); - } + trigger_ctx.anim.config_updated.notify_all(); + + BONGOCAT_LOG_INFO("Animation thread cleanup completed (via pthread_cancel)"); +} - static void *anim_thread(void *arg) { - using namespace assets; - - assert(arg); - auto& trigger_ctx = *static_cast(arg); - - // sanity checks - assert(trigger_ctx._config != nullptr); - assert(trigger_ctx._input != nullptr); - assert(trigger_ctx._update != nullptr); - assert(trigger_ctx._configs_reloaded_cond != nullptr); - assert(trigger_ctx.anim.shm != nullptr); - assert(trigger_ctx.trigger_efd._fd >= 0); - assert(trigger_ctx.render_efd._fd >= 0); - assert(trigger_ctx.anim.update_config_efd._fd >= 0); - - // init animation state - animation_state_t state; - { - platform::LockGuard guard (trigger_ctx.anim.anim_lock); - animation_context_t& ctx = trigger_ctx.anim; - - assert(ctx.shm != nullptr); - //assert(input.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - //const auto& input_shm = *input.shm; - //auto& current_state = state; - auto& current_animation_result = anim_shm.animation_player_result; - [[maybe_unused]] const int anim_index = anim_shm.anim_index; - - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - - anim_init_state(ctx, state); - - // setup animation player - switch (current_config.animation_sprite_sheet_layout) { - case config::config_animation_sprite_sheet_layout_t::None: - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: +static void *anim_thread(void *arg) { + using namespace assets; + + assert(arg); + auto& trigger_ctx = *static_cast(arg); + + // sanity checks + assert(trigger_ctx._config != nullptr); + assert(trigger_ctx._input != nullptr); + assert(trigger_ctx._update != nullptr); + assert(trigger_ctx._configs_reloaded_cond != nullptr); + assert(trigger_ctx.anim.shm != nullptr); + assert(trigger_ctx.trigger_efd._fd >= 0); + assert(trigger_ctx.render_efd._fd >= 0); + assert(trigger_ctx.anim.update_config_efd._fd >= 0); + + // init animation state + animation_state_t state; + { + platform::LockGuard guard(trigger_ctx.anim.anim_lock); + animation_context_t& ctx = trigger_ctx.anim; + + assert(ctx.shm != nullptr); + // assert(input.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + // const auto& input_shm = *input.shm; + // auto& current_state = state; + auto& current_animation_result = anim_shm.animation_player_result; + [[maybe_unused]] const int anim_index = anim_shm.anim_index; + + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + + anim_init_state(ctx, state); + + // setup animation player + switch (current_config.animation_sprite_sheet_layout) { + case config::config_animation_sprite_sheet_layout_t::None: + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: #ifdef FEATURE_BONGOCAT_EMBEDDED_ASSETS - current_animation_result.sprite_sheet_col = current_config.idle_frame; - current_animation_result.sprite_sheet_row = BONGOCAT_SPRITE_SHEET_ROW; - state.animations_index = 0; - state.row_state = animation_state_row_t::Idle; + current_animation_result.sprite_sheet_col = current_config.idle_frame; + current_animation_result.sprite_sheet_row = BONGOCAT_SPRITE_SHEET_ROW; + state.animations_index = 0; + state.row_state = animation_state_row_t::Idle; #endif - break; - case config::config_animation_sprite_sheet_layout_t::Dm: + break; + case config::config_animation_sprite_sheet_layout_t::Dm: #ifdef FEATURE_ENABLE_DM_EMBEDDED_ASSETS - current_animation_result.sprite_sheet_col = current_config.idle_frame; - current_animation_result.sprite_sheet_row = DM_SPRITE_SHEET_ROW; - state.animations_index = 0; - state.row_state = animation_state_row_t::Idle; + current_animation_result.sprite_sheet_col = current_config.idle_frame; + current_animation_result.sprite_sheet_row = DM_SPRITE_SHEET_ROW; + state.animations_index = 0; + state.row_state = animation_state_row_t::Idle; #endif - break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: + break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: #ifdef FEATURE_PKMN_EMBEDDED_ASSETS - current_animation_result.sprite_sheet_col = current_config.idle_frame; - current_animation_result.sprite_sheet_row = PKMN_SPRITE_SHEET_ROW; - state.animations_index = 0; - state.row_state = animation_state_row_t::Idle; + current_animation_result.sprite_sheet_col = current_config.idle_frame; + current_animation_result.sprite_sheet_row = PKMN_SPRITE_SHEET_ROW; + state.animations_index = 0; + state.row_state = animation_state_row_t::Idle; #endif - break; - case config::config_animation_sprite_sheet_layout_t::MsAgent: { + break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: { #ifdef FEATURE_MS_AGENT_EMBEDDED_ASSETS - assert(anim_shm.anim_index >= 0); - const ms_agent_animation_indices_t animation_indices = get_ms_agent_animation_indices(static_cast(anim_shm.anim_index)); - current_animation_result.sprite_sheet_col = current_config.idle_frame; - current_animation_result.sprite_sheet_row = MS_AGENT_SPRITE_SHEET_ROW_IDLE; - state.start_col_index = animation_indices.start_index_frame_idle; - state.end_col_index = animation_indices.end_index_frame_idle; - state.row_state = animation_state_row_t::Idle; + assert(anim_shm.anim_index >= 0); + const ms_agent_animation_indices_t animation_indices = + get_ms_agent_animation_indices(static_cast(anim_shm.anim_index)); + current_animation_result.sprite_sheet_col = current_config.idle_frame; + current_animation_result.sprite_sheet_row = MS_AGENT_SPRITE_SHEET_ROW_IDLE; + state.start_col_index = animation_indices.start_index_frame_idle; + state.end_col_index = animation_indices.end_index_frame_idle; + state.row_state = animation_state_row_t::Idle; #endif - }break; - case config::config_animation_sprite_sheet_layout_t::Custom: { + } break; + case config::config_animation_sprite_sheet_layout_t::Custom: { #ifdef FEATURE_MISC_EMBEDDED_ASSETS - assert(anim_shm.anim_index >= 0); - if (static_cast(anim_shm.anim_index) <= MAX_MISC_ANIM_INDEX) { - const custom_animation_settings_t custom_columns = get_misc_sprite_sheet_columns(static_cast(anim_shm.anim_index)); - current_animation_result.sprite_sheet_col = current_config.idle_frame; - current_animation_result.sprite_sheet_row = CUSTOM_SPRITE_SHEET_ROW_IDLE; - state.start_col_index = 0; - state.end_col_index = custom_columns.idle_frames > 0 ? custom_columns.idle_frames-1 : 0; - state.row_state = animation_state_row_t::Idle; - } + assert(anim_shm.anim_index >= 0); + if (static_cast(anim_shm.anim_index) <= MAX_MISC_ANIM_INDEX) { + const custom_animation_settings_t custom_columns = + get_misc_sprite_sheet_columns(static_cast(anim_shm.anim_index)); + current_animation_result.sprite_sheet_col = current_config.idle_frame; + current_animation_result.sprite_sheet_row = CUSTOM_SPRITE_SHEET_ROW_IDLE; + state.start_col_index = 0; + state.end_col_index = custom_columns.idle_frames > 0 ? custom_columns.idle_frames - 1 : 0; + state.row_state = animation_state_row_t::Idle; + } #endif #ifdef FEATURE_CUSTOM_SPRITE_SHEETS - assert(anim_shm.anim_index >= 0); - if (static_cast(anim_shm.anim_index) == CUSTOM_ANIM_INDEX) { - current_animation_result.sprite_sheet_col = current_config.idle_frame; - current_animation_result.sprite_sheet_row = current_config.custom_sprite_sheet_settings.idle_row_index >= 0 ? current_config.custom_sprite_sheet_settings.idle_row_index : static_cast(CUSTOM_SPRITE_SHEET_ROW_IDLE); - state.start_col_index = 0; - state.end_col_index = current_config.custom_sprite_sheet_settings.idle_frames > 0 ? current_config.custom_sprite_sheet_settings.idle_frames-1 : 0; - state.row_state = animation_state_row_t::Idle; - } + assert(anim_shm.anim_index >= 0); + if (static_cast(anim_shm.anim_index) == CUSTOM_ANIM_INDEX) { + current_animation_result.sprite_sheet_col = current_config.idle_frame; + current_animation_result.sprite_sheet_row = current_config.custom_sprite_sheet_settings.idle_row_index >= 0 + ? current_config.custom_sprite_sheet_settings.idle_row_index + : static_cast(CUSTOM_SPRITE_SHEET_ROW_IDLE); + state.start_col_index = 0; + state.end_col_index = current_config.custom_sprite_sheet_settings.idle_frames > 0 + ? current_config.custom_sprite_sheet_settings.idle_frames - 1 + : 0; + state.row_state = animation_state_row_t::Idle; + } #endif - }break; - } - } + } break; + } + } - BONGOCAT_LOG_DEBUG("Animation thread main loop started"); + BONGOCAT_LOG_DEBUG("Animation thread main loop started"); - // trigger initial render - platform::wayland::request_render(trigger_ctx); + // trigger initial render + platform::wayland::request_render(trigger_ctx); - pthread_cleanup_push(cleanup_anim_thread, arg); + pthread_cleanup_push(cleanup_anim_thread, arg); - // local thread context - timespec next_frame_time{}; - clock_gettime(CLOCK_MONOTONIC, &next_frame_time); + // local thread context + timespec next_frame_time{}; + clock_gettime(CLOCK_MONOTONIC, &next_frame_time); - atomic_store(&trigger_ctx.anim._animation_running, true); - while (atomic_load(&trigger_ctx.anim._animation_running)) { - pthread_testcancel(); // optional, but makes cancellation more responsive + atomic_store(&trigger_ctx.anim._animation_running, true); + while (atomic_load(&trigger_ctx.anim._animation_running)) { + pthread_testcancel(); // optional, but makes cancellation more responsive - animation_context_t& ctx = trigger_ctx.anim; + animation_context_t& ctx = trigger_ctx.anim; - // read from config - platform::time_ms_t timeout_ms; - int32_t fps = 1; - { - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; + // read from config + platform::time_ms_t timeout_ms; + int32_t fps = 1; + { + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; - fps = current_config.fps; - timeout_ms = current_config.fps > 0 ? 1000 / current_config.fps / 3 : POOL_MIN_TIMEOUT_MS; - timeout_ms = timeout_ms < POOL_MIN_TIMEOUT_MS ? POOL_MIN_TIMEOUT_MS : timeout_ms; - timeout_ms = timeout_ms > POOL_MAX_TIMEOUT_MS ? POOL_MAX_TIMEOUT_MS : timeout_ms; - } + fps = current_config.fps; + timeout_ms = current_config.fps > 0 ? 1000 / current_config.fps / 3 : POOL_MIN_TIMEOUT_MS; + timeout_ms = timeout_ms < POOL_MIN_TIMEOUT_MS ? POOL_MIN_TIMEOUT_MS : timeout_ms; + timeout_ms = timeout_ms > POOL_MAX_TIMEOUT_MS ? POOL_MAX_TIMEOUT_MS : timeout_ms; + } - trigger_animation_cause_mask_t triggered_anim_cause = trigger_animation_cause_mask_t::NONE; - int any_key_press_counter = 0; - - bool reload_config = false; - uint64_t new_gen{atomic_load(trigger_ctx._config_generation)}; - - /// event poll - constexpr size_t fds_update_config_index = 0; - constexpr size_t fds_animation_trigger_index = 1; - constexpr nfds_t fds_count = 2; - pollfd fds[fds_count] = { - { .fd = trigger_ctx.anim.update_config_efd._fd, .events = POLLIN, .revents = 0 }, - { .fd = trigger_ctx.trigger_efd._fd, .events = POLLIN, .revents = 0 }, - }; - - assert(timeout_ms <= INT_MAX); - const int poll_result = poll(fds, fds_count, static_cast(timeout_ms)); - if (poll_result < 0) { - if (errno == EINTR) continue; // Interrupted by signal - BONGOCAT_LOG_ERROR("animation: Poll error: %s", strerror(errno)); - break; - } - if (poll_result > 0) { - // cancel pooling (when not running anymore) - if (!atomic_load(&ctx._animation_running)) { - // draining pools - for (size_t i = 0; i < fds_count; i++) { - platform::drain_event(fds[i], MAX_ATTEMPTS); - } - break; - } + trigger_animation_cause_mask_t triggered_anim_cause = trigger_animation_cause_mask_t::NONE; + int any_key_press_counter = 0; - // Handle config update - if (fds[fds_update_config_index].revents & POLLIN) { - BONGOCAT_LOG_DEBUG("animation: Receive update config event"); - platform::drain_event(fds[fds_update_config_index], MAX_ATTEMPTS, "update config eventfd"); - reload_config = new_gen > 0; - } + bool reload_config = false; + uint64_t new_gen{atomic_load(trigger_ctx._config_generation)}; - // animation trigger event - if (fds[fds_animation_trigger_index].revents & POLLIN) { - BONGOCAT_LOG_VERBOSE("Receive animation trigger event"); - uint64_t u; - ssize_t rc; - int attempts = 0; - assert(MAX_ATTEMPTS <= INT_MAX); - while ((rc = read(trigger_ctx.trigger_efd._fd, &u, sizeof(u))) == sizeof(u) && - attempts < static_cast(MAX_ATTEMPTS)) - { - attempts++; - auto cause = static_cast(u); - switch (cause) { - case trigger_animation_cause_mask_t::NONE: - break; - case trigger_animation_cause_mask_t::Init: - triggered_anim_cause = flag_add(triggered_anim_cause, cause); - break; - case trigger_animation_cause_mask_t::KeyPress: - any_key_press_counter++; - triggered_anim_cause = flag_add(triggered_anim_cause, cause); - break; - case trigger_animation_cause_mask_t::IdleUpdate: - triggered_anim_cause = flag_add(triggered_anim_cause, cause); - break; - case trigger_animation_cause_mask_t::CpuUpdate: - triggered_anim_cause = flag_add(triggered_anim_cause, cause); - break; - case trigger_animation_cause_mask_t::UpdateConfig: - triggered_anim_cause = flag_add(triggered_anim_cause, cause); - break; - case trigger_animation_cause_mask_t::Timeout: - triggered_anim_cause = flag_add(triggered_anim_cause, cause); - break; - } - } - if (rc < 0) { - check_errno("animation trigger eventfd"); - } - - BONGOCAT_LOG_VERBOSE("animation trigger: %zu", triggered_anim_cause); - } else { - triggered_anim_cause = flag_add(triggered_anim_cause, trigger_animation_cause_mask_t::Timeout); - triggered_anim_cause = flag_add(triggered_anim_cause, trigger_animation_cause_mask_t::CpuUpdate); - } - } + /// event poll + constexpr size_t fds_update_config_index = 0; + constexpr size_t fds_animation_trigger_index = 1; + constexpr nfds_t fds_count = 2; + pollfd fds[fds_count] = { + {.fd = trigger_ctx.anim.update_config_efd._fd, .events = POLLIN, .revents = 0}, + {.fd = trigger_ctx.trigger_efd._fd, .events = POLLIN, .revents = 0}, + }; - // Update Animations - { - platform::LockGuard guard (trigger_ctx.anim.anim_lock); - assert(ctx.shm != nullptr); - const bool frame_changed = anim_update_state(trigger_ctx, state, { - .anim_cause = triggered_anim_cause, - .any_key_press_counter = any_key_press_counter, - }); - if (frame_changed) { - uint64_t u = 1; - if (write(trigger_ctx.render_efd._fd, &u, sizeof(uint64_t)) >= 0) { - BONGOCAT_LOG_VERBOSE("animation: Write animation render event"); - } else { - BONGOCAT_LOG_ERROR("animation: Failed to write to notify pipe in animation: %s", strerror(errno)); - } - } + assert(timeout_ms <= INT_MAX); + const int poll_result = poll(fds, fds_count, static_cast(timeout_ms)); + if (poll_result < 0) { + if (errno == EINTR) + continue; // Interrupted by signal + BONGOCAT_LOG_ERROR("animation: Poll error: %s", strerror(errno)); + break; + } + if (poll_result > 0) { + // cancel pooling (when not running anymore) + if (!atomic_load(&ctx._animation_running)) { + // draining pools + for (size_t i = 0; i < fds_count; i++) { + platform::drain_event(fds[i], MAX_ATTEMPTS); + } + break; + } + + // Handle config update + if (fds[fds_update_config_index].revents & POLLIN) { + BONGOCAT_LOG_DEBUG("animation: Receive update config event"); + platform::drain_event(fds[fds_update_config_index], MAX_ATTEMPTS, "update config eventfd"); + reload_config = new_gen > 0; + } + + // animation trigger event + if (fds[fds_animation_trigger_index].revents & POLLIN) { + BONGOCAT_LOG_VERBOSE("Receive animation trigger event"); + uint64_t u; + ssize_t rc; + int attempts = 0; + assert(MAX_ATTEMPTS <= INT_MAX); + while ((rc = read(trigger_ctx.trigger_efd._fd, &u, sizeof(u))) == sizeof(u) && + attempts < static_cast(MAX_ATTEMPTS)) { + attempts++; + auto cause = static_cast(u); + switch (cause) { + case trigger_animation_cause_mask_t::NONE: + break; + case trigger_animation_cause_mask_t::Init: + triggered_anim_cause = flag_add(triggered_anim_cause, cause); + break; + case trigger_animation_cause_mask_t::KeyPress: + any_key_press_counter++; + triggered_anim_cause = flag_add(triggered_anim_cause, cause); + break; + case trigger_animation_cause_mask_t::IdleUpdate: + triggered_anim_cause = flag_add(triggered_anim_cause, cause); + break; + case trigger_animation_cause_mask_t::CpuUpdate: + triggered_anim_cause = flag_add(triggered_anim_cause, cause); + break; + case trigger_animation_cause_mask_t::UpdateConfig: + triggered_anim_cause = flag_add(triggered_anim_cause, cause); + break; + case trigger_animation_cause_mask_t::Timeout: + triggered_anim_cause = flag_add(triggered_anim_cause, cause); + break; + } + } + if (rc < 0) { + check_errno("animation trigger eventfd"); + } - // Advance next frame time by exactly one frame - next_frame_time.tv_nsec += state.frame_time_ns; - while (next_frame_time.tv_nsec >= 1000000000LL) { - next_frame_time.tv_nsec -= 1000000000LL; - next_frame_time.tv_sec += 1; - } + BONGOCAT_LOG_VERBOSE("animation trigger: %zu", triggered_anim_cause); + } else { + triggered_anim_cause = flag_add(triggered_anim_cause, trigger_animation_cause_mask_t::Timeout); + triggered_anim_cause = flag_add(triggered_anim_cause, trigger_animation_cause_mask_t::CpuUpdate); + } + } - timespec now{}; - clock_gettime(CLOCK_MONOTONIC, &now); - - // If we're already past the next frame time, catch up (skip missed frames) - if ((now.tv_sec > next_frame_time.tv_sec) || - (now.tv_sec == next_frame_time.tv_sec && now.tv_nsec > next_frame_time.tv_nsec)) { - // Skip ahead until next_frame_time is >= now - do { - next_frame_time.tv_nsec += state.frame_time_ns; - while (next_frame_time.tv_nsec >= 1000000000LL) { - next_frame_time.tv_nsec -= 1000000000LL; - next_frame_time.tv_sec += 1; - } - } while ((now.tv_sec > next_frame_time.tv_sec) || - (now.tv_sec == next_frame_time.tv_sec && now.tv_nsec > next_frame_time.tv_nsec)); - - state.time_until_next_frame_ms = 0; - //BONGOCAT_LOG_VERBOSE("Animation skipped frame(s) to catch up"); - } else { - // Sleep until the next frame using absolute time - const auto sec_diff = next_frame_time.tv_sec - now.tv_sec; - const auto nsec_diff = next_frame_time.tv_nsec - now.tv_nsec; - state.time_until_next_frame_ms = static_cast(sec_diff * 1000L + (nsec_diff + 999999LL) / 1000000LL); - - if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, nullptr) != 0) { - // Interrupted, just continue - } - } + // Update Animations + { + platform::LockGuard guard(trigger_ctx.anim.anim_lock); + assert(ctx.shm != nullptr); + const bool frame_changed = anim_update_state(trigger_ctx, state, + { + .anim_cause = triggered_anim_cause, + .any_key_press_counter = any_key_press_counter, + }); + if (frame_changed) { + uint64_t u = 1; + if (write(trigger_ctx.render_efd._fd, &u, sizeof(uint64_t)) >= 0) { + BONGOCAT_LOG_VERBOSE("animation: Write animation render event"); + } else { + BONGOCAT_LOG_ERROR("animation: Failed to write to notify pipe in animation: %s", strerror(errno)); + } + } + + // Advance next frame time by exactly one frame + next_frame_time.tv_nsec += state.frame_time_ns; + while (next_frame_time.tv_nsec >= 1000000000LL) { + next_frame_time.tv_nsec -= 1000000000LL; + next_frame_time.tv_sec += 1; + } + + timespec now{}; + clock_gettime(CLOCK_MONOTONIC, &now); + + // If we're already past the next frame time, catch up (skip missed frames) + if ((now.tv_sec > next_frame_time.tv_sec) || + (now.tv_sec == next_frame_time.tv_sec && now.tv_nsec > next_frame_time.tv_nsec)) { + // Skip ahead until next_frame_time is >= now + do { + next_frame_time.tv_nsec += state.frame_time_ns; + while (next_frame_time.tv_nsec >= 1000000000LL) { + next_frame_time.tv_nsec -= 1000000000LL; + next_frame_time.tv_sec += 1; + } + } while ((now.tv_sec > next_frame_time.tv_sec) || + (now.tv_sec == next_frame_time.tv_sec && now.tv_nsec > next_frame_time.tv_nsec)); + + state.time_until_next_frame_ms = 0; + // BONGOCAT_LOG_VERBOSE("Animation skipped frame(s) to catch up"); + } else { + // Sleep until the next frame using absolute time + const auto sec_diff = next_frame_time.tv_sec - now.tv_sec; + const auto nsec_diff = next_frame_time.tv_nsec - now.tv_nsec; + state.time_until_next_frame_ms = + static_cast(sec_diff * 1000L + (nsec_diff + 999999LL) / 1000000LL); + + if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, nullptr) != 0) { + // Interrupted, just continue + } + } - // Update variables from config in case FPS changed - state.frame_time_ns = (fps > 0) ? 1000000000LL / fps : 1000000000LL / DEFAULT_FPS; - state.frame_time_ms = state.frame_time_ns / 1000000LL; - } + // Update variables from config in case FPS changed + state.frame_time_ns = (fps > 0) ? 1000000000LL / fps : 1000000000LL / DEFAULT_FPS; + state.frame_time_ms = state.frame_time_ns / 1000000LL; + } - // handle update config - if (reload_config) { - assert(trigger_ctx._config_generation != nullptr); - assert(trigger_ctx._configs_reloaded_cond != nullptr); - assert(trigger_ctx._config != nullptr); + // handle update config + if (reload_config) { + assert(trigger_ctx._config_generation != nullptr); + assert(trigger_ctx._configs_reloaded_cond != nullptr); + assert(trigger_ctx._config != nullptr); + + update_config(ctx, *trigger_ctx._config, new_gen); + + // wait for reload config to be done (all configs) + const int rc = trigger_ctx._configs_reloaded_cond->timedwait( + [&] { return atomic_load(trigger_ctx._config_generation) >= new_gen; }, COND_RELOAD_CONFIGS_TIMEOUT_MS); + if (rc == ETIMEDOUT) { + BONGOCAT_LOG_WARNING("animation: Timed out waiting for reload eventfd: %s", strerror(errno)); + } + if constexpr (features::Debug) { + if (atomic_load(&trigger_ctx.anim.config_seen_generation) < atomic_load(trigger_ctx._config_generation)) { + BONGOCAT_LOG_VERBOSE( + "animation: trigger_ctx.anim.config_seen_generation < trigger_ctx._config_generation; %d < %d", + atomic_load(&trigger_ctx.anim.config_seen_generation), atomic_load(trigger_ctx._config_generation)); + } + } + // assert(atomic_load(&trigger_ctx.anim.config_seen_generation) >= atomic_load(trigger_ctx._config_generation)); + atomic_store(&trigger_ctx.anim.config_seen_generation, atomic_load(trigger_ctx._config_generation)); + BONGOCAT_LOG_INFO("animation: Animation config reloaded (gen=%u)", new_gen); + } + } - update_config(ctx, *trigger_ctx._config, new_gen); + // Will run only on normal return + pthread_cleanup_pop(1); // 1 = call cleanup even if not canceled - // wait for reload config to be done (all configs) - const int rc = trigger_ctx._configs_reloaded_cond->timedwait([&] { - return atomic_load(trigger_ctx._config_generation) >= new_gen; - }, COND_RELOAD_CONFIGS_TIMEOUT_MS); - if (rc == ETIMEDOUT) { - BONGOCAT_LOG_WARNING("animation: Timed out waiting for reload eventfd: %s", strerror(errno)); - } - if constexpr (features::Debug) { - if (atomic_load(&trigger_ctx.anim.config_seen_generation) < atomic_load(trigger_ctx._config_generation)) { - BONGOCAT_LOG_VERBOSE("animation: trigger_ctx.anim.config_seen_generation < trigger_ctx._config_generation; %d < %d", atomic_load(&trigger_ctx.anim.config_seen_generation), atomic_load(trigger_ctx._config_generation)); - } - } - //assert(atomic_load(&trigger_ctx.anim.config_seen_generation) >= atomic_load(trigger_ctx._config_generation)); - atomic_store(&trigger_ctx.anim.config_seen_generation, atomic_load(trigger_ctx._config_generation)); - BONGOCAT_LOG_INFO("animation: Animation config reloaded (gen=%u)", new_gen); - } - } + // done when callback cleanup_anim_context + // cleanup_anim_context(arg); - // Will run only on normal return - pthread_cleanup_pop(1); // 1 = call cleanup even if not canceled + BONGOCAT_LOG_INFO("Animation thread main loop exited"); - // done when callback cleanup_anim_context - //cleanup_anim_context(arg); + return nullptr; +} - BONGOCAT_LOG_INFO("Animation thread main loop exited"); +// ============================================================================= +// PUBLIC API IMPLEMENTATION +// ============================================================================= + +bongocat_error_t start(animation_session_t& trigger_ctx, platform::input::input_context_t& input, + platform::update::update_context_t& upd, const config::config_t& config, + platform::CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { + BONGOCAT_LOG_INFO("Starting animation thread"); + + // Initialize shared memory for local config + trigger_ctx.anim._local_copy_config = platform::make_allocated_mmap(); + if (!trigger_ctx.anim._local_copy_config.ptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(trigger_ctx.anim._local_copy_config != nullptr); + update_config(trigger_ctx.anim, config, atomic_load(&config_generation)); + + // set extern/global references + trigger_ctx._input = &input; + trigger_ctx._update = &upd; + trigger_ctx._config = &config; + trigger_ctx._configs_reloaded_cond = &configs_reloaded_cond; + trigger_ctx._config_generation = &config_generation; + atomic_store(&trigger_ctx.ready, true); + trigger_ctx.init_cond.notify_all(); + + trigger_ctx._configs_reloaded_cond->notify_all(); + + // start animation thread + const int result = pthread_create(&trigger_ctx.anim._anim_thread, nullptr, anim_thread, &trigger_ctx); + if (result != 0) { + BONGOCAT_LOG_ERROR("Failed to create animation thread: %s", strerror(result)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + BONGOCAT_LOG_DEBUG("Animation thread started successfully"); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - return nullptr; - } +void trigger(animation_session_t& trigger_ctx, trigger_animation_cause_mask_t cause) { + const auto u = static_cast(cause); + if (write(trigger_ctx.trigger_efd._fd, &u, sizeof(uint64_t)) >= 0) { + BONGOCAT_LOG_VERBOSE("Write animation trigger event: %zu", cause); + } else { + BONGOCAT_LOG_ERROR("Failed to write to notify pipe in animation: %s", strerror(errno)); + } +} - // ============================================================================= - // PUBLIC API IMPLEMENTATION - // ============================================================================= +void trigger_update_config(animation_session_t& trigger_ctx, const config::config_t& config, + uint64_t config_generation) { + // assert(trigger_ctx.anim._local_copy_config != nullptr); + // assert(trigger_ctx.anim.shm != nullptr); + + trigger_ctx._config = &config; + if (write(trigger_ctx.anim.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { + BONGOCAT_LOG_VERBOSE("Write animation trigger update config"); + } else { + BONGOCAT_LOG_ERROR("Failed to write to notify pipe in animation: %s", strerror(errno)); + } +} - bongocat_error_t start(animation_session_t& trigger_ctx, platform::input::input_context_t& input, platform::update::update_context_t& upd, const config::config_t& config, platform::CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { - BONGOCAT_LOG_INFO("Starting animation thread"); +BONGOCAT_NODISCARD static int rand_animation_index(animation_context_t& ctx, const config::config_t& config) { + using namespace assets; + assert(ctx._local_copy_config != nullptr); + assert(ctx.shm != nullptr); + platform::random_xoshiro128& rng = ctx._rng; - // Initialize shared memory for local config - trigger_ctx.anim._local_copy_config = platform::make_allocated_mmap(); - if (!trigger_ctx.anim._local_copy_config.ptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + if (config.randomize_index) { + if constexpr (features::EnableLazyLoadAssets) { + switch (config.animation_sprite_sheet_layout) { + case config::config_animation_sprite_sheet_layout_t::None: + return config.animation_index; + case config::config_animation_sprite_sheet_layout_t::Bongocat: + assert(BONGOCAT_ANIM_COUNT <= INT32_MAX && BONGOCAT_ANIM_COUNT <= UINT32_MAX); + return BONGOCAT_ANIM_COUNT > 0 ? static_cast(rng.range(0, BONGOCAT_ANIM_COUNT - 1)) : 0; + case config::config_animation_sprite_sheet_layout_t::Dm: + switch (ctx.shm->anim_dm_set) { + case config::config_animation_dm_set_t::None: + return config.animation_index; + case config::config_animation_dm_set_t::min_dm: + assert(MIN_DM_ANIM_COUNT <= INT32_MAX && MIN_DM_ANIM_COUNT <= UINT32_MAX); + return MIN_DM_ANIM_COUNT > 0 ? static_cast(rng.range(0, MIN_DM_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::dm: + assert(DM_ANIM_COUNT <= INT32_MAX && DM_ANIM_COUNT <= UINT32_MAX); + return DM_ANIM_COUNT > 0 ? static_cast(rng.range(0, DM_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::dm20: + assert(DM20_ANIM_COUNT <= INT32_MAX && DM20_ANIM_COUNT <= UINT32_MAX); + return DM20_ANIM_COUNT > 0 ? static_cast(rng.range(0, DM20_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::dmx: + assert(DMX_ANIM_COUNT <= INT32_MAX && DMX_ANIM_COUNT <= UINT32_MAX); + return DMX_ANIM_COUNT > 0 ? static_cast(rng.range(0, DMX_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::pen: + assert(PEN_ANIM_COUNT <= INT32_MAX && PEN_ANIM_COUNT <= UINT32_MAX); + return PEN_ANIM_COUNT > 0 ? static_cast(rng.range(0, PEN_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::pen20: + assert(PEN20_ANIM_COUNT <= INT32_MAX && PEN20_ANIM_COUNT <= UINT32_MAX); + return PEN20_ANIM_COUNT > 0 ? static_cast(rng.range(0, PEN20_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::dmc: + assert(DMC_ANIM_COUNT <= INT32_MAX && DMC_ANIM_COUNT <= UINT32_MAX); + return DMC_ANIM_COUNT > 0 ? static_cast(rng.range(0, DMC_ANIM_COUNT - 1)) : 0; + case config::config_animation_dm_set_t::dmall: + assert(DMALL_ANIM_COUNT <= INT32_MAX && DMALL_ANIM_COUNT <= UINT32_MAX); + return DMALL_ANIM_COUNT > 0 ? static_cast(rng.range(0, DMALL_ANIM_COUNT - 1)) : 0; } - assert(trigger_ctx.anim._local_copy_config != nullptr); - update_config(trigger_ctx.anim, config, atomic_load(&config_generation)); - - // set extern/global references - trigger_ctx._input = &input; - trigger_ctx._update = &upd; - trigger_ctx._config = &config; - trigger_ctx._configs_reloaded_cond = &configs_reloaded_cond; - trigger_ctx._config_generation = &config_generation; - atomic_store(&trigger_ctx.ready, true); - trigger_ctx.init_cond.notify_all(); - - trigger_ctx._configs_reloaded_cond->notify_all(); - - // start animation thread - const int result = pthread_create(&trigger_ctx.anim._anim_thread, nullptr, anim_thread, &trigger_ctx); - if (result != 0) { - BONGOCAT_LOG_ERROR("Failed to create animation thread: %s", strerror(result)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; + break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: + assert(PKMN_ANIM_COUNT <= INT32_MAX && PKMN_ANIM_COUNT <= UINT32_MAX); + return PKMN_ANIM_COUNT > 0 ? static_cast(rng.range(0, PKMN_ANIM_COUNT - 1)) : 0; + case config::config_animation_sprite_sheet_layout_t::MsAgent: + assert(MS_AGENTS_ANIM_COUNT <= INT32_MAX && MS_AGENTS_ANIM_COUNT <= UINT32_MAX); + return MS_AGENTS_ANIM_COUNT > 0 ? static_cast(rng.range(0, MS_AGENTS_ANIM_COUNT - 1)) : 0; + case config::config_animation_sprite_sheet_layout_t::Custom: + switch (ctx.shm->anim_custom_set) { + case config::config_animation_custom_set_t::None: + break; + case config::config_animation_custom_set_t::misc: + assert(MISC_ANIM_COUNT <= INT32_MAX && MISC_ANIM_COUNT <= UINT32_MAX); + return MISC_ANIM_COUNT > 0 ? static_cast(rng.range(0, MISC_ANIM_COUNT - 1)) : 0; + case config::config_animation_custom_set_t::pmd: + assert(PMD_ANIM_COUNT <= INT32_MAX && PMD_ANIM_COUNT <= UINT32_MAX); + return PMD_ANIM_COUNT > 0 ? static_cast(rng.range(0, PMD_ANIM_COUNT - 1)) : 0; + case config::config_animation_custom_set_t::custom: + if (config.animation_index == CUSTOM_ANIM_INDEX) { + return config.animation_index; + } + break; } - - BONGOCAT_LOG_DEBUG("Animation thread started successfully"); - return bongocat_error_t::BONGOCAT_SUCCESS; + } } - - - void trigger(animation_session_t& trigger_ctx, trigger_animation_cause_mask_t cause) { - const auto u = static_cast(cause); - if (write(trigger_ctx.trigger_efd._fd, &u, sizeof(uint64_t)) >= 0) { - BONGOCAT_LOG_VERBOSE("Write animation trigger event: %zu", cause); - } else { - BONGOCAT_LOG_ERROR("Failed to write to notify pipe in animation: %s", strerror(errno)); + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + switch (config.animation_sprite_sheet_layout) { + case config::config_animation_sprite_sheet_layout_t::None: + return config.animation_index; + case config::config_animation_sprite_sheet_layout_t::Bongocat: + assert(ctx.shm->bongocat_anims.count > 0); + assert(ctx.shm->bongocat_anims.count <= INT32_MAX && ctx.shm->bongocat_anims.count <= UINT32_MAX); + return ctx.shm->bongocat_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->bongocat_anims.count - 1))) + : 0; + case config::config_animation_sprite_sheet_layout_t::Dm: + switch (ctx.shm->anim_dm_set) { + case config::config_animation_dm_set_t::None: + return config.animation_index; + case config::config_animation_dm_set_t::min_dm: + assert(ctx.shm->min_dm_anims.count > 0); + assert(ctx.shm->min_dm_anims.count <= INT32_MAX && ctx.shm->min_dm_anims.count <= UINT32_MAX); + return ctx.shm->min_dm_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->min_dm_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::dm: + assert(ctx.shm->dm_anims.count > 0); + assert(ctx.shm->dm_anims.count <= INT32_MAX && ctx.shm->dm_anims.count <= UINT32_MAX); + return ctx.shm->dm_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->dm_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::dm20: + assert(ctx.shm->dm20_anims.count > 0); + assert(ctx.shm->dm20_anims.count <= INT32_MAX && ctx.shm->dm20_anims.count <= UINT32_MAX); + return ctx.shm->dm20_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->dm20_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::dmx: + assert(ctx.shm->dmx_anims.count > 0); + assert(ctx.shm->dmx_anims.count <= INT32_MAX && ctx.shm->dmx_anims.count <= UINT32_MAX); + return ctx.shm->dmx_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->dmx_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::pen: + assert(ctx.shm->pen_anims.count > 0); + assert(ctx.shm->pen_anims.count <= INT32_MAX && ctx.shm->pen_anims.count <= UINT32_MAX); + return ctx.shm->pen_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->pen_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::pen20: + assert(ctx.shm->pen20_anims.count > 0); + assert(ctx.shm->pen20_anims.count <= INT32_MAX && ctx.shm->pen20_anims.count <= UINT32_MAX); + return ctx.shm->pen20_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->pen20_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::dmc: + assert(ctx.shm->dmc_anims.count > 0); + assert(ctx.shm->dmc_anims.count <= INT32_MAX && ctx.shm->dmc_anims.count <= UINT32_MAX); + return ctx.shm->dmc_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->dmc_anims.count - 1))) + : 0; + case config::config_animation_dm_set_t::dmall: + assert(ctx.shm->dmall_anims.count > 0); + assert(ctx.shm->dmall_anims.count <= INT32_MAX && ctx.shm->dmall_anims.count <= UINT32_MAX); + return ctx.shm->dmall_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->dmall_anims.count - 1))) + : 0; } - } - - void trigger_update_config(animation_session_t& trigger_ctx, const config::config_t& config, uint64_t config_generation) { - //assert(trigger_ctx.anim._local_copy_config != nullptr); - //assert(trigger_ctx.anim.shm != nullptr); - - trigger_ctx._config = &config; - if (write(trigger_ctx.anim.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { - BONGOCAT_LOG_VERBOSE("Write animation trigger update config"); - } else { - BONGOCAT_LOG_ERROR("Failed to write to notify pipe in animation: %s", strerror(errno)); + break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: + assert(ctx.shm->pkmn_anims.count > 0); + assert(ctx.shm->pkmn_anims.count <= INT32_MAX && ctx.shm->pkmn_anims.count <= UINT32_MAX); + return ctx.shm->pkmn_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->pkmn_anims.count - 1))) + : 0; + case config::config_animation_sprite_sheet_layout_t::MsAgent: + assert(ctx.shm->ms_anims.count > 0); + assert(ctx.shm->ms_anims.count <= INT32_MAX && ctx.shm->ms_anims.count <= UINT32_MAX); + return ctx.shm->ms_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->ms_anims.count - 1))) + : 0; + case config::config_animation_sprite_sheet_layout_t::Custom: + switch (ctx.shm->anim_custom_set) { + case config::config_animation_custom_set_t::None: + break; + case config::config_animation_custom_set_t::misc: + assert(ctx.shm->misc_anims.count > 0); + assert(ctx.shm->misc_anims.count <= INT32_MAX && ctx.shm->misc_anims.count <= UINT32_MAX); + return ctx.shm->misc_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->misc_anims.count - 1))) + : 0; + case config::config_animation_custom_set_t::pmd: + assert(ctx.shm->pmd_anims.count > 0); + assert(ctx.shm->pmd_anims.count <= INT32_MAX && ctx.shm->pmd_anims.count <= UINT32_MAX); + return ctx.shm->pmd_anims.count > 0 + ? static_cast(rng.range(0, static_cast(ctx.shm->pmd_anims.count - 1))) + : 0; + case config::config_animation_custom_set_t::custom: + if (config.animation_index == CUSTOM_ANIM_INDEX) { + return config.animation_index; + } + break; } + } } + } - BONGOCAT_NODISCARD static int rand_animation_index(animation_context_t& ctx, const config::config_t& config) { - using namespace assets; - assert(ctx._local_copy_config != nullptr); - assert(ctx.shm != nullptr); - platform::random_xoshiro128& rng = ctx._rng; - - if (config.randomize_index) { - if constexpr (features::EnableLazyLoadAssets) { - switch (config.animation_sprite_sheet_layout) { - case config::config_animation_sprite_sheet_layout_t::None: - return config.animation_index; - case config::config_animation_sprite_sheet_layout_t::Bongocat: - assert(BONGOCAT_ANIM_COUNT <= INT32_MAX && BONGOCAT_ANIM_COUNT <= UINT32_MAX); - return BONGOCAT_ANIM_COUNT > 0 ? static_cast(rng.range(0, BONGOCAT_ANIM_COUNT-1)) : 0; - case config::config_animation_sprite_sheet_layout_t::Dm: - switch (ctx.shm->anim_dm_set) { - case config::config_animation_dm_set_t::None: - return config.animation_index; - case config::config_animation_dm_set_t::min_dm: - assert(MIN_DM_ANIM_COUNT <= INT32_MAX && MIN_DM_ANIM_COUNT<= UINT32_MAX); - return MIN_DM_ANIM_COUNT > 0 ? static_cast(rng.range(0, MIN_DM_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::dm: - assert(DM_ANIM_COUNT <= INT32_MAX && DM_ANIM_COUNT <= UINT32_MAX); - return DM_ANIM_COUNT > 0 ? static_cast(rng.range(0, DM_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::dm20: - assert(DM20_ANIM_COUNT <= INT32_MAX && DM20_ANIM_COUNT <= UINT32_MAX); - return DM20_ANIM_COUNT > 0 ? static_cast(rng.range(0, DM20_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::dmx: - assert(DMX_ANIM_COUNT<= INT32_MAX && DMX_ANIM_COUNT <= UINT32_MAX); - return DMX_ANIM_COUNT > 0 ? static_cast(rng.range(0, DMX_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::pen: - assert(PEN_ANIM_COUNT <= INT32_MAX && PEN_ANIM_COUNT <= UINT32_MAX); - return PEN_ANIM_COUNT > 0 ? static_cast(rng.range(0, PEN_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::pen20: - assert(PEN20_ANIM_COUNT <= INT32_MAX && PEN20_ANIM_COUNT <= UINT32_MAX); - return PEN20_ANIM_COUNT > 0 ? static_cast(rng.range(0, PEN20_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::dmc: - assert(DMC_ANIM_COUNT <= INT32_MAX && DMC_ANIM_COUNT <= UINT32_MAX); - return DMC_ANIM_COUNT > 0 ? static_cast(rng.range(0, DMC_ANIM_COUNT-1)) : 0; - case config::config_animation_dm_set_t::dmall: - assert(DMALL_ANIM_COUNT <= INT32_MAX && DMALL_ANIM_COUNT <= UINT32_MAX); - return DMALL_ANIM_COUNT > 0 ? static_cast(rng.range(0, DMALL_ANIM_COUNT-1)) : 0; - } - break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: - assert(PKMN_ANIM_COUNT <= INT32_MAX && PKMN_ANIM_COUNT <= UINT32_MAX); - return PKMN_ANIM_COUNT > 0 ? static_cast(rng.range(0, PKMN_ANIM_COUNT-1)) : 0; - case config::config_animation_sprite_sheet_layout_t::MsAgent: - assert(MS_AGENTS_ANIM_COUNT <= INT32_MAX && MS_AGENTS_ANIM_COUNT <= UINT32_MAX); - return MS_AGENTS_ANIM_COUNT > 0 ? static_cast(rng.range(0, MS_AGENTS_ANIM_COUNT-1)) : 0; - case config::config_animation_sprite_sheet_layout_t::Custom: - switch (ctx.shm->anim_custom_set) { - case config::config_animation_custom_set_t::None: - break; - case config::config_animation_custom_set_t::misc: - assert(MISC_ANIM_COUNT <= INT32_MAX && MISC_ANIM_COUNT <= UINT32_MAX); - return MISC_ANIM_COUNT > 0 ? static_cast(rng.range(0, MISC_ANIM_COUNT-1)) : 0; - case config::config_animation_custom_set_t::pmd: - assert(PMD_ANIM_COUNT <= INT32_MAX && PMD_ANIM_COUNT <= UINT32_MAX); - return PMD_ANIM_COUNT > 0 ? static_cast(rng.range(0, PMD_ANIM_COUNT-1)) : 0; - case config::config_animation_custom_set_t::custom: - if (config.animation_index == CUSTOM_ANIM_INDEX) { - return config.animation_index; - } - break; - } - } - } - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - switch (config.animation_sprite_sheet_layout) { - case config::config_animation_sprite_sheet_layout_t::None: - return config.animation_index; - case config::config_animation_sprite_sheet_layout_t::Bongocat: - assert(ctx.shm->bongocat_anims.count > 0); - assert(ctx.shm->bongocat_anims.count <= INT32_MAX && ctx.shm->bongocat_anims.count <= UINT32_MAX); - return ctx.shm->bongocat_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->bongocat_anims.count-1))) : 0; - case config::config_animation_sprite_sheet_layout_t::Dm: - switch (ctx.shm->anim_dm_set) { - case config::config_animation_dm_set_t::None: - return config.animation_index; - case config::config_animation_dm_set_t::min_dm: - assert(ctx.shm->min_dm_anims.count > 0); - assert(ctx.shm->min_dm_anims.count <= INT32_MAX && ctx.shm->min_dm_anims.count <= UINT32_MAX); - return ctx.shm->min_dm_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->min_dm_anims.count-1))) : 0; - case config::config_animation_dm_set_t::dm: - assert(ctx.shm->dm_anims.count > 0); - assert(ctx.shm->dm_anims.count <= INT32_MAX && ctx.shm->dm_anims.count <= UINT32_MAX); - return ctx.shm->dm_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->dm_anims.count-1))) : 0; - case config::config_animation_dm_set_t::dm20: - assert(ctx.shm->dm20_anims.count > 0); - assert(ctx.shm->dm20_anims.count <= INT32_MAX && ctx.shm->dm20_anims.count <= UINT32_MAX); - return ctx.shm->dm20_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->dm20_anims.count-1))) : 0; - case config::config_animation_dm_set_t::dmx: - assert(ctx.shm->dmx_anims.count > 0); - assert(ctx.shm->dmx_anims.count <= INT32_MAX && ctx.shm->dmx_anims.count <= UINT32_MAX); - return ctx.shm->dmx_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->dmx_anims.count-1))) : 0; - case config::config_animation_dm_set_t::pen: - assert(ctx.shm->pen_anims.count > 0); - assert(ctx.shm->pen_anims.count <= INT32_MAX && ctx.shm->pen_anims.count <= UINT32_MAX); - return ctx.shm->pen_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->pen_anims.count-1))) : 0; - case config::config_animation_dm_set_t::pen20: - assert(ctx.shm->pen20_anims.count > 0); - assert(ctx.shm->pen20_anims.count <= INT32_MAX && ctx.shm->pen20_anims.count <= UINT32_MAX); - return ctx.shm->pen20_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->pen20_anims.count-1))) : 0; - case config::config_animation_dm_set_t::dmc: - assert(ctx.shm->dmc_anims.count > 0); - assert(ctx.shm->dmc_anims.count <= INT32_MAX && ctx.shm->dmc_anims.count <= UINT32_MAX); - return ctx.shm->dmc_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->dmc_anims.count-1))) : 0; - case config::config_animation_dm_set_t::dmall: - assert(ctx.shm->dmall_anims.count > 0); - assert(ctx.shm->dmall_anims.count <= INT32_MAX && ctx.shm->dmall_anims.count <= UINT32_MAX); - return ctx.shm->dmall_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->dmall_anims.count-1))) : 0; - } - break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: - assert(ctx.shm->pkmn_anims.count > 0); - assert(ctx.shm->pkmn_anims.count <= INT32_MAX && ctx.shm->pkmn_anims.count <= UINT32_MAX); - return ctx.shm->pkmn_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->pkmn_anims.count-1))) : 0; - case config::config_animation_sprite_sheet_layout_t::MsAgent: - assert(ctx.shm->ms_anims.count > 0); - assert(ctx.shm->ms_anims.count <= INT32_MAX && ctx.shm->ms_anims.count <= UINT32_MAX); - return ctx.shm->ms_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->ms_anims.count-1))) : 0; - case config::config_animation_sprite_sheet_layout_t::Custom: - switch(ctx.shm->anim_custom_set) { - case config::config_animation_custom_set_t::None: - break; - case config::config_animation_custom_set_t::misc: - assert(ctx.shm->misc_anims.count > 0); - assert(ctx.shm->misc_anims.count <= INT32_MAX && ctx.shm->misc_anims.count <= UINT32_MAX); - return ctx.shm->misc_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->misc_anims.count-1))) : 0; - case config::config_animation_custom_set_t::pmd: - assert(ctx.shm->pmd_anims.count > 0); - assert(ctx.shm->pmd_anims.count <= INT32_MAX && ctx.shm->pmd_anims.count <= UINT32_MAX); - return ctx.shm->pmd_anims.count > 0 ? static_cast(rng.range(0, static_cast(ctx.shm->pmd_anims.count-1))) : 0; - case config::config_animation_custom_set_t::custom: - if (config.animation_index == CUSTOM_ANIM_INDEX) { - return config.animation_index; - } - break; - } - } - } - } + return config.animation_index; +} - return config.animation_index; +static void update_config_reload_sprite_sheet(animation_context_t& ctx) { + using namespace assets; + assert(ctx._local_copy_config != nullptr); + + platform::LockGuard guard(ctx.anim_lock); + const auto old_anim_type = ctx.shm->anim_type; + const auto old_anim_dm_set = ctx.shm->anim_dm_set; + const auto old_anim_custom_set = ctx.shm->anim_custom_set; + const auto old_anim_index = ctx.shm->anim_index; + + ctx.shm->anim_type = ctx._local_copy_config->animation_sprite_sheet_layout; + ctx.shm->anim_dm_set = ctx._local_copy_config->animation_dm_set; + ctx.shm->anim_custom_set = ctx._local_copy_config->animation_custom_set; + /// @NOTE: set dm_set, etc. first so rand_animation_index works + ctx.shm->anim_index = !ctx._local_copy_config->_keep_old_animation_index + ? rand_animation_index(ctx, *ctx._local_copy_config) + : old_anim_index; + + [[maybe_unused]] const auto t0 = platform::get_current_time_us(); + if constexpr (features::EnableLazyLoadAssets) { + auto [result, error] = hot_load_animation(ctx); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) { + // rollback + ctx.shm->anim_type = old_anim_type; + ctx.shm->anim_dm_set = old_anim_dm_set; + ctx.shm->anim_custom_set = old_anim_custom_set; + ctx.shm->anim_index = old_anim_index; } - - static void update_config_reload_sprite_sheet(animation_context_t& ctx) { - using namespace assets; - assert(ctx._local_copy_config != nullptr); - - platform::LockGuard guard (ctx.anim_lock); - const auto old_anim_type = ctx.shm->anim_type; - const auto old_anim_dm_set = ctx.shm->anim_dm_set; - const auto old_anim_custom_set = ctx.shm->anim_custom_set; - const auto old_anim_index = ctx.shm->anim_index; - - ctx.shm->anim_type = ctx._local_copy_config->animation_sprite_sheet_layout; - ctx.shm->anim_dm_set = ctx._local_copy_config->animation_dm_set; - ctx.shm->anim_custom_set = ctx._local_copy_config->animation_custom_set; - /// @NOTE: set dm_set, etc. first so rand_animation_index works - ctx.shm->anim_index = !ctx._local_copy_config->_keep_old_animation_index ? rand_animation_index(ctx, *ctx._local_copy_config) : old_anim_index; - - [[maybe_unused]] const auto t0 = platform::get_current_time_us(); - if constexpr (features::EnableLazyLoadAssets) { - auto [result, error] = hot_load_animation(ctx); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) { - // rollback - ctx.shm->anim_type = old_anim_type; - ctx.shm->anim_dm_set = old_anim_dm_set; - ctx.shm->anim_custom_set = old_anim_custom_set; - ctx.shm->anim_index = old_anim_index; - } + } else { + if constexpr (features::EnableCustomSpriteSheetsAssets) { + if (ctx._local_copy_config->_custom && static_cast(ctx.shm->anim_index) == CUSTOM_ANIM_INDEX) { + auto [result, error] = details::anim_load_custom_animation(ctx, *ctx._local_copy_config); + if (error == bongocat_error_t::BONGOCAT_SUCCESS) { + ctx.shm->anim = bongocat::move(result); } else { - if constexpr (features::EnableCustomSpriteSheetsAssets) { - if (ctx._local_copy_config->_custom && static_cast(ctx.shm->anim_index) == CUSTOM_ANIM_INDEX) { - auto [result, error] = details::anim_load_custom_animation(ctx, *ctx._local_copy_config); - if (error == bongocat_error_t::BONGOCAT_SUCCESS) { - ctx.shm->anim = bongocat::move(result); - } else { - // rollback - ctx.shm->anim_type = old_anim_type; - ctx.shm->anim_dm_set = old_anim_dm_set; - ctx.shm->anim_custom_set = old_anim_custom_set; - ctx.shm->anim_index = old_anim_index; - } - } - } + // rollback + ctx.shm->anim_type = old_anim_type; + ctx.shm->anim_dm_set = old_anim_dm_set; + ctx.shm->anim_custom_set = old_anim_custom_set; + ctx.shm->anim_index = old_anim_index; } + } + } + } - // initial frame - ctx.shm->animation_player_result.sprite_sheet_col = ctx._local_copy_config->idle_frame ? ctx._local_copy_config->idle_frame : 0; - ctx.shm->animation_player_result.sprite_sheet_row = features::EnableCustomSpriteSheetsAssets && ctx._local_copy_config->_custom && ctx._local_copy_config->custom_sprite_sheet_settings.idle_row_index > 0 ? ctx._local_copy_config->custom_sprite_sheet_settings.idle_row_index : 0; + // initial frame + ctx.shm->animation_player_result.sprite_sheet_col = + ctx._local_copy_config->idle_frame ? ctx._local_copy_config->idle_frame : 0; + ctx.shm->animation_player_result.sprite_sheet_row = + features::EnableCustomSpriteSheetsAssets && ctx._local_copy_config->_custom && + ctx._local_copy_config->custom_sprite_sheet_settings.idle_row_index > 0 + ? ctx._local_copy_config->custom_sprite_sheet_settings.idle_row_index + : 0; - [[maybe_unused]] const auto t1 = platform::get_current_time_us(); + [[maybe_unused]] const auto t1 = platform::get_current_time_us(); - BONGOCAT_LOG_DEBUG("Update sprite sheet; load assets in %.3fms (%.6fsec)", static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); - } - void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen) { - assert(ctx._local_copy_config != nullptr); - assert(ctx.shm != nullptr); + BONGOCAT_LOG_DEBUG("Update sprite sheet; load assets in %.3fms (%.6fsec)", static_cast(t1 - t0) / 1000.0, + static_cast(t1 - t0) / 1000000.0); +} +void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen) { + assert(ctx._local_copy_config != nullptr); + assert(ctx.shm != nullptr); - *ctx._local_copy_config = config; + *ctx._local_copy_config = config; - update_config_reload_sprite_sheet(ctx); + update_config_reload_sprite_sheet(ctx); - atomic_store(&ctx.config_seen_generation, new_gen); - // Signal main that reload is done - ctx.config_updated.notify_all(); - } + atomic_store(&ctx.config_seen_generation, new_gen); + // Signal main that reload is done + ctx.config_updated.notify_all(); } +} // namespace bongocat::animation diff --git a/src/graphics/animation_init.cpp b/src/graphics/animation_init.cpp index 6d67fae4..3bb35c92 100644 --- a/src/graphics/animation_init.cpp +++ b/src/graphics/animation_init.cpp @@ -1,648 +1,699 @@ -#include "graphics/animation_context.h" #include "graphics/animation.h" +#include "graphics/animation_context.h" #include "platform/wayland.h" #include "utils/memory.h" + +#include #include #include -#include -#include #include +#include // assets -#include "graphics/embedded_assets_dms.h" -#include "graphics/embedded_assets_pkmn.h" -#include "embedded_assets/bongocat/bongocat.hpp" -#include "embedded_assets/ms_agent/ms_agent.hpp" -#include "embedded_assets/misc/misc.hpp" #include "embedded_assets/bongocat/bongocat.h" -#include "embedded_assets/ms_agent/ms_agent_sprite.h" +#include "embedded_assets/bongocat/bongocat.hpp" #include "embedded_assets/dm/dm_sprite.h" -#include "embedded_assets/min_dm/min_dm_sprite.h" #include "embedded_assets/dm20/dm20_sprite.h" +#include "embedded_assets/dmall/dmall_sprite.h" +#include "embedded_assets/dmc/dmc_sprite.h" #include "embedded_assets/dmx/dmx_sprite.h" +#include "embedded_assets/min_dm/min_dm_sprite.h" +#include "embedded_assets/misc/misc.hpp" +#include "embedded_assets/misc/misc_sprite.h" +#include "embedded_assets/ms_agent/ms_agent.hpp" +#include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "embedded_assets/pen/pen_sprite.h" #include "embedded_assets/pen20/pen20_sprite.h" -#include "embedded_assets/dmc/dmc_sprite.h" -#include "embedded_assets/dmall/dmall_sprite.h" -#include "embedded_assets/misc/misc_sprite.h" #include "embedded_assets/pkmn/pkmn_sprite.h" #include "embedded_assets/pmd/pmd_sprite.h" +#include "graphics/embedded_assets_dms.h" +#include "graphics/embedded_assets_pkmn.h" // image loader #include "image_loader/bongocat/load_images_bongocat.h" #include "image_loader/custom/load_custom.h" -#include "image_loader/ms_agent/load_images_ms_agent.h" #include "image_loader/dm/load_images_dm.h" -#include "image_loader/min_dm/load_images_min_dm.h" #include "image_loader/dm20/load_images_dm20.h" +#include "image_loader/dmall/load_images_dmall.h" +#include "image_loader/dmc/load_images_dmc.h" #include "image_loader/dmx/load_images_dmx.h" +#include "image_loader/min_dm/load_images_min_dm.h" +#include "image_loader/misc/load_images_misc.h" +#include "image_loader/ms_agent/load_images_ms_agent.h" #include "image_loader/pen/load_images_pen.h" #include "image_loader/pen20/load_images_pen20.h" -#include "image_loader/dmc/load_images_dmc.h" -#include "image_loader/dmall/load_images_dmall.h" #include "image_loader/pkmn/load_images_pkmn.h" -#include "image_loader/misc/load_images_misc.h" #include "image_loader/pmd/load_images_pmd.h" - namespace bongocat::animation { - [[maybe_unused]] static constexpr bool should_load_bongocat([[maybe_unused]] const config::config_t& config) { - return features::EnablePreloadAssets || config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Bongocat; +[[maybe_unused]] static constexpr bool should_load_bongocat([[maybe_unused]] const config::config_t& config) { + return features::EnablePreloadAssets || + config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Bongocat; +} +[[maybe_unused]] static constexpr bool should_load_dm([[maybe_unused]] const config::config_t& config) { + return features::EnablePreloadAssets || + (config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Dm && + config.animation_dm_set != config::config_animation_dm_set_t::None); +} +[[maybe_unused]] static constexpr bool should_load_ms_agent([[maybe_unused]] const config::config_t& config) { + return features::EnablePreloadAssets || + config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::MsAgent; +} +[[maybe_unused]] static constexpr bool should_load_pkmn([[maybe_unused]] const config::config_t& config) { + return features::EnablePreloadAssets || + config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Pkmn; +} +[[maybe_unused]] static constexpr bool should_load_misc([[maybe_unused]] const config::config_t& config) { + return features::EnablePreloadAssets || + (config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Custom && + config.animation_custom_set == config::config_animation_custom_set_t::misc); +} +[[maybe_unused]] static constexpr bool should_load_custom([[maybe_unused]] const config::config_t& config) { + return (features::EnablePreloadAssets && config._custom) || + (config._custom && + config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Custom && + config.animation_custom_set == config::config_animation_custom_set_t::custom); +} +[[maybe_unused]] static constexpr bool should_load_pmd([[maybe_unused]] const config::config_t& config) { + return features::EnablePreloadAssets || + (config._custom && + config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Custom && + config.animation_custom_set == config::config_animation_custom_set_t::pmd); +} + +namespace details { + created_result_t anim_load_custom_animation(animation_context_t& ctx, + const config::config_t& config) { + BONGOCAT_CHECK_NULL(config.custom_sprite_sheet_filename, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + if (strlen(config.custom_sprite_sheet_filename) <= 0) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; } - [[maybe_unused]] static constexpr bool should_load_dm([[maybe_unused]] const config::config_t& config) { - return features::EnablePreloadAssets || (config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Dm && config.animation_dm_set != config::config_animation_dm_set_t::None); + + assert(ctx.shm.ptr); + assert(ctx._local_copy_config.ptr); + + BONGOCAT_LOG_VERBOSE("Load custom Animation: %s ...", config.custom_sprite_sheet_filename); + auto sprite_sheet_image_result = load_custom_sprite_sheet_file(config.custom_sprite_sheet_filename); + if (sprite_sheet_image_result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load custom Animation failed: %s", config.custom_sprite_sheet_filename); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + + auto result = load_custom_anim(ctx, sprite_sheet_image_result.result, config.custom_sprite_sheet_settings); + free_custom_sprite_sheet_file(sprite_sheet_image_result.result); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load custom Animation failed: %s", config.custom_sprite_sheet_filename); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; } - [[maybe_unused]] static constexpr bool should_load_ms_agent([[maybe_unused]] const config::config_t& config) { - return features::EnablePreloadAssets || config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::MsAgent; + + return result; + } +} // namespace details + +created_result_t hot_load_animation(animation_context_t& ctx) { + // read-only config + assert(ctx._local_copy_config != nullptr); + const config::config_t& current_config = *ctx._local_copy_config; + assert(ctx.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const int anim_index = anim_shm.anim_index; + + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + // unload other sprite sheets + cleanup_animation(anim_shm.anim); + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: { + if constexpr (features::EnableBongocatEmbeddedAssets) { + auto [result, error] = load_bongocat_sprite_sheet(ctx, anim_index); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return error; + } + anim_shm.anim = bongocat::move(result); } - [[maybe_unused]] static constexpr bool should_load_pkmn([[maybe_unused]] const config::config_t& config) { - return features::EnablePreloadAssets || config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Pkmn; + } break; + case config::config_animation_sprite_sheet_layout_t::Dm: { + bongocat_error_t error = bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + dm_sprite_sheet_t result; + switch (anim_shm.anim_dm_set) { + case config::config_animation_dm_set_t::None: + cleanup_animation(result); + error = bongocat_error_t::BONGOCAT_SUCCESS; + break; + case config::config_animation_dm_set_t::min_dm: { + if constexpr (features::EnableMinDmEmbeddedAssets) { + auto [l_result, l_error] = load_min_dm_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::dm: { + if constexpr (features::EnableFullDmEmbeddedAssets) { + auto [l_result, l_error] = load_dm_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::dm20: { + if constexpr (features::EnableDm20EmbeddedAssets) { + auto [l_result, l_error] = load_dm20_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::dmx: { + if constexpr (features::EnableDmxEmbeddedAssets) { + auto [l_result, l_error] = load_dmx_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::pen: { + if constexpr (features::EnablePenEmbeddedAssets) { + auto [l_result, l_error] = load_pen_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::pen20: { + if constexpr (features::EnablePen20EmbeddedAssets) { + auto [l_result, l_error] = load_pen20_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::dmc: { + if constexpr (features::EnableDmcEmbeddedAssets) { + auto [l_result, l_error] = load_dmc_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; + case config::config_animation_dm_set_t::dmall: { + if constexpr (features::EnableDmAllEmbeddedAssets) { + auto [l_result, l_error] = load_dmall_sprite_sheet(ctx, anim_index); + result = bongocat::move(l_result); + error = bongocat::move(l_error); + } + } break; } - [[maybe_unused]] static constexpr bool should_load_misc([[maybe_unused]] const config::config_t& config) { - return features::EnablePreloadAssets || (config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Custom && config.animation_custom_set == config::config_animation_custom_set_t::misc); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return error; } - [[maybe_unused]] static constexpr bool should_load_custom([[maybe_unused]] const config::config_t& config) { - return (features::EnablePreloadAssets && config._custom) || (config._custom && config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Custom && config.animation_custom_set == config::config_animation_custom_set_t::custom); + anim_shm.anim = bongocat::move(result); + } break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: + if constexpr (features::EnablePkmnEmbeddedAssets) { + auto [result, error] = load_pkmn_sprite_sheet(ctx, anim_index); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return error; + } + anim_shm.anim = bongocat::move(result); } - [[maybe_unused]] static constexpr bool should_load_pmd([[maybe_unused]] const config::config_t& config) { - return features::EnablePreloadAssets || (config._custom && config.animation_sprite_sheet_layout == config::config_animation_sprite_sheet_layout_t::Custom && config.animation_custom_set == config::config_animation_custom_set_t::pmd); + break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: + if constexpr (features::EnableMsAgentEmbeddedAssets) { + auto [result, error] = load_ms_agent_sprite_sheet(ctx, anim_index); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return error; + } + anim_shm.anim = bongocat::move(result); } - - namespace details { - created_result_t anim_load_custom_animation(animation_context_t& ctx, const config::config_t& config) { - BONGOCAT_CHECK_NULL(config.custom_sprite_sheet_filename, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - if (strlen(config.custom_sprite_sheet_filename) <= 0) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - - assert(ctx.shm.ptr); - assert(ctx._local_copy_config.ptr); - - BONGOCAT_LOG_VERBOSE("Load custom Animation: %s ...", config.custom_sprite_sheet_filename); - auto sprite_sheet_image_result = load_custom_sprite_sheet_file(config.custom_sprite_sheet_filename); - if (sprite_sheet_image_result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load custom Animation failed: %s", config.custom_sprite_sheet_filename); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - - auto result = load_custom_anim(ctx, sprite_sheet_image_result.result, config.custom_sprite_sheet_settings); - free_custom_sprite_sheet_file(sprite_sheet_image_result.result); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load custom Animation failed: %s", config.custom_sprite_sheet_filename); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - - return result; + break; + case config::config_animation_sprite_sheet_layout_t::Custom: + assert(anim_index >= 0); + if constexpr (features::EnableCustomSpriteSheetsAssets && features::EnableMiscEmbeddedAssets) { + assert(assets::CUSTOM_ANIM_INDEX > assets::MAX_MISC_ANIM_INDEX); + } + if constexpr (features::EnableCustomSpriteSheetsAssets) { + if (current_config._custom && anim_index == assets::CUSTOM_ANIM_INDEX) { + auto [result, error] = details::anim_load_custom_animation(ctx, current_config); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) { + return error; } + anim_shm.anim = bongocat::move(result); + } } - - created_result_t hot_load_animation(animation_context_t& ctx) { - // read-only config - assert(ctx._local_copy_config != nullptr); - const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const int anim_index = anim_shm.anim_index; - - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - // unload other sprite sheets - cleanup_animation(anim_shm.anim); - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: { - if constexpr (features::EnableBongocatEmbeddedAssets) { - auto [result, error] = load_bongocat_sprite_sheet(ctx, anim_index); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return error; - } - anim_shm.anim = bongocat::move(result); - } - }break; - case config::config_animation_sprite_sheet_layout_t::Dm: { - bongocat_error_t error = bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - dm_sprite_sheet_t result; - switch (anim_shm.anim_dm_set) { - case config::config_animation_dm_set_t::None: - cleanup_animation(result); - error = bongocat_error_t::BONGOCAT_SUCCESS; - break; - case config::config_animation_dm_set_t::min_dm:{ - if constexpr (features::EnableMinDmEmbeddedAssets) { - auto [l_result, l_error] = load_min_dm_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::dm:{ - if constexpr (features::EnableFullDmEmbeddedAssets) { - auto [l_result, l_error] = load_dm_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::dm20:{ - if constexpr (features::EnableDm20EmbeddedAssets) { - auto [l_result, l_error] = load_dm20_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::dmx:{ - if constexpr (features::EnableDmxEmbeddedAssets) { - auto [l_result, l_error] = load_dmx_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::pen:{ - if constexpr (features::EnablePenEmbeddedAssets) { - auto [l_result, l_error] = load_pen_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::pen20:{ - if constexpr (features::EnablePen20EmbeddedAssets) { - auto [l_result, l_error] = load_pen20_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::dmc:{ - if constexpr (features::EnableDmcEmbeddedAssets) { - auto [l_result, l_error] = load_dmc_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - case config::config_animation_dm_set_t::dmall:{ - if constexpr (features::EnableDmAllEmbeddedAssets) { - auto [l_result, l_error] = load_dmall_sprite_sheet(ctx, anim_index); - result = bongocat::move(l_result); - error = bongocat::move(l_error); - } - }break; - } - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return error; - } - anim_shm.anim = bongocat::move(result); - }break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: - if constexpr (features::EnablePkmnEmbeddedAssets) { - auto [result, error] = load_pkmn_sprite_sheet(ctx, anim_index); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return error; - } - anim_shm.anim = bongocat::move(result); - } - break; - case config::config_animation_sprite_sheet_layout_t::MsAgent: - if constexpr (features::EnableMsAgentEmbeddedAssets) { - auto [result, error] = load_ms_agent_sprite_sheet(ctx, anim_index); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return error; - } - anim_shm.anim = bongocat::move(result); - } - break; - case config::config_animation_sprite_sheet_layout_t::Custom: - assert(anim_index >= 0); - if constexpr (features::EnableCustomSpriteSheetsAssets && features::EnableMiscEmbeddedAssets) { - assert(assets::CUSTOM_ANIM_INDEX > assets::MAX_MISC_ANIM_INDEX); - } - if constexpr (features::EnableCustomSpriteSheetsAssets) { - if (current_config._custom && anim_index == assets::CUSTOM_ANIM_INDEX) { - auto [result, error] = details::anim_load_custom_animation(ctx, current_config); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) { - return error; - } - anim_shm.anim = bongocat::move(result); - } - } - if constexpr (features::EnableMiscEmbeddedAssets) { - assert(anim_index >= 0); - if (current_config.animation_custom_set == config::config_animation_custom_set_t::misc && static_cast(anim_index) < assets::MISC_ANIM_COUNT) { - auto [result, error] = load_misc_sprite_sheet(ctx, anim_index); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return error; - } - anim_shm.anim = bongocat::move(result); - } - } - if constexpr (features::EnablePmdEmbeddedAssets) { - assert(anim_index >= 0); - if (current_config.animation_custom_set == config::config_animation_custom_set_t::pmd && static_cast(anim_index) < assets::PMD_ANIM_COUNT) { - auto [result, error] = load_pmd_sprite_sheet(ctx, anim_index); - if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return error; - } - anim_shm.anim = bongocat::move(result); - } - } - - break; - /// @NOTE(assets): 6. add hot reload asset + if constexpr (features::EnableMiscEmbeddedAssets) { + assert(anim_index >= 0); + if (current_config.animation_custom_set == config::config_animation_custom_set_t::misc && + static_cast(anim_index) < assets::MISC_ANIM_COUNT) { + auto [result, error] = load_misc_sprite_sheet(ctx, anim_index); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return error; } - - created_result_t ret; - ret.result = &get_current_animation(ctx); - ret.error = bongocat_error_t::BONGOCAT_SUCCESS; - return ret; + anim_shm.anim = bongocat::move(result); + } } - - animation_t& get_current_animation(animation_context_t& ctx) { - using namespace assets; - // fallback sprite - static animation_t none_sprite_sheet{}; - - // read-only config - assert(ctx._local_copy_config != nullptr); - //const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; - const int anim_index = anim_shm.anim_index; - - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - return none_sprite_sheet; - case config::config_animation_sprite_sheet_layout_t::Bongocat: { - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Bongocat); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.bongocat_anims.count ? anim_shm.bongocat_anims[static_cast(anim_index)] : none_sprite_sheet; - } - case config::config_animation_sprite_sheet_layout_t::Dm: { - switch (anim_shm.anim_dm_set) { - case config::config_animation_dm_set_t::None: - return none_sprite_sheet; - case config::config_animation_dm_set_t::min_dm: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.min_dm_anims.count ? anim_shm.min_dm_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::dm: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - return static_cast(anim_index) < anim_shm.dm_anims.count ? anim_shm.dm_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::dm20: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.dm20_anims.count ? anim_shm.dm20_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::dmx: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.dmx_anims.count ? anim_shm.dmx_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::pen: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - return static_cast(anim_index) < anim_shm.pen_anims.count ? anim_shm.pen_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::pen20: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.pen20_anims.count ? anim_shm.pen20_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::dmc: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.dmc_anims.count ? anim_shm.dmc_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_dm_set_t::dmall: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.dmall_anims.count ? anim_shm.dmall_anims[static_cast(anim_index)] : none_sprite_sheet; - } - }break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Pkmn); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.pkmn_anims.count ? anim_shm.pkmn_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_sprite_sheet_layout_t::MsAgent: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::MsAgent); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.ms_anims.count ? anim_shm.ms_anims[static_cast(anim_index)] : none_sprite_sheet; - case config::config_animation_sprite_sheet_layout_t::Custom: - switch (anim_shm.anim_custom_set) { - case config::config_animation_custom_set_t::None: - break; - case config::config_animation_custom_set_t::misc: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Custom); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.misc_anims.count ? anim_shm.misc_anims[static_cast(anim_index)] : none_sprite_sheet; - break; - case config::config_animation_custom_set_t::pmd: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Custom); - return anim_shm.anim; - } - assert(anim_index >= 0); - return static_cast(anim_index) < anim_shm.pmd_anims.count ? anim_shm.pmd_anims[static_cast(anim_index)] : none_sprite_sheet; - break; - case config::config_animation_custom_set_t::custom: - if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Custom); - return anim_shm.anim; - } - if (static_cast(anim_index) == CUSTOM_ANIM_INDEX) { - return anim_shm.anim; - } - break; - } + if constexpr (features::EnablePmdEmbeddedAssets) { + assert(anim_index >= 0); + if (current_config.animation_custom_set == config::config_animation_custom_set_t::pmd && + static_cast(anim_index) < assets::PMD_ANIM_COUNT) { + auto [result, error] = load_pmd_sprite_sheet(ctx, anim_index); + if (error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return error; } - - return none_sprite_sheet; + anim_shm.anim = bongocat::move(result); + } } - // ============================================================================= - // PUBLIC API IMPLEMENTATION - // ============================================================================= - - created_result_t> create(const config::config_t& config) { - using namespace assets; - BONGOCAT_LOG_INFO("Initializing animation system"); - AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } + break; + /// @NOTE(assets): 6. add hot reload asset + } - ret->_config = &config; + created_result_t ret; + ret.result = &get_current_animation(ctx); + ret.error = bongocat_error_t::BONGOCAT_SUCCESS; + return ret; +} - // Initialize shared memory - ret->anim.shm = platform::make_allocated_mmap(); - if (!ret->anim.shm) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(ret->anim.shm != nullptr); +animation_t& get_current_animation(animation_context_t& ctx) { + using namespace assets; + // fallback sprite + static animation_t none_sprite_sheet{}; + + // read-only config + assert(ctx._local_copy_config != nullptr); + // const config::config_t& current_config = *ctx._local_copy_config; + assert(ctx.shm != nullptr); + animation_shared_memory_t& anim_shm = *ctx.shm; + const int anim_index = anim_shm.anim_index; + + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + return none_sprite_sheet; + case config::config_animation_sprite_sheet_layout_t::Bongocat: { + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Bongocat); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.bongocat_anims.count + ? anim_shm.bongocat_anims[static_cast(anim_index)] + : none_sprite_sheet; + } + case config::config_animation_sprite_sheet_layout_t::Dm: { + switch (anim_shm.anim_dm_set) { + case config::config_animation_dm_set_t::None: + return none_sprite_sheet; + case config::config_animation_dm_set_t::min_dm: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.min_dm_anims.count + ? anim_shm.min_dm_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::dm: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + return static_cast(anim_index) < anim_shm.dm_anims.count + ? anim_shm.dm_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::dm20: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.dm20_anims.count + ? anim_shm.dm20_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::dmx: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.dmx_anims.count + ? anim_shm.dmx_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::pen: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + return static_cast(anim_index) < anim_shm.pen_anims.count + ? anim_shm.pen_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::pen20: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.pen20_anims.count + ? anim_shm.pen20_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::dmc: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.dmc_anims.count + ? anim_shm.dmc_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_dm_set_t::dmall: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Dm); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.dmall_anims.count + ? anim_shm.dmall_anims[static_cast(anim_index)] + : none_sprite_sheet; + } + } break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Pkmn); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.pkmn_anims.count + ? anim_shm.pkmn_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_sprite_sheet_layout_t::MsAgent: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::MsAgent); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.ms_anims.count + ? anim_shm.ms_anims[static_cast(anim_index)] + : none_sprite_sheet; + case config::config_animation_sprite_sheet_layout_t::Custom: + switch (anim_shm.anim_custom_set) { + case config::config_animation_custom_set_t::None: + break; + case config::config_animation_custom_set_t::misc: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Custom); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.misc_anims.count + ? anim_shm.misc_anims[static_cast(anim_index)] + : none_sprite_sheet; + break; + case config::config_animation_custom_set_t::pmd: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Custom); + return anim_shm.anim; + } + assert(anim_index >= 0); + return static_cast(anim_index) < anim_shm.pmd_anims.count + ? anim_shm.pmd_anims[static_cast(anim_index)] + : none_sprite_sheet; + break; + case config::config_animation_custom_set_t::custom: + if (features::EnableLazyLoadAssets) { + assert(anim_shm.anim.type == animation_t::Type::Custom); + return anim_shm.anim; + } + if (static_cast(anim_index) == CUSTOM_ANIM_INDEX) { + return anim_shm.anim; + } + break; + } + } - // Initialize shared memory for local config - ret->anim._local_copy_config = platform::make_allocated_mmap(); - if (!ret->anim._local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(ret->anim._local_copy_config != nullptr); - //config_set_defaults(*ctx._local_copy_config); - *ret->anim._local_copy_config = config; - ret->anim.shm->animation_player_result.sprite_sheet_col = config.idle_frame; // initial frame - - ret->trigger_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->trigger_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for animation trigger: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } + return none_sprite_sheet; +} - ret->render_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->render_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for animation render: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } +// ============================================================================= +// PUBLIC API IMPLEMENTATION +// ============================================================================= + +created_result_t> create(const config::config_t& config) { + using namespace assets; + BONGOCAT_LOG_INFO("Initializing animation system"); + AllocatedMemory ret = make_allocated_memory(); + assert(ret != nullptr); + if (ret == nullptr) { + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + ret->_config = &config; + + // Initialize shared memory + ret->anim.shm = platform::make_allocated_mmap(); + if (!ret->anim.shm) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(ret->anim.shm != nullptr); + + // Initialize shared memory for local config + ret->anim._local_copy_config = platform::make_allocated_mmap(); + if (!ret->anim._local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(ret->anim._local_copy_config != nullptr); + // config_set_defaults(*ctx._local_copy_config); + *ret->anim._local_copy_config = config; + ret->anim.shm->animation_player_result.sprite_sheet_col = config.idle_frame; // initial frame + + ret->trigger_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->trigger_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for animation trigger: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + ret->render_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->render_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for animation render: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + ret->anim.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->anim.update_config_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for input update config: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + [[maybe_unused]] const auto t0 = platform::get_current_time_us(); + // Load embedded images/animations + if constexpr (features::EnableLazyLoadAssets) { + hot_load_animation(ret->anim); + } + + /// @TODO: async assets load + // Initialize embedded images/animations + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + assert(ret->anim._local_copy_config.ptr); + // preload assets + if constexpr (features::EnableBongocatEmbeddedAssets) { + // Load Bongocat + if (should_load_bongocat(*ret->anim._local_copy_config)) { + BONGOCAT_LOG_INFO("Load bongocat sprite sheet frames: %d", BONGOCAT_EMBEDDED_IMAGES_COUNT); + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes - ret->anim.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->anim.update_config_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for input update config: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } + ctx.shm->bongocat_anims = platform::make_allocated_mmap_array(BONGOCAT_ANIM_COUNT); - [[maybe_unused]] const auto t0 = platform::get_current_time_us(); - // Load embedded images/animations - if constexpr (features::EnableLazyLoadAssets) { - hot_load_animation(ret->anim); - } + init_bongocat_anim(ctx, BONGOCAT_ANIM_INDEX, get_bongocat_sprite, BONGOCAT_EMBEDDED_IMAGES_COUNT); + } + } + + if constexpr (features::EnableDmEmbeddedAssets) { + // Load dm + if (should_load_dm(*ret->anim._local_copy_config)) { + BONGOCAT_LOG_INFO("Load dm sprite sheets: %d", DM_ANIMATIONS_COUNT); + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes - /// @TODO: async assets load - // Initialize embedded images/animations - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(ret->anim._local_copy_config.ptr); - // preload assets - if constexpr (features::EnableBongocatEmbeddedAssets) { - // Load Bongocat - if (should_load_bongocat(*ret->anim._local_copy_config)) { - BONGOCAT_LOG_INFO("Load bongocat sprite sheet frames: %d", BONGOCAT_EMBEDDED_IMAGES_COUNT); - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes - - ctx.shm->bongocat_anims = platform::make_allocated_mmap_array(BONGOCAT_ANIM_COUNT); - - init_bongocat_anim(ctx, BONGOCAT_ANIM_INDEX, get_bongocat_sprite, BONGOCAT_EMBEDDED_IMAGES_COUNT); - } - } - - if constexpr (features::EnableDmEmbeddedAssets) { - // Load dm - if (should_load_dm(*ret->anim._local_copy_config)) { - BONGOCAT_LOG_INFO("Load dm sprite sheets: %d", DM_ANIMATIONS_COUNT); - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes - - if constexpr (features::EnableMinDmEmbeddedAssets) { - BONGOCAT_LOG_INFO("Init min_dm sprite sheets: %d", MIN_DM_ANIM_COUNT); - ctx.shm->min_dm_anims = platform::make_allocated_mmap_array(MIN_DM_ANIM_COUNT); + if constexpr (features::EnableMinDmEmbeddedAssets) { + BONGOCAT_LOG_INFO("Init min_dm sprite sheets: %d", MIN_DM_ANIM_COUNT); + ctx.shm->min_dm_anims = platform::make_allocated_mmap_array(MIN_DM_ANIM_COUNT); #ifdef FEATURE_MIN_DM_EMBEDDED_ASSETS - // init minimal dm - //init_dm_anim(ctx, DM_AGUMON_ANIM_INDEX, get_dm_sprite_sheet(DM_AGUMON_ANIM_INDEX), DM_AGUMON_SPRITE_SHEET_COLS, DM_AGUMON_SPRITE_SHEET_ROWS); -#include "min_dm_init_dm_anim.cpp.inl" + // init minimal dm + // init_dm_anim(ctx, DM_AGUMON_ANIM_INDEX, get_dm_sprite_sheet(DM_AGUMON_ANIM_INDEX), + // DM_AGUMON_SPRITE_SHEET_COLS, DM_AGUMON_SPRITE_SHEET_ROWS); +# include "min_dm_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnableFullDmEmbeddedAssets) { - BONGOCAT_LOG_INFO("Init dm sprite sheets: %d", DM_ANIM_COUNT); - ctx.shm->dm_anims = platform::make_allocated_mmap_array(DM_ANIM_COUNT); + } + if constexpr (features::EnableFullDmEmbeddedAssets) { + BONGOCAT_LOG_INFO("Init dm sprite sheets: %d", DM_ANIM_COUNT); + ctx.shm->dm_anims = platform::make_allocated_mmap_array(DM_ANIM_COUNT); #ifdef FEATURE_DM_EMBEDDED_ASSETS - // dm -#include "dm_init_dm_anim.cpp.inl" + // dm +# include "dm_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnableDm20EmbeddedAssets) { - BONGOCAT_LOG_INFO("Init dm20 sprite sheets: %d", DM20_ANIM_COUNT); - ctx.shm->dm20_anims = platform::make_allocated_mmap_array(DM20_ANIM_COUNT); + } + if constexpr (features::EnableDm20EmbeddedAssets) { + BONGOCAT_LOG_INFO("Init dm20 sprite sheets: %d", DM20_ANIM_COUNT); + ctx.shm->dm20_anims = platform::make_allocated_mmap_array(DM20_ANIM_COUNT); #ifdef FEATURE_DM20_EMBEDDED_ASSETS - // dm20 -#include "dm20_init_dm_anim.cpp.inl" + // dm20 +# include "dm20_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnableDmxEmbeddedAssets) { - BONGOCAT_LOG_INFO("Init dmx sprite sheets: %d", DMX_ANIM_COUNT); - ctx.shm->dmx_anims = platform::make_allocated_mmap_array(DMX_ANIM_COUNT); + } + if constexpr (features::EnableDmxEmbeddedAssets) { + BONGOCAT_LOG_INFO("Init dmx sprite sheets: %d", DMX_ANIM_COUNT); + ctx.shm->dmx_anims = platform::make_allocated_mmap_array(DMX_ANIM_COUNT); #ifdef FEATURE_DMX_EMBEDDED_ASSETS - // dmx -#include "dmx_init_dm_anim.cpp.inl" + // dmx +# include "dmx_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnablePenEmbeddedAssets) { - BONGOCAT_LOG_INFO("Init pen sprite sheets: %d", PEN20_ANIM_COUNT); - ctx.shm->pen_anims = platform::make_allocated_mmap_array(PEN_ANIM_COUNT); + } + if constexpr (features::EnablePenEmbeddedAssets) { + BONGOCAT_LOG_INFO("Init pen sprite sheets: %d", PEN20_ANIM_COUNT); + ctx.shm->pen_anims = platform::make_allocated_mmap_array(PEN_ANIM_COUNT); #ifdef FEATURE_PEN_EMBEDDED_ASSETS - // pen -#include "pen_init_dm_anim.cpp.inl" + // pen +# include "pen_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnablePen20EmbeddedAssets) { - BONGOCAT_LOG_INFO("Init pen20 sprite sheets: %d", PEN20_ANIM_COUNT); - ctx.shm->pen20_anims = platform::make_allocated_mmap_array(PEN20_ANIM_COUNT); + } + if constexpr (features::EnablePen20EmbeddedAssets) { + BONGOCAT_LOG_INFO("Init pen20 sprite sheets: %d", PEN20_ANIM_COUNT); + ctx.shm->pen20_anims = platform::make_allocated_mmap_array(PEN20_ANIM_COUNT); #ifdef FEATURE_PEN20_EMBEDDED_ASSETS - // pen20 -#include "pen20_init_dm_anim.cpp.inl" + // pen20 +# include "pen20_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnableDmcEmbeddedAssets) { - BONGOCAT_LOG_INFO("Init dmc sprite sheets: %d", DMC_ANIM_COUNT); - ctx.shm->dmc_anims = platform::make_allocated_mmap_array(DMC_ANIM_COUNT); + } + if constexpr (features::EnableDmcEmbeddedAssets) { + BONGOCAT_LOG_INFO("Init dmc sprite sheets: %d", DMC_ANIM_COUNT); + ctx.shm->dmc_anims = platform::make_allocated_mmap_array(DMC_ANIM_COUNT); #ifdef FEATURE_DMC_EMBEDDED_ASSETS - // dmc -#include "dmc_init_dm_anim.cpp.inl" + // dmc +# include "dmc_init_dm_anim.cpp.inl" #endif - } - if constexpr (features::EnableDmAllEmbeddedAssets) { - BONGOCAT_LOG_INFO("Init dmall sprite sheets: %d", DMALL_ANIM_COUNT); - ctx.shm->dmall_anims = platform::make_allocated_mmap_array(DMALL_ANIM_COUNT); + } + if constexpr (features::EnableDmAllEmbeddedAssets) { + BONGOCAT_LOG_INFO("Init dmall sprite sheets: %d", DMALL_ANIM_COUNT); + ctx.shm->dmall_anims = platform::make_allocated_mmap_array(DMALL_ANIM_COUNT); #ifdef FEATURE_DMALL_EMBEDDED_ASSETS - // dmall -#include "dmall_init_dm_anim.cpp.inl" + // dmall +# include "dmall_init_dm_anim.cpp.inl" #endif - } - } - } + } + } + } - if constexpr (features::EnableMsAgentEmbeddedAssets) { - // Load Ms Pets (Clippy) - if (should_load_ms_agent(*ret->anim._local_copy_config)) { - BONGOCAT_LOG_INFO("Load MS agent sprite sheets: %d", MS_AGENTS_ANIM_COUNT); - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes + if constexpr (features::EnableMsAgentEmbeddedAssets) { + // Load Ms Pets (Clippy) + if (should_load_ms_agent(*ret->anim._local_copy_config)) { + BONGOCAT_LOG_INFO("Load MS agent sprite sheets: %d", MS_AGENTS_ANIM_COUNT); + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes - ctx.shm->ms_anims = platform::make_allocated_mmap_array(MS_AGENTS_ANIM_COUNT); + ctx.shm->ms_anims = platform::make_allocated_mmap_array(MS_AGENTS_ANIM_COUNT); - // clippy - init_ms_agent_anim(ctx, CLIPPY_ANIM_INDEX, get_ms_agent_sprite_sheet(CLIPPY_ANIM_INDEX), CLIPPY_SPRITE_SHEET_COLS, CLIPPY_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(CLIPPY_ANIM_INDEX)); + // clippy + init_ms_agent_anim(ctx, CLIPPY_ANIM_INDEX, get_ms_agent_sprite_sheet(CLIPPY_ANIM_INDEX), + CLIPPY_SPRITE_SHEET_COLS, CLIPPY_SPRITE_SHEET_ROWS, + get_ms_agent_animation_indices(CLIPPY_ANIM_INDEX)); #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - /// @NOTE(config): add more MS Pets here - init_ms_agent_anim(ctx, LINKS_ANIM_INDEX, get_ms_agent_sprite_sheet(LINKS_ANIM_INDEX), LINKS_SPRITE_SHEET_COLS, LINKS_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(LINKS_ANIM_INDEX)); - init_ms_agent_anim(ctx, ROVER_ANIM_INDEX, get_ms_agent_sprite_sheet(ROVER_ANIM_INDEX), ROVER_SPRITE_SHEET_COLS, ROVER_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(ROVER_ANIM_INDEX)); - init_ms_agent_anim(ctx, MERLIN_ANIM_INDEX, get_ms_agent_sprite_sheet(MERLIN_ANIM_INDEX), MERLIN_SPRITE_SHEET_COLS, MERLIN_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(MERLIN_ANIM_INDEX)); + /// @NOTE(config): add more MS Pets here + init_ms_agent_anim(ctx, LINKS_ANIM_INDEX, get_ms_agent_sprite_sheet(LINKS_ANIM_INDEX), LINKS_SPRITE_SHEET_COLS, + LINKS_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(LINKS_ANIM_INDEX)); + init_ms_agent_anim(ctx, ROVER_ANIM_INDEX, get_ms_agent_sprite_sheet(ROVER_ANIM_INDEX), ROVER_SPRITE_SHEET_COLS, + ROVER_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(ROVER_ANIM_INDEX)); + init_ms_agent_anim(ctx, MERLIN_ANIM_INDEX, get_ms_agent_sprite_sheet(MERLIN_ANIM_INDEX), + MERLIN_SPRITE_SHEET_COLS, MERLIN_SPRITE_SHEET_ROWS, + get_ms_agent_animation_indices(MERLIN_ANIM_INDEX)); #endif - } - } + } + } - if constexpr (features::EnablePkmnEmbeddedAssets) { - // Load pkmn - if (should_load_pkmn(*ret->anim._local_copy_config)) { - BONGOCAT_LOG_INFO("Load pkmn sprite sheets: %d", PKMN_ANIM_COUNT); - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes + if constexpr (features::EnablePkmnEmbeddedAssets) { + // Load pkmn + if (should_load_pkmn(*ret->anim._local_copy_config)) { + BONGOCAT_LOG_INFO("Load pkmn sprite sheets: %d", PKMN_ANIM_COUNT); + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes - ctx.shm->pkmn_anims = platform::make_allocated_mmap_array(PKMN_ANIM_COUNT); + ctx.shm->pkmn_anims = platform::make_allocated_mmap_array(PKMN_ANIM_COUNT); #ifdef FEATURE_PKMN_EMBEDDED_ASSETS - // pkmn -#include "pkmn_init_pkmn_anim.cpp.inl" + // pkmn +# include "pkmn_init_pkmn_anim.cpp.inl" #endif - } - } - if constexpr (features::EnablePmdEmbeddedAssets) { - // Load pmd (pkmn) - if (should_load_pkmn(*ret->anim._local_copy_config)) { - BONGOCAT_LOG_INFO("Load pmd sprite sheets: %d", PKMN_ANIM_COUNT); - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes - - ctx.shm->pmd_anims = platform::make_allocated_mmap_array(PMD_ANIM_COUNT); + } + } + if constexpr (features::EnablePmdEmbeddedAssets) { + // Load pmd (pkmn) + if (should_load_pkmn(*ret->anim._local_copy_config)) { + BONGOCAT_LOG_INFO("Load pmd sprite sheets: %d", PKMN_ANIM_COUNT); + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes + + ctx.shm->pmd_anims = platform::make_allocated_mmap_array(PMD_ANIM_COUNT); #ifdef FEATURE_PMD_EMBEDDED_ASSETS - // pmd (pkmn) -#include "pmd_init_custom_anim.cpp.inl" + // pmd (pkmn) +# include "pmd_init_custom_anim.cpp.inl" #endif - } - } - - if constexpr (features::EnableMiscEmbeddedAssets) { - // Load Misc Pets (neko) - if (should_load_misc(*ret->anim._local_copy_config)) { - BONGOCAT_LOG_INFO("Load Misc sprite sheets: %d", MISC_ANIM_COUNT); - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes - - ctx.shm->misc_anims = platform::make_allocated_mmap_array(MISC_ANIM_COUNT); - - // neko - init_misc_anim(ctx, MISC_NEKO_ANIM_INDEX, get_misc_sprite_sheet(MISC_NEKO_ANIM_INDEX), get_misc_sprite_sheet_columns(MISC_NEKO_ANIM_INDEX)); - } - } - - if constexpr (features::EnableCustomSpriteSheetsAssets) { - assert(ret->anim._local_copy_config.ptr); - // Load custom sprite sheet - if (should_load_custom(*ret->anim._local_copy_config)) { - assert(ret->anim.shm != nullptr); - animation_context_t& ctx = ret->anim; // alias for inits in includes - assert(ctx.shm.ptr); - assert(ctx._local_copy_config.ptr); - - if (ctx._local_copy_config->_custom) { - BONGOCAT_LOG_INFO("Load custom sprite sheets: %s", ctx._local_copy_config->custom_sprite_sheet_filename); - - auto result = details::anim_load_custom_animation(ctx, *ctx._local_copy_config); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) { - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - - ctx.shm->anim = bongocat::move(result.result); - } - } - } - - /// @NOTE(assets): 7. add pre-load asset - } - [[maybe_unused]] const auto t1 = platform::get_current_time_us(); + } + } + + if constexpr (features::EnableMiscEmbeddedAssets) { + // Load Misc Pets (neko) + if (should_load_misc(*ret->anim._local_copy_config)) { + BONGOCAT_LOG_INFO("Load Misc sprite sheets: %d", MISC_ANIM_COUNT); + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes - // init anim - ret->anim._rng = platform::random_xoshiro128(platform::slow_rand()); + ctx.shm->misc_anims = platform::make_allocated_mmap_array(MISC_ANIM_COUNT); - BONGOCAT_LOG_INFO("Animation system initialized successfully with embedded assets; load assets in %.3fms (%.6fsec)", static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); - return ret; + // neko + init_misc_anim(ctx, MISC_NEKO_ANIM_INDEX, get_misc_sprite_sheet(MISC_NEKO_ANIM_INDEX), + get_misc_sprite_sheet_columns(MISC_NEKO_ANIM_INDEX)); + } } - void stop(animation_context_t& ctx) { - atomic_store(&ctx._animation_running, false); - if (ctx._anim_thread) { - BONGOCAT_LOG_DEBUG("Stopping animation thread"); - // Wait for thread to finish gracefully - //pthread_cancel(ctx->_anim_thread); - if (platform::stop_thread_graceful_or_cancel(ctx._anim_thread, ctx._animation_running) != 0) { - BONGOCAT_LOG_ERROR("Failed to join animation thread: %s", strerror(errno)); - } - BONGOCAT_LOG_DEBUG("Animation thread terminated"); + if constexpr (features::EnableCustomSpriteSheetsAssets) { + assert(ret->anim._local_copy_config.ptr); + // Load custom sprite sheet + if (should_load_custom(*ret->anim._local_copy_config)) { + assert(ret->anim.shm != nullptr); + animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ctx.shm.ptr); + assert(ctx._local_copy_config.ptr); + + if (ctx._local_copy_config->_custom) { + BONGOCAT_LOG_INFO("Load custom sprite sheets: %s", ctx._local_copy_config->custom_sprite_sheet_filename); + + auto result = details::anim_load_custom_animation(ctx, *ctx._local_copy_config); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) { + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + + ctx.shm->anim = bongocat::move(result.result); } - ctx._anim_thread = 0; + } + } + + /// @NOTE(assets): 7. add pre-load asset + } + [[maybe_unused]] const auto t1 = platform::get_current_time_us(); + + // init anim + ret->anim._rng = platform::random_xoshiro128(platform::slow_rand()); - ctx.config_updated.notify_all(); + BONGOCAT_LOG_INFO("Animation system initialized successfully with embedded assets; load assets in %.3fms (%.6fsec)", + static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); + return ret; +} + +void stop(animation_context_t& ctx) { + atomic_store(&ctx._animation_running, false); + if (ctx._anim_thread) { + BONGOCAT_LOG_DEBUG("Stopping animation thread"); + // Wait for thread to finish gracefully + // pthread_cancel(ctx->_anim_thread); + if (platform::stop_thread_graceful_or_cancel(ctx._anim_thread, ctx._animation_running) != 0) { + BONGOCAT_LOG_ERROR("Failed to join animation thread: %s", strerror(errno)); } + BONGOCAT_LOG_DEBUG("Animation thread terminated"); + } + ctx._anim_thread = 0; + + ctx.config_updated.notify_all(); } +} // namespace bongocat::animation diff --git a/src/graphics/bar.cpp b/src/graphics/bar.cpp index b49bf7e0..f9053b91 100644 --- a/src/graphics/bar.cpp +++ b/src/graphics/bar.cpp @@ -1,839 +1,907 @@ -#include "platform/wayland-protocols.hpp" - #include "bar.h" -#include "graphics/drawing.h" -#include "graphics/animation_context.h" -#include "platform/wayland.h" -#include "platform/wayland_callbacks.h" -#include "graphics/animation.h" -#include -#include #include "embedded_assets/bongocat/bongocat.h" #include "embedded_assets/misc/misc.hpp" +#include "graphics/animation.h" +#include "graphics/animation_context.h" +#include "graphics/drawing.h" #include "graphics/embedded_assets_dms.h" #include "graphics/embedded_assets_pkmn.h" +#include "platform/wayland-protocols.hpp" +#include "platform/wayland.h" +#include "platform/wayland_callbacks.h" -namespace bongocat::animation { - inline static uint32_t DEFAULT_FILL_COLOR = 0x00000000; // ARGB - inline static uint32_t DEBUG_MOVEMENT_BAR_COLOR = 0xFFFF0000; // ARGB - - // ============================================================================= - // DRAWING MANAGEMENT - // ============================================================================= - - struct cat_rect_t { - int x; - int y; - int width; - int height; - }; - - template - /// @TODO: required SpriteSheet must be _sprite_sheet_t - cat_rect_t get_position(const platform::wayland::wayland_context_t& wayland_ctx, const SpriteSheet& sheet, const config::config_t& config) { - const int cat_height = config.cat_height; - const int cat_width = static_cast(static_cast(cat_height) * (static_cast(sheet.frame_width) / static_cast(sheet.frame_height))); - - int cat_x = 0; - switch (config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - cat_x = (wayland_ctx._screen_width - cat_width) / 2 + config.cat_x_offset; - break; - case config::align_type_t::ALIGN_LEFT: - cat_x = config.cat_x_offset; - break; - case config::align_type_t::ALIGN_RIGHT: - cat_x = wayland_ctx._screen_width - cat_width - config.cat_x_offset; - break; - default: - BONGOCAT_LOG_VERBOSE("Invalid cat_align %d", config.cat_align); - break; - } - const int cat_y = (wayland_ctx._bar_height - cat_height) / 2 + config.cat_y_offset; - - return { .x = cat_x, .y = cat_y, .width = cat_width, .height = cat_height }; - } +#include +#include - /// @TODO: make draw_sprite more generic (template?) - void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const bongocat_sprite_sheet_t& sheet, blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { - using namespace assets; - if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { - return; - } +namespace bongocat::animation { +inline static uint32_t DEFAULT_FILL_COLOR = 0x00000000; // ARGB +inline static uint32_t DEBUG_MOVEMENT_BAR_COLOR = 0xFFFF0000; // ARGB + +// ============================================================================= +// DRAWING MANAGEMENT +// ============================================================================= + +struct cat_rect_t { + int x; + int y; + int width; + int height; +}; + +template +/// @TODO: required SpriteSheet must be _sprite_sheet_t +cat_rect_t get_position(const platform::wayland::wayland_context_t& wayland_ctx, const SpriteSheet& sheet, + const config::config_t& config) { + const int cat_height = config.cat_height; + const int cat_width = static_cast(static_cast(cat_height) * (static_cast(sheet.frame_width) / + static_cast(sheet.frame_height))); + + int cat_x = 0; + switch (config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + cat_x = (wayland_ctx._screen_width - cat_width) / 2 + config.cat_x_offset; + break; + case config::align_type_t::ALIGN_LEFT: + cat_x = config.cat_x_offset; + break; + case config::align_type_t::ALIGN_RIGHT: + cat_x = wayland_ctx._screen_width - cat_width - config.cat_x_offset; + break; + default: + BONGOCAT_LOG_VERBOSE("Invalid cat_align %d", config.cat_align); + break; + } + const int cat_y = (wayland_ctx._bar_height - cat_height) / 2 + config.cat_y_offset; + + return {.x = cat_x, .y = cat_y, .width = cat_width, .height = cat_height}; +} - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - //platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - const animation_shared_memory_t& anim_shm = *anim.shm; - - uint8_t *pixels = shm_buffer.pixels.data; - const size_t pixels_size = shm_buffer.pixels._size_bytes; - - const sprite_sheet_animation_frame_t* region = nullptr; - switch (anim_shm.animation_player_result.sprite_sheet_col) { - case BONGOCAT_FRAME_BOTH_UP: - region = &sheet.both_up; - break; - case BONGOCAT_FRAME_LEFT_DOWN: - region = &sheet.left_down; - break; - case BONGOCAT_FRAME_RIGHT_DOWN: - region = &sheet.right_down; - break; - case BONGOCAT_FRAME_BOTH_DOWN: - region = &sheet.both_down; - break; - default: - assert(anim_shm.animation_player_result.sprite_sheet_col >= 0 && static_cast(anim_shm.animation_player_result.sprite_sheet_col) < BONGOCAT_SPRITE_SHEET_COLS); - break; +/// @TODO: make draw_sprite more generic (template?) +void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, + const bongocat_sprite_sheet_t& sheet, + blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { + using namespace assets; + if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { + return; + } + + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; + + assert(wayland_ctx._local_copy_config != nullptr); + assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + const animation_shared_memory_t& anim_shm = *anim.shm; + + uint8_t *pixels = shm_buffer.pixels.data; + const size_t pixels_size = shm_buffer.pixels._size_bytes; + + const sprite_sheet_animation_frame_t *region = nullptr; + switch (anim_shm.animation_player_result.sprite_sheet_col) { + case BONGOCAT_FRAME_BOTH_UP: + region = &sheet.both_up; + break; + case BONGOCAT_FRAME_LEFT_DOWN: + region = &sheet.left_down; + break; + case BONGOCAT_FRAME_RIGHT_DOWN: + region = &sheet.right_down; + break; + case BONGOCAT_FRAME_BOTH_DOWN: + region = &sheet.both_down; + break; + default: + assert(anim_shm.animation_player_result.sprite_sheet_col >= 0 && + static_cast(anim_shm.animation_player_result.sprite_sheet_col) < BONGOCAT_SPRITE_SHEET_COLS); + break; + } + + auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); + auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); + + if (region) { + // draw debug rectangle + if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + cat_rect_t movement_debug_bar{}; + switch (current_config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_LEFT: + movement_debug_bar = { + .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_RIGHT: + movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + } + + // Skip fullscreen hiding when layer is LAYER_OVERLAY (always visible) + const bool is_overlay_layer = current_config.layer == config::layer_type_t::LAYER_OVERLAY; + const bool is_fullscreen = !is_overlay_layer && wayland_ctx._fullscreen_detected; + const bool bar_visible = + !is_fullscreen && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; + const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; + const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & + (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian + auto *p = reinterpret_cast(pixels); + assert(wayland_ctx._screen_width >= 0); + [[maybe_unused]] const size_t total_pixels = + static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); + for (int32_t y = movement_debug_bar.y; + y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { + for (int32_t x = movement_debug_bar.x; + x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { + if (x >= 0 && y >= 0) { + size_t pi = + static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + assert(pi < total_pixels); + p[pi] = fill; + } } + } + } - auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); - auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - - if (region) { - // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { - cat_rect_t movement_debug_bar{}; - switch (current_config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = { .x = cat_x + cat_width/2 - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - case config::align_type_t::ALIGN_LEFT: - movement_debug_bar = { .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = { .x = cat_x + cat_width - current_config.movement_radius*2, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - } - - // Skip fullscreen hiding when layer is LAYER_OVERLAY (always visible) - const bool is_overlay_layer = current_config.layer == config::layer_type_t::LAYER_OVERLAY; - const bool is_fullscreen = !is_overlay_layer && wayland_ctx._fullscreen_detected; - const bool bar_visible = !is_fullscreen && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; - const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; - const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian - auto *p = reinterpret_cast(pixels); - assert(wayland_ctx._screen_width >= 0); - [[maybe_unused]] const size_t total_pixels = static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); - for (int32_t y = movement_debug_bar.y;y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { - for (int32_t x = movement_debug_bar.x;x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { - if (x >= 0 && y >= 0) { - size_t pi = static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); - assert(pi < total_pixels); - p[pi] = fill; - } - } - } - } - - blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); - } - if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } else { - if (current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } - if (current_config.mirror_y) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); - } - if (current_config.enable_antialiasing) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); - } - if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { - drawing_option = flag_add(drawing_option, extra_drawing_option); - } - - blit_image_scaled(pixels, pixels_size, - wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, - sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, sheet.image.sprite_sheet_height, sheet.image.channels, - region->col * sheet.frame_width, region->row * sheet.frame_height, - sheet.frame_width, sheet.frame_height, - cat_x_with_offset, cat_y, cat_width, cat_height, - blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, drawing_option); - } + blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; + if (current_config.invert_color) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); + } + if (anim_shm.anim_direction >= 1.0f) { + if (!current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } else { + if (current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } + if (current_config.mirror_y) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); + } + if (current_config.enable_antialiasing) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); + } + if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { + drawing_option = flag_add(drawing_option, extra_drawing_option); } - void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const dm_sprite_sheet_t& sheet, blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { - using namespace assets; - if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { - return; - } + blit_image_scaled(pixels, pixels_size, wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, + sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, + sheet.image.sprite_sheet_height, sheet.image.channels, region->col * sheet.frame_width, + region->row * sheet.frame_height, sheet.frame_width, sheet.frame_height, cat_x_with_offset, cat_y, + cat_width, cat_height, blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, + drawing_option); + } +} - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - //platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - const animation_shared_memory_t& anim_shm = *anim.shm; - - uint8_t *pixels = shm_buffer.pixels.data; - const size_t pixels_size = shm_buffer.pixels._size_bytes; - - const sprite_sheet_animation_frame_t* region = nullptr; - switch (anim_shm.animation_player_result.sprite_sheet_col) { - case DM_FRAME_IDLE1: - region = &sheet.frames.idle_1; - break; - case DM_FRAME_IDLE2: - region = &sheet.frames.idle_2; - break; - case DM_FRAME_ANGRY: - region = &sheet.frames.angry; - break; - case DM_FRAME_DOWN: - region = &sheet.frames.down; - break; - case DM_FRAME_HAPPY: - region = &sheet.frames.happy; - break; - case DM_FRAME_EAT1: - region = &sheet.frames.eat_1; - break; - case DM_FRAME_SLEEP: - region = &sheet.frames.sleep; - break; - case DM_FRAME_REFUSE: - region = &sheet.frames.refuse; - break; - case DM_FRAME_SAD: - region = &sheet.frames.sad; - break; - case DM_FRAME_LOSE1: - region = &sheet.frames.lose_1; - break; - case DM_FRAME_EAT2: - region = &sheet.frames.eat_2; - break; - case DM_FRAME_LOSE2: - region = &sheet.frames.lose_2; - break; - case DM_FRAME_ATTACK: - region = &sheet.frames.attack_1; - break; - case DM_FRAME_MOVEMENT1: - region = &sheet.frames.movement_1; - break; - case DM_FRAME_MOVEMENT2: - region = &sheet.frames.movement_2; - break; - default: - assert(anim_shm.animation_player_result.sprite_sheet_col >= 0 && static_cast(anim_shm.animation_player_result.sprite_sheet_col) < DM_SPRITE_SHEET_MAX_COLS); - break; +void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, + const dm_sprite_sheet_t& sheet, + blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { + using namespace assets; + if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { + return; + } + + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; + + assert(wayland_ctx._local_copy_config != nullptr); + assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + const animation_shared_memory_t& anim_shm = *anim.shm; + + uint8_t *pixels = shm_buffer.pixels.data; + const size_t pixels_size = shm_buffer.pixels._size_bytes; + + const sprite_sheet_animation_frame_t *region = nullptr; + switch (anim_shm.animation_player_result.sprite_sheet_col) { + case DM_FRAME_IDLE1: + region = &sheet.frames.idle_1; + break; + case DM_FRAME_IDLE2: + region = &sheet.frames.idle_2; + break; + case DM_FRAME_ANGRY: + region = &sheet.frames.angry; + break; + case DM_FRAME_DOWN: + region = &sheet.frames.down; + break; + case DM_FRAME_HAPPY: + region = &sheet.frames.happy; + break; + case DM_FRAME_EAT1: + region = &sheet.frames.eat_1; + break; + case DM_FRAME_SLEEP: + region = &sheet.frames.sleep; + break; + case DM_FRAME_REFUSE: + region = &sheet.frames.refuse; + break; + case DM_FRAME_SAD: + region = &sheet.frames.sad; + break; + case DM_FRAME_LOSE1: + region = &sheet.frames.lose_1; + break; + case DM_FRAME_EAT2: + region = &sheet.frames.eat_2; + break; + case DM_FRAME_LOSE2: + region = &sheet.frames.lose_2; + break; + case DM_FRAME_ATTACK: + region = &sheet.frames.attack_1; + break; + case DM_FRAME_MOVEMENT1: + region = &sheet.frames.movement_1; + break; + case DM_FRAME_MOVEMENT2: + region = &sheet.frames.movement_2; + break; + default: + assert(anim_shm.animation_player_result.sprite_sheet_col >= 0 && + static_cast(anim_shm.animation_player_result.sprite_sheet_col) < DM_SPRITE_SHEET_MAX_COLS); + break; + } + + auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); + auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); + + if (region) { + // draw debug rectangle + if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + cat_rect_t movement_debug_bar{}; + switch (current_config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_LEFT: + movement_debug_bar = { + .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_RIGHT: + movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + } + + const bool bar_visible = + !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; + const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; + const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & + (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian + auto *p = reinterpret_cast(pixels); + assert(wayland_ctx._screen_width >= 0); + [[maybe_unused]] const size_t total_pixels = + static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); + for (int32_t y = movement_debug_bar.y; + y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { + for (int32_t x = movement_debug_bar.x; + x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { + if (x >= 0 && y >= 0) { + size_t pi = + static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + assert(pi < total_pixels); + p[pi] = fill; + } } + } + } - auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); - auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - - if (region) { - // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { - cat_rect_t movement_debug_bar{}; - switch (current_config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = { .x = cat_x + cat_width/2 - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - case config::align_type_t::ALIGN_LEFT: - movement_debug_bar = { .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = { .x = cat_x + cat_width - current_config.movement_radius*2, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - } - - const bool bar_visible = !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; - const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; - const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian - auto *p = reinterpret_cast(pixels); - assert(wayland_ctx._screen_width >= 0); - [[maybe_unused]] const size_t total_pixels = static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); - for (int32_t y = movement_debug_bar.y;y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { - for (int32_t x = movement_debug_bar.x;x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { - if (x >= 0 && y >= 0) { - size_t pi = static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); - assert(pi < total_pixels); - p[pi] = fill; - } - } - } - } + blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; + if (current_config.invert_color) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); + } + if (anim_shm.anim_direction >= 1.0f) { + if (!current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } else { + if (current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } + if (current_config.mirror_y) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); + } + if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { + drawing_option = flag_add(drawing_option, extra_drawing_option); + } - blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); - } - if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } else { - if (current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } - if (current_config.mirror_y) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); - } - if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { - drawing_option = flag_add(drawing_option, extra_drawing_option); - } + blit_image_scaled(pixels, pixels_size, wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, + sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, + sheet.image.sprite_sheet_height, sheet.image.channels, region->col * sheet.frame_width, + region->row * sheet.frame_height, sheet.frame_width, sheet.frame_height, cat_x_with_offset, cat_y, + cat_width, cat_height, blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, + drawing_option); + } +} - blit_image_scaled(pixels, pixels_size, - wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, - sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, sheet.image.sprite_sheet_height, sheet.image.channels, - region->col * sheet.frame_width, region->row * sheet.frame_height, - sheet.frame_width, sheet.frame_height, - cat_x_with_offset, cat_y, cat_width, cat_height, - blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, drawing_option); +void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, + const pkmn_sprite_sheet_t& sheet, + blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { + using namespace assets; + if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { + return; + } + + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; + + assert(wayland_ctx._local_copy_config != nullptr); + assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + const animation_shared_memory_t& anim_shm = *anim.shm; + + uint8_t *pixels = shm_buffer.pixels.data; + const size_t pixels_size = shm_buffer.pixels._size_bytes; + + const sprite_sheet_animation_frame_t *region = nullptr; + switch (anim_shm.animation_player_result.sprite_sheet_col) { + case PKMN_FRAME_IDLE1: + region = &sheet.idle_1; + break; + case PKMN_FRAME_IDLE2: + region = &sheet.idle_2; + break; + default: + assert(anim_shm.animation_player_result.sprite_sheet_col >= 0 && + static_cast(anim_shm.animation_player_result.sprite_sheet_col) < PKMN_SPRITE_SHEET_COLS); + break; + } + + auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); + auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); + + if (region) { + // draw debug rectangle + if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + cat_rect_t movement_debug_bar{}; + switch (current_config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_LEFT: + movement_debug_bar = { + .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_RIGHT: + movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + } + + const bool bar_visible = + !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; + const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; + const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & + (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian + auto *p = reinterpret_cast(pixels); + assert(wayland_ctx._screen_width >= 0); + [[maybe_unused]] const size_t total_pixels = + static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); + for (int32_t y = movement_debug_bar.y; + y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { + for (int32_t x = movement_debug_bar.x; + x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { + if (x >= 0 && y >= 0) { + size_t pi = + static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + assert(pi < total_pixels); + p[pi] = fill; + } } + } } - void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const pkmn_sprite_sheet_t& sheet, blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { - using namespace assets; - if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { - return; - } - - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - //platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - const animation_shared_memory_t& anim_shm = *anim.shm; - - uint8_t *pixels = shm_buffer.pixels.data; - const size_t pixels_size = shm_buffer.pixels._size_bytes; - - const sprite_sheet_animation_frame_t* region = nullptr; - switch (anim_shm.animation_player_result.sprite_sheet_col) { - case PKMN_FRAME_IDLE1: - region = &sheet.idle_1; - break; - case PKMN_FRAME_IDLE2: - region = &sheet.idle_2; - break; - default: - assert(anim_shm.animation_player_result.sprite_sheet_col >= 0 && static_cast(anim_shm.animation_player_result.sprite_sheet_col) < PKMN_SPRITE_SHEET_COLS); - break; - } + blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; + if (current_config.invert_color) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); + } + if (anim_shm.anim_direction >= 1.0f) { + if (!current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } else { + if (current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } + if (current_config.mirror_y) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); + } + if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { + drawing_option = flag_add(drawing_option, extra_drawing_option); + } - auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); - auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - - if (region) { - // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { - cat_rect_t movement_debug_bar{}; - switch (current_config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = { .x = cat_x + cat_width/2 - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - case config::align_type_t::ALIGN_LEFT: - movement_debug_bar = { .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = { .x = cat_x + cat_width - current_config.movement_radius*2, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; - break; - } - - const bool bar_visible = !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; - const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; - const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian - auto *p = reinterpret_cast(pixels); - assert(wayland_ctx._screen_width >= 0); - [[maybe_unused]] const size_t total_pixels = static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); - for (int32_t y = movement_debug_bar.y;y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { - for (int32_t x = movement_debug_bar.x;x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { - if (x >= 0 && y >= 0) { - size_t pi = static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); - assert(pi < total_pixels); - p[pi] = fill; - } - } - } - } + blit_image_scaled(pixels, pixels_size, wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, + sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, + sheet.image.sprite_sheet_height, sheet.image.channels, region->col * sheet.frame_width, + region->row * sheet.frame_height, sheet.frame_width, sheet.frame_height, cat_x_with_offset, cat_y, + cat_width, cat_height, blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, + drawing_option); + } +} - blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); - } - if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } else { - if (current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } - if (current_config.mirror_y) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); - } - if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { - drawing_option = flag_add(drawing_option, extra_drawing_option); - } +void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, + const ms_agent_sprite_sheet_t& sheet, int col, int row) { + if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { + return; + } + + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; + + assert(wayland_ctx._local_copy_config != nullptr); + // assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + // const animation_shared_memory_t& anim_shm = *anim.shm; + + uint8_t *pixels = shm_buffer.pixels.data; + const size_t pixels_size = shm_buffer.pixels._size_bytes; + + auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); + + blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; + if (current_config.invert_color) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); + } + if (current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + if (current_config.mirror_y) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); + } + if (current_config.enable_antialiasing) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); + } + + blit_image_scaled(pixels, pixels_size, wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, + sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, + sheet.image.sprite_sheet_height, sheet.image.channels, col * sheet.frame_width, + row * sheet.frame_height, sheet.frame_width, sheet.frame_height, cat_x, cat_y, cat_width, + cat_height, blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, drawing_option); +} - blit_image_scaled(pixels, pixels_size, - wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, - sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, sheet.image.sprite_sheet_height, sheet.image.channels, - region->col * sheet.frame_width, region->row * sheet.frame_height, - sheet.frame_width, sheet.frame_height, - cat_x_with_offset, cat_y, cat_width, cat_height, - blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, drawing_option); - } +enum class draw_sprite_overwrite_option_t : uint32_t { + None = 0, + MovementNoMirror = (1 << 0), + MovementMirror = (1 << 1), +}; +void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, + const custom_sprite_sheet_t& sheet, int col, int row, + draw_sprite_overwrite_option_t overwrite_option = draw_sprite_overwrite_option_t::None) { + if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { + return; + } + + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; + + assert(wayland_ctx._local_copy_config != nullptr); + assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + const animation_shared_memory_t& anim_shm = *anim.shm; + + uint8_t *pixels = shm_buffer.pixels.data; + const size_t pixels_size = shm_buffer.pixels._size_bytes; + + auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); + auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); + + // draw debug rectangle + if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + cat_rect_t movement_debug_bar{}; + switch (current_config.cat_align) { + case config::align_type_t::ALIGN_CENTER: + movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_LEFT: + movement_debug_bar = { + .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; + break; + case config::align_type_t::ALIGN_RIGHT: + movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + .y = 0, + .width = current_config.movement_radius * 2, + .height = wayland_ctx._bar_height}; + break; } - void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const ms_agent_sprite_sheet_t& sheet, int col, int row) { - if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { - return; + const bool bar_visible = + !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; + const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; + const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & + (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian + auto *p = reinterpret_cast(pixels); + assert(wayland_ctx._screen_width >= 0); + [[maybe_unused]] const size_t total_pixels = + static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); + for (int32_t y = movement_debug_bar.y; + y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { + for (int32_t x = movement_debug_bar.x; + x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { + if (x >= 0 && y >= 0) { + size_t pi = static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + assert(pi < total_pixels); + p[pi] = fill; } + } + } + } + + blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; + if (current_config.invert_color) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); + } + if (current_config.mirror_y) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); + } + if (current_config.enable_antialiasing) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); + } + switch (overwrite_option) { + case draw_sprite_overwrite_option_t::None: + if (anim_shm.anim_direction >= 1.0f) { + if (!current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } else { + if (current_config.mirror_x) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + } + break; + case draw_sprite_overwrite_option_t::MovementNoMirror: + if (anim_shm.anim_direction >= 1.0f) { + drawing_option = flag_remove(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + break; + case draw_sprite_overwrite_option_t::MovementMirror: + if (anim_shm.anim_direction < 0.0f) { + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + } + break; + } + + blit_image_scaled(pixels, pixels_size, wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, + sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, + sheet.image.sprite_sheet_height, sheet.image.channels, col * sheet.frame_width, + row * sheet.frame_height, sheet.frame_width, sheet.frame_height, cat_x_with_offset, cat_y, + cat_width, cat_height, blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, + drawing_option); +} - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - //platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - - assert(wayland_ctx._local_copy_config != nullptr); - //assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - //const animation_shared_memory_t& anim_shm = *anim.shm; - - uint8_t *pixels = shm_buffer.pixels.data; - const size_t pixels_size = shm_buffer.pixels._size_bytes; - - auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); - - blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); +static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, + platform::wayland::wayland_shm_buffer_t& shm_buffer) { + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; + + // read-only + assert(wayland_ctx._local_copy_config != nullptr); + assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + + assert(shm_buffer.pixels.data); + uint8_t *pixels = shm_buffer.pixels.data; + const size_t pixels_size = shm_buffer.pixels._size_bytes; + + const bool bar_visible = + !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; + const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; + + assert(wayland_ctx._screen_width >= 0); + assert(wayland_ctx._bar_height >= 0); + assert(effective_opacity >= 0); + + // Fast clear with 32-bit fill + const uint32_t fill = DEFAULT_FILL_COLOR | (static_cast(effective_opacity) << 24u); // RGBA, little-endian + auto *p = reinterpret_cast(pixels); + const size_t total_pixels = + static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); + if (current_config.enable_debug) { + if (const size_t expected_bytes = total_pixels * sizeof(uint32_t); expected_bytes > pixels_size) { + BONGOCAT_LOG_VERBOSE("draw_bar: pixel write would overflow buffer (expected %zu bytes, have %zu). Aborting draw.", + expected_bytes, pixels_size); + return false; + } + } + for (size_t i = 0; i < total_pixels; i++) { + p[i] = fill; + } + + { + platform::LockGuard guard(anim.anim_lock); + const animation_shared_memory_t& anim_shm = *anim.shm; + + if (bar_visible) { + switch (anim_shm.anim_type) { + case config::config_animation_sprite_sheet_layout_t::None: + break; + case config::config_animation_sprite_sheet_layout_t::Bongocat: { + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.bongocat_anims.count); } - if (current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); + const animation_t& cat_anim = get_current_animation(anim); + assert(cat_anim.type == animation_t::Type::Bongocat); + const bongocat_sprite_sheet_t& sheet = cat_anim.bongocat; + draw_sprite(ctx, shm_buffer, sheet, + current_config.enable_antialiasing ? blit_image_color_option_flags_t::BilinearInterpolation + : blit_image_color_option_flags_t::Normal); + } break; + case config::config_animation_sprite_sheet_layout_t::Dm: { + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + switch (anim_shm.anim_dm_set) { + case config::config_animation_dm_set_t::None: + break; + case config::config_animation_dm_set_t::min_dm: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.min_dm_anims.count); + } break; + case config::config_animation_dm_set_t::dm: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dm_anims.count); + } break; + case config::config_animation_dm_set_t::dm20: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dm20_anims.count); + } break; + case config::config_animation_dm_set_t::dmx: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dmx_anims.count); + } break; + case config::config_animation_dm_set_t::pen: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pen_anims.count); + } break; + case config::config_animation_dm_set_t::pen20: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pen20_anims.count); + } break; + case config::config_animation_dm_set_t::dmc: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dmc_anims.count); + } break; + case config::config_animation_dm_set_t::dmall: { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dmall_anims.count); + } break; + } } - if (current_config.mirror_y) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); + const animation_t& dm_anim = get_current_animation(anim); + assert(dm_anim.type == animation_t::Type::Dm); + const dm_sprite_sheet_t& sheet = dm_anim.dm; + draw_sprite(ctx, shm_buffer, sheet); + } break; + case config::config_animation_sprite_sheet_layout_t::Pkmn: { + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pkmn_anims.count); } - if (current_config.enable_antialiasing) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); + const animation_t& pkmn_anim = get_current_animation(anim); + assert(pkmn_anim.type == animation_t::Type::Pkmn); + const auto& sheet = pkmn_anim.pkmn; + draw_sprite(ctx, shm_buffer, sheet); + } break; + case config::config_animation_sprite_sheet_layout_t::MsAgent: { + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.ms_anims.count); } - - blit_image_scaled(pixels, pixels_size, - wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, - sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, sheet.image.sprite_sheet_height, sheet.image.channels, - col * sheet.frame_width, row * sheet.frame_height, - sheet.frame_width, sheet.frame_height, - cat_x, cat_y, cat_width, cat_height, - blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, drawing_option); - } - - enum class draw_sprite_overwrite_option_t : uint32_t { - None = 0, - MovementNoMirror = (1 << 0), - MovementMirror = (1 << 1), - }; - void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const custom_sprite_sheet_t& sheet, int col, int row, draw_sprite_overwrite_option_t overwrite_option = draw_sprite_overwrite_option_t::None) { - if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { - return; - } - - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - //platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - const animation_shared_memory_t& anim_shm = *anim.shm; - - uint8_t *pixels = shm_buffer.pixels.data; - const size_t pixels_size = shm_buffer.pixels._size_bytes; - - auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); - auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - - // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { - cat_rect_t movement_debug_bar{}; - switch (current_config.cat_align) { - case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = { .x = cat_x + cat_width/2 - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; + const animation_t& ms_anim = get_current_animation(anim); + assert(ms_anim.type == animation_t::Type::MsAgent); + const ms_agent_sprite_sheet_t& sheet = ms_anim.ms_agent; + const int col = anim_shm.animation_player_result.sprite_sheet_col; + const int row = anim_shm.animation_player_result.sprite_sheet_row; + draw_sprite(ctx, shm_buffer, sheet, col, row); + } break; + case config::config_animation_sprite_sheet_layout_t::Custom: { + const int col = anim_shm.animation_player_result.sprite_sheet_col; + const int row = anim_shm.animation_player_result.sprite_sheet_row; + assert(anim_shm.anim_index >= 0); + switch (anim_shm.anim_custom_set) { + case config::config_animation_custom_set_t::None: + break; + case config::config_animation_custom_set_t::misc: + if (features::EnableMiscEmbeddedAssets) { + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.misc_anims.count); + } + const animation_t& custom_anim = get_current_animation(anim); + assert(custom_anim.type == animation_t::Type::Custom); + const custom_sprite_sheet_t& sheet = custom_anim.custom; + draw_sprite(ctx, shm_buffer, sheet, col, row); + } + break; + case config::config_animation_custom_set_t::pmd: + if (features::EnablePmdEmbeddedAssets) { + if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { + assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pmd_anims.count); + } + const animation_t& custom_anim = get_current_animation(anim); + assert(custom_anim.type == animation_t::Type::Custom); + const custom_sprite_sheet_t& sheet = custom_anim.custom; + draw_sprite_overwrite_option_t overwrite_mirror_x{draw_sprite_overwrite_option_t::None}; + /* + switch (anim_shm.animation_player_result.overwrite_mirror_x) { + case animation_player_custom_overwrite_mirror_x::None: break; - case config::align_type_t::ALIGN_LEFT: - movement_debug_bar = { .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; + case animation_player_custom_overwrite_mirror_x::NoMirror: + overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementNoMirror; break; - case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = { .x = cat_x + cat_width - current_config.movement_radius*2, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height }; + case animation_player_custom_overwrite_mirror_x::Mirror: + overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementMirror; break; } - - const bool bar_visible = !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; - const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; - const uint32_t fill = DEBUG_MOVEMENT_BAR_COLOR & (0x00FFFFFF | (static_cast(effective_opacity) << 24u)); // RGBA, little-endian - auto *p = reinterpret_cast(pixels); - assert(wayland_ctx._screen_width >= 0); - [[maybe_unused]] const size_t total_pixels = static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); - for (int32_t y = movement_debug_bar.y;y < movement_debug_bar.y + movement_debug_bar.height && y < wayland_ctx._bar_height; y++) { - for (int32_t x = movement_debug_bar.x;x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { - if (x >= 0 && y >= 0) { - size_t pi = static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); - assert(pi < total_pixels); - p[pi] = fill; - } - } + */ + draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_mirror_x); + } + break; + case config::config_animation_custom_set_t::custom: + if (features::EnableCustomSpriteSheetsAssets && anim_shm.anim_index >= 0 && + static_cast(anim_shm.anim_index) == assets::CUSTOM_ANIM_INDEX) { + const animation_t& custom_anim = get_current_animation(anim); + assert(custom_anim.type == animation_t::Type::Custom); + const custom_sprite_sheet_t& sheet = custom_anim.custom; + draw_sprite_overwrite_option_t overwrite_mirror_x{draw_sprite_overwrite_option_t::None}; + switch (anim_shm.animation_player_result.overwrite_mirror_x) { + case animation_player_custom_overwrite_mirror_x::None: + break; + case animation_player_custom_overwrite_mirror_x::NoMirror: + overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementNoMirror; + break; + case animation_player_custom_overwrite_mirror_x::Mirror: + overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementMirror; + break; } + draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_mirror_x); + } + break; } - - blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); - } - if (current_config.mirror_y) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); - } - if (current_config.enable_antialiasing) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); - } - switch (overwrite_option) { - case draw_sprite_overwrite_option_t::None: - if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } else { - if (current_config.mirror_x) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - } - break; - case draw_sprite_overwrite_option_t::MovementNoMirror: - if (anim_shm.anim_direction >= 1.0f) { - drawing_option = flag_remove(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - break; - case draw_sprite_overwrite_option_t::MovementMirror: - if (anim_shm.anim_direction < 0.0f) { - drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); - } - break; - } - - blit_image_scaled(pixels, pixels_size, - wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, - sheet.image.pixels.data, sheet.image.pixels._size_bytes, sheet.image.sprite_sheet_width, sheet.image.sprite_sheet_height, sheet.image.channels, - col * sheet.frame_width, row * sheet.frame_height, - sheet.frame_width, sheet.frame_height, - cat_x_with_offset, cat_y, cat_width, cat_height, - blit_image_color_order_t::BGRA, blit_image_color_order_t::RGBA, drawing_option); + } break; + } + } else { + BONGOCAT_LOG_VERBOSE("skip drawing, keep buffer clean: fullscreen=%d, visibility=%d", + wayland_ctx._fullscreen_detected, wayland_ctx.bar_visibility); } + } - static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer) { - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - //platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - - // read-only - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - - assert(shm_buffer.pixels.data); - uint8_t *pixels = shm_buffer.pixels.data; - const size_t pixels_size = shm_buffer.pixels._size_bytes; - - const bool bar_visible = !wayland_ctx._fullscreen_detected && wayland_ctx.bar_visibility == platform::wayland::bar_visibility_t::Show; - const int effective_opacity = bar_visible ? current_config.overlay_opacity : 0; - - assert(wayland_ctx._screen_width >= 0); - assert(wayland_ctx._bar_height >= 0); - assert(effective_opacity >= 0); - - // Fast clear with 32-bit fill - const uint32_t fill = DEFAULT_FILL_COLOR | (static_cast(effective_opacity) << 24u); // RGBA, little-endian - auto *p = reinterpret_cast(pixels); - const size_t total_pixels = static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); - if (current_config.enable_debug) { - if (const size_t expected_bytes = total_pixels * sizeof(uint32_t); expected_bytes > pixels_size) { - BONGOCAT_LOG_VERBOSE("draw_bar: pixel write would overflow buffer (expected %zu bytes, have %zu). Aborting draw.", - expected_bytes, pixels_size); - return false; - } - } - for (size_t i = 0; i < total_pixels; i++) { - p[i] = fill; - } - - { - platform::LockGuard guard (anim.anim_lock); - const animation_shared_memory_t& anim_shm = *anim.shm; - - if (bar_visible) { - switch (anim_shm.anim_type) { - case config::config_animation_sprite_sheet_layout_t::None: - break; - case config::config_animation_sprite_sheet_layout_t::Bongocat: { - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.bongocat_anims.count); - } - const animation_t& cat_anim = get_current_animation(anim); - assert(cat_anim.type == animation_t::Type::Bongocat); - const bongocat_sprite_sheet_t& sheet = cat_anim.bongocat; - draw_sprite(ctx, shm_buffer, sheet, current_config.enable_antialiasing ? blit_image_color_option_flags_t::BilinearInterpolation : blit_image_color_option_flags_t::Normal); - }break; - case config::config_animation_sprite_sheet_layout_t::Dm: { - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - switch (anim_shm.anim_dm_set) { - case config::config_animation_dm_set_t::None: - break; - case config::config_animation_dm_set_t::min_dm: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.min_dm_anims.count); - }break; - case config::config_animation_dm_set_t::dm: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dm_anims.count); - }break; - case config::config_animation_dm_set_t::dm20: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dm20_anims.count); - }break; - case config::config_animation_dm_set_t::dmx: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dmx_anims.count); - }break; - case config::config_animation_dm_set_t::pen: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pen_anims.count); - }break; - case config::config_animation_dm_set_t::pen20: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pen20_anims.count); - }break; - case config::config_animation_dm_set_t::dmc: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dmc_anims.count); - }break; - case config::config_animation_dm_set_t::dmall: { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.dmall_anims.count); - }break; - } - } - const animation_t& dm_anim = get_current_animation(anim); - assert(dm_anim.type == animation_t::Type::Dm); - const dm_sprite_sheet_t& sheet = dm_anim.dm; - draw_sprite(ctx, shm_buffer, sheet); - }break; - case config::config_animation_sprite_sheet_layout_t::Pkmn: { - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pkmn_anims.count); - } - const animation_t& pkmn_anim = get_current_animation(anim); - assert(pkmn_anim.type == animation_t::Type::Pkmn); - const auto& sheet = pkmn_anim.pkmn; - draw_sprite(ctx, shm_buffer, sheet); - }break; - case config::config_animation_sprite_sheet_layout_t::MsAgent:{ - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.ms_anims.count); - } - const animation_t& ms_anim = get_current_animation(anim); - assert(ms_anim.type == animation_t::Type::MsAgent); - const ms_agent_sprite_sheet_t& sheet = ms_anim.ms_agent; - const int col = anim_shm.animation_player_result.sprite_sheet_col; - const int row = anim_shm.animation_player_result.sprite_sheet_row; - draw_sprite(ctx, shm_buffer, sheet, col, row); - }break; - case config::config_animation_sprite_sheet_layout_t::Custom:{ - const int col = anim_shm.animation_player_result.sprite_sheet_col; - const int row = anim_shm.animation_player_result.sprite_sheet_row; - assert(anim_shm.anim_index >= 0); - switch (anim_shm.anim_custom_set) { - case config::config_animation_custom_set_t::None: - break; - case config::config_animation_custom_set_t::misc: - if (features::EnableMiscEmbeddedAssets) { - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.misc_anims.count); - } - const animation_t& custom_anim = get_current_animation(anim); - assert(custom_anim.type == animation_t::Type::Custom); - const custom_sprite_sheet_t& sheet = custom_anim.custom; - draw_sprite(ctx, shm_buffer, sheet, col, row); - } - break; - case config::config_animation_custom_set_t::pmd: - if (features::EnablePmdEmbeddedAssets) { - if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pmd_anims.count); - } - const animation_t& custom_anim = get_current_animation(anim); - assert(custom_anim.type == animation_t::Type::Custom); - const custom_sprite_sheet_t& sheet = custom_anim.custom; - draw_sprite_overwrite_option_t overwrite_mirror_x {draw_sprite_overwrite_option_t::None}; - /* - switch (anim_shm.animation_player_result.overwrite_mirror_x) { - case animation_player_custom_overwrite_mirror_x::None: - break; - case animation_player_custom_overwrite_mirror_x::NoMirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementNoMirror; - break; - case animation_player_custom_overwrite_mirror_x::Mirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementMirror; - break; - } - */ - draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_mirror_x); - } - break; - case config::config_animation_custom_set_t::custom: - if (features::EnableCustomSpriteSheetsAssets && anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) == assets::CUSTOM_ANIM_INDEX) { - const animation_t& custom_anim = get_current_animation(anim); - assert(custom_anim.type == animation_t::Type::Custom); - const custom_sprite_sheet_t& sheet = custom_anim.custom; - draw_sprite_overwrite_option_t overwrite_mirror_x {draw_sprite_overwrite_option_t::None}; - switch (anim_shm.animation_player_result.overwrite_mirror_x) { - case animation_player_custom_overwrite_mirror_x::None: - break; - case animation_player_custom_overwrite_mirror_x::NoMirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementNoMirror; - break; - case animation_player_custom_overwrite_mirror_x::Mirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementMirror; - break; - } - draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_mirror_x); - } - break; - } - }break; - } - } else { - BONGOCAT_LOG_VERBOSE("skip drawing, keep buffer clean: fullscreen=%d, visibility=%d", wayland_ctx._fullscreen_detected, wayland_ctx.bar_visibility); - } - } + return true; +} - return true; +draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; + + // read-only + assert(wayland_ctx._local_copy_config != nullptr); + // assert(anim.shm != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; + + if (!atomic_load(&wayland_ctx_shm.configured)) { + BONGOCAT_LOG_VERBOSE("Surface not configured yet, skipping draw"); + return draw_bar_result_t::Skip; + } + + // assert(wayland_ctx_shm->current_buffer_index >= 0); + assert(platform::wayland::WAYLAND_NUM_BUFFERS > 0); + assert(platform::wayland::WAYLAND_NUM_BUFFERS <= INT_MAX); + [[maybe_unused]] const size_t current_buffer_index = wayland_ctx_shm.current_buffer_index; + [[maybe_unused]] size_t next_buffer_index = + (wayland_ctx_shm.current_buffer_index + 1) % platform::wayland::WAYLAND_NUM_BUFFERS; + + platform::wayland::wayland_shm_buffer_t *shm_buffer = nullptr; + if constexpr (platform::wayland::WAYLAND_NUM_BUFFERS == 1) { + shm_buffer = &wayland_ctx_shm.buffers[0]; + if (atomic_load(&shm_buffer->busy)) { + BONGOCAT_LOG_VERBOSE("Wayland: single buffer still busy, skip draw"); + atomic_store(&wayland_ctx._redraw_after_frame, true); + return draw_bar_result_t::Busy; + } + } else { + for (size_t i = 0; i < platform::wayland::WAYLAND_NUM_BUFFERS; i++) { + // assert(next_buffer_index >= 0); + auto *buf = &wayland_ctx_shm.buffers[next_buffer_index]; + if (!atomic_load(&buf->busy)) { + shm_buffer = buf; + next_buffer_index = i; + break; + } + next_buffer_index = (next_buffer_index + 1) % platform::wayland::WAYLAND_NUM_BUFFERS; } - draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; - - // read-only - assert(wayland_ctx._local_copy_config != nullptr); - //assert(anim.shm != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; - - if (!atomic_load(&wayland_ctx_shm.configured)) { - BONGOCAT_LOG_VERBOSE("Surface not configured yet, skipping draw"); - return draw_bar_result_t::Skip; - } - - //assert(wayland_ctx_shm->current_buffer_index >= 0); - assert(platform::wayland::WAYLAND_NUM_BUFFERS > 0); - assert(platform::wayland::WAYLAND_NUM_BUFFERS <= INT_MAX); - [[maybe_unused]] const size_t current_buffer_index = wayland_ctx_shm.current_buffer_index; - [[maybe_unused]] size_t next_buffer_index = (wayland_ctx_shm.current_buffer_index + 1) % platform::wayland::WAYLAND_NUM_BUFFERS; - - platform::wayland::wayland_shm_buffer_t *shm_buffer = nullptr; - if constexpr (platform::wayland::WAYLAND_NUM_BUFFERS == 1) { - shm_buffer = &wayland_ctx_shm.buffers[0]; - if (atomic_load(&shm_buffer->busy)) { - BONGOCAT_LOG_VERBOSE("Wayland: single buffer still busy, skip draw"); - atomic_store(&wayland_ctx._redraw_after_frame, true); - return draw_bar_result_t::Busy; - } - } else { - for (size_t i = 0; i < platform::wayland::WAYLAND_NUM_BUFFERS; i++) { - //assert(next_buffer_index >= 0); - auto *buf = &wayland_ctx_shm.buffers[next_buffer_index]; - if (!atomic_load(&buf->busy)) { - shm_buffer = buf; - next_buffer_index = i; - break; - } - next_buffer_index = (next_buffer_index + 1) % platform::wayland::WAYLAND_NUM_BUFFERS; - } - - if (!shm_buffer) { - BONGOCAT_LOG_VERBOSE("draw_bar: All buffers busy, skip drawing"); - atomic_store(&wayland_ctx._redraw_after_frame, true); - return draw_bar_result_t::Busy; - } - } - - assert(shm_buffer); - if (!shm_buffer->pixels.data) { - BONGOCAT_LOG_VERBOSE("draw_bar: Config or pixels not ready, skipping draw"); - return draw_bar_result_t::Skip; - } - - BONGOCAT_LOG_VERBOSE("draw_bar: using buffer %zu", next_buffer_index); - if constexpr (platform::wayland::WAYLAND_NUM_BUFFERS > 1) { - wayland_ctx_shm.current_buffer_index = next_buffer_index; - BONGOCAT_LOG_VERBOSE("draw_bar: new current_buffer_index: %i", next_buffer_index); - } - assert(wayland_ctx_shm.current_buffer_index < platform::wayland::WAYLAND_NUM_BUFFERS); - - assert(shm_buffer); - draw_bar_on_buffer(ctx, *shm_buffer); - - assert(shm_buffer->buffer); - wl_surface_attach(wayland_ctx.surface, shm_buffer->buffer, 0, 0); - wl_surface_damage_buffer(wayland_ctx.surface, 0, 0, wayland_ctx._screen_width, wayland_ctx._bar_height); - - { - platform::LockGuard guard (wayland_ctx._frame_cb_lock); - if (!atomic_load(&wayland_ctx._frame_pending) && !wayland_ctx._frame_cb) { - wayland_ctx._frame_cb = wl_surface_frame(wayland_ctx.surface); - wl_callback_add_listener(wayland_ctx._frame_cb, &platform::wayland::details::frame_listener, &ctx); - atomic_store(&wayland_ctx._frame_pending, true); - BONGOCAT_LOG_VERBOSE("draw_bar: Set frame pending"); - } else { - // Frame callback is pending: queue redraw after current frame - atomic_store(&wayland_ctx._redraw_after_frame, true); - BONGOCAT_LOG_VERBOSE("draw_bar: Queued redraw after pending frame"); - } - } - - wl_surface_commit(wayland_ctx.surface); + if (!shm_buffer) { + BONGOCAT_LOG_VERBOSE("draw_bar: All buffers busy, skip drawing"); + atomic_store(&wayland_ctx._redraw_after_frame, true); + return draw_bar_result_t::Busy; + } + } + + assert(shm_buffer); + if (!shm_buffer->pixels.data) { + BONGOCAT_LOG_VERBOSE("draw_bar: Config or pixels not ready, skipping draw"); + return draw_bar_result_t::Skip; + } + + BONGOCAT_LOG_VERBOSE("draw_bar: using buffer %zu", next_buffer_index); + if constexpr (platform::wayland::WAYLAND_NUM_BUFFERS > 1) { + wayland_ctx_shm.current_buffer_index = next_buffer_index; + BONGOCAT_LOG_VERBOSE("draw_bar: new current_buffer_index: %i", next_buffer_index); + } + assert(wayland_ctx_shm.current_buffer_index < platform::wayland::WAYLAND_NUM_BUFFERS); + + assert(shm_buffer); + draw_bar_on_buffer(ctx, *shm_buffer); + + assert(shm_buffer->buffer); + wl_surface_attach(wayland_ctx.surface, shm_buffer->buffer, 0, 0); + wl_surface_damage_buffer(wayland_ctx.surface, 0, 0, wayland_ctx._screen_width, wayland_ctx._bar_height); + + { + platform::LockGuard guard(wayland_ctx._frame_cb_lock); + if (!atomic_load(&wayland_ctx._frame_pending) && !wayland_ctx._frame_cb) { + wayland_ctx._frame_cb = wl_surface_frame(wayland_ctx.surface); + wl_callback_add_listener(wayland_ctx._frame_cb, &platform::wayland::details::frame_listener, &ctx); + atomic_store(&wayland_ctx._frame_pending, true); + BONGOCAT_LOG_VERBOSE("draw_bar: Set frame pending"); + } else { + // Frame callback is pending: queue redraw after current frame + atomic_store(&wayland_ctx._redraw_after_frame, true); + BONGOCAT_LOG_VERBOSE("draw_bar: Queued redraw after pending frame"); + } + } - atomic_store(&shm_buffer->busy, true); + wl_surface_commit(wayland_ctx.surface); - /// @TODO: flush here or on main ??? - const int flush_ret = wl_display_flush(wayland_ctx.display); - if (flush_ret == -1 && errno == EAGAIN) { - // send buffer full; need to make progress by reading pending events first - wl_display_cancel_read(wayland_ctx.display); - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("draw_bar: wl_display_dispatch_pending failed after EAGAIN"); - } - } else if (flush_ret == -1) { - BONGOCAT_LOG_ERROR("draw_bar: wl_display_flush failed: %s", strerror(errno)); - wl_display_cancel_read(wayland_ctx.display); - wl_display_dispatch_pending(wayland_ctx.display); - } - - const platform::timestamp_ms_t now = platform::get_current_time_ms(); - assert(current_config.fps > 0); - if (const platform::time_ms_t frame_interval_ms = 1000 / current_config.fps; wayland_ctx._last_frame_timestamp_ms <= 0 || (now - wayland_ctx._last_frame_timestamp_ms) >= frame_interval_ms) { - wayland_ctx._last_frame_timestamp_ms = now; - } + atomic_store(&shm_buffer->busy, true); - return draw_bar_result_t::NoFlushNeeded; + /// @TODO: flush here or on main ??? + const int flush_ret = wl_display_flush(wayland_ctx.display); + if (flush_ret == -1 && errno == EAGAIN) { + // send buffer full; need to make progress by reading pending events first + wl_display_cancel_read(wayland_ctx.display); + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("draw_bar: wl_display_dispatch_pending failed after EAGAIN"); } + } else if (flush_ret == -1) { + BONGOCAT_LOG_ERROR("draw_bar: wl_display_flush failed: %s", strerror(errno)); + wl_display_cancel_read(wayland_ctx.display); + wl_display_dispatch_pending(wayland_ctx.display); + } + + const platform::timestamp_ms_t now = platform::get_current_time_ms(); + assert(current_config.fps > 0); + if (const platform::time_ms_t frame_interval_ms = 1000 / current_config.fps; + wayland_ctx._last_frame_timestamp_ms <= 0 || (now - wayland_ctx._last_frame_timestamp_ms) >= frame_interval_ms) { + wayland_ctx._last_frame_timestamp_ms = now; + } + + return draw_bar_result_t::NoFlushNeeded; } +} // namespace bongocat::animation diff --git a/src/graphics/bar.h b/src/graphics/bar.h index af115381..9158cc6c 100644 --- a/src/graphics/bar.h +++ b/src/graphics/bar.h @@ -4,15 +4,15 @@ #include "platform/global_wayland_session.h" namespace bongocat::animation { - enum class draw_bar_result_t : uint8_t { - Skip, - Busy, - FlushNeeded, - NoFlushNeeded, - }; +enum class draw_bar_result_t : uint8_t { + Skip, + Busy, + FlushNeeded, + NoFlushNeeded, +}; - // Draw the overlay bar - draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx); -} +// Draw the overlay bar +draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx); +} // namespace bongocat::animation -#endif // BONGOCAT_ANIMATION_BAR_H \ No newline at end of file +#endif // BONGOCAT_ANIMATION_BAR_H \ No newline at end of file diff --git a/src/graphics/drawing_images.cpp b/src/graphics/drawing_images.cpp index e85b6688..62aef85c 100644 --- a/src/graphics/drawing_images.cpp +++ b/src/graphics/drawing_images.cpp @@ -1,265 +1,295 @@ -#include "graphics/drawing.h" #include "graphics/animation_context.h" +#include "graphics/drawing.h" + #include #include namespace bongocat::animation { - // ============================================================================= - // GLOBAL STATE AND CONFIGURATION - // ============================================================================= - - static inline constexpr uint8_t THRESHOLD_ALPHA = 120; - static inline constexpr unsigned int FIXED_SHIFT = 16; - static inline constexpr unsigned int FIXED_ONE = (1u << FIXED_SHIFT); - - // ============================================================================= - // DRAWING OPERATIONS MODULE - // ============================================================================= - - /* - static bool drawing_is_pixel_in_bounds(int x, int y, int width, int height) { - return x >= 0 && y >= 0 && x < width && y < height; - } - */ - - constexpr static uint8_t apply_invert(uint8_t v, bool invert) { - return v ^ (invert ? 0xFF : 0x00); // branchless invert - } - - static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_channels, int dest_idx, - blit_image_color_order_t dest_order, - uint8_t r, uint8_t g, uint8_t b, uint8_t a) { - // Map destination channel indices - const int dr = (dest_order == blit_image_color_order_t::RGBA) ? 0 : 2; - constexpr int dg = 1; - const int db = (dest_order == blit_image_color_order_t::RGBA) ? 2 : 0; - - // Store without branching - if (dest_channels >= 1) dest[dest_idx + dr] = r; - if (dest_channels >= 2) dest[dest_idx + dg] = g; - if (dest_channels >= 3) dest[dest_idx + db] = b; - if (dest_channels >= 4) dest[dest_idx + 3] = a; - } - void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, - const unsigned char *src, int src_channels, int src_idx, - blit_image_color_option_flags_t options, - blit_image_color_order_t dest_order, - blit_image_color_order_t src_order) - { - if (has_flag(options, blit_image_color_option_flags_t::Invisible)) return; - const bool invert = has_flag(options, blit_image_color_option_flags_t::Invert); - - // Map source channel indices for RGB - const int sr = (src_order == blit_image_color_order_t::RGBA) ? 0 : 2; - const int sg = 1; - const int sb = (src_order == blit_image_color_order_t::RGBA) ? 2 : 0; - - // Load into RGBA without branches - uint8_t r, g, b, a; - if (src_channels == 1) { - // 1-channel grayscale -> fill all channels with 0/255 - uint8_t v = src[src_idx] ? 255 : 0; - v = apply_invert(v, invert); - r = g = b = a = v; - } - else if (src_channels == 2) { - // 2-channel grayscale + alpha (alpha ignored in original) - const uint8_t gray = apply_invert(src[src_idx], invert); - r = g = b = gray; - a = 255; - } - else { - // RGB / RGBA - r = apply_invert(src[src_idx + sr], invert); - g = apply_invert(src[src_idx + sg], invert); - b = apply_invert(src[src_idx + sb], invert); - a = (src_channels >= 4) ? src[src_idx + 3] : 255; // Alpha not inverted - } - - drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, r, g, b, a); +// ============================================================================= +// GLOBAL STATE AND CONFIGURATION +// ============================================================================= + +static inline constexpr uint8_t THRESHOLD_ALPHA = 120; +static inline constexpr unsigned int FIXED_SHIFT = 16; +static inline constexpr unsigned int FIXED_ONE = (1u << FIXED_SHIFT); + +// ============================================================================= +// DRAWING OPERATIONS MODULE +// ============================================================================= + +/* +static bool drawing_is_pixel_in_bounds(int x, int y, int width, int height) { + return x >= 0 && y >= 0 && x < width && y < height; +} +*/ + +constexpr static uint8_t apply_invert(uint8_t v, bool invert) { + return v ^ (invert ? 0xFF : 0x00); // branchless invert +} + +static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_channels, int dest_idx, blit_image_color_order_t dest_order, + uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + // Map destination channel indices + const int dr = (dest_order == blit_image_color_order_t::RGBA) ? 0 : 2; + constexpr int dg = 1; + const int db = (dest_order == blit_image_color_order_t::RGBA) ? 2 : 0; + + // Store without branching + if (dest_channels >= 1) + dest[dest_idx + dr] = r; + if (dest_channels >= 2) + dest[dest_idx + dg] = g; + if (dest_channels >= 3) + dest[dest_idx + db] = b; + if (dest_channels >= 4) + dest[dest_idx + 3] = a; +} +void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const unsigned char *src, int src_channels, + int src_idx, blit_image_color_option_flags_t options, blit_image_color_order_t dest_order, + blit_image_color_order_t src_order) { + if (has_flag(options, blit_image_color_option_flags_t::Invisible)) + return; + const bool invert = has_flag(options, blit_image_color_option_flags_t::Invert); + + // Map source channel indices for RGB + const int sr = (src_order == blit_image_color_order_t::RGBA) ? 0 : 2; + const int sg = 1; + const int sb = (src_order == blit_image_color_order_t::RGBA) ? 2 : 0; + + // Load into RGBA without branches + uint8_t r, g, b, a; + if (src_channels == 1) { + // 1-channel grayscale -> fill all channels with 0/255 + uint8_t v = src[src_idx] ? 255 : 0; + v = apply_invert(v, invert); + r = g = b = a = v; + } else if (src_channels == 2) { + // 2-channel grayscale + alpha (alpha ignored in original) + const uint8_t gray = apply_invert(src[src_idx], invert); + r = g = b = gray; + a = 255; + } else { + // RGB / RGBA + r = apply_invert(src[src_idx + sr], invert); + g = apply_invert(src[src_idx + sg], invert); + b = apply_invert(src[src_idx + sb], invert); + a = (src_channels >= 4) ? src[src_idx + 3] : 255; // Alpha not inverted + } + + drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, r, g, b, a); +} + +struct drawing_get_interpolated_pixel_result_t { + unsigned char r{0}; + unsigned char g{0}; + unsigned char b{0}; + unsigned char a{0}; +}; +// Bilinear interpolation for smooth scaling +static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(const unsigned char *src, size_t src_size, + int src_w, int src_h, int src_channels, + float fx, float fy) { + // Clamp coordinates to image bounds + if (fx < 0) + fx = 0; + if (fy < 0) + fy = 0; + if (fx >= static_cast(src_w - 1)) + fx = static_cast(src_w - 1); + if (fy >= static_cast(src_h - 1)) + fy = static_cast(src_h - 1); + + int x1 = static_cast(fx); + int y1 = static_cast(fy); + int x2 = x1 + 1; + int y2 = y1 + 1; + + // Clamp to bounds + if (x2 >= src_w) + x2 = src_w - 1; + if (y2 >= src_h) + y2 = src_h - 1; + + const float dx = fx - static_cast(x1); + const float dy = fy - static_cast(y1); + + // Get the four surrounding pixels + const int idx_tl = (y1 * src_w + x1) * src_channels; // top-left + const int idx_tr = (y1 * src_w + x2) * src_channels; // top-right + const int idx_bl = (y2 * src_w + x1) * src_channels; // bottom-left + const int idx_br = (y2 * src_w + x2) * src_channels; // bottom-right + + // Interpolate each channel + drawing_get_interpolated_pixel_result_t ret; + for (int c = 0; c < src_channels; c++) { + assert(idx_tl >= 0); + assert(idx_tr >= 0); + assert(idx_bl >= 0); + assert(idx_br >= 0); + const size_t tl_idx_c = static_cast(idx_tl) + static_cast(c); + const size_t tr_idx_c = static_cast(idx_tr) + static_cast(c); + const size_t bl_idx_c = static_cast(idx_bl) + static_cast(c); + const size_t br_idx_c = static_cast(idx_br) + static_cast(c); + + if (tl_idx_c < src_size && tr_idx_c < src_size && bl_idx_c < src_size && br_idx_c < src_size) { + float top = static_cast(src[tl_idx_c]) * (1.0f - dx) + static_cast(src[tr_idx_c]) * dx; + float bottom = static_cast(src[bl_idx_c]) * (1.0f - dx) + static_cast(src[br_idx_c]) * dx; + float result = top * (1.0f - dy) + bottom * dy; + + switch (c) { + case 0: + ret.r = static_cast(result + 0.5f); + break; // R + case 1: + ret.g = static_cast(result + 0.5f); + break; // G + case 2: + ret.b = static_cast(result + 0.5f); + break; // B + case 3: + ret.a = static_cast(result + 0.5f); + break; // A + default: + break; + } } - - - struct drawing_get_interpolated_pixel_result_t { unsigned char r{0}; unsigned char g{0}; unsigned char b{0}; unsigned char a{0}; }; - // Bilinear interpolation for smooth scaling - static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(const unsigned char *src, size_t src_size, int src_w, int src_h, int src_channels, float fx, float fy) { - // Clamp coordinates to image bounds - if (fx < 0) fx = 0; - if (fy < 0) fy = 0; - if (fx >= static_cast(src_w - 1)) fx = static_cast(src_w - 1); - if (fy >= static_cast(src_h - 1)) fy = static_cast(src_h - 1); - - int x1 = static_cast(fx); - int y1 = static_cast(fy); - int x2 = x1 + 1; - int y2 = y1 + 1; - - // Clamp to bounds - if (x2 >= src_w) x2 = src_w - 1; - if (y2 >= src_h) y2 = src_h - 1; - - const float dx = fx - static_cast(x1); - const float dy = fy - static_cast(y1); - - // Get the four surrounding pixels - const int idx_tl = (y1 * src_w + x1) * src_channels; // top-left - const int idx_tr = (y1 * src_w + x2) * src_channels; // top-right - const int idx_bl = (y2 * src_w + x1) * src_channels; // bottom-left - const int idx_br = (y2 * src_w + x2) * src_channels; // bottom-right - - // Interpolate each channel - drawing_get_interpolated_pixel_result_t ret; - for (int c = 0; c < src_channels; c++) { - assert(idx_tl >= 0); - assert(idx_tr >= 0); - assert(idx_bl >= 0); - assert(idx_br >= 0); - const size_t tl_idx_c = static_cast(idx_tl) + static_cast(c); - const size_t tr_idx_c = static_cast(idx_tr) + static_cast(c); - const size_t bl_idx_c = static_cast(idx_bl) + static_cast(c); - const size_t br_idx_c = static_cast(idx_br) + static_cast(c); - - if (tl_idx_c < src_size && tr_idx_c < src_size && bl_idx_c < src_size && br_idx_c < src_size) { - float top = static_cast(src[tl_idx_c]) * (1.0f - dx) + static_cast(src[tr_idx_c]) * dx; - float bottom = static_cast(src[bl_idx_c]) * (1.0f - dx) + static_cast(src[br_idx_c]) * dx; - float result = top * (1.0f - dy) + bottom * dy; - - switch (c) { - case 0: ret.r = static_cast(result + 0.5f); break; // R - case 1: ret.g = static_cast(result + 0.5f); break; // G - case 2: ret.b = static_cast(result + 0.5f); break; // B - case 3: ret.a = static_cast(result + 0.5f); break; // A - default: break; - } + } + return ret; +} + +void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int dest_channels, + const unsigned char *src, size_t src_size, int src_w, int src_h, int src_channels, int src_x, + int src_y, int frame_w, int frame_h, int offset_x, int offset_y, int target_w, int target_h, + blit_image_color_order_t dest_order, blit_image_color_order_t src_order, + blit_image_color_option_flags_t options) { + if (!dest || !src) + return; + if (dest_w <= 0 || dest_h <= 0 || src_w <= 0 || src_h <= 0) + return; + if (dest_channels <= 0 || src_channels <= 0) + return; + if (target_w <= 0 || target_h <= 0) + return; + if (frame_w <= 0 || frame_h <= 0) + return; + if (has_flag(options, blit_image_color_option_flags_t::Invisible)) + return; + + assert(dest_w >= 0); + assert(dest_h >= 0); + assert(dest_channels >= 0); + assert(src_w >= 0); + assert(src_h >= 0); + assert(src_channels >= 0); + // Verify buffers are large enough + const size_t needed_dest = + static_cast(dest_w) * static_cast(dest_h) * static_cast(dest_channels); + const size_t needed_src = static_cast(src_w) * static_cast(src_h) * static_cast(src_channels); + if (dest_size < needed_dest || src_size < needed_src) + return; + + // Clip destination rectangle + const int dst_left = offset_x; + const int dst_top = offset_y; + const int dst_right = offset_x + target_w; + const int dst_bottom = offset_y + target_h; + + int x0 = 0; + int x1 = target_w; + if (dst_left < 0) + x0 = -dst_left; + if (dst_right > dest_w) + x1 = target_w - (dst_right - dest_w); + if (x0 >= x1) + return; + + int y0 = 0; + int y1 = target_h; + if (dst_top < 0) + y0 = -dst_top; + if (dst_bottom > dest_h) + y1 = target_h - (dst_bottom - dest_h); + if (y0 >= y1) + return; + + // Fixed-point increments + assert(target_w > 0); + assert(target_h > 0); + int32_t inc_x = static_cast((static_cast(frame_w) << FIXED_SHIFT) / target_w); + int32_t inc_y = static_cast((static_cast(frame_h) << FIXED_SHIFT) / target_h); + + int32_t src_x_start = src_x << FIXED_SHIFT; + int32_t src_y_start = src_y << FIXED_SHIFT; + + // MirrorX / MirrorY affect direction and start point + if (has_flag(options, blit_image_color_option_flags_t::MirrorX)) { + src_x_start = (src_x + frame_w - 1) << FIXED_SHIFT; + inc_x = -inc_x; + } + if (has_flag(options, blit_image_color_option_flags_t::MirrorY)) { + src_y_start = (src_y + frame_h - 1) << FIXED_SHIFT; + inc_y = -inc_y; + } + + const size_t src_row_bytes = static_cast(src_w) * static_cast(src_channels); + const size_t dest_row_bytes = static_cast(dest_w) * static_cast(dest_channels); + + const bool use_bilinear_interpolation = has_flag(options, blit_image_color_option_flags_t::BilinearInterpolation); + + for (int ty = y0; ty < y1; ++ty) { + const int dy = offset_y + ty; + assert(dy < dest_h); + + int32_t sy_fixed = src_y_start + static_cast(static_cast(ty) * inc_y); + const int sy = sy_fixed >> FIXED_SHIFT; + + if (static_cast(sy) >= static_cast(src_h)) + continue; + + uint8_t *dest_row = dest + static_cast(dy) * dest_row_bytes; + const unsigned char *src_row = src + static_cast(sy) * src_row_bytes; + + int32_t sx_fixed = src_x_start + static_cast(static_cast(x0) * inc_x); + uint8_t *dest_ptr = dest_row + static_cast(offset_x + x0) * static_cast(dest_channels); + + for (int tx = x0; tx < x1; ++tx) { + const int sx = sx_fixed >> FIXED_SHIFT; + + if (static_cast(sx) < static_cast(src_w)) { + const uint8_t *src_pixel = src_row + static_cast(sx) * static_cast(src_channels); + int dest_idx = static_cast(dest_ptr - dest); + int src_idx = static_cast(src_pixel - src); + + if (use_bilinear_interpolation) { + // Use bilinear interpolation for smooth scaling + float fx = static_cast(sx_fixed) / static_cast(1 << FIXED_SHIFT); + float fy = static_cast(sy_fixed) / static_cast(1 << FIXED_SHIFT); + + auto pixel = drawing_get_interpolated_pixel(src, src_size, src_w, src_h, src_channels, fx, fy); + if (src_channels >= 4) { + if (src_pixel[3] > THRESHOLD_ALPHA) { + drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, pixel.r, pixel.g, pixel.b, pixel.a); } - } - return ret; - } - - void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int dest_channels, - const unsigned char *src, size_t src_size, int src_w, int src_h, int src_channels, - int src_x, int src_y, - int frame_w, int frame_h, - int offset_x, int offset_y, int target_w, int target_h, - blit_image_color_order_t dest_order, - blit_image_color_order_t src_order, - blit_image_color_option_flags_t options) - { - if (!dest || !src) return; - if (dest_w <= 0 || dest_h <= 0 || src_w <= 0 || src_h <= 0) return; - if (dest_channels <= 0 || src_channels <= 0) return; - if (target_w <= 0 || target_h <= 0) return; - if (frame_w <= 0 || frame_h <= 0) return; - if (has_flag(options, blit_image_color_option_flags_t::Invisible)) return; - - assert(dest_w >= 0); - assert(dest_h >= 0); - assert(dest_channels >= 0); - assert(src_w >= 0); - assert(src_h >= 0); - assert(src_channels >= 0); - // Verify buffers are large enough - const size_t needed_dest = static_cast(dest_w) * static_cast(dest_h) * static_cast(dest_channels); - const size_t needed_src = static_cast(src_w) * static_cast(src_h) * static_cast(src_channels); - if (dest_size < needed_dest || src_size < needed_src) return; - - // Clip destination rectangle - const int dst_left = offset_x; - const int dst_top = offset_y; - const int dst_right = offset_x + target_w; - const int dst_bottom = offset_y + target_h; - - int x0 = 0; - int x1 = target_w; - if (dst_left < 0) x0 = -dst_left; - if (dst_right > dest_w) x1 = target_w - (dst_right - dest_w); - if (x0 >= x1) return; - - int y0 = 0; - int y1 = target_h; - if (dst_top < 0) y0 = -dst_top; - if (dst_bottom > dest_h) y1 = target_h - (dst_bottom - dest_h); - if (y0 >= y1) return; - - // Fixed-point increments - assert(target_w > 0); - assert(target_h > 0); - int32_t inc_x = static_cast((static_cast(frame_w) << FIXED_SHIFT) / target_w); - int32_t inc_y = static_cast((static_cast(frame_h) << FIXED_SHIFT) / target_h); - - int32_t src_x_start = src_x << FIXED_SHIFT; - int32_t src_y_start = src_y << FIXED_SHIFT; - - // MirrorX / MirrorY affect direction and start point - if (has_flag(options, blit_image_color_option_flags_t::MirrorX)) { - src_x_start = (src_x + frame_w - 1) << FIXED_SHIFT; - inc_x = -inc_x; - } - if (has_flag(options, blit_image_color_option_flags_t::MirrorY)) { - src_y_start = (src_y + frame_h - 1) << FIXED_SHIFT; - inc_y = -inc_y; - } - - const size_t src_row_bytes = static_cast(src_w) * static_cast(src_channels); - const size_t dest_row_bytes = static_cast(dest_w) * static_cast(dest_channels); - - const bool use_bilinear_interpolation = has_flag(options, blit_image_color_option_flags_t::BilinearInterpolation); - - for (int ty = y0; ty < y1; ++ty) { - const int dy = offset_y + ty; - assert(dy < dest_h); - - int32_t sy_fixed = src_y_start + static_cast(static_cast(ty) * inc_y); - const int sy = sy_fixed >> FIXED_SHIFT; - - if (static_cast(sy) >= static_cast(src_h)) continue; - - uint8_t *dest_row = dest + static_cast(dy) * dest_row_bytes; - const unsigned char *src_row = src + static_cast(sy) * src_row_bytes; - - int32_t sx_fixed = src_x_start + static_cast(static_cast(x0) * inc_x); - uint8_t *dest_ptr = dest_row + static_cast(offset_x + x0) * static_cast(dest_channels); - - for (int tx = x0; tx < x1; ++tx) { - const int sx = sx_fixed >> FIXED_SHIFT; - - if (static_cast(sx) < static_cast(src_w)) { - const uint8_t *src_pixel = src_row + static_cast(sx) * static_cast(src_channels); - int dest_idx = static_cast(dest_ptr - dest); - int src_idx = static_cast(src_pixel - src); - - if (use_bilinear_interpolation) { - // Use bilinear interpolation for smooth scaling - float fx = static_cast(sx_fixed) / static_cast(1 << FIXED_SHIFT); - float fy = static_cast(sy_fixed) / static_cast(1 << FIXED_SHIFT); - - auto pixel = drawing_get_interpolated_pixel(src, src_size, src_w, src_h, src_channels, fx, fy); - if (src_channels >= 4) { - if (src_pixel[3] > THRESHOLD_ALPHA) { - drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, pixel.r, pixel.g, pixel.b, pixel.a); - } - } else { - drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, pixel.r, pixel.g, pixel.b, 255); - } - } else { - // Use nearest-neighbor scaling (original behavior) - if (src_channels >= 4) { - if (src_pixel[3] > THRESHOLD_ALPHA) { - drawing_copy_pixel(dest, dest_channels, dest_idx, - src, src_channels, src_idx, - options, dest_order, src_order); - } - } else { - drawing_copy_pixel(dest, dest_channels, dest_idx, - src, src_channels, src_idx, - options, dest_order, src_order); - } - } - } - - sx_fixed += inc_x; - dest_ptr += dest_channels; + } else { + drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, pixel.r, pixel.g, pixel.b, 255); + } + } else { + // Use nearest-neighbor scaling (original behavior) + if (src_channels >= 4) { + if (src_pixel[3] > THRESHOLD_ALPHA) { + drawing_copy_pixel(dest, dest_channels, dest_idx, src, src_channels, src_idx, options, dest_order, + src_order); } + } else { + drawing_copy_pixel(dest, dest_channels, dest_idx, src, src_channels, src_idx, options, dest_order, + src_order); + } } + } + + sx_fixed += inc_x; + dest_ptr += dest_channels; } -} \ No newline at end of file + } +} +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/CMakeLists.txt b/src/image_loader/CMakeLists.txt index 4ae91eea..3b829a7f 100644 --- a/src/image_loader/CMakeLists.txt +++ b/src/image_loader/CMakeLists.txt @@ -1,57 +1,53 @@ # stb_image lib add_library(assets_image_loader STATIC) -target_include_directories(assets_image_loader - PRIVATE ${INCLUDE_DIR}/image_loader - PUBLIC ${INCLUDE_DIR}) +target_include_directories( + assets_image_loader + PRIVATE ${INCLUDE_DIR}/image_loader + PUBLIC ${INCLUDE_DIR}) target_include_directories(assets_image_loader SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) target_link_libraries(assets_image_loader PRIVATE bongocat_options) -if (FEATURE_USE_HYBRID_IMAGE_BACKEND) +if(FEATURE_USE_HYBRID_IMAGE_BACKEND) + add_library(pngle STATIC) + target_include_directories(pngle SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) + target_sources(pngle PRIVATE pngle.c miniz.c) + target_compile_definitions(pngle PUBLIC PNGLE_NO_GAMMA_CORRECTION) + + add_library(stb_image STATIC) + target_include_directories(stb_image SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) + target_sources(stb_image PRIVATE stb_image.c) + target_compile_definitions(stb_image PUBLIC STBI_NO_STDIO STBI_ONLY_PNG) + target_compile_definitions(stb_image PUBLIC $<$:STBI_NO_LINEAR>) + + target_sources(assets_image_loader PRIVATE load_images.cpp load_images_hybrid.cpp) + target_link_libraries(assets_image_loader PRIVATE pngle stb_image) + target_compile_definitions(assets_image_loader PUBLIC FEATURE_USE_HYBRID_IMAGE_BACKEND) + message(STATUS "Use hybrid image backend (stb_image + pngle)") +else() + if(FEATURE_USE_PNGLE) add_library(pngle STATIC) target_include_directories(pngle SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) target_sources(pngle PRIVATE pngle.c miniz.c) target_compile_definitions(pngle PUBLIC PNGLE_NO_GAMMA_CORRECTION) + target_sources(assets_image_loader PRIVATE load_images.cpp load_images_pngle.cpp) + target_link_libraries(assets_image_loader PRIVATE pngle) + target_compile_definitions(assets_image_loader PUBLIC FEATURE_USE_PNGLE) + message(STATUS "Use pngle image backend") + else() add_library(stb_image STATIC) target_include_directories(stb_image SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) target_sources(stb_image PRIVATE stb_image.c) target_compile_definitions(stb_image PUBLIC STBI_NO_STDIO STBI_ONLY_PNG) - target_compile_definitions(stb_image PUBLIC - $<$:STBI_NO_LINEAR> - ) + target_compile_definitions(stb_image PUBLIC $<$:STBI_NO_LINEAR>) - target_sources(assets_image_loader PRIVATE load_images.cpp load_images_hybrid.cpp) - target_link_libraries(assets_image_loader PRIVATE pngle stb_image) - target_compile_definitions(assets_image_loader PUBLIC FEATURE_USE_HYBRID_IMAGE_BACKEND) - message(STATUS "Use hybrid image backend (stb_image + pngle)") -else() - if (FEATURE_USE_PNGLE) - add_library(pngle STATIC) - target_include_directories(pngle SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) - target_sources(pngle PRIVATE pngle.c miniz.c) - target_compile_definitions(pngle PUBLIC PNGLE_NO_GAMMA_CORRECTION) - - target_sources(assets_image_loader PRIVATE load_images.cpp load_images_pngle.cpp) - target_link_libraries(assets_image_loader PRIVATE pngle) - target_compile_definitions(assets_image_loader PUBLIC FEATURE_USE_PNGLE) - message(STATUS "Use pngle image backend") - else() - add_library(stb_image STATIC) - target_include_directories(stb_image SYSTEM PRIVATE ${PROJECT_SOURCE_DIR}/lib) - target_sources(stb_image PRIVATE stb_image.c) - target_compile_definitions(stb_image PUBLIC STBI_NO_STDIO STBI_ONLY_PNG) - target_compile_definitions(stb_image PUBLIC - $<$:STBI_NO_LINEAR> - ) - - target_sources(assets_image_loader PRIVATE load_images.cpp load_images_stb_image.cpp) - target_link_libraries(assets_image_loader PRIVATE stb_image) - target_compile_definitions(assets_image_loader PUBLIC FEATURE_USE_STB_IMAGE) - message(STATUS "Use stb_image backend") - endif() + target_sources(assets_image_loader PRIVATE load_images.cpp load_images_stb_image.cpp) + target_link_libraries(assets_image_loader PRIVATE stb_image) + target_compile_definitions(assets_image_loader PUBLIC FEATURE_USE_STB_IMAGE) + message(STATUS "Use stb_image backend") + endif() endif() - # @NOTE(assets): 3. add image_loader sub directory add_subdirectory(bongocat) add_subdirectory(base_dm) @@ -69,5 +65,5 @@ add_subdirectory(custom) add_subdirectory(misc) add_subdirectory(pmd) -# @NOTE(assets): 3.1. add image_loader in include/image_loader (see other load_images_...h as reference) -# @NOTE(assets): 3.2. add image_loader in src/image_loader/xxx (see other load_images_...cpp as reference) \ No newline at end of file +# @NOTE(assets): 3.1. add image_loader in include/image_loader (see other load_images_...h as reference) @NOTE(assets): 3.2. add +# image_loader in src/image_loader/xxx (see other load_images_...cpp as reference) diff --git a/src/image_loader/base_dm/CMakeLists.txt b/src/image_loader/base_dm/CMakeLists.txt index 71be2290..8670ab22 100644 --- a/src/image_loader/base_dm/CMakeLists.txt +++ b/src/image_loader/base_dm/CMakeLists.txt @@ -1,5 +1,11 @@ add_library(assets_base_dm_loader STATIC) target_sources(assets_base_dm_loader PRIVATE load_dm.cpp) target_compile_options(assets_base_dm_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_base_dm_loader PRIVATE ${INCLUDE_DIR}/image_loader/base_dm PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_base_dm_loader PUBLIC assets_image_loader PRIVATE bongocat_options) \ No newline at end of file +target_include_directories( + assets_base_dm_loader + PRIVATE ${INCLUDE_DIR}/image_loader/base_dm + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_base_dm_loader + PUBLIC assets_image_loader + PRIVATE bongocat_options) diff --git a/src/image_loader/base_dm/load_dm.cpp b/src/image_loader/base_dm/load_dm.cpp index 6ac1e5c3..a871474a 100644 --- a/src/image_loader/base_dm/load_dm.cpp +++ b/src/image_loader/base_dm/load_dm.cpp @@ -1,149 +1,154 @@ #include "image_loader/base_dm/load_dm.h" + #include "graphics/animation_context.h" #include "image_loader/load_images.h" namespace bongocat::animation { - created_result_t load_dm_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - auto result = load_sprite_sheet_anim(*ctx._local_copy_config, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load dm Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - assert(result.result.total_frames > 0); ///< this SHOULD always work if it's an valid EMBEDDED image - assert(MAX_NUM_FRAMES <= INT_MAX); - assert(result.result.total_frames <= static_cast(MAX_NUM_FRAMES)); - if (result.result.total_frames > static_cast(MAX_NUM_FRAMES)) { - BONGOCAT_LOG_ERROR("Sprite Sheet does not fit in out_frames: %d, total_frames: %d", MAX_NUM_FRAMES, result.result.total_frames); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - - dm_sprite_sheet_t ret; - ret.image = bongocat::move(result.result.image); - ret.frame_width = bongocat::move(result.result.frame_width); - ret.frame_height = bongocat::move(result.result.frame_height); - ret.total_frames = bongocat::move(result.result.total_frames); - - ret.frames.idle_1 = bongocat::move(result.result.frames[0]); - ret.frames.idle_2 = bongocat::move(result.result.frames[1]); - ret.frames.angry = bongocat::move(result.result.frames[2]); - ret.frames.down = bongocat::move(result.result.frames[3]); - ret.frames.happy = bongocat::move(result.result.frames[4]); - ret.frames.eat_1 = bongocat::move(result.result.frames[5]); - ret.frames.sleep = bongocat::move(result.result.frames[6]); - ret.frames.refuse = bongocat::move(result.result.frames[7]); - ret.frames.sad = bongocat::move(result.result.frames[8]); - - ret.frames.lose_1 = bongocat::move(result.result.frames[9]); - ret.frames.eat_2 = bongocat::move(result.result.frames[10]); - ret.frames.lose_2 = bongocat::move(result.result.frames[11]); - ret.frames.attack_1 = bongocat::move(result.result.frames[12]); - - ret.frames.movement_1 = bongocat::move(result.result.frames[13]); - ret.frames.movement_2 = bongocat::move(result.result.frames[14]); - - // setup animations - using namespace assets; - - // minimal frames existing - assert(ret.frames.idle_1.valid); - assert(ret.frames.idle_2.valid); - assert(ret.frames.angry.valid); - assert(ret.frames.down.valid); - - assert(MAX_ANIMATION_FRAMES >= 4); - ret.animations.idle[0] = ret.frames.idle_1.col; - ret.animations.idle[1] = ret.frames.idle_2.col; - ret.animations.idle[2] = ret.frames.idle_1.col; - ret.animations.idle[3] = ret.frames.idle_2.col; - - ret.animations.boring[0] = ret.frames.sad.col ? ret.frames.sad.col : ret.frames.idle_1.col; - ret.animations.boring[1] = ret.frames.lose_1.col ? ret.frames.lose_1.col : ret.frames.idle_2.col; - ret.animations.boring[2] = ret.frames.lose_2.col ? ret.frames.lose_2.col : ret.frames.idle_1.col; - ret.animations.boring[3] = ret.frames.idle_2.col; - - ret.animations.writing[0] = ret.frames.idle_2.col; - ret.animations.writing[1] = ret.frames.idle_1.col; - ret.animations.writing[2] = ret.frames.idle_2.col; - ret.animations.writing[3] = ret.frames.idle_1.col; - - // sleep animation - if (ret.frames.sleep.valid) { - ret.animations.sleep[0] = ret.frames.sleep.col; - ret.animations.sleep[1] = ret.frames.sleep.col; - ret.animations.sleep[2] = ret.frames.sleep.col; - ret.animations.sleep[3] = ret.frames.sleep.col; - } else if (ret.frames.down.valid) { - ret.animations.sleep[0] = ret.frames.down.col; - ret.animations.sleep[1] = ret.frames.down.col; - ret.animations.sleep[2] = ret.frames.down.col; - ret.animations.sleep[3] = ret.frames.down.col; - } else { - // fallback - ret.animations.sleep[0] = ret.frames.idle_2.col; - ret.animations.sleep[1] = ret.frames.idle_2.col; - ret.animations.sleep[2] = ret.frames.idle_2.col; - ret.animations.sleep[3] = ret.frames.idle_2.col; - } - - ret.animations.wake_up[0] = ret.frames.idle_1.col; - ret.animations.wake_up[1] = ret.frames.idle_2.col; - ret.animations.wake_up[2] = ret.frames.idle_1.col; - ret.animations.wake_up[3] = ret.frames.idle_2.col; - - // working/attack animation - ret.animations.working[0] = ret.frames.idle_1.col; - ret.animations.working[1] = ret.frames.idle_2.col; - ret.animations.working[2] = ret.frames.angry.valid ? ret.frames.angry.col : ret.frames.idle_1.col; - ret.animations.working[3] = ret.frames.idle_2.col; - ret.animations.working[2] = ret.frames.attack_1.valid ? ret.frames.attack_1.col : ret.animations.working[2]; - ret.animations.working[3] = ret.frames.attack_2.valid ? ret.frames.attack_2.col : ret.animations.working[3]; - - // moving/walking animation - if (ret.frames.movement_1.valid || ret.frames.movement_2.valid) { - ret.animations.moving[0] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; - ret.animations.moving[1] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; - ret.animations.moving[2] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; - ret.animations.moving[3] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; - } else { - // fallback - ret.animations.moving[0] = ret.frames.idle_1.col; - ret.animations.moving[1] = ret.frames.idle_1.col; - ret.animations.moving[2] = ret.frames.idle_2.col; - ret.animations.moving[3] = ret.frames.idle_2.col; - } - // running animation (same as moving) - if (ret.frames.movement_1.valid || ret.frames.movement_2.valid) { - ret.animations.running[0] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; - ret.animations.running[1] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; - ret.animations.running[2] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; - ret.animations.running[3] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; - } else { - // fallback - ret.animations.running[0] = ret.frames.idle_1.col; - ret.animations.running[1] = ret.frames.idle_1.col; - ret.animations.running[2] = ret.frames.idle_2.col; - ret.animations.running[3] = ret.frames.idle_2.col; - } - - // happy animation - if (ret.frames.happy.valid) { - ret.animations.happy[0] = ret.frames.happy.valid ? ret.frames.happy.col : ret.frames.idle_2.col; - ret.animations.happy[1] = ret.frames.idle_1.col; - ret.animations.happy[2] = ret.frames.happy.valid ? ret.frames.happy.col : ret.frames.idle_2.col; - ret.animations.happy[3] = ret.frames.idle_1.col; - } else { - // fallback - ret.animations.happy[0] = ret.frames.idle_1.col; - ret.animations.happy[1] = ret.frames.idle_2.col; - ret.animations.happy[2] = ret.frames.idle_1.col; - ret.animations.happy[3] = ret.frames.idle_2.col; - } - - return ret; - } - +created_result_t load_dm_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, + const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + auto result = + load_sprite_sheet_anim(*ctx._local_copy_config, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load dm Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + assert(result.result.total_frames > 0); ///< this SHOULD always work if it's an valid EMBEDDED image + assert(MAX_NUM_FRAMES <= INT_MAX); + assert(result.result.total_frames <= static_cast(MAX_NUM_FRAMES)); + if (result.result.total_frames > static_cast(MAX_NUM_FRAMES)) { + BONGOCAT_LOG_ERROR("Sprite Sheet does not fit in out_frames: %d, total_frames: %d", MAX_NUM_FRAMES, + result.result.total_frames); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + dm_sprite_sheet_t ret; + ret.image = bongocat::move(result.result.image); + ret.frame_width = bongocat::move(result.result.frame_width); + ret.frame_height = bongocat::move(result.result.frame_height); + ret.total_frames = bongocat::move(result.result.total_frames); + + ret.frames.idle_1 = bongocat::move(result.result.frames[0]); + ret.frames.idle_2 = bongocat::move(result.result.frames[1]); + ret.frames.angry = bongocat::move(result.result.frames[2]); + ret.frames.down = bongocat::move(result.result.frames[3]); + ret.frames.happy = bongocat::move(result.result.frames[4]); + ret.frames.eat_1 = bongocat::move(result.result.frames[5]); + ret.frames.sleep = bongocat::move(result.result.frames[6]); + ret.frames.refuse = bongocat::move(result.result.frames[7]); + ret.frames.sad = bongocat::move(result.result.frames[8]); + + ret.frames.lose_1 = bongocat::move(result.result.frames[9]); + ret.frames.eat_2 = bongocat::move(result.result.frames[10]); + ret.frames.lose_2 = bongocat::move(result.result.frames[11]); + ret.frames.attack_1 = bongocat::move(result.result.frames[12]); + + ret.frames.movement_1 = bongocat::move(result.result.frames[13]); + ret.frames.movement_2 = bongocat::move(result.result.frames[14]); + + // setup animations + using namespace assets; + + // minimal frames existing + assert(ret.frames.idle_1.valid); + assert(ret.frames.idle_2.valid); + assert(ret.frames.angry.valid); + assert(ret.frames.down.valid); + + assert(MAX_ANIMATION_FRAMES >= 4); + ret.animations.idle[0] = ret.frames.idle_1.col; + ret.animations.idle[1] = ret.frames.idle_2.col; + ret.animations.idle[2] = ret.frames.idle_1.col; + ret.animations.idle[3] = ret.frames.idle_2.col; + + ret.animations.boring[0] = ret.frames.sad.col ? ret.frames.sad.col : ret.frames.idle_1.col; + ret.animations.boring[1] = ret.frames.lose_1.col ? ret.frames.lose_1.col : ret.frames.idle_2.col; + ret.animations.boring[2] = ret.frames.lose_2.col ? ret.frames.lose_2.col : ret.frames.idle_1.col; + ret.animations.boring[3] = ret.frames.idle_2.col; + + ret.animations.writing[0] = ret.frames.idle_2.col; + ret.animations.writing[1] = ret.frames.idle_1.col; + ret.animations.writing[2] = ret.frames.idle_2.col; + ret.animations.writing[3] = ret.frames.idle_1.col; + + // sleep animation + if (ret.frames.sleep.valid) { + ret.animations.sleep[0] = ret.frames.sleep.col; + ret.animations.sleep[1] = ret.frames.sleep.col; + ret.animations.sleep[2] = ret.frames.sleep.col; + ret.animations.sleep[3] = ret.frames.sleep.col; + } else if (ret.frames.down.valid) { + ret.animations.sleep[0] = ret.frames.down.col; + ret.animations.sleep[1] = ret.frames.down.col; + ret.animations.sleep[2] = ret.frames.down.col; + ret.animations.sleep[3] = ret.frames.down.col; + } else { + // fallback + ret.animations.sleep[0] = ret.frames.idle_2.col; + ret.animations.sleep[1] = ret.frames.idle_2.col; + ret.animations.sleep[2] = ret.frames.idle_2.col; + ret.animations.sleep[3] = ret.frames.idle_2.col; + } + + ret.animations.wake_up[0] = ret.frames.idle_1.col; + ret.animations.wake_up[1] = ret.frames.idle_2.col; + ret.animations.wake_up[2] = ret.frames.idle_1.col; + ret.animations.wake_up[3] = ret.frames.idle_2.col; + + // working/attack animation + ret.animations.working[0] = ret.frames.idle_1.col; + ret.animations.working[1] = ret.frames.idle_2.col; + ret.animations.working[2] = ret.frames.angry.valid ? ret.frames.angry.col : ret.frames.idle_1.col; + ret.animations.working[3] = ret.frames.idle_2.col; + ret.animations.working[2] = ret.frames.attack_1.valid ? ret.frames.attack_1.col : ret.animations.working[2]; + ret.animations.working[3] = ret.frames.attack_2.valid ? ret.frames.attack_2.col : ret.animations.working[3]; + + // moving/walking animation + if (ret.frames.movement_1.valid || ret.frames.movement_2.valid) { + ret.animations.moving[0] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; + ret.animations.moving[1] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; + ret.animations.moving[2] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; + ret.animations.moving[3] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; + } else { + // fallback + ret.animations.moving[0] = ret.frames.idle_1.col; + ret.animations.moving[1] = ret.frames.idle_1.col; + ret.animations.moving[2] = ret.frames.idle_2.col; + ret.animations.moving[3] = ret.frames.idle_2.col; + } + // running animation (same as moving) + if (ret.frames.movement_1.valid || ret.frames.movement_2.valid) { + ret.animations.running[0] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; + ret.animations.running[1] = ret.frames.movement_1.valid ? ret.frames.movement_1.col : ret.frames.idle_1.col; + ret.animations.running[2] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; + ret.animations.running[3] = ret.frames.movement_2.valid ? ret.frames.movement_2.col : ret.frames.idle_2.col; + } else { + // fallback + ret.animations.running[0] = ret.frames.idle_1.col; + ret.animations.running[1] = ret.frames.idle_1.col; + ret.animations.running[2] = ret.frames.idle_2.col; + ret.animations.running[3] = ret.frames.idle_2.col; + } + + // happy animation + if (ret.frames.happy.valid) { + ret.animations.happy[0] = ret.frames.happy.valid ? ret.frames.happy.col : ret.frames.idle_2.col; + ret.animations.happy[1] = ret.frames.idle_1.col; + ret.animations.happy[2] = ret.frames.happy.valid ? ret.frames.happy.col : ret.frames.idle_2.col; + ret.animations.happy[3] = ret.frames.idle_1.col; + } else { + // fallback + ret.animations.happy[0] = ret.frames.idle_1.col; + ret.animations.happy[1] = ret.frames.idle_2.col; + ret.animations.happy[2] = ret.frames.idle_1.col; + ret.animations.happy[3] = ret.frames.idle_2.col; + } + + return ret; } + +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/bongocat/CMakeLists.txt b/src/image_loader/bongocat/CMakeLists.txt index 40b1af9e..29256a3f 100644 --- a/src/image_loader/bongocat/CMakeLists.txt +++ b/src/image_loader/bongocat/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_bongocat_loader STATIC) target_sources(assets_bongocat_loader PRIVATE load_images_bongocat.cpp) target_compile_options(assets_bongocat_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_bongocat_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/bongocat ${INCLUDE_DIR}/image_loader/bongocat - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_bongocat_loader - PUBLIC assets_image_loader assets_bongocat - PRIVATE assets_bongocat_interface assets_bongocat_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_bongocat_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/bongocat ${INCLUDE_DIR}/image_loader/bongocat + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_bongocat_loader + PUBLIC assets_image_loader assets_bongocat + PRIVATE assets_bongocat_interface assets_bongocat_feature bongocat_options) diff --git a/src/image_loader/bongocat/load_images_bongocat.cpp b/src/image_loader/bongocat/load_images_bongocat.cpp index 70db0c98..56895430 100644 --- a/src/image_loader/bongocat/load_images_bongocat.cpp +++ b/src/image_loader/bongocat/load_images_bongocat.cpp @@ -1,132 +1,138 @@ -#include "graphics/drawing.h" -#include "graphics/animation_context.h" +#include "embedded_assets/bongocat/bongocat.h" +#include "embedded_assets/bongocat/bongocat.hpp" #include "graphics/animation.h" -#include "utils/memory.h" +#include "graphics/animation_context.h" +#include "graphics/drawing.h" #include "image_loader/load_images.h" -#include "embedded_assets/bongocat/bongocat.hpp" -#include "embedded_assets/bongocat/bongocat.h" -#include +#include "utils/memory.h" +#include namespace bongocat::animation { - created_result_t load_bongocat_anim([[maybe_unused]] int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count) { - BONGOCAT_LOG_VERBOSE("Load bongocat Animation(index=%d) ...", anim_index); - auto result = anim_sprite_sheet_from_embedded_images(get_sprite, embedded_images_count); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return result.error; - } - - bongocat_sprite_sheet_t ret; - ret.image = bongocat::move(result.result.image); - ret.frame_width = bongocat::move(result.result.frame_width); - ret.frame_height = bongocat::move(result.result.frame_height); - ret.total_frames = bongocat::move(result.result.total_frames); - ret.both_up = bongocat::move(result.result.frames[0]); - ret.left_down = bongocat::move(result.result.frames[1]); - ret.right_down = bongocat::move(result.result.frames[2]); - ret.both_down = bongocat::move(result.result.frames[3]); - - // setup animations (cache) - using namespace assets; - - assert(ret.both_up.valid); - assert(ret.left_down.valid); - assert(ret.right_down.valid); - assert(ret.both_down.valid); - - assert(MAX_ANIMATION_FRAMES >= 4); - ret.animations.idle[0] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.idle[1] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.idle[2] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.idle[3] = BONGOCAT_FRAME_BOTH_UP; - - ret.animations.boring[0] = BONGOCAT_FRAME_BOTH_DOWN; - ret.animations.boring[1] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.boring[2] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.boring[3] = BONGOCAT_FRAME_BOTH_UP; - - ret.animations.writing[0] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.writing[1] = BONGOCAT_FRAME_RIGHT_DOWN; - ret.animations.writing[2] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.writing[3] = BONGOCAT_FRAME_RIGHT_DOWN; - - ret.animations.sleep[0] = BONGOCAT_FRAME_BOTH_DOWN; - ret.animations.sleep[1] = BONGOCAT_FRAME_BOTH_DOWN; - ret.animations.sleep[2] = BONGOCAT_FRAME_BOTH_DOWN; - ret.animations.sleep[3] = BONGOCAT_FRAME_BOTH_DOWN; - - ret.animations.wake_up[0] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.wake_up[1] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.wake_up[2] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.wake_up[3] = BONGOCAT_FRAME_BOTH_UP; - - ret.animations.working[0] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.working[1] = BONGOCAT_FRAME_RIGHT_DOWN; - ret.animations.working[2] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.working[3] = BONGOCAT_FRAME_RIGHT_DOWN; - - ret.animations.moving[0] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.moving[1] = BONGOCAT_FRAME_BOTH_DOWN; - ret.animations.moving[2] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.moving[3] = BONGOCAT_FRAME_BOTH_DOWN; - - ret.animations.happy[0] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.happy[1] = BONGOCAT_FRAME_RIGHT_DOWN; - ret.animations.happy[2] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.happy[3] = BONGOCAT_FRAME_RIGHT_DOWN; - - ret.animations.running[0] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.running[1] = BONGOCAT_FRAME_BOTH_DOWN; - ret.animations.running[2] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.running[3] = BONGOCAT_FRAME_BOTH_DOWN; - - ret.animations.left_writing[0] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.left_writing[1] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.left_writing[2] = BONGOCAT_FRAME_LEFT_DOWN; - ret.animations.left_writing[3] = BONGOCAT_FRAME_BOTH_UP; - - ret.animations.right_writing[0] = BONGOCAT_FRAME_RIGHT_DOWN; - ret.animations.right_writing[1] = BONGOCAT_FRAME_BOTH_UP; - ret.animations.right_writing[2] = BONGOCAT_FRAME_RIGHT_DOWN; - ret.animations.right_writing[3] = BONGOCAT_FRAME_BOTH_UP; - - return ret; - } - - bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - assert(anim_index >= 0 && static_cast(anim_index) < BONGOCAT_ANIM_COUNT); - BONGOCAT_LOG_VERBOSE("Load bongocat Animation (%d/%d): %s ...", anim_index, BONGOCAT_ANIM_COUNT, get_sprite(embedded_images_count).name); - auto result = load_bongocat_anim(anim_index, get_sprite, embedded_images_count); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load bongocat Animation failed: index: %d", anim_index); - return result.error; - } - assert(result.result.total_frames > 0); ///< this SHOULD always work if it's an valid EMBEDDED image - assert(MAX_NUM_FRAMES <= INT_MAX); - assert(result.result.total_frames <= static_cast(MAX_NUM_FRAMES)); - if (result.result.total_frames > static_cast(MAX_NUM_FRAMES)) { - BONGOCAT_LOG_ERROR("Sprite Sheet does not fit in out_frames: %d, total_frames: %d", MAX_NUM_FRAMES, result.result.total_frames); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - - assert(anim_index >= 0); - ctx.shm->bongocat_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->bongocat_anims[static_cast(anim_index)].type == animation_t::Type::Bongocat); - - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, int index) { - using namespace assets; - using namespace animation; - switch (index) { - case BONGOCAT_ANIM_INDEX: return load_bongocat_anim(BONGOCAT_ANIM_INDEX, get_bongocat_sprite, BONGOCAT_EMBEDDED_IMAGES_COUNT); - default: return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } +created_result_t +load_bongocat_anim([[maybe_unused]] int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count) { + BONGOCAT_LOG_VERBOSE("Load bongocat Animation(index=%d) ...", anim_index); + auto result = anim_sprite_sheet_from_embedded_images(get_sprite, embedded_images_count); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return result.error; + } + + bongocat_sprite_sheet_t ret; + ret.image = bongocat::move(result.result.image); + ret.frame_width = bongocat::move(result.result.frame_width); + ret.frame_height = bongocat::move(result.result.frame_height); + ret.total_frames = bongocat::move(result.result.total_frames); + ret.both_up = bongocat::move(result.result.frames[0]); + ret.left_down = bongocat::move(result.result.frames[1]); + ret.right_down = bongocat::move(result.result.frames[2]); + ret.both_down = bongocat::move(result.result.frames[3]); + + // setup animations (cache) + using namespace assets; + + assert(ret.both_up.valid); + assert(ret.left_down.valid); + assert(ret.right_down.valid); + assert(ret.both_down.valid); + + assert(MAX_ANIMATION_FRAMES >= 4); + ret.animations.idle[0] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.idle[1] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.idle[2] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.idle[3] = BONGOCAT_FRAME_BOTH_UP; + + ret.animations.boring[0] = BONGOCAT_FRAME_BOTH_DOWN; + ret.animations.boring[1] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.boring[2] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.boring[3] = BONGOCAT_FRAME_BOTH_UP; + + ret.animations.writing[0] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.writing[1] = BONGOCAT_FRAME_RIGHT_DOWN; + ret.animations.writing[2] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.writing[3] = BONGOCAT_FRAME_RIGHT_DOWN; + + ret.animations.sleep[0] = BONGOCAT_FRAME_BOTH_DOWN; + ret.animations.sleep[1] = BONGOCAT_FRAME_BOTH_DOWN; + ret.animations.sleep[2] = BONGOCAT_FRAME_BOTH_DOWN; + ret.animations.sleep[3] = BONGOCAT_FRAME_BOTH_DOWN; + + ret.animations.wake_up[0] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.wake_up[1] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.wake_up[2] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.wake_up[3] = BONGOCAT_FRAME_BOTH_UP; + + ret.animations.working[0] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.working[1] = BONGOCAT_FRAME_RIGHT_DOWN; + ret.animations.working[2] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.working[3] = BONGOCAT_FRAME_RIGHT_DOWN; + + ret.animations.moving[0] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.moving[1] = BONGOCAT_FRAME_BOTH_DOWN; + ret.animations.moving[2] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.moving[3] = BONGOCAT_FRAME_BOTH_DOWN; + + ret.animations.happy[0] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.happy[1] = BONGOCAT_FRAME_RIGHT_DOWN; + ret.animations.happy[2] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.happy[3] = BONGOCAT_FRAME_RIGHT_DOWN; + + ret.animations.running[0] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.running[1] = BONGOCAT_FRAME_BOTH_DOWN; + ret.animations.running[2] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.running[3] = BONGOCAT_FRAME_BOTH_DOWN; + + ret.animations.left_writing[0] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.left_writing[1] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.left_writing[2] = BONGOCAT_FRAME_LEFT_DOWN; + ret.animations.left_writing[3] = BONGOCAT_FRAME_BOTH_UP; + + ret.animations.right_writing[0] = BONGOCAT_FRAME_RIGHT_DOWN; + ret.animations.right_writing[1] = BONGOCAT_FRAME_BOTH_UP; + ret.animations.right_writing[2] = BONGOCAT_FRAME_RIGHT_DOWN; + ret.animations.right_writing[3] = BONGOCAT_FRAME_BOTH_UP; + + return ret; +} + +bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, + size_t embedded_images_count) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + assert(anim_index >= 0 && static_cast(anim_index) < BONGOCAT_ANIM_COUNT); + BONGOCAT_LOG_VERBOSE("Load bongocat Animation (%d/%d): %s ...", anim_index, BONGOCAT_ANIM_COUNT, + get_sprite(embedded_images_count).name); + auto result = load_bongocat_anim(anim_index, get_sprite, embedded_images_count); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load bongocat Animation failed: index: %d", anim_index); + return result.error; + } + assert(result.result.total_frames > 0); ///< this SHOULD always work if it's an valid EMBEDDED image + assert(MAX_NUM_FRAMES <= INT_MAX); + assert(result.result.total_frames <= static_cast(MAX_NUM_FRAMES)); + if (result.result.total_frames > static_cast(MAX_NUM_FRAMES)) { + BONGOCAT_LOG_ERROR("Sprite Sheet does not fit in out_frames: %d, total_frames: %d", MAX_NUM_FRAMES, + result.result.total_frames); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + assert(anim_index >= 0); + ctx.shm->bongocat_anims[static_cast(anim_index)] = bongocat::move(result.result); + assert(ctx.shm->bongocat_anims[static_cast(anim_index)].type == animation_t::Type::Bongocat); + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, int index) { + using namespace assets; + using namespace animation; + switch (index) { + case BONGOCAT_ANIM_INDEX: + return load_bongocat_anim(BONGOCAT_ANIM_INDEX, get_bongocat_sprite, BONGOCAT_EMBEDDED_IMAGES_COUNT); + default: + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; } +} // namespace bongocat::animation diff --git a/src/image_loader/custom/CMakeLists.txt b/src/image_loader/custom/CMakeLists.txt index 59bd254c..a67071a3 100644 --- a/src/image_loader/custom/CMakeLists.txt +++ b/src/image_loader/custom/CMakeLists.txt @@ -1,5 +1,11 @@ add_library(assets_custom_loader STATIC) target_sources(assets_custom_loader PRIVATE load_custom.cpp) target_compile_options(assets_custom_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_custom_loader PRIVATE ${INCLUDE_DIR}/image_loader/custom PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_custom_loader PUBLIC assets_image_loader PRIVATE bongocat_options) \ No newline at end of file +target_include_directories( + assets_custom_loader + PRIVATE ${INCLUDE_DIR}/image_loader/custom + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_custom_loader + PUBLIC assets_image_loader + PRIVATE bongocat_options) diff --git a/src/image_loader/custom/load_custom.cpp b/src/image_loader/custom/load_custom.cpp index 9629bc13..f4ddf1bf 100644 --- a/src/image_loader/custom/load_custom.cpp +++ b/src/image_loader/custom/load_custom.cpp @@ -1,218 +1,326 @@ #include "image_loader/custom/load_custom.h" + #include "graphics/animation_context.h" #include "image_loader/load_images.h" namespace bongocat::animation { - created_result_t load_custom_sprite_sheet_file(const char* filename) { - using namespace assets; - BONGOCAT_CHECK_NULL(filename, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - auto file_content_result = platform::make_allocated_mmap_file_content_open(filename); - if (file_content_result.error != bongocat_error_t::BONGOCAT_SUCCESS) { - return file_content_result.error; - } - - assets::custom_image_t ret; - ret.data = bongocat::move(file_content_result.result); - ret.name = ::strdup(filename); - return ret; - } - void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept { - platform::release_allocated_mmap_file_content(image.data); - if (image.name) ::free(image.name); - image.name = nullptr; - } - - created_result_t load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { - assert(sprite_sheet_image.data._size_bytes >= 0); - return load_custom_anim(ctx, assets::embedded_image_t{ .data = sprite_sheet_image.data.data, .size = static_cast(sprite_sheet_image.data._size_bytes), .name = sprite_sheet_image.name ? sprite_sheet_image.name : "" }, sprite_sheet_settings); - } - created_result_t load_custom_anim(const animation_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - const int sprite_sheet_cols = get_custom_animation_settings_max_cols(sprite_sheet_settings); - const int sprite_sheet_rows = get_custom_animation_settings_rows_count(sprite_sheet_settings); - - if (sprite_sheet_cols == 0 || sprite_sheet_rows == 0) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load custom Animation failed, no cols and rows: %s; %i, %i", sprite_sheet_image.name, sprite_sheet_cols, sprite_sheet_rows); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - - auto result = load_sprite_sheet_anim(*ctx._local_copy_config, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load custom Animation failed: %s", sprite_sheet_image.name); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - - custom_sprite_sheet_t ret; - ret.image = bongocat::move(result.result.image); - ret.frame_width = bongocat::move(result.result.frame_width); - ret.frame_height = bongocat::move(result.result.frame_height); - - // setup animations - if (sprite_sheet_rows > 0) { - int row = 0; - - if (sprite_sheet_settings.idle_frames > 0) { - ret.idle = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.idle_frames-1, .row = sprite_sheet_settings.idle_row_index >= 0 ? sprite_sheet_settings.idle_row_index : row }; - ret.idle.row = ret.idle.row >= 0 ? ret.idle.row : 0; - ret.idle.row = ret.idle.row < sprite_sheet_rows ? ret.idle.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.boring_frames > 0) { - ret.boring = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.boring_frames-1, .row = sprite_sheet_settings.boring_row_index >= 0 ? sprite_sheet_settings.boring_row_index : row }; - ret.boring.row = ret.boring.row >= 0 ? ret.boring.row : 0; - ret.boring.row = ret.boring.row < sprite_sheet_rows ? ret.boring.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.start_writing_frames > 0) { - ret.start_writing = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.start_writing_frames-1, .row = sprite_sheet_settings.start_writing_row_index >= 0 ? sprite_sheet_settings.start_writing_row_index : row }; - ret.start_writing.row = ret.start_writing.row >= 0 ? ret.start_writing.row : 0; - ret.start_writing.row = ret.start_writing.row < sprite_sheet_rows ? ret.start_writing.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.writing_frames > 0) { - ret.writing = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.writing_frames-1, .row = sprite_sheet_settings.writing_row_index >= 0 ? sprite_sheet_settings.writing_row_index : row }; - ret.writing.row = ret.writing.row >= 0 ? ret.writing.row : 0; - ret.writing.row = ret.writing.row < sprite_sheet_rows ? ret.writing.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.end_writing_frames > 0) { - ret.end_writing = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.end_writing_frames-1, .row = sprite_sheet_settings.end_writing_row_index >= 0 ? sprite_sheet_settings.end_writing_row_index : row }; - ret.end_writing.row = ret.end_writing.row >= 0 ? ret.end_writing.row : 0; - ret.end_writing.row = ret.end_writing.row < sprite_sheet_rows ? ret.end_writing.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.happy_frames > 0) { - ret.happy = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.happy_frames-1, .row = sprite_sheet_settings.happy_row_index >= 0 ? sprite_sheet_settings.happy_row_index : row }; - ret.happy.row = ret.happy.row >= 0 ? ret.happy.row : 0; - ret.happy.row = ret.happy.row < sprite_sheet_rows ? ret.happy.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.asleep_frames > 0) { - ret.fall_asleep = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.asleep_frames-1, .row = sprite_sheet_settings.asleep_row_index >= 0 ? sprite_sheet_settings.asleep_row_index : row }; - ret.fall_asleep.row = ret.fall_asleep.row >= 0 ? ret.fall_asleep.row : 0; - ret.fall_asleep.row = ret.fall_asleep.row < sprite_sheet_rows ? ret.fall_asleep.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.sleep_frames > 0) { - ret.sleep = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.sleep_frames-1, .row = sprite_sheet_settings.sleep_row_index >= 0 ? sprite_sheet_settings.sleep_row_index : row }; - ret.sleep.row = ret.sleep.row >= 0 ? ret.sleep.row : 0; - ret.sleep.row = ret.sleep.row < sprite_sheet_rows ? ret.sleep.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.wake_up_frames > 0) { - ret.wake_up = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.wake_up_frames-1, .row = sprite_sheet_settings.wake_up_row_index >= 0 ? sprite_sheet_settings.wake_up_row_index : row }; - ret.wake_up.row = ret.wake_up.row >= 0 ? ret.wake_up.row : 0; - ret.wake_up.row = ret.wake_up.row < sprite_sheet_rows ? ret.wake_up.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.start_working_frames > 0) { - ret.start_working = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.start_working_frames-1, .row = sprite_sheet_settings.start_working_row_index >= 0 ? sprite_sheet_settings.start_working_row_index : row }; - ret.start_working.row = ret.start_working.row >= 0 ? ret.start_working.row : 0; - ret.start_working.row = ret.start_working.row < sprite_sheet_rows ? ret.start_working.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.working_frames > 0) { - ret.working = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.working_frames-1, .row = sprite_sheet_settings.working_row_index >= 0 ? sprite_sheet_settings.working_row_index : row }; - ret.working.row = ret.working.row >= 0 ? ret.working.row : 0; - ret.working.row = ret.working.row < sprite_sheet_rows ? ret.working.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.end_working_frames > 0) { - ret.end_working = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.end_working_frames-1, .row = sprite_sheet_settings.end_working_row_index >= 0 ? sprite_sheet_settings.end_working_row_index : row }; - ret.end_working.row = ret.end_working.row >= 0 ? ret.end_working.row : 0; - ret.end_working.row = ret.end_working.row < sprite_sheet_rows ? ret.end_working.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.start_moving_frames > 0) { - ret.start_moving = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.start_moving_frames-1, .row = sprite_sheet_settings.start_moving_row_index >= 0 ? sprite_sheet_settings.start_moving_row_index : row }; - ret.start_moving.row = ret.start_moving.row >= 0 ? ret.start_moving.row : 0; - ret.start_moving.row = ret.start_moving.row < sprite_sheet_rows ? ret.start_moving.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.moving_frames > 0) { - ret.moving = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.moving_frames-1, .row = sprite_sheet_settings.moving_row_index >= 0 ? sprite_sheet_settings.moving_row_index : row }; - ret.moving.row = ret.moving.row >= 0 ? ret.moving.row : 0; - ret.moving.row = ret.moving.row < sprite_sheet_rows ? ret.moving.row : sprite_sheet_rows-1; - row++; - } - if (sprite_sheet_settings.end_moving_frames > 0) { - ret.end_moving = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.end_moving_frames-1, .row = sprite_sheet_settings.end_moving_row_index >= 0 ? sprite_sheet_settings.end_moving_row_index : row }; - ret.end_moving.row = ret.end_moving.row >= 0 ? ret.end_moving.row : 0; - ret.end_moving.row = ret.end_moving.row < sprite_sheet_rows ? ret.end_moving.row : sprite_sheet_rows-1; - row++; - } - - if (sprite_sheet_settings.start_running_frames > 0) { - ret.start_running = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.start_running_frames-1, .row = sprite_sheet_settings.start_running_row_index >= 0 ? sprite_sheet_settings.start_running_row_index : row }; - if (ret.start_moving.valid) { - ret.start_running.row = ret.start_moving.row; - } else { - ret.start_running.row = ret.start_running.row >= 0 ? ret.start_running.row : 0; - ret.start_running.row = ret.start_running.row < sprite_sheet_rows ? ret.start_running.row : sprite_sheet_rows-1; - } - row++; - } else if (ret.start_moving.valid) { - ret.start_running = { .valid = true, .start_col = ret.start_moving.start_col, .end_col = ret.start_moving.end_col, .row = sprite_sheet_settings.start_running_row_index >= 0 ? sprite_sheet_settings.start_running_row_index : ret.start_moving.row }; - } - if (sprite_sheet_settings.running_frames > 0) { - ret.running = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.running_frames-1, .row = sprite_sheet_settings.running_row_index >= 0 ? sprite_sheet_settings.running_row_index : row }; - if (ret.moving.valid) { - ret.running.row = ret.moving.row; - } else { - ret.running.row = ret.running.row >= 0 ? ret.running.row : 0; - ret.running.row = ret.running.row < sprite_sheet_rows ? ret.running.row : sprite_sheet_rows-1; - } - row++; - } else if (ret.moving.valid) { - ret.running = { .valid = true, .start_col = ret.moving.start_col, .end_col = ret.moving.end_col, .row = sprite_sheet_settings.running_row_index >= 0 ? sprite_sheet_settings.running_row_index : ret.moving.row }; - } - if (sprite_sheet_settings.end_running_frames > 0) { - ret.end_running = { .valid = true, .start_col = 0, .end_col = sprite_sheet_settings.end_running_frames-1, .row = sprite_sheet_settings.end_running_row_index >= 0 ? sprite_sheet_settings.end_running_row_index : row }; - if (ret.end_moving.valid) { - ret.end_running.row = ret.end_moving.row; - } else { - ret.end_running.row = ret.end_running.row >= 0 ? ret.end_running.row : 0; - ret.end_running.row = ret.end_running.row < sprite_sheet_rows ? ret.end_running.row : sprite_sheet_rows-1; - } - row++; - } else if (ret.end_moving.valid) { - ret.end_running = { .valid = true, .start_col = ret.end_moving.start_col, .end_col = ret.end_moving.end_col, .row = sprite_sheet_settings.end_running_row_index >= 0 ? sprite_sheet_settings.end_running_row_index : ret.end_moving.row }; - } - } - - // features - ret.feature_idle = ret.idle.valid; - ret.feature_boring = ret.boring.valid; - ret.feature_writing = ret.writing.valid || ret.start_writing.valid || ret.end_writing.valid; - ret.feature_writing_happy = ret.feature_writing && ret.happy.valid; - ret.feature_sleep = ret.sleep.valid || ret.fall_asleep.valid; - ret.feature_sleep_wake_up = ret.feature_sleep && ret.wake_up.valid; - ret.feature_working = ret.working.valid || ret.start_working.valid || ret.end_working.valid; - ret.feature_moving = ret.moving.valid || ret.start_moving.valid || ret.end_moving.valid; - ret.feature_running = ret.running.valid || ret.start_running.valid || ret.end_running.valid; - // is feature_toggle_writing_frames enabled or writing has only 2 frames (default) - ret.feature_writing_toggle_frames = ret.working.valid && (sprite_sheet_settings.feature_toggle_writing_frames >= 1 || (sprite_sheet_settings.feature_toggle_writing_frames < 0 && !ret.start_moving.valid && !ret.end_working.valid && ret.working.valid && sprite_sheet_settings.working_frames == 2)); - ret.feature_writing_toggle_frames_random = ret.working.valid && (sprite_sheet_settings.feature_toggle_writing_frames_random >= 1 || (sprite_sheet_settings.feature_toggle_writing_frames_random < 0 && !ret.start_moving.valid && !ret.end_working.valid && ret.working.valid && sprite_sheet_settings.working_frames == 2)); - - if (!ret.feature_idle) [[unlikely]] { - BONGOCAT_LOG_WARNING("Custom Animation without idle animation: %s", sprite_sheet_image.name); - // default to first frame - ret.idle = { .valid = true, .start_col = 0, .end_col = 0, .row = sprite_sheet_settings.idle_row_index >= 0 ? sprite_sheet_settings.idle_row_index : 0 }; - ret.idle.row = ret.idle.row >= 0 ? ret.idle.row : 0; - ret.idle.row = ret.idle.row < sprite_sheet_rows ? ret.idle.row : sprite_sheet_rows-1; - } - - return ret; +created_result_t load_custom_sprite_sheet_file(const char *filename) { + using namespace assets; + BONGOCAT_CHECK_NULL(filename, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + auto file_content_result = platform::make_allocated_mmap_file_content_open(filename); + if (file_content_result.error != bongocat_error_t::BONGOCAT_SUCCESS) { + return file_content_result.error; + } + + assets::custom_image_t ret; + ret.data = bongocat::move(file_content_result.result); + ret.name = ::strdup(filename); + return ret; +} +void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept { + platform::release_allocated_mmap_file_content(image.data); + if (image.name) + ::free(image.name); + image.name = nullptr; +} + +created_result_t +load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, + const assets::custom_animation_settings_t& sprite_sheet_settings) { + assert(sprite_sheet_image.data._size_bytes >= 0); + return load_custom_anim(ctx, + assets::embedded_image_t{.data = sprite_sheet_image.data.data, + .size = static_cast(sprite_sheet_image.data._size_bytes), + .name = sprite_sheet_image.name ? sprite_sheet_image.name : ""}, + sprite_sheet_settings); +} +created_result_t +load_custom_anim(const animation_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, + const assets::custom_animation_settings_t& sprite_sheet_settings) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + const int sprite_sheet_cols = get_custom_animation_settings_max_cols(sprite_sheet_settings); + const int sprite_sheet_rows = get_custom_animation_settings_rows_count(sprite_sheet_settings); + + if (sprite_sheet_cols == 0 || sprite_sheet_rows == 0) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load custom Animation failed, no cols and rows: %s; %i, %i", sprite_sheet_image.name, + sprite_sheet_cols, sprite_sheet_rows); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + + auto result = + load_sprite_sheet_anim(*ctx._local_copy_config, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load custom Animation failed: %s", sprite_sheet_image.name); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + + custom_sprite_sheet_t ret; + ret.image = bongocat::move(result.result.image); + ret.frame_width = bongocat::move(result.result.frame_width); + ret.frame_height = bongocat::move(result.result.frame_height); + + // setup animations + if (sprite_sheet_rows > 0) { + int row = 0; + + if (sprite_sheet_settings.idle_frames > 0) { + ret.idle = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.idle_frames - 1, + .row = sprite_sheet_settings.idle_row_index >= 0 ? sprite_sheet_settings.idle_row_index : row}; + ret.idle.row = ret.idle.row >= 0 ? ret.idle.row : 0; + ret.idle.row = ret.idle.row < sprite_sheet_rows ? ret.idle.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.boring_frames > 0) { + ret.boring = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.boring_frames - 1, + .row = sprite_sheet_settings.boring_row_index >= 0 ? sprite_sheet_settings.boring_row_index : row}; + ret.boring.row = ret.boring.row >= 0 ? ret.boring.row : 0; + ret.boring.row = ret.boring.row < sprite_sheet_rows ? ret.boring.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.start_writing_frames > 0) { + ret.start_writing = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.start_writing_frames - 1, + .row = sprite_sheet_settings.start_writing_row_index >= 0 + ? sprite_sheet_settings.start_writing_row_index + : row}; + ret.start_writing.row = ret.start_writing.row >= 0 ? ret.start_writing.row : 0; + ret.start_writing.row = ret.start_writing.row < sprite_sheet_rows ? ret.start_writing.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.writing_frames > 0) { + ret.writing = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.writing_frames - 1, + .row = + sprite_sheet_settings.writing_row_index >= 0 ? sprite_sheet_settings.writing_row_index : row}; + ret.writing.row = ret.writing.row >= 0 ? ret.writing.row : 0; + ret.writing.row = ret.writing.row < sprite_sheet_rows ? ret.writing.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.end_writing_frames > 0) { + ret.end_writing = { + .valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.end_writing_frames - 1, + .row = sprite_sheet_settings.end_writing_row_index >= 0 ? sprite_sheet_settings.end_writing_row_index : row}; + ret.end_writing.row = ret.end_writing.row >= 0 ? ret.end_writing.row : 0; + ret.end_writing.row = ret.end_writing.row < sprite_sheet_rows ? ret.end_writing.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.happy_frames > 0) { + ret.happy = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.happy_frames - 1, + .row = sprite_sheet_settings.happy_row_index >= 0 ? sprite_sheet_settings.happy_row_index : row}; + ret.happy.row = ret.happy.row >= 0 ? ret.happy.row : 0; + ret.happy.row = ret.happy.row < sprite_sheet_rows ? ret.happy.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.asleep_frames > 0) { + ret.fall_asleep = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.asleep_frames - 1, + .row = sprite_sheet_settings.asleep_row_index >= 0 ? sprite_sheet_settings.asleep_row_index + : row}; + ret.fall_asleep.row = ret.fall_asleep.row >= 0 ? ret.fall_asleep.row : 0; + ret.fall_asleep.row = ret.fall_asleep.row < sprite_sheet_rows ? ret.fall_asleep.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.sleep_frames > 0) { + ret.sleep = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.sleep_frames - 1, + .row = sprite_sheet_settings.sleep_row_index >= 0 ? sprite_sheet_settings.sleep_row_index : row}; + ret.sleep.row = ret.sleep.row >= 0 ? ret.sleep.row : 0; + ret.sleep.row = ret.sleep.row < sprite_sheet_rows ? ret.sleep.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.wake_up_frames > 0) { + ret.wake_up = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.wake_up_frames - 1, + .row = + sprite_sheet_settings.wake_up_row_index >= 0 ? sprite_sheet_settings.wake_up_row_index : row}; + ret.wake_up.row = ret.wake_up.row >= 0 ? ret.wake_up.row : 0; + ret.wake_up.row = ret.wake_up.row < sprite_sheet_rows ? ret.wake_up.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.start_working_frames > 0) { + ret.start_working = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.start_working_frames - 1, + .row = sprite_sheet_settings.start_working_row_index >= 0 + ? sprite_sheet_settings.start_working_row_index + : row}; + ret.start_working.row = ret.start_working.row >= 0 ? ret.start_working.row : 0; + ret.start_working.row = ret.start_working.row < sprite_sheet_rows ? ret.start_working.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.working_frames > 0) { + ret.working = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.working_frames - 1, + .row = + sprite_sheet_settings.working_row_index >= 0 ? sprite_sheet_settings.working_row_index : row}; + ret.working.row = ret.working.row >= 0 ? ret.working.row : 0; + ret.working.row = ret.working.row < sprite_sheet_rows ? ret.working.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.end_working_frames > 0) { + ret.end_working = { + .valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.end_working_frames - 1, + .row = sprite_sheet_settings.end_working_row_index >= 0 ? sprite_sheet_settings.end_working_row_index : row}; + ret.end_working.row = ret.end_working.row >= 0 ? ret.end_working.row : 0; + ret.end_working.row = ret.end_working.row < sprite_sheet_rows ? ret.end_working.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.start_moving_frames > 0) { + ret.start_moving = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.start_moving_frames - 1, + .row = sprite_sheet_settings.start_moving_row_index >= 0 + ? sprite_sheet_settings.start_moving_row_index + : row}; + ret.start_moving.row = ret.start_moving.row >= 0 ? ret.start_moving.row : 0; + ret.start_moving.row = ret.start_moving.row < sprite_sheet_rows ? ret.start_moving.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.moving_frames > 0) { + ret.moving = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.moving_frames - 1, + .row = sprite_sheet_settings.moving_row_index >= 0 ? sprite_sheet_settings.moving_row_index : row}; + ret.moving.row = ret.moving.row >= 0 ? ret.moving.row : 0; + ret.moving.row = ret.moving.row < sprite_sheet_rows ? ret.moving.row : sprite_sheet_rows - 1; + row++; + } + if (sprite_sheet_settings.end_moving_frames > 0) { + ret.end_moving = { + .valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.end_moving_frames - 1, + .row = sprite_sheet_settings.end_moving_row_index >= 0 ? sprite_sheet_settings.end_moving_row_index : row}; + ret.end_moving.row = ret.end_moving.row >= 0 ? ret.end_moving.row : 0; + ret.end_moving.row = ret.end_moving.row < sprite_sheet_rows ? ret.end_moving.row : sprite_sheet_rows - 1; + row++; + } + + if (sprite_sheet_settings.start_running_frames > 0) { + ret.start_running = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.start_running_frames - 1, + .row = sprite_sheet_settings.start_running_row_index >= 0 + ? sprite_sheet_settings.start_running_row_index + : row}; + if (ret.start_moving.valid) { + ret.start_running.row = ret.start_moving.row; + } else { + ret.start_running.row = ret.start_running.row >= 0 ? ret.start_running.row : 0; + ret.start_running.row = + ret.start_running.row < sprite_sheet_rows ? ret.start_running.row : sprite_sheet_rows - 1; + } + row++; + } else if (ret.start_moving.valid) { + ret.start_running = {.valid = true, + .start_col = ret.start_moving.start_col, + .end_col = ret.start_moving.end_col, + .row = sprite_sheet_settings.start_running_row_index >= 0 + ? sprite_sheet_settings.start_running_row_index + : ret.start_moving.row}; } + if (sprite_sheet_settings.running_frames > 0) { + ret.running = {.valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.running_frames - 1, + .row = + sprite_sheet_settings.running_row_index >= 0 ? sprite_sheet_settings.running_row_index : row}; + if (ret.moving.valid) { + ret.running.row = ret.moving.row; + } else { + ret.running.row = ret.running.row >= 0 ? ret.running.row : 0; + ret.running.row = ret.running.row < sprite_sheet_rows ? ret.running.row : sprite_sheet_rows - 1; + } + row++; + } else if (ret.moving.valid) { + ret.running = {.valid = true, + .start_col = ret.moving.start_col, + .end_col = ret.moving.end_col, + .row = sprite_sheet_settings.running_row_index >= 0 ? sprite_sheet_settings.running_row_index + : ret.moving.row}; + } + if (sprite_sheet_settings.end_running_frames > 0) { + ret.end_running = { + .valid = true, + .start_col = 0, + .end_col = sprite_sheet_settings.end_running_frames - 1, + .row = sprite_sheet_settings.end_running_row_index >= 0 ? sprite_sheet_settings.end_running_row_index : row}; + if (ret.end_moving.valid) { + ret.end_running.row = ret.end_moving.row; + } else { + ret.end_running.row = ret.end_running.row >= 0 ? ret.end_running.row : 0; + ret.end_running.row = ret.end_running.row < sprite_sheet_rows ? ret.end_running.row : sprite_sheet_rows - 1; + } + row++; + } else if (ret.end_moving.valid) { + ret.end_running = {.valid = true, + .start_col = ret.end_moving.start_col, + .end_col = ret.end_moving.end_col, + .row = sprite_sheet_settings.end_running_row_index >= 0 + ? sprite_sheet_settings.end_running_row_index + : ret.end_moving.row}; + } + } + + // features + ret.feature_idle = ret.idle.valid; + ret.feature_boring = ret.boring.valid; + ret.feature_writing = ret.writing.valid || ret.start_writing.valid || ret.end_writing.valid; + ret.feature_writing_happy = ret.feature_writing && ret.happy.valid; + ret.feature_sleep = ret.sleep.valid || ret.fall_asleep.valid; + ret.feature_sleep_wake_up = ret.feature_sleep && ret.wake_up.valid; + ret.feature_working = ret.working.valid || ret.start_working.valid || ret.end_working.valid; + ret.feature_moving = ret.moving.valid || ret.start_moving.valid || ret.end_moving.valid; + ret.feature_running = ret.running.valid || ret.start_running.valid || ret.end_running.valid; + // is feature_toggle_writing_frames enabled or writing has only 2 frames (default) + ret.feature_writing_toggle_frames = + ret.working.valid && (sprite_sheet_settings.feature_toggle_writing_frames >= 1 || + (sprite_sheet_settings.feature_toggle_writing_frames < 0 && !ret.start_moving.valid && + !ret.end_working.valid && ret.working.valid && sprite_sheet_settings.working_frames == 2)); + ret.feature_writing_toggle_frames_random = + ret.working.valid && + (sprite_sheet_settings.feature_toggle_writing_frames_random >= 1 || + (sprite_sheet_settings.feature_toggle_writing_frames_random < 0 && !ret.start_moving.valid && + !ret.end_working.valid && ret.working.valid && sprite_sheet_settings.working_frames == 2)); + if (!ret.feature_idle) [[unlikely]] { + BONGOCAT_LOG_WARNING("Custom Animation without idle animation: %s", sprite_sheet_image.name); + // default to first frame + ret.idle = {.valid = true, + .start_col = 0, + .end_col = 0, + .row = sprite_sheet_settings.idle_row_index >= 0 ? sprite_sheet_settings.idle_row_index : 0}; + ret.idle.row = ret.idle.row >= 0 ? ret.idle.row : 0; + ret.idle.row = ret.idle.row < sprite_sheet_rows ? ret.idle.row : sprite_sheet_rows - 1; + } + + return ret; } + +} // namespace bongocat::animation diff --git a/src/image_loader/dm/CMakeLists.txt b/src/image_loader/dm/CMakeLists.txt index 508077cf..038f8490 100644 --- a/src/image_loader/dm/CMakeLists.txt +++ b/src/image_loader/dm/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_dm_loader STATIC) target_sources(assets_dm_loader PRIVATE dm_load_sprite_sheet.cpp load_images_dm.cpp) target_compile_options(assets_dm_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_dm_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/dm ${INCLUDE_DIR}/image_loader/dm - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_dm_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_dm - PRIVATE assets_dm_interface assets_dm_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_dm_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/dm ${INCLUDE_DIR}/image_loader/dm + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_dm_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_dm + PRIVATE assets_dm_interface assets_dm_feature bongocat_options) diff --git a/src/image_loader/dm20/CMakeLists.txt b/src/image_loader/dm20/CMakeLists.txt index 36af11e6..010a7dcf 100644 --- a/src/image_loader/dm20/CMakeLists.txt +++ b/src/image_loader/dm20/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_dm20_loader STATIC) target_sources(assets_dm20_loader PRIVATE dm20_load_sprite_sheet.cpp load_images_dm20.cpp) target_compile_options(assets_dm20_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_dm20_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/dm20 ${INCLUDE_DIR}/image_loader/dm20 - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_dm20_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_dm20 - PRIVATE assets_dm20_interface assets_dm20_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_dm20_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/dm20 ${INCLUDE_DIR}/image_loader/dm20 + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_dm20_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_dm20 + PRIVATE assets_dm20_interface assets_dm20_feature bongocat_options) diff --git a/src/image_loader/dmall/CMakeLists.txt b/src/image_loader/dmall/CMakeLists.txt index 65ad38cb..202f229a 100644 --- a/src/image_loader/dmall/CMakeLists.txt +++ b/src/image_loader/dmall/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_dmall_loader STATIC) target_sources(assets_dmall_loader PRIVATE dmall_load_sprite_sheet.cpp load_images_dmall.cpp) target_compile_options(assets_dmall_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_dmall_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/dmall ${INCLUDE_DIR}/image_loader/dmall - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_dmall_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_dmall - PRIVATE assets_dmall_interface assets_dmall_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_dmall_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/dmall ${INCLUDE_DIR}/image_loader/dmall + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_dmall_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_dmall + PRIVATE assets_dmall_interface assets_dmall_feature bongocat_options) diff --git a/src/image_loader/dmc/CMakeLists.txt b/src/image_loader/dmc/CMakeLists.txt index 98be355c..a9881535 100644 --- a/src/image_loader/dmc/CMakeLists.txt +++ b/src/image_loader/dmc/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_dmc_loader STATIC) target_sources(assets_dmc_loader PRIVATE dmc_load_sprite_sheet.cpp load_images_dmc.cpp) target_compile_options(assets_dmc_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_dmc_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/dmc ${INCLUDE_DIR}/image_loader/dmc - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_dmc_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_dmc - PRIVATE assets_dmc_interface assets_dmc_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_dmc_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/dmc ${INCLUDE_DIR}/image_loader/dmc + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_dmc_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_dmc + PRIVATE assets_dmc_interface assets_dmc_feature bongocat_options) diff --git a/src/image_loader/dmx/CMakeLists.txt b/src/image_loader/dmx/CMakeLists.txt index 3fa89ba7..8c4cde37 100644 --- a/src/image_loader/dmx/CMakeLists.txt +++ b/src/image_loader/dmx/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_dmx_loader STATIC) target_sources(assets_dmx_loader PRIVATE dmx_load_sprite_sheet.cpp load_images_dmx.cpp) target_compile_options(assets_dmx_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_dmx_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/dmx ${INCLUDE_DIR}/image_loader/dmx - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_dmx_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_dmx - PRIVATE assets_dmx_interface assets_dmx_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_dmx_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/dmx ${INCLUDE_DIR}/image_loader/dmx + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_dmx_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_dmx + PRIVATE assets_dmx_interface assets_dmx_feature bongocat_options) diff --git a/src/image_loader/load_images.cpp b/src/image_loader/load_images.cpp index 489dd1b7..c1c8ecf2 100644 --- a/src/image_loader/load_images.cpp +++ b/src/image_loader/load_images.cpp @@ -1,236 +1,253 @@ -#include "graphics/drawing.h" -#include "graphics/animation_context.h" +#include "image_loader/load_images.h" + #include "graphics/animation.h" +#include "graphics/animation_context.h" +#include "graphics/drawing.h" #include "utils/memory.h" -#include "image_loader/load_images.h" + #include namespace bongocat::animation { - // ============================================================================= - // IMAGE LOADING MODULE - // ============================================================================= - - BONGOCAT_NODISCARD static created_result_t load_sprite_sheet_from_memory(const uint8_t* sprite_data, size_t sprite_data_size, - int frame_columns, int frame_rows, - int padding_x, int padding_y) { - generic_sprite_sheet_t ret; - - auto [sprite_sheet, sprite_sheet_error] = load_image(sprite_data, sprite_data_size, RGBA_CHANNELS); - if (sprite_sheet_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to load sprite sheet. %dx%d", sprite_sheet.width, sprite_sheet.height); - return sprite_sheet_error; - } - - assert(frame_columns != 0 && frame_rows != 0 && sprite_sheet.width % frame_columns == 0 && sprite_sheet.height % frame_rows == 0); - if (frame_columns == 0 || frame_rows == 0 || sprite_sheet.width % frame_columns != 0 || sprite_sheet.height % frame_rows != 0) { - BONGOCAT_LOG_ERROR("Sprite sheet dimensions not divisible by frame grid; frame_columns=%d, frame_rows=%d vs %dx%d sprite size", frame_columns, frame_rows, sprite_sheet.width, sprite_sheet.height); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - - const auto frame_width = sprite_sheet.width / frame_columns; - const auto frame_height = sprite_sheet.height / frame_rows; - const auto total_frames = frame_columns * frame_rows; - - const auto dest_frame_width = frame_width + padding_x*2; - const auto dest_frame_height = frame_height + padding_y*2; - const auto dest_pixels_width = dest_frame_width * frame_columns; - const auto dest_pixels_height = dest_frame_height * frame_rows; - assert(dest_pixels_width >= 0); - assert(dest_pixels_height >= 0); - assert(sprite_sheet.channels >= 0); - const size_t dest_pixels_size = static_cast(dest_pixels_width) * static_cast(dest_pixels_height) * static_cast(sprite_sheet.channels); - auto dest_pixels = make_allocated_array(dest_pixels_size); - if (!dest_pixels) { - BONGOCAT_LOG_ERROR("Failed to allocate memory for dest_pixels (%zu bytes)\n", dest_pixels_size); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - //memset(dest_pixels.data, 0, dest_pixels_size); - - const auto src_frame_width = frame_width; - const auto src_frame_height = frame_height; - const auto src_pixels_width = sprite_sheet.width; - const auto src_pixels_height = sprite_sheet.height; - assert(src_pixels_width >= 0); - assert(src_pixels_height >= 0); - assert(sprite_sheet.channels >= 0); - const size_t src_pixels_size = static_cast(src_pixels_width) * static_cast(src_pixels_height) * static_cast(sprite_sheet.channels); - size_t frame_index = 0; - for (int row = 0; row < frame_rows; ++row) { - for (int col = 0; col < frame_columns; ++col) { - const auto src_x = col * src_frame_width; - const auto src_y = row * src_frame_height; - const auto dst_x = col * dest_frame_width + padding_x; - const auto dst_y = row * dest_frame_height + padding_y; - [[maybe_unused]] const auto src_idx = (src_y * src_pixels_width + src_x) * sprite_sheet.channels; - [[maybe_unused]] const auto dst_idx = (dst_y * dest_pixels_width + dst_x) * sprite_sheet.channels; - assert(src_idx >= 0); - assert(dst_idx >= 0); - - bool set_frames = false; - for (int fy = 0; fy < src_frame_height; fy++) { - for (int fx = 0; fx < src_frame_width; fx++) { - const auto src_px_idx = ((src_y + fy) * src_pixels_width + (src_x + fx)) * sprite_sheet.channels; - const auto dst_px_idx = ((dst_y + fy) * dest_pixels_width + (dst_x + fx)) * sprite_sheet.channels; - - if (src_px_idx >= 0 && dst_px_idx >= 0 && - static_cast(src_px_idx) < src_pixels_size && - static_cast(dst_px_idx) < dest_pixels_size) { - drawing_copy_pixel(dest_pixels.data, sprite_sheet.channels, dst_px_idx, - sprite_sheet.pixels, sprite_sheet.channels, src_px_idx, - blit_image_color_option_flags_t::Normal, - blit_image_color_order_t::RGBA, - blit_image_color_order_t::RGBA); - if (!set_frames && frame_index < MAX_NUM_FRAMES) { - set_frames = true; - } - } - } - } - if (frame_index < MAX_NUM_FRAMES) { - if (set_frames) { - ret.frames[frame_index] = { .valid = true, .col = col, .row = row }; - } else { - ret.frames[frame_index] = { .valid = false, .col = 0, .row = 0, }; - } - } - - frame_index++; - } - } - - ret.image.sprite_sheet_width = sprite_sheet.width; - ret.image.sprite_sheet_height = sprite_sheet.height; - ret.image.channels = sprite_sheet.channels; - // move pixels ownership into out_frames - ret.image.pixels = bongocat::move(dest_pixels); - dest_pixels = nullptr; - ret.frame_width = dest_frame_width; - ret.frame_height = dest_frame_height; - ret.total_frames = total_frames; - - return ret; - } - - created_result_t anim_sprite_sheet_from_embedded_images(get_sprite_callback_t get_sprite, size_t embedded_images_count) { - generic_sprite_sheet_t ret; - - int total_frames = 0; - int max_frame_width = 0; - int max_frame_height = 0; - int max_channels = 0; - auto loaded_images = make_allocated_array(embedded_images_count); - for (size_t i = 0;i < embedded_images_count && i < loaded_images.count; i++) { - const assets::embedded_image_t img = get_sprite(i); - - BONGOCAT_LOG_DEBUG("Loading embedded image: %s", img.name); - auto [loaded_image, image_error] = load_image(img.data, img.size, RGBA_CHANNELS); - if (image_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to load embedded image: %s (%d)", img.name, image_error); - continue; +// ============================================================================= +// IMAGE LOADING MODULE +// ============================================================================= + +BONGOCAT_NODISCARD static created_result_t +load_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite_data_size, int frame_columns, int frame_rows, + int padding_x, int padding_y) { + generic_sprite_sheet_t ret; + + auto [sprite_sheet, sprite_sheet_error] = load_image(sprite_data, sprite_data_size, RGBA_CHANNELS); + if (sprite_sheet_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to load sprite sheet. %dx%d", sprite_sheet.width, sprite_sheet.height); + return sprite_sheet_error; + } + + assert(frame_columns != 0 && frame_rows != 0 && sprite_sheet.width % frame_columns == 0 && + sprite_sheet.height % frame_rows == 0); + if (frame_columns == 0 || frame_rows == 0 || sprite_sheet.width % frame_columns != 0 || + sprite_sheet.height % frame_rows != 0) { + BONGOCAT_LOG_ERROR( + "Sprite sheet dimensions not divisible by frame grid; frame_columns=%d, frame_rows=%d vs %dx%d sprite size", + frame_columns, frame_rows, sprite_sheet.width, sprite_sheet.height); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + const auto frame_width = sprite_sheet.width / frame_columns; + const auto frame_height = sprite_sheet.height / frame_rows; + const auto total_frames = frame_columns * frame_rows; + + const auto dest_frame_width = frame_width + padding_x * 2; + const auto dest_frame_height = frame_height + padding_y * 2; + const auto dest_pixels_width = dest_frame_width * frame_columns; + const auto dest_pixels_height = dest_frame_height * frame_rows; + assert(dest_pixels_width >= 0); + assert(dest_pixels_height >= 0); + assert(sprite_sheet.channels >= 0); + const size_t dest_pixels_size = static_cast(dest_pixels_width) * static_cast(dest_pixels_height) * + static_cast(sprite_sheet.channels); + auto dest_pixels = make_allocated_array(dest_pixels_size); + if (!dest_pixels) { + BONGOCAT_LOG_ERROR("Failed to allocate memory for dest_pixels (%zu bytes)\n", dest_pixels_size); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + // memset(dest_pixels.data, 0, dest_pixels_size); + + const auto src_frame_width = frame_width; + const auto src_frame_height = frame_height; + const auto src_pixels_width = sprite_sheet.width; + const auto src_pixels_height = sprite_sheet.height; + assert(src_pixels_width >= 0); + assert(src_pixels_height >= 0); + assert(sprite_sheet.channels >= 0); + const size_t src_pixels_size = static_cast(src_pixels_width) * static_cast(src_pixels_height) * + static_cast(sprite_sheet.channels); + size_t frame_index = 0; + for (int row = 0; row < frame_rows; ++row) { + for (int col = 0; col < frame_columns; ++col) { + const auto src_x = col * src_frame_width; + const auto src_y = row * src_frame_height; + const auto dst_x = col * dest_frame_width + padding_x; + const auto dst_y = row * dest_frame_height + padding_y; + [[maybe_unused]] const auto src_idx = (src_y * src_pixels_width + src_x) * sprite_sheet.channels; + [[maybe_unused]] const auto dst_idx = (dst_y * dest_pixels_width + dst_x) * sprite_sheet.channels; + assert(src_idx >= 0); + assert(dst_idx >= 0); + + bool set_frames = false; + for (int fy = 0; fy < src_frame_height; fy++) { + for (int fx = 0; fx < src_frame_width; fx++) { + const auto src_px_idx = ((src_y + fy) * src_pixels_width + (src_x + fx)) * sprite_sheet.channels; + const auto dst_px_idx = ((dst_y + fy) * dest_pixels_width + (dst_x + fx)) * sprite_sheet.channels; + + if (src_px_idx >= 0 && dst_px_idx >= 0 && static_cast(src_px_idx) < src_pixels_size && + static_cast(dst_px_idx) < dest_pixels_size) { + drawing_copy_pixel(dest_pixels.data, sprite_sheet.channels, dst_px_idx, sprite_sheet.pixels, + sprite_sheet.channels, src_px_idx, blit_image_color_option_flags_t::Normal, + blit_image_color_order_t::RGBA, blit_image_color_order_t::RGBA); + if (!set_frames && frame_index < MAX_NUM_FRAMES) { + set_frames = true; } - loaded_images[i] = bongocat::move(loaded_image); - assert(loaded_images[i].width >= 0); - assert(loaded_images[i].height >= 0); - assert(loaded_images[i].channels >= 0); - - // update image properties - max_frame_width = loaded_images[i].width > max_frame_width ? loaded_images[i].width : max_frame_width; - max_frame_height = loaded_images[i].height > max_frame_height ? loaded_images[i].height : max_frame_height; - max_channels = loaded_images[i].channels > max_channels ? loaded_images[i].channels : max_channels; - - BONGOCAT_LOG_DEBUG("Loaded %dx%d embedded image", loaded_images[i].width, loaded_images[i].height); - total_frames++; + } } - - ret.frame_width = max_frame_width; - ret.frame_height = max_frame_height; - ret.total_frames = total_frames; - ret.image.sprite_sheet_width = max_frame_width * total_frames; - ret.image.sprite_sheet_height = max_frame_height; - ret.image.channels = max_channels; - // create sprite sheet - assert(ret.image.sprite_sheet_width >= 0); - assert(ret.image.sprite_sheet_height >= 0); - assert(ret.image.channels >= 0); - ret.image.pixels = make_allocated_array(static_cast(ret.image.sprite_sheet_width) * static_cast(ret.image.sprite_sheet_height) * static_cast(ret.image.channels)); - if (!ret.image.pixels) { - ret.frame_width = 0; - ret.frame_height = 0; - ret.total_frames = 0; - ret.image.sprite_sheet_width = 0; - ret.image.sprite_sheet_height = 0; - ret.image.channels = 0; - - for (size_t i = 0; i < loaded_images.count; i++) { - if (loaded_images[i].pixels) ::free(loaded_images[i].pixels); - loaded_images[i].pixels = nullptr; - } - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + if (frame_index < MAX_NUM_FRAMES) { + if (set_frames) { + ret.frames[frame_index] = {.valid = true, .col = col, .row = row}; + } else { + ret.frames[frame_index] = { + .valid = false, + .col = 0, + .row = 0, + }; } - // reset frames - //memset(anim->pixels.data, 0, anim->pixels.count * sizeof(uint8_t)); - for (size_t i = 0;i < MAX_NUM_FRAMES;i++) { - ret.frames[i] = {}; - } - // append images into one sprite sheet - assert(max_frame_width >= 0); - assert(max_channels >= 0); - assert(ret.image.sprite_sheet_width >= 0); - for (size_t frame = 0; frame < loaded_images.count; frame++) { - const auto& src = loaded_images[frame]; - assert(src.channels >= 0); - assert(src.width >= 0); - assert(src.height >= 0); - if (src.pixels && src.height > 0) { - // copy pixel data of sub-region - assert(src.height >= 0); - for (size_t y = 0; y < static_cast(src.height); y++) { - unsigned char* dest_row = ret.image.pixels.data + - ((y) * static_cast(ret.image.sprite_sheet_width) + (frame * static_cast(max_frame_width))) * static_cast(max_channels); - const unsigned char* src_row = src.pixels + (y * static_cast(src.width) * static_cast(src.channels)); - memcpy(dest_row, src_row, static_cast(src.width) * static_cast(max_channels)); - } - - // update sub-region - if (frame < MAX_NUM_FRAMES) { - ret.frames[frame] = { .valid = true, .col = static_cast(frame), .row = 0 }; - } - } else { - if (frame < MAX_NUM_FRAMES) { - ret.frames[frame] = { .valid = false, .col = static_cast(frame), .row = 0 }; - } - } - } - + } - for (size_t i = 0; i < loaded_images.count; i++) { - if (loaded_images[i].pixels) ::free(loaded_images[i].pixels); - loaded_images[i].pixels = nullptr; - } - return ret; + frame_index++; } - - created_result_t load_sprite_sheet_anim(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { - if (sprite_sheet_cols < 0 || sprite_sheet_rows < 0) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - - assert(sprite_sheet_image.size <= INT_MAX); - auto result = load_sprite_sheet_from_memory(sprite_sheet_image.data, sprite_sheet_image.size, - sprite_sheet_cols, sprite_sheet_rows, - config.padding_x, config.padding_y); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Sprite Sheet load failed: %s", sprite_sheet_image.name); - return result.error; - } - if (result.result.total_frames <= 0) { - BONGOCAT_LOG_ERROR("Sprite Sheet is empty: %s", sprite_sheet_image.name); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - - // assume every frame is the same size, pick first frame - BONGOCAT_LOG_DEBUG("Loaded %dx%d sprite sheet with %d frames", result.result.image.sprite_sheet_width, result.result.image.sprite_sheet_height, result.result.total_frames); - - return result; + } + + ret.image.sprite_sheet_width = sprite_sheet.width; + ret.image.sprite_sheet_height = sprite_sheet.height; + ret.image.channels = sprite_sheet.channels; + // move pixels ownership into out_frames + ret.image.pixels = bongocat::move(dest_pixels); + dest_pixels = nullptr; + ret.frame_width = dest_frame_width; + ret.frame_height = dest_frame_height; + ret.total_frames = total_frames; + + return ret; +} + +created_result_t anim_sprite_sheet_from_embedded_images(get_sprite_callback_t get_sprite, + size_t embedded_images_count) { + generic_sprite_sheet_t ret; + + int total_frames = 0; + int max_frame_width = 0; + int max_frame_height = 0; + int max_channels = 0; + auto loaded_images = make_allocated_array(embedded_images_count); + for (size_t i = 0; i < embedded_images_count && i < loaded_images.count; i++) { + const assets::embedded_image_t img = get_sprite(i); + + BONGOCAT_LOG_DEBUG("Loading embedded image: %s", img.name); + auto [loaded_image, image_error] = load_image(img.data, img.size, RGBA_CHANNELS); + if (image_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to load embedded image: %s (%d)", img.name, image_error); + continue; + } + loaded_images[i] = bongocat::move(loaded_image); + assert(loaded_images[i].width >= 0); + assert(loaded_images[i].height >= 0); + assert(loaded_images[i].channels >= 0); + + // update image properties + max_frame_width = loaded_images[i].width > max_frame_width ? loaded_images[i].width : max_frame_width; + max_frame_height = loaded_images[i].height > max_frame_height ? loaded_images[i].height : max_frame_height; + max_channels = loaded_images[i].channels > max_channels ? loaded_images[i].channels : max_channels; + + BONGOCAT_LOG_DEBUG("Loaded %dx%d embedded image", loaded_images[i].width, loaded_images[i].height); + total_frames++; + } + + ret.frame_width = max_frame_width; + ret.frame_height = max_frame_height; + ret.total_frames = total_frames; + ret.image.sprite_sheet_width = max_frame_width * total_frames; + ret.image.sprite_sheet_height = max_frame_height; + ret.image.channels = max_channels; + // create sprite sheet + assert(ret.image.sprite_sheet_width >= 0); + assert(ret.image.sprite_sheet_height >= 0); + assert(ret.image.channels >= 0); + ret.image.pixels = make_allocated_array(static_cast(ret.image.sprite_sheet_width) * + static_cast(ret.image.sprite_sheet_height) * + static_cast(ret.image.channels)); + if (!ret.image.pixels) { + ret.frame_width = 0; + ret.frame_height = 0; + ret.total_frames = 0; + ret.image.sprite_sheet_width = 0; + ret.image.sprite_sheet_height = 0; + ret.image.channels = 0; + + for (size_t i = 0; i < loaded_images.count; i++) { + if (loaded_images[i].pixels) + ::free(loaded_images[i].pixels); + loaded_images[i].pixels = nullptr; + } + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + // reset frames + // memset(anim->pixels.data, 0, anim->pixels.count * sizeof(uint8_t)); + for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { + ret.frames[i] = {}; + } + // append images into one sprite sheet + assert(max_frame_width >= 0); + assert(max_channels >= 0); + assert(ret.image.sprite_sheet_width >= 0); + for (size_t frame = 0; frame < loaded_images.count; frame++) { + const auto& src = loaded_images[frame]; + assert(src.channels >= 0); + assert(src.width >= 0); + assert(src.height >= 0); + if (src.pixels && src.height > 0) { + // copy pixel data of sub-region + assert(src.height >= 0); + for (size_t y = 0; y < static_cast(src.height); y++) { + unsigned char *dest_row = ret.image.pixels.data + ((y) * static_cast(ret.image.sprite_sheet_width) + + (frame * static_cast(max_frame_width))) * + static_cast(max_channels); + const unsigned char *src_row = + src.pixels + (y * static_cast(src.width) * static_cast(src.channels)); + memcpy(dest_row, src_row, static_cast(src.width) * static_cast(max_channels)); + } + + // update sub-region + if (frame < MAX_NUM_FRAMES) { + ret.frames[frame] = {.valid = true, .col = static_cast(frame), .row = 0}; + } + } else { + if (frame < MAX_NUM_FRAMES) { + ret.frames[frame] = {.valid = false, .col = static_cast(frame), .row = 0}; + } } -} \ No newline at end of file + } + + for (size_t i = 0; i < loaded_images.count; i++) { + if (loaded_images[i].pixels) + ::free(loaded_images[i].pixels); + loaded_images[i].pixels = nullptr; + } + return ret; +} + +created_result_t load_sprite_sheet_anim(const config::config_t& config, + const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows) { + if (sprite_sheet_cols < 0 || sprite_sheet_rows < 0) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + assert(sprite_sheet_image.size <= INT_MAX); + auto result = load_sprite_sheet_from_memory(sprite_sheet_image.data, sprite_sheet_image.size, sprite_sheet_cols, + sprite_sheet_rows, config.padding_x, config.padding_y); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Sprite Sheet load failed: %s", sprite_sheet_image.name); + return result.error; + } + if (result.result.total_frames <= 0) { + BONGOCAT_LOG_ERROR("Sprite Sheet is empty: %s", sprite_sheet_image.name); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + + // assume every frame is the same size, pick first frame + BONGOCAT_LOG_DEBUG("Loaded %dx%d sprite sheet with %d frames", result.result.image.sprite_sheet_width, + result.result.image.sprite_sheet_height, result.result.total_frames); + + return result; +} +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/load_images_hybrid.cpp b/src/image_loader/load_images_hybrid.cpp index e2a80edb..735d91f8 100644 --- a/src/image_loader/load_images_hybrid.cpp +++ b/src/image_loader/load_images_hybrid.cpp @@ -1,149 +1,154 @@ #include "image_loader/load_images.h" + #include #include #include // include pngle #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-function" -#pragma GCC diagnostic ignored "-Wold-style-cast" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wcast-align" -#pragma GCC diagnostic ignored "-Wconversion" -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#pragma GCC diagnostic ignored "-Wuseless-cast" -//#pragma GCC diagnostic ignored "-Wimplicit-int-conversion" -#endif +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdouble-promotion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wcast-align" +# pragma GCC diagnostic ignored "-Wconversion" +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic ignored "-Wduplicated-branches" +# pragma GCC diagnostic ignored "-Wuseless-cast" +// #pragma GCC diagnostic ignored "-Wimplicit-int-conversion" +# endif #endif #include "pngle.h" #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop +# pragma GCC diagnostic pop #endif // include stb_image #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-function" -#pragma GCC diagnostic ignored "-Wold-style-cast" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wcast-align" -#pragma GCC diagnostic ignored "-Wconversion" -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#pragma GCC diagnostic ignored "-Wuseless-cast" -//#pragma GCC diagnostic ignored "-Wimplicit-int-conversion" -#endif +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdouble-promotion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wcast-align" +# pragma GCC diagnostic ignored "-Wconversion" +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic ignored "-Wduplicated-branches" +# pragma GCC diagnostic ignored "-Wuseless-cast" +// #pragma GCC diagnostic ignored "-Wimplicit-int-conversion" +# endif #endif #include "stb_image.h" #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop +# pragma GCC diagnostic pop #endif namespace bongocat::animation { - inline static constexpr size_t HybridImageBackendPngleThresholdBytes = 192 * 1024; // 192kb - - struct decode_state_t { - Image *image{nullptr}; - int desired_channels{RGBA_CHANNELS}; - }; - BONGOCAT_NODISCARD static created_result_t load_image_pngle(const unsigned char *data, size_t size, int desired_channels) { - Image ret; - pngle_t *pngle = pngle_new(); - if (!pngle) { - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } - - decode_state_t state { .image = &ret, .desired_channels = desired_channels }; - - // Pixel callback: pngle calls this for each RGBA pixel - pngle_set_draw_callback(pngle, - [](pngle_t *p_pngle, uint32_t x, uint32_t y, [[maybe_unused]] uint32_t w, [[maybe_unused]] uint32_t h, const uint8_t rgba[4]) { - auto *st = static_cast(pngle_get_user_data(p_pngle)); - Image *img = st->image; - - constexpr uint32_t channels = 4; - if (!img->pixels) { - // Allocate buffer on first pixel - img->width = static_cast(pngle_get_width(p_pngle)); - img->height = static_cast(pngle_get_height(p_pngle)); - img->channels = channels; // pngle always gives RGBA - size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; - img->pixels = static_cast(::malloc(buf_size)); - if (!img->pixels) return; - } - - unsigned char *dst = &img->pixels[(y * pngle_get_width(p_pngle) + x) * channels]; - dst[0] = rgba[0]; - dst[1] = rgba[1]; - dst[2] = rgba[2]; - dst[3] = rgba[3]; - }); - - pngle_set_user_data(pngle, &state); - - // Feed the PNG data - const int fed = pngle_feed(pngle, data, size); - if (fed < 0) { - pngle_destroy(pngle); - if (ret.pixels) ::free(ret.pixels); - ret.pixels = nullptr; - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } - - pngle_destroy(pngle); - - if (!ret.pixels) { - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } - - // Handle desired_channels (pngle gives RGBA only) - if (desired_channels > 0 && desired_channels != 4) { - // Optionally strip alpha or replicate grayscale here - // For now, just return RGBA - ret.channels = 4; - } - - assert(ret.width > 0); - assert(ret.height > 0); - return ret; - } - - BONGOCAT_NODISCARD static created_result_t load_image_stb_image(const unsigned char *data, size_t size, int desired_channels) { - Image ret; - assert(size <= INT_MAX); - int channels_in_file; - ret.pixels = stbi_load_from_memory(data, static_cast(size), &ret.width, &ret.height, &channels_in_file, desired_channels); - if (ret.pixels == nullptr) { - ret.pixels = nullptr; - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } - assert(ret.width > 0); - assert(ret.height > 0); - assert(channels_in_file > 0); - ret.channels = desired_channels; - return ret; - } - - created_result_t load_image(const unsigned char *data, size_t size, int desired_channels) { - if (size >= HybridImageBackendPngleThresholdBytes) { - return load_image_pngle(data, size, desired_channels); - } - - return load_image_stb_image(data, size, desired_channels); - } - - void cleanup_image(Image& image) { - if (image.pixels) ::free(image.pixels); - image.pixels = nullptr; +inline static constexpr size_t HybridImageBackendPngleThresholdBytes = 192 * 1024; // 192kb + +struct decode_state_t { + Image *image{nullptr}; + int desired_channels{RGBA_CHANNELS}; +}; +BONGOCAT_NODISCARD static created_result_t load_image_pngle(const unsigned char *data, size_t size, + int desired_channels) { + Image ret; + pngle_t *pngle = pngle_new(); + if (!pngle) { + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } + + decode_state_t state{.image = &ret, .desired_channels = desired_channels}; + + // Pixel callback: pngle calls this for each RGBA pixel + pngle_set_draw_callback(pngle, [](pngle_t *p_pngle, uint32_t x, uint32_t y, [[maybe_unused]] uint32_t w, + [[maybe_unused]] uint32_t h, const uint8_t rgba[4]) { + auto *st = static_cast(pngle_get_user_data(p_pngle)); + Image *img = st->image; + + constexpr uint32_t channels = 4; + if (!img->pixels) { + // Allocate buffer on first pixel + img->width = static_cast(pngle_get_width(p_pngle)); + img->height = static_cast(pngle_get_height(p_pngle)); + img->channels = channels; // pngle always gives RGBA + size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; + img->pixels = static_cast(::malloc(buf_size)); + if (!img->pixels) + return; } - void init_image_loader() { - - } -} \ No newline at end of file + unsigned char *dst = &img->pixels[(y * pngle_get_width(p_pngle) + x) * channels]; + dst[0] = rgba[0]; + dst[1] = rgba[1]; + dst[2] = rgba[2]; + dst[3] = rgba[3]; + }); + + pngle_set_user_data(pngle, &state); + + // Feed the PNG data + const int fed = pngle_feed(pngle, data, size); + if (fed < 0) { + pngle_destroy(pngle); + if (ret.pixels) + ::free(ret.pixels); + ret.pixels = nullptr; + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } + + pngle_destroy(pngle); + + if (!ret.pixels) { + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } + + // Handle desired_channels (pngle gives RGBA only) + if (desired_channels > 0 && desired_channels != 4) { + // Optionally strip alpha or replicate grayscale here + // For now, just return RGBA + ret.channels = 4; + } + + assert(ret.width > 0); + assert(ret.height > 0); + return ret; +} + +BONGOCAT_NODISCARD static created_result_t load_image_stb_image(const unsigned char *data, size_t size, + int desired_channels) { + Image ret; + assert(size <= INT_MAX); + int channels_in_file; + ret.pixels = + stbi_load_from_memory(data, static_cast(size), &ret.width, &ret.height, &channels_in_file, desired_channels); + if (ret.pixels == nullptr) { + ret.pixels = nullptr; + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } + assert(ret.width > 0); + assert(ret.height > 0); + assert(channels_in_file > 0); + ret.channels = desired_channels; + return ret; +} + +created_result_t load_image(const unsigned char *data, size_t size, int desired_channels) { + if (size >= HybridImageBackendPngleThresholdBytes) { + return load_image_pngle(data, size, desired_channels); + } + + return load_image_stb_image(data, size, desired_channels); +} + +void cleanup_image(Image& image) { + if (image.pixels) + ::free(image.pixels); + image.pixels = nullptr; +} + +void init_image_loader() {} +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/load_images_pngle.cpp b/src/image_loader/load_images_pngle.cpp index f3de2d3f..7713801f 100644 --- a/src/image_loader/load_images_pngle.cpp +++ b/src/image_loader/load_images_pngle.cpp @@ -1,102 +1,104 @@ #include "image_loader/load_images.h" + #include #include #include // include pngle #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdouble-promotion" -#pragma GCC diagnostic ignored "-Wsign-compare" -#pragma GCC diagnostic ignored "-Wunused-function" -#pragma GCC diagnostic ignored "-Wold-style-cast" -#pragma GCC diagnostic ignored "-Wsign-conversion" -#pragma GCC diagnostic ignored "-Wcast-align" -#pragma GCC diagnostic ignored "-Wconversion" -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic ignored "-Wduplicated-branches" -#pragma GCC diagnostic ignored "-Wuseless-cast" -//#pragma GCC diagnostic ignored "-Wimplicit-int-conversion" -#endif +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdouble-promotion" +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wold-style-cast" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wcast-align" +# pragma GCC diagnostic ignored "-Wconversion" +# if defined(__GNUC__) && !defined(__clang__) +# pragma GCC diagnostic ignored "-Wduplicated-branches" +# pragma GCC diagnostic ignored "-Wuseless-cast" +// #pragma GCC diagnostic ignored "-Wimplicit-int-conversion" +# endif #endif #include "pngle.h" #if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop +# pragma GCC diagnostic pop #endif namespace bongocat::animation { - struct decode_state_t { - Image *image{nullptr}; - int desired_channels{RGBA_CHANNELS}; - }; - created_result_t load_image(const unsigned char *data, size_t size, int desired_channels) { - Image ret; - pngle_t *pngle = pngle_new(); - if (!pngle) { - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } +struct decode_state_t { + Image *image{nullptr}; + int desired_channels{RGBA_CHANNELS}; +}; +created_result_t load_image(const unsigned char *data, size_t size, int desired_channels) { + Image ret; + pngle_t *pngle = pngle_new(); + if (!pngle) { + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } - decode_state_t state { .image = &ret, .desired_channels = desired_channels }; + decode_state_t state{.image = &ret, .desired_channels = desired_channels}; - // Pixel callback: pngle calls this for each RGBA pixel - pngle_set_draw_callback(pngle, - [](pngle_t *p_pngle, uint32_t x, uint32_t y, [[maybe_unused]] uint32_t w, [[maybe_unused]] uint32_t h, const uint8_t rgba[4]) { - auto *st = static_cast(pngle_get_user_data(p_pngle)); - Image *img = st->image; + // Pixel callback: pngle calls this for each RGBA pixel + pngle_set_draw_callback(pngle, [](pngle_t *p_pngle, uint32_t x, uint32_t y, [[maybe_unused]] uint32_t w, + [[maybe_unused]] uint32_t h, const uint8_t rgba[4]) { + auto *st = static_cast(pngle_get_user_data(p_pngle)); + Image *img = st->image; - constexpr uint32_t channels = 4; - if (!img->pixels) { - // Allocate buffer on first pixel - img->width = static_cast(pngle_get_width(p_pngle)); - img->height = static_cast(pngle_get_height(p_pngle)); - img->channels = channels; // pngle always gives RGBA - size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; - img->pixels = static_cast(::malloc(buf_size)); - if (!img->pixels) return; - } + constexpr uint32_t channels = 4; + if (!img->pixels) { + // Allocate buffer on first pixel + img->width = static_cast(pngle_get_width(p_pngle)); + img->height = static_cast(pngle_get_height(p_pngle)); + img->channels = channels; // pngle always gives RGBA + size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; + img->pixels = static_cast(::malloc(buf_size)); + if (!img->pixels) + return; + } - unsigned char *dst = &img->pixels[(y * pngle_get_width(p_pngle) + x) * channels]; - dst[0] = rgba[0]; - dst[1] = rgba[1]; - dst[2] = rgba[2]; - dst[3] = rgba[3]; - }); + unsigned char *dst = &img->pixels[(y * pngle_get_width(p_pngle) + x) * channels]; + dst[0] = rgba[0]; + dst[1] = rgba[1]; + dst[2] = rgba[2]; + dst[3] = rgba[3]; + }); - pngle_set_user_data(pngle, &state); + pngle_set_user_data(pngle, &state); - // Feed the PNG data - const int fed = pngle_feed(pngle, data, size); - if (fed < 0) { - pngle_destroy(pngle); - if (ret.pixels) ::free(ret.pixels); - ret.pixels = nullptr; - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } + // Feed the PNG data + const int fed = pngle_feed(pngle, data, size); + if (fed < 0) { + pngle_destroy(pngle); + if (ret.pixels) + ::free(ret.pixels); + ret.pixels = nullptr; + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } - pngle_destroy(pngle); + pngle_destroy(pngle); - if (!ret.pixels) { - return bongocat_error_t::BONGOCAT_ERROR_IMAGE; - } + if (!ret.pixels) { + return bongocat_error_t::BONGOCAT_ERROR_IMAGE; + } - // Handle desired_channels (pngle gives RGBA only) - if (desired_channels > 0 && desired_channels != 4) { - // Optionally strip alpha or replicate grayscale here - // For now, just return RGBA - ret.channels = 4; - } + // Handle desired_channels (pngle gives RGBA only) + if (desired_channels > 0 && desired_channels != 4) { + // Optionally strip alpha or replicate grayscale here + // For now, just return RGBA + ret.channels = 4; + } - assert(ret.width > 0); - assert(ret.height > 0); - return ret; - } + assert(ret.width > 0); + assert(ret.height > 0); + return ret; +} - void cleanup_image(Image& image) { - if (image.pixels) ::free(image.pixels); - image.pixels = nullptr; - } - - void init_image_loader() { +void cleanup_image(Image& image) { + if (image.pixels) + ::free(image.pixels); + image.pixels = nullptr; +} - } -} \ No newline at end of file +void init_image_loader() {} +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/min_dm/CMakeLists.txt b/src/image_loader/min_dm/CMakeLists.txt index 4536ac83..2e26abee 100644 --- a/src/image_loader/min_dm/CMakeLists.txt +++ b/src/image_loader/min_dm/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_min_dm_loader STATIC) target_sources(assets_min_dm_loader PRIVATE load_images_min_dm.cpp min_dm_load_sprite_sheet.cpp) target_compile_options(assets_min_dm_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_min_dm_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/min_dm ${INCLUDE_DIR}/image_loader/min_dm - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_min_dm_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_min_dm - PRIVATE assets_min_dm_interface assets_min_dm_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_min_dm_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/min_dm ${INCLUDE_DIR}/image_loader/min_dm + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_min_dm_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_min_dm + PRIVATE assets_min_dm_interface assets_min_dm_feature bongocat_options) diff --git a/src/image_loader/min_dm/load_images_min_dm.cpp b/src/image_loader/min_dm/load_images_min_dm.cpp index 3d8b2a0e..90ce94da 100644 --- a/src/image_loader/min_dm/load_images_min_dm.cpp +++ b/src/image_loader/min_dm/load_images_min_dm.cpp @@ -1,28 +1,31 @@ #include "load_images_min_dm.h" -#include "graphics/animation_context.h" -#include "image_loader/base_dm/load_dm.h" + #include "embedded_assets/embedded_image.h" #include "embedded_assets/min_dm/min_dm.hpp" +#include "graphics/animation_context.h" +#include "image_loader/base_dm/load_dm.h" namespace bongocat::animation { - bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); +bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, + const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, + int sprite_sheet_rows) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - assert(anim_index >= 0 && static_cast(anim_index) < MIN_DM_ANIM_COUNT); - BONGOCAT_LOG_VERBOSE("Load min_dm Animation (%d/%d): %s ...", anim_index, MIN_DM_ANIM_COUNT, sprite_sheet_image.name); - auto result = load_dm_anim(ctx, anim_index, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load dm20 Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - assert(result.result.total_frames > 0); ///< this SHOULD always work if it's an valid EMBEDDED image + assert(anim_index >= 0 && static_cast(anim_index) < MIN_DM_ANIM_COUNT); + BONGOCAT_LOG_VERBOSE("Load min_dm Animation (%d/%d): %s ...", anim_index, MIN_DM_ANIM_COUNT, sprite_sheet_image.name); + auto result = load_dm_anim(ctx, anim_index, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load dm20 Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + assert(result.result.total_frames > 0); ///< this SHOULD always work if it's an valid EMBEDDED image - assert(anim_index >= 0); - ctx.shm->min_dm_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->min_dm_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(anim_index >= 0); + ctx.shm->min_dm_anims[static_cast(anim_index)] = bongocat::move(result.result); + assert(ctx.shm->min_dm_anims[static_cast(anim_index)].type == animation_t::Type::Dm); - return bongocat_error_t::BONGOCAT_SUCCESS; - } + return bongocat_error_t::BONGOCAT_SUCCESS; } +} // namespace bongocat::animation diff --git a/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp b/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp index 229df1e9..341922cf 100644 --- a/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp +++ b/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp @@ -1,33 +1,62 @@ #include "core/bongocat.h" +#include "embedded_assets/embedded_image.h" +#include "embedded_assets/min_dm/min_dm.hpp" +#include "embedded_assets/min_dm/min_dm_sprite.h" #include "graphics/animation_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" -#include "embedded_assets/min_dm/min_dm.hpp" -#include "embedded_assets/embedded_image.h" -#include "embedded_assets/min_dm/min_dm_sprite.h" #include "image_loader/min_dm/load_images_min_dm.h" namespace bongocat::animation { - created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index) { - using namespace animation; - using namespace assets; - switch (index) { - case DM_BOTAMON_ANIM_INDEX: return load_dm_anim(ctx, DM_BOTAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_BOTAMON_ANIM_INDEX), DM_BOTAMON_SPRITE_SHEET_COLS, DM_BOTAMON_SPRITE_SHEET_ROWS); - case DM_KOROMON_ANIM_INDEX: return load_dm_anim(ctx, DM_KOROMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_KOROMON_ANIM_INDEX), DM_KOROMON_SPRITE_SHEET_COLS, DM_KOROMON_SPRITE_SHEET_ROWS); - case DM_AGUMON_ANIM_INDEX: return load_dm_anim(ctx, DM_AGUMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_AGUMON_ANIM_INDEX), DM_AGUMON_SPRITE_SHEET_COLS, DM_AGUMON_SPRITE_SHEET_ROWS); - case DM_BETAMON_ANIM_INDEX: return load_dm_anim(ctx, DM_BETAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_BETAMON_ANIM_INDEX), DM_BETAMON_SPRITE_SHEET_COLS, DM_BETAMON_SPRITE_SHEET_ROWS); - case DM_GREYMON_ANIM_INDEX: return load_dm_anim(ctx, DM_GREYMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_GREYMON_ANIM_INDEX), DM_GREYMON_SPRITE_SHEET_COLS, DM_GREYMON_SPRITE_SHEET_ROWS); - case DM_TYRANOMON_ANIM_INDEX: return load_dm_anim(ctx, DM_TYRANOMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_TYRANOMON_ANIM_INDEX), DM_TYRANOMON_SPRITE_SHEET_COLS, DM_TYRANOMON_SPRITE_SHEET_ROWS); - case DM_DEVIMON_ANIM_INDEX: return load_dm_anim(ctx, DM_DEVIMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_DEVIMON_ANIM_INDEX), DM_DEVIMON_SPRITE_SHEET_COLS, DM_DEVIMON_SPRITE_SHEET_ROWS); - case DM_MERAMON_ANIM_INDEX: return load_dm_anim(ctx, DM_MERAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_MERAMON_ANIM_INDEX), DM_MERAMON_SPRITE_SHEET_COLS, DM_MERAMON_SPRITE_SHEET_ROWS); - case DM_AIRDRAMON_ANIM_INDEX: return load_dm_anim(ctx, DM_AIRDRAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_AIRDRAMON_ANIM_INDEX), DM_AIRDRAMON_SPRITE_SHEET_COLS, DM_AIRDRAMON_SPRITE_SHEET_ROWS); - case DM_SEADRAMON_ANIM_INDEX: return load_dm_anim(ctx, DM_SEADRAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_SEADRAMON_ANIM_INDEX), DM_SEADRAMON_SPRITE_SHEET_COLS, DM_SEADRAMON_SPRITE_SHEET_ROWS); - case DM_NUMEMON_ANIM_INDEX: return load_dm_anim(ctx, DM_NUMEMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_NUMEMON_ANIM_INDEX), DM_NUMEMON_SPRITE_SHEET_COLS, DM_NUMEMON_SPRITE_SHEET_ROWS); - case DM_METAL_GREYMON_ANIM_INDEX: return load_dm_anim(ctx, DM_METAL_GREYMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_METAL_GREYMON_ANIM_INDEX), DM_METAL_GREYMON_SPRITE_SHEET_COLS, DM_METAL_GREYMON_SPRITE_SHEET_ROWS); - case DM_MAMEMON_ANIM_INDEX: return load_dm_anim(ctx, DM_MAMEMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_MAMEMON_ANIM_INDEX), DM_MAMEMON_SPRITE_SHEET_COLS, DM_MAMEMON_SPRITE_SHEET_ROWS); - case DM_MONZAEMON_ANIM_INDEX: return load_dm_anim(ctx, DM_MONZAEMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_MONZAEMON_ANIM_INDEX), DM_MONZAEMON_SPRITE_SHEET_COLS, DM_MONZAEMON_SPRITE_SHEET_ROWS); - default: return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } -} \ No newline at end of file +created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index) { + using namespace animation; + using namespace assets; + switch (index) { + case DM_BOTAMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_BOTAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_BOTAMON_ANIM_INDEX), + DM_BOTAMON_SPRITE_SHEET_COLS, DM_BOTAMON_SPRITE_SHEET_ROWS); + case DM_KOROMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_KOROMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_KOROMON_ANIM_INDEX), + DM_KOROMON_SPRITE_SHEET_COLS, DM_KOROMON_SPRITE_SHEET_ROWS); + case DM_AGUMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_AGUMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_AGUMON_ANIM_INDEX), + DM_AGUMON_SPRITE_SHEET_COLS, DM_AGUMON_SPRITE_SHEET_ROWS); + case DM_BETAMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_BETAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_BETAMON_ANIM_INDEX), + DM_BETAMON_SPRITE_SHEET_COLS, DM_BETAMON_SPRITE_SHEET_ROWS); + case DM_GREYMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_GREYMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_GREYMON_ANIM_INDEX), + DM_GREYMON_SPRITE_SHEET_COLS, DM_GREYMON_SPRITE_SHEET_ROWS); + case DM_TYRANOMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_TYRANOMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_TYRANOMON_ANIM_INDEX), + DM_TYRANOMON_SPRITE_SHEET_COLS, DM_TYRANOMON_SPRITE_SHEET_ROWS); + case DM_DEVIMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_DEVIMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_DEVIMON_ANIM_INDEX), + DM_DEVIMON_SPRITE_SHEET_COLS, DM_DEVIMON_SPRITE_SHEET_ROWS); + case DM_MERAMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_MERAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_MERAMON_ANIM_INDEX), + DM_MERAMON_SPRITE_SHEET_COLS, DM_MERAMON_SPRITE_SHEET_ROWS); + case DM_AIRDRAMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_AIRDRAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_AIRDRAMON_ANIM_INDEX), + DM_AIRDRAMON_SPRITE_SHEET_COLS, DM_AIRDRAMON_SPRITE_SHEET_ROWS); + case DM_SEADRAMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_SEADRAMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_SEADRAMON_ANIM_INDEX), + DM_SEADRAMON_SPRITE_SHEET_COLS, DM_SEADRAMON_SPRITE_SHEET_ROWS); + case DM_NUMEMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_NUMEMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_NUMEMON_ANIM_INDEX), + DM_NUMEMON_SPRITE_SHEET_COLS, DM_NUMEMON_SPRITE_SHEET_ROWS); + case DM_METAL_GREYMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_METAL_GREYMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_METAL_GREYMON_ANIM_INDEX), + DM_METAL_GREYMON_SPRITE_SHEET_COLS, DM_METAL_GREYMON_SPRITE_SHEET_ROWS); + case DM_MAMEMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_MAMEMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_MAMEMON_ANIM_INDEX), + DM_MAMEMON_SPRITE_SHEET_COLS, DM_MAMEMON_SPRITE_SHEET_ROWS); + case DM_MONZAEMON_ANIM_INDEX: + return load_dm_anim(ctx, DM_MONZAEMON_ANIM_INDEX, get_min_dm_sprite_sheet(DM_MONZAEMON_ANIM_INDEX), + DM_MONZAEMON_SPRITE_SHEET_COLS, DM_MONZAEMON_SPRITE_SHEET_ROWS); + default: + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; +} +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/misc/CMakeLists.txt b/src/image_loader/misc/CMakeLists.txt index 027a7b68..6b6c0cd0 100644 --- a/src/image_loader/misc/CMakeLists.txt +++ b/src/image_loader/misc/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_misc_loader STATIC) target_sources(assets_misc_loader PRIVATE load_images_misc.cpp misc_load_sprite_sheet.cpp) target_compile_options(assets_misc_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_misc_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/misc ${INCLUDE_DIR}/image_loader/misc - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_misc_loader - PUBLIC assets_image_loader assets_custom_loader assets_misc - PRIVATE assets_misc_interface assets_misc_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_misc_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/misc ${INCLUDE_DIR}/image_loader/misc + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_misc_loader + PUBLIC assets_image_loader assets_custom_loader assets_misc + PRIVATE assets_misc_interface assets_misc_feature bongocat_options) diff --git a/src/image_loader/misc/load_images_misc.cpp b/src/image_loader/misc/load_images_misc.cpp index 4bf78ff2..07fb6c7a 100644 --- a/src/image_loader/misc/load_images_misc.cpp +++ b/src/image_loader/misc/load_images_misc.cpp @@ -1,27 +1,30 @@ #include "load_images_misc.h" -#include "graphics/animation_context.h" -#include "image_loader/custom/load_custom.h" + #include "embedded_assets/embedded_image.h" #include "embedded_assets/misc/misc.hpp" +#include "graphics/animation_context.h" +#include "image_loader/custom/load_custom.h" namespace bongocat::animation { - bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); +bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, + const assets::embedded_image_t& sprite_sheet_image, + const assets::custom_animation_settings_t& sprite_sheet_settings) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - assert(anim_index >= 0 && static_cast(anim_index) < MISC_ANIM_COUNT); - BONGOCAT_LOG_VERBOSE("Load misc Animation (%d/%d): %s ...", anim_index, MISC_ANIM_COUNT, sprite_sheet_image.name); - auto result = load_custom_anim(ctx, sprite_sheet_image, sprite_sheet_settings); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Load misc Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } + assert(anim_index >= 0 && static_cast(anim_index) < MISC_ANIM_COUNT); + BONGOCAT_LOG_VERBOSE("Load misc Animation (%d/%d): %s ...", anim_index, MISC_ANIM_COUNT, sprite_sheet_image.name); + auto result = load_custom_anim(ctx, sprite_sheet_image, sprite_sheet_settings); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Load misc Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } - assert(anim_index >= 0); - ctx.shm->misc_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->misc_anims[static_cast(anim_index)].type == animation_t::Type::Custom); + assert(anim_index >= 0); + ctx.shm->misc_anims[static_cast(anim_index)] = bongocat::move(result.result); + assert(ctx.shm->misc_anims[static_cast(anim_index)].type == animation_t::Type::Custom); - return bongocat_error_t::BONGOCAT_SUCCESS; - } + return bongocat_error_t::BONGOCAT_SUCCESS; } +} // namespace bongocat::animation diff --git a/src/image_loader/misc/misc_load_sprite_sheet.cpp b/src/image_loader/misc/misc_load_sprite_sheet.cpp index 4fe51725..f63b806a 100644 --- a/src/image_loader/misc/misc_load_sprite_sheet.cpp +++ b/src/image_loader/misc/misc_load_sprite_sheet.cpp @@ -1,20 +1,21 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" -#include "graphics/sprite_sheet.h" -#include "image_loader/custom/load_custom.h" -#include "embedded_assets/misc/misc.hpp" #include "embedded_assets/embedded_image.h" +#include "embedded_assets/misc/misc.hpp" #include "embedded_assets/misc/misc_sprite.h" +#include "graphics/animation_context.h" +#include "graphics/sprite_sheet.h" #include "image_loader/custom/load_custom.h" namespace bongocat::animation { - created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index) { - using namespace animation; - using namespace assets; - switch (index) { - case MISC_NEKO_ANIM_INDEX: return load_custom_anim(ctx, get_misc_sprite_sheet(MISC_NEKO_ANIM_INDEX), MISC_NEKO_SPRITE_SHEET_SETTINGS); - default: return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } -} \ No newline at end of file +created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index) { + using namespace animation; + using namespace assets; + switch (index) { + case MISC_NEKO_ANIM_INDEX: + return load_custom_anim(ctx, get_misc_sprite_sheet(MISC_NEKO_ANIM_INDEX), MISC_NEKO_SPRITE_SHEET_SETTINGS); + default: + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; +} +} // namespace bongocat::animation \ No newline at end of file diff --git a/src/image_loader/ms_agent/CMakeLists.txt b/src/image_loader/ms_agent/CMakeLists.txt index 851f3660..d88a0d7e 100644 --- a/src/image_loader/ms_agent/CMakeLists.txt +++ b/src/image_loader/ms_agent/CMakeLists.txt @@ -1,21 +1,23 @@ add_library(assets_ms_agent_loader STATIC) target_sources(assets_ms_agent_loader PRIVATE load_images_ms_agent.cpp) target_compile_options(assets_ms_agent_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_ms_agent_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/ms_agent ${INCLUDE_DIR}/image_loader/ms_agent - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_ms_agent_loader - PUBLIC assets_image_loader assets_ms_agent - PRIVATE assets_ms_agent_interface assets_ms_agent_feature bongocat_options) - - +target_include_directories( + assets_ms_agent_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/ms_agent ${INCLUDE_DIR}/image_loader/ms_agent + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_ms_agent_loader + PUBLIC assets_image_loader assets_ms_agent + PRIVATE assets_ms_agent_interface assets_ms_agent_feature bongocat_options) add_library(assets_more_ms_agent_loader STATIC) target_sources(assets_more_ms_agent_loader PRIVATE load_images_ms_agent.cpp) target_compile_options(assets_more_ms_agent_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_more_ms_agent_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/ms_agent ${INCLUDE_DIR}/image_loader/ms_agent - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_more_ms_agent_loader - PUBLIC assets_image_loader assets_more_ms_agent - PRIVATE assets_more_ms_agent_interface assets_more_ms_agent_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_more_ms_agent_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/ms_agent ${INCLUDE_DIR}/image_loader/ms_agent + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_more_ms_agent_loader + PUBLIC assets_image_loader assets_more_ms_agent + PRIVATE assets_more_ms_agent_interface assets_more_ms_agent_feature bongocat_options) diff --git a/src/image_loader/ms_agent/load_images_ms_agent.cpp b/src/image_loader/ms_agent/load_images_ms_agent.cpp index 78142b5d..04168254 100644 --- a/src/image_loader/ms_agent/load_images_ms_agent.cpp +++ b/src/image_loader/ms_agent/load_images_ms_agent.cpp @@ -1,229 +1,297 @@ -#include "graphics/drawing.h" -#include "graphics/animation_context.h" +#include "embedded_assets/ms_agent/ms_agent.hpp" +#include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "graphics/animation.h" -#include "utils/memory.h" +#include "graphics/animation_context.h" +#include "graphics/drawing.h" #include "image_loader/load_images.h" -#include "embedded_assets/ms_agent/ms_agent_sprite.h" -#include "embedded_assets/ms_agent/ms_agent.hpp" -#include +#include "utils/memory.h" +#include namespace bongocat::animation { - BONGOCAT_NODISCARD static created_result_t load_ms_agent_sprite_sheet_from_memory(const uint8_t* sprite_data, size_t sprite_data_size, - int frame_columns, int frame_rows, - int padding_x, int padding_y) { - auto [sprite_sheet, sprite_sheet_error] = load_image(sprite_data, sprite_data_size, RGBA_CHANNELS); // Force RGBA - if (sprite_sheet_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Failed to load sprite sheet."); - return sprite_sheet_error; - } +BONGOCAT_NODISCARD static created_result_t +load_ms_agent_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite_data_size, int frame_columns, + int frame_rows, int padding_x, int padding_y) { + auto [sprite_sheet, sprite_sheet_error] = load_image(sprite_data, sprite_data_size, RGBA_CHANNELS); // Force RGBA + if (sprite_sheet_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Failed to load sprite sheet."); + return sprite_sheet_error; + } - assert(frame_columns != 0 && frame_rows != 0 && sprite_sheet.width % frame_columns == 0 && sprite_sheet.height % frame_rows == 0); - if (frame_columns == 0 || frame_rows == 0 || sprite_sheet.width % frame_columns != 0 || sprite_sheet.height % frame_rows != 0) { - BONGOCAT_LOG_ERROR("Sprite sheet dimensions not divisible by frame grid; frame_columns=%d, frame_rows=%d vs %dx%d sprite size", frame_columns, frame_rows, sprite_sheet.width, sprite_sheet.height); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } + assert(frame_columns != 0 && frame_rows != 0 && sprite_sheet.width % frame_columns == 0 && + sprite_sheet.height % frame_rows == 0); + if (frame_columns == 0 || frame_rows == 0 || sprite_sheet.width % frame_columns != 0 || + sprite_sheet.height % frame_rows != 0) { + BONGOCAT_LOG_ERROR( + "Sprite sheet dimensions not divisible by frame grid; frame_columns=%d, frame_rows=%d vs %dx%d sprite size", + frame_columns, frame_rows, sprite_sheet.width, sprite_sheet.height); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } - const auto frame_width = sprite_sheet.width / frame_columns; - const auto frame_height = sprite_sheet.height / frame_rows; + const auto frame_width = sprite_sheet.width / frame_columns; + const auto frame_height = sprite_sheet.height / frame_rows; - /* - assert(MAX_NUM_FRAMES <= INT_MAX); - if (total_frames > (int)MAX_NUM_FRAMES) { - BONGOCAT_LOG_ERROR("Sprite Sheet does not fit in out_frames: %d, total_frames: %d", MAX_NUM_FRAMES, total_frames); - return BONGOCAT_ERROR_INVALID_PARAM; - } - */ - - const auto dest_frame_width = frame_width + padding_x*2; - const auto dest_frame_height = frame_height + padding_y*2; - const auto dest_pixels_width = dest_frame_width * frame_columns; - const auto dest_pixels_height = dest_frame_height * frame_rows; - assert(dest_pixels_width >= 0); - assert(dest_pixels_height >= 0); - assert(sprite_sheet.channels >= 0); - const size_t dest_pixels_size = static_cast(dest_pixels_width) * static_cast(dest_pixels_height) * static_cast(sprite_sheet.channels); - auto dest_pixels = make_allocated_array(dest_pixels_size); - if (!dest_pixels) { - BONGOCAT_LOG_ERROR("Failed to allocate memory for dest_pixels (%zu bytes)\n", dest_pixels_size); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - //memset(dest_pixels.data, 0, dest_pixels_size); - - const auto src_frame_width = frame_width; - const auto src_frame_height = frame_height; - const auto src_pixels_width = sprite_sheet.width; - const auto src_pixels_height = sprite_sheet.height; - assert(src_pixels_width >= 0); - assert(src_pixels_height >= 0); - assert(sprite_sheet.channels >= 0); - const size_t src_pixels_size = static_cast(src_pixels_width) * static_cast(src_pixels_height) * static_cast(sprite_sheet.channels); - size_t frame_index = 0; - for (int row = 0; row < frame_rows; ++row) { - for (int col = 0; col < frame_columns; ++col) { - const auto src_x = col * src_frame_width; - const auto src_y = row * src_frame_height; - const auto dst_x = col * dest_frame_width + padding_x; - const auto dst_y = row * dest_frame_height + padding_y; - [[maybe_unused]] const auto src_idx = (src_y * src_pixels_width + src_x) * sprite_sheet.channels; - [[maybe_unused]] const auto dst_idx = (dst_y * dest_pixels_width + dst_x) * sprite_sheet.channels; - assert(src_idx >= 0); - assert(dst_idx >= 0); - - bool set_frames = false; - for (int fy = 0; fy < src_frame_height; fy++) { - for (int fx = 0; fx < src_frame_width; fx++) { - const auto src_px_idx = ((src_y + fy) * src_pixels_width + (src_x + fx)) * sprite_sheet.channels; - const auto dst_px_idx = ((dst_y + fy) * dest_pixels_width + (dst_x + fx)) * sprite_sheet.channels; - - if (src_px_idx >= 0 && dst_px_idx >= 0 && - static_cast(src_px_idx) < src_pixels_size && - static_cast(dst_px_idx) < dest_pixels_size) { - drawing_copy_pixel(dest_pixels.data, sprite_sheet.channels, dst_px_idx, - sprite_sheet.pixels, sprite_sheet.channels, src_px_idx, - blit_image_color_option_flags_t::Normal, - blit_image_color_order_t::RGBA, - blit_image_color_order_t::RGBA); - if (!set_frames && frame_index < MAX_NUM_FRAMES) { - set_frames = true; - } - } - } - } - frame_index++; + /* + assert(MAX_NUM_FRAMES <= INT_MAX); + if (total_frames > (int)MAX_NUM_FRAMES) { + BONGOCAT_LOG_ERROR("Sprite Sheet does not fit in out_frames: %d, total_frames: %d", MAX_NUM_FRAMES, total_frames); + return BONGOCAT_ERROR_INVALID_PARAM; + } + */ + + const auto dest_frame_width = frame_width + padding_x * 2; + const auto dest_frame_height = frame_height + padding_y * 2; + const auto dest_pixels_width = dest_frame_width * frame_columns; + const auto dest_pixels_height = dest_frame_height * frame_rows; + assert(dest_pixels_width >= 0); + assert(dest_pixels_height >= 0); + assert(sprite_sheet.channels >= 0); + const size_t dest_pixels_size = static_cast(dest_pixels_width) * static_cast(dest_pixels_height) * + static_cast(sprite_sheet.channels); + auto dest_pixels = make_allocated_array(dest_pixels_size); + if (!dest_pixels) { + BONGOCAT_LOG_ERROR("Failed to allocate memory for dest_pixels (%zu bytes)\n", dest_pixels_size); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + // memset(dest_pixels.data, 0, dest_pixels_size); + + const auto src_frame_width = frame_width; + const auto src_frame_height = frame_height; + const auto src_pixels_width = sprite_sheet.width; + const auto src_pixels_height = sprite_sheet.height; + assert(src_pixels_width >= 0); + assert(src_pixels_height >= 0); + assert(sprite_sheet.channels >= 0); + const size_t src_pixels_size = static_cast(src_pixels_width) * static_cast(src_pixels_height) * + static_cast(sprite_sheet.channels); + size_t frame_index = 0; + for (int row = 0; row < frame_rows; ++row) { + for (int col = 0; col < frame_columns; ++col) { + const auto src_x = col * src_frame_width; + const auto src_y = row * src_frame_height; + const auto dst_x = col * dest_frame_width + padding_x; + const auto dst_y = row * dest_frame_height + padding_y; + [[maybe_unused]] const auto src_idx = (src_y * src_pixels_width + src_x) * sprite_sheet.channels; + [[maybe_unused]] const auto dst_idx = (dst_y * dest_pixels_width + dst_x) * sprite_sheet.channels; + assert(src_idx >= 0); + assert(dst_idx >= 0); + + bool set_frames = false; + for (int fy = 0; fy < src_frame_height; fy++) { + for (int fx = 0; fx < src_frame_width; fx++) { + const auto src_px_idx = ((src_y + fy) * src_pixels_width + (src_x + fx)) * sprite_sheet.channels; + const auto dst_px_idx = ((dst_y + fy) * dest_pixels_width + (dst_x + fx)) * sprite_sheet.channels; + + if (src_px_idx >= 0 && dst_px_idx >= 0 && static_cast(src_px_idx) < src_pixels_size && + static_cast(dst_px_idx) < dest_pixels_size) { + drawing_copy_pixel(dest_pixels.data, sprite_sheet.channels, dst_px_idx, sprite_sheet.pixels, + sprite_sheet.channels, src_px_idx, blit_image_color_option_flags_t::Normal, + blit_image_color_order_t::RGBA, blit_image_color_order_t::RGBA); + if (!set_frames && frame_index < MAX_NUM_FRAMES) { + set_frames = true; } + } } - - ms_agent_sprite_sheet_t ret; - ret.image.sprite_sheet_width = sprite_sheet.width; - ret.image.sprite_sheet_height = sprite_sheet.height; - ret.image.channels = sprite_sheet.channels; - // move pixels ownership into out_frames - ret.image.pixels = move(dest_pixels); - dest_pixels = nullptr; - ret.frame_width = dest_frame_width; - ret.frame_height = dest_frame_height; - - return ret; + } + frame_index++; } + } - created_result_t load_ms_agent_sprite_sheet(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { - if (sprite_sheet_cols < 0 || sprite_sheet_rows < 0) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } + ms_agent_sprite_sheet_t ret; + ret.image.sprite_sheet_width = sprite_sheet.width; + ret.image.sprite_sheet_height = sprite_sheet.height; + ret.image.channels = sprite_sheet.channels; + // move pixels ownership into out_frames + ret.image.pixels = move(dest_pixels); + dest_pixels = nullptr; + ret.frame_width = dest_frame_width; + ret.frame_height = dest_frame_height; - assert(sprite_sheet_image.size <= INT_MAX); - auto result = load_ms_agent_sprite_sheet_from_memory(sprite_sheet_image.data, sprite_sheet_image.size, - sprite_sheet_cols, sprite_sheet_rows, - config.padding_x, config.padding_y); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_ERROR("Sprite Sheet load failed: %s", sprite_sheet_image.name); - return result.error; - } + return ret; +} - // assume every frame is the same size, pick first frame - BONGOCAT_LOG_DEBUG("Loaded %dx%d sprite sheet", result.result.image.sprite_sheet_width, result.result.image.sprite_sheet_height); +created_result_t load_ms_agent_sprite_sheet(const config::config_t& config, + const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows) { + if (sprite_sheet_cols < 0 || sprite_sheet_rows < 0) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } - return result; - } + assert(sprite_sheet_image.size <= INT_MAX); + auto result = + load_ms_agent_sprite_sheet_from_memory(sprite_sheet_image.data, sprite_sheet_image.size, sprite_sheet_cols, + sprite_sheet_rows, config.padding_x, config.padding_y); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_ERROR("Sprite Sheet load failed: %s", sprite_sheet_image.name); + return result.error; + } - created_result_t load_ms_agent_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + // assume every frame is the same size, pick first frame + BONGOCAT_LOG_DEBUG("Loaded %dx%d sprite sheet", result.result.image.sprite_sheet_width, + result.result.image.sprite_sheet_height); - BONGOCAT_LOG_VERBOSE("Load MS agent Animation(index=%d) ...", anim_index); - auto result = load_ms_agent_sprite_sheet(*ctx._local_copy_config, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - return result.error; - } + return result; +} +created_result_t +load_ms_agent_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, + const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, + const assets::ms_agent_animation_indices_t& animation_data) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - // setup animation frames data - if (result.result.frame_height > 0 && sprite_sheet_rows > 0) { - [[maybe_unused]] const auto rows = result.result.image.sprite_sheet_height / result.result.frame_height; - assert(rows == sprite_sheet_rows); + BONGOCAT_LOG_VERBOSE("Load MS agent Animation(index=%d) ...", anim_index); + auto result = + load_ms_agent_sprite_sheet(*ctx._local_copy_config, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + return result.error; + } - assert(sprite_sheet_rows > 0); - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_IDLE) { - result.result.idle = { .valid = true, .start_col = animation_data.start_index_frame_idle, .end_col = animation_data.end_index_frame_idle, .row = MS_AGENT_SPRITE_SHEET_ROW_IDLE }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_BORING) { - result.result.boring = { .valid = true, .start_col = animation_data.start_index_frame_boring, .end_col = animation_data.end_index_frame_boring, .row = MS_AGENT_SPRITE_SHEET_ROW_BORING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_START_WRITING) { - result.result.start_writing = { .valid = true, .start_col = animation_data.start_index_frame_start_writing, .end_col = animation_data.end_index_frame_start_writing, .row = MS_AGENT_SPRITE_SHEET_ROW_START_WRITING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_WRITING) { - result.result.writing = { .valid = true, .start_col = animation_data.start_index_frame_writing, .end_col = animation_data.end_index_frame_writing, .row = MS_AGENT_SPRITE_SHEET_ROW_WRITING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_END_WRITING) { - result.result.end_writing = { .valid = true, .start_col = animation_data.start_index_frame_end_writing, .end_col = animation_data.end_index_frame_end_writing, .row = MS_AGENT_SPRITE_SHEET_ROW_END_WRITING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_SLEEP) { - result.result.sleep = { .valid = true, .start_col = animation_data.start_index_frame_sleep, .end_col = animation_data.end_index_frame_sleep, .row = MS_AGENT_SPRITE_SHEET_ROW_SLEEP }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP) { - result.result.wake_up = { .valid = true, .start_col = animation_data.start_index_frame_wake_up, .end_col = animation_data.end_index_frame_wake_up, .row = MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_START_WORKING) { - result.result.start_working = { .valid = true, .start_col = animation_data.start_index_frame_start_working, .end_col = animation_data.end_index_frame_start_working, .row = MS_AGENT_SPRITE_SHEET_ROW_START_WORKING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_WORKING) { - result.result.working = { .valid = true, .start_col = animation_data.start_index_frame_working, .end_col = animation_data.end_index_frame_working, .row = MS_AGENT_SPRITE_SHEET_ROW_WORKING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_END_WORKING) { - result.result.end_working = { .valid = true, .start_col = animation_data.start_index_frame_end_working, .end_col = animation_data.end_index_frame_end_working, .row = MS_AGENT_SPRITE_SHEET_ROW_END_WORKING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_START_MOVING) { - result.result.start_moving = { .valid = true, .start_col = animation_data.start_index_frame_start_moving, .end_col = animation_data.end_index_frame_start_moving, .row = MS_AGENT_SPRITE_SHEET_ROW_START_MOVING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_MOVING) { - result.result.moving = { .valid = true, .start_col = animation_data.start_index_frame_moving, .end_col = animation_data.end_index_frame_moving, .row = MS_AGENT_SPRITE_SHEET_ROW_MOVING }; - } - if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_END_MOVING) { - result.result.end_moving = { .valid = true, .start_col = animation_data.start_index_frame_end_moving, .end_col = animation_data.end_index_frame_end_moving, .row = MS_AGENT_SPRITE_SHEET_ROW_END_MOVING }; - } - //if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_HAPPY) { - // result.result.happy = { .valid = true, .start_col = animation_data.start_index_frame_happy), .end_col = animation_data.end_index_happy_moving), .row = MS_AGENT_SPRITE_SHEET_ROW_HAPPY }; - //} - } + // setup animation frames data + if (result.result.frame_height > 0 && sprite_sheet_rows > 0) { + [[maybe_unused]] const auto rows = result.result.image.sprite_sheet_height / result.result.frame_height; + assert(rows == sprite_sheet_rows); - return bongocat::move(result.result); + assert(sprite_sheet_rows > 0); + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_IDLE) { + result.result.idle = {.valid = true, + .start_col = animation_data.start_index_frame_idle, + .end_col = animation_data.end_index_frame_idle, + .row = MS_AGENT_SPRITE_SHEET_ROW_IDLE}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_BORING) { + result.result.boring = {.valid = true, + .start_col = animation_data.start_index_frame_boring, + .end_col = animation_data.end_index_frame_boring, + .row = MS_AGENT_SPRITE_SHEET_ROW_BORING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_START_WRITING) { + result.result.start_writing = {.valid = true, + .start_col = animation_data.start_index_frame_start_writing, + .end_col = animation_data.end_index_frame_start_writing, + .row = MS_AGENT_SPRITE_SHEET_ROW_START_WRITING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_WRITING) { + result.result.writing = {.valid = true, + .start_col = animation_data.start_index_frame_writing, + .end_col = animation_data.end_index_frame_writing, + .row = MS_AGENT_SPRITE_SHEET_ROW_WRITING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_END_WRITING) { + result.result.end_writing = {.valid = true, + .start_col = animation_data.start_index_frame_end_writing, + .end_col = animation_data.end_index_frame_end_writing, + .row = MS_AGENT_SPRITE_SHEET_ROW_END_WRITING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_SLEEP) { + result.result.sleep = {.valid = true, + .start_col = animation_data.start_index_frame_sleep, + .end_col = animation_data.end_index_frame_sleep, + .row = MS_AGENT_SPRITE_SHEET_ROW_SLEEP}; } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP) { + result.result.wake_up = {.valid = true, + .start_col = animation_data.start_index_frame_wake_up, + .end_col = animation_data.end_index_frame_wake_up, + .row = MS_AGENT_SPRITE_SHEET_ROW_WAKE_UP}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_START_WORKING) { + result.result.start_working = {.valid = true, + .start_col = animation_data.start_index_frame_start_working, + .end_col = animation_data.end_index_frame_start_working, + .row = MS_AGENT_SPRITE_SHEET_ROW_START_WORKING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_WORKING) { + result.result.working = {.valid = true, + .start_col = animation_data.start_index_frame_working, + .end_col = animation_data.end_index_frame_working, + .row = MS_AGENT_SPRITE_SHEET_ROW_WORKING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_END_WORKING) { + result.result.end_working = {.valid = true, + .start_col = animation_data.start_index_frame_end_working, + .end_col = animation_data.end_index_frame_end_working, + .row = MS_AGENT_SPRITE_SHEET_ROW_END_WORKING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_START_MOVING) { + result.result.start_moving = {.valid = true, + .start_col = animation_data.start_index_frame_start_moving, + .end_col = animation_data.end_index_frame_start_moving, + .row = MS_AGENT_SPRITE_SHEET_ROW_START_MOVING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_MOVING) { + result.result.moving = {.valid = true, + .start_col = animation_data.start_index_frame_moving, + .end_col = animation_data.end_index_frame_moving, + .row = MS_AGENT_SPRITE_SHEET_ROW_MOVING}; + } + if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_END_MOVING) { + result.result.end_moving = {.valid = true, + .start_col = animation_data.start_index_frame_end_moving, + .end_col = animation_data.end_index_frame_end_moving, + .row = MS_AGENT_SPRITE_SHEET_ROW_END_MOVING}; + } + // if (static_cast(sprite_sheet_rows - 1) >= MS_AGENT_SPRITE_SHEET_ROW_HAPPY) { + // result.result.happy = { .valid = true, .start_col = animation_data.start_index_frame_happy), .end_col = + // animation_data.end_index_happy_moving), .row = MS_AGENT_SPRITE_SHEET_ROW_HAPPY }; + // } + } - created_result_t init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data) { - using namespace assets; - BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - assert(anim_index >= 0 && static_cast(anim_index) < MS_AGENTS_ANIM_COUNT); - BONGOCAT_LOG_VERBOSE("Load MS agent Animation (%d/%d): %s ...", anim_index, MS_AGENTS_ANIM_COUNT, sprite_sheet_image.name); - auto result = load_ms_agent_anim(ctx, anim_index, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows, animation_data); - if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Load MS agent Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); - return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; - } - assert(result.error == bongocat_error_t::BONGOCAT_SUCCESS); ///< this SHOULD always work if it's an valid EMBEDDED image + return bongocat::move(result.result); +} - assert(anim_index >= 0); - ctx.shm->ms_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->ms_anims[static_cast(anim_index)].type == animation_t::Type::MsAgent); +created_result_t +init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, + int sprite_sheet_cols, int sprite_sheet_rows, + const assets::ms_agent_animation_indices_t& animation_data) { + using namespace assets; + BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - return bongocat_error_t::BONGOCAT_SUCCESS; - } + assert(anim_index >= 0 && static_cast(anim_index) < MS_AGENTS_ANIM_COUNT); + BONGOCAT_LOG_VERBOSE("Load MS agent Animation (%d/%d): %s ...", anim_index, MS_AGENTS_ANIM_COUNT, + sprite_sheet_image.name); + auto result = + load_ms_agent_anim(ctx, anim_index, sprite_sheet_image, sprite_sheet_cols, sprite_sheet_rows, animation_data); + if (result.error != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Load MS agent Animation failed: %s, index: %d", sprite_sheet_image.name, anim_index); + return bongocat_error_t::BONGOCAT_ERROR_ANIMATION; + } + assert(result.error == + bongocat_error_t::BONGOCAT_SUCCESS); ///< this SHOULD always work if it's an valid EMBEDDED image - created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, int index) { - using namespace assets; - using namespace animation; - switch (index) { - case CLIPPY_ANIM_INDEX: return load_ms_agent_anim(ctx, CLIPPY_ANIM_INDEX, get_ms_agent_sprite_sheet(CLIPPY_ANIM_INDEX), CLIPPY_SPRITE_SHEET_COLS, CLIPPY_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(CLIPPY_ANIM_INDEX)); + assert(anim_index >= 0); + ctx.shm->ms_anims[static_cast(anim_index)] = bongocat::move(result.result); + assert(ctx.shm->ms_anims[static_cast(anim_index)].type == animation_t::Type::MsAgent); + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, int index) { + using namespace assets; + using namespace animation; + switch (index) { + case CLIPPY_ANIM_INDEX: + return load_ms_agent_anim(ctx, CLIPPY_ANIM_INDEX, get_ms_agent_sprite_sheet(CLIPPY_ANIM_INDEX), + CLIPPY_SPRITE_SHEET_COLS, CLIPPY_SPRITE_SHEET_ROWS, + get_ms_agent_animation_indices(CLIPPY_ANIM_INDEX)); #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS - case LINKS_ANIM_INDEX: return load_ms_agent_anim(ctx, LINKS_ANIM_INDEX, get_ms_agent_sprite_sheet(LINKS_ANIM_INDEX), LINKS_SPRITE_SHEET_COLS, LINKS_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(LINKS_ANIM_INDEX)); - case ROVER_ANIM_INDEX: return load_ms_agent_anim(ctx, ROVER_ANIM_INDEX, get_ms_agent_sprite_sheet(ROVER_ANIM_INDEX), ROVER_SPRITE_SHEET_COLS, ROVER_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(ROVER_ANIM_INDEX)); - case MERLIN_ANIM_INDEX: return load_ms_agent_anim(ctx, MERLIN_ANIM_INDEX, get_ms_agent_sprite_sheet(MERLIN_ANIM_INDEX), MERLIN_SPRITE_SHEET_COLS, MERLIN_SPRITE_SHEET_ROWS, get_ms_agent_animation_indices(MERLIN_ANIM_INDEX)); + case LINKS_ANIM_INDEX: + return load_ms_agent_anim(ctx, LINKS_ANIM_INDEX, get_ms_agent_sprite_sheet(LINKS_ANIM_INDEX), + LINKS_SPRITE_SHEET_COLS, LINKS_SPRITE_SHEET_ROWS, + get_ms_agent_animation_indices(LINKS_ANIM_INDEX)); + case ROVER_ANIM_INDEX: + return load_ms_agent_anim(ctx, ROVER_ANIM_INDEX, get_ms_agent_sprite_sheet(ROVER_ANIM_INDEX), + ROVER_SPRITE_SHEET_COLS, ROVER_SPRITE_SHEET_ROWS, + get_ms_agent_animation_indices(ROVER_ANIM_INDEX)); + case MERLIN_ANIM_INDEX: + return load_ms_agent_anim(ctx, MERLIN_ANIM_INDEX, get_ms_agent_sprite_sheet(MERLIN_ANIM_INDEX), + MERLIN_SPRITE_SHEET_COLS, MERLIN_SPRITE_SHEET_ROWS, + get_ms_agent_animation_indices(MERLIN_ANIM_INDEX)); #endif - default: return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } + default: + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; } +} // namespace bongocat::animation diff --git a/src/image_loader/pen/CMakeLists.txt b/src/image_loader/pen/CMakeLists.txt index 073b6ee9..190352a2 100644 --- a/src/image_loader/pen/CMakeLists.txt +++ b/src/image_loader/pen/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_pen_loader STATIC) target_sources(assets_pen_loader PRIVATE pen_load_sprite_sheet.cpp load_images_pen.cpp) target_compile_options(assets_pen_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_pen_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/pen ${INCLUDE_DIR}/image_loader/pen - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_pen_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_pen - PRIVATE assets_pen_interface assets_pen_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_pen_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/pen ${INCLUDE_DIR}/image_loader/pen + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_pen_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_pen + PRIVATE assets_pen_interface assets_pen_feature bongocat_options) diff --git a/src/image_loader/pen20/CMakeLists.txt b/src/image_loader/pen20/CMakeLists.txt index c7dcb0df..e56d9d24 100644 --- a/src/image_loader/pen20/CMakeLists.txt +++ b/src/image_loader/pen20/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_pen20_loader STATIC) target_sources(assets_pen20_loader PRIVATE pen20_load_sprite_sheet.cpp load_images_pen20.cpp) target_compile_options(assets_pen20_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_pen20_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/pen20 ${INCLUDE_DIR}/image_loader/pen20 - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_pen20_loader - PUBLIC assets_image_loader assets_base_dm_loader assets_pen20 - PRIVATE assets_pen20_interface assets_pen20_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_pen20_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/pen20 ${INCLUDE_DIR}/image_loader/pen20 + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_pen20_loader + PUBLIC assets_image_loader assets_base_dm_loader assets_pen20 + PRIVATE assets_pen20_interface assets_pen20_feature bongocat_options) diff --git a/src/image_loader/pkmn/CMakeLists.txt b/src/image_loader/pkmn/CMakeLists.txt index e0414b8a..798a242b 100644 --- a/src/image_loader/pkmn/CMakeLists.txt +++ b/src/image_loader/pkmn/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_pkmn_loader STATIC) target_sources(assets_pkmn_loader PRIVATE pkmn_load_sprite_sheet.cpp load_images_pkmn.cpp) target_compile_options(assets_pkmn_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_pkmn_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/pkmn ${INCLUDE_DIR}/image_loader/pkmn - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_pkmn_loader - PUBLIC assets_image_loader assets_pkmn - PRIVATE assets_pkmn_interface assets_pkmn_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_pkmn_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/pkmn ${INCLUDE_DIR}/image_loader/pkmn + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_pkmn_loader + PUBLIC assets_image_loader assets_pkmn + PRIVATE assets_pkmn_interface assets_pkmn_feature bongocat_options) diff --git a/src/image_loader/pmd/CMakeLists.txt b/src/image_loader/pmd/CMakeLists.txt index 921ff213..09849fba 100644 --- a/src/image_loader/pmd/CMakeLists.txt +++ b/src/image_loader/pmd/CMakeLists.txt @@ -1,9 +1,11 @@ add_library(assets_pmd_loader STATIC) target_sources(assets_pmd_loader PRIVATE load_images_pmd.cpp pmd_load_sprite_sheet.cpp) target_compile_options(assets_pmd_loader PRIVATE -ffunction-sections -fdata-sections) -target_include_directories(assets_pmd_loader - PRIVATE ${INCLUDE_DIR}/embedded_assets/misc ${INCLUDE_DIR}/image_loader/pmd - PUBLIC ${INCLUDE_DIR}) -target_link_libraries(assets_pmd_loader - PUBLIC assets_image_loader assets_custom_loader assets_pmd - PRIVATE assets_pmd_interface assets_pmd_feature bongocat_options) \ No newline at end of file +target_include_directories( + assets_pmd_loader + PRIVATE ${INCLUDE_DIR}/embedded_assets/misc ${INCLUDE_DIR}/image_loader/pmd + PUBLIC ${INCLUDE_DIR}) +target_link_libraries( + assets_pmd_loader + PUBLIC assets_image_loader assets_custom_loader assets_pmd + PRIVATE assets_pmd_interface assets_pmd_feature bongocat_options) diff --git a/src/platform/input.cpp b/src/platform/input.cpp index 0cc0dcf8..5863c72d 100644 --- a/src/platform/input.cpp +++ b/src/platform/input.cpp @@ -1,42 +1,44 @@ #include "platform/input.h" + #include "graphics/animation.h" -#include "utils/memory.h" #include "platform/wayland.h" -#include -#include -#include -#include -#include -#include -#include +#include "utils/memory.h" + #include +#include #include +#include +#include #include +#include #include -#include -#include +#include +#include +#include +#include +#include namespace bongocat::platform::input { - static inline constexpr size_t INPUT_EVENT_BUF = 128; - static inline constexpr size_t MAX_DEVICE_FDS = 256; - inline static constexpr int MAX_ATTEMPTS = 2048; +static inline constexpr size_t INPUT_EVENT_BUF = 128; +static inline constexpr size_t MAX_DEVICE_FDS = 256; +inline static constexpr int MAX_ATTEMPTS = 2048; - static inline constexpr auto INPUT_POOL_TIMEOUT_MS = 10; +static inline constexpr auto INPUT_POOL_TIMEOUT_MS = 10; - static inline constexpr time_sec_t START_ADAPTIVE_CHECK_INTERVAL_SEC = 5; - static inline constexpr time_sec_t MID_ADAPTIVE_CHECK_INTERVAL_SEC = 15; - static inline constexpr time_sec_t MAX_ADAPTIVE_CHECK_INTERVAL_SEC = 30; +static inline constexpr time_sec_t START_ADAPTIVE_CHECK_INTERVAL_SEC = 5; +static inline constexpr time_sec_t MID_ADAPTIVE_CHECK_INTERVAL_SEC = 15; +static inline constexpr time_sec_t MAX_ADAPTIVE_CHECK_INTERVAL_SEC = 30; - static inline constexpr time_ms_t RESET_KPM_TIMEOUT_MS = 5 * 1000; +static inline constexpr time_ms_t RESET_KPM_TIMEOUT_MS = 5 * 1000; - inline static constexpr time_ms_t COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS = 5000; - inline static constexpr time_ms_t COND_RELOAD_CONFIGS_TIMEOUT_MS = 5000; +inline static constexpr time_ms_t COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS = 5000; +inline static constexpr time_ms_t COND_RELOAD_CONFIGS_TIMEOUT_MS = 5000; - inline static constexpr size_t TEST_STDIN_BUF_LEN = 256; +inline static constexpr size_t TEST_STDIN_BUF_LEN = 256; - // Uses Linux input keycodes from - // Left-hand keys on QWERTY keyboard - // clang-format off +// Uses Linux input keycodes from +// Left-hand keys on QWERTY keyboard +// clang-format off inline static constexpr int INPUT_LEFT_KEYS[] = { // Number row left half (1-6) 2, 3, 4, 5, 6, 7, // KEY_1 to KEY_6 @@ -56,1206 +58,1229 @@ namespace bongocat::platform::input { 41, // KEY_GRAVE (backtick) 125, // KEY_LEFTMETA (super) }; - // clang-format on +// clang-format on - static input_hand_mapping_t get_hand_mapping_form_keycode([[maybe_unused]] const input_context_t& input, int keycode) { - // read-only config - assert(input._local_copy_config != nullptr); - //const config::config_t& current_config = *input._local_copy_config; +static input_hand_mapping_t get_hand_mapping_form_keycode([[maybe_unused]] const input_context_t& input, int keycode) { + // read-only config + assert(input._local_copy_config != nullptr); + // const config::config_t& current_config = *input._local_copy_config; - for (size_t i = 0; i < LEN_ARRAY(INPUT_LEFT_KEYS); i++) { - if (keycode == INPUT_LEFT_KEYS[i]) { - return input_hand_mapping_t::Left; // Left hand - } - } - return input_hand_mapping_t::Right; // Right hand (default for all other keys) + for (size_t i = 0; i < LEN_ARRAY(INPUT_LEFT_KEYS); i++) { + if (keycode == INPUT_LEFT_KEYS[i]) { + return input_hand_mapping_t::Left; // Left hand } + } + return input_hand_mapping_t::Right; // Right hand (default for all other keys) +} - static void cleanup_input_devices_paths(input_context_t& input, size_t device_paths_count) { - for (size_t i = 0; i < device_paths_count; i++) { - if (input._device_paths[i]) ::free(input._device_paths[i]); - input._device_paths[i] = nullptr; - } - release_allocated_array(input._device_paths); - } +static void cleanup_input_devices_paths(input_context_t& input, size_t device_paths_count) { + for (size_t i = 0; i < device_paths_count; i++) { + if (input._device_paths[i]) + ::free(input._device_paths[i]); + input._device_paths[i] = nullptr; + } + release_allocated_array(input._device_paths); +} - static void cleanup_input_thread_context(input_context_t& input) { - cleanup_input_devices_paths(input, input._device_paths.count); - release_allocated_array(input._device_paths); - release_allocated_array(input._unique_paths_indices); - input._unique_paths_indices_capacity = 0; - release_allocated_array(input._unique_devices); - if (input._udev_mon) udev_monitor_unref(input._udev_mon); - if (input._udev) udev_unref(input._udev); - input._udev_mon = nullptr; - input._udev = nullptr; - input._udev_fd = -1; - } +static void cleanup_input_thread_context(input_context_t& input) { + cleanup_input_devices_paths(input, input._device_paths.count); + release_allocated_array(input._device_paths); + release_allocated_array(input._unique_paths_indices); + input._unique_paths_indices_capacity = 0; + release_allocated_array(input._unique_devices); + if (input._udev_mon) + udev_monitor_unref(input._udev_mon); + if (input._udev) + udev_unref(input._udev); + input._udev_mon = nullptr; + input._udev = nullptr; + input._udev_fd = -1; +} - static void cleanup_input_thread(void* arg) { - assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); - assert(trigger_ctx._input); - input_context_t& input = *trigger_ctx._input; +static void cleanup_input_thread(void *arg) { + assert(arg); + animation::animation_session_t& trigger_ctx = *static_cast(arg); + assert(trigger_ctx._input); + input_context_t& input = *trigger_ctx._input; - atomic_store(&input._capture_input_running, false); + atomic_store(&input._capture_input_running, false); - input.config_updated.notify_all(); + input.config_updated.notify_all(); - cleanup_input_thread_context(*trigger_ctx._input); + cleanup_input_thread_context(*trigger_ctx._input); + + BONGOCAT_LOG_INFO("Input thread cleanup completed (via pthread_cancel)"); +} - BONGOCAT_LOG_INFO("Input thread cleanup completed (via pthread_cancel)"); +inline static bool is_device_valid(const char *path) { + struct stat fd_st{}; + return stat(path, &fd_st) == 0 && (S_ISCHR(fd_st.st_mode) && !S_ISLNK(fd_st.st_mode)); +} +inline static bool is_open_device_valid(int fd) { + struct stat fd_st{}; + return fd >= 0 && fstat(fd, &fd_st) == 0 && (S_ISCHR(fd_st.st_mode) && !S_ISLNK(fd_st.st_mode)); +} +inline static void trigger_key_press(animation::animation_session_t& trigger_ctx, int keycode = 0) { + assert(trigger_ctx._input); + // animation_context_t& anim = trigger_ctx.anim; + input_context_t& input = *trigger_ctx._input; + + // read-only config + assert(input._local_copy_config != nullptr); + const config::config_t& current_config = *input._local_copy_config; + + int timeout = INPUT_POOL_TIMEOUT_MS; + if (current_config.input_fps > 0) { + timeout = 1000 / current_config.input_fps; + } else if (current_config.fps > 0) { + timeout = 1000 / current_config.fps / 3; + } + + const timestamp_ms_t now = get_current_time_ms(); + const time_ms_t duration_ms = now - input._latest_kpm_update_ms; + time_ms_t min_key_press_check_time_ms = timeout * 2; + if (current_config.input_fps > 0) { + min_key_press_check_time_ms = 2000 / current_config.input_fps; + } else if (current_config.fps > 0) { + min_key_press_check_time_ms = 2000 / current_config.fps; + } + if (duration_ms >= min_key_press_check_time_ms) { + const int input_kpm_counter = atomic_load(&input._input_kpm_counter); + if (input_kpm_counter > 0) { + if (duration_ms > 0) { + const double duration_min = static_cast(duration_ms) / 60000.0; + assert(duration_min > 0.0); + input.shm->kpm = static_cast(static_cast(input_kpm_counter) / duration_min); + } else { + input.shm->kpm = 0; + } + atomic_store(&input._input_kpm_counter, 0); + input._latest_kpm_update_ms = now; } + } + input.shm->any_key_pressed = keycode != 0 ? 1 : 0; + input.shm->last_key_pressed_timestamp = now; + atomic_fetch_add(&input.shm->input_counter, 1); + atomic_fetch_add(&input._input_kpm_counter, 1); + if (current_config.enable_hand_mapping && keycode != 0) { + input.shm->hand_mapping = get_hand_mapping_form_keycode(input, keycode); + } else { + input.shm->hand_mapping = input_hand_mapping_t::None; + } + animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::KeyPress); +} + +// for testing +static FileDescriptor open_tty_nonblocking() { + int fd = dup(STDIN_FILENO); + if (fd < 0) { + BONGOCAT_LOG_ERROR("dup stdin"); + return FileDescriptor(fd); + } + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) { + BONGOCAT_LOG_ERROR("fcntl getfl"); + close(fd); + fd = -1; + return FileDescriptor(fd); + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { + BONGOCAT_LOG_ERROR("fcntl setfl"); + close(fd); + fd = -1; + return FileDescriptor(fd); + } + return FileDescriptor(fd); +} - inline static bool is_device_valid(const char* path) { - struct stat fd_st{}; - return stat(path, &fd_st) == 0 && (S_ISCHR(fd_st.st_mode) && !S_ISLNK(fd_st.st_mode)); +struct sync_devices_options_t { + bool reload_devices_needed{false}; +}; +struct sync_devices_options_result_t { + size_t valid_devices{0}; + size_t broken_symlink{0}; + size_t failed{0}; + size_t ignore{0}; +}; +BONGOCAT_NODISCARD static created_result_t +sync_devices(input_context_t& input, sync_devices_options_t options = {}) { + assert(input.shm != nullptr); + + size_t valid_devices = 0; + // Ensure buffer size + if (input._unique_paths_indices_capacity < input._device_paths.count || options.reload_devices_needed) { + auto new_unique_devices = make_allocated_array(input._device_paths.count); + if (!new_unique_devices) { + BONGOCAT_LOG_ERROR("Failed to allocate memory for unique devices"); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - inline static bool is_open_device_valid(int fd) { - struct stat fd_st{}; - return fd >= 0 && fstat(fd, &fd_st) == 0 && (S_ISCHR(fd_st.st_mode) && !S_ISLNK(fd_st.st_mode)); + auto new_unique_paths_indices = make_allocated_array(input._device_paths.count); + if (!new_unique_paths_indices) { + release_allocated_array(input._unique_devices); + BONGOCAT_LOG_ERROR("Failed to allocate memory for unique path indices"); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - inline static void trigger_key_press(animation::animation_session_t& trigger_ctx, int keycode = 0) { - assert(trigger_ctx._input); - //animation_context_t& anim = trigger_ctx.anim; - input_context_t& input = *trigger_ctx._input; - - // read-only config - assert(input._local_copy_config != nullptr); - const config::config_t& current_config = *input._local_copy_config; - - int timeout = INPUT_POOL_TIMEOUT_MS; - if (current_config.input_fps > 0) { - timeout = 1000 / current_config.input_fps; - } else if (current_config.fps > 0) { - timeout = 1000 / current_config.fps / 3; - } - - const timestamp_ms_t now = get_current_time_ms(); - const time_ms_t duration_ms = now - input._latest_kpm_update_ms; - time_ms_t min_key_press_check_time_ms = timeout*2; - if (current_config.input_fps > 0) { - min_key_press_check_time_ms = 2000 / current_config.input_fps; - } else if (current_config.fps > 0) { - min_key_press_check_time_ms = 2000 / current_config.fps; - } - if (duration_ms >= min_key_press_check_time_ms) { - const int input_kpm_counter = atomic_load(&input._input_kpm_counter); - if (input_kpm_counter > 0) { - if (duration_ms > 0) { - const double duration_min = static_cast(duration_ms) / 60000.0; - assert(duration_min > 0.0); - input.shm->kpm = static_cast(static_cast(input_kpm_counter) / duration_min); - } else { - input.shm->kpm = 0; - } - atomic_store(&input._input_kpm_counter, 0); - input._latest_kpm_update_ms = now; - } - } - input.shm->any_key_pressed = keycode != 0 ? 1 : 0; - input.shm->last_key_pressed_timestamp = now; - atomic_fetch_add(&input.shm->input_counter, 1); - atomic_fetch_add(&input._input_kpm_counter, 1); - if (current_config.enable_hand_mapping && keycode != 0) { - input.shm->hand_mapping = get_hand_mapping_form_keycode(input, keycode); - } else { - input.shm->hand_mapping = input_hand_mapping_t::None; - } - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::KeyPress); + input._unique_devices = bongocat::move(new_unique_devices); + input._unique_paths_indices = bongocat::move(new_unique_paths_indices); + input._unique_paths_indices_capacity = input._device_paths.count; + } + + size_t broken_symlink = 0; + size_t failed = 0; + size_t ignore = 0; + + // recover real size for syncing + input._unique_devices.count = input._unique_paths_indices_capacity; + input._unique_paths_indices.count = input._unique_paths_indices_capacity; + size_t num_unique_devices = 0; + for (size_t i = 0; i < input._device_paths.count; i++) { + const char *device_path = input._device_paths[i]; + + // Resolve to canonical path + char resolved[PATH_MAX]; + const char *candidate = nullptr; + input_unique_file_type_t new_type = input_unique_file_type_t::File; + + struct stat lst{}; + if (lstat(device_path, &lst) == 0 && S_ISLNK(lst.st_mode)) { + new_type = input_unique_file_type_t::Symlink; + if (realpath(device_path, resolved)) { + candidate = resolved; + } else { + BONGOCAT_LOG_WARNING("Broken symlink: %s", device_path); + broken_symlink++; + continue; + } + } else { + candidate = device_path; } - // for testing - static FileDescriptor open_tty_nonblocking() { - int fd = dup(STDIN_FILENO); - if (fd < 0) { - BONGOCAT_LOG_ERROR("dup stdin"); - return FileDescriptor(fd); - } - int flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) { - BONGOCAT_LOG_ERROR("fcntl getfl"); - close(fd); - fd = -1; - return FileDescriptor(fd); - } - if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { - BONGOCAT_LOG_ERROR("fcntl setfl"); - close(fd); - fd = -1; - return FileDescriptor(fd); - } - return FileDescriptor(fd); + // Skip if non-existent + if (access(candidate, F_OK) != 0) { + failed++; + BONGOCAT_LOG_WARNING("Device missing: %s", candidate); + continue; } - struct sync_devices_options_t { - bool reload_devices_needed{false}; - }; - struct sync_devices_options_result_t { - size_t valid_devices{0}; - size_t broken_symlink{0}; - size_t failed{0}; - size_t ignore{0}; - }; - BONGOCAT_NODISCARD static created_result_t sync_devices(input_context_t& input, sync_devices_options_t options = {}) { - assert(input.shm != nullptr); - - size_t valid_devices = 0; - // Ensure buffer size - if (input._unique_paths_indices_capacity < input._device_paths.count || options.reload_devices_needed) { - auto new_unique_devices = make_allocated_array(input._device_paths.count); - if (!new_unique_devices) { - BONGOCAT_LOG_ERROR("Failed to allocate memory for unique devices"); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - auto new_unique_paths_indices = make_allocated_array(input._device_paths.count); - if (!new_unique_paths_indices) { - release_allocated_array(input._unique_devices); - BONGOCAT_LOG_ERROR("Failed to allocate memory for unique path indices"); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - input._unique_devices = bongocat::move(new_unique_devices); - input._unique_paths_indices = bongocat::move(new_unique_paths_indices); - input._unique_paths_indices_capacity = input._device_paths.count; - } - - size_t broken_symlink = 0; - size_t failed = 0; - size_t ignore = 0; - - // recover real size for syncing - input._unique_devices.count = input._unique_paths_indices_capacity; - input._unique_paths_indices.count = input._unique_paths_indices_capacity; - size_t num_unique_devices = 0; - for (size_t i = 0; i < input._device_paths.count; i++) { - const char* device_path = input._device_paths[i]; - - // Resolve to canonical path - char resolved[PATH_MAX]; - const char* candidate = nullptr; - input_unique_file_type_t new_type = input_unique_file_type_t::File; - - struct stat lst{}; - if (lstat(device_path, &lst) == 0 && S_ISLNK(lst.st_mode)) { - new_type = input_unique_file_type_t::Symlink; - if (realpath(device_path, resolved)) { - candidate = resolved; - } else { - BONGOCAT_LOG_WARNING("Broken symlink: %s", device_path); - broken_symlink++; - continue; - } - } else { - candidate = device_path; - } - - // Skip if non-existent - if (access(candidate, F_OK) != 0) { - failed++; - BONGOCAT_LOG_WARNING("Device missing: %s", candidate); - continue; - } + // Check if we already added this canonical path + bool duplicate = false; + for (size_t j = 0; j < num_unique_devices; j++) { + const input_unique_file_t& prev = input._unique_devices[j]; + if (prev.canonical_path && strcmp(prev.canonical_path, candidate) == 0) { + duplicate = true; + break; + } + } + if (duplicate) + continue; - // Check if we already added this canonical path - bool duplicate = false; - for (size_t j = 0; j < num_unique_devices; j++) { - const input_unique_file_t& prev = input._unique_devices[j]; - if (prev.canonical_path && strcmp(prev.canonical_path, candidate) == 0) { - duplicate = true; - break; - } - } - if (duplicate) continue; + input_unique_file_t& cur = input._unique_devices[num_unique_devices]; + input._unique_paths_indices[num_unique_devices] = i; - input_unique_file_t& cur = input._unique_devices[num_unique_devices]; - input._unique_paths_indices[num_unique_devices] = i; + // Decide if we need to replace real_device_path + bool need_reopen = false; + bool need_replace = false; + if (cur.canonical_path) { + need_replace = strcmp(cur.canonical_path, candidate) != 0; + } + if (static_cast(cur.canonical_path) != static_cast(candidate)) { + need_reopen = !cur.canonical_path && candidate; + need_replace = true; + } + if (!need_reopen && cur.type != new_type) { + need_reopen = true; + } - // Decide if we need to replace real_device_path - bool need_reopen = false; - bool need_replace = false; - if (cur.canonical_path) { - need_replace = strcmp(cur.canonical_path, candidate) != 0; - } - if (static_cast(cur.canonical_path) != static_cast(candidate)) { - need_reopen = !cur.canonical_path && candidate; - need_replace = true; - } - if (!need_reopen && cur.type != new_type) { - need_reopen = true; - } + // Check existing FD + if (!need_reopen && cur.fd._fd >= 0 && is_open_device_valid(cur.fd._fd)) { + valid_devices++; + num_unique_devices++; + continue; + } - // Check existing FD - if (!need_reopen && cur.fd._fd >= 0 && is_open_device_valid(cur.fd._fd)) { - valid_devices++; - num_unique_devices++; - continue; - } + // Reopen + if (need_reopen || need_replace) { + close_fd(cur.fd); + struct stat st{}; + if (stat(candidate, &st) == 0 && S_ISCHR(st.st_mode)) { + if (need_reopen) { + int fd = open(candidate, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (fd >= 0 && is_open_device_valid(fd)) { + cur.fd = FileDescriptor(fd); + BONGOCAT_LOG_INFO("Opened input device: %s (fd=%d)", candidate, cur.fd._fd); + } + } + if (cur.fd._fd >= 0 && is_open_device_valid(cur.fd._fd)) { + // Replace canonical path if changed + if (need_replace) { + if (cur.canonical_path) + ::free(cur.canonical_path); + cur.canonical_path = ::strdup(candidate); + } + cur.type = new_type; + cur._device_path = device_path; + valid_devices++; + } else { + cleanup(cur); + failed++; + BONGOCAT_LOG_WARNING("Failed to open %s", candidate); + } + } else { + cleanup(cur); + ignore++; + BONGOCAT_LOG_WARNING("Ignoring non-char device: %s", candidate); + } + } - // Reopen - if (need_reopen || need_replace) { - close_fd(cur.fd); - struct stat st{}; - if (stat(candidate, &st) == 0 && S_ISCHR(st.st_mode)) { - if (need_reopen) { - int fd = open(candidate, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (fd >= 0 && is_open_device_valid(fd)) { - cur.fd = FileDescriptor(fd); - BONGOCAT_LOG_INFO("Opened input device: %s (fd=%d)", candidate, cur.fd._fd); - } - } - if (cur.fd._fd >= 0 && is_open_device_valid(cur.fd._fd)) { - // Replace canonical path if changed - if (need_replace) { - if (cur.canonical_path) ::free(cur.canonical_path); - cur.canonical_path = ::strdup(candidate); - } - cur.type = new_type; - cur._device_path = device_path; - valid_devices++; - } else { - cleanup(cur); - failed++; - BONGOCAT_LOG_WARNING("Failed to open %s", candidate); - } - } else { - cleanup(cur); - ignore++; - BONGOCAT_LOG_WARNING("Ignoring non-char device: %s", candidate); - } - } + num_unique_devices++; + } + + assert(num_unique_devices <= input._device_paths.count); + // shrink size, @NOTE: don't do this with mmap array + input._unique_devices.count = num_unique_devices; + input._unique_paths_indices.count = num_unique_devices; + return sync_devices_options_result_t{ + .valid_devices = valid_devices, + .broken_symlink = broken_symlink, + .failed = failed, + .ignore = ignore, + }; +} - num_unique_devices++; - } +static bongocat_error_t setup_udev_monitor(input_context_t& input) { + if (input._udev_mon) + udev_monitor_unref(input._udev_mon); + if (input._udev) + udev_unref(input._udev); + input._udev_mon = nullptr; + input._udev = nullptr; + input._udev_fd = -1; + + input._udev = udev_new(); + if (!input._udev) { + BONGOCAT_LOG_ERROR("Failed to init udev\n"); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + input._udev_mon = udev_monitor_new_from_netlink(input._udev, "udev"); + if (!input._udev_mon) { + BONGOCAT_LOG_ERROR("Failed to create udev monitor\n"); + udev_unref(input._udev); + input._udev = nullptr; + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // only care about input subsystem + udev_monitor_filter_add_match_subsystem_devtype(input._udev_mon, "input", nullptr); + udev_monitor_enable_receiving(input._udev_mon); + + input._udev_fd = udev_monitor_get_fd(input._udev_mon); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - assert(num_unique_devices <= input._device_paths.count); - // shrink size, @NOTE: don't do this with mmap array - input._unique_devices.count = num_unique_devices; - input._unique_paths_indices.count = num_unique_devices; - return sync_devices_options_result_t { - .valid_devices = valid_devices, - .broken_symlink = broken_symlink, - .failed = failed, - .ignore = ignore, - }; +static void *input_thread(void *arg) { + assert(arg); + animation::animation_session_t& trigger_ctx = *static_cast(arg); + + // from thread context + // animation_context_t& anim = trigger_ctx.anim; + // wait for input context (in animation start) + trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + assert(trigger_ctx._input != nullptr); + input_context_t& input = *trigger_ctx._input; + + // sanity checks + assert(input._config != nullptr); + assert(input._configs_reloaded_cond != nullptr); + assert(!input._capture_input_running); + assert(input.shm != nullptr); + assert(input._local_copy_config != nullptr); + assert(input.update_config_efd._fd >= 0); + + // keep local copies of device_paths + { + // read-only config + assert(input._local_copy_config != nullptr); + const config::config_t& current_config = *input._local_copy_config; + + assert(current_config.num_keyboard_devices >= 0); + int device_paths_count = current_config.num_keyboard_devices; + const char *const *device_paths = current_config.keyboard_devices; + assert(device_paths_count >= 0); + input._device_paths = make_allocated_array(static_cast(device_paths_count)); + for (size_t i = 0; i < input._device_paths.count; i++) { + input._device_paths[i] = strdup(device_paths[i]); + if (!input._device_paths[i]) { + atomic_store(&input._capture_input_running, false); + cleanup_input_devices_paths(input, i); + cleanup_input_thread_context(input); + BONGOCAT_LOG_ERROR("input: Failed to allocate memory for device_paths"); + return nullptr; + } + } + } + + BONGOCAT_LOG_DEBUG("input: Starting input capture on %d devices", input._device_paths.count); + + // init unique devices + size_t track_valid_devices = 0; + { + [[maybe_unused]] const auto t0 = platform::get_current_time_us(); + auto [sync_devices_result, init_devices_result] = sync_devices(input); + if (init_devices_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + atomic_store(&input._capture_input_running, false); + cleanup_input_thread_context(input); + BONGOCAT_LOG_ERROR("input: Failed to init devices and file descriptors"); + return nullptr; + } + [[maybe_unused]] const auto t1 = platform::get_current_time_us(); + + BONGOCAT_LOG_INFO("input: Successfully opened %d/%d input devices; init time %.3fms (%.6fsec)", + sync_devices_result.valid_devices, input._device_paths.count, + static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); + track_valid_devices = sync_devices_result.valid_devices; + } + + // udev monitoring + { + const bongocat_error_t udev_result = setup_udev_monitor(input); + if (udev_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { + BONGOCAT_LOG_WARNING("Can't create udev monitoring"); + } else { + BONGOCAT_LOG_INFO("Start udev monitoring"); + } + } + + // trigger initial render + wayland::request_render(trigger_ctx); + + pthread_cleanup_push(cleanup_input_thread, arg); + + // local thread context + int check_counter = 0; // check is done periodically + time_sec_t adaptive_check_interval_sec = START_ADAPTIVE_CHECK_INTERVAL_SEC; + /// event poll + // 0: reload config event + // 1: udev monitor fd + // 2 - n: device events + // last: stdin (optional) + constexpr size_t MAX_PFDS = 2 + MAX_DEVICE_FDS + ((features::Debug) ? 1 : 0); + pollfd pfds[MAX_PFDS]; + input_event ev[INPUT_EVENT_BUF]; + + constexpr bool include_stdin = features::Debug; + FileDescriptor tty_fd; + if constexpr (include_stdin) { + tty_fd = open_tty_nonblocking(); + BONGOCAT_LOG_INFO("input: Open stdin for testing (fd=%d)", tty_fd._fd); + } + + atomic_store(&input._capture_input_running, true); + while (atomic_load(&input._capture_input_running)) { + pthread_testcancel(); // optional, but makes cancellation more responsive + + // read from config + int timeout = INPUT_POOL_TIMEOUT_MS; + bool enable_debug = false; + { + // read-only config + assert(input._local_copy_config != nullptr); + const config::config_t& current_config = *input._local_copy_config; + + enable_debug = current_config.enable_debug; + + if (current_config.input_fps > 0) { + timeout = 1000 / current_config.input_fps; + } else if (current_config.fps > 0) { + timeout = 1000 / current_config.fps / 3; + } } - static bongocat_error_t setup_udev_monitor(input_context_t& input) { - if (input._udev_mon) udev_monitor_unref(input._udev_mon); - if (input._udev) udev_unref(input._udev); - input._udev_mon = nullptr; - input._udev = nullptr; - input._udev_fd = -1; - - input._udev = udev_new(); - if (!input._udev) { - BONGOCAT_LOG_ERROR("Failed to init udev\n"); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - - input._udev_mon = udev_monitor_new_from_netlink(input._udev, "udev"); - if (!input._udev_mon) { - BONGOCAT_LOG_ERROR("Failed to create udev monitor\n"); - udev_unref(input._udev); - input._udev = nullptr; - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + // only map valid fds into pfds + constexpr size_t fds_update_config_index = 0; + constexpr size_t fds_udev_monitor_index = 1; + constexpr size_t fds_device_potential_start_index = 2; + pfds[fds_update_config_index] = {.fd = input.update_config_efd._fd, .events = POLLIN, .revents = 0}; + pfds[fds_udev_monitor_index] = {.fd = input._udev_fd, .events = POLLIN, .revents = 0}; + + assert(fds_device_potential_start_index < SSIZE_MAX); + ssize_t fds_device_start_index = + input._unique_devices.count > 0 ? static_cast(fds_device_potential_start_index) : -1; + ssize_t fds_device_end_index = input._unique_devices.count > 0 ? fds_device_start_index : -1; + nfds_t nfds = fds_device_potential_start_index; + nfds_t device_nfds = 0; + for (size_t i = 0; i < input._unique_devices.count && i < MAX_DEVICE_FDS; i++) { + if (input._unique_devices[i].fd._fd >= 0) { + pfds[nfds].fd = input._unique_devices[i].fd._fd; + pfds[nfds].events = POLLIN; + pfds[nfds].revents = 0; + nfds++; + device_nfds++; + } + } + assert(device_nfds <= input._unique_devices.count); + assert(input._unique_devices.count <= SSIZE_MAX); + fds_device_end_index = (fds_device_start_index >= 0 && device_nfds > 0) + ? fds_device_start_index + static_cast(device_nfds) + : fds_device_start_index; + + ssize_t fds_stdin_index = -1; + if constexpr (include_stdin) { + if (fds_device_end_index >= 0) { + fds_stdin_index = fds_device_end_index + 1; + } else { + fds_stdin_index = 1; + } + } + if (device_nfds == 0 && !include_stdin) { + BONGOCAT_LOG_ERROR("input: All input devices became unavailable"); + break; + } else if (device_nfds > MAX_DEVICE_FDS) { + device_nfds = MAX_DEVICE_FDS; + fds_stdin_index = -1; + } + if (nfds > MAX_PFDS) { + nfds = MAX_PFDS - ((include_stdin) ? 2 : 1); + } + if (fds_stdin_index >= 0) { + pfds[fds_stdin_index] = {.fd = tty_fd._fd, .events = POLLIN, .revents = 0}; + nfds++; + } + { + // read-only config + assert(input._local_copy_config != nullptr); + const config::config_t& current_config = *input._local_copy_config; + if (device_nfds > MAX_DEVICE_FDS) { + if (current_config._strict) { + BONGOCAT_LOG_ERROR("input: Max input devices fds: %d/%d (%d)", device_nfds, MAX_DEVICE_FDS, + input._unique_devices.count); + break; + } else { + BONGOCAT_LOG_WARNING("input: Max input devices fds: %d/%d (%d)", device_nfds, MAX_DEVICE_FDS, + input._unique_devices.count); } + } + } - // only care about input subsystem - udev_monitor_filter_add_match_subsystem_devtype(input._udev_mon, "input", nullptr); - udev_monitor_enable_receiving(input._udev_mon); - - input._udev_fd = udev_monitor_get_fd(input._udev_mon); - return bongocat_error_t::BONGOCAT_SUCCESS; + /// @TODO: move to tests + // check indices + if constexpr (features::Debug) { + // const bool has_update_config = fds_update_config_index >= 0; + // const bool has_udev = fds_udev_monitor_index >= 0; + const bool has_std_in = fds_stdin_index >= 0; + const bool has_devices = input._unique_devices.count > 0; + + // include every fd + if (has_devices && has_std_in) { + assert(fds_update_config_index == 0); + assert(fds_udev_monitor_index == 1); + assert(fds_device_start_index >= 0); + assert(fds_device_end_index >= 0); + assert(fds_stdin_index >= 0); + + assert(fds_update_config_index == 0); + assert(static_cast(fds_device_start_index) > fds_update_config_index); + assert(static_cast(fds_device_end_index) > fds_update_config_index); + assert(fds_device_end_index >= fds_device_start_index); + assert(fds_stdin_index > fds_device_end_index); + + // assert(device_nfds >= 0); + // assert(nfds >= 0); + assert(device_nfds <= SSIZE_MAX); + assert(nfds <= SSIZE_MAX); + assert(static_cast(device_nfds) == fds_device_end_index - fds_device_start_index); + assert(static_cast(nfds) == fds_device_end_index - fds_device_start_index + 3); + } + // only update + devices + if (has_devices && !has_std_in) { + assert(fds_update_config_index == 0); + assert(fds_udev_monitor_index == 1); + assert(fds_device_start_index >= 0); + assert(fds_device_end_index >= 0); + assert(fds_stdin_index == -1); + + assert(fds_update_config_index == 0); + assert(fds_device_end_index >= 0); + assert(static_cast(fds_device_end_index) > fds_update_config_index); + assert(fds_device_end_index >= fds_device_start_index); + + // assert(device_nfds >= 0); + // assert(nfds >= 0); + assert(device_nfds <= SSIZE_MAX); + assert(nfds <= SSIZE_MAX); + assert(static_cast(device_nfds) == fds_device_end_index - fds_device_start_index); + assert(static_cast(nfds) == fds_device_end_index - fds_device_start_index + 3); + } + // only devices + if (has_devices && !has_std_in) { + assert(fds_update_config_index == 0); + assert(fds_udev_monitor_index == 1); + assert(fds_device_start_index >= 0); + assert(fds_device_end_index >= 0); + assert(fds_stdin_index == -1); + + assert(fds_device_end_index >= 0 && static_cast(fds_device_end_index) <= SIZE_MAX); + assert(static_cast(fds_device_end_index) > fds_update_config_index); + assert(fds_device_end_index >= fds_device_start_index); + + // assert(device_nfds >= 0); + // assert(nfds >= 0); + assert(device_nfds <= SSIZE_MAX); + assert(nfds <= SSIZE_MAX); + assert(static_cast(device_nfds) == fds_device_end_index - fds_device_start_index); + assert(static_cast(nfds) == fds_device_end_index - fds_device_start_index); + } + // nothing (empty) + if (!has_devices && !has_std_in) { + assert(fds_update_config_index == 0); + assert(fds_udev_monitor_index == 1); + assert(fds_device_start_index == -1); + assert(fds_device_end_index == -1); + assert(fds_stdin_index == -1); + + assert(fds_device_end_index >= 0 && static_cast(fds_device_end_index) <= SIZE_MAX); + assert(static_cast(fds_device_end_index) > fds_update_config_index); + assert(fds_device_end_index >= fds_device_start_index); + + assert(device_nfds == 0); + assert(nfds == 2); + } + // no devices, only config + if (!has_devices && !has_std_in) { + assert(fds_update_config_index == 0); + assert(fds_udev_monitor_index == 1); + assert(fds_udev_monitor_index == 1); + assert(fds_device_start_index == -1); + assert(fds_device_end_index == -1); + assert(fds_stdin_index == -1); + + assert(device_nfds == 0); + assert(nfds == 2); + } + // no devices, only config + stdin + if (!has_devices && has_std_in) { + assert(fds_update_config_index == 0); + assert(fds_udev_monitor_index == 1); + assert(fds_device_start_index == -1); + assert(fds_device_end_index == -1); + assert(fds_stdin_index == 1); + + assert(device_nfds == 0); + assert(nfds == 3); + } } - static void* input_thread(void* arg) { - assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); - - // from thread context - //animation_context_t& anim = trigger_ctx.anim; - // wait for input context (in animation start) - trigger_ctx.init_cond.timedwait([&]() { - return atomic_load(&trigger_ctx.ready); - }, COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - assert(trigger_ctx._input != nullptr); - input_context_t& input = *trigger_ctx._input; - - // sanity checks - assert(input._config != nullptr); - assert(input._configs_reloaded_cond != nullptr); - assert(!input._capture_input_running); - assert(input.shm != nullptr); - assert(input._local_copy_config != nullptr); - assert(input.update_config_efd._fd >= 0); - - // keep local copies of device_paths - { - // read-only config - assert(input._local_copy_config != nullptr); - const config::config_t& current_config = *input._local_copy_config; - - assert(current_config.num_keyboard_devices >= 0); - int device_paths_count = current_config.num_keyboard_devices; - const char *const *device_paths = current_config.keyboard_devices; - assert(device_paths_count >= 0); - input._device_paths = make_allocated_array(static_cast(device_paths_count)); - for (size_t i = 0; i < input._device_paths.count; i++) { - input._device_paths[i] = strdup(device_paths[i]); - if (!input._device_paths[i]) { - atomic_store(&input._capture_input_running, false); - cleanup_input_devices_paths(input, i); - cleanup_input_thread_context(input); - BONGOCAT_LOG_ERROR("input: Failed to allocate memory for device_paths"); - return nullptr; + // poll events + const int poll_result = poll(pfds, nfds, timeout); + if (poll_result < 0) { + if (errno == EINTR) + continue; // Interrupted by signal + BONGOCAT_LOG_ERROR("input: Poll error: %s", strerror(errno)); + break; + } + if (poll_result == 0) { + // Timeout — adaptive device checking + check_counter++; + if (check_counter >= (adaptive_check_interval_sec * (1000 / 100))) { + check_counter = 0; + bool found_new_device = false; + for (size_t i = 0; i < input._unique_devices.count; i++) { + const char *device_path = input._unique_devices[i].canonical_path; + bool need_reopen = false; + if (device_path == nullptr) + continue; + // If an fd is already open, check if it is still valid + if (input._unique_devices[i].fd._fd >= 0) { + if (!is_open_device_valid(input._unique_devices[i].fd._fd)) { + // fd no longer valid + need_reopen = true; + } else { + // check if device node changed + struct stat old_st{}; + if (input._unique_devices[i].fd._fd >= 0 && fstat(input._unique_devices[i].fd._fd, &old_st) == 0) { + struct stat new_st{}; + if (stat(device_path, &new_st) == 0) { + if (old_st.st_rdev != new_st.st_rdev) { + need_reopen = true; + } } + } + } + } else { + // FD never opened + need_reopen = true; + } + + if (need_reopen) { + // Close old FD if still open + if (input._unique_devices[i].fd._fd >= 0) { + close_fd(input._unique_devices[i].fd); } - } - BONGOCAT_LOG_DEBUG("input: Starting input capture on %d devices", input._device_paths.count); - - // init unique devices - size_t track_valid_devices = 0; - { - [[maybe_unused]] const auto t0 = platform::get_current_time_us(); - auto [sync_devices_result, init_devices_result] = sync_devices(input); - if (init_devices_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - atomic_store(&input._capture_input_running, false); - cleanup_input_thread_context(input); - BONGOCAT_LOG_ERROR("input: Failed to init devices and file descriptors"); - return nullptr; + if (int new_fd = open(device_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); new_fd >= 0) { + if (is_open_device_valid(new_fd)) { + input._unique_devices[i].fd = FileDescriptor(new_fd); + new_fd = -1; + found_new_device = true; + BONGOCAT_LOG_INFO("input: New input device detected and opened: %s (fd=%d)", device_path, + input._unique_devices[i].fd._fd); + } else { + // Not a valid char device — close immediately + close(new_fd); + BONGOCAT_LOG_VERBOSE("input: vFile opened but not a char device: %s", device_path); + } + } else { + BONGOCAT_LOG_VERBOSE("input: Failed to open input device: %s (%s)", device_path, strerror(errno)); } - [[maybe_unused]] const auto t1 = platform::get_current_time_us(); + } + } - BONGOCAT_LOG_INFO("input: Successfully opened %d/%d input devices; init time %.3fms (%.6fsec)", sync_devices_result.valid_devices, input._device_paths.count, static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); - track_valid_devices = sync_devices_result.valid_devices; + if (!found_new_device && adaptive_check_interval_sec < MAX_ADAPTIVE_CHECK_INTERVAL_SEC) { + adaptive_check_interval_sec = (adaptive_check_interval_sec < MID_ADAPTIVE_CHECK_INTERVAL_SEC) + ? MID_ADAPTIVE_CHECK_INTERVAL_SEC + : MAX_ADAPTIVE_CHECK_INTERVAL_SEC; + BONGOCAT_LOG_DEBUG("input: Increased device check interval to %d seconds", adaptive_check_interval_sec); + } else if (found_new_device && adaptive_check_interval_sec > START_ADAPTIVE_CHECK_INTERVAL_SEC) { + adaptive_check_interval_sec = START_ADAPTIVE_CHECK_INTERVAL_SEC; + BONGOCAT_LOG_DEBUG("input: Reset device check interval to %d seconds", START_ADAPTIVE_CHECK_INTERVAL_SEC); } + } + continue; + } - // udev monitoring - { - const bongocat_error_t udev_result = setup_udev_monitor(input); - if (udev_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { - BONGOCAT_LOG_WARNING("Can't create udev monitoring"); + // cancel pooling (when not running anymore) + if (!atomic_load(&input._capture_input_running)) { + // draining pools + if (pfds[fds_update_config_index].revents & POLLIN) { + drain_event(pfds[fds_update_config_index], MAX_ATTEMPTS); + } + if (pfds[fds_udev_monitor_index].revents & POLLIN) { + drain_event(pfds[fds_udev_monitor_index], MAX_ATTEMPTS); + } + if (fds_device_start_index >= 0) { + assert(fds_device_start_index >= 0); + assert(fds_device_end_index >= 0); + for (nfds_t p = static_cast(fds_device_start_index); p <= static_cast(fds_device_end_index); + p++) { + // Handle ready devices + if (pfds[p].revents & POLLIN) { + // discard evdev input + [[maybe_unused]] auto discard_result = read(pfds[p].fd, ev, sizeof(ev)); + ((void)discard_result); + } + } + } + if (fds_stdin_index >= 0) { + if (pfds[fds_stdin_index].revents & POLLIN) { + char buf[TEST_STDIN_BUF_LEN] = {0}; + // Drain stdin until empty (EAGAIN) + int attempts = 0; + ssize_t rd = 0; + while (attempts < MAX_ATTEMPTS) { + rd = read(pfds[fds_stdin_index].fd, buf, TEST_STDIN_BUF_LEN); + if (rd > 0) { + continue; + } +#if EAGAIN != EWOULDBLOCK + if (rd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { +#else + if (rd == -1 && errno == EAGAIN) { +#endif + break; // drained completely } else { - BONGOCAT_LOG_INFO("Start udev monitoring"); + break; // EOF or error } + } } + } + break; + } - // trigger initial render - wayland::request_render(trigger_ctx); - - pthread_cleanup_push(cleanup_input_thread, arg); - - // local thread context - int check_counter = 0; // check is done periodically - time_sec_t adaptive_check_interval_sec = START_ADAPTIVE_CHECK_INTERVAL_SEC; - /// event poll - // 0: reload config event - // 1: udev monitor fd - // 2 - n: device events - // last: stdin (optional) - constexpr size_t MAX_PFDS = 2 + MAX_DEVICE_FDS + ((features::Debug) ? 1 : 0); - pollfd pfds[MAX_PFDS]; - input_event ev[INPUT_EVENT_BUF]; - - constexpr bool include_stdin = features::Debug; - FileDescriptor tty_fd; - if constexpr (include_stdin) { - tty_fd = open_tty_nonblocking(); - BONGOCAT_LOG_INFO("input: Open stdin for testing (fd=%d)", tty_fd._fd); + // handle hotplug + bool sync_devices_needed = false; + bool reload_devices_needed = false; + if (pfds[fds_udev_monitor_index].revents & POLLIN) { + BONGOCAT_LOG_DEBUG("input: Receive udev event"); + size_t attempts = 0; + udev_device *dev = nullptr; + while ((dev = udev_monitor_receive_device(input._udev_mon)) != nullptr && attempts < MAX_ATTEMPTS) { + const char *action = udev_device_get_action(dev); + const char *node = udev_device_get_devnode(dev); + + if (action && node) { + BONGOCAT_LOG_VERBOSE("input: udev %s: %s", action, node); + if (strcmp(action, "add") == 0 || strcmp(action, "remove") == 0) { + sync_devices_needed = true; + reload_devices_needed = true; + } } - atomic_store(&input._capture_input_running, true); - while (atomic_load(&input._capture_input_running)) { - pthread_testcancel(); // optional, but makes cancellation more responsive - - // read from config - int timeout = INPUT_POOL_TIMEOUT_MS; - bool enable_debug = false; - { - // read-only config - assert(input._local_copy_config != nullptr); - const config::config_t& current_config = *input._local_copy_config; - - enable_debug = current_config.enable_debug; + udev_device_unref(dev); + attempts++; + } + drain_event(pfds[fds_udev_monitor_index], MAX_ATTEMPTS, "udev monitor fd"); + } - if (current_config.input_fps > 0) { - timeout = 1000 / current_config.input_fps; - } else if (current_config.fps > 0) { - timeout = 1000 / current_config.fps / 3; - } - } + // Handle config update + assert(input._config_generation != nullptr); + bool reload_config = false; + uint64_t new_gen{atomic_load(input._config_generation)}; + if (pfds[fds_update_config_index].revents & POLLIN) { + BONGOCAT_LOG_DEBUG("input: Receive update config event"); + drain_event(pfds[fds_update_config_index], MAX_ATTEMPTS, "update config eventfd"); + reload_config = new_gen > 0; + sync_devices_needed |= reload_config; + } - // only map valid fds into pfds - constexpr size_t fds_update_config_index = 0; - constexpr size_t fds_udev_monitor_index = 1; - constexpr size_t fds_device_potential_start_index = 2; - pfds[fds_update_config_index] = { .fd = input.update_config_efd._fd, .events = POLLIN, .revents = 0 }; - pfds[fds_udev_monitor_index] = { .fd = input._udev_fd, .events = POLLIN, .revents = 0 }; - - assert(fds_device_potential_start_index < SSIZE_MAX); - ssize_t fds_device_start_index = input._unique_devices.count > 0 ? static_cast(fds_device_potential_start_index) : -1; - ssize_t fds_device_end_index = input._unique_devices.count > 0 ? fds_device_start_index : -1; - nfds_t nfds = fds_device_potential_start_index; - nfds_t device_nfds = 0; - for (size_t i = 0; i < input._unique_devices.count && i < MAX_DEVICE_FDS; i++) { - if (input._unique_devices[i].fd._fd >= 0) { - pfds[nfds].fd = input._unique_devices[i].fd._fd; - pfds[nfds].events = POLLIN; - pfds[nfds].revents = 0; - nfds++; - device_nfds++; + // Handle device events + { + platform::LockGuard guard(input.input_lock); + assert(input.shm != nullptr); + // auto& input_shm = *input.shm; + + if (fds_device_start_index >= 0) { + assert(fds_device_start_index >= 0); + assert(fds_device_end_index >= 0); + for (nfds_t p = static_cast(fds_device_start_index); p <= static_cast(fds_device_end_index); + p++) { + // Handle ready devices + if (pfds[p].revents & POLLIN) { + // handle evdev input + const ssize_t rd = read(pfds[p].fd, ev, sizeof(ev)); + if (rd < 0) { + if (errno == ENODEV) + sync_devices_needed = true; + if (errno == EAGAIN) + continue; + BONGOCAT_LOG_WARNING("input: Read error on fd=%d: %s", pfds[p].fd, strerror(errno)); + close(pfds[p].fd); + // pfds[p].fd is only a reference, reset also the owner (unique_fd) + for (size_t i = 0; i < input._unique_devices.count; i++) { + if (input._unique_devices[i].fd._fd == pfds[p].fd) { + input._unique_devices[i].fd._fd = -1; + pfds[i].fd = -1; + break; } + } + pfds[p].fd = -1; + sync_devices_needed = true; + continue; } - assert(device_nfds <= input._unique_devices.count); - assert(input._unique_devices.count <= SSIZE_MAX); - fds_device_end_index = (fds_device_start_index >= 0 && device_nfds > 0)? fds_device_start_index + static_cast(device_nfds) : fds_device_start_index; - - ssize_t fds_stdin_index = -1; - if constexpr (include_stdin) { - if (fds_device_end_index >= 0) { - fds_stdin_index = fds_device_end_index+1; - } else { - fds_stdin_index = 1; - } - } - if (device_nfds == 0 && !include_stdin) { - BONGOCAT_LOG_ERROR("input: All input devices became unavailable"); - break; - } else if (device_nfds > MAX_DEVICE_FDS) { - device_nfds = MAX_DEVICE_FDS; - fds_stdin_index = -1; - } - if (nfds > MAX_PFDS) { - nfds = MAX_PFDS - ((include_stdin) ? 2 : 1); - } - if (fds_stdin_index >= 0) { - pfds[fds_stdin_index] = { .fd = tty_fd._fd, .events = POLLIN, .revents = 0 }; - nfds++; - } - { - // read-only config - assert(input._local_copy_config != nullptr); - const config::config_t& current_config = *input._local_copy_config; - if (device_nfds > MAX_DEVICE_FDS) { - if (current_config._strict) { - BONGOCAT_LOG_ERROR("input: Max input devices fds: %d/%d (%d)", device_nfds, MAX_DEVICE_FDS, input._unique_devices.count); - break; - } else { - BONGOCAT_LOG_WARNING("input: Max input devices fds: %d/%d (%d)", device_nfds, MAX_DEVICE_FDS, input._unique_devices.count); - } + assert(rd >= 0); + if (rd == 0 || static_cast(rd) % sizeof(input_event) != 0) { + BONGOCAT_LOG_WARNING("input: EOF or partial read on fd=%d", pfds[p].fd); + close(pfds[p].fd); + // pfds[p].fd is only a reference, reset also the owner (unique_fd) + for (size_t i = 0; i < input._unique_devices.count; i++) { + if (input._unique_devices[i].fd._fd == pfds[p].fd) { + input._unique_devices[i].fd._fd = -1; + pfds[i].fd = -1; + break; } + } + pfds[p].fd = -1; + sync_devices_needed = true; + continue; } - /// @TODO: move to tests - // check indices - if constexpr (features::Debug) { - //const bool has_update_config = fds_update_config_index >= 0; - //const bool has_udev = fds_udev_monitor_index >= 0; - const bool has_std_in = fds_stdin_index >= 0; - const bool has_devices = input._unique_devices.count > 0; - - // include every fd - if (has_devices && has_std_in) { - assert(fds_update_config_index == 0); - assert(fds_udev_monitor_index == 1); - assert(fds_device_start_index >= 0); - assert(fds_device_end_index >= 0); - assert(fds_stdin_index >= 0); - - assert(fds_update_config_index == 0); - assert(static_cast(fds_device_start_index) > fds_update_config_index); - assert(static_cast(fds_device_end_index) > fds_update_config_index); - assert(fds_device_end_index >= fds_device_start_index); - assert(fds_stdin_index > fds_device_end_index); - - //assert(device_nfds >= 0); - //assert(nfds >= 0); - assert(device_nfds <= SSIZE_MAX); - assert(nfds <= SSIZE_MAX); - assert(static_cast(device_nfds) == fds_device_end_index-fds_device_start_index); - assert(static_cast(nfds) == fds_device_end_index-fds_device_start_index + 3); - } - // only update + devices - if (has_devices && !has_std_in) { - assert(fds_update_config_index == 0); - assert(fds_udev_monitor_index == 1); - assert(fds_device_start_index >= 0); - assert(fds_device_end_index >= 0); - assert(fds_stdin_index == -1); - - assert(fds_update_config_index == 0); - assert(fds_device_end_index >= 0); - assert(static_cast(fds_device_end_index) > fds_update_config_index); - assert(fds_device_end_index >= fds_device_start_index); - - //assert(device_nfds >= 0); - //assert(nfds >= 0); - assert(device_nfds <= SSIZE_MAX); - assert(nfds <= SSIZE_MAX); - assert(static_cast(device_nfds) == fds_device_end_index-fds_device_start_index); - assert(static_cast(nfds) == fds_device_end_index-fds_device_start_index + 3); - } - // only devices - if (has_devices && !has_std_in) { - assert(fds_update_config_index == 0); - assert(fds_udev_monitor_index == 1); - assert(fds_device_start_index >= 0); - assert(fds_device_end_index >= 0); - assert(fds_stdin_index == -1); - - assert(fds_device_end_index >= 0 && static_cast(fds_device_end_index) <= SIZE_MAX); - assert(static_cast(fds_device_end_index) > fds_update_config_index); - assert(fds_device_end_index >= fds_device_start_index); - - //assert(device_nfds >= 0); - //assert(nfds >= 0); - assert(device_nfds <= SSIZE_MAX); - assert(nfds <= SSIZE_MAX); - assert(static_cast(device_nfds) == fds_device_end_index-fds_device_start_index); - assert(static_cast(nfds) == fds_device_end_index-fds_device_start_index); - } - // nothing (empty) - if (!has_devices && !has_std_in) { - assert(fds_update_config_index == 0); - assert(fds_udev_monitor_index == 1); - assert(fds_device_start_index == -1); - assert(fds_device_end_index == -1); - assert(fds_stdin_index == -1); - - assert(fds_device_end_index >= 0 && static_cast(fds_device_end_index) <= SIZE_MAX); - assert(static_cast(fds_device_end_index) > fds_update_config_index); - assert(fds_device_end_index >= fds_device_start_index); - - assert(device_nfds == 0); - assert(nfds == 2); - } - // no devices, only config - if (!has_devices && !has_std_in) { - assert(fds_update_config_index == 0); - assert(fds_udev_monitor_index == 1); - assert(fds_udev_monitor_index == 1); - assert(fds_device_start_index == -1); - assert(fds_device_end_index == -1); - assert(fds_stdin_index == -1); - - assert(device_nfds == 0); - assert(nfds == 2); - } - // no devices, only config + stdin - if (!has_devices && has_std_in) { - assert(fds_update_config_index == 0); - assert(fds_udev_monitor_index == 1); - assert(fds_device_start_index == -1); - assert(fds_device_end_index == -1); - assert(fds_stdin_index == 1); - - assert(device_nfds == 0); - assert(nfds == 3); + bool key_pressed = false; + // keep captured keycode short-lived + int captured_keycode = 0; + assert(rd >= 0); + assert(sizeof(input_event) > 0); + const auto num_events = static_cast(static_cast(rd) / sizeof(input_event)); + for (ssize_t j = 0; j < num_events; j++) { + if (ev[j].type == EV_KEY && ev[j].value == 1) { + key_pressed = true; + captured_keycode = ev[j].code; // Store for hand mapping + if (enable_debug) { + BONGOCAT_LOG_VERBOSE("input: Key event: fd=%d, code=%d, time=%lld.%06lld", pfds[p].fd, ev[j].code, + ev[j].time.tv_sec, ev[j].time.tv_usec); + } else { + // break loop early, when no debug (no print needed for every key press) + break; } + } } - // poll events - const int poll_result = poll(pfds, nfds, timeout); - if (poll_result < 0) { - if (errno == EINTR) continue; // Interrupted by signal - BONGOCAT_LOG_ERROR("input: Poll error: %s", strerror(errno)); - break; - } - if (poll_result == 0) { - // Timeout — adaptive device checking - check_counter++; - if (check_counter >= (adaptive_check_interval_sec * (1000 / 100))) { - check_counter = 0; - bool found_new_device = false; - for (size_t i = 0; i < input._unique_devices.count; i++) { - const char* device_path = input._unique_devices[i].canonical_path; - bool need_reopen = false; - if (device_path == nullptr) continue; - // If an fd is already open, check if it is still valid - if (input._unique_devices[i].fd._fd >= 0) { - if (!is_open_device_valid(input._unique_devices[i].fd._fd)) { - // fd no longer valid - need_reopen = true; - } else { - // check if device node changed - struct stat old_st{}; - if (input._unique_devices[i].fd._fd >= 0 && - fstat(input._unique_devices[i].fd._fd, &old_st) == 0) { - struct stat new_st{}; - if (stat(device_path, &new_st) == 0) { - if (old_st.st_rdev != new_st.st_rdev) { - need_reopen = true; - } - } - } - } - } else { - // FD never opened - need_reopen = true; - } - - if (need_reopen) { - // Close old FD if still open - if (input._unique_devices[i].fd._fd >= 0) { - close_fd(input._unique_devices[i].fd); - } - - if (int new_fd = open(device_path, O_RDONLY | O_NONBLOCK | O_CLOEXEC); new_fd >= 0) { - if (is_open_device_valid(new_fd)) { - input._unique_devices[i].fd = FileDescriptor(new_fd); - new_fd = -1; - found_new_device = true; - BONGOCAT_LOG_INFO("input: New input device detected and opened: %s (fd=%d)", device_path, input._unique_devices[i].fd._fd); - } else { - // Not a valid char device — close immediately - close(new_fd); - BONGOCAT_LOG_VERBOSE("input: vFile opened but not a char device: %s", device_path); - } - } else { - BONGOCAT_LOG_VERBOSE("input: Failed to open input device: %s (%s)", device_path, strerror(errno)); - } - } - } - - if (!found_new_device && adaptive_check_interval_sec < MAX_ADAPTIVE_CHECK_INTERVAL_SEC) { - adaptive_check_interval_sec = - (adaptive_check_interval_sec < MID_ADAPTIVE_CHECK_INTERVAL_SEC) - ? MID_ADAPTIVE_CHECK_INTERVAL_SEC - : MAX_ADAPTIVE_CHECK_INTERVAL_SEC; - BONGOCAT_LOG_DEBUG("input: Increased device check interval to %d seconds", adaptive_check_interval_sec); - } else if (found_new_device && adaptive_check_interval_sec > START_ADAPTIVE_CHECK_INTERVAL_SEC) { - adaptive_check_interval_sec = START_ADAPTIVE_CHECK_INTERVAL_SEC; - BONGOCAT_LOG_DEBUG("input: Reset device check interval to %d seconds", START_ADAPTIVE_CHECK_INTERVAL_SEC); - } - } - continue; + const timestamp_ms_t now = get_current_time_ms(); + if (key_pressed) { + trigger_key_press(trigger_ctx, captured_keycode); + } else { + input.shm->any_key_pressed = 0; + input.shm->hand_mapping = input_hand_mapping_t::None; + if (input.shm->kpm > 0 && now - input._latest_kpm_update_ms >= RESET_KPM_TIMEOUT_MS) { + input.shm->kpm = 0; + atomic_store(&input._input_kpm_counter, 0); + input._latest_kpm_update_ms = now; + } } - - // cancel pooling (when not running anymore) - if (!atomic_load(&input._capture_input_running)) { - // draining pools - if (pfds[fds_update_config_index].revents & POLLIN) { - drain_event(pfds[fds_update_config_index], MAX_ATTEMPTS); - } - if (pfds[fds_udev_monitor_index].revents & POLLIN) { - drain_event(pfds[fds_udev_monitor_index], MAX_ATTEMPTS); - } - if (fds_device_start_index >= 0) { - assert(fds_device_start_index >= 0); - assert(fds_device_end_index >= 0); - for (nfds_t p = static_cast(fds_device_start_index); p <= static_cast(fds_device_end_index); p++) { - // Handle ready devices - if (pfds[p].revents & POLLIN) { - // discard evdev input - [[maybe_unused]] auto discard_result = read(pfds[p].fd, ev, sizeof(ev)); - ((void)discard_result); - } - } - } - if (fds_stdin_index >= 0) { - if (pfds[fds_stdin_index].revents & POLLIN) { - char buf[TEST_STDIN_BUF_LEN] = {0}; - // Drain stdin until empty (EAGAIN) - int attempts = 0; - ssize_t rd = 0; - while (attempts < MAX_ATTEMPTS) { - rd = read(pfds[fds_stdin_index].fd, buf, TEST_STDIN_BUF_LEN); - if (rd > 0) { - continue; - } -#if EAGAIN != EWOULDBLOCK - if (rd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { -#else - if (rd == -1 && errno == EAGAIN) { -#endif - break; // drained completely - } else { - break; // EOF or error - } - } - } - } + } + + if (pfds[p].revents & (POLLERR | POLLHUP | POLLNVAL)) { + close(pfds[p].fd); + // pfds[p].fd is only a reference, reset also the owner (unique_fd) + for (size_t i = 0; i < input._unique_devices.count; i++) { + if (input._unique_devices[i].fd._fd == pfds[p].fd) { + input._unique_devices[i].fd._fd = -1; + pfds[i].fd = -1; break; + } } - - // handle hotplug - bool sync_devices_needed = false; - bool reload_devices_needed = false; - if (pfds[fds_udev_monitor_index].revents & POLLIN) { - BONGOCAT_LOG_DEBUG("input: Receive udev event"); - size_t attempts = 0; - udev_device *dev = nullptr; - while ((dev = udev_monitor_receive_device(input._udev_mon)) != nullptr && attempts < MAX_ATTEMPTS) { - const char* action = udev_device_get_action(dev); - const char* node = udev_device_get_devnode(dev); - - if (action && node) { - BONGOCAT_LOG_VERBOSE("input: udev %s: %s", action, node); - if (strcmp(action, "add") == 0 || strcmp(action, "remove") == 0) { - sync_devices_needed = true; - reload_devices_needed = true; - } - } - - udev_device_unref(dev); - attempts++; - } - drain_event(pfds[fds_udev_monitor_index], MAX_ATTEMPTS, "udev monitor fd"); - } - - // Handle config update - assert(input._config_generation != nullptr); - bool reload_config = false; - uint64_t new_gen{atomic_load(input._config_generation)}; - if (pfds[fds_update_config_index].revents & POLLIN) { - BONGOCAT_LOG_DEBUG("input: Receive update config event"); - drain_event(pfds[fds_update_config_index], MAX_ATTEMPTS, "update config eventfd"); - reload_config = new_gen > 0; - sync_devices_needed |= reload_config; + pfds[p].fd = -1; + sync_devices_needed = true; + } + } + } + + // simulate "any key pressed" + if (fds_stdin_index >= 0) { + if (pfds[fds_stdin_index].revents & POLLIN) { + char buf[TEST_STDIN_BUF_LEN] = {0}; + bool got_key = false; + + // Drain stdin until empty (EAGAIN) + int attempts = 0; + ssize_t rd = 0; + while (attempts < MAX_ATTEMPTS) { + rd = read(pfds[fds_stdin_index].fd, buf, TEST_STDIN_BUF_LEN); + if (rd > 0) { + got_key = true; } - - // Handle device events - { - platform::LockGuard guard (input.input_lock); - assert(input.shm != nullptr); - //auto& input_shm = *input.shm; - - if (fds_device_start_index >= 0) { - assert(fds_device_start_index >= 0); - assert(fds_device_end_index >= 0); - for (nfds_t p = static_cast(fds_device_start_index); p <= static_cast(fds_device_end_index); p++) { - // Handle ready devices - if (pfds[p].revents & POLLIN) { - // handle evdev input - const ssize_t rd = read(pfds[p].fd, ev, sizeof(ev)); - if (rd < 0) { - if (errno == ENODEV) sync_devices_needed = true; - if (errno == EAGAIN) continue; - BONGOCAT_LOG_WARNING("input: Read error on fd=%d: %s", pfds[p].fd, strerror(errno)); - close(pfds[p].fd); - // pfds[p].fd is only a reference, reset also the owner (unique_fd) - for (size_t i = 0; i < input._unique_devices.count; i++) { - if (input._unique_devices[i].fd._fd == pfds[p].fd) { - input._unique_devices[i].fd._fd = -1; - pfds[i].fd = -1; - break; - } - } - pfds[p].fd = -1; - sync_devices_needed = true; - continue; - } - assert(rd >= 0); - if (rd == 0 || static_cast(rd) % sizeof(input_event) != 0) { - BONGOCAT_LOG_WARNING("input: EOF or partial read on fd=%d", pfds[p].fd); - close(pfds[p].fd); - // pfds[p].fd is only a reference, reset also the owner (unique_fd) - for (size_t i = 0; i < input._unique_devices.count; i++) { - if (input._unique_devices[i].fd._fd == pfds[p].fd) { - input._unique_devices[i].fd._fd = -1; - pfds[i].fd = -1; - break; - } - } - pfds[p].fd = -1; - sync_devices_needed = true; - continue; - } - - bool key_pressed = false; - // keep captured keycode short-lived - int captured_keycode = 0; - assert(rd >= 0); - assert(sizeof(input_event) > 0); - const auto num_events = static_cast(static_cast(rd) / sizeof(input_event)); - for (ssize_t j = 0; j < num_events; j++) { - if (ev[j].type == EV_KEY && ev[j].value == 1) { - key_pressed = true; - captured_keycode = ev[j].code; // Store for hand mapping - if (enable_debug) { - BONGOCAT_LOG_VERBOSE("input: Key event: fd=%d, code=%d, time=%lld.%06lld", - pfds[p].fd, ev[j].code, - ev[j].time.tv_sec, ev[j].time.tv_usec); - } else { - // break loop early, when no debug (no print needed for every key press) - break; - } - } - } - - - const timestamp_ms_t now = get_current_time_ms(); - if (key_pressed) { - trigger_key_press(trigger_ctx, captured_keycode); - } else { - input.shm->any_key_pressed = 0; - input.shm->hand_mapping = input_hand_mapping_t::None; - if (input.shm->kpm > 0 && now - input._latest_kpm_update_ms >= RESET_KPM_TIMEOUT_MS) { - input.shm->kpm = 0; - atomic_store(&input._input_kpm_counter, 0); - input._latest_kpm_update_ms = now; - } - } - } - - if (pfds[p].revents & (POLLERR | POLLHUP | POLLNVAL)) { - close(pfds[p].fd); - // pfds[p].fd is only a reference, reset also the owner (unique_fd) - for (size_t i = 0; i < input._unique_devices.count; i++) { - if (input._unique_devices[i].fd._fd == pfds[p].fd) { - input._unique_devices[i].fd._fd = -1; - pfds[i].fd = -1; - break; - } - } - pfds[p].fd = -1; - sync_devices_needed = true; - } - } - } - - // simulate "any key pressed" - if (fds_stdin_index >= 0) { - if (pfds[fds_stdin_index].revents & POLLIN) { - char buf[TEST_STDIN_BUF_LEN] = {0}; - bool got_key = false; - - // Drain stdin until empty (EAGAIN) - int attempts = 0; - ssize_t rd = 0; - while (attempts < MAX_ATTEMPTS) { - rd = read(pfds[fds_stdin_index].fd, buf, TEST_STDIN_BUF_LEN); - if (rd > 0) { - got_key = true; - } #if EAGAIN != EWOULDBLOCK - if (rd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + if (rd == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { #else - if (rd == -1 && errno == EAGAIN) { + if (rd == -1 && errno == EAGAIN) { #endif - break; // drained completely - } else { - break; // EOF or error - } - } - - if (got_key) { - trigger_key_press(trigger_ctx); - if (enable_debug) { - size_t len = (rd > 0) ? (static_cast(rd) < TEST_STDIN_BUF_LEN ? static_cast(rd) : TEST_STDIN_BUF_LEN-1) : 0; - buf[len] = '\0'; - BONGOCAT_LOG_VERBOSE("input: stdin input: %s", buf); - } - } - } - } + break; // drained completely + } else { + break; // EOF or error } - - // Revalidate valid devices - if (sync_devices_needed) { - platform::LockGuard guard (input.input_lock); - - auto [sync_devices_result, revalid_devices_result] = sync_devices(input, { .reload_devices_needed = reload_devices_needed }); - if (revalid_devices_result != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("input: Failed to revalidate devices and file descriptors"); - } else { - if (sync_devices_result.valid_devices == 0) { - BONGOCAT_LOG_VERBOSE("input: All input devices became unavailable"); - } - } - track_valid_devices = sync_devices_result.valid_devices; - } - - // handle update config - if (reload_config) { - assert(input._config_generation != nullptr); - assert(input._configs_reloaded_cond != nullptr); - assert(input._config != nullptr); - - update_config(input, *input._config, new_gen); - - // wait for reload config to be done (all configs) - const int rc = input._configs_reloaded_cond->timedwait([&] { - return atomic_load(input._config_generation) >= new_gen; - }, COND_RELOAD_CONFIGS_TIMEOUT_MS); - if (rc == ETIMEDOUT) { - BONGOCAT_LOG_WARNING("input: Timed out waiting for reload eventfd: %s", strerror(errno)); - } - if constexpr (features::Debug) { - if (atomic_load(&input.config_seen_generation) < atomic_load(input._config_generation)) { - BONGOCAT_LOG_VERBOSE("input: input.config_seen_generation < input._config_generation; %d < %d", atomic_load(&input.config_seen_generation), atomic_load(input._config_generation)); - } - } - //assert(atomic_load(&input.config_seen_generation) >= atomic_load(input._config_generation)); - atomic_store(&input.config_seen_generation, atomic_load(input._config_generation)); - BONGOCAT_LOG_INFO("input: Input config reloaded (gen=%u)", new_gen); + } + + if (got_key) { + trigger_key_press(trigger_ctx); + if (enable_debug) { + size_t len = (rd > 0) ? (static_cast(rd) < TEST_STDIN_BUF_LEN ? static_cast(rd) + : TEST_STDIN_BUF_LEN - 1) + : 0; + buf[len] = '\0'; + BONGOCAT_LOG_VERBOSE("input: stdin input: %s", buf); } + } } - - close_fd(tty_fd); - - atomic_store(&input._capture_input_running, false); - if (track_valid_devices == 0) { - BONGOCAT_LOG_ERROR("input: All input devices are unavailable"); - } - - // Will run only on normal return - pthread_cleanup_pop(1); // 1 = call cleanup even if not canceled - - // done when callback cleanup_input_thread - //cleanup_input_thread_context(arg); - // sanity check for clean up - assert(input._device_paths == nullptr); - assert(input._unique_devices == nullptr); - assert(input._unique_paths_indices == nullptr); - assert(input._unique_paths_indices_capacity == 0); - - BONGOCAT_LOG_INFO("Input monitoring stopped"); - - return nullptr; + } } - created_result_t> create(const config::config_t& config) { - AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - if (config.num_keyboard_devices <= 0) { - BONGOCAT_LOG_WARNING("No input devices specified"); - } - - const timestamp_ms_t now = get_current_time_ms(); - ret->_capture_input_running = false; - ret->_input_kpm_counter = 0; - ret->_latest_kpm_update_ms = now; - - BONGOCAT_LOG_INFO("Initializing input monitoring system for %d devices", config.num_keyboard_devices); - - // Initialize shared memory for key press flag - ret->shm = make_allocated_mmap(); - if (!ret->shm.ptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - ret->shm->any_key_pressed = 0; - ret->shm->hand_mapping = input_hand_mapping_t::None; - ret->shm->kpm = 0; - ret->shm->input_counter = 0; - ret->shm->last_key_pressed_timestamp = 0; - - // Initialize shared memory for local config - ret->_local_copy_config = make_allocated_mmap(); - if (!ret->_local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(ret->_local_copy_config != nullptr); - *ret->_local_copy_config = config; - - ret->update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->update_config_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for input update config: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + // Revalidate valid devices + if (sync_devices_needed) { + platform::LockGuard guard(input.input_lock); + + auto [sync_devices_result, revalid_devices_result] = + sync_devices(input, {.reload_devices_needed = reload_devices_needed}); + if (revalid_devices_result != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("input: Failed to revalidate devices and file descriptors"); + } else { + if (sync_devices_result.valid_devices == 0) { + BONGOCAT_LOG_VERBOSE("input: All input devices became unavailable"); } - - BONGOCAT_LOG_INFO("Input monitoring started"); - return ret; + } + track_valid_devices = sync_devices_result.valid_devices; } - bongocat_error_t start(input_context_t& input, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { - if (config.num_keyboard_devices < 0) { - BONGOCAT_LOG_ERROR("No input devices specified"); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } else if (config.num_keyboard_devices == 0) { - BONGOCAT_LOG_WARNING("No input devices specified"); + // handle update config + if (reload_config) { + assert(input._config_generation != nullptr); + assert(input._configs_reloaded_cond != nullptr); + assert(input._config != nullptr); + + update_config(input, *input._config, new_gen); + + // wait for reload config to be done (all configs) + const int rc = input._configs_reloaded_cond->timedwait( + [&] { return atomic_load(input._config_generation) >= new_gen; }, COND_RELOAD_CONFIGS_TIMEOUT_MS); + if (rc == ETIMEDOUT) { + BONGOCAT_LOG_WARNING("input: Timed out waiting for reload eventfd: %s", strerror(errno)); + } + if constexpr (features::Debug) { + if (atomic_load(&input.config_seen_generation) < atomic_load(input._config_generation)) { + BONGOCAT_LOG_VERBOSE("input: input.config_seen_generation < input._config_generation; %d < %d", + atomic_load(&input.config_seen_generation), atomic_load(input._config_generation)); } + } + // assert(atomic_load(&input.config_seen_generation) >= atomic_load(input._config_generation)); + atomic_store(&input.config_seen_generation, atomic_load(input._config_generation)); + BONGOCAT_LOG_INFO("input: Input config reloaded (gen=%u)", new_gen); + } + } - const timestamp_ms_t now = get_current_time_ms(); - input._latest_kpm_update_ms = now; + close_fd(tty_fd); - BONGOCAT_LOG_INFO("Initializing input monitoring system for %d devices", config.num_keyboard_devices); + atomic_store(&input._capture_input_running, false); + if (track_valid_devices == 0) { + BONGOCAT_LOG_ERROR("input: All input devices are unavailable"); + } - // Initialize shared memory for key press flag - input.shm = make_allocated_mmap(); - if (!input.shm.ptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - input.shm->any_key_pressed = 0; - input.shm->hand_mapping = input_hand_mapping_t::None; - input.shm->kpm = 0; - input.shm->input_counter = 0; - input.shm->last_key_pressed_timestamp = now; // for idle check timestamp should be zero - - // Initialize shared memory for local config - input._local_copy_config = make_allocated_mmap(); - if (!input._local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(input._local_copy_config != nullptr); - update_config(input, config, atomic_load(&config_generation)); - - // wait for animation trigger to be ready (input should be the same) - int cond_ret = trigger_ctx.init_cond.timedwait([&]() { - return atomic_load(&trigger_ctx.ready); - }, COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - if (cond_ret == ETIMEDOUT) { - BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); - } else { - assert(trigger_ctx._input == &input); - } - // set extern/global references - { - // guard for anim_update_state - LockGuard guard (trigger_ctx.anim.anim_lock); - trigger_ctx._input = &input; - } - trigger_ctx.init_cond.notify_all(); - input._config = &config; - input._configs_reloaded_cond = &configs_reloaded_cond; - input._config_generation = &config_generation; - atomic_store(&input.ready, true); - input.init_cond.notify_all(); - - input._configs_reloaded_cond->notify_all(); - - // start input monitoring thread - const int result = pthread_create(&input._input_thread, nullptr, input_thread, &trigger_ctx); - if (result != 0) { - BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; - } + // Will run only on normal return + pthread_cleanup_pop(1); // 1 = call cleanup even if not canceled - BONGOCAT_LOG_INFO("Input monitoring started"); - return bongocat_error_t::BONGOCAT_SUCCESS; - } + // done when callback cleanup_input_thread + // cleanup_input_thread_context(arg); + // sanity check for clean up + assert(input._device_paths == nullptr); + assert(input._unique_devices == nullptr); + assert(input._unique_paths_indices == nullptr); + assert(input._unique_paths_indices_capacity == 0); - bongocat_error_t restart(input_context_t& input, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { - BONGOCAT_LOG_INFO("Restarting input monitoring system"); - // Stop current monitoring - if (input._input_thread) { - BONGOCAT_LOG_DEBUG("Input monitoring thread"); - atomic_store(&input._capture_input_running, false); - //pthread_cancel(ctx->_input_thread); - if (stop_thread_graceful_or_cancel(input._input_thread, input._capture_input_running) != 0) { - BONGOCAT_LOG_ERROR("Failed to join input thread: %s", strerror(errno)); - } - BONGOCAT_LOG_DEBUG("Input monitoring thread terminated"); - } + BONGOCAT_LOG_INFO("Input monitoring stopped"); - // already done when stop current input thread - //cleanup_input_thread_context(ctx); - assert(!input._device_paths); - assert(!input._unique_paths_indices); - assert(!input._unique_devices); - assert(input._unique_paths_indices_capacity == 0); - - // reset stats - //ret._latest_kpm_update_ms = get_current_time_ms(); - - /// @TODO: re-create context with create(), avoid duplicate code - - // Start new monitoring (reuse shared memory if it exists) - { - LockGuard guard (input.input_lock); - if (input.shm == nullptr) { - input.shm = make_allocated_mmap(); - if (input.shm.ptr == MAP_FAILED) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - } - } + return nullptr; +} - if (!input._local_copy_config) { - input._local_copy_config = make_unallocated_mmap_value(config); - if (!input._local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - } - assert(input._local_copy_config != nullptr); - update_config(input, config, atomic_load(&config_generation)); - - if (input.update_config_efd._fd < 0) { - input.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (input.update_config_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for input, update config: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - } +created_result_t> create(const config::config_t& config) { + AllocatedMemory ret = make_allocated_memory(); + assert(ret != nullptr); + if (ret == nullptr) { + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + if (config.num_keyboard_devices <= 0) { + BONGOCAT_LOG_WARNING("No input devices specified"); + } + + const timestamp_ms_t now = get_current_time_ms(); + ret->_capture_input_running = false; + ret->_input_kpm_counter = 0; + ret->_latest_kpm_update_ms = now; + + BONGOCAT_LOG_INFO("Initializing input monitoring system for %d devices", config.num_keyboard_devices); + + // Initialize shared memory for key press flag + ret->shm = make_allocated_mmap(); + if (!ret->shm.ptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + ret->shm->any_key_pressed = 0; + ret->shm->hand_mapping = input_hand_mapping_t::None; + ret->shm->kpm = 0; + ret->shm->input_counter = 0; + ret->shm->last_key_pressed_timestamp = 0; + + // Initialize shared memory for local config + ret->_local_copy_config = make_allocated_mmap(); + if (!ret->_local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(ret->_local_copy_config != nullptr); + *ret->_local_copy_config = config; + + ret->update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->update_config_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for input update config: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + BONGOCAT_LOG_INFO("Input monitoring started"); + return ret; +} - //if (trigger_ctx._input != ctx._input) { - // BONGOCAT_LOG_DEBUG("Input context in animation differs from animation trigger input context"); - //} +bongocat_error_t start(input_context_t& input, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation) { + if (config.num_keyboard_devices < 0) { + BONGOCAT_LOG_ERROR("No input devices specified"); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } else if (config.num_keyboard_devices == 0) { + BONGOCAT_LOG_WARNING("No input devices specified"); + } + + const timestamp_ms_t now = get_current_time_ms(); + input._latest_kpm_update_ms = now; + + BONGOCAT_LOG_INFO("Initializing input monitoring system for %d devices", config.num_keyboard_devices); + + // Initialize shared memory for key press flag + input.shm = make_allocated_mmap(); + if (!input.shm.ptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + input.shm->any_key_pressed = 0; + input.shm->hand_mapping = input_hand_mapping_t::None; + input.shm->kpm = 0; + input.shm->input_counter = 0; + input.shm->last_key_pressed_timestamp = now; // for idle check timestamp should be zero + + // Initialize shared memory for local config + input._local_copy_config = make_allocated_mmap(); + if (!input._local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(input._local_copy_config != nullptr); + update_config(input, config, atomic_load(&config_generation)); + + // wait for animation trigger to be ready (input should be the same) + int cond_ret = trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + if (cond_ret == ETIMEDOUT) { + BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); + } else { + assert(trigger_ctx._input == &input); + } + // set extern/global references + { + // guard for anim_update_state + LockGuard guard(trigger_ctx.anim.anim_lock); + trigger_ctx._input = &input; + } + trigger_ctx.init_cond.notify_all(); + input._config = &config; + input._configs_reloaded_cond = &configs_reloaded_cond; + input._config_generation = &config_generation; + atomic_store(&input.ready, true); + input.init_cond.notify_all(); + + input._configs_reloaded_cond->notify_all(); + + // start input monitoring thread + const int result = pthread_create(&input._input_thread, nullptr, input_thread, &trigger_ctx); + if (result != 0) { + BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + BONGOCAT_LOG_INFO("Input monitoring started"); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - // set extern/global references - { - // guard for anim_update_state - LockGuard guard (trigger_ctx.anim.anim_lock); - trigger_ctx._input = &input; - } - input._config = &config; - input._configs_reloaded_cond = &configs_reloaded_cond; - input._config_generation = &config_generation; - atomic_store(&input.config_seen_generation, atomic_load(&config_generation)); - input.init_cond.notify_all(); - input._configs_reloaded_cond->notify_all(); - - // start input monitoring - const int result = pthread_create(&input._input_thread, nullptr, input_thread, &trigger_ctx); - if (result != 0) { - BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; - } +bongocat_error_t restart(input_context_t& input, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation) { + BONGOCAT_LOG_INFO("Restarting input monitoring system"); + // Stop current monitoring + if (input._input_thread) { + BONGOCAT_LOG_DEBUG("Input monitoring thread"); + atomic_store(&input._capture_input_running, false); + // pthread_cancel(ctx->_input_thread); + if (stop_thread_graceful_or_cancel(input._input_thread, input._capture_input_running) != 0) { + BONGOCAT_LOG_ERROR("Failed to join input thread: %s", strerror(errno)); + } + BONGOCAT_LOG_DEBUG("Input monitoring thread terminated"); + } + + // already done when stop current input thread + // cleanup_input_thread_context(ctx); + assert(!input._device_paths); + assert(!input._unique_paths_indices); + assert(!input._unique_devices); + assert(input._unique_paths_indices_capacity == 0); + + // reset stats + // ret._latest_kpm_update_ms = get_current_time_ms(); + + /// @TODO: re-create context with create(), avoid duplicate code + + // Start new monitoring (reuse shared memory if it exists) + { + LockGuard guard(input.input_lock); + if (input.shm == nullptr) { + input.shm = make_allocated_mmap(); + if (input.shm.ptr == MAP_FAILED) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + } + } - BONGOCAT_LOG_INFO("Input monitoring restarted"); - return bongocat_error_t::BONGOCAT_SUCCESS; + if (!input._local_copy_config) { + input._local_copy_config = make_unallocated_mmap_value(config); + if (!input._local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } + } + assert(input._local_copy_config != nullptr); + update_config(input, config, atomic_load(&config_generation)); + + if (input.update_config_efd._fd < 0) { + input.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (input.update_config_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for input, update config: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + } + + // if (trigger_ctx._input != ctx._input) { + // BONGOCAT_LOG_DEBUG("Input context in animation differs from animation trigger input context"); + // } + + // set extern/global references + { + // guard for anim_update_state + LockGuard guard(trigger_ctx.anim.anim_lock); + trigger_ctx._input = &input; + } + input._config = &config; + input._configs_reloaded_cond = &configs_reloaded_cond; + input._config_generation = &config_generation; + atomic_store(&input.config_seen_generation, atomic_load(&config_generation)); + input.init_cond.notify_all(); + input._configs_reloaded_cond->notify_all(); + + // start input monitoring + const int result = pthread_create(&input._input_thread, nullptr, input_thread, &trigger_ctx); + if (result != 0) { + BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + BONGOCAT_LOG_INFO("Input monitoring restarted"); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - void stop(input_context_t& ctx) { - atomic_store(&ctx._capture_input_running, false); - if (ctx._input_thread) { - BONGOCAT_LOG_DEBUG("Stopping input thread"); - //pthread_cancel(ctx->_input_thread); - if (stop_thread_graceful_or_cancel(ctx._input_thread, ctx._capture_input_running) != 0) { - BONGOCAT_LOG_ERROR("Failed to join input thread: %s", strerror(errno)); - } - BONGOCAT_LOG_DEBUG("Input monitoring thread terminated"); - } - ctx._input_thread = 0; +void stop(input_context_t& ctx) { + atomic_store(&ctx._capture_input_running, false); + if (ctx._input_thread) { + BONGOCAT_LOG_DEBUG("Stopping input thread"); + // pthread_cancel(ctx->_input_thread); + if (stop_thread_graceful_or_cancel(ctx._input_thread, ctx._capture_input_running) != 0) { + BONGOCAT_LOG_ERROR("Failed to join input thread: %s", strerror(errno)); + } + BONGOCAT_LOG_DEBUG("Input monitoring thread terminated"); + } + ctx._input_thread = 0; - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; + ctx._config = nullptr; + ctx._configs_reloaded_cond = nullptr; + ctx._config_generation = nullptr; - ctx.config_updated.notify_all(); - atomic_store(&ctx.ready, false); - ctx.init_cond.notify_all(); - } + ctx.config_updated.notify_all(); + atomic_store(&ctx.ready, false); + ctx.init_cond.notify_all(); +} - void trigger_update_config(input_context_t& input, const config::config_t& config, uint64_t config_generation) { - //assert(input.anim._local_copy_config != nullptr); - //assert(input.anim.shm != nullptr); +void trigger_update_config(input_context_t& input, const config::config_t& config, uint64_t config_generation) { + // assert(input.anim._local_copy_config != nullptr); + // assert(input.anim.shm != nullptr); - input._config = &config; - if (write(input.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { - BONGOCAT_LOG_VERBOSE("Write input trigger update config"); - } else { - BONGOCAT_LOG_ERROR("Failed to write to notify pipe in input: %s", strerror(errno)); - } - } + input._config = &config; + if (write(input.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { + BONGOCAT_LOG_VERBOSE("Write input trigger update config"); + } else { + BONGOCAT_LOG_ERROR("Failed to write to notify pipe in input: %s", strerror(errno)); + } +} - void update_config(input_context_t& input, const config::config_t& config, uint64_t new_gen) { - assert(input._local_copy_config != nullptr); +void update_config(input_context_t& input, const config::config_t& config, uint64_t new_gen) { + assert(input._local_copy_config != nullptr); - *input._local_copy_config = config; + *input._local_copy_config = config; - atomic_store(&input.config_seen_generation, new_gen); - // Signal main that reload is done - input.config_updated.notify_all(); - } + atomic_store(&input.config_seen_generation, new_gen); + // Signal main that reload is done + input.config_updated.notify_all(); } +} // namespace bongocat::platform::input diff --git a/src/platform/update.cpp b/src/platform/update.cpp index f0d244aa..0f9d76eb 100644 --- a/src/platform/update.cpp +++ b/src/platform/update.cpp @@ -1,692 +1,715 @@ #include "platform/update.h" + #include "graphics/animation.h" -#include "utils/memory.h" #include "platform/wayland.h" -#include -#include -#include -#include -#include -#include -#include +#include "utils/memory.h" + #include #include +#include #include +#include #include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include namespace bongocat::platform::update { - inline static constexpr int MAX_ATTEMPTS = 2048; - inline static constexpr size_t GET_CPU_PRESENT_LAST_BUF = 256; - inline static constexpr size_t CPU_INFO_BUF = 8192; +inline static constexpr int MAX_ATTEMPTS = 2048; +inline static constexpr size_t GET_CPU_PRESENT_LAST_BUF = 256; +inline static constexpr size_t CPU_INFO_BUF = 8192; - static inline constexpr auto UPDATE_POOL_TIMEOUT_MS = 1000; - static inline constexpr auto COND_STORED_TIMEOUT_MS = 1000; +static inline constexpr auto UPDATE_POOL_TIMEOUT_MS = 1000; +static inline constexpr auto COND_STORED_TIMEOUT_MS = 1000; - inline static constexpr time_ms_t COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS = 5000; - inline static constexpr time_ms_t COND_RELOAD_CONFIGS_TIMEOUT_MS = 5000; +inline static constexpr time_ms_t COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS = 5000; +inline static constexpr time_ms_t COND_RELOAD_CONFIGS_TIMEOUT_MS = 5000; - inline static constexpr const char* FILENAME_CPU_PRESET = "/sys/devices/system/cpu/present"; - inline static constexpr const char* FILENAME_PROC_STAT = "/proc/stat"; +inline static constexpr const char *FILENAME_CPU_PRESET = "/sys/devices/system/cpu/present"; +inline static constexpr const char *FILENAME_PROC_STAT = "/proc/stat"; - static void cleanup_update_thread(void* arg) { - assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); - assert(trigger_ctx._update); - update_context_t& input = *trigger_ctx._update; +static void cleanup_update_thread(void *arg) { + assert(arg); + animation::animation_session_t& trigger_ctx = *static_cast(arg); + assert(trigger_ctx._update); + update_context_t& input = *trigger_ctx._update; - atomic_store(&input._running, false); + atomic_store(&input._running, false); - input.config_updated.notify_all(); + input.config_updated.notify_all(); - BONGOCAT_LOG_INFO("Update thread cleanup completed (via pthread_cancel)"); - } + BONGOCAT_LOG_INFO("Update thread cleanup completed (via pthread_cancel)"); +} +static int set_nonblocking(int fd) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + return -1; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +} - static int set_nonblocking(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) return -1; - return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +/* +static size_t get_cpu_present_last(int fd) { + lseek(fd, 0, SEEK_SET); + char buf[GET_CPU_PRESENT_LAST_BUF] = {0}; + ssize_t len = read(fd, buf, sizeof(buf)-1); + if (len <= 0) return 0; + assert(len >= 0 && static_cast(len) < GET_CPU_PRESENT_LAST_BUF); + buf[len] = '\0'; + + const char* last_sep = strpbrk(buf, "-,"); + if (last_sep) { + return strtoul(last_sep + 1, nullptr, 10); } + return 0; +} +*/ + +static const cpu_snapshot_t& get_latest_snapshot_unlocked(update_context_t& ctx) { + assert(ctx.shm != nullptr); + auto& update_shm = *ctx.shm; + ctx.update_cond.timedwait([&]() { return update_shm.cpu_snapshots.stored > 0; }, COND_STORED_TIMEOUT_MS); + assert(CpuSnapshotRingBufferMaxHistory > 0); + const size_t latest = + (update_shm.cpu_snapshots.head + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; + assert(latest < CpuSnapshotRingBufferMaxHistory); + return update_shm.cpu_snapshots.history[latest]; +} - /* - static size_t get_cpu_present_last(int fd) { - lseek(fd, 0, SEEK_SET); - char buf[GET_CPU_PRESENT_LAST_BUF] = {0}; - ssize_t len = read(fd, buf, sizeof(buf)-1); - if (len <= 0) return 0; - assert(len >= 0 && static_cast(len) < GET_CPU_PRESENT_LAST_BUF); - buf[len] = '\0'; - - const char* last_sep = strpbrk(buf, "-,"); - if (last_sep) { - return strtoul(last_sep + 1, nullptr, 10); - } - return 0; - } - */ - - static const cpu_snapshot_t& get_latest_snapshot_unlocked(update_context_t& ctx) { - assert(ctx.shm != nullptr); - auto& update_shm = *ctx.shm; - ctx.update_cond.timedwait([&]() { - return update_shm.cpu_snapshots.stored > 0; - }, COND_STORED_TIMEOUT_MS); - assert(CpuSnapshotRingBufferMaxHistory > 0); - const size_t latest = (update_shm.cpu_snapshots.head + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; - assert(latest < CpuSnapshotRingBufferMaxHistory); - return update_shm.cpu_snapshots.history[latest]; - } +const cpu_snapshot_t& get_latest_snapshot(update_context_t& ctx) { + LockGuard guard(ctx.update_lock); + return get_latest_snapshot_unlocked(ctx); +} - const cpu_snapshot_t& get_latest_snapshot(update_context_t& ctx) { - LockGuard guard (ctx.update_lock); - return get_latest_snapshot_unlocked(ctx); +static size_t parse_cpuinfo_fd(int fd, size_t cpu_present_last, cpu_stat_t *out, size_t out_size) { + lseek(fd, 0, SEEK_SET); + char buf[CPU_INFO_BUF] = {0}; + const ssize_t len = read(fd, buf, sizeof(buf) - 1); + if (len <= 0) + return 0; + assert(len >= 0 && static_cast(len) < CPU_INFO_BUF); + buf[len] = '\0'; + + ssize_t current_cpu_number = -1; + size_t used = 0; + + char *saveptr = nullptr; + char *line = strtok_r(buf, "\n", &saveptr); + while (line) { + if (strncmp(line, "cpu", 3) != 0) + break; + + size_t line_cpu_number = 0; + if (current_cpu_number >= 0) { + line_cpu_number = strtoul(line + 3, nullptr, 10); + assert(current_cpu_number >= 0); + // assert(current_cpu_number <= SIZE_MAX); + while (line_cpu_number > static_cast(current_cpu_number) && used < out_size) { + out[used] = {}; + used++; + current_cpu_number++; + } } - static size_t parse_cpuinfo_fd(int fd, size_t cpu_present_last, cpu_stat_t* out, size_t out_size) { - lseek(fd, 0, SEEK_SET); - char buf[CPU_INFO_BUF] = {0}; - const ssize_t len = read(fd, buf, sizeof(buf)-1); - if (len <= 0) return 0; - assert(len >= 0 && static_cast(len) < CPU_INFO_BUF); - buf[len] = '\0'; - - ssize_t current_cpu_number = -1; - size_t used = 0; - - char* saveptr = nullptr; - char* line = strtok_r(buf, "\n", &saveptr); - while (line) { - if (strncmp(line, "cpu", 3) != 0) break; - - size_t line_cpu_number = 0; - if (current_cpu_number >= 0) { - line_cpu_number = strtoul(line + 3, nullptr, 10); - assert(current_cpu_number >= 0); - //assert(current_cpu_number <= SIZE_MAX); - while (line_cpu_number > static_cast(current_cpu_number) && used < out_size) { - out[used] = {}; - used++; - current_cpu_number++; - } - } - - char* p = line; - while (*p && !isspace(static_cast(*p))) p++; - while (*p && isspace(static_cast(*p))) p++; - - constexpr size_t times_size = 16; - size_t times[times_size] = {0}; - size_t times_count = 0; - char* tok = strtok(p, " \t"); - while (tok && times_count < times_size) { - times[times_count++] = strtoull(tok, nullptr, 10); - tok = strtok(nullptr, " \t"); - } - - size_t idle_time = 0; - size_t total_time = 0; - assert(times_size >= 5); - if (times_count >= 5) { - idle_time = times[3] + times[4]; - for (size_t i = 0; i < times_count; i++) total_time += times[i]; - } - - if (used < MaxCpus) { - out[used] = cpu_stat_t{.idle_time = idle_time, .total_time = total_time}; - used++; - } - current_cpu_number++; - - line = strtok_r(nullptr, "\n", &saveptr); - } - - if (current_cpu_number >= 0) { - assert(current_cpu_number >= 0); - //assert(current_cpu_number <= SIZE_MAX); - while (static_cast(current_cpu_number) <= cpu_present_last && used < out_size) { - out[used] = {}; - used++; - current_cpu_number++; - } - } - - return used; + char *p = line; + while (*p && !isspace(static_cast(*p))) + p++; + while (*p && isspace(static_cast(*p))) + p++; + + constexpr size_t times_size = 16; + size_t times[times_size] = {0}; + size_t times_count = 0; + char *tok = strtok(p, " \t"); + while (tok && times_count < times_size) { + times[times_count++] = strtoull(tok, nullptr, 10); + tok = strtok(nullptr, " \t"); } - static double compute_avg_cpu_usage(const cpu_snapshot_t& prev, const cpu_snapshot_t& curr) { - assert(curr.count == prev.count); // both snapshots must have same CPU count - - size_t total_delta = 0; - size_t idle_delta = 0; - for (size_t i = 0; i < curr.count; i++) { - const cpu_stat_t& p = prev.stats[i]; - const cpu_stat_t& c = curr.stats[i]; - - const size_t d_total = (c.total_time > p.total_time) ? (c.total_time - p.total_time) : 0; - const size_t d_idle = (c.idle_time > p.idle_time) ? (c.idle_time - p.idle_time) : 0; - - total_delta += d_total; - idle_delta += d_idle; - } - - if (total_delta == 0) return 0.0; - return 100.0 * static_cast(total_delta - idle_delta) / static_cast(total_delta); + size_t idle_time = 0; + size_t total_time = 0; + assert(times_size >= 5); + if (times_count >= 5) { + idle_time = times[3] + times[4]; + for (size_t i = 0; i < times_count; i++) + total_time += times[i]; } - static double compute_max_cpu_usage(const cpu_snapshot_t& prev, const cpu_snapshot_t& curr) { - assert(curr.count == prev.count); // both snapshots must have same CPU count - - double max_usage = 0.0; - - for (size_t i = 0; i < curr.count; i++) { - const cpu_stat_t* p = &prev.stats[i]; - const cpu_stat_t* c = &curr.stats[i]; - - const size_t d_total = (c->total_time > p->total_time) ? (c->total_time - p->total_time) : 0; - const size_t d_idle = (c->idle_time > p->idle_time) ? (c->idle_time - p->idle_time) : 0; - if (d_total == 0) { - continue; // skip if no change - } - - const double usage = 100.0 * static_cast(d_total - d_idle) / static_cast(d_total); - if (usage > max_usage) { - max_usage = usage; - } - } - - return max_usage; + if (used < MaxCpus) { + out[used] = cpu_stat_t{.idle_time = idle_time, .total_time = total_time}; + used++; + } + current_cpu_number++; + + line = strtok_r(nullptr, "\n", &saveptr); + } + + if (current_cpu_number >= 0) { + assert(current_cpu_number >= 0); + // assert(current_cpu_number <= SIZE_MAX); + while (static_cast(current_cpu_number) <= cpu_present_last && used < out_size) { + out[used] = {}; + used++; + current_cpu_number++; } + } - static void* update_thread(void* arg) { - assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); - - // from thread context - //animation_context_t& anim = trigger_ctx.anim; - // wait for input context (in animation start) - trigger_ctx.init_cond.timedwait([&]() { - return atomic_load(&trigger_ctx.ready); - }, COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - assert(trigger_ctx._input != nullptr); - update_context_t& upd = *trigger_ctx._update; - - // sanity checks - assert(upd._config != nullptr); - assert(upd._configs_reloaded_cond != nullptr); - assert(!upd._running); - assert(upd.shm != nullptr); - assert(upd._local_copy_config != nullptr); - assert(upd.update_config_efd._fd >= 0); - assert(upd.fd_present._fd >= 0); - assert(upd.fd_stat._fd >= 0); - - // trigger initial render - wayland::request_render(trigger_ctx); - - pthread_cleanup_push(cleanup_update_thread, arg); - - // local thread context - /// event poll - // 0: reload config event - // 1: fd_stat - // 2: fd_present - constexpr size_t nfds = 3; - pollfd pfds[nfds]; - - atomic_store(&upd._running, true); - while (atomic_load(&upd._running)) { - pthread_testcancel(); // optional, but makes cancellation more responsive - - // read from config - time_ms_t timeout = UPDATE_POOL_TIMEOUT_MS; - time_ms_t update_rate_ms = 0; - time_ms_t animation_speed_ms = 0; - double cpu_threshold = 0; - int fps = 0; - //bool enable_debug = false; - { - // read-only config - assert(upd._local_copy_config != nullptr); - const config::config_t& current_config = *upd._local_copy_config; - - //enable_debug = current_config.enable_debug; - - cpu_threshold = current_config.cpu_threshold; - update_rate_ms = current_config.update_rate_ms; - animation_speed_ms = current_config.animation_speed_ms; - fps = current_config.fps; - if (update_rate_ms > 0) { - timeout = update_rate_ms; - } else if (animation_speed_ms > 0) { - timeout = animation_speed_ms; - } else if (current_config.fps > 0) { - timeout = 1000 / current_config.fps / 2; - } - } + return used; +} - // init pfds - constexpr size_t fds_update_config_index = 0; - constexpr size_t fds_stat_index = 1; - constexpr size_t fds_preset_index = 2; - pfds[fds_update_config_index] = { .fd = upd.update_config_efd._fd, .events = POLLIN, .revents = 0 }; - pfds[fds_stat_index] = { .fd = upd.fd_stat._fd, .events = POLLIN, .revents = 0 }; - pfds[fds_preset_index] = { .fd = upd.fd_present._fd, .events = POLLIN, .revents = 0 }; - - // poll events - const int poll_result = poll(pfds, nfds, static_cast(timeout)); - if (poll_result < 0) { - if (errno == EINTR) continue; // Interrupted by signal - BONGOCAT_LOG_ERROR("update: Poll error: %s", strerror(errno)); - break; - } +static double compute_avg_cpu_usage(const cpu_snapshot_t& prev, const cpu_snapshot_t& curr) { + assert(curr.count == prev.count); // both snapshots must have same CPU count - // cancel pooling (when not running anymore) - if (!atomic_load(&upd._running)) { - // draining pools - for (size_t i = 0;i < nfds;i++) { - if (pfds[i].revents & POLLIN) { - drain_event(pfds[i], MAX_ATTEMPTS); - } - } - break; - } + size_t total_delta = 0; + size_t idle_delta = 0; + for (size_t i = 0; i < curr.count; i++) { + const cpu_stat_t& p = prev.stats[i]; + const cpu_stat_t& c = curr.stats[i]; - // Handle config update - assert(upd._config_generation != nullptr); - bool reload_config = false; - uint64_t new_gen{atomic_load(upd._config_generation)}; - if (pfds[fds_update_config_index].revents & POLLIN) { - BONGOCAT_LOG_DEBUG("update: Receive update config event"); - drain_event(pfds[fds_update_config_index], MAX_ATTEMPTS, "update config eventfd"); - reload_config = new_gen > 0; - } + const size_t d_total = (c.total_time > p.total_time) ? (c.total_time - p.total_time) : 0; + const size_t d_idle = (c.idle_time > p.idle_time) ? (c.idle_time - p.idle_time) : 0; - // Handle stat - if (pfds[fds_stat_index].revents & POLLIN) { - BONGOCAT_LOG_VERBOSE("update: Receive update CPU stats event"); - drain_event(pfds[fds_stat_index], MAX_ATTEMPTS, FILENAME_PROC_STAT); - - if (update_rate_ms > 0) { - platform::LockGuard guard (upd.update_lock); - assert(upd.shm != nullptr); - auto& update_shm = *upd.shm; - - const size_t count = parse_cpuinfo_fd(upd.fd_stat._fd, 0, update_shm.cpu_snapshots.history[update_shm.cpu_snapshots.head].stats, MaxCpus); - update_shm.cpu_snapshots.history[update_shm.cpu_snapshots.head].count = count; - - { - LockGuard guard_cond (upd.update_cond._mutex); - update_shm.cpu_snapshots.head = (update_shm.cpu_snapshots.head + 1) % CpuSnapshotRingBufferMaxHistory; - if (update_shm.cpu_snapshots.stored < CpuSnapshotRingBufferMaxHistory) update_shm.cpu_snapshots.stored++; - pthread_cond_broadcast(&upd.update_cond._cond); - } - } - } + total_delta += d_total; + idle_delta += d_idle; + } - // cpu_present file changed - if (pfds[fds_preset_index].revents & POLLIN) { - BONGOCAT_LOG_VERBOSE("update: Receive update CPU present event"); - drain_event(pfds[fds_preset_index], MAX_ATTEMPTS, FILENAME_CPU_PRESET); + if (total_delta == 0) + return 0.0; + return 100.0 * static_cast(total_delta - idle_delta) / static_cast(total_delta); +} - if (update_rate_ms > 0) { - BONGOCAT_LOG_VERBOSE("update: cpu_present file changed (hotplug)"); - } - } +static double compute_max_cpu_usage(const cpu_snapshot_t& prev, const cpu_snapshot_t& curr) { + assert(curr.count == prev.count); // both snapshots must have same CPU count - // trigger animation - bool animation_triggered = false; - if (update_rate_ms > 0) { - LockGuard guard (upd.update_lock); - assert(upd.shm != nullptr); - auto& update_shm = *upd.shm; - - // read-only config - assert(upd._local_copy_config != nullptr); - const config::config_t& current_config = *upd._local_copy_config; - - update_shm.latest_snapshot = &get_latest_snapshot_unlocked(upd); - - if (update_shm.cpu_snapshots.stored >= 2) { - const size_t latest = (update_shm.cpu_snapshots.head + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; - const size_t prev_i = (latest + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; - - const cpu_snapshot_t& curr = update_shm.cpu_snapshots.history[latest]; - const cpu_snapshot_t& prev = update_shm.cpu_snapshots.history[prev_i]; - - update_shm.avg_cpu_usage = compute_avg_cpu_usage(prev, curr); - update_shm.max_cpu_usage = compute_max_cpu_usage(prev, curr); - - const bool above_threshold = current_config.cpu_threshold >= ENABLED_MIN_CPU_PERCENT && (update_shm.avg_cpu_usage >= current_config.cpu_threshold || update_shm.max_cpu_usage >= current_config.cpu_threshold); - const bool lower_threshold = current_config.cpu_threshold >= ENABLED_MIN_CPU_PERCENT && (update_shm.last_avg_cpu_usage >= current_config.cpu_threshold || update_shm.last_max_cpu_usage >= current_config.cpu_threshold) && (update_shm.avg_cpu_usage < current_config.cpu_threshold || update_shm.max_cpu_usage < current_config.cpu_threshold); - const bool crossed_delta = fabs(update_shm.avg_cpu_usage - update_shm.last_avg_cpu_usage) >= TRIGGER_ANIMATION_CPU_DIFF_PERCENT || fabs(update_shm.max_cpu_usage - update_shm.last_max_cpu_usage) >= TRIGGER_ANIMATION_CPU_DIFF_PERCENT; - if (above_threshold || lower_threshold) { - if (!update_shm.cpu_active || crossed_delta) { - BONGOCAT_LOG_VERBOSE("update: avg. CPU: %.2f (max: %.2f)", update_shm.avg_cpu_usage, update_shm.max_cpu_usage); - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); - if (lower_threshold) { - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); - } - animation_triggered = true; - } - update_shm.cpu_active = true; - } else { - if (update_shm.cpu_active) { - update_shm.cpu_active = false; - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); - } - } - } - - update_shm.last_avg_cpu_usage = update_shm.avg_cpu_usage; - update_shm.last_max_cpu_usage = update_shm.max_cpu_usage; - } + double max_usage = 0.0; - // handle update config - if (reload_config) { - assert(upd._config_generation != nullptr); - assert(upd._configs_reloaded_cond != nullptr); - assert(upd._config != nullptr); - - update_config(upd, *upd._config, new_gen); - - // wait for reload config to be done (all configs) - const int rc = upd._configs_reloaded_cond->timedwait([&] { - return atomic_load(upd._config_generation) >= new_gen; - }, COND_RELOAD_CONFIGS_TIMEOUT_MS); - if (rc == ETIMEDOUT) { - BONGOCAT_LOG_WARNING("update: Timed out waiting for reload eventfd: %s", strerror(errno)); - } - if constexpr (features::Debug) { - if (atomic_load(&upd.config_seen_generation) < atomic_load(upd._config_generation)) { - BONGOCAT_LOG_VERBOSE("update: update.config_seen_generation < update._config_generation; %d < %d", atomic_load(&upd.config_seen_generation), atomic_load(upd._config_generation)); - } - } - //assert(atomic_load(&upd.config_seen_generation) >= atomic_load(upd._config_generation)); - atomic_store(&upd.config_seen_generation, atomic_load(upd._config_generation)); - BONGOCAT_LOG_INFO("update: Update config reloaded (gen=%u)", new_gen); - } + for (size_t i = 0; i < curr.count; i++) { + const cpu_stat_t *p = &prev.stats[i]; + const cpu_stat_t *c = &curr.stats[i]; - if (update_rate_ms) { - // sleep - timespec ts; - ts.tv_sec = 0; - assert(fps > 0); - if (animation_triggered && animation_speed_ms > 0) { - // when animation were triggered, wait for animation to end (assumption), before triggering a possible new animation - ts.tv_nsec = animation_speed_ms * 1000 * 1000; - } else if (animation_triggered) { - ts.tv_nsec = (1000 / fps) * 1000 * 1000; - } else { - ts.tv_nsec = timeout * 1000 * 1000; - } - while (ts.tv_nsec >= 1000000000LL) { - ts.tv_nsec -= 1000000000LL; - ts.tv_sec += 1; - } - nanosleep(&ts, nullptr); - { - LockGuard guard (upd.update_lock); - assert(upd.shm != nullptr); - auto& update_shm = *upd.shm; - update_shm.cpu_active = false; - } - } else { - if (update_rate_ms == 0 || cpu_threshold < ENABLED_MIN_CPU_PERCENT) { - atomic_store(&upd._running, false); - BONGOCAT_LOG_WARNING("update: Stop update thread, not needed"); - } - } - } + const size_t d_total = (c->total_time > p->total_time) ? (c->total_time - p->total_time) : 0; + const size_t d_idle = (c->idle_time > p->idle_time) ? (c->idle_time - p->idle_time) : 0; + if (d_total == 0) { + continue; // skip if no change + } - atomic_store(&upd._running, false); + const double usage = 100.0 * static_cast(d_total - d_idle) / static_cast(d_total); + if (usage > max_usage) { + max_usage = usage; + } + } - // Will run only on normal return - pthread_cleanup_pop(1); // 1 = call cleanup even if not canceled + return max_usage; +} - BONGOCAT_LOG_INFO("Update monitoring stopped"); +static void *update_thread(void *arg) { + assert(arg); + animation::animation_session_t& trigger_ctx = *static_cast(arg); + + // from thread context + // animation_context_t& anim = trigger_ctx.anim; + // wait for input context (in animation start) + trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + assert(trigger_ctx._input != nullptr); + update_context_t& upd = *trigger_ctx._update; + + // sanity checks + assert(upd._config != nullptr); + assert(upd._configs_reloaded_cond != nullptr); + assert(!upd._running); + assert(upd.shm != nullptr); + assert(upd._local_copy_config != nullptr); + assert(upd.update_config_efd._fd >= 0); + assert(upd.fd_present._fd >= 0); + assert(upd.fd_stat._fd >= 0); + + // trigger initial render + wayland::request_render(trigger_ctx); + + pthread_cleanup_push(cleanup_update_thread, arg); + + // local thread context + /// event poll + // 0: reload config event + // 1: fd_stat + // 2: fd_present + constexpr size_t nfds = 3; + pollfd pfds[nfds]; + + atomic_store(&upd._running, true); + while (atomic_load(&upd._running)) { + pthread_testcancel(); // optional, but makes cancellation more responsive + + // read from config + time_ms_t timeout = UPDATE_POOL_TIMEOUT_MS; + time_ms_t update_rate_ms = 0; + time_ms_t animation_speed_ms = 0; + double cpu_threshold = 0; + int fps = 0; + // bool enable_debug = false; + { + // read-only config + assert(upd._local_copy_config != nullptr); + const config::config_t& current_config = *upd._local_copy_config; + + // enable_debug = current_config.enable_debug; + + cpu_threshold = current_config.cpu_threshold; + update_rate_ms = current_config.update_rate_ms; + animation_speed_ms = current_config.animation_speed_ms; + fps = current_config.fps; + if (update_rate_ms > 0) { + timeout = update_rate_ms; + } else if (animation_speed_ms > 0) { + timeout = animation_speed_ms; + } else if (current_config.fps > 0) { + timeout = 1000 / current_config.fps / 2; + } + } - return nullptr; + // init pfds + constexpr size_t fds_update_config_index = 0; + constexpr size_t fds_stat_index = 1; + constexpr size_t fds_preset_index = 2; + pfds[fds_update_config_index] = {.fd = upd.update_config_efd._fd, .events = POLLIN, .revents = 0}; + pfds[fds_stat_index] = {.fd = upd.fd_stat._fd, .events = POLLIN, .revents = 0}; + pfds[fds_preset_index] = {.fd = upd.fd_present._fd, .events = POLLIN, .revents = 0}; + + // poll events + const int poll_result = poll(pfds, nfds, static_cast(timeout)); + if (poll_result < 0) { + if (errno == EINTR) + continue; // Interrupted by signal + BONGOCAT_LOG_ERROR("update: Poll error: %s", strerror(errno)); + break; } - created_result_t> create(const config::config_t& config) { - AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + // cancel pooling (when not running anymore) + if (!atomic_load(&upd._running)) { + // draining pools + for (size_t i = 0; i < nfds; i++) { + if (pfds[i].revents & POLLIN) { + drain_event(pfds[i], MAX_ATTEMPTS); } + } + break; + } - ret->_running = false; - - BONGOCAT_LOG_INFO("Initializing update monitoring system"); - - // Initialize shared memory - ret->shm = make_allocated_mmap(); - if (!ret->shm.ptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for update monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } + // Handle config update + assert(upd._config_generation != nullptr); + bool reload_config = false; + uint64_t new_gen{atomic_load(upd._config_generation)}; + if (pfds[fds_update_config_index].revents & POLLIN) { + BONGOCAT_LOG_DEBUG("update: Receive update config event"); + drain_event(pfds[fds_update_config_index], MAX_ATTEMPTS, "update config eventfd"); + reload_config = new_gen > 0; + } - // Initialize shared memory for local config - ret->_local_copy_config = make_allocated_mmap(); - if (!ret->_local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for update monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(ret->_local_copy_config != nullptr); - *ret->_local_copy_config = config; + // Handle stat + if (pfds[fds_stat_index].revents & POLLIN) { + BONGOCAT_LOG_VERBOSE("update: Receive update CPU stats event"); + drain_event(pfds[fds_stat_index], MAX_ATTEMPTS, FILENAME_PROC_STAT); - ret->update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->update_config_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for update update config: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } + if (update_rate_ms > 0) { + platform::LockGuard guard(upd.update_lock); + assert(upd.shm != nullptr); + auto& update_shm = *upd.shm; - // open files - ret->fd_present = FileDescriptor(open(FILENAME_CPU_PRESET, O_RDONLY)); - if (ret->fd_present._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to open cpu_present"); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - set_nonblocking(ret->fd_present._fd); + const size_t count = parse_cpuinfo_fd( + upd.fd_stat._fd, 0, update_shm.cpu_snapshots.history[update_shm.cpu_snapshots.head].stats, MaxCpus); + update_shm.cpu_snapshots.history[update_shm.cpu_snapshots.head].count = count; - ret->fd_stat = FileDescriptor(open(FILENAME_PROC_STAT, O_RDONLY)); - if (ret->fd_stat._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to open proc stat"); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + { + LockGuard guard_cond(upd.update_cond._mutex); + update_shm.cpu_snapshots.head = (update_shm.cpu_snapshots.head + 1) % CpuSnapshotRingBufferMaxHistory; + if (update_shm.cpu_snapshots.stored < CpuSnapshotRingBufferMaxHistory) + update_shm.cpu_snapshots.stored++; + pthread_cond_broadcast(&upd.update_cond._cond); } - set_nonblocking(ret->fd_stat._fd); - - BONGOCAT_LOG_INFO("Input monitoring started"); - return ret; + } } - bongocat_error_t start(update_context_t& upd, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { - BONGOCAT_LOG_INFO("Initializing update monitoring"); + // cpu_present file changed + if (pfds[fds_preset_index].revents & POLLIN) { + BONGOCAT_LOG_VERBOSE("update: Receive update CPU present event"); + drain_event(pfds[fds_preset_index], MAX_ATTEMPTS, FILENAME_CPU_PRESET); - // Initialize shared memory for key press flag - upd.shm = make_allocated_mmap(); - if (!upd.shm.ptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } + if (update_rate_ms > 0) { + BONGOCAT_LOG_VERBOSE("update: cpu_present file changed (hotplug)"); + } + } - // Initialize shared memory for local config - upd._local_copy_config = make_allocated_mmap(); - if (!upd._local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(upd._local_copy_config != nullptr); - update_config(upd, config, atomic_load(&config_generation)); - - // wait for animation trigger to be ready (input should be the same) - int cond_ret = trigger_ctx.init_cond.timedwait([&]() { - return atomic_load(&trigger_ctx.ready); - }, COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - if (cond_ret == ETIMEDOUT) { - BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); + // trigger animation + bool animation_triggered = false; + if (update_rate_ms > 0) { + LockGuard guard(upd.update_lock); + assert(upd.shm != nullptr); + auto& update_shm = *upd.shm; + + // read-only config + assert(upd._local_copy_config != nullptr); + const config::config_t& current_config = *upd._local_copy_config; + + update_shm.latest_snapshot = &get_latest_snapshot_unlocked(upd); + + if (update_shm.cpu_snapshots.stored >= 2) { + const size_t latest = + (update_shm.cpu_snapshots.head + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; + const size_t prev_i = (latest + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; + + const cpu_snapshot_t& curr = update_shm.cpu_snapshots.history[latest]; + const cpu_snapshot_t& prev = update_shm.cpu_snapshots.history[prev_i]; + + update_shm.avg_cpu_usage = compute_avg_cpu_usage(prev, curr); + update_shm.max_cpu_usage = compute_max_cpu_usage(prev, curr); + + const bool above_threshold = current_config.cpu_threshold >= ENABLED_MIN_CPU_PERCENT && + (update_shm.avg_cpu_usage >= current_config.cpu_threshold || + update_shm.max_cpu_usage >= current_config.cpu_threshold); + const bool lower_threshold = current_config.cpu_threshold >= ENABLED_MIN_CPU_PERCENT && + (update_shm.last_avg_cpu_usage >= current_config.cpu_threshold || + update_shm.last_max_cpu_usage >= current_config.cpu_threshold) && + (update_shm.avg_cpu_usage < current_config.cpu_threshold || + update_shm.max_cpu_usage < current_config.cpu_threshold); + const bool crossed_delta = + fabs(update_shm.avg_cpu_usage - update_shm.last_avg_cpu_usage) >= TRIGGER_ANIMATION_CPU_DIFF_PERCENT || + fabs(update_shm.max_cpu_usage - update_shm.last_max_cpu_usage) >= TRIGGER_ANIMATION_CPU_DIFF_PERCENT; + if (above_threshold || lower_threshold) { + if (!update_shm.cpu_active || crossed_delta) { + BONGOCAT_LOG_VERBOSE("update: avg. CPU: %.2f (max: %.2f)", update_shm.avg_cpu_usage, + update_shm.max_cpu_usage); + animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); + if (lower_threshold) { + animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); + } + animation_triggered = true; + } + update_shm.cpu_active = true; } else { - assert(trigger_ctx._update == &upd); - } - // set extern/global references - { - // guard for anim_update_state - LockGuard guard (trigger_ctx.anim.anim_lock); - trigger_ctx._update = &upd; - } - trigger_ctx.init_cond.notify_all(); - upd._config = &config; - upd._configs_reloaded_cond = &configs_reloaded_cond; - upd._config_generation = &config_generation; - atomic_store(&upd.ready, true); - upd.init_cond.notify_all(); - - upd._configs_reloaded_cond->notify_all(); - - // start update thread - const int result = pthread_create(&upd._update_thread, nullptr, update_thread, &trigger_ctx); - if (result != 0) { - BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; + if (update_shm.cpu_active) { + update_shm.cpu_active = false; + animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); + animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); + } } + } - BONGOCAT_LOG_INFO("Update monitoring started"); - return bongocat_error_t::BONGOCAT_SUCCESS; + update_shm.last_avg_cpu_usage = update_shm.avg_cpu_usage; + update_shm.last_max_cpu_usage = update_shm.max_cpu_usage; } - bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& trigger_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { - BONGOCAT_LOG_INFO("Restarting update system"); - // Stop current monitoring - if (upd._update_thread) { - BONGOCAT_LOG_DEBUG("Update thread"); - atomic_store(&upd._running, false); - //pthread_cancel(ctx->_update_thread); - if (stop_thread_graceful_or_cancel(upd._update_thread, upd._running) != 0) { - BONGOCAT_LOG_ERROR("Failed to join update thread: %s", strerror(errno)); - } - BONGOCAT_LOG_DEBUG("Update thread terminated"); + // handle update config + if (reload_config) { + assert(upd._config_generation != nullptr); + assert(upd._configs_reloaded_cond != nullptr); + assert(upd._config != nullptr); + + update_config(upd, *upd._config, new_gen); + + // wait for reload config to be done (all configs) + const int rc = upd._configs_reloaded_cond->timedwait( + [&] { return atomic_load(upd._config_generation) >= new_gen; }, COND_RELOAD_CONFIGS_TIMEOUT_MS); + if (rc == ETIMEDOUT) { + BONGOCAT_LOG_WARNING("update: Timed out waiting for reload eventfd: %s", strerror(errno)); + } + if constexpr (features::Debug) { + if (atomic_load(&upd.config_seen_generation) < atomic_load(upd._config_generation)) { + BONGOCAT_LOG_VERBOSE("update: update.config_seen_generation < update._config_generation; %d < %d", + atomic_load(&upd.config_seen_generation), atomic_load(upd._config_generation)); } + } + // assert(atomic_load(&upd.config_seen_generation) >= atomic_load(upd._config_generation)); + atomic_store(&upd.config_seen_generation, atomic_load(upd._config_generation)); + BONGOCAT_LOG_INFO("update: Update config reloaded (gen=%u)", new_gen); + } - /// @TODO: re-create context with create(), avoid duplicate code - { - LockGuard guard (upd.update_lock); - // Start new monitoring (reuse shared memory if it exists) - if (upd.shm == nullptr) { - upd.shm = make_allocated_mmap(); - if (upd.shm.ptr == MAP_FAILED) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for update thread: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - } - } + if (update_rate_ms) { + // sleep + timespec ts; + ts.tv_sec = 0; + assert(fps > 0); + if (animation_triggered && animation_speed_ms > 0) { + // when animation were triggered, wait for animation to end (assumption), before triggering a possible new + // animation + ts.tv_nsec = animation_speed_ms * 1000 * 1000; + } else if (animation_triggered) { + ts.tv_nsec = (1000 / fps) * 1000 * 1000; + } else { + ts.tv_nsec = timeout * 1000 * 1000; + } + while (ts.tv_nsec >= 1000000000LL) { + ts.tv_nsec -= 1000000000LL; + ts.tv_sec += 1; + } + nanosleep(&ts, nullptr); + { + LockGuard guard(upd.update_lock); + assert(upd.shm != nullptr); + auto& update_shm = *upd.shm; + update_shm.cpu_active = false; + } + } else { + if (update_rate_ms == 0 || cpu_threshold < ENABLED_MIN_CPU_PERCENT) { + atomic_store(&upd._running, false); + BONGOCAT_LOG_WARNING("update: Stop update thread, not needed"); + } + } + } - if (!upd._local_copy_config) { - upd._local_copy_config = make_unallocated_mmap_value(config); - if (!upd._local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for update: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - } - assert(upd._local_copy_config != nullptr); - update_config(upd, config, atomic_load(&config_generation)); - - if (upd.update_config_efd._fd < 0) { - upd.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (upd.update_config_efd._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to create notify pipe for update, update config: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - } + atomic_store(&upd._running, false); - // re-open files - /// @TODO: healthcheck of fd before re-start + // Will run only on normal return + pthread_cleanup_pop(1); // 1 = call cleanup even if not canceled - if (upd.fd_present._fd < 0) { - upd.fd_present = FileDescriptor(open(FILENAME_CPU_PRESET, O_RDONLY)); - if (upd.fd_present._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to open cpu_present"); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - set_nonblocking(upd.fd_present._fd); - } - if (upd.fd_stat._fd < 0) { - upd.fd_stat = FileDescriptor(open(FILENAME_PROC_STAT, O_RDONLY)); - if (upd.fd_stat._fd < 0) { - BONGOCAT_LOG_ERROR("Failed to open proc stat"); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - set_nonblocking(upd.fd_stat._fd); - } + BONGOCAT_LOG_INFO("Update monitoring stopped"); - //if (trigger_ctx._update != ctx._update) { - // BONGOCAT_LOG_DEBUG("Update context in animation differs from animation trigger update context"); - //} + return nullptr; +} - // set extern/global references - { - // guard for anim_update_state - LockGuard guard (trigger_ctx.anim.anim_lock); - trigger_ctx._update = &upd; - } - upd._config = &config; - upd._configs_reloaded_cond = &configs_reloaded_cond; - upd._config_generation = &config_generation; - atomic_store(&upd.config_seen_generation, atomic_load(&config_generation)); - upd.init_cond.notify_all(); - upd._configs_reloaded_cond->notify_all(); - - // start update monitoring - const int result = pthread_create(&upd._update_thread, nullptr, update_thread, &trigger_ctx); - if (result != 0) { - BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_THREAD; - } +created_result_t> create(const config::config_t& config) { + AllocatedMemory ret = make_allocated_memory(); + assert(ret != nullptr); + if (ret == nullptr) { + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + ret->_running = false; + + BONGOCAT_LOG_INFO("Initializing update monitoring system"); + + // Initialize shared memory + ret->shm = make_allocated_mmap(); + if (!ret->shm.ptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for update monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // Initialize shared memory for local config + ret->_local_copy_config = make_allocated_mmap(); + if (!ret->_local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for update monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(ret->_local_copy_config != nullptr); + *ret->_local_copy_config = config; + + ret->update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->update_config_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for update update config: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + // open files + ret->fd_present = FileDescriptor(open(FILENAME_CPU_PRESET, O_RDONLY)); + if (ret->fd_present._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to open cpu_present"); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + set_nonblocking(ret->fd_present._fd); + + ret->fd_stat = FileDescriptor(open(FILENAME_PROC_STAT, O_RDONLY)); + if (ret->fd_stat._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to open proc stat"); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + set_nonblocking(ret->fd_stat._fd); + + BONGOCAT_LOG_INFO("Input monitoring started"); + return ret; +} - BONGOCAT_LOG_INFO("Update thread restarted"); - return bongocat_error_t::BONGOCAT_SUCCESS; +bongocat_error_t start(update_context_t& upd, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation) { + BONGOCAT_LOG_INFO("Initializing update monitoring"); + + // Initialize shared memory for key press flag + upd.shm = make_allocated_mmap(); + if (!upd.shm.ptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // Initialize shared memory for local config + upd._local_copy_config = make_allocated_mmap(); + if (!upd._local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(upd._local_copy_config != nullptr); + update_config(upd, config, atomic_load(&config_generation)); + + // wait for animation trigger to be ready (input should be the same) + int cond_ret = trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + if (cond_ret == ETIMEDOUT) { + BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); + } else { + assert(trigger_ctx._update == &upd); + } + // set extern/global references + { + // guard for anim_update_state + LockGuard guard(trigger_ctx.anim.anim_lock); + trigger_ctx._update = &upd; + } + trigger_ctx.init_cond.notify_all(); + upd._config = &config; + upd._configs_reloaded_cond = &configs_reloaded_cond; + upd._config_generation = &config_generation; + atomic_store(&upd.ready, true); + upd.init_cond.notify_all(); + + upd._configs_reloaded_cond->notify_all(); + + // start update thread + const int result = pthread_create(&upd._update_thread, nullptr, update_thread, &trigger_ctx); + if (result != 0) { + BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + BONGOCAT_LOG_INFO("Update monitoring started"); + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& trigger_ctx, + const config::config_t& config, CondVariable& configs_reloaded_cond, + atomic_uint64_t& config_generation) { + BONGOCAT_LOG_INFO("Restarting update system"); + // Stop current monitoring + if (upd._update_thread) { + BONGOCAT_LOG_DEBUG("Update thread"); + atomic_store(&upd._running, false); + // pthread_cancel(ctx->_update_thread); + if (stop_thread_graceful_or_cancel(upd._update_thread, upd._running) != 0) { + BONGOCAT_LOG_ERROR("Failed to join update thread: %s", strerror(errno)); + } + BONGOCAT_LOG_DEBUG("Update thread terminated"); + } + + /// @TODO: re-create context with create(), avoid duplicate code + { + LockGuard guard(upd.update_lock); + // Start new monitoring (reuse shared memory if it exists) + if (upd.shm == nullptr) { + upd.shm = make_allocated_mmap(); + if (upd.shm.ptr == MAP_FAILED) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for update thread: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } } + } - void stop(update_context_t& ctx) { - atomic_store(&ctx._running, false); - if (ctx._update_thread) { - BONGOCAT_LOG_DEBUG("Stopping update thread"); - //pthread_cancel(ctx->_update_thread); - if (stop_thread_graceful_or_cancel(ctx._update_thread, ctx._running) != 0) { - BONGOCAT_LOG_ERROR("Failed to join update thread: %s", strerror(errno)); - } - BONGOCAT_LOG_DEBUG("Update thread terminated"); - } - ctx._update_thread = 0; + if (!upd._local_copy_config) { + upd._local_copy_config = make_unallocated_mmap_value(config); + if (!upd._local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for update: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + } + assert(upd._local_copy_config != nullptr); + update_config(upd, config, atomic_load(&config_generation)); + + if (upd.update_config_efd._fd < 0) { + upd.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (upd.update_config_efd._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to create notify pipe for update, update config: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + } - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; + // re-open files + /// @TODO: healthcheck of fd before re-start - ctx.config_updated.notify_all(); - atomic_store(&ctx.ready, false); - ctx.init_cond.notify_all(); + if (upd.fd_present._fd < 0) { + upd.fd_present = FileDescriptor(open(FILENAME_CPU_PRESET, O_RDONLY)); + if (upd.fd_present._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to open cpu_present"); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; } + set_nonblocking(upd.fd_present._fd); + } + if (upd.fd_stat._fd < 0) { + upd.fd_stat = FileDescriptor(open(FILENAME_PROC_STAT, O_RDONLY)); + if (upd.fd_stat._fd < 0) { + BONGOCAT_LOG_ERROR("Failed to open proc stat"); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + set_nonblocking(upd.fd_stat._fd); + } + + // if (trigger_ctx._update != ctx._update) { + // BONGOCAT_LOG_DEBUG("Update context in animation differs from animation trigger update context"); + // } + + // set extern/global references + { + // guard for anim_update_state + LockGuard guard(trigger_ctx.anim.anim_lock); + trigger_ctx._update = &upd; + } + upd._config = &config; + upd._configs_reloaded_cond = &configs_reloaded_cond; + upd._config_generation = &config_generation; + atomic_store(&upd.config_seen_generation, atomic_load(&config_generation)); + upd.init_cond.notify_all(); + upd._configs_reloaded_cond->notify_all(); + + // start update monitoring + const int result = pthread_create(&upd._update_thread, nullptr, update_thread, &trigger_ctx); + if (result != 0) { + BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_THREAD; + } + + BONGOCAT_LOG_INFO("Update thread restarted"); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - void trigger_update_config(update_context_t& upd, const config::config_t& config, uint64_t config_generation) { - upd._config = &config; - if (write(upd.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { - BONGOCAT_LOG_VERBOSE("Write update trigger update config"); - } else { - BONGOCAT_LOG_ERROR("Failed to write to notify pipe in update: %s", strerror(errno)); - } +void stop(update_context_t& ctx) { + atomic_store(&ctx._running, false); + if (ctx._update_thread) { + BONGOCAT_LOG_DEBUG("Stopping update thread"); + // pthread_cancel(ctx->_update_thread); + if (stop_thread_graceful_or_cancel(ctx._update_thread, ctx._running) != 0) { + BONGOCAT_LOG_ERROR("Failed to join update thread: %s", strerror(errno)); } + BONGOCAT_LOG_DEBUG("Update thread terminated"); + } + ctx._update_thread = 0; - void update_config(update_context_t& upd, const config::config_t& config, uint64_t new_gen) { - assert(upd._local_copy_config != nullptr); + ctx._config = nullptr; + ctx._configs_reloaded_cond = nullptr; + ctx._config_generation = nullptr; - *upd._local_copy_config = config; + ctx.config_updated.notify_all(); + atomic_store(&ctx.ready, false); + ctx.init_cond.notify_all(); +} - atomic_store(&upd.config_seen_generation, new_gen); - // Signal main that reload is done - upd.config_updated.notify_all(); - } +void trigger_update_config(update_context_t& upd, const config::config_t& config, uint64_t config_generation) { + upd._config = &config; + if (write(upd.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { + BONGOCAT_LOG_VERBOSE("Write update trigger update config"); + } else { + BONGOCAT_LOG_ERROR("Failed to write to notify pipe in update: %s", strerror(errno)); + } +} + +void update_config(update_context_t& upd, const config::config_t& config, uint64_t new_gen) { + assert(upd._local_copy_config != nullptr); + + *upd._local_copy_config = config; + + atomic_store(&upd.config_seen_generation, new_gen); + // Signal main that reload is done + upd.config_updated.notify_all(); } +} // namespace bongocat::platform::update diff --git a/src/platform/wayland.cpp b/src/platform/wayland.cpp index 1ccc87e4..4591aee0 100644 --- a/src/platform/wayland.cpp +++ b/src/platform/wayland.cpp @@ -1,513 +1,519 @@ -#include "platform/wayland-protocols.hpp" +#include "platform/wayland.h" +#include "../graphics/bar.h" #include "graphics/animation.h" -#include "platform/wayland.h" -#include "platform/wayland_shared_memory.h" #include "platform/global_wayland_session.h" +#include "platform/wayland-protocols.hpp" +#include "platform/wayland_shared_memory.h" #include "utils/memory.h" -#include "../graphics/bar.h" +#include "wayland_hyprland.h" + #include -#include -#include -#include #include #include -#include +#include #include +#include +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #include - -#include "wayland_hyprland.h" -//#include "wayland_sway.h" +#include +#include +// #include "wayland_sway.h" #include "platform/wayland_callbacks.h" #include "platform/wayland_setups.h" namespace bongocat::platform::wayland { - // ============================================================================= - // GLOBAL STATE AND CONFIGURATION - // ============================================================================= - - static inline constexpr time_ms_t CHECK_INTERVAL_MS = 100; - static inline constexpr time_ms_t POOL_MIN_TIMEOUT_MS = 5; - static inline constexpr time_ms_t POOL_MAX_TIMEOUT_MS = 1000; - static_assert(POOL_MAX_TIMEOUT_MS >= POOL_MIN_TIMEOUT_MS); - - inline static constexpr time_ms_t COND_INIT_TIMEOUT_MS = 5000; - - static inline constexpr auto WAYLAND_LAYER_NAME = "OVERLAY"; +// ============================================================================= +// GLOBAL STATE AND CONFIGURATION +// ============================================================================= + +static inline constexpr time_ms_t CHECK_INTERVAL_MS = 100; +static inline constexpr time_ms_t POOL_MIN_TIMEOUT_MS = 5; +static inline constexpr time_ms_t POOL_MAX_TIMEOUT_MS = 1000; +static_assert(POOL_MAX_TIMEOUT_MS >= POOL_MIN_TIMEOUT_MS); + +inline static constexpr time_ms_t COND_INIT_TIMEOUT_MS = 5000; + +static inline constexpr auto WAYLAND_LAYER_NAME = "OVERLAY"; + +// ============================================================================= +// MAIN WAYLAND INTERFACE IMPLEMENTATION +// ============================================================================= + +created_result_t> create(animation::animation_session_t& anim, + const config::config_t& config) { + AllocatedMemory ret = make_allocated_memory(); + assert(ret != nullptr); + if (ret == nullptr) { + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + ret->animation_trigger_context = &anim; + ret->wayland_context._bar_height = DEFAULT_BAR_HEIGHT; + + // Initialize shared memory + ret->wayland_context.ctx_shm = make_allocated_mmap(); + if (ret->wayland_context.ctx_shm == nullptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + if (ret->wayland_context.ctx_shm != nullptr) { + static_assert(WAYLAND_NUM_BUFFERS <= INT_MAX); + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { + ret->wayland_context.ctx_shm->buffers[i] = {}; + } + atomic_store(&ret->wayland_context.ctx_shm->configured, false); + } + + // Initialize shared memory for local config + ret->wayland_context._local_copy_config = make_allocated_mmap(); + if (ret->wayland_context._local_copy_config == nullptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + assert(ret->wayland_context._local_copy_config != nullptr); + *ret->wayland_context._local_copy_config = config; + ret->wayland_context._bar_height = config.overlay_height; + + return ret; +} - // ============================================================================= - // MAIN WAYLAND INTERFACE IMPLEMENTATION - // ============================================================================= +bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim) { + ctx.animation_trigger_context = &anim; + + if (ctx.wayland_context.ctx_shm == nullptr) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + if (!ctx.wayland_context._local_copy_config) { + BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + BONGOCAT_LOG_INFO("Initializing Wayland connection"); + + ctx.wayland_context.display = wl_display_connect(nullptr); + if (!ctx.wayland_context.display) { + BONGOCAT_LOG_ERROR("Failed to connect to Wayland display"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + bongocat_error_t result = details::wayland_setup_protocols(ctx); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + return result; + } + result = details::wayland_setup_surface(ctx); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + return result; + } + result = details::wayland_setup_buffer(ctx.wayland_context, *ctx.animation_trigger_context); + if (result != bongocat_error_t::BONGOCAT_SUCCESS) { + return result; + } + + atomic_store(&ctx.ready, true); + + BONGOCAT_LOG_INFO("Wayland initialization complete (%dx%d buffer)", ctx.wayland_context._screen_width, + ctx.wayland_context._bar_height); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - created_result_t> create(animation::animation_session_t& anim, const config::config_t& config) { - AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } +bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int signal_fd, + input::input_context_t& input, const config::config_t& config, + const config::config_watcher_t *config_watcher, config_reload_callback_t config_reload_callback) { + BONGOCAT_CHECK_NULL(config_reload_callback, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(ctx.animation_trigger_context, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + + // from thread context + wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = trigger_ctx.anim; + // wait for context + ctx.animation_trigger_context->init_cond.timedwait( + [&]() { return atomic_load(&ctx.animation_trigger_context->ready); }, COND_INIT_TIMEOUT_MS); + input.init_cond.timedwait([&]() { return atomic_load(&ctx.animation_trigger_context->ready); }, COND_INIT_TIMEOUT_MS); + animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; + assert(trigger_ctx._input != nullptr); + assert(trigger_ctx._input == &input); + // wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm; + + BONGOCAT_LOG_INFO("Starting Wayland event loop"); + + running = 1; + while (running && wayland_ctx.display) { + const time_ms_t frame_based_timeout = config.fps > 0 ? 1000 / config.fps : 0; + // Periodic fullscreen check for fallback fullscreen detection + timeval now{}; + gettimeofday(&now, nullptr); + const time_ms_t elapsed_ms = (now.tv_sec - ctx.fs_detector.last_check.tv_sec) * 1000L + + (now.tv_usec - ctx.fs_detector.last_check.tv_usec) / 1000L; + time_ms_t fullscreen_check_interval = frame_based_timeout; + if (fullscreen_check_interval < CHECK_INTERVAL_MS) + fullscreen_check_interval = CHECK_INTERVAL_MS; + if (elapsed_ms >= fullscreen_check_interval) { + details::fs_update_state_fallback(ctx); + ctx.fs_detector.last_check = now; + } - ret->animation_trigger_context = &anim; - ret->wayland_context._bar_height = DEFAULT_BAR_HEIGHT; + // Handle Wayland events + constexpr size_t fds_signals_index = 0; + constexpr size_t fds_config_reload_index = 1; + constexpr size_t fds_animation_render_index = 2; + constexpr size_t fds_wayland_index = 3; + constexpr nfds_t fds_count = 4; + pollfd fds[fds_count] = { + {.fd = signal_fd, .events = POLLIN, .revents = 0}, + {.fd = config_watcher ? config_watcher->reload_efd._fd : -1, .events = POLLIN, .revents = 0}, + {.fd = trigger_ctx.render_efd._fd, .events = POLLIN, .revents = 0}, + {.fd = wl_display_get_fd(wayland_ctx.display), .events = POLLIN, .revents = 0}, + }; + static_assert(fds_count == LEN_ARRAY(fds)); + + // compute desired timeout + time_ms_t timeout_ms = frame_based_timeout; + if (timeout_ms < POOL_MIN_TIMEOUT_MS) + timeout_ms = POOL_MIN_TIMEOUT_MS; + if (timeout_ms > POOL_MAX_TIMEOUT_MS) + timeout_ms = POOL_MAX_TIMEOUT_MS; + + // avoid reloading twice, by signal OR watcher + bool config_reload_requested = false; + bool render_requested = false; + bool needs_flush = false; + bool toggle_visibility_requested = false; + + bool prepared_read = false; + { + int attempts = 0; + while (wl_display_prepare_read(wayland_ctx.display) != 0 && attempts < MAX_ATTEMPTS) { + wl_display_dispatch_pending(wayland_ctx.display); + attempts++; + } + prepared_read = attempts < MAX_ATTEMPTS; + } - // Initialize shared memory - ret->wayland_context.ctx_shm = make_allocated_mmap(); - if (ret->wayland_context.ctx_shm == nullptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + if (prepared_read) { + // Try to flush queued requests to the compositor so it can process them and send replies. + // If flush would block (EAGAIN), cancel the prepared read and dispatch pending events to make progress. + const int flush_ret = wl_display_flush(wayland_ctx.display); + if (flush_ret == -1 && errno == EAGAIN) { + // send buffer full; need to make progress by reading pending events first + wl_display_cancel_read(wayland_ctx.display); + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("wl_display_dispatch_pending failed after EAGAIN"); + running = 0; } - if (ret->wayland_context.ctx_shm != nullptr) { - static_assert(WAYLAND_NUM_BUFFERS <= INT_MAX); - for (size_t i = 0;i < WAYLAND_NUM_BUFFERS;i++) { - ret->wayland_context.ctx_shm->buffers[i] = {}; - } - atomic_store(&ret->wayland_context.ctx_shm->configured, false); + } else if (flush_ret == -1) { + BONGOCAT_LOG_ERROR("wl_display_flush failed: %s", strerror(errno)); + wl_display_cancel_read(wayland_ctx.display); + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + running = 0; } - - // Initialize shared memory for local config - ret->wayland_context._local_copy_config = make_allocated_mmap(); - if (ret->wayland_context._local_copy_config == nullptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - assert(ret->wayland_context._local_copy_config != nullptr); - *ret->wayland_context._local_copy_config = config; - ret->wayland_context._bar_height = config.overlay_height; - - return ret; + } } - bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim) { - ctx.animation_trigger_context = &anim; - - if (ctx.wayland_context.ctx_shm == nullptr) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + assert(timeout_ms <= INT_MAX); + const int poll_result = poll(fds, fds_count, static_cast(timeout_ms)); + if (poll_result > 0) { + // signal events + if (fds[fds_signals_index].revents & POLLIN) { + signalfd_siginfo fdsi{}; + ssize_t s = read(fds[fds_signals_index].fd, &fdsi, sizeof(fdsi)); + if (s != sizeof(fdsi)) { + BONGOCAT_LOG_ERROR("Failed to read signal fd"); + } else { + switch (fdsi.ssi_signo) { + case SIGINT: + case SIGTERM: + case SIGQUIT: // Handle Ctrl+\ for graceful shutdown + case SIGHUP: // Handle terminal hangup + BONGOCAT_LOG_INFO("Received signal %d, shutting down gracefully", fdsi.ssi_signo); + running = 0; + break; + case SIGCHLD: + // Handle child process termination - reap zombies + while (waitpid(-1, nullptr, WNOHANG) > 0) {} + break; + case SIGUSR1: + BONGOCAT_LOG_INFO("Received SIGUSR1, toggle bar visibility"); + toggle_visibility_requested = true; + break; + case SIGUSR2: + BONGOCAT_LOG_INFO("Received SIGUSR2, reloading config"); + config_reload_requested = true; + break; + default: + BONGOCAT_LOG_WARNING("Received unexpected signal %d", fdsi.ssi_signo); + break; + } } - if (!ctx.wayland_context._local_copy_config) { - BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + if (!running) { + // draining pools + for (size_t i = 0; i < fds_count; i++) { + platform::drain_event(fds[i], MAX_ATTEMPTS); } - - BONGOCAT_LOG_INFO("Initializing Wayland connection"); - - ctx.wayland_context.display = wl_display_connect(nullptr); - if (!ctx.wayland_context.display) { - BONGOCAT_LOG_ERROR("Failed to connect to Wayland display"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + if (prepared_read) + wl_display_cancel_read(wayland_ctx.display); + render_requested = false; + toggle_visibility_requested = false; + break; + } + + // reload config event + if (fds[fds_config_reload_index].revents & POLLIN) { + BONGOCAT_LOG_DEBUG("Receive reload event"); + if (config_watcher) { + platform::drain_event(fds[fds_config_reload_index], MAX_ATTEMPTS, "update config eventfd"); } - - - bongocat_error_t result = details::wayland_setup_protocols(ctx); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - return result; + config_reload_requested = true; + } + + // render event + if (fds[fds_animation_render_index].revents & POLLIN) { + BONGOCAT_LOG_VERBOSE("Receive render event"); + platform::drain_event(fds[fds_animation_render_index], MAX_ATTEMPTS, "render eventfd"); + if (atomic_load(&wayland_ctx.ctx_shm->configured)) { + render_requested = true; } - result = details::wayland_setup_surface(ctx); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - return result; + } + + // wayland events + if (prepared_read) { + if (fds[fds_wayland_index].revents & POLLIN) { + if (wl_display_read_events(wayland_ctx.display) == -1 || + wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("Failed to handle Wayland events: %s", strerror(errno)); + running = 0; + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + } else { + wl_display_cancel_read(wayland_ctx.display); } - result = details::wayland_setup_buffer(ctx.wayland_context, *ctx.animation_trigger_context); - if (result != bongocat_error_t::BONGOCAT_SUCCESS) { - return result; + } else { + if (fds[fds_wayland_index].revents & POLLIN) { + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("Failed to dispatch pending events: %s", strerror(errno)); + running = 0; + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + } else { + // dispatch any events already read + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("Failed to dispatch pending Wayland events: %s", strerror(errno)); + running = 0; + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } } + } - atomic_store(&ctx.ready, true); - - BONGOCAT_LOG_INFO("Wayland initialization complete (%dx%d buffer)", - ctx.wayland_context._screen_width, - ctx.wayland_context._bar_height); - return bongocat_error_t::BONGOCAT_SUCCESS; - } - - bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int signal_fd, input::input_context_t& input, const config::config_t& config, const config::config_watcher_t* config_watcher, config_reload_callback_t config_reload_callback) { - BONGOCAT_CHECK_NULL(config_reload_callback, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - BONGOCAT_CHECK_NULL(ctx.animation_trigger_context, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - - // from thread context - wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = trigger_ctx.anim; - // wait for context - ctx.animation_trigger_context->init_cond.timedwait([&]() { - return atomic_load(&ctx.animation_trigger_context->ready); - }, COND_INIT_TIMEOUT_MS); - input.init_cond.timedwait([&]() { - return atomic_load(&ctx.animation_trigger_context->ready); - }, COND_INIT_TIMEOUT_MS); - animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; - assert(trigger_ctx._input != nullptr); - assert(trigger_ctx._input == &input); - //wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm; - - BONGOCAT_LOG_INFO("Starting Wayland event loop"); - - running = 1; - while (running && wayland_ctx.display) { - const time_ms_t frame_based_timeout = config.fps > 0 ? 1000 / config.fps : 0; - // Periodic fullscreen check for fallback fullscreen detection - timeval now{}; - gettimeofday(&now, nullptr); - const time_ms_t elapsed_ms = (now.tv_sec - ctx.fs_detector.last_check.tv_sec) * 1000L + (now.tv_usec - ctx.fs_detector.last_check.tv_usec) / 1000L; - time_ms_t fullscreen_check_interval = frame_based_timeout; - if (fullscreen_check_interval < CHECK_INTERVAL_MS) fullscreen_check_interval = CHECK_INTERVAL_MS; - if (elapsed_ms >= fullscreen_check_interval) { - details::fs_update_state_fallback(ctx); - ctx.fs_detector.last_check = now; - } - - // Handle Wayland events - constexpr size_t fds_signals_index = 0; - constexpr size_t fds_config_reload_index = 1; - constexpr size_t fds_animation_render_index = 2; - constexpr size_t fds_wayland_index = 3; - constexpr nfds_t fds_count = 4; - pollfd fds[fds_count] = { - { .fd = signal_fd, .events = POLLIN, .revents = 0 }, - { .fd = config_watcher ? config_watcher->reload_efd._fd : -1, .events = POLLIN, .revents = 0 }, - { .fd = trigger_ctx.render_efd._fd, .events = POLLIN, .revents = 0 }, - { .fd = wl_display_get_fd(wayland_ctx.display), .events = POLLIN, .revents = 0 }, - }; - static_assert(fds_count == LEN_ARRAY(fds)); - - // compute desired timeout - time_ms_t timeout_ms = frame_based_timeout; - if (timeout_ms < POOL_MIN_TIMEOUT_MS) timeout_ms = POOL_MIN_TIMEOUT_MS; - if (timeout_ms > POOL_MAX_TIMEOUT_MS) timeout_ms = POOL_MAX_TIMEOUT_MS; - - // avoid reloading twice, by signal OR watcher - bool config_reload_requested = false; - bool render_requested = false; - bool needs_flush = false; - bool toggle_visibility_requested = false; - - bool prepared_read = false; - { - int attempts = 0; - while (wl_display_prepare_read(wayland_ctx.display) != 0 && attempts < MAX_ATTEMPTS) { - wl_display_dispatch_pending(wayland_ctx.display); - attempts++; - } - prepared_read = attempts < MAX_ATTEMPTS; - } - - - if (prepared_read) { - // Try to flush queued requests to the compositor so it can process them and send replies. - // If flush would block (EAGAIN), cancel the prepared read and dispatch pending events to make progress. - const int flush_ret = wl_display_flush(wayland_ctx.display); - if (flush_ret == -1 && errno == EAGAIN) { - // send buffer full; need to make progress by reading pending events first - wl_display_cancel_read(wayland_ctx.display); - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("wl_display_dispatch_pending failed after EAGAIN"); - running = 0; - } - } else if (flush_ret == -1) { - BONGOCAT_LOG_ERROR("wl_display_flush failed: %s", strerror(errno)); - wl_display_cancel_read(wayland_ctx.display); - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - running = 0; - } - } - } - - assert(timeout_ms <= INT_MAX); - const int poll_result = poll(fds, fds_count, static_cast(timeout_ms)); - if (poll_result > 0) { - // signal events - if (fds[fds_signals_index].revents & POLLIN) { - signalfd_siginfo fdsi{}; - ssize_t s = read(fds[fds_signals_index].fd, &fdsi, sizeof(fdsi)); - if (s != sizeof(fdsi)) { - BONGOCAT_LOG_ERROR("Failed to read signal fd"); - } else { - switch (fdsi.ssi_signo) { - case SIGINT: - case SIGTERM: - case SIGQUIT: // Handle Ctrl+\ for graceful shutdown - case SIGHUP: // Handle terminal hangup - BONGOCAT_LOG_INFO("Received signal %d, shutting down gracefully", fdsi.ssi_signo); - running = 0; - break; - case SIGCHLD: - // Handle child process termination - reap zombies - while (waitpid(-1, nullptr, WNOHANG) > 0){} - break; - case SIGUSR1: - BONGOCAT_LOG_INFO("Received SIGUSR1, toggle bar visibility"); - toggle_visibility_requested = true; - break; - case SIGUSR2: - BONGOCAT_LOG_INFO("Received SIGUSR2, reloading config"); - config_reload_requested = true; - break; - default: - BONGOCAT_LOG_WARNING("Received unexpected signal %d", fdsi.ssi_signo); - break; - } - } - } - if (!running) { - // draining pools - for (size_t i = 0; i < fds_count; i++) { - platform::drain_event(fds[i], MAX_ATTEMPTS); - } - if (prepared_read) wl_display_cancel_read(wayland_ctx.display); - render_requested = false; - toggle_visibility_requested = false; - break; - } - - // reload config event - if (fds[fds_config_reload_index].revents & POLLIN) { - BONGOCAT_LOG_DEBUG("Receive reload event"); - if (config_watcher) { - platform::drain_event(fds[fds_config_reload_index], MAX_ATTEMPTS, "update config eventfd"); - } - config_reload_requested = true; - } - - // render event - if (fds[fds_animation_render_index].revents & POLLIN) { - BONGOCAT_LOG_VERBOSE("Receive render event"); - platform::drain_event(fds[fds_animation_render_index], MAX_ATTEMPTS, "render eventfd"); - if (atomic_load(&wayland_ctx.ctx_shm->configured)) { - render_requested = true; - } - } - - // wayland events - if (prepared_read) { - if (fds[fds_wayland_index].revents & POLLIN) { - if (wl_display_read_events(wayland_ctx.display) == -1 || - wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("Failed to handle Wayland events: %s", strerror(errno)); - running = 0; - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - } else { - wl_display_cancel_read(wayland_ctx.display); - } - } else { - if (fds[fds_wayland_index].revents & POLLIN) { - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("Failed to dispatch pending events: %s", strerror(errno)); - running = 0; - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - } else { - // dispatch any events already read - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("Failed to dispatch pending Wayland events: %s", strerror(errno)); - running = 0; - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - } - } - - if (render_requested) { - if (!atomic_load(&wayland_ctx.ctx_shm->configured)) { - BONGOCAT_LOG_VERBOSE("Surface not configured yet, skip drawing"); - render_requested = false; - } - } - - BONGOCAT_LOG_VERBOSE("Poll revents: poll_result=%d; signal=%x, reload=%x, render=%x, wayland=%x", - poll_result, - fds[fds_signals_index].revents, - fds[fds_config_reload_index].revents, - fds[fds_animation_render_index].revents, - fds[fds_wayland_index].revents); - } else if (poll_result == 0) { - if (prepared_read) wl_display_cancel_read(wayland_ctx.display); - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("Failed to dispatch pending events"); - running = 0; - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - } else { - if (prepared_read) wl_display_cancel_read(wayland_ctx.display); - if (errno != EINTR) { - BONGOCAT_LOG_ERROR("Poll error: %s", strerror(errno)); - running = 0; - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - } - - // do reload once - if (config_reload_requested && config_reload_callback) { - config_reload_callback(); - render_requested = true; - } - if (toggle_visibility_requested) { - wayland_ctx.bar_visibility = wayland_ctx.bar_visibility == bar_visibility_t::Show ? bar_visibility_t::Hide : bar_visibility_t::Show; - render_requested = true; - } - - /// @TODO: release buffer fallback after timeout, fallback - /* - const auto now_ms = platform::get_current_time_ms(); - if (now_ms - wayland_ctx._last_frame_timestamp_ms > 500 && - all_buffers_busy(wayland_ctx.ctx_shm.ptr)) - { - for (auto& buf : wayland_ctx.ctx_shm->buffers) { - atomic_store(&buf.busy, false); - } - BONGOCAT_LOG_WARNING("Missed frame_done fallback: forcibly releasing stuck buffers"); - } - */ - - if (render_requested) { - BONGOCAT_LOG_VERBOSE("Receive render event"); - BONGOCAT_LOG_VERBOSE("Try to draw_bar in wayland_run"); - - if (!atomic_load(&wayland_ctx._frame_pending)) { - wl_display_dispatch_pending(wayland_ctx.display); - const auto draw_bar_result = animation::draw_bar(ctx); - needs_flush = draw_bar_result == animation::draw_bar_result_t::FlushNeeded; - } else { - if (!atomic_exchange(&wayland_ctx._redraw_after_frame, true)) { - BONGOCAT_LOG_VERBOSE("Queued redraw after frame"); - request_render(trigger_ctx); - } else { - const auto draw_bar_result = animation::draw_bar(ctx); - needs_flush = draw_bar_result == animation::draw_bar_result_t::FlushNeeded; - } - } - render_requested = false; - } - toggle_visibility_requested = false; - - if (needs_flush) { - const int flush_ret = wl_display_flush(wayland_ctx.display); - if (flush_ret == -1 && errno == EAGAIN) { - // send buffer full; need to make progress by reading pending events first - wl_display_cancel_read(wayland_ctx.display); - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - BONGOCAT_LOG_ERROR("wl_display_dispatch_pending failed after EAGAIN"); - running = 0; - } - } else if (flush_ret == -1) { - BONGOCAT_LOG_ERROR("wl_display_flush failed: %s", strerror(errno)); - wl_display_cancel_read(wayland_ctx.display); - if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { - running = 0; - } - } - } + if (render_requested) { + if (!atomic_load(&wayland_ctx.ctx_shm->configured)) { + BONGOCAT_LOG_VERBOSE("Surface not configured yet, skip drawing"); + render_requested = false; } + } + + BONGOCAT_LOG_VERBOSE("Poll revents: poll_result=%d; signal=%x, reload=%x, render=%x, wayland=%x", poll_result, + fds[fds_signals_index].revents, fds[fds_config_reload_index].revents, + fds[fds_animation_render_index].revents, fds[fds_wayland_index].revents); + } else if (poll_result == 0) { + if (prepared_read) + wl_display_cancel_read(wayland_ctx.display); + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("Failed to dispatch pending events"); running = 0; - - BONGOCAT_LOG_INFO("Wayland event loop exited"); - return bongocat_error_t::BONGOCAT_SUCCESS; + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + } else { + if (prepared_read) + wl_display_cancel_read(wayland_ctx.display); + if (errno != EINTR) { + BONGOCAT_LOG_ERROR("Poll error: %s", strerror(errno)); + running = 0; + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } } - // ============================================================================= - // PUBLIC API IMPLEMENTATION - // ============================================================================= - - int get_screen_width(const wayland_session_t& ctx) { - return (ctx.wayland_context._screen_info) ? ctx.wayland_context._screen_info->screen_width : 0; + // do reload once + if (config_reload_requested && config_reload_callback) { + config_reload_callback(); + render_requested = true; + } + if (toggle_visibility_requested) { + wayland_ctx.bar_visibility = + wayland_ctx.bar_visibility == bar_visibility_t::Show ? bar_visibility_t::Hide : bar_visibility_t::Show; + render_requested = true; } - void update_config(wayland_session_t& ctx, const config::config_t& config, animation::animation_session_t& trigger_ctx) { - assert(ctx.wayland_context._local_copy_config != nullptr && ctx.wayland_context._local_copy_config.ptr != MAP_FAILED); - - // Check if dimensions changed - requires buffer/surface recreation - const auto old_height = ctx.wayland_context._local_copy_config ? ctx.wayland_context._local_copy_config->overlay_height : 0; - const auto old_width = ctx.wayland_context._screen_width; - - // update old config - *ctx.wayland_context._local_copy_config = config; - - const bool dimensions_changed = (old_height != ctx.wayland_context._local_copy_config->overlay_height) || (ctx.wayland_context._local_copy_config->screen_width > 0 && old_width != ctx.wayland_context._local_copy_config->screen_width); - - if (dimensions_changed && old_height > 0 && old_width > 0 && ctx.wayland_context.ctx_shm) { - // ~~Lock animation mutex to prevent draw_bar() during config update - // This is critical - animation thread must not access buffer while we - // recreate it~~ - // not sure if this is needed, animation thread don't touch the bar directly, - // it just triggers a rerender - platform::LockGuard anim_guard (trigger_ctx.anim.anim_lock); - - BONGOCAT_LOG_INFO("Dimensions changed (%dx%d -> %dx%d), recreating buffer...", old_width, old_height, ctx.wayland_context._local_copy_config->screen_width, ctx.wayland_context._local_copy_config->overlay_height); - - // Mark as not configured first - assert(ctx.wayland_context.ctx_shm); - atomic_store(&ctx.wayland_context.ctx_shm->configured, false); - - if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to update width for bar"); - return; - } - ctx.wayland_context._bar_height = config.overlay_height; - - if (ctx.wayland_context._screen_width > 0 && ctx.wayland_context._bar_height > 0) { - // Cleanup old buffer - cleanup_wayland_context_buffer(ctx.wayland_context); - - // Cleanup old surface - cleanup_wayland_context_surface(ctx.wayland_context); - - if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to update width for bar"); - return; - } - ctx.wayland_context._bar_height = config.overlay_height; - - // Recreate surface and buffer with new dimensions - if (details::wayland_setup_surface(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to recreate surface after config change"); - return; - } - if (details::wayland_setup_buffer(ctx.wayland_context, trigger_ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { - BONGOCAT_LOG_ERROR("Failed to recreate buffer after config change"); - return; - } - - atomic_store(&ctx.wayland_context.ctx_shm->configured, true); - - // Wait for new configure event - wl_display_roundtrip(ctx.wayland_context.display); - - BONGOCAT_LOG_INFO("Buffer recreated successfully (%dx%d)", - ctx.wayland_context._local_copy_config->screen_width, - ctx.wayland_context._local_copy_config->overlay_height); - } else { - BONGOCAT_LOG_ERROR("Buffer recreated failed (%dx%d)", - ctx.wayland_context._local_copy_config->screen_width, - ctx.wayland_context._local_copy_config->overlay_height); - } + /// @TODO: release buffer fallback after timeout, fallback + /* + const auto now_ms = platform::get_current_time_ms(); + if (now_ms - wayland_ctx._last_frame_timestamp_ms > 500 && + all_buffers_busy(wayland_ctx.ctx_shm.ptr)) + { + for (auto& buf : wayland_ctx.ctx_shm->buffers) { + atomic_store(&buf.busy, false); } - - /// @NOTE: assume animation has the same local copy as wayland config - //animation_update_config(anim, config); - if (atomic_load(&ctx.wayland_context.ctx_shm->configured)) { - request_render(trigger_ctx); + BONGOCAT_LOG_WARNING("Missed frame_done fallback: forcibly releasing stuck buffers"); + } + */ + + if (render_requested) { + BONGOCAT_LOG_VERBOSE("Receive render event"); + BONGOCAT_LOG_VERBOSE("Try to draw_bar in wayland_run"); + + if (!atomic_load(&wayland_ctx._frame_pending)) { + wl_display_dispatch_pending(wayland_ctx.display); + const auto draw_bar_result = animation::draw_bar(ctx); + needs_flush = draw_bar_result == animation::draw_bar_result_t::FlushNeeded; + } else { + if (!atomic_exchange(&wayland_ctx._redraw_after_frame, true)) { + BONGOCAT_LOG_VERBOSE("Queued redraw after frame"); + request_render(trigger_ctx); + } else { + const auto draw_bar_result = animation::draw_bar(ctx); + needs_flush = draw_bar_result == animation::draw_bar_result_t::FlushNeeded; } + } + render_requested = false; } - - const char* get_current_layer_name() { - return WAYLAND_LAYER_NAME; + toggle_visibility_requested = false; + + if (needs_flush) { + const int flush_ret = wl_display_flush(wayland_ctx.display); + if (flush_ret == -1 && errno == EAGAIN) { + // send buffer full; need to make progress by reading pending events first + wl_display_cancel_read(wayland_ctx.display); + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + BONGOCAT_LOG_ERROR("wl_display_dispatch_pending failed after EAGAIN"); + running = 0; + } + } else if (flush_ret == -1) { + BONGOCAT_LOG_ERROR("wl_display_flush failed: %s", strerror(errno)); + wl_display_cancel_read(wayland_ctx.display); + if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { + running = 0; + } + } } + } + running = 0; - bongocat_error_t request_render(animation::animation_session_t& trigger_ctx) { - if (trigger_ctx.render_efd._fd < 0) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } + BONGOCAT_LOG_INFO("Wayland event loop exited"); + return bongocat_error_t::BONGOCAT_SUCCESS; +} - constexpr uint64_t u = 1; - const ssize_t s = write(trigger_ctx.render_efd._fd, &u, sizeof(u)); - if (s != sizeof(u)) { - BONGOCAT_LOG_WARNING("Failed to write render eventfd: %s", strerror(errno)); - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } +// ============================================================================= +// PUBLIC API IMPLEMENTATION +// ============================================================================= + +int get_screen_width(const wayland_session_t& ctx) { + return (ctx.wayland_context._screen_info) ? ctx.wayland_context._screen_info->screen_width : 0; +} - return bongocat_error_t::BONGOCAT_SUCCESS; +void update_config(wayland_session_t& ctx, const config::config_t& config, + animation::animation_session_t& trigger_ctx) { + assert(ctx.wayland_context._local_copy_config != nullptr && ctx.wayland_context._local_copy_config.ptr != MAP_FAILED); + + // Check if dimensions changed - requires buffer/surface recreation + const auto old_height = + ctx.wayland_context._local_copy_config ? ctx.wayland_context._local_copy_config->overlay_height : 0; + const auto old_width = ctx.wayland_context._screen_width; + + // update old config + *ctx.wayland_context._local_copy_config = config; + + const bool dimensions_changed = (old_height != ctx.wayland_context._local_copy_config->overlay_height) || + (ctx.wayland_context._local_copy_config->screen_width > 0 && + old_width != ctx.wayland_context._local_copy_config->screen_width); + + if (dimensions_changed && old_height > 0 && old_width > 0 && ctx.wayland_context.ctx_shm) { + // ~~Lock animation mutex to prevent draw_bar() during config update + // This is critical - animation thread must not access buffer while we + // recreate it~~ + // not sure if this is needed, animation thread don't touch the bar directly, + // it just triggers a rerender + platform::LockGuard anim_guard(trigger_ctx.anim.anim_lock); + + BONGOCAT_LOG_INFO("Dimensions changed (%dx%d -> %dx%d), recreating buffer...", old_width, old_height, + ctx.wayland_context._local_copy_config->screen_width, + ctx.wayland_context._local_copy_config->overlay_height); + + // Mark as not configured first + assert(ctx.wayland_context.ctx_shm); + atomic_store(&ctx.wayland_context.ctx_shm->configured, false); + + if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to update width for bar"); + return; + } + ctx.wayland_context._bar_height = config.overlay_height; + + if (ctx.wayland_context._screen_width > 0 && ctx.wayland_context._bar_height > 0) { + // Cleanup old buffer + cleanup_wayland_context_buffer(ctx.wayland_context); + + // Cleanup old surface + cleanup_wayland_context_surface(ctx.wayland_context); + + if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to update width for bar"); + return; + } + ctx.wayland_context._bar_height = config.overlay_height; + + // Recreate surface and buffer with new dimensions + if (details::wayland_setup_surface(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to recreate surface after config change"); + return; + } + if (details::wayland_setup_buffer(ctx.wayland_context, trigger_ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + BONGOCAT_LOG_ERROR("Failed to recreate buffer after config change"); + return; + } + + atomic_store(&ctx.wayland_context.ctx_shm->configured, true); + + // Wait for new configure event + wl_display_roundtrip(ctx.wayland_context.display); + + BONGOCAT_LOG_INFO("Buffer recreated successfully (%dx%d)", ctx.wayland_context._local_copy_config->screen_width, + ctx.wayland_context._local_copy_config->overlay_height); + } else { + BONGOCAT_LOG_ERROR("Buffer recreated failed (%dx%d)", ctx.wayland_context._local_copy_config->screen_width, + ctx.wayland_context._local_copy_config->overlay_height); } + } + + /// @NOTE: assume animation has the same local copy as wayland config + // animation_update_config(anim, config); + if (atomic_load(&ctx.wayland_context.ctx_shm->configured)) { + request_render(trigger_ctx); + } +} + +const char *get_current_layer_name() { + return WAYLAND_LAYER_NAME; +} + +bongocat_error_t request_render(animation::animation_session_t& trigger_ctx) { + if (trigger_ctx.render_efd._fd < 0) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + + constexpr uint64_t u = 1; + const ssize_t s = write(trigger_ctx.render_efd._fd, &u, sizeof(u)); + if (s != sizeof(u)) { + BONGOCAT_LOG_WARNING("Failed to write render eventfd: %s", strerror(errno)); + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + return bongocat_error_t::BONGOCAT_SUCCESS; } +} // namespace bongocat::platform::wayland diff --git a/src/platform/wayland_callbacks.cpp b/src/platform/wayland_callbacks.cpp index 01baf460..58629714 100644 --- a/src/platform/wayland_callbacks.cpp +++ b/src/platform/wayland_callbacks.cpp @@ -1,932 +1,928 @@ -#include "platform/wayland-protocols.hpp" +#include "platform/wayland_callbacks.h" +#include "../graphics/bar.h" #include "graphics/animation.h" +#include "platform/global_wayland_session.h" +#include "platform/wayland-protocols.hpp" #include "platform/wayland.h" +#include "platform/wayland_setups.h" #include "platform/wayland_shared_memory.h" -#include "platform/global_wayland_session.h" -#include "platform/wayland_callbacks.h" #include "utils/memory.h" +#include "wayland_hyprland.h" +#include "wayland_sway.h" + #include -#include -#include -#include -#include #include -#include -#include +#include +#include +#include #include -#include #include +#include +#include #include - -#include "wayland_hyprland.h" -#include "wayland_sway.h" -#include "../graphics/bar.h" -#include "platform/wayland_setups.h" +#include +#include namespace bongocat::platform::wayland::details { #ifdef __cplusplus -#define wl_array_for_each_typed(pos, array, type) \ -for (type *pos = reinterpret_cast((array)->data); \ - reinterpret_cast(pos) < (reinterpret_cast((array)->data) + (array)->size); \ - ++pos) +# define wl_array_for_each_typed(pos, array, type) \ + for (type *pos = reinterpret_cast((array)->data); \ + reinterpret_cast(pos) < (reinterpret_cast((array)->data) + (array)->size); ++pos) #endif - // ============================================================================= - // ZXDG LISTENER IMPLEMENTATION - // ============================================================================= - - void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, - const char *name) { - if (!data || !name) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - auto *oref = static_cast(data); - - /// name received - { - snprintf(oref->name_str, sizeof(oref->name_str), "%s", name); - oref->received = flag_add(oref->received, output_ref_received_flags_t::Name); - BONGOCAT_LOG_DEBUG("xdg_output.name: xdg-output name received: %s", name); - } - - // @NOTE: this should always be set ? - //assert(oref->wayland); - - /// Reconnection handling - if (oref->wayland) { - wayland_context_t& wayland_ctx = oref->wayland->wayland_context; - //animation_context_t& anim = *ctx.animation_context; - //animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; - - // read-only config - //assert(wayland_ctx._local_copy_config != nullptr); - //const config::config_t& current_config = *wayland_ctx._local_copy_config; - - // Check if this is the output we're waiting for (reconnection case) - if (!atomic_load(&oref->wayland->output_lost)) { - return; - } - - bool should_reconnect = false; - // Case 1: User specified an output name - match exactly - if (wayland_ctx.using_named_output && wayland_ctx._output_name_str) { - should_reconnect = (strcmp(name, wayland_ctx._output_name_str) == 0); - } - // Case 2: Using fallback (first output) - reconnect to any output - else if (!wayland_ctx.using_named_output) { - should_reconnect = true; - BONGOCAT_LOG_DEBUG("Using fallback output, accepting '%s'", name); - } - - if (should_reconnect) { - BONGOCAT_LOG_INFO("Target output '%s' reconnected!", name); - - // Clean up old surface if it exists - cleanup_wayland_context_surface(wayland_ctx); - - // Set new output - wayland_ctx.output = oref->wl_output; - wayland_ctx.bound_output_name = oref->name; - atomic_store(&oref->wayland->output_lost, false); - - // Recreate surface on new output - // Note: wayland_setup_surface already commits, triggering a configure - // event. The layer_surface_configure callback will ack and call draw_bar() - // to render. - if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { - assert(wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED); - if (wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED) { - atomic_store(&wayland_ctx.ctx_shm->configured, true); - } - if constexpr (WAYLAND_NUM_BUFFERS != 1) { - // Wait for configure event to be processed - wl_display_roundtrip(wayland_ctx.display); - } - if (oref->wayland->animation_trigger_context) { - request_render(*oref->wayland->animation_trigger_context); - } - BONGOCAT_LOG_INFO("Surface recreated, configure event processed"); - } else { - BONGOCAT_LOG_ERROR("Failed to recreate surface on reconnected output"); - } - } - } - } - - void handle_xdg_output_logical_position(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - auto *oref = static_cast(data); - - oref->x = x; - oref->y = y; - oref->received = flag_add(oref->received, output_ref_received_flags_t::LogicalPosition); - - BONGOCAT_LOG_VERBOSE("xdg_output.logical_position: %d,%d received", x, y); - } - void handle_xdg_output_logical_size(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, - int32_t width, int32_t height) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - auto *oref = static_cast(data); - - oref->width = width; - oref->height = height; - oref->received = flag_add(oref->received, output_ref_received_flags_t::LogicalSize); - - BONGOCAT_LOG_VERBOSE("xdg_output.logical_size: %dx%d received", width, height); - } - void handle_xdg_output_done(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //auto *oref = static_cast(data); - - BONGOCAT_LOG_VERBOSE("xdg_output.done: done received"); - } - - void handle_xdg_output_description(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, [[maybe_unused]] const char *description) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //auto *oref = static_cast(data); - - BONGOCAT_LOG_VERBOSE("xdg_output.description: description received"); - } - - - // ============================================================================= - // FULLSCREEN DETECTION IMPLEMENTATION - // ============================================================================= - - static bool fs_update_state(wayland_session_t& ctx, bool new_state) { - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping update"); - return false; - } - if (!ctx.animation_trigger_context) { - BONGOCAT_LOG_VERBOSE("Wayland not configured yet"); - return false; - } - - //const wayland_shared_memory_t& wayland_ctx_shm = *ctx.wayland_context.ctx_shm; - - if (new_state != ctx.fs_detector.has_fullscreen_toplevel) { - ctx.fs_detector.has_fullscreen_toplevel = new_state; - ctx.wayland_context._fullscreen_detected = new_state; - - BONGOCAT_LOG_INFO("Fullscreen state changed: %s", - ctx.wayland_context._fullscreen_detected ? "detected" : "cleared"); +// ============================================================================= +// ZXDG LISTENER IMPLEMENTATION +// ============================================================================= + +void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, const char *name) { + if (!data || !name) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + auto *oref = static_cast(data); + + /// name received + { + snprintf(oref->name_str, sizeof(oref->name_str), "%s", name); + oref->received = flag_add(oref->received, output_ref_received_flags_t::Name); + BONGOCAT_LOG_DEBUG("xdg_output.name: xdg-output name received: %s", name); + } + + // @NOTE: this should always be set ? + // assert(oref->wayland); + + /// Reconnection handling + if (oref->wayland) { + wayland_context_t& wayland_ctx = oref->wayland->wayland_context; + // animation_context_t& anim = *ctx.animation_context; + // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; + + // read-only config + // assert(wayland_ctx._local_copy_config != nullptr); + // const config::config_t& current_config = *wayland_ctx._local_copy_config; + + // Check if this is the output we're waiting for (reconnection case) + if (!atomic_load(&oref->wayland->output_lost)) { + return; + } + + bool should_reconnect = false; + // Case 1: User specified an output name - match exactly + if (wayland_ctx.using_named_output && wayland_ctx._output_name_str) { + should_reconnect = (strcmp(name, wayland_ctx._output_name_str) == 0); + } + // Case 2: Using fallback (first output) - reconnect to any output + else if (!wayland_ctx.using_named_output) { + should_reconnect = true; + BONGOCAT_LOG_DEBUG("Using fallback output, accepting '%s'", name); + } + + if (should_reconnect) { + BONGOCAT_LOG_INFO("Target output '%s' reconnected!", name); + + // Clean up old surface if it exists + cleanup_wayland_context_surface(wayland_ctx); + + // Set new output + wayland_ctx.output = oref->wl_output; + wayland_ctx.bound_output_name = oref->name; + atomic_store(&oref->wayland->output_lost, false); + + // Recreate surface on new output + // Note: wayland_setup_surface already commits, triggering a configure + // event. The layer_surface_configure callback will ack and call draw_bar() + // to render. + if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { + assert(wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED); + if (wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED) { + atomic_store(&wayland_ctx.ctx_shm->configured, true); + } + if constexpr (WAYLAND_NUM_BUFFERS != 1) { + // Wait for configure event to be processed + wl_display_roundtrip(wayland_ctx.display); + } + if (oref->wayland->animation_trigger_context) { + request_render(*oref->wayland->animation_trigger_context); + } + BONGOCAT_LOG_INFO("Surface recreated, configure event processed"); + } else { + BONGOCAT_LOG_ERROR("Failed to recreate surface on reconnected output"); + } + } + } +} - if (ctx.wayland_context.ctx_shm != nullptr && atomic_load(&ctx.wayland_context.ctx_shm->configured)) { - request_render(*ctx.animation_trigger_context); - } else { - BONGOCAT_LOG_VERBOSE("Wayland not configured yet, skipping request rendering"); - } +void handle_xdg_output_logical_position(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + auto *oref = static_cast(data); - return true; - } + oref->x = x; + oref->y = y; + oref->received = flag_add(oref->received, output_ref_received_flags_t::LogicalPosition); - return false; - } + BONGOCAT_LOG_VERBOSE("xdg_output.logical_position: %d,%d received", x, y); +} +void handle_xdg_output_logical_size(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, int32_t width, + int32_t height) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + auto *oref = static_cast(data); + + oref->width = width; + oref->height = height; + oref->received = flag_add(oref->received, output_ref_received_flags_t::LogicalSize); + + BONGOCAT_LOG_VERBOSE("xdg_output.logical_size: %dx%d received", width, height); +} +void handle_xdg_output_done(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // auto *oref = static_cast(data); + + BONGOCAT_LOG_VERBOSE("xdg_output.done: done received"); +} - namespace hyprland { - static int fs_update_state(wayland_session_t& ctx) { - if (wayland::hyprland::window_info_t win; wayland::hyprland::get_active_window(win)) { - bool fullscreen_on_same_output = false; - for (size_t i = 0; i < ctx.output_count; i++) { - if (ctx.outputs[i].hypr_id == win.monitor_id) { - if (ctx.wayland_context.output == ctx.outputs[i].wl_output) { - fullscreen_on_same_output = true; - break; - } - } - } - if (fullscreen_on_same_output) { - details::fs_update_state(ctx, win.fullscreen); - return win.fullscreen ? 1 : 0; - } - - details::fs_update_state(ctx, false); - return 0; - } - - return -1; - } - } +void handle_xdg_output_description(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, + [[maybe_unused]] const char *description) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // auto *oref = static_cast(data); - static bool fs_check_compositor_fallback() { - //BONGOCAT_LOG_VERBOSE("Using compositor-specific fullscreen detection"); + BONGOCAT_LOG_VERBOSE("xdg_output.description: description received"); +} - // Try Hyprland first - if (const int result = wayland::hyprland::fs_check_compositor_fallback(); result >= 0) { - return result == 1; - } +// ============================================================================= +// FULLSCREEN DETECTION IMPLEMENTATION +// ============================================================================= - // Try Sway as fallback - if (const int result = wayland::sway::fs_check_compositor_fallback(); result >= 0) { - return result == 1; - } +static bool fs_update_state(wayland_session_t& ctx, bool new_state) { + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping update"); + return false; + } + if (!ctx.animation_trigger_context) { + BONGOCAT_LOG_VERBOSE("Wayland not configured yet"); + return false; + } - BONGOCAT_LOG_DEBUG("No supported compositor found for fullscreen detection"); - return false; - } + // const wayland_shared_memory_t& wayland_ctx_shm = *ctx.wayland_context.ctx_shm; - static bool fs_check_status(wayland_session_t& ctx) { - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); - return false; - } + if (new_state != ctx.fs_detector.has_fullscreen_toplevel) { + ctx.fs_detector.has_fullscreen_toplevel = new_state; + ctx.wayland_context._fullscreen_detected = new_state; - if (ctx.fs_detector.manager) { - return ctx.fs_detector.has_fullscreen_toplevel; - } + BONGOCAT_LOG_INFO("Fullscreen state changed: %s", + ctx.wayland_context._fullscreen_detected ? "detected" : "cleared"); - return fs_check_compositor_fallback(); + if (ctx.wayland_context.ctx_shm != nullptr && atomic_load(&ctx.wayland_context.ctx_shm->configured)) { + request_render(*ctx.animation_trigger_context); + } else { + BONGOCAT_LOG_VERBOSE("Wayland not configured yet, skipping request rendering"); } - void fs_update_state_fallback(wayland_session_t& ctx) { - for (size_t i = 0; i < ctx.num_toplevels; ++i) { - const tracked_toplevel_t& tracked = ctx.tracked_toplevels[i]; - // Skip handles that are not mapped or destroyed - if (!tracked.handle) continue; - if (tracked.is_fullscreen) { - // Only update overlay if on our output - if (tracked.output == ctx.wayland_context.output) { - fs_update_state(ctx, true); - return; - } - } - } + return true; + } - const bool new_state = fs_check_status(ctx); - if (new_state != ctx.wayland_context._fullscreen_detected) { - fs_update_state(ctx, new_state); - } - } + return false; +} - struct update_fullscreen_state_toplevel_result_t { bool output_found{false}; bool changed{false}; }; - static update_fullscreen_state_toplevel_result_t update_fullscreen_state_toplevel(wayland_session_t& ctx, tracked_toplevel_t& tracked, bool is_fullscreen) { - bool state_changed = tracked.is_fullscreen != is_fullscreen; - tracked.is_fullscreen = is_fullscreen; - - /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert - // Only trigger overlay update if this fullscreen window is on our output - if (tracked.output == ctx.wayland_context.output && state_changed) { - state_changed = fs_update_state(ctx, is_fullscreen); - BONGOCAT_LOG_VERBOSE("Fullscreen state updated for window %p: %d", - static_cast(tracked.handle), - is_fullscreen); - return { .output_found = true, .changed = state_changed }; - } +namespace hyprland { + static int fs_update_state(wayland_session_t& ctx) { + if (wayland::hyprland::window_info_t win; wayland::hyprland::get_active_window(win)) { + bool fullscreen_on_same_output = false; + for (size_t i = 0; i < ctx.output_count; i++) { + if (ctx.outputs[i].hypr_id == win.monitor_id) { + if (ctx.wayland_context.output == ctx.outputs[i].wl_output) { + fullscreen_on_same_output = true; + break; + } + } + } + if (fullscreen_on_same_output) { + details::fs_update_state(ctx, win.fullscreen); + return win.fullscreen ? 1 : 0; + } + + details::fs_update_state(ctx, false); + return 0; + } + + return -1; + } +} // namespace hyprland + +static bool fs_check_compositor_fallback() { + // BONGOCAT_LOG_VERBOSE("Using compositor-specific fullscreen detection"); + + // Try Hyprland first + if (const int result = wayland::hyprland::fs_check_compositor_fallback(); result >= 0) { + return result == 1; + } + + // Try Sway as fallback + if (const int result = wayland::sway::fs_check_compositor_fallback(); result >= 0) { + return result == 1; + } + + BONGOCAT_LOG_DEBUG("No supported compositor found for fullscreen detection"); + return false; +} - return { .output_found = false, .changed = state_changed }; - } +static bool fs_check_status(wayland_session_t& ctx) { + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); + return false; + } - // Foreign toplevel protocol event handlers - void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, - wl_array *state) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); - return; - } - // only check for state changes when everything is ready, no need to do something before like fullscreen check - - // check if fullscreen state event change - bool is_fullscreen = false; - wl_array_for_each_typed(state_ptr, state, uint32_t) { - if (*state_ptr == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { - is_fullscreen = true; - break; - } - } + if (ctx.fs_detector.manager) { + return ctx.fs_detector.has_fullscreen_toplevel; + } - /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert - for (size_t i = 0; i < ctx.num_toplevels; ++i) { - if (ctx.tracked_toplevels[i].handle == handle) { - auto [output_found, changed] = update_fullscreen_state_toplevel(ctx, ctx.tracked_toplevels[i], is_fullscreen); - if (output_found) { - if (changed) { - BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: %d", is_fullscreen); - } - return; - } - } - } + return fs_check_compositor_fallback(); +} +void fs_update_state_fallback(wayland_session_t& ctx) { + for (size_t i = 0; i < ctx.num_toplevels; ++i) { + const tracked_toplevel_t& tracked = ctx.tracked_toplevels[i]; + // Skip handles that are not mapped or destroyed + if (!tracked.handle) + continue; + if (tracked.is_fullscreen) { + // Only update overlay if on our output + if (tracked.output == ctx.wayland_context.output) { + fs_update_state(ctx, true); + return; + } + } + } + + const bool new_state = fs_check_status(ctx); + if (new_state != ctx.wayland_context._fullscreen_detected) { + fs_update_state(ctx, new_state); + } +} - // check for hyprland - if (const int result = hyprland::fs_update_state(ctx); result >= 0) { - BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: %d (hyprland)", result); - return; - } +struct update_fullscreen_state_toplevel_result_t { + bool output_found{false}; + bool changed{false}; +}; +static update_fullscreen_state_toplevel_result_t +update_fullscreen_state_toplevel(wayland_session_t& ctx, tracked_toplevel_t& tracked, bool is_fullscreen) { + bool state_changed = tracked.is_fullscreen != is_fullscreen; + tracked.is_fullscreen = is_fullscreen; + + /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert + // Only trigger overlay update if this fullscreen window is on our output + if (tracked.output == ctx.wayland_context.output && state_changed) { + state_changed = fs_update_state(ctx, is_fullscreen); + BONGOCAT_LOG_VERBOSE("Fullscreen state updated for window %p: %d", static_cast(tracked.handle), + is_fullscreen); + return {.output_found = true, .changed = state_changed}; + } + + return {.output_found = false, .changed = state_changed}; +} - // Fallback for when no toplevel was found - const bool changed = fs_update_state(ctx, is_fullscreen); +// Foreign toplevel protocol event handlers +void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, wl_array *state) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); + return; + } + // only check for state changes when everything is ready, no need to do something before like fullscreen check + + // check if fullscreen state event change + bool is_fullscreen = false; + wl_array_for_each_typed(state_ptr, state, uint32_t) { + if (*state_ptr == ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN) { + is_fullscreen = true; + break; + } + } + + /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert + for (size_t i = 0; i < ctx.num_toplevels; ++i) { + if (ctx.tracked_toplevels[i].handle == handle) { + auto [output_found, changed] = update_fullscreen_state_toplevel(ctx, ctx.tracked_toplevels[i], is_fullscreen); + if (output_found) { if (changed) { - BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: %d", is_fullscreen); + BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: %d", is_fullscreen); } + return; + } } + } - void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *handle) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); - return; - } - - if (handle) zwlr_foreign_toplevel_handle_v1_destroy(handle); - - // remove from tracked_toplevels if present - for (size_t i = 0; i < ctx.num_toplevels; ++i) { - if (ctx.tracked_toplevels[i].handle == handle) { - ctx.tracked_toplevels[i].handle = nullptr; - // compact array to keep contiguous - for (size_t j = i; j + 1 < ctx.num_toplevels; ++j) { - ctx.tracked_toplevels[j] = ctx.tracked_toplevels[j+1]; - } - ctx.tracked_toplevels[ctx.num_toplevels - 1].handle = {}; - ctx.num_toplevels--; - break; - } - } - - BONGOCAT_LOG_DEBUG("fs_handle_toplevel.closed: Close toplevel handle"); - } + // check for hyprland + if (const int result = hyprland::fs_update_state(ctx); result >= 0) { + BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: %d (hyprland)", result); + return; + } - // Minimal event handlers for unused events - void fs_handle_title(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] const char *title) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); - - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.title: title received"); - } - - void fs_handle_app_id(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] const char *app_id) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); - - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.app_id: app_id received"); - } - - void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] wl_output *output) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - - for (size_t i = 0; i < ctx.num_toplevels; i++) { - auto &tracked = ctx.tracked_toplevels[i]; - if (tracked.handle == handle) { - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_enter: update tracked_toplevels[%i] output", i); - tracked.output = output; - if (tracked.is_fullscreen) { - if (tracked.output == ctx.wayland_context.output) { - fs_update_state(ctx, true); - } - } - break; - } - } - - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_enter: output received"); - } - - void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] wl_output *output) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - - for (size_t i = 0; i < ctx.num_toplevels; i++) { - auto &tracked = ctx.tracked_toplevels[i]; - if (tracked.handle == handle && tracked.output == output) { - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_leave: update tracked_toplevels[%i] output", i); - if (tracked.is_fullscreen && tracked.output == ctx.wayland_context.output) { - fs_update_state(ctx, false); - } - tracked.output = nullptr; - break; - } - } - - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_leave: output received"); - } - - void fs_handle_done(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); + // Fallback for when no toplevel was found + const bool changed = fs_update_state(ctx, is_fullscreen); + if (changed) { + BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: %d", is_fullscreen); + } +} - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.done: done received"); - } +void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *handle) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); + return; + } + + if (handle) + zwlr_foreign_toplevel_handle_v1_destroy(handle); + + // remove from tracked_toplevels if present + for (size_t i = 0; i < ctx.num_toplevels; ++i) { + if (ctx.tracked_toplevels[i].handle == handle) { + ctx.tracked_toplevels[i].handle = nullptr; + // compact array to keep contiguous + for (size_t j = i; j + 1 < ctx.num_toplevels; ++j) { + ctx.tracked_toplevels[j] = ctx.tracked_toplevels[j + 1]; + } + ctx.tracked_toplevels[ctx.num_toplevels - 1].handle = {}; + ctx.num_toplevels--; + break; + } + } + + BONGOCAT_LOG_DEBUG("fs_handle_toplevel.closed: Close toplevel handle"); +} - void fs_handle_parent(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *parent) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); +// Minimal event handlers for unused events +void fs_handle_title(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, + [[maybe_unused]] const char *title) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.parent: parent received"); - } + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.title: title received"); +} +void fs_handle_app_id(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, + [[maybe_unused]] const char *app_id) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - void fs_handle_manager_toplevel(void *data, [[maybe_unused]] zwlr_foreign_toplevel_manager_v1 *manager, - zwlr_foreign_toplevel_handle_v1 *toplevel) { - if (!data) { - BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - - BONGOCAT_LOG_VERBOSE("fs_toplevel_manager_listener.toplevel: toplevel received"); - - zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &fs_toplevel_listener, &ctx); - if (ctx.num_toplevels < MAX_TOP_LEVELS) { - bool already_tracked = false; - for (size_t i = 0; i < ctx.num_toplevels; i++) { - if (ctx.tracked_toplevels[i].handle == toplevel) { - already_tracked = true; - break; - } - } - if (!already_tracked) { - ctx.tracked_toplevels[ctx.num_toplevels].handle = toplevel; - ctx.num_toplevels++; - } - } else { - BONGOCAT_LOG_ERROR("fs_toplevel_manager_listener.toplevel: toplevel tracker is full, %zu max: %d", ctx.num_toplevels, MAX_TOP_LEVELS); - } + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.app_id: app_id received"); +} - BONGOCAT_LOG_DEBUG("fs_toplevel_manager_listener.toplevel: New toplevel registered for fullscreen monitoring: %zu", ctx.num_toplevels); - } +void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, + [[maybe_unused]] wl_output *output) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + for (size_t i = 0; i < ctx.num_toplevels; i++) { + auto& tracked = ctx.tracked_toplevels[i]; + if (tracked.handle == handle) { + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_enter: update tracked_toplevels[%i] output", i); + tracked.output = output; + if (tracked.is_fullscreen) { + if (tracked.output == ctx.wayland_context.output) { + fs_update_state(ctx, true); + } + } + break; + } + } + + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_enter: output received"); +} - void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *manager) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); - return; - } +void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, + [[maybe_unused]] wl_output *output) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + for (size_t i = 0; i < ctx.num_toplevels; i++) { + auto& tracked = ctx.tracked_toplevels[i]; + if (tracked.handle == handle && tracked.output == output) { + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_leave: update tracked_toplevels[%i] output", i); + if (tracked.is_fullscreen && tracked.output == ctx.wayland_context.output) { + fs_update_state(ctx, false); + } + tracked.output = nullptr; + break; + } + } + + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_leave: output received"); +} - BONGOCAT_LOG_INFO("fs_toplevel_manager_listener.finished: Foreign toplevel manager finished"); - if (manager) zwlr_foreign_toplevel_manager_v1_destroy(manager); - ctx.fs_detector.manager = nullptr; - } +void fs_handle_done(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - // ============================================================================= - // SCREEN DIMENSION MANAGEMENT - // ============================================================================= + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.done: done received"); +} - static void screen_calculate_dimensions(screen_info_t& screen_info) { - if (screen_info.received == screen_info_received_flags_t::None || - (static_cast(screen_info.received) & static_cast(screen_info_received_flags_t::Geometry)) == 0 || - (static_cast(screen_info.received) & static_cast(screen_info_received_flags_t::Mode)) == 0 || - screen_info.screen_width > 0) { - return; - } +void fs_handle_parent(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, + [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *parent) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - const bool is_rotated = (screen_info.transform == WL_OUTPUT_TRANSFORM_90 || - screen_info.transform == WL_OUTPUT_TRANSFORM_270 || - screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 || - screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_270); - - if (is_rotated) { - screen_info.screen_width = screen_info.raw_height; - screen_info.screen_height = screen_info.raw_width; - BONGOCAT_LOG_INFO("Detected rotated screen: %dx%d (transform: %d)", - screen_info.raw_height, screen_info.raw_width, screen_info.transform); - } else { - screen_info.screen_width = screen_info.raw_width; - screen_info.screen_height = screen_info.raw_height; - BONGOCAT_LOG_INFO("Detected screen: %dx%d (transform: %d)", - screen_info.raw_width, screen_info.raw_height, screen_info.transform); - } - } + BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.parent: parent received"); +} +void fs_handle_manager_toplevel(void *data, [[maybe_unused]] zwlr_foreign_toplevel_manager_v1 *manager, + zwlr_foreign_toplevel_handle_v1 *toplevel) { + if (!data) { + BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + BONGOCAT_LOG_VERBOSE("fs_toplevel_manager_listener.toplevel: toplevel received"); + + zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &fs_toplevel_listener, &ctx); + if (ctx.num_toplevels < MAX_TOP_LEVELS) { + bool already_tracked = false; + for (size_t i = 0; i < ctx.num_toplevels; i++) { + if (ctx.tracked_toplevels[i].handle == toplevel) { + already_tracked = true; + break; + } + } + if (!already_tracked) { + ctx.tracked_toplevels[ctx.num_toplevels].handle = toplevel; + ctx.num_toplevels++; + } + } else { + BONGOCAT_LOG_ERROR("fs_toplevel_manager_listener.toplevel: toplevel tracker is full, %zu max: %d", + ctx.num_toplevels, MAX_TOP_LEVELS); + } + + BONGOCAT_LOG_DEBUG("fs_toplevel_manager_listener.toplevel: New toplevel registered for fullscreen monitoring: %zu", + ctx.num_toplevels); +} - // ============================================================================= - // WAYLAND EVENT HANDLERS - // ============================================================================= +void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *manager) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); + return; + } + + BONGOCAT_LOG_INFO("fs_toplevel_manager_listener.finished: Foreign toplevel manager finished"); + if (manager) + zwlr_foreign_toplevel_manager_v1_destroy(manager); + ctx.fs_detector.manager = nullptr; +} - void layer_surface_configure(void *data, - zwlr_layer_surface_v1 *ls, - uint32_t serial, [[maybe_unused]] uint32_t w, [[maybe_unused]] uint32_t h) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); +// ============================================================================= +// SCREEN DIMENSION MANAGEMENT +// ============================================================================= + +static void screen_calculate_dimensions(screen_info_t& screen_info) { + if (screen_info.received == screen_info_received_flags_t::None || + (static_cast(screen_info.received) & static_cast(screen_info_received_flags_t::Geometry)) == + 0 || + (static_cast(screen_info.received) & static_cast(screen_info_received_flags_t::Mode)) == 0 || + screen_info.screen_width > 0) { + return; + } + + const bool is_rotated = + (screen_info.transform == WL_OUTPUT_TRANSFORM_90 || screen_info.transform == WL_OUTPUT_TRANSFORM_270 || + screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 || + screen_info.transform == WL_OUTPUT_TRANSFORM_FLIPPED_270); + + if (is_rotated) { + screen_info.screen_width = screen_info.raw_height; + screen_info.screen_height = screen_info.raw_width; + BONGOCAT_LOG_INFO("Detected rotated screen: %dx%d (transform: %d)", screen_info.raw_height, screen_info.raw_width, + screen_info.transform); + } else { + screen_info.screen_width = screen_info.raw_width; + screen_info.screen_height = screen_info.raw_height; + BONGOCAT_LOG_INFO("Detected screen: %dx%d (transform: %d)", screen_info.raw_width, screen_info.raw_height, + screen_info.transform); + } +} - assert(ctx.animation_trigger_context != nullptr); - assert(ctx.wayland_context.ctx_shm != nullptr); - wayland_shared_memory_t& wayland_ctx_shm = *ctx.wayland_context.ctx_shm; +// ============================================================================= +// WAYLAND EVENT HANDLERS +// ============================================================================= + +void layer_surface_configure(void *data, zwlr_layer_surface_v1 *ls, uint32_t serial, [[maybe_unused]] uint32_t w, + [[maybe_unused]] uint32_t h) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + assert(ctx.animation_trigger_context != nullptr); + assert(ctx.wayland_context.ctx_shm != nullptr); + wayland_shared_memory_t& wayland_ctx_shm = *ctx.wayland_context.ctx_shm; + + zwlr_layer_surface_v1_ack_configure(ls, serial); + atomic_store(&wayland_ctx_shm.configured, true); + if (atomic_load(&ctx.ready)) { + // trigger initial rendering + request_render(*ctx.animation_trigger_context); + } + + BONGOCAT_LOG_DEBUG("layer_surface.configure: Layer surface configured: %dx%d", w, h); +} +void layer_surface_closed(void *data, [[maybe_unused]] zwlr_layer_surface_v1 *ls) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); + return; + } + + BONGOCAT_LOG_VERBOSE("layer_surface.closed: Layer surface closed"); +} - zwlr_layer_surface_v1_ack_configure(ls, serial); - atomic_store(&wayland_ctx_shm.configured, true); - if (atomic_load(&ctx.ready)) { - // trigger initial rendering - request_render(*ctx.animation_trigger_context); - } +void xdg_wm_base_ping(void *data, xdg_wm_base *wm_base, uint32_t serial) { + assert(data); + [[maybe_unused]] wayland_session_t& ctx = *static_cast(data); - BONGOCAT_LOG_DEBUG("layer_surface.configure: Layer surface configured: %dx%d", w, h); - } - void layer_surface_closed(void *data, [[maybe_unused]] zwlr_layer_surface_v1 *ls) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); - return; - } - - BONGOCAT_LOG_VERBOSE("layer_surface.closed: Layer surface closed"); - } + BONGOCAT_LOG_VERBOSE("xdg_wm_base.ping: base pong %x", serial); + xdg_wm_base_pong(wm_base, serial); +} - void xdg_wm_base_ping(void *data, xdg_wm_base *wm_base, uint32_t serial) { - assert(data); - [[maybe_unused]] wayland_session_t& ctx = *static_cast(data); +void output_geometry(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] int32_t x, + [[maybe_unused]] int32_t y, [[maybe_unused]] int32_t physical_width, + [[maybe_unused]] int32_t physical_height, [[maybe_unused]] int32_t subpixel, + [[maybe_unused]] const char *make, [[maybe_unused]] const char *model, int32_t transform) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + for (size_t i = 0; i < MAX_OUTPUTS; i++) { + if (ctx.screen_infos[i].wl_output == wl_output) { + ctx.screen_infos[i].transform = transform; + ctx.screen_infos[i].received = + static_cast(static_cast(ctx.screen_infos[i].received) | + static_cast(screen_info_received_flags_t::Geometry)); + screen_calculate_dimensions(ctx.screen_infos[i]); + } + } + BONGOCAT_LOG_DEBUG("wl_output.geometry: Output transform: %d", transform); +} - BONGOCAT_LOG_VERBOSE("xdg_wm_base.ping: base pong %x", serial); - xdg_wm_base_pong(wm_base, serial); - } +void output_mode(void *data, [[maybe_unused]] wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, + [[maybe_unused]] int32_t refresh) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + BONGOCAT_LOG_VERBOSE("wl_output.mode: mode received: %u", flags); + + if (flags & WL_OUTPUT_MODE_CURRENT) { + for (size_t i = 0; i < MAX_OUTPUTS; i++) { + if (ctx.screen_infos[i].wl_output == wl_output) { + ctx.screen_infos[i].raw_width = width; + ctx.screen_infos[i].raw_height = height; + ctx.screen_infos[i].received = + static_cast(static_cast(ctx.screen_infos[i].received) | + static_cast(screen_info_received_flags_t::Mode)); + BONGOCAT_LOG_DEBUG("wl_output.mode: Received raw screen mode: %dx%d", width, height); + screen_calculate_dimensions(ctx.screen_infos[i]); + } + } + } +} - void output_geometry(void *data, - [[maybe_unused]] wl_output *wl_output, - [[maybe_unused]] int32_t x, - [[maybe_unused]] int32_t y, - [[maybe_unused]] int32_t physical_width, - [[maybe_unused]] int32_t physical_height, - [[maybe_unused]] int32_t subpixel, - [[maybe_unused]] const char *make, - [[maybe_unused]] const char *model, - int32_t transform) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - - for (size_t i = 0; i < MAX_OUTPUTS; i++) { - if (ctx.screen_infos[i].wl_output == wl_output) { - ctx.screen_infos[i].transform = transform; - ctx.screen_infos[i].received = static_cast(static_cast(ctx.screen_infos[i].received) | static_cast( - screen_info_received_flags_t::Geometry)); - screen_calculate_dimensions(ctx.screen_infos[i]); - } - } - BONGOCAT_LOG_DEBUG("wl_output.geometry: Output transform: %d", transform); - } +void output_done(void *data, [[maybe_unused]] wl_output *wl_output) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); - void output_mode(void *data , - [[maybe_unused]] wl_output *wl_output, - uint32_t flags, int32_t width, int32_t height, - [[maybe_unused]] int32_t refresh) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - - BONGOCAT_LOG_VERBOSE("wl_output.mode: mode received: %u", flags); - - if (flags & WL_OUTPUT_MODE_CURRENT) { - for (size_t i = 0; i < MAX_OUTPUTS; i++) { - if (ctx.screen_infos[i].wl_output == wl_output) { - ctx.screen_infos[i].raw_width = width; - ctx.screen_infos[i].raw_height = height; - ctx.screen_infos[i].received = static_cast(static_cast(ctx.screen_infos[i].received) | static_cast( - screen_info_received_flags_t::Mode)); - BONGOCAT_LOG_DEBUG("wl_output.mode: Received raw screen mode: %dx%d", width, height); - screen_calculate_dimensions(ctx.screen_infos[i]); - } - } - } + for (size_t i = 0; i < MAX_OUTPUTS; i++) { + if (ctx.screen_infos[i].wl_output == wl_output) { + screen_calculate_dimensions(ctx.screen_infos[i]); } + } + BONGOCAT_LOG_DEBUG("wl_output.done: Output configuration complete"); +} - void output_done(void *data, [[maybe_unused]] wl_output *wl_output) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); +void output_scale(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] int32_t factor) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - for (size_t i = 0; i < MAX_OUTPUTS; i++) { - if (ctx.screen_infos[i].wl_output == wl_output) { - screen_calculate_dimensions(ctx.screen_infos[i]); - } - } - BONGOCAT_LOG_DEBUG("wl_output.done: Output configuration complete"); - } + // Scale not needed for our use case + BONGOCAT_LOG_VERBOSE("wl_output.scale: factor received"); +} - void output_scale(void *data, - [[maybe_unused]] wl_output *wl_output, - [[maybe_unused]] int32_t factor) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); +void output_name(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] const char *name) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - // Scale not needed for our use case - BONGOCAT_LOG_VERBOSE("wl_output.scale: factor received"); - } + BONGOCAT_LOG_VERBOSE("wl_output.name: name received"); +} - void output_name(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] const char *name) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); +void output_description(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] const char *name) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + // wayland_session_t& ctx = *static_cast(data); - BONGOCAT_LOG_VERBOSE("wl_output.name: name received"); - } + BONGOCAT_LOG_VERBOSE("wl_output.description: description received"); +} - void output_description(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] const char *name) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - //wayland_session_t& ctx = *static_cast(data); +void buffer_release(void *data, wl_buffer *buffer) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_shm_buffer_t& wayland_shm_buffer = *static_cast(data); + + if (wayland_shm_buffer.buffer == buffer) { + atomic_store(&wayland_shm_buffer.busy, false); + BONGOCAT_LOG_VERBOSE("wl_buffer.release: buffer %d released", wayland_shm_buffer.index); + + /* + if (atomic_exchange(&wayland_shm_buffer._wayland_context->_redraw_after_frame, false)) { + // @TODO: render immediately (optional) + } + */ + } else { + BONGOCAT_LOG_VERBOSE("wl_buffer.release: buffer is not matching with data.buffer"); + } +} - BONGOCAT_LOG_VERBOSE("wl_output.description: description received"); - } +void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { + if (!data) { + BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); + return; + } + auto& ctx = *static_cast(data); + + if (!atomic_load(&ctx.ready)) { + BONGOCAT_LOG_WARNING("Wayland configured yet, skipping handling"); + return; + } + if (!ctx.animation_trigger_context) { + BONGOCAT_LOG_WARNING("Wayland configured yet, skipping handling"); + return; + } + + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = *ctx->animation_context; + // animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; + // wayland_shared_memory_t& wayland_ctx_shm = wayland_ctx->ctx_shm; + // read-only + assert(wayland_ctx._local_copy_config != nullptr); + // const config::config_t& current_config = *wayland_ctx._local_copy_config; + // const animation_shared_memory_t *const anim_shm = anim->shm; + // assert(anim_shm); + + // Clear frame callback under lock + { + platform::LockGuard guard(wayland_ctx._frame_cb_lock); + if (wayland_ctx._frame_cb == cb) { + wl_callback_destroy(wayland_ctx._frame_cb); + wayland_ctx._frame_cb = nullptr; + atomic_store(&wayland_ctx._frame_pending, false); + BONGOCAT_LOG_VERBOSE("wl_callback.done: frame done"); + } else { + BONGOCAT_LOG_VERBOSE("wl_callback.done: cb is not matching"); + } + } + + // Fallback for missed buffer.release + platform::wayland::wayland_shared_memory_t *shm_ctx = wayland_ctx.ctx_shm.ptr; + assert(shm_ctx); + for (size_t i = 0; i < platform::wayland::WAYLAND_NUM_BUFFERS; i++) { + auto& buf = shm_ctx->buffers[i]; + if (atomic_load(&buf.busy)) { + atomic_store(&buf.busy, false); + BONGOCAT_LOG_WARNING("wl_callback.done: fallback released stuck buffer %zu (missed wl_buffer.release)", i); + } + } + + if (atomic_exchange(&wayland_ctx._redraw_after_frame, false)) { + // render immediately + BONGOCAT_LOG_VERBOSE("wl_callback.done: redraw"); + animation::draw_bar(ctx); + } +} +// ============================================================================= +// WAYLAND PROTOCOL REGISTRY +// ============================================================================= + +void registry_global(void *data, wl_registry *reg, uint32_t name, const char *iface, [[maybe_unused]] uint32_t ver) { + if (!data) { + BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + + BONGOCAT_LOG_VERBOSE("wl_registry.global: registry received: %s", iface); + + if (strcmp(iface, wl_compositor_interface.name) == 0) { + ctx.wayland_context.compositor = + static_cast(wl_registry_bind(reg, name, &wl_compositor_interface, 4)); + BONGOCAT_LOG_VERBOSE("wl_registry.global: compositor registry bind"); + } else if (strcmp(iface, wl_shm_interface.name) == 0) { + ctx.wayland_context.shm = static_cast(wl_registry_bind(reg, name, &wl_shm_interface, 1)); + BONGOCAT_LOG_VERBOSE("wl_registry.global: shm registry bind"); + } else if (strcmp(iface, zwlr_layer_shell_v1_interface.name) == 0) { + ctx.wayland_context.layer_shell = + static_cast(wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 1)); + BONGOCAT_LOG_VERBOSE("wl_registry.global: layer_shell registry bind"); + } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { + ctx.wayland_context.xdg_wm_base = + static_cast(wl_registry_bind(reg, name, &xdg_wm_base_interface, 1)); + BONGOCAT_LOG_VERBOSE("wl_registry.global: xdg_wm_base registry bind"); + if (ctx.wayland_context.xdg_wm_base) { + xdg_wm_base_add_listener(ctx.wayland_context.xdg_wm_base, &xdg_wm_base_listener, &ctx); + } + } else if (strcmp(iface, zxdg_output_manager_v1_interface.name) == 0) { + ctx.xdg_output_manager = + static_cast(wl_registry_bind(reg, name, &zxdg_output_manager_v1_interface, 3)); + BONGOCAT_LOG_VERBOSE("wl_registry.global: xdg_output_manager registry bind"); + } else if (strcmp(iface, wl_output_interface.name) == 0) { + if (ctx.output_count < MAX_OUTPUTS) { + ctx.outputs[ctx.output_count].name = name; + ctx.outputs[ctx.output_count].wl_output = + static_cast(wl_registry_bind(reg, name, &wl_output_interface, 2)); + wl_output_add_listener(ctx.outputs[ctx.output_count].wl_output, &output_listener, &ctx); + BONGOCAT_LOG_VERBOSE("wl_registry.global: wl_output registry bind: %i", ctx.output_count); + + // If we lost our output, get xdg_output to check if this is the one + // reconnecting + if (atomic_load(&ctx.output_lost) && ctx.xdg_output_manager) { + ctx.outputs[ctx.output_count].xdg_output = + zxdg_output_manager_v1_get_xdg_output(ctx.xdg_output_manager, ctx.outputs[ctx.output_count].wl_output); + ctx.outputs[ctx.output_count].received = flag_remove( + ctx.outputs[ctx.output_count].received, output_ref_received_flags_t::Name); + ctx.outputs[ctx.output_count].wayland = &ctx; + zxdg_output_v1_add_listener(ctx.outputs[ctx.output_count].xdg_output, &xdg_output_listener, + &ctx.outputs[ctx.output_count]); + BONGOCAT_LOG_VERBOSE("wl_registry.global: New output appeared while output_lost, checking name..."); + } + + ctx.output_count++; + } + } else if (strcmp(iface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { + ctx.fs_detector.manager = static_cast( + wl_registry_bind(reg, name, &zwlr_foreign_toplevel_manager_v1_interface, 3)); + BONGOCAT_LOG_VERBOSE("wl_registry.global: foreign_toplevel_manager (fs_detector.manager) registry bind"); + if (ctx.fs_detector.manager) { + zwlr_foreign_toplevel_manager_v1_add_listener(ctx.fs_detector.manager, &fs_manager_listener, &ctx); + BONGOCAT_LOG_INFO( + "wl_registry.global: Foreign toplevel manager bound - using Wayland protocol for fullscreen detection"); + } + } +} - void buffer_release(void *data, wl_buffer *buffer) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_shm_buffer_t& wayland_shm_buffer = *static_cast(data); - - if (wayland_shm_buffer.buffer == buffer) { - atomic_store(&wayland_shm_buffer.busy, false); - BONGOCAT_LOG_VERBOSE("wl_buffer.release: buffer %d released", wayland_shm_buffer.index); - - /* - if (atomic_exchange(&wayland_shm_buffer._wayland_context->_redraw_after_frame, false)) { - // @TODO: render immediately (optional) - } - */ - } else { - BONGOCAT_LOG_VERBOSE("wl_buffer.release: buffer is not matching with data.buffer"); - } - } +void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe_unused]] uint32_t name) { + if (!data || !registry) { + BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); + return; + } + wayland_session_t& ctx = *static_cast(data); + platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = ctx.animation_trigger_context->anim; + // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; + + if (!wayland_ctx.ctx_shm.ptr) { + BONGOCAT_LOG_WARNING("Handler called with null wayland_ctx.ctx_shm (ignored)"); + return; + } + assert(wayland_ctx.ctx_shm.ptr); + platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; + + BONGOCAT_LOG_VERBOSE("wl_registry.global_remove: registry received"); + + // Check if the removed global is our bound output + if (name == ctx.wayland_context.bound_output_name && ctx.wayland_context.bound_output_name != 0) { + BONGOCAT_LOG_VERBOSE("Bound output disconnected (registry name %u)", name); + atomic_store(&ctx.output_lost, true); + atomic_store(&wayland_ctx_shm.configured, false); + + // Clean up the old output reference + wayland_ctx.output = nullptr; + + // Remove from outputs array + for (size_t i = 0; i < ctx.output_count; ++i) { + if (ctx.outputs[i].name == name) { + if (ctx.outputs[i].xdg_output) { + zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); + ctx.outputs[i].xdg_output = nullptr; + } + if (ctx.outputs[i].wl_output) { + wl_output_destroy(ctx.outputs[i].wl_output); + ctx.outputs[i].wl_output = nullptr; + } + // Shift remaining outputs + for (size_t j = i; j < ctx.output_count - 1; ++j) { + ctx.outputs[j] = ctx.outputs[j + 1]; + } + // Zero out the now-unused slot + ctx.outputs[ctx.output_count - 1] = {}; + ctx.output_count--; + break; + } + } + } +} - void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { - if (!data) { - BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); - return; - } - auto& ctx = *static_cast(data); +// Helper to handle output reconnection +void wayland_handle_output_reconnect(output_ref_t *oref, struct wl_output *new_output, uint32_t registry_name, + const char *output_name) { + assert(oref->wayland); - if (!atomic_load(&ctx.ready)) { - BONGOCAT_LOG_WARNING("Wayland configured yet, skipping handling"); - return; - } - if (!ctx.animation_trigger_context) { - BONGOCAT_LOG_WARNING("Wayland configured yet, skipping handling"); - return; - } + wayland_context_t& wayland_ctx = oref->wayland->wayland_context; + // animation_context_t& anim = *ctx.animation_context; + // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = *ctx->animation_context; - //animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; - //wayland_shared_memory_t& wayland_ctx_shm = wayland_ctx->ctx_shm; - // read-only - assert(wayland_ctx._local_copy_config != nullptr); - //const config::config_t& current_config = *wayland_ctx._local_copy_config; - //const animation_shared_memory_t *const anim_shm = anim->shm; - //assert(anim_shm); - - // Clear frame callback under lock - { - platform::LockGuard guard (wayland_ctx._frame_cb_lock); - if (wayland_ctx._frame_cb == cb) { - wl_callback_destroy(wayland_ctx._frame_cb); - wayland_ctx._frame_cb = nullptr; - atomic_store(&wayland_ctx._frame_pending, false); - BONGOCAT_LOG_VERBOSE("wl_callback.done: frame done"); - } else { - BONGOCAT_LOG_VERBOSE("wl_callback.done: cb is not matching"); - } - } + // read-only config + // assert(wayland_ctx._local_copy_config != nullptr); + // const config::config_t& current_config = *wayland_ctx._local_copy_config; - // Fallback for missed buffer.release - platform::wayland::wayland_shared_memory_t* shm_ctx = wayland_ctx.ctx_shm.ptr; - assert(shm_ctx); - for (size_t i = 0; i < platform::wayland::WAYLAND_NUM_BUFFERS; i++) { - auto& buf = shm_ctx->buffers[i]; - if (atomic_load(&buf.busy)) { - atomic_store(&buf.busy, false); - BONGOCAT_LOG_WARNING("wl_callback.done: fallback released stuck buffer %zu (missed wl_buffer.release)", i); - } - } + BONGOCAT_LOG_INFO("Output '%s' reconnected (registry name %u)", output_name, registry_name); - if (atomic_exchange(&wayland_ctx._redraw_after_frame, false)) { - // render immediately - BONGOCAT_LOG_VERBOSE("wl_callback.done: redraw"); - animation::draw_bar(ctx); - } - } + // Clean up old surface if it exists + cleanup_wayland_context_surface(wayland_ctx); - // ============================================================================= - // WAYLAND PROTOCOL REGISTRY - // ============================================================================= + // Set new output + wayland_ctx.output = new_output; + wayland_ctx.bound_output_name = registry_name; + atomic_store(&oref->wayland->output_lost, false); - void registry_global(void *data , wl_registry *reg, - uint32_t name, const char *iface, - [[maybe_unused]] uint32_t ver) { - if (!data) { - BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - - BONGOCAT_LOG_VERBOSE("wl_registry.global: registry received: %s", iface); - - if (strcmp(iface, wl_compositor_interface.name) == 0) { - ctx.wayland_context.compositor = static_cast(wl_registry_bind(reg, name, &wl_compositor_interface, 4)); - BONGOCAT_LOG_VERBOSE("wl_registry.global: compositor registry bind"); - } else if (strcmp(iface, wl_shm_interface.name) == 0) { - ctx.wayland_context.shm = static_cast(wl_registry_bind(reg, name, &wl_shm_interface, 1)); - BONGOCAT_LOG_VERBOSE("wl_registry.global: shm registry bind"); - } else if (strcmp(iface, zwlr_layer_shell_v1_interface.name) == 0) { - ctx.wayland_context.layer_shell = static_cast(wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 1)); - BONGOCAT_LOG_VERBOSE("wl_registry.global: layer_shell registry bind"); - } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { - ctx.wayland_context.xdg_wm_base = static_cast(wl_registry_bind(reg, name, &xdg_wm_base_interface, 1)); - BONGOCAT_LOG_VERBOSE("wl_registry.global: xdg_wm_base registry bind"); - if (ctx.wayland_context.xdg_wm_base) { - xdg_wm_base_add_listener(ctx.wayland_context.xdg_wm_base, &xdg_wm_base_listener, &ctx); - } - } else if (strcmp(iface, zxdg_output_manager_v1_interface.name) == 0) { - ctx.xdg_output_manager = static_cast(wl_registry_bind(reg, name, &zxdg_output_manager_v1_interface, 3)); - BONGOCAT_LOG_VERBOSE("wl_registry.global: xdg_output_manager registry bind"); - } else if (strcmp(iface, wl_output_interface.name) == 0) { - if (ctx.output_count < MAX_OUTPUTS) { - ctx.outputs[ctx.output_count].name = name; - ctx.outputs[ctx.output_count].wl_output = static_cast(wl_registry_bind(reg, name, &wl_output_interface, 2)); - wl_output_add_listener(ctx.outputs[ctx.output_count].wl_output, &output_listener, &ctx); - BONGOCAT_LOG_VERBOSE("wl_registry.global: wl_output registry bind: %i", ctx.output_count); - - // If we lost our output, get xdg_output to check if this is the one - // reconnecting - if (atomic_load(&ctx.output_lost) && ctx.xdg_output_manager) { - ctx.outputs[ctx.output_count].xdg_output = - zxdg_output_manager_v1_get_xdg_output( - ctx.xdg_output_manager, ctx.outputs[ctx.output_count].wl_output); - ctx.outputs[ctx.output_count].received = flag_remove(ctx.outputs[ctx.output_count].received, output_ref_received_flags_t::Name); - ctx.outputs[ctx.output_count].wayland = &ctx; - zxdg_output_v1_add_listener(ctx.outputs[ctx.output_count].xdg_output, - &xdg_output_listener, - &ctx.outputs[ctx.output_count]); - BONGOCAT_LOG_VERBOSE("wl_registry.global: New output appeared while output_lost, checking name..."); - } - - ctx.output_count++; - } - } else if (strcmp(iface, zwlr_foreign_toplevel_manager_v1_interface.name) == 0) { - ctx.fs_detector.manager = static_cast(wl_registry_bind( - reg, name, &zwlr_foreign_toplevel_manager_v1_interface, 3)); - BONGOCAT_LOG_VERBOSE("wl_registry.global: foreign_toplevel_manager (fs_detector.manager) registry bind"); - if (ctx.fs_detector.manager) { - zwlr_foreign_toplevel_manager_v1_add_listener(ctx.fs_detector.manager, &fs_manager_listener, &ctx); - BONGOCAT_LOG_INFO("wl_registry.global: Foreign toplevel manager bound - using Wayland protocol for fullscreen detection"); - } - } + // Recreate surface on new output + if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { + assert(wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED); + if (wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED) { + atomic_store(&wayland_ctx.ctx_shm->configured, true); } - - void registry_remove(void *data, - [[maybe_unused]] wl_registry *registry, - [[maybe_unused]] uint32_t name) { - if (!data || !registry) { - BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); - return; - } - wayland_session_t& ctx = *static_cast(data); - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = ctx.animation_trigger_context->anim; - //animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - - if (!wayland_ctx.ctx_shm.ptr) { - BONGOCAT_LOG_WARNING("Handler called with null wayland_ctx.ctx_shm (ignored)"); - return; - } - assert(wayland_ctx.ctx_shm.ptr); - platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; - - BONGOCAT_LOG_VERBOSE("wl_registry.global_remove: registry received"); - - // Check if the removed global is our bound output - if (name == ctx.wayland_context.bound_output_name && ctx.wayland_context.bound_output_name != 0) { - BONGOCAT_LOG_VERBOSE("Bound output disconnected (registry name %u)", name); - atomic_store(&ctx.output_lost, true); - atomic_store(&wayland_ctx_shm.configured, false); - - // Clean up the old output reference - wayland_ctx.output = nullptr; - - // Remove from outputs array - for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].name == name) { - if (ctx.outputs[i].xdg_output) { - zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); - ctx.outputs[i].xdg_output = nullptr; - } - if (ctx.outputs[i].wl_output) { - wl_output_destroy(ctx.outputs[i].wl_output); - ctx.outputs[i].wl_output = nullptr; - } - // Shift remaining outputs - for (size_t j = i; j < ctx.output_count - 1; ++j) { - ctx.outputs[j] = ctx.outputs[j + 1]; - } - // Zero out the now-unused slot - ctx.outputs[ctx.output_count - 1] = {}; - ctx.output_count--; - break; - } - } - } + BONGOCAT_LOG_INFO("Surface recreated on reconnected output"); + if constexpr (WAYLAND_NUM_BUFFERS != 1) { + wl_display_roundtrip(wayland_ctx.display); } - - // Helper to handle output reconnection - void wayland_handle_output_reconnect(output_ref_t *oref, - struct wl_output *new_output, - uint32_t registry_name, - const char *output_name) { - assert(oref->wayland); - - wayland_context_t& wayland_ctx = oref->wayland->wayland_context; - //animation_context_t& anim = *ctx.animation_context; - //animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; - - // read-only config - //assert(wayland_ctx._local_copy_config != nullptr); - //const config::config_t& current_config = *wayland_ctx._local_copy_config; - - BONGOCAT_LOG_INFO("Output '%s' reconnected (registry name %u)", output_name, - registry_name); - - // Clean up old surface if it exists - cleanup_wayland_context_surface(wayland_ctx); - - // Set new output - wayland_ctx.output = new_output; - wayland_ctx.bound_output_name = registry_name; - atomic_store(&oref->wayland->output_lost, false); - - // Recreate surface on new output - if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { - assert(wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED); - if (wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED) { - atomic_store(&wayland_ctx.ctx_shm->configured, true); - } - BONGOCAT_LOG_INFO("Surface recreated on reconnected output"); - if constexpr (WAYLAND_NUM_BUFFERS != 1) { - wl_display_roundtrip(wayland_ctx.display); - } - if (oref->wayland->animation_trigger_context) { - request_render(*oref->wayland->animation_trigger_context); - } - } else { - BONGOCAT_LOG_ERROR("Failed to recreate surface on reconnected output"); - } + if (oref->wayland->animation_trigger_context) { + request_render(*oref->wayland->animation_trigger_context); } + } else { + BONGOCAT_LOG_ERROR("Failed to recreate surface on reconnected output"); + } } +} // namespace bongocat::platform::wayland::details diff --git a/src/platform/wayland_hyprland.cpp b/src/platform/wayland_hyprland.cpp index 51f1c5f2..3c7533c6 100644 --- a/src/platform/wayland_hyprland.cpp +++ b/src/platform/wayland_hyprland.cpp @@ -1,110 +1,115 @@ #include "wayland_hyprland.h" + #include "utils/error.h" + #include #include -#include #include -#include -#include +#include #include -#include #include +#include +#include #include +#include namespace bongocat::platform::wayland::hyprland { int fs_check_compositor_fallback() { - FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); - if (fp) { - bool is_fullscreen = false; - - char line[LINE_BUF]; - while (fgets(line, LINE_BUF, fp)) { - const size_t len = strlen(line); - if (len > 0 && line[len-1] == '\n') { - line[len-1] = '\0'; - } - - if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || strstr(line, "fullscreen: true")) { - is_fullscreen = true; - BONGOCAT_LOG_DEBUG("Fullscreen detected in Hyprland"); - break; - } - } + FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); + if (fp) { + bool is_fullscreen = false; + + char line[LINE_BUF]; + while (fgets(line, LINE_BUF, fp)) { + const size_t len = strlen(line); + if (len > 0 && line[len - 1] == '\n') { + line[len - 1] = '\0'; + } - pclose(fp); - return is_fullscreen ? 1 : 0; + if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || strstr(line, "fullscreen: true")) { + is_fullscreen = true; + BONGOCAT_LOG_DEBUG("Fullscreen detected in Hyprland"); + break; + } } - return -1; + pclose(fp); + return is_fullscreen ? 1 : 0; + } + + return -1; } void update_outputs_with_monitor_ids(wayland_session_t& ctx) { - FILE *fp = popen("hyprctl monitors 2>/dev/null", "r"); - if (!fp) return; + FILE *fp = popen("hyprctl monitors 2>/dev/null", "r"); + if (!fp) + return; - char line[LINE_BUF]; - while (fgets(line, LINE_BUF, fp)) { - int id = -1; - char name[256] = {0}; - int result = sscanf(line, "Monitor %d \"%255[^\"]\"", &id, name); - if (result < 2) { - result = sscanf(line, "Monitor %255s (ID %d)", name, &id); - } + char line[LINE_BUF]; + while (fgets(line, LINE_BUF, fp)) { + int id = -1; + char name[256] = {0}; + int result = sscanf(line, "Monitor %d \"%255[^\"]\"", &id, name); + if (result < 2) { + result = sscanf(line, "Monitor %255s (ID %d)", name, &id); + } - if (result == 2) { - for (size_t i = 0; i < ctx.output_count; i++) { - // match by xdg-output name - if (has_flag(ctx.outputs[i].received, output_ref_received_flags_t::Name) && strcmp(ctx.outputs[i].name_str, name) == 0) { - ctx.outputs[i].hypr_id = id; - BONGOCAT_LOG_DEBUG("Mapped xdg-output '%s' to Hyprland ID %d", name, id); - break; - } - } + if (result == 2) { + for (size_t i = 0; i < ctx.output_count; i++) { + // match by xdg-output name + if (has_flag(ctx.outputs[i].received, output_ref_received_flags_t::Name) && + strcmp(ctx.outputs[i].name_str, name) == 0) { + ctx.outputs[i].hypr_id = id; + BONGOCAT_LOG_DEBUG("Mapped xdg-output '%s' to Hyprland ID %d", name, id); + break; } + } } + } - pclose(fp); + pclose(fp); } bool get_active_window(window_info_t& win) { - FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); - if (!fp) return false; + FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); + if (!fp) + return false; - bool has_window = false; - win.monitor_id = -1; - win.fullscreen = false; + bool has_window = false; + win.monitor_id = -1; + win.fullscreen = false; - char line[LINE_BUF]; - while (fgets(line, LINE_BUF, fp)) { - // monitor: 0 - if (strstr(line, "monitor:")) { - sscanf(line, "%*[\t ]monitor: %d", &win.monitor_id); - has_window = true; - } - // fullscreen: 0/1/2 - if (strstr(line, "fullscreen:")) { - int val; - if (sscanf(line, "%*[\t ]fullscreen: %d", &val) == 1) { - win.fullscreen = (val != 0); - } - } - // at: X,Y - if (strstr(line, "at:")) { - if (sscanf(line, "%*[\t ]at: [%d, %d]", &win.x, &win.y) < 2) { - sscanf(line, "%*[\t ]at: %d,%d", &win.x, &win.y); - } - } - // size: W,H - if (strstr(line, "size:")) { - if (sscanf(line, "%*[\t ]size: [%d, %d]", &win.width, &win.height) < 2) { - sscanf(line, "%*[\t ]size: %d,%d", &win.width, &win.height); - } - } + char line[LINE_BUF]; + while (fgets(line, LINE_BUF, fp)) { + // monitor: 0 + if (strstr(line, "monitor:")) { + sscanf(line, "%*[\t ]monitor: %d", &win.monitor_id); + has_window = true; + } + // fullscreen: 0/1/2 + if (strstr(line, "fullscreen:")) { + int val; + if (sscanf(line, "%*[\t ]fullscreen: %d", &val) == 1) { + win.fullscreen = (val != 0); + } } + // at: X,Y + if (strstr(line, "at:")) { + if (sscanf(line, "%*[\t ]at: [%d, %d]", &win.x, &win.y) < 2) { + sscanf(line, "%*[\t ]at: %d,%d", &win.x, &win.y); + } + } + // size: W,H + if (strstr(line, "size:")) { + if (sscanf(line, "%*[\t ]size: [%d, %d]", &win.width, &win.height) < 2) { + sscanf(line, "%*[\t ]size: %d,%d", &win.width, &win.height); + } + } + } - pclose(fp); - return has_window; + pclose(fp); + return has_window; } -} \ No newline at end of file +} // namespace bongocat::platform::wayland::hyprland \ No newline at end of file diff --git a/src/platform/wayland_hyprland.h b/src/platform/wayland_hyprland.h index 70e2cbf0..90fc7126 100644 --- a/src/platform/wayland_hyprland.h +++ b/src/platform/wayland_hyprland.h @@ -7,12 +7,12 @@ namespace bongocat::platform::wayland::hyprland { static inline constexpr size_t LINE_BUF = 512; struct window_info_t { - int monitor_id{-1}; // monitor number in Hyprland - int x{0}; - int y{0}; - int width{0}; - int height{0}; - bool fullscreen{false}; + int monitor_id{-1}; // monitor number in Hyprland + int x{0}; + int y{0}; + int width{0}; + int height{0}; + bool fullscreen{false}; }; extern int fs_check_compositor_fallback(); @@ -20,4 +20,4 @@ extern int fs_check_compositor_fallback(); extern void update_outputs_with_monitor_ids(wayland_session_t& ctx); extern bool get_active_window(window_info_t& win); -} +} // namespace bongocat::platform::wayland::hyprland diff --git a/src/platform/wayland_setups.cpp b/src/platform/wayland_setups.cpp index 88a5608e..0b86ecf0 100644 --- a/src/platform/wayland_setups.cpp +++ b/src/platform/wayland_setups.cpp @@ -1,396 +1,397 @@ -#include "platform/wayland-protocols.hpp" #include "platform/wayland_setups.h" +#include "../graphics/bar.h" #include "graphics/animation.h" +#include "platform/global_wayland_session.h" +#include "platform/wayland-protocols.hpp" #include "platform/wayland.h" #include "platform/wayland_shared_memory.h" -#include "platform/global_wayland_session.h" #include "utils/memory.h" -#include "../graphics/bar.h" +#include "wayland_hyprland.h" + #include -#include -#include -#include #include #include -#include +#include #include +#include +#include +#include +#include #include +#include #include #include -#include -#include -#include -#include #include - -#include "wayland_hyprland.h" -//#include "wayland_sway.h" +#include +#include +// #include "wayland_sway.h" #include "platform/wayland_callbacks.h" namespace bongocat::platform::wayland::details { - // ============================================================================= - // GLOBAL STATE AND CONFIGURATION - // ============================================================================= - - static inline constexpr int CREATE_SHM_MAX_ATTEMPTS = 100; - - static inline constexpr auto WAYLAND_LAYER_NAMESPACE = "bongocat-overlay"; - - static inline constexpr size_t CREATE_SHM_NAME_SUFFIX_LEN = 8; - static inline constexpr char CREATE_SHM_NAME_TEMPLATE[] = "/bongocat-bar-shm-XXXXXXXX"; - static inline constexpr size_t CREATE_SHM_NAME_PREFIX_LEN = LEN_ARRAY(CREATE_SHM_NAME_TEMPLATE)-1 - CREATE_SHM_NAME_SUFFIX_LEN; - static_assert((CREATE_SHM_NAME_PREFIX_LEN + CREATE_SHM_NAME_SUFFIX_LEN) == LEN_ARRAY(CREATE_SHM_NAME_TEMPLATE)-1); - - // ============================================================================= - // BUFFER AND DRAWING MANAGEMENT - // ============================================================================= - - FileDescriptor create_shm(off_t size) { - char* name = strdup(CREATE_SHM_NAME_TEMPLATE); - constexpr char charset_arr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - constexpr size_t charset_len = sizeof(charset_arr) - 1; - int fd = -1; - - random_xoshiro128 rng (slow_rand()); - for (int i = 0; i < CREATE_SHM_MAX_ATTEMPTS; i++) { - for (size_t j = 0; j < CREATE_SHM_NAME_SUFFIX_LEN; j++) { - assert(sizeof(charset_arr) - 1 > 0); - name[CREATE_SHM_NAME_PREFIX_LEN + j] = charset_arr[rng.range(0, charset_len-1)]; - } - fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); - if (fd >= 0) { - shm_unlink(name); - break; - } - } - - if (fd < 0 || ftruncate(fd, size) < 0) { - close(fd); - fd = -1; - perror("shm"); - } - - ::free(name); - return FileDescriptor(fd); +// ============================================================================= +// GLOBAL STATE AND CONFIGURATION +// ============================================================================= + +static inline constexpr int CREATE_SHM_MAX_ATTEMPTS = 100; + +static inline constexpr auto WAYLAND_LAYER_NAMESPACE = "bongocat-overlay"; + +static inline constexpr size_t CREATE_SHM_NAME_SUFFIX_LEN = 8; +static inline constexpr char CREATE_SHM_NAME_TEMPLATE[] = "/bongocat-bar-shm-XXXXXXXX"; +static inline constexpr size_t CREATE_SHM_NAME_PREFIX_LEN = + LEN_ARRAY(CREATE_SHM_NAME_TEMPLATE) - 1 - CREATE_SHM_NAME_SUFFIX_LEN; +static_assert((CREATE_SHM_NAME_PREFIX_LEN + CREATE_SHM_NAME_SUFFIX_LEN) == LEN_ARRAY(CREATE_SHM_NAME_TEMPLATE) - 1); + +// ============================================================================= +// BUFFER AND DRAWING MANAGEMENT +// ============================================================================= + +FileDescriptor create_shm(off_t size) { + char *name = strdup(CREATE_SHM_NAME_TEMPLATE); + constexpr char charset_arr[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + constexpr size_t charset_len = sizeof(charset_arr) - 1; + int fd = -1; + + random_xoshiro128 rng(slow_rand()); + for (int i = 0; i < CREATE_SHM_MAX_ATTEMPTS; i++) { + for (size_t j = 0; j < CREATE_SHM_NAME_SUFFIX_LEN; j++) { + assert(sizeof(charset_arr) - 1 > 0); + name[CREATE_SHM_NAME_PREFIX_LEN + j] = charset_arr[rng.range(0, charset_len - 1)]; + } + fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); + if (fd >= 0) { + shm_unlink(name); + break; } + } + + if (fd < 0 || ftruncate(fd, size) < 0) { + close(fd); + fd = -1; + perror("shm"); + } - // ============================================================================= - // MAIN WAYLAND INTERFACE IMPLEMENTATION - // ============================================================================= - - bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { - wayland_context_t& wayland_ctx = ctx.wayland_context; - - // read-only config - assert(wayland_ctx._local_copy_config != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config; - - wayland_ctx.output = nullptr; - wayland_ctx.bound_output_name = 0; - wayland_ctx.using_named_output = false; - if (current_config.output_name) { - for (size_t i = 0; i < ctx.output_count; ++i) { - if (has_flag(ctx.outputs[i].received, output_ref_received_flags_t::Name) && - strcmp(ctx.outputs[i].name_str, current_config.output_name) == 0) { - wayland_ctx.output = ctx.outputs[i].wl_output; - wayland_ctx._output_name_str = ctx.outputs[i].name_str; - wayland_ctx._screen_info = &ctx.screen_infos[i]; - wayland_ctx.bound_output_name = ctx.outputs[i].name; // Store registry name for tracking - wayland_ctx.using_named_output = true; // User specified this output - BONGOCAT_LOG_INFO("Matched output: %s", wayland_ctx._output_name_str); - break; - } - } - - if (!wayland_ctx.output) { - if (current_config._strict) { - BONGOCAT_LOG_ERROR("Could not find output named '%s'", current_config.output_name); - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } else { - BONGOCAT_LOG_ERROR("Could not find output named '%s', defaulting to first output", current_config.output_name); - } - } - } - - // Fallback - if (!wayland_ctx.output && ctx.output_count > 0) { - wayland_ctx.output = ctx.outputs[0].wl_output; - wayland_ctx._output_name_str = ctx.outputs[0].name_str; - wayland_ctx._screen_info = &ctx.screen_infos[0]; - wayland_ctx.bound_output_name = ctx.outputs[0].name; - wayland_ctx.using_named_output = false; // Using fallback, not a named output - BONGOCAT_LOG_WARNING("Falling back to first output: %s", wayland_ctx._output_name_str); - } - - if (!wayland_ctx.compositor || !wayland_ctx.shm || !wayland_ctx.layer_shell) { - BONGOCAT_LOG_ERROR("Missing required Wayland protocols"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - // Configure screen dimensions - int screen_width {DEFAULT_SCREEN_WIDTH}; - if (current_config.screen_width > 0) { - BONGOCAT_LOG_WARNING("Use screen width from config: %d", current_config.screen_width); - screen_width = current_config.screen_width; - } else { - // auto-detect screen width - if (wayland_ctx.output) { - wl_display_roundtrip(wayland_ctx.display); - if (wayland_ctx._screen_info && wayland_ctx._screen_info->screen_width > 0) { - BONGOCAT_LOG_INFO("Detected screen width: %d", wayland_ctx._screen_info->screen_width); - screen_width = wayland_ctx._screen_info->screen_width; - } else { - BONGOCAT_LOG_WARNING("Using default screen width: %d", DEFAULT_SCREEN_WIDTH); - screen_width = DEFAULT_SCREEN_WIDTH; - } - } else { - BONGOCAT_LOG_WARNING("No output found, using default screen width: %d", DEFAULT_SCREEN_WIDTH); - screen_width = DEFAULT_SCREEN_WIDTH; - if (current_config._strict) { - return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; - } - } - - } - wayland_ctx._screen_width = screen_width; - - return bongocat_error_t::BONGOCAT_SUCCESS; + ::free(name); + return FileDescriptor(fd); +} + +// ============================================================================= +// MAIN WAYLAND INTERFACE IMPLEMENTATION +// ============================================================================= + +bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { + wayland_context_t& wayland_ctx = ctx.wayland_context; + + // read-only config + assert(wayland_ctx._local_copy_config != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config; + + wayland_ctx.output = nullptr; + wayland_ctx.bound_output_name = 0; + wayland_ctx.using_named_output = false; + if (current_config.output_name) { + for (size_t i = 0; i < ctx.output_count; ++i) { + if (has_flag(ctx.outputs[i].received, output_ref_received_flags_t::Name) && + strcmp(ctx.outputs[i].name_str, current_config.output_name) == 0) { + wayland_ctx.output = ctx.outputs[i].wl_output; + wayland_ctx._output_name_str = ctx.outputs[i].name_str; + wayland_ctx._screen_info = &ctx.screen_infos[i]; + wayland_ctx.bound_output_name = ctx.outputs[i].name; // Store registry name for tracking + wayland_ctx.using_named_output = true; // User specified this output + BONGOCAT_LOG_INFO("Matched output: %s", wayland_ctx._output_name_str); + break; + } } - bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { - wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = *ctx.animation_context; - //animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; - - // read-only config - //assert(wayland_ctx._local_copy_config != nullptr); - //const config::config_t& current_config = *wayland_ctx._local_copy_config; - - /// @TODO: add RAII wrapper for wl_registry - wl_registry *registry = wl_display_get_registry(wayland_ctx.display); - if (!registry) { - BONGOCAT_LOG_ERROR("Failed to get Wayland registry"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - wl_registry_add_listener(registry, &details::reg_listener, &ctx); - wl_display_roundtrip(wayland_ctx.display); - - if (ctx.xdg_output_manager) { - for (size_t i = 0; i < ctx.output_count && i < MAX_OUTPUTS; i++) { - ctx.outputs[i].wayland = &ctx; - ctx.outputs[i].xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx.xdg_output_manager, ctx.outputs[i].wl_output); - zxdg_output_v1_add_listener(ctx.outputs[i].xdg_output, &details::xdg_output_listener, &ctx.outputs[i]); - ctx.screen_infos[i] = {}; - assert(ctx.outputs[i].wl_output); - ctx.screen_infos[i].wl_output = ctx.outputs[i].wl_output; - } - - // Wait for all xdg_output events - wl_display_roundtrip(wayland_ctx.display); // Process initial events - wl_display_roundtrip(wayland_ctx.display); // Ensure all `done` events arrive - BONGOCAT_LOG_DEBUG("Listener bound for xdg_output and foreign toplevel handle"); - - // DE specific inits - hyprland::update_outputs_with_monitor_ids(ctx); - } - - if (!wayland_ctx.compositor || !wayland_ctx.shm || !wayland_ctx.layer_shell) { - BONGOCAT_LOG_ERROR("Missing required Wayland protocols"); - wl_registry_destroy(registry); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - const bongocat_error_t result_update_screen_width = wayland_update_screen_width(ctx); - if (result_update_screen_width != bongocat_error_t::BONGOCAT_SUCCESS) { - wl_registry_destroy(registry); - return result_update_screen_width; - } - - // move new registry - if (wayland_ctx.registry) wl_registry_destroy(wayland_ctx.registry); - wayland_ctx.registry = registry; - registry = nullptr; - - for (size_t i = 0; i < ctx.output_count && i < MAX_OUTPUTS; i++) { - ctx.outputs[i].wayland = &ctx; - } - - return bongocat_error_t::BONGOCAT_SUCCESS; + + if (!wayland_ctx.output) { + if (current_config._strict) { + BONGOCAT_LOG_ERROR("Could not find output named '%s'", current_config.output_name); + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } else { + BONGOCAT_LOG_ERROR("Could not find output named '%s', defaulting to first output", current_config.output_name); + } } + } + + // Fallback + if (!wayland_ctx.output && ctx.output_count > 0) { + wayland_ctx.output = ctx.outputs[0].wl_output; + wayland_ctx._output_name_str = ctx.outputs[0].name_str; + wayland_ctx._screen_info = &ctx.screen_infos[0]; + wayland_ctx.bound_output_name = ctx.outputs[0].name; + wayland_ctx.using_named_output = false; // Using fallback, not a named output + BONGOCAT_LOG_WARNING("Falling back to first output: %s", wayland_ctx._output_name_str); + } + + if (!wayland_ctx.compositor || !wayland_ctx.shm || !wayland_ctx.layer_shell) { + BONGOCAT_LOG_ERROR("Missing required Wayland protocols"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + // Configure screen dimensions + int screen_width{DEFAULT_SCREEN_WIDTH}; + if (current_config.screen_width > 0) { + BONGOCAT_LOG_WARNING("Use screen width from config: %d", current_config.screen_width); + screen_width = current_config.screen_width; + } else { + // auto-detect screen width + if (wayland_ctx.output) { + wl_display_roundtrip(wayland_ctx.display); + if (wayland_ctx._screen_info && wayland_ctx._screen_info->screen_width > 0) { + BONGOCAT_LOG_INFO("Detected screen width: %d", wayland_ctx._screen_info->screen_width); + screen_width = wayland_ctx._screen_info->screen_width; + } else { + BONGOCAT_LOG_WARNING("Using default screen width: %d", DEFAULT_SCREEN_WIDTH); + screen_width = DEFAULT_SCREEN_WIDTH; + } + } else { + BONGOCAT_LOG_WARNING("No output found, using default screen width: %d", DEFAULT_SCREEN_WIDTH); + screen_width = DEFAULT_SCREEN_WIDTH; + if (current_config._strict) { + return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; + } + } + } + wayland_ctx._screen_width = screen_width; - bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { - wayland_context_t& wayland_ctx = ctx.wayland_context; - //animation_context_t& anim = *ctx.animation_context; - //animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; - - // read-only config - assert(wayland_ctx._local_copy_config != nullptr); - const config::config_t& current_config = *wayland_ctx._local_copy_config; - - wayland_ctx.surface = wl_compositor_create_surface(wayland_ctx.compositor); - if (!wayland_ctx.surface) { - BONGOCAT_LOG_ERROR("Failed to create surface"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; - switch (current_config.layer) { - case config::layer_type_t::LAYER_BACKGROUND: - layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; - break; - case config::layer_type_t::LAYER_BOTTOM: - layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; - break; - case config::layer_type_t::LAYER_TOP: - layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; - break; - case config::layer_type_t::LAYER_OVERLAY: - layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; - break; - } - wayland_ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(wayland_ctx.layer_shell, wayland_ctx.surface, wayland_ctx.output, - layer, WAYLAND_LAYER_NAMESPACE); - if (!wayland_ctx.layer_surface) { - BONGOCAT_LOG_ERROR("Failed to create layer surface"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - // Configure layer surface - uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; - switch (current_config.overlay_position) { - case config::overlay_position_t::POSITION_TOP: - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - break; - case config::overlay_position_t::POSITION_BOTTOM: - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; - break; - default: - BONGOCAT_LOG_ERROR("Invalid overlay_position %d for layer surface, set to top (default)"); - anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; - break; - } - - assert(wayland_ctx._bar_height >= 0); - //assert(current_config.bar_height <= UINT32_MAX); - if (wayland_ctx._bar_height == 0) { - BONGOCAT_LOG_ERROR("Can not set anchor with bar_height=0"); - zwlr_layer_surface_v1_destroy(wayland_ctx.layer_surface); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - zwlr_layer_surface_v1_set_anchor(wayland_ctx.layer_surface, anchor); - zwlr_layer_surface_v1_set_size(wayland_ctx.layer_surface, 0, static_cast(wayland_ctx._bar_height)); - zwlr_layer_surface_v1_set_exclusive_zone(wayland_ctx.layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity(wayland_ctx.layer_surface, - ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); - zwlr_layer_surface_v1_add_listener(wayland_ctx.layer_surface, &details::layer_listener, &ctx); - - // Make surface click-through - wl_region *input_region = wl_compositor_create_region(wayland_ctx.compositor); - if (input_region) { - wl_surface_set_input_region(wayland_ctx.surface, input_region); - wl_region_destroy(input_region); - } - - wl_surface_commit(wayland_ctx.surface); - if constexpr (WAYLAND_NUM_BUFFERS == 1) { - wl_display_roundtrip(wayland_ctx.display); - } - return bongocat_error_t::BONGOCAT_SUCCESS; + return bongocat_error_t::BONGOCAT_SUCCESS; +} +bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { + wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = *ctx.animation_context; + // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; + + // read-only config + // assert(wayland_ctx._local_copy_config != nullptr); + // const config::config_t& current_config = *wayland_ctx._local_copy_config; + + /// @TODO: add RAII wrapper for wl_registry + wl_registry *registry = wl_display_get_registry(wayland_ctx.display); + if (!registry) { + BONGOCAT_LOG_ERROR("Failed to get Wayland registry"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + wl_registry_add_listener(registry, &details::reg_listener, &ctx); + wl_display_roundtrip(wayland_ctx.display); + + if (ctx.xdg_output_manager) { + for (size_t i = 0; i < ctx.output_count && i < MAX_OUTPUTS; i++) { + ctx.outputs[i].wayland = &ctx; + ctx.outputs[i].xdg_output = + zxdg_output_manager_v1_get_xdg_output(ctx.xdg_output_manager, ctx.outputs[i].wl_output); + zxdg_output_v1_add_listener(ctx.outputs[i].xdg_output, &details::xdg_output_listener, &ctx.outputs[i]); + ctx.screen_infos[i] = {}; + assert(ctx.outputs[i].wl_output); + ctx.screen_infos[i].wl_output = ctx.outputs[i].wl_output; } - bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animation::animation_session_t& anim) { - // read-only config - assert(wayland_context._local_copy_config != nullptr); - //const config::config_t& current_config = *wayland_context._local_copy_config; - - wayland_shared_memory_t& wayland_ctx_shm = *wayland_context.ctx_shm; - - /// @TODO: limit screen_width and bar_height for buffer_size - const int32_t buffer_width = wayland_context._screen_width; - const int32_t buffer_height = wayland_context._bar_height; - assert(buffer_width >= 0); - assert(buffer_height >= 0); - assert(RGBA_CHANNELS >= 0); - const size_t buffer_size = static_cast(buffer_width) * static_cast(buffer_height) * RGBA_CHANNELS; - if (buffer_size <= 0) { - BONGOCAT_LOG_ERROR("Invalid buffer size: %d", buffer_size); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - static_assert(WAYLAND_NUM_BUFFERS > 0); - static_assert(WAYLAND_NUM_BUFFERS <= INT32_MAX); - if (buffer_size > INT32_MAX / static_cast(WAYLAND_NUM_BUFFERS)) { - BONGOCAT_LOG_ERROR("Buffer size too large for SHM pool offset"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - assert(buffer_size <= INT32_MAX / static_cast(WAYLAND_NUM_BUFFERS)); - const size_t total_size = buffer_size * WAYLAND_NUM_BUFFERS; - - assert(total_size <= INT32_MAX); - FileDescriptor fd = create_shm(static_cast(total_size)); - if (fd._fd < 0) { - return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; - } - - wl_shm_pool *pool = wl_shm_create_pool(wayland_context.shm, fd._fd, static_cast(total_size)); - if (!pool) { - BONGOCAT_LOG_ERROR("Failed to create shared memory pool"); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - static_assert(WAYLAND_NUM_BUFFERS > 0); - static_assert(WAYLAND_NUM_BUFFERS <= INT32_MAX); - for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { - //assert(buffer_size >= 0); - assert(i <= INT32_MAX); - assert(buffer_size <= INT32_MAX); - assert(buffer_size <= static_cast(INT32_MAX)); - assert(buffer_size <= static_cast(INT32_MAX) / WAYLAND_NUM_BUFFERS); - const off_t offset = static_cast(i) * static_cast(buffer_size); - - assert(static_cast(buffer_size) <= SIZE_MAX); - wayland_ctx_shm.buffers[i].pixels = make_allocated_mmap_file_buffer_value(0, buffer_size, fd._fd, offset); - if (wayland_ctx_shm.buffers[i].pixels == nullptr) { - BONGOCAT_LOG_ERROR("Failed to map shared memory: %s", strerror(errno)); - for (size_t j = 0; j < i; j++) { - cleanup_shm_buffer(wayland_ctx_shm.buffers[j]); - } - wl_shm_pool_destroy(pool); - return bongocat_error_t::BONGOCAT_ERROR_MEMORY; - } - - //assert(buffer_size >= 0); - assert(i <= INT32_MAX); - assert(buffer_size <= INT32_MAX); - assert(offset <= INT32_MAX); - wayland_ctx_shm.buffers[i].buffer = wl_shm_pool_create_buffer(pool, static_cast(offset), wayland_context._screen_width, - wayland_context._bar_height, - wayland_context._screen_width * RGBA_CHANNELS, - WL_SHM_FORMAT_ARGB8888); - if (wayland_ctx_shm.buffers[i].buffer == nullptr) { - BONGOCAT_LOG_ERROR("Failed to create buffer"); - for (size_t j = 0; j < i; j++) { - cleanup_shm_buffer(wayland_ctx_shm.buffers[j]); - } - wl_shm_pool_destroy(pool); - return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; - } - - // created buffer successfully, set other properties - assert(i <= INT_MAX); - wayland_ctx_shm.buffers[i].index = i; - atomic_store(&wayland_ctx_shm.buffers[i].busy, false); - atomic_store(&wayland_ctx_shm.buffers[i].pending, false); - wayland_ctx_shm.buffers[i]._animation_trigger_context = &anim; - wayland_ctx_shm.buffers[i]._wayland_context = &wayland_context; - wl_buffer_add_listener(wayland_ctx_shm.buffers[i].buffer, &details::buffer_listener, &wayland_ctx_shm.buffers[i]); - } - - wl_shm_pool_destroy(pool); - - wayland_ctx_shm.current_buffer_index = 0; - - return bongocat_error_t::BONGOCAT_SUCCESS; + // Wait for all xdg_output events + wl_display_roundtrip(wayland_ctx.display); // Process initial events + wl_display_roundtrip(wayland_ctx.display); // Ensure all `done` events arrive + BONGOCAT_LOG_DEBUG("Listener bound for xdg_output and foreign toplevel handle"); + + // DE specific inits + hyprland::update_outputs_with_monitor_ids(ctx); + } + + if (!wayland_ctx.compositor || !wayland_ctx.shm || !wayland_ctx.layer_shell) { + BONGOCAT_LOG_ERROR("Missing required Wayland protocols"); + wl_registry_destroy(registry); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + const bongocat_error_t result_update_screen_width = wayland_update_screen_width(ctx); + if (result_update_screen_width != bongocat_error_t::BONGOCAT_SUCCESS) { + wl_registry_destroy(registry); + return result_update_screen_width; + } + + // move new registry + if (wayland_ctx.registry) + wl_registry_destroy(wayland_ctx.registry); + wayland_ctx.registry = registry; + registry = nullptr; + + for (size_t i = 0; i < ctx.output_count && i < MAX_OUTPUTS; i++) { + ctx.outputs[i].wayland = &ctx; + } + + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { + wayland_context_t& wayland_ctx = ctx.wayland_context; + // animation_context_t& anim = *ctx.animation_context; + // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; + + // read-only config + assert(wayland_ctx._local_copy_config != nullptr); + const config::config_t& current_config = *wayland_ctx._local_copy_config; + + wayland_ctx.surface = wl_compositor_create_surface(wayland_ctx.compositor); + if (!wayland_ctx.surface) { + BONGOCAT_LOG_ERROR("Failed to create surface"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + zwlr_layer_shell_v1_layer layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; + switch (current_config.layer) { + case config::layer_type_t::LAYER_BACKGROUND: + layer = ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND; + break; + case config::layer_type_t::LAYER_BOTTOM: + layer = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; + break; + case config::layer_type_t::LAYER_TOP: + layer = ZWLR_LAYER_SHELL_V1_LAYER_TOP; + break; + case config::layer_type_t::LAYER_OVERLAY: + layer = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY; + break; + } + wayland_ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(wayland_ctx.layer_shell, wayland_ctx.surface, + wayland_ctx.output, layer, WAYLAND_LAYER_NAMESPACE); + if (!wayland_ctx.layer_surface) { + BONGOCAT_LOG_ERROR("Failed to create layer surface"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + // Configure layer surface + uint32_t anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; + switch (current_config.overlay_position) { + case config::overlay_position_t::POSITION_TOP: + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + break; + case config::overlay_position_t::POSITION_BOTTOM: + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; + break; + default: + BONGOCAT_LOG_ERROR("Invalid overlay_position %d for layer surface, set to top (default)"); + anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; + break; + } + + assert(wayland_ctx._bar_height >= 0); + // assert(current_config.bar_height <= UINT32_MAX); + if (wayland_ctx._bar_height == 0) { + BONGOCAT_LOG_ERROR("Can not set anchor with bar_height=0"); + zwlr_layer_surface_v1_destroy(wayland_ctx.layer_surface); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + zwlr_layer_surface_v1_set_anchor(wayland_ctx.layer_surface, anchor); + zwlr_layer_surface_v1_set_size(wayland_ctx.layer_surface, 0, static_cast(wayland_ctx._bar_height)); + zwlr_layer_surface_v1_set_exclusive_zone(wayland_ctx.layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(wayland_ctx.layer_surface, + ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); + zwlr_layer_surface_v1_add_listener(wayland_ctx.layer_surface, &details::layer_listener, &ctx); + + // Make surface click-through + wl_region *input_region = wl_compositor_create_region(wayland_ctx.compositor); + if (input_region) { + wl_surface_set_input_region(wayland_ctx.surface, input_region); + wl_region_destroy(input_region); + } + + wl_surface_commit(wayland_ctx.surface); + if constexpr (WAYLAND_NUM_BUFFERS == 1) { + wl_display_roundtrip(wayland_ctx.display); + } + return bongocat_error_t::BONGOCAT_SUCCESS; +} + +bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animation::animation_session_t& anim) { + // read-only config + assert(wayland_context._local_copy_config != nullptr); + // const config::config_t& current_config = *wayland_context._local_copy_config; + + wayland_shared_memory_t& wayland_ctx_shm = *wayland_context.ctx_shm; + + /// @TODO: limit screen_width and bar_height for buffer_size + const int32_t buffer_width = wayland_context._screen_width; + const int32_t buffer_height = wayland_context._bar_height; + assert(buffer_width >= 0); + assert(buffer_height >= 0); + assert(RGBA_CHANNELS >= 0); + const size_t buffer_size = static_cast(buffer_width) * static_cast(buffer_height) * RGBA_CHANNELS; + if (buffer_size <= 0) { + BONGOCAT_LOG_ERROR("Invalid buffer size: %d", buffer_size); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + static_assert(WAYLAND_NUM_BUFFERS > 0); + static_assert(WAYLAND_NUM_BUFFERS <= INT32_MAX); + if (buffer_size > INT32_MAX / static_cast(WAYLAND_NUM_BUFFERS)) { + BONGOCAT_LOG_ERROR("Buffer size too large for SHM pool offset"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + assert(buffer_size <= INT32_MAX / static_cast(WAYLAND_NUM_BUFFERS)); + const size_t total_size = buffer_size * WAYLAND_NUM_BUFFERS; + + assert(total_size <= INT32_MAX); + FileDescriptor fd = create_shm(static_cast(total_size)); + if (fd._fd < 0) { + return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; + } + + wl_shm_pool *pool = wl_shm_create_pool(wayland_context.shm, fd._fd, static_cast(total_size)); + if (!pool) { + BONGOCAT_LOG_ERROR("Failed to create shared memory pool"); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; + } + + static_assert(WAYLAND_NUM_BUFFERS > 0); + static_assert(WAYLAND_NUM_BUFFERS <= INT32_MAX); + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { + // assert(buffer_size >= 0); + assert(i <= INT32_MAX); + assert(buffer_size <= INT32_MAX); + assert(buffer_size <= static_cast(INT32_MAX)); + assert(buffer_size <= static_cast(INT32_MAX) / WAYLAND_NUM_BUFFERS); + const off_t offset = static_cast(i) * static_cast(buffer_size); + + assert(static_cast(buffer_size) <= SIZE_MAX); + wayland_ctx_shm.buffers[i].pixels = make_allocated_mmap_file_buffer_value(0, buffer_size, fd._fd, offset); + if (wayland_ctx_shm.buffers[i].pixels == nullptr) { + BONGOCAT_LOG_ERROR("Failed to map shared memory: %s", strerror(errno)); + for (size_t j = 0; j < i; j++) { + cleanup_shm_buffer(wayland_ctx_shm.buffers[j]); + } + wl_shm_pool_destroy(pool); + return bongocat_error_t::BONGOCAT_ERROR_MEMORY; + } + + // assert(buffer_size >= 0); + assert(i <= INT32_MAX); + assert(buffer_size <= INT32_MAX); + assert(offset <= INT32_MAX); + wayland_ctx_shm.buffers[i].buffer = wl_shm_pool_create_buffer( + pool, static_cast(offset), wayland_context._screen_width, wayland_context._bar_height, + wayland_context._screen_width * RGBA_CHANNELS, WL_SHM_FORMAT_ARGB8888); + if (wayland_ctx_shm.buffers[i].buffer == nullptr) { + BONGOCAT_LOG_ERROR("Failed to create buffer"); + for (size_t j = 0; j < i; j++) { + cleanup_shm_buffer(wayland_ctx_shm.buffers[j]); + } + wl_shm_pool_destroy(pool); + return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } + + // created buffer successfully, set other properties + assert(i <= INT_MAX); + wayland_ctx_shm.buffers[i].index = i; + atomic_store(&wayland_ctx_shm.buffers[i].busy, false); + atomic_store(&wayland_ctx_shm.buffers[i].pending, false); + wayland_ctx_shm.buffers[i]._animation_trigger_context = &anim; + wayland_ctx_shm.buffers[i]._wayland_context = &wayland_context; + wl_buffer_add_listener(wayland_ctx_shm.buffers[i].buffer, &details::buffer_listener, &wayland_ctx_shm.buffers[i]); + } + + wl_shm_pool_destroy(pool); + + wayland_ctx_shm.current_buffer_index = 0; + + return bongocat_error_t::BONGOCAT_SUCCESS; } +} // namespace bongocat::platform::wayland::details diff --git a/src/platform/wayland_sway.cpp b/src/platform/wayland_sway.cpp index 744a1af8..1904c03c 100644 --- a/src/platform/wayland_sway.cpp +++ b/src/platform/wayland_sway.cpp @@ -1,36 +1,38 @@ #include "wayland_sway.h" + #include "utils/error.h" + #include -#include -#include #include -#include -#include +#include #include -#include +#include +#include #include +#include +#include namespace bongocat::platform::wayland::sway { int fs_check_compositor_fallback() { - FILE *fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); - if (fp) { - bool is_fullscreen = false; - - char sway_buffer[SWAY_BUF] = {0}; - while (fgets(sway_buffer, SWAY_BUF, fp)) { - if (strstr(sway_buffer, "\"fullscreen_mode\":1")) { - is_fullscreen = true; - BONGOCAT_LOG_DEBUG("Fullscreen detected in Sway"); - break; - } - } - - pclose(fp); - return is_fullscreen ? 1 : 0; + FILE *fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); + if (fp) { + bool is_fullscreen = false; + + char sway_buffer[SWAY_BUF] = {0}; + while (fgets(sway_buffer, SWAY_BUF, fp)) { + if (strstr(sway_buffer, "\"fullscreen_mode\":1")) { + is_fullscreen = true; + BONGOCAT_LOG_DEBUG("Fullscreen detected in Sway"); + break; + } } - return -1; -} + pclose(fp); + return is_fullscreen ? 1 : 0; + } + return -1; } + +} // namespace bongocat::platform::wayland::sway diff --git a/src/platform/wayland_sway.h b/src/platform/wayland_sway.h index 3319ecd6..4b0d5bf9 100644 --- a/src/platform/wayland_sway.h +++ b/src/platform/wayland_sway.h @@ -8,4 +8,4 @@ static inline constexpr size_t SWAY_BUF = 4096; extern int fs_check_compositor_fallback(); -} +} // namespace bongocat::platform::wayland::sway diff --git a/src/utils/error.cpp b/src/utils/error.cpp index e991966d..362520a8 100644 --- a/src/utils/error.cpp +++ b/src/utils/error.cpp @@ -1,95 +1,125 @@ #include "utils/error.h" + #include "utils/system_memory.h" + #include #include -#include -#include #include -#include #include +#include +#include +#include namespace bongocat { - namespace details { - inline atomic_bool& get_debug_enabled() { - static atomic_bool g_instance = true; - return g_instance; - } - } +namespace details { + inline atomic_bool& get_debug_enabled() { + static atomic_bool g_instance = true; + return g_instance; + } +} // namespace details - void error_init(bool enable_debug) { - atomic_store(&details::get_debug_enabled(), enable_debug); - } +void error_init(bool enable_debug) { + atomic_store(&details::get_debug_enabled(), enable_debug); +} #if !defined(BONGOCAT_DISABLE_LOGGER) || defined(BONGOCAT_ENABLE_LOGGER) - namespace details { - inline platform::Mutex& get_log_mutex() { - static platform::Mutex g_instance; - return g_instance; - } - - inline void log_timestamp(FILE *stream) { - timeval tv{}; - tm tm_info{}; - char timestamp[64] = {0}; - - gettimeofday(&tv, nullptr); - localtime_r(&tv.tv_sec, &tm_info); // Thread-safe version - - strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); - fprintf(stream, "[%s.%03ld] ", timestamp, tv.tv_usec / 1000); - } - - // Core log function using va_list - inline void log_vprintf(const char* name, const char* format, va_list args) { - const int name_len = static_cast(strlen(name)); - assert(name_len > 0); - - platform::LockGuard guard (get_log_mutex()); - log_timestamp(stdout); - fprintf(stdout, "%.*s: ", name_len, name); - vfprintf(stdout, format, args); - fprintf(stdout, "\n"); - fflush(stdout); - } - - // Convenience inline functions - void log_error(const char* fmt, ...) { - va_list args; va_start(args, fmt); log_vprintf("ERROR", fmt, args); va_end(args); - } - - void log_warning(const char* fmt, ...) { - va_list args; va_start(args, fmt); log_vprintf("WARNING", fmt, args); va_end(args); - } - - void log_info(const char* fmt, ...) { - va_list args; va_start(args, fmt); log_vprintf("INFO", fmt, args); va_end(args); - } - - void log_debug(const char* fmt, ...) { - if (!atomic_load(&get_debug_enabled())) return; - va_list args; va_start(args, fmt); log_vprintf("DEBUG", fmt, args); va_end(args); - } - - void log_verbose(const char* fmt, ...) { - if (!atomic_load(&get_debug_enabled())) return; - va_list args; va_start(args, fmt); log_vprintf("VERBOSE", fmt, args); va_end(args); - } - } +namespace details { + inline platform::Mutex& get_log_mutex() { + static platform::Mutex g_instance; + return g_instance; + } + + inline void log_timestamp(FILE *stream) { + timeval tv{}; + tm tm_info{}; + char timestamp[64] = {0}; + + gettimeofday(&tv, nullptr); + localtime_r(&tv.tv_sec, &tm_info); // Thread-safe version + + strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); + fprintf(stream, "[%s.%03ld] ", timestamp, tv.tv_usec / 1000); + } + + // Core log function using va_list + inline void log_vprintf(const char *name, const char *format, va_list args) { + const int name_len = static_cast(strlen(name)); + assert(name_len > 0); + + platform::LockGuard guard(get_log_mutex()); + log_timestamp(stdout); + fprintf(stdout, "%.*s: ", name_len, name); + vfprintf(stdout, format, args); + fprintf(stdout, "\n"); + fflush(stdout); + } + + // Convenience inline functions + void log_error(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_vprintf("ERROR", fmt, args); + va_end(args); + } + + void log_warning(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_vprintf("WARNING", fmt, args); + va_end(args); + } + + void log_info(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + log_vprintf("INFO", fmt, args); + va_end(args); + } + + void log_debug(const char *fmt, ...) { + if (!atomic_load(&get_debug_enabled())) + return; + va_list args; + va_start(args, fmt); + log_vprintf("DEBUG", fmt, args); + va_end(args); + } + + void log_verbose(const char *fmt, ...) { + if (!atomic_load(&get_debug_enabled())) + return; + va_list args; + va_start(args, fmt); + log_vprintf("VERBOSE", fmt, args); + va_end(args); + } +} // namespace details #endif - const char* error_string(bongocat_error_t error) { - switch (error) { - case bongocat_error_t::BONGOCAT_SUCCESS: return "Success"; - case bongocat_error_t::BONGOCAT_ERROR_MEMORY: return "Memory allocation error"; - case bongocat_error_t::BONGOCAT_ERROR_FILE_IO: return "File I/O error"; - case bongocat_error_t::BONGOCAT_ERROR_WAYLAND: return "Wayland error"; - case bongocat_error_t::BONGOCAT_ERROR_CONFIG: return "Configuration error"; - case bongocat_error_t::BONGOCAT_ERROR_INPUT: return "Input error"; - case bongocat_error_t::BONGOCAT_ERROR_ANIMATION: return "Animation error"; - case bongocat_error_t::BONGOCAT_ERROR_THREAD: return "Thread error"; - case bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM: return "Invalid parameter"; - case bongocat_error_t::BONGOCAT_ERROR_IMAGE: return "Load image error"; - default: return "Unknown error"; - } - } +const char *error_string(bongocat_error_t error) { + switch (error) { + case bongocat_error_t::BONGOCAT_SUCCESS: + return "Success"; + case bongocat_error_t::BONGOCAT_ERROR_MEMORY: + return "Memory allocation error"; + case bongocat_error_t::BONGOCAT_ERROR_FILE_IO: + return "File I/O error"; + case bongocat_error_t::BONGOCAT_ERROR_WAYLAND: + return "Wayland error"; + case bongocat_error_t::BONGOCAT_ERROR_CONFIG: + return "Configuration error"; + case bongocat_error_t::BONGOCAT_ERROR_INPUT: + return "Input error"; + case bongocat_error_t::BONGOCAT_ERROR_ANIMATION: + return "Animation error"; + case bongocat_error_t::BONGOCAT_ERROR_THREAD: + return "Thread error"; + case bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM: + return "Invalid parameter"; + case bongocat_error_t::BONGOCAT_ERROR_IMAGE: + return "Load image error"; + default: + return "Unknown error"; + } } +} // namespace bongocat diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp index fb56d4c6..e04587dd 100644 --- a/src/utils/memory.cpp +++ b/src/utils/memory.cpp @@ -1,316 +1,328 @@ #include "utils/memory.h" -#include "utils/system_memory.h" -#include "utils/error.h" + #include "core/bongocat.h" -#include +#include "utils/error.h" +#include "utils/system_memory.h" + #include -#include +#include #include +#include namespace bongocat { - namespace details { +namespace details { #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - inline memory_stats_t& get_memory_stats() { - static memory_stats_t g_instance{}; - return g_instance; - } - inline platform::Mutex& get_memory_mutex() { - static platform::Mutex g_instance; - return g_instance; - } + inline memory_stats_t& get_memory_stats() { + static memory_stats_t g_instance{}; + return g_instance; + } + inline platform::Mutex& get_memory_mutex() { + static platform::Mutex g_instance; + return g_instance; + } #endif #ifndef NDEBUG - struct allocation_record_t { - void *ptr{nullptr}; - size_t size{0}; - const char *file{}; - int line{0}; - allocation_record_t *next{nullptr}; - }; - - inline allocation_record_t*& get_allocations() { - static allocation_record_t *g_instance = nullptr; - return g_instance; - } + struct allocation_record_t { + void *ptr{nullptr}; + size_t size{0}; + const char *file{}; + int line{0}; + allocation_record_t *next{nullptr}; + }; + + inline allocation_record_t *& get_allocations() { + static allocation_record_t *g_instance = nullptr; + return g_instance; + } #endif - } +} // namespace details +void *malloc(size_t size) { + if (size == 0) { + BONGOCAT_LOG_WARNING("Attempted to allocate 0 bytes"); + return nullptr; + } - void* malloc(size_t size) { - if (size == 0) { - BONGOCAT_LOG_WARNING("Attempted to allocate 0 bytes"); - return nullptr; - } - - void *ptr = ::malloc(size); - if (!ptr) { - BONGOCAT_LOG_ERROR("Failed to allocate %zu bytes", size); - return nullptr; - } + void *ptr = ::malloc(size); + if (!ptr) { + BONGOCAT_LOG_ERROR("Failed to allocate %zu bytes", size); + return nullptr; + } #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - { - using namespace details; - platform::LockGuard guard(get_memory_mutex()); - get_memory_stats().total_allocated += size; - get_memory_stats().current_allocated += size; - if (get_memory_stats().current_allocated > get_memory_stats().peak_allocated) { - atomic_store(&get_memory_stats().peak_allocated, atomic_load(&get_memory_stats().current_allocated)); - } - ++get_memory_stats().allocation_count; - } + { + using namespace details; + platform::LockGuard guard(get_memory_mutex()); + get_memory_stats().total_allocated += size; + get_memory_stats().current_allocated += size; + if (get_memory_stats().current_allocated > get_memory_stats().peak_allocated) { + atomic_store(&get_memory_stats().peak_allocated, atomic_load(&get_memory_stats().current_allocated)); + } + ++get_memory_stats().allocation_count; + } #endif - return ptr; - } + return ptr; +} - void* calloc(size_t count, size_t size) { - if (count == 0 || size == 0) { - BONGOCAT_LOG_WARNING("Attempted to allocate 0 bytes"); - return nullptr; - } - - // Check for overflow - assert(size > 0); - if (count > SIZE_MAX / size) { - BONGOCAT_LOG_ERROR("Integer overflow in calloc"); - return nullptr; - } - - void *ptr = ::calloc(count, size); - if (!ptr) { - BONGOCAT_LOG_ERROR("Failed to allocate %zu bytes", count * size); - return nullptr; - } +void *calloc(size_t count, size_t size) { + if (count == 0 || size == 0) { + BONGOCAT_LOG_WARNING("Attempted to allocate 0 bytes"); + return nullptr; + } + + // Check for overflow + assert(size > 0); + if (count > SIZE_MAX / size) { + BONGOCAT_LOG_ERROR("Integer overflow in calloc"); + return nullptr; + } + + void *ptr = ::calloc(count, size); + if (!ptr) { + BONGOCAT_LOG_ERROR("Failed to allocate %zu bytes", count * size); + return nullptr; + } #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - { - using namespace details; - platform::LockGuard guard(get_memory_mutex()); - const size_t total_size = count * size; - get_memory_stats().total_allocated += total_size; - get_memory_stats().current_allocated += total_size; - if (get_memory_stats().current_allocated > get_memory_stats().peak_allocated) { - atomic_store(&get_memory_stats().peak_allocated, atomic_load(&get_memory_stats().current_allocated)); - } - ++get_memory_stats().allocation_count; - } + { + using namespace details; + platform::LockGuard guard(get_memory_mutex()); + const size_t total_size = count * size; + get_memory_stats().total_allocated += total_size; + get_memory_stats().current_allocated += total_size; + if (get_memory_stats().current_allocated > get_memory_stats().peak_allocated) { + atomic_store(&get_memory_stats().peak_allocated, atomic_load(&get_memory_stats().current_allocated)); + } + ++get_memory_stats().allocation_count; + } #endif - return ptr; - } + return ptr; +} - void* bongocat_realloc(void *ptr, size_t size) { - if (size == 0) { - bongocat::free(ptr); - return nullptr; - } +void *bongocat_realloc(void *ptr, size_t size) { + if (size == 0) { + bongocat::free(ptr); + return nullptr; + } - void *new_ptr = ::realloc(ptr, size); - if (!new_ptr) { - BONGOCAT_LOG_ERROR("Failed to reallocate to %zu bytes", size); - return nullptr; - } + void *new_ptr = ::realloc(ptr, size); + if (!new_ptr) { + BONGOCAT_LOG_ERROR("Failed to reallocate to %zu bytes", size); + return nullptr; + } - // Note: We can't track size changes accurately without storing original sizes - // This is a limitation of this simple tracking system + // Note: We can't track size changes accurately without storing original sizes + // This is a limitation of this simple tracking system - return new_ptr; - } + return new_ptr; +} - void free(void *ptr) { - if (!ptr) return; +void free(void *ptr) { + if (!ptr) + return; - ::free(ptr); + ::free(ptr); #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - { - using namespace details; - platform::LockGuard guard(get_memory_mutex()); - ++get_memory_stats().free_count; - /* - if (static_cast(g_memory_stats.free_count) > static_cast(g_memory_stats.allocation_count)) { - BONGOCAT_LOG_VERBOSE("Potential double free: %d/%d", atomic_load(&g_memory_stats.allocation_count), atomic_load(&g_memory_stats.free_count)); - } - */ - } -#endif + { + using namespace details; + platform::LockGuard guard(get_memory_mutex()); + ++get_memory_stats().free_count; + /* + if (static_cast(g_memory_stats.free_count) > static_cast(g_memory_stats.allocation_count)) { + BONGOCAT_LOG_VERBOSE("Potential double free: %d/%d", atomic_load(&g_memory_stats.allocation_count), + atomic_load(&g_memory_stats.free_count)); } + */ + } +#endif +} - memory_pool_t* memory_pool_create(size_t size, size_t alignment) { - if (size == 0 || alignment == 0) { - BONGOCAT_LOG_ERROR("Invalid memory pool parameters"); - return nullptr; - } - - // Validate alignment is a power of 2 - if ((alignment & (alignment - 1)) != 0) { - BONGOCAT_LOG_ERROR("Memory pool alignment must be a power of 2, got %zu", - alignment); - return NULL; - } - - auto *pool = static_cast(bongocat::malloc(sizeof(memory_pool_t))); - if (!pool) return nullptr; - - pool->data = bongocat::malloc(size); - if (!pool->data) { - bongocat::free(pool); - return nullptr; - } - - pool->size = size; - pool->used = 0; - pool->alignment = alignment; - - return pool; - } +memory_pool_t *memory_pool_create(size_t size, size_t alignment) { + if (size == 0 || alignment == 0) { + BONGOCAT_LOG_ERROR("Invalid memory pool parameters"); + return nullptr; + } + + // Validate alignment is a power of 2 + if ((alignment & (alignment - 1)) != 0) { + BONGOCAT_LOG_ERROR("Memory pool alignment must be a power of 2, got %zu", alignment); + return NULL; + } + + auto *pool = static_cast(bongocat::malloc(sizeof(memory_pool_t))); + if (!pool) + return nullptr; + + pool->data = bongocat::malloc(size); + if (!pool->data) { + bongocat::free(pool); + return nullptr; + } + + pool->size = size; + pool->used = 0; + pool->alignment = alignment; + + return pool; +} - void* memory_pool_alloc(memory_pool_t& pool, size_t size) { - if (size == 0) return nullptr; +void *memory_pool_alloc(memory_pool_t& pool, size_t size) { + if (size == 0) + return nullptr; - // Align the size - const size_t aligned_size = (size + pool.alignment - 1) & ~(pool.alignment - 1); + // Align the size + const size_t aligned_size = (size + pool.alignment - 1) & ~(pool.alignment - 1); - if (pool.used + aligned_size > pool.size) { - BONGOCAT_LOG_ERROR("Memory pool exhausted"); - return nullptr; - } + if (pool.used + aligned_size > pool.size) { + BONGOCAT_LOG_ERROR("Memory pool exhausted"); + return nullptr; + } - void *ptr = static_cast(pool.data) + pool.used; - pool.used += aligned_size; + void *ptr = static_cast(pool.data) + pool.used; + pool.used += aligned_size; - return ptr; - } + return ptr; +} - void memory_pool_reset(memory_pool_t *pool) { - if (pool) { - pool->used = 0; - } - } +void memory_pool_reset(memory_pool_t *pool) { + if (pool) { + pool->used = 0; + } +} - void memory_pool_destroy(memory_pool_t *pool) { - if (pool) { - bongocat::free(pool->data); - bongocat::free(pool); - } - } +void memory_pool_destroy(memory_pool_t *pool) { + if (pool) { + bongocat::free(pool->data); + bongocat::free(pool); + } +} #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - void memory_get_stats(memory_stats_t *stats) { - if (!stats) return; - - { - using namespace details; - platform::LockGuard guard(get_memory_mutex()); - stats->total_allocated = atomic_load(&get_memory_stats().total_allocated); - stats->current_allocated = atomic_load(&get_memory_stats().current_allocated); - stats->peak_allocated = atomic_load(&get_memory_stats().peak_allocated); - stats->allocation_count = atomic_load(&get_memory_stats().allocation_count); - stats->free_count = atomic_load(&get_memory_stats().free_count); - } - } +void memory_get_stats(memory_stats_t *stats) { + if (!stats) + return; + + { + using namespace details; + platform::LockGuard guard(get_memory_mutex()); + stats->total_allocated = atomic_load(&get_memory_stats().total_allocated); + stats->current_allocated = atomic_load(&get_memory_stats().current_allocated); + stats->peak_allocated = atomic_load(&get_memory_stats().peak_allocated); + stats->allocation_count = atomic_load(&get_memory_stats().allocation_count); + stats->free_count = atomic_load(&get_memory_stats().free_count); + } +} - void memory_print_stats() { - memory_stats_t stats; - memory_get_stats(&stats); - - { - using namespace details; - platform::LockGuard guard(get_memory_mutex()); - const size_t total_allocated = atomic_load(&stats.total_allocated); - const size_t current_allocated = atomic_load(&stats.current_allocated); - const size_t peak_allocated = atomic_load(&stats.peak_allocated); - const size_t allocation_count = atomic_load(&stats.allocation_count); - const size_t free_count = atomic_load(&stats.free_count); - - assert(allocation_count <= INT_MAX); - assert(free_count <= INT_MAX); - - bongocat::details::log_info("Memory Statistics:"); - bongocat::details::log_info(" Total allocated: %zu bytes (%.2f MB)", total_allocated, static_cast(total_allocated) / (1024.0 * 1024.0)); - bongocat::details::log_info(" Current allocated: %zu bytes (%.2f MB)", current_allocated, static_cast(current_allocated) / (1024.0 * 1024.0)); - bongocat::details::log_info(" Peak allocated: %zu bytes (%.2f MB)", peak_allocated, static_cast(peak_allocated) / (1024.0 * 1024.0)); - bongocat::details::log_info(" Allocations: %zu", allocation_count); - bongocat::details::log_info(" Frees: %zu", free_count); - bongocat::details::log_info(" Potential leaks: %d", static_cast(allocation_count) - static_cast(free_count)); - } - } +void memory_print_stats() { + memory_stats_t stats; + memory_get_stats(&stats); + + { + using namespace details; + platform::LockGuard guard(get_memory_mutex()); + const size_t total_allocated = atomic_load(&stats.total_allocated); + const size_t current_allocated = atomic_load(&stats.current_allocated); + const size_t peak_allocated = atomic_load(&stats.peak_allocated); + const size_t allocation_count = atomic_load(&stats.allocation_count); + const size_t free_count = atomic_load(&stats.free_count); + + assert(allocation_count <= INT_MAX); + assert(free_count <= INT_MAX); + + bongocat::details::log_info("Memory Statistics:"); + bongocat::details::log_info(" Total allocated: %zu bytes (%.2f MB)", total_allocated, + static_cast(total_allocated) / (1024.0 * 1024.0)); + bongocat::details::log_info(" Current allocated: %zu bytes (%.2f MB)", current_allocated, + static_cast(current_allocated) / (1024.0 * 1024.0)); + bongocat::details::log_info(" Peak allocated: %zu bytes (%.2f MB)", peak_allocated, + static_cast(peak_allocated) / (1024.0 * 1024.0)); + bongocat::details::log_info(" Allocations: %zu", allocation_count); + bongocat::details::log_info(" Frees: %zu", free_count); + bongocat::details::log_info(" Potential leaks: %d", + static_cast(allocation_count) - static_cast(free_count)); + } +} #else - void memory_print_stats() {} +void memory_print_stats() {} #endif #ifndef NDEBUG - void* malloc_debug(size_t size, const char *file, int line) { - void *ptr = bongocat::malloc(size); - if (!ptr) return nullptr; - - { - using namespace details; - platform::LockGuard guard(details::get_memory_mutex()); - auto *record = static_cast(::malloc(sizeof(allocation_record_t))); - if (record) { - record->ptr = ptr; - record->size = size; - record->file = file; - record->line = line; - record->next = get_allocations(); - get_allocations() = record; - } - } - - return ptr; +void *malloc_debug(size_t size, const char *file, int line) { + void *ptr = bongocat::malloc(size); + if (!ptr) + return nullptr; + + { + using namespace details; + platform::LockGuard guard(details::get_memory_mutex()); + auto *record = static_cast(::malloc(sizeof(allocation_record_t))); + if (record) { + record->ptr = ptr; + record->size = size; + record->file = file; + record->line = line; + record->next = get_allocations(); + get_allocations() = record; } + } - void free_debug(void *ptr, const char *file, int line) { - if (!ptr) return; - - { - using namespace details; - platform::LockGuard guard(details::get_memory_mutex()); - allocation_record_t **current = &get_allocations(); - while (*current) { - if ((*current)->ptr == ptr) { - allocation_record_t *to_remove = *current; - *current = (*current)->next; - assert(to_remove); - ::free(to_remove); - break; - } - current = &(*current)->next; - } - } - - bongocat::free(ptr); + return ptr; +} -#if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) - { - using namespace details; - platform::LockGuard guard(details::get_memory_mutex()); - const size_t free_count = atomic_load(&get_memory_stats().free_count); - const size_t current_allocated = atomic_load(&get_memory_stats().current_allocated); - if (free_count > current_allocated) { - BONGOCAT_LOG_WARNING("possible double free, one free is to much: Frees: %zu; Allocations: %zu %s:%d", free_count > current_allocated, file, line); - } - } -#endif +void free_debug(void *ptr, const char *file, int line) { + if (!ptr) + return; + + { + using namespace details; + platform::LockGuard guard(details::get_memory_mutex()); + allocation_record_t **current = &get_allocations(); + while (*current) { + if ((*current)->ptr == ptr) { + allocation_record_t *to_remove = *current; + *current = (*current)->next; + assert(to_remove); + ::free(to_remove); + break; + } + current = &(*current)->next; } - - void memory_leak_check() { - using namespace details; - platform::LockGuard guard(get_memory_mutex()); - if (!get_allocations()) { - BONGOCAT_LOG_INFO("No memory leaks detected"); - return; - } - - BONGOCAT_LOG_ERROR("Memory leaks detected:"); - allocation_record_t *current = get_allocations(); - while (current) { - BONGOCAT_LOG_ERROR(" %zu bytes at %s:%d", current->size, current->file, current->line); - current = current->next; - } + } + + bongocat::free(ptr); + +# if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) + { + using namespace details; + platform::LockGuard guard(details::get_memory_mutex()); + const size_t free_count = atomic_load(&get_memory_stats().free_count); + const size_t current_allocated = atomic_load(&get_memory_stats().current_allocated); + if (free_count > current_allocated) { + BONGOCAT_LOG_WARNING("possible double free, one free is to much: Frees: %zu; Allocations: %zu %s:%d", + free_count > current_allocated, file, line); } -#endif + } +# endif } + +void memory_leak_check() { + using namespace details; + platform::LockGuard guard(get_memory_mutex()); + if (!get_allocations()) { + BONGOCAT_LOG_INFO("No memory leaks detected"); + return; + } + + BONGOCAT_LOG_ERROR("Memory leaks detected:"); + allocation_record_t *current = get_allocations(); + while (current) { + BONGOCAT_LOG_ERROR(" %zu bytes at %s:%d", current->size, current->file, current->line); + current = current->next; + } +} +#endif +} // namespace bongocat diff --git a/src/utils/random.cpp b/src/utils/random.cpp index f5e6ab85..48d264cf 100644 --- a/src/utils/random.cpp +++ b/src/utils/random.cpp @@ -1,55 +1,56 @@ #include "utils/random.h" + #include "utils/system_memory.h" + #include -#include -#include #include - +#include +#include namespace bongocat::platform { - static inline constexpr int MAX_ATTEMPTS = 2048; - - uint32_t slow_rand() { - constexpr const char* random_device_filename = "/dev/urandom"; - FileDescriptor fd (open(random_device_filename, O_RDONLY | O_CLOEXEC | O_NONBLOCK)); - if (fd._fd < 0) { - BONGOCAT_LOG_ERROR("Can not open random device: %s", random_device_filename); - } - uint32_t val = 0; +static inline constexpr int MAX_ATTEMPTS = 2048; - ssize_t r = read(fd._fd, &val, sizeof(val)); - if (r == static_cast(sizeof(val))) { - return val; - } - if (r == -1) { - // Non-blocking mode: return on EAGAIN (no entropy available yet) - // or on other read errors (errno preserved). - return 0; - } +uint32_t slow_rand() { + constexpr const char *random_device_filename = "/dev/urandom"; + FileDescriptor fd(open(random_device_filename, O_RDONLY | O_CLOEXEC | O_NONBLOCK)); + if (fd._fd < 0) { + BONGOCAT_LOG_ERROR("Can not open random device: %s", random_device_filename); + } + uint32_t val = 0; - // Partial read: try to finish the request but still respect non-blocking. - // If we encounter EAGAIN or other error, return 0. - size_t got = (r > 0) ? static_cast(r) : 0; - unsigned char *p = reinterpret_cast(&val); - int attempts = 0; - while (got < sizeof(val) && attempts < MAX_ATTEMPTS) { - r = read(fd._fd, p + got, sizeof(val) - got); - if (r > 0) { - got += static_cast(r); - continue; - } - if (r == 0) { - errno = EIO; - return 0; - } - if (errno == EINTR) { - attempts++; - continue; /* retry on interrupt */ - } - // If EAGAIN (non-blocking & no data), or any other error -> fail - return 0; - } + ssize_t r = read(fd._fd, &val, sizeof(val)); + if (r == static_cast(sizeof(val))) { + return val; + } + if (r == -1) { + // Non-blocking mode: return on EAGAIN (no entropy available yet) + // or on other read errors (errno preserved). + return 0; + } - return val; + // Partial read: try to finish the request but still respect non-blocking. + // If we encounter EAGAIN or other error, return 0. + size_t got = (r > 0) ? static_cast(r) : 0; + unsigned char *p = reinterpret_cast(&val); + int attempts = 0; + while (got < sizeof(val) && attempts < MAX_ATTEMPTS) { + r = read(fd._fd, p + got, sizeof(val) - got); + if (r > 0) { + got += static_cast(r); + continue; + } + if (r == 0) { + errno = EIO; + return 0; } + if (errno == EINTR) { + attempts++; + continue; /* retry on interrupt */ + } + // If EAGAIN (non-blocking & no data), or any other error -> fail + return 0; + } + + return val; } +} // namespace bongocat::platform diff --git a/src/utils/system_memory.cpp b/src/utils/system_memory.cpp index 8aa3d8ea..d72c0bf8 100644 --- a/src/utils/system_memory.cpp +++ b/src/utils/system_memory.cpp @@ -1,57 +1,62 @@ #include "utils/system_memory.h" + #include "utils/error.h" #include "utils/time.h" + #include namespace bongocat::platform { - static inline constexpr time_ms_t THREAD_JOIN_TIMEOUT_MS = 5000; // maximum wait for graceful exit - static inline constexpr time_ms_t THREAD_SlEEP_WHEN_WAITING_FOR_THREAD_MS = 100; - static inline constexpr int THREAD_SLEEP_MAX_ATTEMPTS = 2048; - - - int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms) { - if (thread == 0) return 0; - - timespec start{}; - timespec now{}; - clock_gettime(CLOCK_MONOTONIC, &start); - - int attempts = 0; - while (attempts < THREAD_SLEEP_MAX_ATTEMPTS) { - const int ret = pthread_tryjoin_np(thread, nullptr); - if (ret == 0) { - thread = 0; - return 0; - } - if (ret != EBUSY) return ret; // error other than "still running" - - // Check elapsed time - clock_gettime(CLOCK_MONOTONIC, &now); - const time_ms_t elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec - start.tv_nsec) / 1000000L; - if (elapsed_ms >= timeout_ms) return ETIMEDOUT; - - // small sleep to avoid busy waiting - timespec ts = {.tv_sec = 0, .tv_nsec = 1000000L * THREAD_SlEEP_WHEN_WAITING_FOR_THREAD_MS}; - nanosleep(&ts, nullptr); - attempts++; - } - - return EBUSY; - } - - int stop_thread_graceful_or_cancel(pthread_t& thread, atomic_bool &running_flag) { - if (thread == 0) return 0; - - atomic_store(&running_flag, false); - const int ret = join_thread_with_timeout(thread, THREAD_JOIN_TIMEOUT_MS); - if (thread != 0 && ret == ETIMEDOUT) { - BONGOCAT_LOG_WARNING("Thread did not exit in time, cancelling: %dms", THREAD_JOIN_TIMEOUT_MS); - pthread_cancel(thread); - pthread_join(thread, nullptr); - } - - thread = 0; - - return ret; +static inline constexpr time_ms_t THREAD_JOIN_TIMEOUT_MS = 5000; // maximum wait for graceful exit +static inline constexpr time_ms_t THREAD_SlEEP_WHEN_WAITING_FOR_THREAD_MS = 100; +static inline constexpr int THREAD_SLEEP_MAX_ATTEMPTS = 2048; + +int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms) { + if (thread == 0) + return 0; + + timespec start{}; + timespec now{}; + clock_gettime(CLOCK_MONOTONIC, &start); + + int attempts = 0; + while (attempts < THREAD_SLEEP_MAX_ATTEMPTS) { + const int ret = pthread_tryjoin_np(thread, nullptr); + if (ret == 0) { + thread = 0; + return 0; } -} \ No newline at end of file + if (ret != EBUSY) + return ret; // error other than "still running" + + // Check elapsed time + clock_gettime(CLOCK_MONOTONIC, &now); + const time_ms_t elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec - start.tv_nsec) / 1000000L; + if (elapsed_ms >= timeout_ms) + return ETIMEDOUT; + + // small sleep to avoid busy waiting + timespec ts = {.tv_sec = 0, .tv_nsec = 1000000L * THREAD_SlEEP_WHEN_WAITING_FOR_THREAD_MS}; + nanosleep(&ts, nullptr); + attempts++; + } + + return EBUSY; +} + +int stop_thread_graceful_or_cancel(pthread_t& thread, atomic_bool& running_flag) { + if (thread == 0) + return 0; + + atomic_store(&running_flag, false); + const int ret = join_thread_with_timeout(thread, THREAD_JOIN_TIMEOUT_MS); + if (thread != 0 && ret == ETIMEDOUT) { + BONGOCAT_LOG_WARNING("Thread did not exit in time, cancelling: %dms", THREAD_JOIN_TIMEOUT_MS); + pthread_cancel(thread); + pthread_join(thread, nullptr); + } + + thread = 0; + + return ret; +} +} // namespace bongocat::platform \ No newline at end of file diff --git a/src/utils/time.cpp b/src/utils/time.cpp index fdc8bf0f..10ab1314 100644 --- a/src/utils/time.cpp +++ b/src/utils/time.cpp @@ -1,25 +1,26 @@ #include "utils/time.h" + #include #include namespace bongocat::platform { - timestamp_us_t get_current_time_us() { - timeval now{}; - gettimeofday(&now, nullptr); - return now.tv_sec * 1000000LL + now.tv_usec; - } - timestamp_ms_t get_current_time_ms() { - return get_current_time_us() / 1000; - } +timestamp_us_t get_current_time_us() { + timeval now{}; + gettimeofday(&now, nullptr); + return now.tv_sec * 1000000LL + now.tv_usec; +} +timestamp_ms_t get_current_time_ms() { + return get_current_time_us() / 1000; +} - time_us_t get_uptime_us() { - timespec ts{}; - if (clock_gettime(CLOCK_BOOTTIME, &ts) != 0) { - return 0; - } - return ts.tv_sec * 1000000000LL + ts.tv_nsec; - } - time_ms_t get_uptime_ms() { - return get_uptime_us() / 1000; - } -} \ No newline at end of file +time_us_t get_uptime_us() { + timespec ts{}; + if (clock_gettime(CLOCK_BOOTTIME, &ts) != 0) { + return 0; + } + return ts.tv_sec * 1000000000LL + ts.tv_nsec; +} +time_ms_t get_uptime_ms() { + return get_uptime_us() / 1000; +} +} // namespace bongocat::platform \ No newline at end of file From d6206f2251f908e1319153c2d9ac7328543480df Mon Sep 17 00:00:00 2001 From: furudbat Date: Tue, 9 Dec 2025 11:48:58 +0100 Subject: [PATCH 13/18] chore: fix warnings & formatting --- .clang-format | 1 + include/config/config.h | 86 ++-- include/config/config_watcher.h | 6 +- include/core/bongocat.h | 24 +- include/embedded_assets/bongocat/bongocat.h | 4 +- include/embedded_assets/embedded_image.h | 2 +- .../embedded_assets/min_dm/min_dm_sprite.h | 2 +- include/embedded_assets/misc/misc_sprite.h | 4 +- .../ms_agent/ms_agent_sprite.h | 4 +- include/graphics/animation_shared_memory.h | 2 +- include/graphics/global_animation_session.h | 26 +- include/graphics/sprite_sheet.h | 96 ++-- .../bongocat/load_images_bongocat.h | 4 +- include/image_loader/custom/load_custom.h | 11 +- include/image_loader/load_images.h | 17 +- .../image_loader/min_dm/load_images_min_dm.h | 2 +- include/image_loader/misc/load_images_misc.h | 2 +- .../ms_agent/load_images_ms_agent.h | 8 +- include/platform/global_wayland_session.h | 50 +- include/platform/input_context.h | 50 +- include/platform/update_context.h | 12 +- include/platform/update_shared_memory.h | 2 +- include/platform/wayland_context.h | 96 ++-- include/platform/wayland_shared_memory.h | 25 +- include/utils/error.h | 2 +- include/utils/memory.h | 127 +++-- include/utils/system_memory.h | 306 ++++++----- src/config/config.cpp | 232 +++++---- src/config/config_watcher.cpp | 11 +- src/core/main.cpp | 238 +++++---- src/graphics/animation.cpp | 479 +++++++++--------- src/graphics/animation_init.cpp | 58 +-- src/graphics/bar.cpp | 169 +++--- src/graphics/drawing_images.cpp | 110 ++-- .../bongocat/load_images_bongocat.cpp | 2 +- src/image_loader/custom/load_custom.cpp | 7 +- src/image_loader/dm/load_images_dm.cpp | 2 +- src/image_loader/dm20/load_images_dm20.cpp | 2 +- src/image_loader/dmall/load_images_dmall.cpp | 2 +- src/image_loader/dmc/load_images_dmc.cpp | 2 +- src/image_loader/dmx/load_images_dmx.cpp | 2 +- src/image_loader/load_images.cpp | 24 +- src/image_loader/load_images_hybrid.cpp | 29 +- src/image_loader/load_images_pngle.cpp | 23 +- src/image_loader/load_images_stb_image.cpp | 10 +- .../min_dm/load_images_min_dm.cpp | 2 +- src/image_loader/misc/load_images_misc.cpp | 2 +- .../ms_agent/load_images_ms_agent.cpp | 12 +- src/image_loader/pen/load_images_pen.cpp | 2 +- src/image_loader/pen20/load_images_pen20.cpp | 2 +- src/image_loader/pkmn/load_images_pkmn.cpp | 2 +- src/image_loader/pmd/load_images_pmd.cpp | 2 +- src/platform/input.cpp | 158 +++--- src/platform/update.cpp | 119 +++-- src/platform/wayland.cpp | 68 +-- src/platform/wayland_callbacks.cpp | 125 ++--- src/platform/wayland_hyprland.cpp | 19 +- src/platform/wayland_setups.cpp | 46 +- src/platform/wayland_sway.cpp | 2 +- src/utils/error.cpp | 8 +- src/utils/memory.cpp | 59 ++- src/utils/system_memory.cpp | 20 +- src/utils/time.cpp | 8 +- 63 files changed, 1635 insertions(+), 1394 deletions(-) diff --git a/.clang-format b/.clang-format index edcd12be..82e14f3d 100644 --- a/.clang-format +++ b/.clang-format @@ -72,6 +72,7 @@ BreakConstructorInitializers: BeforeComma ConstructorInitializerAllOnOneLineOrOnePerLine: true AllowAllConstructorInitializersOnNextLine: false IndentWrappedFunctionNames: false +AlwaysBreakTemplateDeclarations: Yes # Spaces SpaceAfterCStyleCast: false diff --git a/include/config/config.h b/include/config/config.h index a14bcd22..f5fdca79 100644 --- a/include/config/config.h +++ b/include/config/config.h @@ -8,8 +8,6 @@ #include #include #include -// POSIX feature test macro - must be before includes -#define _POSIX_C_SOURCE 200809L namespace bongocat::config { enum class overlay_position_t : uint8_t { @@ -89,7 +87,7 @@ enum class config_animation_custom_set_t : uint8_t { }; struct config_t { - char *output_name{nullptr}; + char *output_name{BONGOCAT_NULLPTR}; char *keyboard_devices[input::MAX_INPUT_DEVICES]{}; int32_t num_keyboard_devices{0}; int32_t cat_x_offset{0}; @@ -143,7 +141,7 @@ struct config_t { int32_t screen_width{0}; - char *custom_sprite_sheet_filename{nullptr}; // must be png file + char *custom_sprite_sheet_filename{BONGOCAT_NULLPTR}; // must be png file assets::custom_animation_settings_t custom_sprite_sheet_settings{}; int32_t enable_hand_mapping{0}; @@ -151,14 +149,14 @@ struct config_t { // for keep old index when reload config bool _keep_old_animation_index{false}; bool _strict{false}; - bool _custom{false}; // is custom sprite sheet - char *_animation_name{nullptr}; // original animation_anim from parsing config - char *_loaded_animation_fqname{nullptr}; + bool _custom{false}; // is custom sprite sheet + char *_animation_name{BONGOCAT_NULLPTR}; // original animation_anim from parsing config + char *_loaded_animation_fqname{BONGOCAT_NULLPTR}; // Make Config movable and copyable config_t() { for (size_t i = 0; i < input::MAX_INPUT_DEVICES; ++i) { - keyboard_devices[i] = nullptr; + keyboard_devices[i] = BONGOCAT_NULLPTR; } } ~config_t() { @@ -213,12 +211,14 @@ struct config_t { , _keep_old_animation_index(other._keep_old_animation_index) , _strict(other._strict) , _custom(other._custom) { - output_name = other.output_name != nullptr ? strdup(other.output_name) : nullptr; + output_name = other.output_name != BONGOCAT_NULLPTR ? strdup(other.output_name) : BONGOCAT_NULLPTR; config_copy_keyboard_devices_from(*this, other); - custom_sprite_sheet_filename = - other.custom_sprite_sheet_filename != nullptr ? strdup(other.custom_sprite_sheet_filename) : nullptr; - _animation_name = other._animation_name != nullptr? strdup(other._animation_name) : nullptr; - _loaded_animation_fqname = other._loaded_animation_fqname != nullptr ? strdup(other._loaded_animation_fqname) : nullptr; + custom_sprite_sheet_filename = other.custom_sprite_sheet_filename != BONGOCAT_NULLPTR + ? strdup(other.custom_sprite_sheet_filename) + : BONGOCAT_NULLPTR; + _animation_name = other._animation_name != BONGOCAT_NULLPTR ? strdup(other._animation_name) : BONGOCAT_NULLPTR; + _loaded_animation_fqname = + other._loaded_animation_fqname != BONGOCAT_NULLPTR ? strdup(other._loaded_animation_fqname) : BONGOCAT_NULLPTR; } config_t& operator=(const config_t& other) { @@ -273,12 +273,15 @@ struct config_t { _strict = other._strict; _custom = other._custom; - output_name = other.output_name != nullptr ? strdup(other.output_name) : nullptr; + output_name = other.output_name != BONGOCAT_NULLPTR ? strdup(other.output_name) : BONGOCAT_NULLPTR; config_copy_keyboard_devices_from(*this, other); - custom_sprite_sheet_filename = - other.custom_sprite_sheet_filename != nullptr ? strdup(other.custom_sprite_sheet_filename) : nullptr; - _animation_name = other._animation_name != nullptr ? strdup(other._animation_name) : nullptr; - _loaded_animation_fqname = other._loaded_animation_fqname != nullptr ? strdup(other._loaded_animation_fqname) : nullptr; + custom_sprite_sheet_filename = other.custom_sprite_sheet_filename != BONGOCAT_NULLPTR + ? strdup(other.custom_sprite_sheet_filename) + : BONGOCAT_NULLPTR; + _animation_name = other._animation_name != BONGOCAT_NULLPTR ? strdup(other._animation_name) : BONGOCAT_NULLPTR; + _loaded_animation_fqname = other._loaded_animation_fqname != BONGOCAT_NULLPTR + ? strdup(other._loaded_animation_fqname) + : BONGOCAT_NULLPTR; } return *this; } @@ -338,13 +341,13 @@ struct config_t { , _loaded_animation_fqname(other._loaded_animation_fqname) { for (int i = 0; i < num_keyboard_devices; ++i) { keyboard_devices[i] = other.keyboard_devices[i]; - other.keyboard_devices[i] = nullptr; + other.keyboard_devices[i] = BONGOCAT_NULLPTR; } other.num_keyboard_devices = 0; - other.output_name = nullptr; - other.custom_sprite_sheet_filename = nullptr; - other._animation_name = nullptr; - other._loaded_animation_fqname = nullptr; + other.output_name = BONGOCAT_NULLPTR; + other.custom_sprite_sheet_filename = BONGOCAT_NULLPTR; + other._animation_name = BONGOCAT_NULLPTR; + other._loaded_animation_fqname = BONGOCAT_NULLPTR; } config_t& operator=(config_t&& other) noexcept { @@ -406,44 +409,44 @@ struct config_t { for (int i = 0; i < num_keyboard_devices; ++i) { keyboard_devices[i] = other.keyboard_devices[i]; - other.keyboard_devices[i] = nullptr; + other.keyboard_devices[i] = BONGOCAT_NULLPTR; } other.num_keyboard_devices = 0; - other.output_name = nullptr; - other.custom_sprite_sheet_filename = nullptr; - other._animation_name = nullptr; - other._loaded_animation_fqname = nullptr; + other.output_name = BONGOCAT_NULLPTR; + other.custom_sprite_sheet_filename = BONGOCAT_NULLPTR; + other._animation_name = BONGOCAT_NULLPTR; + other._loaded_animation_fqname = BONGOCAT_NULLPTR; } return *this; } }; inline void cleanup(config_t& config) { - if (config.output_name != nullptr) { + if (config.output_name != BONGOCAT_NULLPTR) { ::free(config.output_name); + config.output_name = BONGOCAT_NULLPTR; } - config.output_name = nullptr; config_free_keyboard_devices(config); - if (config.custom_sprite_sheet_filename != nullptr) { + if (config.custom_sprite_sheet_filename != BONGOCAT_NULLPTR) { ::free(config.custom_sprite_sheet_filename); + config.custom_sprite_sheet_filename = BONGOCAT_NULLPTR; } - config.custom_sprite_sheet_filename = nullptr; - if (config._animation_name != nullptr) { + if (config._animation_name != BONGOCAT_NULLPTR) { ::free(config._animation_name); + config._animation_name = BONGOCAT_NULLPTR; } - config._animation_name = nullptr; - if (config._loaded_animation_fqname != nullptr) { + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); + config._loaded_animation_fqname = BONGOCAT_NULLPTR; } - config._loaded_animation_fqname = nullptr; } inline void config_free_keyboard_devices(config_t& config) { assert(config.num_keyboard_devices >= 0); for (size_t i = 0; i < static_cast(config.num_keyboard_devices) && i < input::MAX_INPUT_DEVICES; i++) { - if (config.keyboard_devices[i] != nullptr) { + if (config.keyboard_devices[i] != BONGOCAT_NULLPTR) { ::free(config.keyboard_devices[i]); + config.keyboard_devices[i] = BONGOCAT_NULLPTR; } - config.keyboard_devices[i] = nullptr; } config.num_keyboard_devices = 0; } @@ -452,7 +455,8 @@ inline void config_copy_keyboard_devices_from(config_t& config, const config_t& config.num_keyboard_devices = other.num_keyboard_devices; assert(config.num_keyboard_devices >= 0); for (size_t i = 0; i < static_cast(config.num_keyboard_devices) && i < input::MAX_INPUT_DEVICES; i++) { - config.keyboard_devices[i] = other.keyboard_devices[i] != nullptr ? strdup(other.keyboard_devices[i]) : nullptr; + config.keyboard_devices[i] = + other.keyboard_devices[i] != BONGOCAT_NULLPTR ? strdup(other.keyboard_devices[i]) : BONGOCAT_NULLPTR; } } @@ -461,10 +465,10 @@ inline void config_copy_keyboard_devices_from(config_t& config, const config_t& // ============================================================================= struct load_config_overwrite_parameters_t { - const char *output_name{nullptr}; + const char *output_name{BONGOCAT_NULLPTR}; int32_t randomize_index{-1}; int32_t strict{-1}; - const char *animation_name{nullptr}; + const char *animation_name{BONGOCAT_NULLPTR}; }; BONGOCAT_NODISCARD created_result_t load(const char *config_file_path, load_config_overwrite_parameters_t overwrite_parameters); diff --git a/include/config/config_watcher.h b/include/config/config_watcher.h index 79a1ef0c..57de6d62 100644 --- a/include/config/config_watcher.h +++ b/include/config/config_watcher.h @@ -29,7 +29,7 @@ struct config_watcher_t { platform::FileDescriptor inotify_fd; platform::FileDescriptor wd_file; platform::FileDescriptor wd_dir; - char *config_path{nullptr}; + char *config_path{BONGOCAT_NULLPTR}; platform::FileDescriptor reload_efd; @@ -62,9 +62,9 @@ inline void cleanup_watcher(config_watcher_t& watcher) { close_fd(watcher.inotify_fd); close_fd(watcher.reload_efd); - if (watcher.config_path) { + if (watcher.config_path != BONGOCAT_NULLPTR) { ::free(watcher.config_path); - watcher.config_path = nullptr; + watcher.config_path = BONGOCAT_NULLPTR; } } diff --git a/include/core/bongocat.h b/include/core/bongocat.h index 2f7e030a..6b412d9f 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -1,6 +1,9 @@ #ifndef BONGOCAT_BONGOCAT_H #define BONGOCAT_BONGOCAT_H +// POSIX feature test macro - must be before includes +#define _POSIX_C_SOURCE 200809L + #include "utils/error.h" #include "utils/memory.h" @@ -26,13 +29,14 @@ inline static constexpr int32_t RGBA_CHANNELS = 4; inline static constexpr int32_t BGRA_CHANNELS = 4; namespace bongocat { -template struct created_result_t { +template +struct created_result_t { T result{}; bongocat_error_t error{bongocat_error_t::BONGOCAT_SUCCESS}; created_result_t() = default; explicit(false) created_result_t(bongocat_error_t err) : error(err) {} - explicit(false) created_result_t(T&& res) : result(bongocat::move(res)), error(bongocat_error_t::BONGOCAT_SUCCESS) {} + explicit(false) created_result_t(T&& res) : result(bongocat::move(res)) {} }; // feature flags @@ -196,54 +200,54 @@ namespace platform { template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_or(Enum lhs, Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_or(Enum lhs, Enum rhs) noexcept { return static_cast(static_cast>(lhs) | static_cast>(rhs)); } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_and(Enum lhs, Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_and(Enum lhs, Enum rhs) noexcept { return static_cast(static_cast>(lhs) & static_cast>(rhs)); } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_xor(Enum lhs, Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_xor(Enum lhs, Enum rhs) noexcept { return static_cast(static_cast>(lhs) ^ static_cast>(rhs)); } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_not(Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_not(Enum rhs) noexcept { return static_cast(~static_cast>(rhs)); } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_add(Enum lhs, Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_add(Enum lhs, Enum rhs) noexcept { lhs = flag_or(lhs, rhs); return lhs; } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_remove(Enum lhs, Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_remove(Enum lhs, Enum rhs) noexcept { return static_cast(static_cast(lhs) & ~static_cast(rhs)); } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr Enum flag_assign(Enum lhs, Enum rhs) noexcept { +BONGOCAT_NODISCARD constexpr Enum flag_assign(Enum lhs, Enum rhs) noexcept { lhs = flag_and(lhs, rhs); return lhs; } template requires std::is_enum_v && (std::is_same_v, uint32_t> || std::is_same_v, uint64_t>) -BONGOCAT_NODISCARD inline constexpr bool has_flag(Enum value, Enum flag) noexcept { +BONGOCAT_NODISCARD constexpr bool has_flag(Enum value, Enum flag) noexcept { return (static_cast(value) & static_cast(flag)) != 0; } } // namespace bongocat diff --git a/include/embedded_assets/bongocat/bongocat.h b/include/embedded_assets/bongocat/bongocat.h index bab6de9b..83a286d3 100644 --- a/include/embedded_assets/bongocat/bongocat.h +++ b/include/embedded_assets/bongocat/bongocat.h @@ -29,8 +29,8 @@ inline static constexpr int BONGOCAT_FRAME_HEIGHT = 360; inline static constexpr size_t BONGOCAT_EMBEDDED_IMAGES_COUNT = animation::BONGOCAT_NUM_FRAMES; inline static constexpr size_t BONGOCAT_ANIMATIONS_COUNT = 1; -[[nodiscard]] extern embedded_image_t get_bongocat_sprite(size_t i); -[[nodiscard]] extern created_result_t +BONGOCAT_NODISCARD extern embedded_image_t get_bongocat_sprite(size_t i); +BONGOCAT_NODISCARD extern created_result_t get_bongocat_sprite_sheet(const animation::animation_context_t& ctx, int index); } // namespace bongocat::assets diff --git a/include/embedded_assets/embedded_image.h b/include/embedded_assets/embedded_image.h index 86022d82..c5cc019e 100644 --- a/include/embedded_assets/embedded_image.h +++ b/include/embedded_assets/embedded_image.h @@ -7,7 +7,7 @@ namespace bongocat::assets { struct embedded_image_t { - const unsigned char *data{nullptr}; + const unsigned char *data{BONGOCAT_NULLPTR}; size_t size{0}; const char *name{""}; }; diff --git a/include/embedded_assets/min_dm/min_dm_sprite.h b/include/embedded_assets/min_dm/min_dm_sprite.h index b0985fdd..db5b2502 100644 --- a/include/embedded_assets/min_dm/min_dm_sprite.h +++ b/include/embedded_assets/min_dm/min_dm_sprite.h @@ -4,7 +4,7 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::assets { -[[nodiscard]] extern embedded_image_t get_min_dm_sprite_sheet(size_t i); +BONGOCAT_NODISCARD extern embedded_image_t get_min_dm_sprite_sheet(size_t i); } #endif \ No newline at end of file diff --git a/include/embedded_assets/misc/misc_sprite.h b/include/embedded_assets/misc/misc_sprite.h index 2cd947fe..6d219285 100644 --- a/include/embedded_assets/misc/misc_sprite.h +++ b/include/embedded_assets/misc/misc_sprite.h @@ -7,8 +7,8 @@ namespace bongocat::assets { inline static constexpr size_t MISC_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 1; inline static constexpr size_t MISC_ANIMATIONS_COUNT = 1; -[[nodiscard]] extern embedded_image_t get_misc_sprite_sheet(size_t i); -[[nodiscard]] extern custom_animation_settings_t get_misc_sprite_sheet_columns(size_t i); +BONGOCAT_NODISCARD extern embedded_image_t get_misc_sprite_sheet(size_t i); +BONGOCAT_NODISCARD extern custom_animation_settings_t get_misc_sprite_sheet_columns(size_t i); } // namespace bongocat::assets #endif \ No newline at end of file diff --git a/include/embedded_assets/ms_agent/ms_agent_sprite.h b/include/embedded_assets/ms_agent/ms_agent_sprite.h index 46c00af7..266a5027 100644 --- a/include/embedded_assets/ms_agent/ms_agent_sprite.h +++ b/include/embedded_assets/ms_agent/ms_agent_sprite.h @@ -85,8 +85,8 @@ inline static constexpr size_t MS_AGENTS_SPRITE_SHEET_EMBEDDED_IMAGES_COUNT = 1; inline static constexpr size_t MS_AGENTS_ANIMATIONS_COUNT = 1; #endif -[[nodiscard]] embedded_image_t get_ms_agent_sprite_sheet(size_t i); -[[nodiscard]] ms_agent_animation_indices_t get_ms_agent_animation_indices(size_t i); +BONGOCAT_NODISCARD embedded_image_t get_ms_agent_sprite_sheet(size_t i); +BONGOCAT_NODISCARD ms_agent_animation_indices_t get_ms_agent_animation_indices(size_t i); } // namespace bongocat::assets #endif // BONGOCAT_EMBEDDED_ASSETS_CLIPPY_H diff --git a/include/graphics/animation_shared_memory.h b/include/graphics/animation_shared_memory.h index 37bee41c..712de431 100644 --- a/include/graphics/animation_shared_memory.h +++ b/include/graphics/animation_shared_memory.h @@ -7,7 +7,7 @@ #include "utils/time.h" namespace bongocat::animation { -enum class animation_player_custom_overwrite_mirror_x : uint32_t { +enum class animation_player_custom_overwrite_mirror_x : uint8_t { None, NoMirror, Mirror diff --git a/include/graphics/global_animation_session.h b/include/graphics/global_animation_session.h index c905947e..561ab522 100644 --- a/include/graphics/global_animation_session.h +++ b/include/graphics/global_animation_session.h @@ -19,12 +19,12 @@ struct animation_session_t { platform::FileDescriptor render_efd; // globals (references) - const config::config_t *_config{nullptr}; - platform::CondVariable *_configs_reloaded_cond{nullptr}; - platform::input::input_context_t *_input{nullptr}; - platform::update::update_context_t *_update{nullptr}; - atomic_uint64_t *_config_generation{nullptr}; - atomic_bool ready; + const config::config_t *_config{BONGOCAT_NULLPTR}; + platform::CondVariable *_configs_reloaded_cond{BONGOCAT_NULLPTR}; + platform::input::input_context_t *_input{BONGOCAT_NULLPTR}; + platform::update::update_context_t *_update{BONGOCAT_NULLPTR}; + atomic_uint64_t *_config_generation{BONGOCAT_NULLPTR}; + atomic_bool ready{false}; platform::CondVariable init_cond; animation_session_t() = default; @@ -40,9 +40,9 @@ struct animation_session_t { inline void stop(animation_session_t& anim_ctx) { stop(anim_ctx.anim); - anim_ctx._config = nullptr; - anim_ctx._configs_reloaded_cond = nullptr; - anim_ctx._config_generation = nullptr; + anim_ctx._config = BONGOCAT_NULLPTR; + anim_ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + anim_ctx._config_generation = BONGOCAT_NULLPTR; anim_ctx.anim.config_updated.notify_all(); atomic_store(&anim_ctx.ready, false); @@ -54,10 +54,10 @@ inline void cleanup(animation_session_t& anim_ctx) { platform::close_fd(anim_ctx.trigger_efd); platform::close_fd(anim_ctx.render_efd); - anim_ctx._config = nullptr; - anim_ctx._input = nullptr; - anim_ctx._update = nullptr; - anim_ctx._configs_reloaded_cond = nullptr; + anim_ctx._config = BONGOCAT_NULLPTR; + anim_ctx._input = BONGOCAT_NULLPTR; + anim_ctx._update = BONGOCAT_NULLPTR; + anim_ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; atomic_store(&anim_ctx.ready, false); anim_ctx.init_cond.notify_all(); } diff --git a/include/graphics/sprite_sheet.h b/include/graphics/sprite_sheet.h index 55d2e910..173c8c42 100644 --- a/include/graphics/sprite_sheet.h +++ b/include/graphics/sprite_sheet.h @@ -232,17 +232,17 @@ struct animation_t { custom_sprite_sheet_t custom; generic_sprite_sheet_t sprite_sheet; }; - enum class Type : uint8_t { + enum class type_t : uint8_t { Generic, Bongocat, Dm, MsAgent, Pkmn, Custom - } type{Type::Generic}; + } type{type_t::Generic}; animation_t() { - sprite_sheet.image.pixels.data = nullptr; + sprite_sheet.image.pixels.data = BONGOCAT_NULLPTR; sprite_sheet.image.pixels.count = 0; sprite_sheet.image.channels = 0; sprite_sheet.image.sprite_sheet_width = 0; @@ -253,7 +253,7 @@ struct animation_t { for (size_t i = 0; i < MAX_NUM_FRAMES; i++) { sprite_sheet.frames[i] = {}; } - type = Type::Generic; + type = type_t::Generic; } ~animation_t() { cleanup_animation(*this); @@ -262,22 +262,22 @@ struct animation_t { animation_t(const animation_t& other) { type = other.type; switch (other.type) { - case Type::Bongocat: + case type_t::Bongocat: bongocat = other.bongocat; break; - case Type::Dm: + case type_t::Dm: dm = other.dm; break; - case Type::MsAgent: + case type_t::MsAgent: ms_agent = other.ms_agent; break; - case Type::Pkmn: + case type_t::Pkmn: pkmn = other.pkmn; break; - case Type::Custom: + case type_t::Custom: custom = other.custom; break; - case Type::Generic: + case type_t::Generic: sprite_sheet = other.sprite_sheet; break; } @@ -287,22 +287,22 @@ struct animation_t { cleanup_animation(*this); type = other.type; switch (other.type) { - case Type::Bongocat: + case type_t::Bongocat: bongocat = other.bongocat; break; - case Type::Dm: + case type_t::Dm: dm = other.dm; break; - case Type::MsAgent: + case type_t::MsAgent: ms_agent = other.ms_agent; break; - case Type::Pkmn: + case type_t::Pkmn: pkmn = other.pkmn; break; - case Type::Custom: + case type_t::Custom: custom = other.custom; break; - case Type::Generic: + case type_t::Generic: sprite_sheet = other.sprite_sheet; break; } @@ -313,26 +313,26 @@ struct animation_t { animation_t(animation_t&& other) noexcept { type = other.type; switch (other.type) { - case Type::Bongocat: + case type_t::Bongocat: new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(other.bongocat)); break; - case Type::Dm: + case type_t::Dm: new (&dm) dm_sprite_sheet_t(bongocat::move(other.dm)); break; - case Type::MsAgent: + case type_t::MsAgent: new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(other.ms_agent)); break; - case Type::Pkmn: + case type_t::Pkmn: new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(other.pkmn)); break; - case Type::Custom: + case type_t::Custom: new (&custom) custom_sprite_sheet_t(bongocat::move(other.custom)); break; - case Type::Generic: + case type_t::Generic: new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(other.sprite_sheet)); break; } - other.type = Type::Generic; + other.type = type_t::Generic; new (&other.sprite_sheet) generic_sprite_sheet_t(); } animation_t& operator=(animation_t&& other) noexcept { @@ -340,26 +340,26 @@ struct animation_t { cleanup_animation(*this); type = other.type; switch (other.type) { - case Type::Bongocat: + case type_t::Bongocat: new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(other.bongocat)); break; - case Type::Dm: + case type_t::Dm: new (&dm) dm_sprite_sheet_t(bongocat::move(other.dm)); break; - case Type::MsAgent: + case type_t::MsAgent: new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(other.ms_agent)); break; - case Type::Pkmn: + case type_t::Pkmn: new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(other.pkmn)); break; - case Type::Custom: + case type_t::Custom: new (&custom) custom_sprite_sheet_t(bongocat::move(other.custom)); break; - case Type::Generic: + case type_t::Generic: new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(other.sprite_sheet)); break; } - other.type = Type::Generic; + other.type = type_t::Generic; new (&other.sprite_sheet) generic_sprite_sheet_t(); } return *this; @@ -367,56 +367,56 @@ struct animation_t { explicit animation_t(bongocat_sprite_sheet_t&& sheet) noexcept : bongocat(bongocat::move(sheet)) - , type(Type::Bongocat) {} + , type(type_t::Bongocat) {} - explicit animation_t(dm_sprite_sheet_t&& sheet) noexcept : dm(bongocat::move(sheet)), type(Type::Dm) {} + explicit animation_t(dm_sprite_sheet_t&& sheet) noexcept : dm(bongocat::move(sheet)), type(type_t::Dm) {} explicit animation_t(ms_agent_sprite_sheet_t&& sheet) noexcept : ms_agent(bongocat::move(sheet)) - , type(Type::MsAgent) {} + , type(type_t::MsAgent) {} - explicit animation_t(pkmn_sprite_sheet_t&& sheet) noexcept : pkmn(bongocat::move(sheet)), type(Type::Pkmn) {} + explicit animation_t(pkmn_sprite_sheet_t&& sheet) noexcept : pkmn(bongocat::move(sheet)), type(type_t::Pkmn) {} - explicit animation_t(custom_sprite_sheet_t&& sheet) noexcept : custom(bongocat::move(sheet)), type(Type::Custom) {} + explicit animation_t(custom_sprite_sheet_t&& sheet) noexcept : custom(bongocat::move(sheet)), type(type_t::Custom) {} explicit animation_t(generic_sprite_sheet_t&& sheet) noexcept : sprite_sheet(bongocat::move(sheet)) - , type(Type::Generic) {} + , type(type_t::Generic) {} animation_t& operator=(bongocat_sprite_sheet_t&& sheet) noexcept { cleanup_animation(*this); new (&bongocat) bongocat_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Bongocat; + type = type_t::Bongocat; return *this; } animation_t& operator=(dm_sprite_sheet_t&& sheet) noexcept { cleanup_animation(*this); new (&dm) dm_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Dm; + type = type_t::Dm; return *this; } animation_t& operator=(ms_agent_sprite_sheet_t&& sheet) noexcept { cleanup_animation(*this); new (&ms_agent) ms_agent_sprite_sheet_t(bongocat::move(sheet)); - type = Type::MsAgent; + type = type_t::MsAgent; return *this; } animation_t& operator=(pkmn_sprite_sheet_t&& sheet) noexcept { cleanup_animation(*this); new (&pkmn) pkmn_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Pkmn; + type = type_t::Pkmn; return *this; } animation_t& operator=(custom_sprite_sheet_t&& sheet) noexcept { cleanup_animation(*this); new (&custom) custom_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Custom; + type = type_t::Custom; return *this; } animation_t& operator=(generic_sprite_sheet_t&& sheet) noexcept { cleanup_animation(*this); new (&sprite_sheet) generic_sprite_sheet_t(bongocat::move(sheet)); - type = Type::Generic; + type = type_t::Generic; return *this; } }; @@ -520,7 +520,7 @@ inline void cleanup_animation(custom_sprite_sheet_t& sprite_sheet) { } inline void cleanup_animation(animation_t& anim) { switch (anim.type) { - case animation_t::Type::Bongocat: + case animation_t::type_t::Bongocat: release_allocated_array(anim.bongocat.image.pixels); anim.bongocat.image.sprite_sheet_width = 0; anim.bongocat.image.sprite_sheet_height = 0; @@ -528,7 +528,7 @@ inline void cleanup_animation(animation_t& anim) { anim.bongocat.frame_width = 0; anim.bongocat.frame_height = 0; break; - case animation_t::Type::Dm: + case animation_t::type_t::Dm: release_allocated_array(anim.dm.image.pixels); anim.dm.image.sprite_sheet_width = 0; anim.dm.image.sprite_sheet_height = 0; @@ -538,7 +538,7 @@ inline void cleanup_animation(animation_t& anim) { anim.dm.total_frames = 0; break; - case animation_t::Type::MsAgent: + case animation_t::type_t::MsAgent: release_allocated_array(anim.ms_agent.image.pixels); anim.ms_agent.image.sprite_sheet_width = 0; anim.ms_agent.image.sprite_sheet_height = 0; @@ -546,7 +546,7 @@ inline void cleanup_animation(animation_t& anim) { anim.ms_agent.frame_width = 0; anim.ms_agent.frame_height = 0; break; - case animation_t::Type::Pkmn: + case animation_t::type_t::Pkmn: release_allocated_array(anim.pkmn.image.pixels); anim.pkmn.image.sprite_sheet_width = 0; anim.pkmn.image.sprite_sheet_height = 0; @@ -554,7 +554,7 @@ inline void cleanup_animation(animation_t& anim) { anim.pkmn.frame_width = 0; anim.pkmn.frame_height = 0; break; - case animation_t::Type::Custom: + case animation_t::type_t::Custom: release_allocated_array(anim.custom.image.pixels); anim.custom.image.sprite_sheet_width = 0; anim.custom.image.sprite_sheet_height = 0; @@ -562,7 +562,7 @@ inline void cleanup_animation(animation_t& anim) { anim.custom.frame_width = 0; anim.custom.frame_height = 0; break; - case animation_t::Type::Generic: + case animation_t::type_t::Generic: release_allocated_array(anim.sprite_sheet.image.pixels); anim.sprite_sheet.image.sprite_sheet_width = 0; anim.sprite_sheet.image.sprite_sheet_height = 0; diff --git a/include/image_loader/bongocat/load_images_bongocat.h b/include/image_loader/bongocat/load_images_bongocat.h index 85b298e6..4635ec4b 100644 --- a/include/image_loader/bongocat/load_images_bongocat.h +++ b/include/image_loader/bongocat/load_images_bongocat.h @@ -6,11 +6,11 @@ namespace bongocat::animation { struct animation_context_t; -[[nodiscard]] created_result_t +BONGOCAT_NODISCARD created_result_t load_bongocat_anim(int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); -[[nodiscard]] created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, +BONGOCAT_NODISCARD created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, int index); } // namespace bongocat::animation diff --git a/include/image_loader/custom/load_custom.h b/include/image_loader/custom/load_custom.h index 2e3d1d16..c372f188 100644 --- a/include/image_loader/custom/load_custom.h +++ b/include/image_loader/custom/load_custom.h @@ -16,12 +16,12 @@ void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept; namespace bongocat::assets { struct custom_image_t { platform::MMapFileContent data; - char *name{nullptr}; + char *name{BONGOCAT_NULLPTR}; custom_image_t() = default; custom_image_t(const custom_image_t& other) = delete; custom_image_t(custom_image_t&& other) noexcept : data(bongocat::move(other.data)), name(other.name) { - other.name = nullptr; + other.name = BONGOCAT_NULLPTR; } ~custom_image_t() { animation::free_custom_sprite_sheet_file(*this); @@ -34,12 +34,13 @@ struct custom_image_t { data = bongocat::move(other.data); - if (name) + if (name != BONGOCAT_NULLPTR) { ::free(name); - name = nullptr; + name = BONGOCAT_NULLPTR; + } name = other.name; - other.name = nullptr; + other.name = BONGOCAT_NULLPTR; } return *this; } diff --git a/include/image_loader/load_images.h b/include/image_loader/load_images.h index f3e180ef..2c231e21 100644 --- a/include/image_loader/load_images.h +++ b/include/image_loader/load_images.h @@ -14,13 +14,14 @@ namespace bongocat::animation { // IMAGE LOADING MODULE // ============================================================================= -struct Image; +class Image; created_result_t load_image(const unsigned char *data, size_t size, int desired_channels = RGBA_CHANNELS); void cleanup_image(Image& image); void init_image_loader(); -struct Image { - unsigned char *pixels{nullptr}; +class Image { +public: + unsigned char *pixels{BONGOCAT_NULLPTR}; int width{0}; int height{0}; int channels{0}; @@ -39,8 +40,9 @@ struct Image { ::memcpy(pixels, other.pixels, data_size); } Image& operator=(const Image& other) { - if (this == &other) + if (this == &other) { return *this; + } cleanup_image(*this); @@ -60,14 +62,15 @@ struct Image { Image(Image&& other) noexcept : pixels(other.pixels), width(other.width), height(other.height), channels(other.channels) { - other.pixels = nullptr; + other.pixels = BONGOCAT_NULLPTR; other.width = 0; other.height = 0; other.channels = 0; } Image& operator=(Image&& other) noexcept { - if (this == &other) + if (this == &other) { return *this; + } cleanup_image(*this); @@ -76,7 +79,7 @@ struct Image { height = other.height; channels = other.channels; - other.pixels = nullptr; + other.pixels = BONGOCAT_NULLPTR; other.width = 0; other.height = 0; other.channels = 0; diff --git a/include/image_loader/min_dm/load_images_min_dm.h b/include/image_loader/min_dm/load_images_min_dm.h index aebef148..dc1e1b5a 100644 --- a/include/image_loader/min_dm/load_images_min_dm.h +++ b/include/image_loader/min_dm/load_images_min_dm.h @@ -10,5 +10,5 @@ bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); -[[nodiscard]] created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index); +BONGOCAT_NODISCARD created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index); } // namespace bongocat::animation diff --git a/include/image_loader/misc/load_images_misc.h b/include/image_loader/misc/load_images_misc.h index 45d5f005..bfce9733 100644 --- a/include/image_loader/misc/load_images_misc.h +++ b/include/image_loader/misc/load_images_misc.h @@ -11,5 +11,5 @@ bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); -[[nodiscard]] created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index); +BONGOCAT_NODISCARD created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index); } // namespace bongocat::animation diff --git a/include/image_loader/ms_agent/load_images_ms_agent.h b/include/image_loader/ms_agent/load_images_ms_agent.h index 07cc4d5e..4d06e5f5 100644 --- a/include/image_loader/ms_agent/load_images_ms_agent.h +++ b/include/image_loader/ms_agent/load_images_ms_agent.h @@ -7,10 +7,10 @@ namespace bongocat::animation { struct animation_context_t; -[[nodiscard]] created_result_t +BONGOCAT_NODISCARD created_result_t load_ms_agent_sprite_sheet(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); -[[nodiscard]] created_result_t +BONGOCAT_NODISCARD created_result_t load_ms_agent_anim(const animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data); @@ -19,6 +19,6 @@ init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embed int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data); -[[nodiscard]] created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, - int index); +BONGOCAT_NODISCARD created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, + int index); } // namespace bongocat::animation diff --git a/include/platform/global_wayland_session.h b/include/platform/global_wayland_session.h index 237c4884..447e2910 100644 --- a/include/platform/global_wayland_session.h +++ b/include/platform/global_wayland_session.h @@ -19,13 +19,13 @@ inline static constexpr size_t OUTPUT_NAME_SIZE = 128; // ============================================================================= struct fullscreen_detector_t { - struct zwlr_foreign_toplevel_manager_v1 *manager{nullptr}; + struct zwlr_foreign_toplevel_manager_v1 *manager{BONGOCAT_NULLPTR}; bool has_fullscreen_toplevel{false}; timeval last_check{}; }; struct tracked_toplevel_t { - struct zwlr_foreign_toplevel_handle_v1 *handle{nullptr}; - wl_output *output{nullptr}; + struct zwlr_foreign_toplevel_handle_v1 *handle{BONGOCAT_NULLPTR}; + wl_output *output{BONGOCAT_NULLPTR}; bool is_fullscreen{false}; }; @@ -34,12 +34,12 @@ struct tracked_toplevel_t { // ============================================================================= enum class screen_info_received_flags_t : uint32_t { - None = (1u << 0), - Mode = (1u << 1), - Geometry = (1u << 2), + None = (1U << 0), + Mode = (1U << 1), + Geometry = (1U << 2), }; struct screen_info_t { - struct wl_output *wl_output{nullptr}; // ref of output + struct wl_output *wl_output{BONGOCAT_NULLPTR}; // ref of output int screen_width{0}; int screen_height{0}; int transform{0}; @@ -58,8 +58,8 @@ enum class output_ref_received_flags_t : uint32_t { }; // Output monitor reference structure struct output_ref_t { - struct wl_output *wl_output{nullptr}; - zxdg_output_v1 *xdg_output{nullptr}; + struct wl_output *wl_output{BONGOCAT_NULLPTR}; + zxdg_output_v1 *xdg_output{BONGOCAT_NULLPTR}; uint32_t name{0}; // Registry name char name_str[OUTPUT_NAME_SIZE]{}; // From xdg-output int32_t x{0}; @@ -70,21 +70,21 @@ struct output_ref_t { // monitor ID in Hyprland int64_t hypr_id{-1}; // back reference - wayland_session_t *wayland{nullptr}; + wayland_session_t *wayland{BONGOCAT_NULLPTR}; }; void cleanup_wayland(wayland_session_t& ctx); struct wayland_session_t { wayland_context_t wayland_context; - animation::animation_session_t *animation_trigger_context{nullptr}; + animation::animation_session_t *animation_trigger_context{BONGOCAT_NULLPTR}; tracked_toplevel_t tracked_toplevels[MAX_TOP_LEVELS]; size_t num_toplevels{0}; output_ref_t outputs[MAX_OUTPUTS]; size_t output_count{0}; - zxdg_output_manager_v1 *xdg_output_manager{nullptr}; + zxdg_output_manager_v1 *xdg_output_manager{BONGOCAT_NULLPTR}; fullscreen_detector_t fs_detector; @@ -117,38 +117,40 @@ inline void cleanup_wayland(wayland_session_t& ctx) { // First destroy xdg_output objects for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].xdg_output) { + if (ctx.outputs[i].xdg_output != BONGOCAT_NULLPTR) { zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); - ctx.outputs[i].xdg_output = nullptr; + ctx.outputs[i].xdg_output = BONGOCAT_NULLPTR; } } // Then destroy the manager - if (ctx.xdg_output_manager) { + if (ctx.xdg_output_manager != BONGOCAT_NULLPTR) { zxdg_output_manager_v1_destroy(ctx.xdg_output_manager); - ctx.xdg_output_manager = nullptr; + ctx.xdg_output_manager = BONGOCAT_NULLPTR; } // Finally destroy wl_output objects for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].wl_output) { + if (ctx.outputs[i].wl_output != BONGOCAT_NULLPTR) { wl_output_destroy(ctx.outputs[i].wl_output); - ctx.outputs[i].wl_output = nullptr; + ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; } ctx.outputs[i] = {}; - ctx.outputs[i].wl_output = nullptr; - ctx.outputs[i].wayland = nullptr; + ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; + ctx.outputs[i].wayland = BONGOCAT_NULLPTR; } ctx.output_count = 0; - if (ctx.fs_detector.manager) { + if (ctx.fs_detector.manager != BONGOCAT_NULLPTR) { zwlr_foreign_toplevel_manager_v1_destroy(ctx.fs_detector.manager); - ctx.fs_detector.manager = nullptr; + ctx.fs_detector.manager = BONGOCAT_NULLPTR; } for (size_t i = 0; i < ctx.num_toplevels; ++i) { - if (ctx.tracked_toplevels[i].handle) + if (ctx.tracked_toplevels[i].handle != BONGOCAT_NULLPTR) { zwlr_foreign_toplevel_handle_v1_destroy(ctx.tracked_toplevels[i].handle); + ctx.tracked_toplevels[i].handle = BONGOCAT_NULLPTR; + } ctx.tracked_toplevels[i] = {}; } ctx.num_toplevels = 0; @@ -161,7 +163,7 @@ inline void cleanup_wayland(wayland_session_t& ctx) { // clean up wayland context cleanup_wayland_context(ctx.wayland_context); - ctx.animation_trigger_context = nullptr; + ctx.animation_trigger_context = BONGOCAT_NULLPTR; } } // namespace bongocat::platform::wayland diff --git a/include/platform/input_context.h b/include/platform/input_context.h index d77fee07..84ee0d4e 100644 --- a/include/platform/input_context.h +++ b/include/platform/input_context.h @@ -19,8 +19,8 @@ enum class input_unique_file_type_t : uint8_t { struct input_unique_file_t; void cleanup(input_unique_file_t& file); struct input_unique_file_t { - const char *_device_path{nullptr}; // original string from config (ref to input_context_t._device_paths[i]) - char *canonical_path{nullptr}; // resolved real path (malloc'd) + const char *_device_path{BONGOCAT_NULLPTR}; // original string from config (ref to input_context_t._device_paths[i]) + char *canonical_path{BONGOCAT_NULLPTR}; // resolved real path (malloc'd) FileDescriptor fd; input_unique_file_type_t type{input_unique_file_type_t::NONE}; @@ -37,8 +37,8 @@ struct input_unique_file_t { , canonical_path(other.canonical_path) , fd(bongocat::move(other.fd)) , type(other.type) { - other._device_path = nullptr; - other.canonical_path = nullptr; + other._device_path = BONGOCAT_NULLPTR; + other.canonical_path = BONGOCAT_NULLPTR; other.type = input_unique_file_type_t::NONE; } input_unique_file_t& operator=(input_unique_file_t&& other) noexcept { @@ -50,8 +50,8 @@ struct input_unique_file_t { fd = bongocat::move(other.fd); type = other.type; - other._device_path = nullptr; - other.canonical_path = nullptr; + other._device_path = BONGOCAT_NULLPTR; + other.canonical_path = BONGOCAT_NULLPTR; other.type = input_unique_file_type_t::NONE; } return *this; @@ -59,10 +59,11 @@ struct input_unique_file_t { }; inline void cleanup(input_unique_file_t& file) { close_fd(file.fd); - file._device_path = nullptr; - if (file.canonical_path) + file._device_path = BONGOCAT_NULLPTR; + if (file.canonical_path != BONGOCAT_NULLPTR) { ::free(file.canonical_path); - file.canonical_path = nullptr; + file.canonical_path = BONGOCAT_NULLPTR; + } file.type = input_unique_file_type_t::NONE; } @@ -94,8 +95,8 @@ struct input_context_t { // _unique_paths_indices.count to used unique_paths_indices AllocatedArray _unique_devices; /// udev monitoring - udev *_udev{nullptr}; - udev_monitor *_udev_mon{nullptr}; + udev *_udev{BONGOCAT_NULLPTR}; + udev_monitor *_udev_mon{BONGOCAT_NULLPTR}; int _udev_fd{-1}; // config reload threading @@ -104,9 +105,9 @@ struct input_context_t { platform::CondVariable config_updated; // globals (references) - const config::config_t *_config{nullptr}; - platform::CondVariable *_configs_reloaded_cond{nullptr}; - atomic_uint64_t *_config_generation{nullptr}; + const config::config_t *_config{BONGOCAT_NULLPTR}; + platform::CondVariable *_configs_reloaded_cond{BONGOCAT_NULLPTR}; + atomic_uint64_t *_config_generation{BONGOCAT_NULLPTR}; atomic_bool ready; platform::CondVariable init_cond; @@ -132,18 +133,21 @@ inline void cleanup(input_context_t& ctx) { ctx._unique_paths_indices_capacity = 0; release_allocated_array(ctx._unique_paths_indices); for (size_t i = 0; i < ctx._device_paths.count; i++) { - if (ctx._device_paths[i]) + if (ctx._device_paths[i] != BONGOCAT_NULLPTR) { ::free(ctx._device_paths[i]); - ctx._device_paths[i] = nullptr; + ctx._device_paths[i] = BONGOCAT_NULLPTR; + } } release_allocated_array(ctx._device_paths); - if (ctx._udev_mon) + if (ctx._udev_mon != BONGOCAT_NULLPTR) { udev_monitor_unref(ctx._udev_mon); - if (ctx._udev) + ctx._udev_mon = BONGOCAT_NULLPTR; + } + if (ctx._udev != BONGOCAT_NULLPTR) { udev_unref(ctx._udev); - ctx._udev_mon = nullptr; - ctx._udev = nullptr; + ctx._udev = BONGOCAT_NULLPTR; + } ctx._udev_fd = -1; close_fd(ctx.update_config_efd); @@ -152,9 +156,9 @@ inline void cleanup(input_context_t& ctx) { release_allocated_mmap_memory(ctx._local_copy_config); release_allocated_mmap_memory(ctx.shm); - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; + ctx._config = BONGOCAT_NULLPTR; + ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + ctx._config_generation = BONGOCAT_NULLPTR; atomic_store(&ctx.ready, false); ctx.init_cond.notify_all(); } diff --git a/include/platform/update_context.h b/include/platform/update_context.h index aab3007f..533efb43 100644 --- a/include/platform/update_context.h +++ b/include/platform/update_context.h @@ -35,9 +35,9 @@ struct update_context_t { platform::CondVariable config_updated; // globals (references) - const config::config_t *_config{nullptr}; - platform::CondVariable *_configs_reloaded_cond{nullptr}; - atomic_uint64_t *_config_generation{nullptr}; + const config::config_t *_config{BONGOCAT_NULLPTR}; + platform::CondVariable *_configs_reloaded_cond{BONGOCAT_NULLPTR}; + atomic_uint64_t *_config_generation{BONGOCAT_NULLPTR}; atomic_bool ready; platform::CondVariable init_cond; @@ -68,9 +68,9 @@ inline void cleanup(update_context_t& ctx) { release_allocated_mmap_memory(ctx._local_copy_config); release_allocated_mmap_memory(ctx.shm); - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; + ctx._config = BONGOCAT_NULLPTR; + ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + ctx._config_generation = BONGOCAT_NULLPTR; atomic_store(&ctx.ready, false); ctx.init_cond.notify_all(); } diff --git a/include/platform/update_shared_memory.h b/include/platform/update_shared_memory.h index b67e7d21..9087f32d 100644 --- a/include/platform/update_shared_memory.h +++ b/include/platform/update_shared_memory.h @@ -31,7 +31,7 @@ struct cpu_snapshot_ring_buffer_t { struct update_shared_memory_t { cpu_snapshot_ring_buffer_t cpu_snapshots; - const cpu_snapshot_t *latest_snapshot{nullptr}; + const cpu_snapshot_t *latest_snapshot{BONGOCAT_NULLPTR}; double avg_cpu_usage{0}; double max_cpu_usage{0}; double last_avg_cpu_usage{0}; diff --git a/include/platform/wayland_context.h b/include/platform/wayland_context.h index 8faa3d97..470d9f84 100644 --- a/include/platform/wayland_context.h +++ b/include/platform/wayland_context.h @@ -25,17 +25,17 @@ enum class bar_visibility_t : bool { struct screen_info_t; struct wayland_context_t { - wl_display *display{nullptr}; - wl_compositor *compositor{nullptr}; - wl_shm *shm{nullptr}; - zwlr_layer_shell_v1 *layer_shell{nullptr}; - struct xdg_wm_base *xdg_wm_base{nullptr}; - wl_output *output{nullptr}; - wl_surface *surface{nullptr}; - zwlr_layer_surface_v1 *layer_surface{nullptr}; + wl_display *display{BONGOCAT_NULLPTR}; + wl_compositor *compositor{BONGOCAT_NULLPTR}; + wl_shm *shm{BONGOCAT_NULLPTR}; + zwlr_layer_shell_v1 *layer_shell{BONGOCAT_NULLPTR}; + struct xdg_wm_base *xdg_wm_base{BONGOCAT_NULLPTR}; + wl_output *output{BONGOCAT_NULLPTR}; + wl_surface *surface{BONGOCAT_NULLPTR}; + zwlr_layer_surface_v1 *layer_surface{BONGOCAT_NULLPTR}; // Output reconnection handling - struct wl_registry *registry{nullptr}; + struct wl_registry *registry{BONGOCAT_NULLPTR}; uint32_t bound_output_name{0}; // Registry name of our bound output bool using_named_output{false}; // True if user specified an output name @@ -46,12 +46,13 @@ struct wayland_context_t { int32_t _bar_height{0}; int32_t _screen_width{0}; - char *_output_name_str{nullptr}; // ref to existing name in output, Will default to automatic one if kept null + char *_output_name_str{ + BONGOCAT_NULLPTR}; // ref to existing name in output, Will default to automatic one if kept null bool _fullscreen_detected{false}; - screen_info_t *_screen_info{nullptr}; + screen_info_t *_screen_info{BONGOCAT_NULLPTR}; // frame done callback data - wl_callback *_frame_cb{nullptr}; + wl_callback *_frame_cb{BONGOCAT_NULLPTR}; Mutex _frame_cb_lock; atomic_bool _frame_pending{false}; atomic_bool _redraw_after_frame{false}; @@ -69,35 +70,35 @@ struct wayland_context_t { }; inline void cleanup_wayland_context_protocols(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + if (ctx.ctx_shm) { atomic_store(&ctx.ctx_shm->configured, false); } - if (ctx.registry) { + if (ctx.registry != BONGOCAT_NULLPTR) { wl_registry_destroy(ctx.registry); - ctx.registry = nullptr; + ctx.registry = BONGOCAT_NULLPTR; } } inline void cleanup_wayland_context_surface(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + if (ctx.ctx_shm) { atomic_store(&ctx.ctx_shm->configured, false); } - if (ctx.layer_surface) { + if (ctx.layer_surface != BONGOCAT_NULLPTR) { zwlr_layer_surface_v1_destroy(ctx.layer_surface); - ctx.layer_surface = nullptr; + ctx.layer_surface = BONGOCAT_NULLPTR; } - if (ctx.surface) { + if (ctx.surface != BONGOCAT_NULLPTR) { wl_surface_destroy(ctx.surface); - ctx.surface = nullptr; + ctx.surface = BONGOCAT_NULLPTR; } } inline void cleanup_wayland_context_buffer(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + if (ctx.ctx_shm) { atomic_store(&ctx.ctx_shm->configured, false); } - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + if (ctx.ctx_shm) { for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { cleanup_shm_buffer(ctx.ctx_shm->buffers[i]); } @@ -109,12 +110,12 @@ inline void cleanup_wayland_context_buffer(wayland_context_t& ctx) { ctx._bar_height = 0; } inline void cleanup_wayland_context(wayland_context_t& ctx) { - if (ctx.ctx_shm.ptr && ctx.ctx_shm.ptr != MAP_FAILED) { + if (ctx.ctx_shm) { atomic_store(&ctx.ctx_shm->configured, false); } // drain pending events - if (ctx.display) { + if (ctx.display != BONGOCAT_NULLPTR) { wl_display_flush(ctx.display); wl_display_roundtrip(ctx.display); int attempts = 0; @@ -132,29 +133,30 @@ inline void cleanup_wayland_context(wayland_context_t& ctx) { atomic_store(&ctx._frame_pending, false); atomic_store(&ctx._redraw_after_frame, false); // ctx._frame_cb_lock should be unlocked - if (ctx._frame_cb) + if (ctx._frame_cb != BONGOCAT_NULLPTR) { wl_callback_destroy(ctx._frame_cb); - ctx._frame_cb = nullptr; + ctx._frame_cb = BONGOCAT_NULLPTR; + } ctx._last_frame_timestamp_ms = 0; // surfaces cleanup_wayland_context_surface(ctx); - if (ctx.layer_shell) { + if (ctx.layer_shell != BONGOCAT_NULLPTR) { zwlr_layer_shell_v1_destroy(ctx.layer_shell); - ctx.layer_shell = nullptr; + ctx.layer_shell = BONGOCAT_NULLPTR; } - if (ctx.xdg_wm_base) { + if (ctx.xdg_wm_base != BONGOCAT_NULLPTR) { xdg_wm_base_destroy(ctx.xdg_wm_base); - ctx.xdg_wm_base = nullptr; + ctx.xdg_wm_base = BONGOCAT_NULLPTR; } - if (ctx.shm) { + if (ctx.shm != BONGOCAT_NULLPTR) { wl_shm_destroy(ctx.shm); - ctx.shm = nullptr; + ctx.shm = BONGOCAT_NULLPTR; } - if (ctx.compositor) { + if (ctx.compositor != BONGOCAT_NULLPTR) { wl_compositor_destroy(ctx.compositor); - ctx.compositor = nullptr; + ctx.compositor = BONGOCAT_NULLPTR; } // release shm @@ -162,33 +164,33 @@ inline void cleanup_wayland_context(wayland_context_t& ctx) { release_allocated_mmap_memory(ctx.ctx_shm); release_allocated_mmap_memory(ctx._local_copy_config); - if (ctx.display) { + if (ctx.display != BONGOCAT_NULLPTR) { wl_display_disconnect(ctx.display); - ctx.display = nullptr; + ctx.display = BONGOCAT_NULLPTR; } // Note: output is just a reference to one of the outputs[] entries // It will be destroyed when we destroy the outputs[] array above - ctx.output = nullptr; + ctx.output = BONGOCAT_NULLPTR; ctx.bound_output_name = 0; ctx.using_named_output = false; // Reset state - ctx.display = nullptr; - ctx.compositor = nullptr; - ctx.shm = nullptr; - ctx.layer_shell = nullptr; - ctx.xdg_wm_base = nullptr; - ctx.output = nullptr; - ctx.surface = nullptr; - ctx.layer_surface = nullptr; - ctx._output_name_str = nullptr; + ctx.display = BONGOCAT_NULLPTR; + ctx.compositor = BONGOCAT_NULLPTR; + ctx.shm = BONGOCAT_NULLPTR; + ctx.layer_shell = BONGOCAT_NULLPTR; + ctx.xdg_wm_base = BONGOCAT_NULLPTR; + ctx.output = BONGOCAT_NULLPTR; + ctx.surface = BONGOCAT_NULLPTR; + ctx.layer_surface = BONGOCAT_NULLPTR; + ctx._output_name_str = BONGOCAT_NULLPTR; ctx._frame_pending = false; ctx._redraw_after_frame = false; ctx._bar_height = 0; ctx._screen_width = 0; ctx._fullscreen_detected = false; - ctx._screen_info = nullptr; + ctx._screen_info = BONGOCAT_NULLPTR; } } // namespace bongocat::platform::wayland diff --git a/include/platform/wayland_shared_memory.h b/include/platform/wayland_shared_memory.h index 94c2d2e3..c062903b 100644 --- a/include/platform/wayland_shared_memory.h +++ b/include/platform/wayland_shared_memory.h @@ -17,15 +17,15 @@ void cleanup_shm_buffer(wayland_shm_buffer_t& buffer); struct wayland_context_t; struct wayland_shm_buffer_t { - wl_buffer *buffer{nullptr}; + wl_buffer *buffer{BONGOCAT_NULLPTR}; MMapFileBuffer pixels; atomic_bool busy{false}; // 0: free / 1: busy atomic_bool pending{false}; // 0/1: a render was requested while busy size_t index{0}; // index track from wayland_shared_memory_t.buffers // extra context for listeners - animation::animation_session_t *_animation_trigger_context{nullptr}; - wayland_context_t *_wayland_context{nullptr}; // parent ref. for buffer_release + animation::animation_session_t *_animation_trigger_context{BONGOCAT_NULLPTR}; + wayland_context_t *_wayland_context{BONGOCAT_NULLPTR}; // parent ref. for buffer_release wayland_shm_buffer_t() = default; ~wayland_shm_buffer_t() { @@ -44,9 +44,9 @@ struct wayland_shm_buffer_t { atomic_store(&busy, atomic_load(&other.busy)); atomic_store(&pending, atomic_load(&other.pending)); - other.buffer = nullptr; + other.buffer = BONGOCAT_NULLPTR; other.index = 0; - other._animation_trigger_context = nullptr; + other._animation_trigger_context = BONGOCAT_NULLPTR; atomic_store(&other.busy, false); atomic_store(&other.pending, false); } @@ -60,10 +60,10 @@ struct wayland_shm_buffer_t { _animation_trigger_context = other._animation_trigger_context; _wayland_context = other._wayland_context; - other.buffer = nullptr; + other.buffer = BONGOCAT_NULLPTR; other.index = 0; - other._animation_trigger_context = nullptr; - other._wayland_context = nullptr; + other._animation_trigger_context = BONGOCAT_NULLPTR; + other._wayland_context = BONGOCAT_NULLPTR; atomic_store(&other.busy, false); atomic_store(&other.pending, false); } @@ -120,14 +120,15 @@ struct wayland_shared_memory_t { inline void cleanup_shm_buffer(wayland_shm_buffer_t& buffer) { atomic_store(&buffer.pending, false); atomic_store(&buffer.busy, true); - if (buffer.buffer) + if (buffer.buffer != BONGOCAT_NULLPTR) { wl_buffer_destroy(buffer.buffer); - buffer.buffer = nullptr; + buffer.buffer = BONGOCAT_NULLPTR; + } release_allocated_mmap_file_buffer(buffer.pixels); atomic_store(&buffer.busy, false); buffer.index = 0; - buffer._animation_trigger_context = nullptr; - buffer._wayland_context = nullptr; + buffer._animation_trigger_context = BONGOCAT_NULLPTR; + buffer._wayland_context = BONGOCAT_NULLPTR; } } // namespace bongocat::platform::wayland diff --git a/include/utils/error.h b/include/utils/error.h index c6367567..b65ce86b 100644 --- a/include/utils/error.h +++ b/include/utils/error.h @@ -156,7 +156,7 @@ namespace features { #endif inline int check_errno([[maybe_unused]] const char *fd_name) { - int err = errno; + const int err = errno; // supress compiler warning #if EAGAIN == EWOULDBLOCK if (err != EAGAIN && err != -1) { diff --git a/include/utils/memory.h b/include/utils/memory.h index e04e43e9..e17bb3f4 100644 --- a/include/utils/memory.h +++ b/include/utils/memory.h @@ -19,7 +19,7 @@ namespace bongocat { // Memory pool for efficient allocation struct memory_pool_t { - void *data{nullptr}; + void *data{BONGOCAT_NULLPTR}; size_t size{0}; size_t used{0}; size_t alignment{0}; @@ -105,7 +105,8 @@ inline void auto_free_impl(void *ptr) { } \ } while (0) -template constexpr std::size_t LEN_ARRAY(const T (&)[N]) noexcept { +template +constexpr std::size_t LEN_ARRAY(const T (&)[N]) noexcept { return N; } @@ -122,7 +123,8 @@ inline void bongocat_auto_pool_impl(struct memory_pool **pool) { // Auto-destroy memory pool when variable goes out of scope // #define BONGOCAT_AUTO_POOL __attribute__((cleanup(bongocat::auto_pool_impl))) -template struct is_trivially_copyable { +template +struct is_trivially_copyable { #if defined(__clang__) inline static constexpr bool value = __is_trivially_copyable(T); #elif defined(__GNUC__) || defined(__GNUG__) @@ -134,7 +136,8 @@ template struct is_trivially_copyable { #endif }; -template struct is_trivially_destructible { +template +struct is_trivially_destructible { #if defined(__clang__) inline static constexpr bool value = __is_trivially_destructible(T); #elif defined(__GNUC__) || defined(__GNUG__) @@ -154,11 +157,15 @@ template struct is_trivially_destructible { // RAII CLEANUP // ============================================================================= -template struct AllocatedMemory; -template void release_allocated_memory(AllocatedMemory& memory) noexcept; +template +class AllocatedMemory; +template +void release_allocated_memory(AllocatedMemory& memory) noexcept; -template struct AllocatedMemory { - T *ptr{nullptr}; +template +class AllocatedMemory { +public: + T *ptr{BONGOCAT_NULLPTR}; size_t _size_bytes{0}; constexpr AllocatedMemory() = default; @@ -166,17 +173,17 @@ template struct AllocatedMemory { release_allocated_memory(*this); } - explicit AllocatedMemory(decltype(nullptr)) noexcept {} - AllocatedMemory& operator=(decltype(nullptr)) noexcept { + explicit AllocatedMemory(decltype(BONGOCAT_NULLPTR)) noexcept {} + AllocatedMemory& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_memory(*this); return *this; } AllocatedMemory(const AllocatedMemory& other) : _size_bytes(other._size_bytes) { _size_bytes = sizeof(T); - if (other.ptr != nullptr && _size_bytes > 0) { + if (other.ptr != BONGOCAT_NULLPTR && _size_bytes > 0) { ptr = static_cast(BONGOCAT_MALLOC(_size_bytes)); - if (ptr != nullptr) { + if (ptr != BONGOCAT_NULLPTR) { if constexpr (is_trivially_copyable::value) { memcpy(ptr, other.ptr, _size_bytes); } else { @@ -184,7 +191,7 @@ template struct AllocatedMemory { } } else { _size_bytes = 0; - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; BONGOCAT_LOG_ERROR("memory allocation failed"); } } else { @@ -195,7 +202,7 @@ template struct AllocatedMemory { if (this != &other) { release_allocated_memory(*this); _size_bytes = sizeof(T); - if (other.ptr != nullptr && _size_bytes > 0) { + if (other.ptr != BONGOCAT_NULLPTR && _size_bytes > 0) { ptr = static_cast(BONGOCAT_MALLOC(_size_bytes)); if (ptr) { if constexpr (is_trivially_copyable::value) { @@ -205,7 +212,7 @@ template struct AllocatedMemory { } } else { _size_bytes = 0; - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; BONGOCAT_LOG_ERROR("memory allocation failed"); } } else { @@ -216,7 +223,7 @@ template struct AllocatedMemory { } AllocatedMemory(AllocatedMemory&& other) noexcept : ptr(other.ptr), _size_bytes(other._size_bytes) { - other.ptr = nullptr; + other.ptr = BONGOCAT_NULLPTR; other._size_bytes = 0; } AllocatedMemory& operator=(AllocatedMemory&& other) noexcept { @@ -224,14 +231,14 @@ template struct AllocatedMemory { release_allocated_memory(*this); ptr = other.ptr; _size_bytes = other._size_bytes; - other.ptr = nullptr; + other.ptr = BONGOCAT_NULLPTR; other._size_bytes = 0; } return *this; } constexpr operator bool() const noexcept { - return ptr != nullptr; + return ptr != BONGOCAT_NULLPTR; } T& operator*() { @@ -257,32 +264,35 @@ template struct AllocatedMemory { return ptr; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return ptr == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return ptr == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return ptr != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return ptr != BONGOCAT_NULLPTR; } }; -template void release_allocated_memory(AllocatedMemory& memory) noexcept { - if (memory.ptr != nullptr) { +template +void release_allocated_memory(AllocatedMemory& memory) noexcept { + if (memory.ptr != BONGOCAT_NULLPTR) { if constexpr (!is_trivially_destructible::value) { memory.ptr->~T(); } BONGOCAT_SAFE_FREE(memory.ptr); - memory.ptr = nullptr; + memory.ptr = BONGOCAT_NULLPTR; memory._size_bytes = 0; } } -template BONGOCAT_NODISCARD inline static AllocatedMemory make_null_memory() noexcept { +template +BONGOCAT_NODISCARD inline static AllocatedMemory make_null_memory() noexcept { return AllocatedMemory(); } -template BONGOCAT_NODISCARD inline static AllocatedMemory make_allocated_memory() { +template +BONGOCAT_NODISCARD inline static AllocatedMemory make_allocated_memory() { AllocatedMemory ret; ret._size_bytes = sizeof(T); if (ret._size_bytes > 0) { ret.ptr = static_cast(BONGOCAT_MALLOC(ret._size_bytes)); - if (ret.ptr != nullptr) { + if (ret.ptr != BONGOCAT_NULLPTR) { // default ctor new (ret.ptr) T(); return ret; @@ -291,15 +301,19 @@ template BONGOCAT_NODISCARD inline static AllocatedMemory make_a } } ret._size_bytes = 0; - ret.ptr = nullptr; + ret.ptr = BONGOCAT_NULLPTR; return ret; } -template struct AllocatedArray; -template void release_allocated_array(AllocatedArray& memory) noexcept; +template +class AllocatedArray; +template +void release_allocated_array(AllocatedArray& memory) noexcept; -template struct AllocatedArray { - T *data{nullptr}; +template +class AllocatedArray { +public: + T *data{BONGOCAT_NULLPTR}; size_t count{0}; size_t _size_bytes{0}; @@ -308,8 +322,8 @@ template struct AllocatedArray { release_allocated_array(*this); } - explicit AllocatedArray(decltype(nullptr)) noexcept {} - AllocatedArray& operator=(decltype(nullptr)) noexcept { + explicit AllocatedArray(decltype(BONGOCAT_NULLPTR)) noexcept {} + AllocatedArray& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_array(*this); return *this; } @@ -346,7 +360,7 @@ template struct AllocatedArray { count = 0; _size_bytes = 0; - data = nullptr; + data = BONGOCAT_NULLPTR; } AllocatedArray& operator=(const AllocatedArray& other) { if (this != &other) { @@ -371,7 +385,7 @@ template struct AllocatedArray { count = 0; _size_bytes = 0; - data = nullptr; + data = BONGOCAT_NULLPTR; } return *this; } @@ -380,7 +394,7 @@ template struct AllocatedArray { : data(other.data) , count(other.count) , _size_bytes(other._size_bytes) { - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other.count = 0; other._size_bytes = 0; } @@ -390,7 +404,7 @@ template struct AllocatedArray { data = other.data; count = other.count; _size_bytes = other._size_bytes; - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other.count = 0; other._size_bytes = 0; } @@ -407,38 +421,41 @@ template struct AllocatedArray { } constexpr explicit operator bool() const noexcept { - return data != nullptr; + return data != BONGOCAT_NULLPTR; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data != BONGOCAT_NULLPTR; } }; -template void release_allocated_array(AllocatedArray& memory) noexcept { - if (memory.data != nullptr) { +template +void release_allocated_array(AllocatedArray& memory) noexcept { + if (memory.data != BONGOCAT_NULLPTR) { if constexpr (!is_trivially_destructible::value) { for (size_t i = 0; i < memory.count; i++) { memory.data[i].~T(); } } BONGOCAT_SAFE_FREE(memory.data); - memory.data = nullptr; + memory.data = BONGOCAT_NULLPTR; memory.count = 0; memory._size_bytes = 0; } } -template BONGOCAT_NODISCARD inline static AllocatedArray make_unallocated_array() noexcept { +template +BONGOCAT_NODISCARD inline static AllocatedArray make_unallocated_array() noexcept { return AllocatedArray(); } template BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array_uninitialized(size_t count) { return count > 0 ? AllocatedArray(count) : AllocatedArray(); } -template BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array(size_t count) { +template +BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array(size_t count) { auto ret = count > 0 ? AllocatedArray(count) : AllocatedArray(); for (size_t i = 0; i < ret.count; i++) { new (&ret.data[i]) T(); @@ -455,18 +472,22 @@ BONGOCAT_NODISCARD inline static AllocatedArray make_allocated_array_with_val } // remove_reference implementation (no STL) -template struct remove_reference { +template +struct remove_reference { typedef T type; }; -template struct remove_reference { +template +struct remove_reference { typedef T type; }; -template struct remove_reference { +template +struct remove_reference { typedef T type; }; // move implementation (no STL) -template inline typename remove_reference::type&& move(T&& t) { +template +inline typename remove_reference::type&& move(T&& t) { typedef typename remove_reference::type U; return static_cast(t); } diff --git a/include/utils/system_memory.h b/include/utils/system_memory.h index 9dbad3d2..67cfa5c7 100644 --- a/include/utils/system_memory.h +++ b/include/utils/system_memory.h @@ -19,7 +19,8 @@ namespace bongocat::platform { int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms); int stop_thread_graceful_or_cancel(pthread_t& thread, atomic_bool& running_flag); -struct Mutex { +class Mutex { +public: pthread_mutex_t pt_mutex{}; Mutex() { @@ -69,7 +70,9 @@ struct Mutex { } */ }; -struct LockGuard { + +class LockGuard { +public: explicit LockGuard(Mutex& m) : pt_mutex(&m.pt_mutex) { if (const int rc = pthread_mutex_lock(pt_mutex); rc != 0) { BONGOCAT_LOG_ERROR("LockGuard: pthread_mutex_lock failed"); @@ -92,19 +95,20 @@ struct LockGuard { LockGuard(const LockGuard&&) = delete; LockGuard&& operator=(const LockGuard&&) = delete; - pthread_mutex_t *pt_mutex{nullptr}; + pthread_mutex_t *pt_mutex{BONGOCAT_NULLPTR}; }; -struct SingleCondVariable; +class SingleCondVariable; void cond_destroy(SingleCondVariable& cond); -struct SingleCondVariable { +class SingleCondVariable { +public: Mutex mutex; pthread_cond_t cond; atomic_bool _predicate{false}; bool _inited{false}; SingleCondVariable() { - pthread_cond_init(&cond, nullptr); + pthread_cond_init(&cond, BONGOCAT_NULLPTR); _inited = true; } @@ -127,10 +131,11 @@ inline void cond_destroy(SingleCondVariable& cond) { cond._inited = false; } -struct CondVariable { +class CondVariable { +public: CondVariable() { - pthread_mutex_init(&_mutex, nullptr); - pthread_cond_init(&_cond, nullptr); + pthread_mutex_init(&_mutex, BONGOCAT_NULLPTR); + pthread_cond_init(&_cond, BONGOCAT_NULLPTR); } // No copying, no move @@ -145,7 +150,8 @@ struct CondVariable { pthread_mutex_destroy(&_mutex); } - template [[deprecated("better use timedwait")]] int wait(Predicate&& pred) { + template + [[deprecated("better use timedwait")]] int wait(Predicate&& pred) { int ret = 0; pthread_mutex_lock(&_mutex); while (!pred()) { @@ -155,7 +161,8 @@ struct CondVariable { return ret; } - template int timedwait(Predicate&& pred, time_ms_t timeout_ms) { + template + int timedwait(Predicate&& pred, time_ms_t timeout_ms) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += timeout_ms / 1000; @@ -189,7 +196,8 @@ struct CondVariable { pthread_cond_t _cond; }; -struct CondVarGuard { +class CondVarGuard { +public: explicit CondVarGuard(pthread_mutex_t& m, pthread_cond_t& c, atomic_bool& pred) : _mutex(m) , _cond(c) @@ -251,11 +259,15 @@ struct CondVarGuard { atomic_bool& _predicate; }; -template struct MMapMemory; -template void release_allocated_mmap_memory(MMapMemory& memory) noexcept; +template +class MMapMemory; +template +void release_allocated_mmap_memory(MMapMemory& memory) noexcept; -template struct MMapMemory { - T *ptr{nullptr}; +template +class MMapMemory { +public: + T *ptr{BONGOCAT_NULLPTR}; size_t _size_bytes{0}; constexpr MMapMemory() = default; @@ -263,15 +275,16 @@ template struct MMapMemory { release_allocated_mmap_memory(*this); } - explicit MMapMemory(decltype(nullptr)) noexcept {} - MMapMemory& operator=(decltype(nullptr)) noexcept { + explicit MMapMemory(decltype(BONGOCAT_NULLPTR)) noexcept {} + MMapMemory& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_mmap_memory(*this); return *this; } MMapMemory(const MMapMemory& other) : _size_bytes(other._size_bytes) { if (other.ptr && _size_bytes > 0) { - ptr = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + ptr = static_cast( + mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); if (ptr != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(ptr, other.ptr, _size_bytes); @@ -285,14 +298,15 @@ template struct MMapMemory { } } _size_bytes = 0; - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; } MMapMemory& operator=(const MMapMemory& other) { if (this != &other) { release_allocated_mmap_memory(*this); _size_bytes = other._size_bytes; if (_size_bytes > 0) { - ptr = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + ptr = static_cast( + mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); if (ptr != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(ptr, other.ptr, _size_bytes); @@ -306,13 +320,13 @@ template struct MMapMemory { } } _size_bytes = 0; - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; } return *this; } MMapMemory(MMapMemory&& other) noexcept : ptr(other.ptr), _size_bytes(other._size_bytes) { - other.ptr = nullptr; + other.ptr = BONGOCAT_NULLPTR; other._size_bytes = 0; } MMapMemory& operator=(MMapMemory&& other) noexcept { @@ -320,7 +334,7 @@ template struct MMapMemory { release_allocated_mmap_memory(*this); ptr = other.ptr; _size_bytes = other._size_bytes; - other.ptr = nullptr; + other.ptr = BONGOCAT_NULLPTR; other._size_bytes = 0; } return *this; @@ -350,36 +364,39 @@ template struct MMapMemory { } constexpr explicit operator bool() const noexcept { - return ptr != nullptr && ptr != MAP_FAILED; + return ptr != BONGOCAT_NULLPTR && ptr != MAP_FAILED; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return ptr == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return ptr == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return ptr != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return ptr != BONGOCAT_NULLPTR; } }; -template void release_allocated_mmap_memory(MMapMemory& memory) noexcept { - if (memory.ptr != nullptr) { +template +void release_allocated_mmap_memory(MMapMemory& memory) noexcept { + if (memory.ptr != BONGOCAT_NULLPTR) { if constexpr (!is_trivially_destructible::value) { memory.ptr->~T(); } munmap(memory.ptr, memory._size_bytes); - memory.ptr = nullptr; + memory.ptr = BONGOCAT_NULLPTR; memory._size_bytes = 0; } } -template BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap() noexcept { +template +BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap() noexcept { return MMapMemory(); } // Allocate shared memory using mmap -template BONGOCAT_NODISCARD inline static MMapMemory make_allocated_mmap() { +template +BONGOCAT_NODISCARD inline static MMapMemory make_allocated_mmap() { MMapMemory ret; ret._size_bytes = sizeof(T); if (ret._size_bytes > 0) { - ret.ptr = - static_cast(mmap(nullptr, ret._size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + ret.ptr = static_cast( + mmap(BONGOCAT_NULLPTR, ret._size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); if (ret.ptr && ret.ptr != MAP_FAILED) { // default ctor new (ret.ptr) T(); @@ -388,23 +405,28 @@ template BONGOCAT_NODISCARD inline static MMapMemory make_alloca BONGOCAT_LOG_ERROR("mmap failed"); } } - ret.ptr = nullptr; + ret.ptr = BONGOCAT_NULLPTR; ret._size_bytes = 0; return ret; } -template BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap_value(const T& value) { +template +BONGOCAT_NODISCARD inline static MMapMemory make_unallocated_mmap_value(const T& value) { auto ret = make_allocated_mmap(); - if (ret.ptr != nullptr) { + if (ret.ptr != BONGOCAT_NULLPTR) { *ret.ptr = value; } return ret; } -template struct MMapArray; -template void release_allocated_mmap_array(MMapArray& memory) noexcept; +template +class MMapArray; +template +void release_allocated_mmap_array(MMapArray& memory) noexcept; -template struct MMapArray { - T *data{nullptr}; +template +class MMapArray { +public: + T *data{BONGOCAT_NULLPTR}; size_t count{0}; size_t _size_bytes{0}; @@ -413,8 +435,8 @@ template struct MMapArray { release_allocated_mmap_array(*this); } - explicit MMapArray(decltype(nullptr)) noexcept {} - MMapArray& operator=(decltype(nullptr)) noexcept { + explicit MMapArray(decltype(BONGOCAT_NULLPTR)) noexcept {} + MMapArray& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_mmap_array(*this); return *this; } @@ -422,21 +444,23 @@ template struct MMapArray { // Allocate shared memory using mmap and count explicit MMapArray(size_t p_count) : count(p_count), _size_bytes(sizeof(T) * count) { if (_size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + data = static_cast( + mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); if (data != MAP_FAILED) { return; } else { BONGOCAT_LOG_ERROR("mmap buffer failed"); } } - data = nullptr; + data = BONGOCAT_NULLPTR; count = 0; _size_bytes = 0; } MMapArray(const MMapArray& other) : count(other.count), _size_bytes(other._size_bytes) { if (other.data && _size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + data = static_cast( + mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); if (data != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(data, other.data, _size_bytes); @@ -450,7 +474,7 @@ template struct MMapArray { BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); } } - data = nullptr; + data = BONGOCAT_NULLPTR; count = 0; _size_bytes = 0; } @@ -460,7 +484,8 @@ template struct MMapArray { count = other.count; _size_bytes = other._size_bytes; if (other.data && _size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); + data = static_cast( + mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); if (data != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(data, other.data, _size_bytes); @@ -474,7 +499,7 @@ template struct MMapArray { BONGOCAT_LOG_ERROR("mmap buffer failed in copy assignment"); } } - data = nullptr; + data = BONGOCAT_NULLPTR; count = 0; _size_bytes = 0; } @@ -482,7 +507,7 @@ template struct MMapArray { } MMapArray(MMapArray&& other) noexcept : data(other.data), count(other.count), _size_bytes(other._size_bytes) { - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other.count = 0; other._size_bytes = 0; } @@ -492,7 +517,7 @@ template struct MMapArray { data = other.data; count = other.count; _size_bytes = other._size_bytes; - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other.count = 0; other._size_bytes = 0; } @@ -509,17 +534,18 @@ template struct MMapArray { } constexpr explicit operator bool() const noexcept { - return data != nullptr && data != MAP_FAILED; + return data != BONGOCAT_NULLPTR && data != MAP_FAILED; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data != BONGOCAT_NULLPTR; } }; -template void release_allocated_mmap_array(MMapArray& memory) noexcept { +template +void release_allocated_mmap_array(MMapArray& memory) noexcept { if (memory.data) { if constexpr (!is_trivially_destructible::value) { for (size_t i = 0; i < memory.count; i++) { @@ -527,19 +553,21 @@ template void release_allocated_mmap_array(MMapArray& memory) no } } munmap(memory.data, memory._size_bytes); - memory.data = nullptr; + memory.data = BONGOCAT_NULLPTR; memory.count = 0; memory._size_bytes = 0; } } -template BONGOCAT_NODISCARD inline static MMapArray make_unallocated_mmap_array() noexcept { +template +BONGOCAT_NODISCARD inline static MMapArray make_unallocated_mmap_array() noexcept { return MMapArray(); } template BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array_uninitialized(size_t count) { return count > 0 ? MMapArray(count) : MMapArray(); } -template BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array(size_t count) { +template +BONGOCAT_NODISCARD inline static MMapArray make_allocated_mmap_array(size_t count) { auto ret = count > 0 ? MMapArray(count) : MMapArray(); for (size_t i = 0; i < ret.count; i++) { new (&ret.data[i]) T(); @@ -547,11 +575,15 @@ template BONGOCAT_NODISCARD inline static MMapArray make_allocat return ret; } -template struct MMapFile; -template void release_allocated_mmap_file(MMapFile& memory) noexcept; +template +class MMapFile; +template +void release_allocated_mmap_file(MMapFile& memory) noexcept; -template struct MMapFile { - T *ptr{nullptr}; +template +class MMapFile { +public: + T *ptr{BONGOCAT_NULLPTR}; size_t _size_bytes{0}; int _fd{-1}; off_t _offset{0}; @@ -561,28 +593,28 @@ template struct MMapFile { release_allocated_mmap_file(*this); } - explicit MMapFile(decltype(nullptr)) noexcept {} - MMapFile& operator=(decltype(nullptr)) noexcept { + explicit MMapFile(decltype(BONGOCAT_NULLPTR)) noexcept {} + MMapFile& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_mmap_file(*this); return *this; } explicit MMapFile(int fd, off_t offset = 0) : _size_bytes(sizeof(T)), _fd(fd), _offset(offset) { if (_size_bytes > 0) { - ptr = mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); + ptr = mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); if (ptr != MAP_FAILED) { return; } else { BONGOCAT_LOG_ERROR("mmap failed to map file"); } } - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; _size_bytes = 0; } MMapFile(const MMapFile& other) : _size_bytes(other._size_bytes), _fd(other._fd), _offset(other._offset) { if (other.ptr && _size_bytes > 0) { - ptr = mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); + ptr = mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); if (ptr != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(ptr, other.ptr, _size_bytes); @@ -594,7 +626,7 @@ template struct MMapFile { BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); } } - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; _size_bytes = 0; } MMapFile& operator=(const MMapFile& other) { @@ -604,7 +636,7 @@ template struct MMapFile { _fd = other._fd; _offset = other._offset; if (other.ptr && _size_bytes > 0) { - ptr = mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); + ptr = mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset); if (ptr) { if constexpr (is_trivially_copyable::value) { memcpy(ptr, other.ptr, _size_bytes); @@ -616,7 +648,7 @@ template struct MMapFile { BONGOCAT_LOG_ERROR("file mmap failed in copy assignment"); } } - ptr = nullptr; + ptr = BONGOCAT_NULLPTR; _size_bytes = 0; } return *this; @@ -627,7 +659,7 @@ template struct MMapFile { , _size_bytes(other._size_bytes) , _fd(other._fd) , _offset(other._offset) { - other.ptr = nullptr; + other.ptr = BONGOCAT_NULLPTR; other._size_bytes = 0; other._fd = -1; other._offset = 0; @@ -639,7 +671,7 @@ template struct MMapFile { _size_bytes = other._size_bytes; _fd = other._fd; _offset = other._offset; - other.ptr = nullptr; + other.ptr = BONGOCAT_NULLPTR; other._size_bytes = 0; other._fd = -1; other._offset = 0; @@ -657,29 +689,31 @@ template struct MMapFile { } constexpr explicit operator bool() const noexcept { - return ptr != nullptr && ptr != MAP_FAILED; + return ptr != BONGOCAT_NULLPTR && ptr != MAP_FAILED; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return ptr == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return ptr == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return ptr != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return ptr != BONGOCAT_NULLPTR; } }; -template void release_allocated_mmap_file(MMapFile& memory) noexcept { +template +void release_allocated_mmap_file(MMapFile& memory) noexcept { if (memory.ptr) { if constexpr (!is_trivially_destructible::value) { memory.ptr->~T(); } munmap(memory.ptr, memory._size_bytes); - memory.ptr = nullptr; + memory.ptr = BONGOCAT_NULLPTR; memory._size_bytes = 0; memory._fd = -1; memory._offset = 0; } } -template BONGOCAT_NODISCARD inline static MMapFile make_unallocated_mmap_file() noexcept { +template +BONGOCAT_NODISCARD inline static MMapFile make_unallocated_mmap_file() noexcept { return MMapFile(); } template @@ -703,11 +737,15 @@ BONGOCAT_NODISCARD inline static MMapFile make_allocated_mmap_file_value(cons return ret; } -template struct MMapFileBuffer; -template void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept; +template +class MMapFileBuffer; +template +void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept; -template struct MMapFileBuffer { - T *data{nullptr}; +template +class MMapFileBuffer { +public: + T *data{BONGOCAT_NULLPTR}; size_t count{0}; size_t _size_bytes{0}; int _fd{-1}; @@ -718,8 +756,8 @@ template struct MMapFileBuffer { release_allocated_mmap_file_buffer(*this); } - explicit MMapFileBuffer(decltype(nullptr)) noexcept {} - MMapFileBuffer& operator=(decltype(nullptr)) noexcept { + explicit MMapFileBuffer(decltype(BONGOCAT_NULLPTR)) noexcept {} + MMapFileBuffer& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_mmap_file_buffer(*this); return *this; } @@ -731,14 +769,14 @@ template struct MMapFileBuffer { , _fd(fd) , _offset(offset) { if (_size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); + data = static_cast(mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); if (data != MAP_FAILED) { return; } else { BONGOCAT_LOG_ERROR("mmap buffer failed to map file"); } } - data = nullptr; + data = BONGOCAT_NULLPTR; count = 0; _size_bytes = 0; } @@ -749,7 +787,7 @@ template struct MMapFileBuffer { , _fd(other._fd) , _offset(other._offset) { if (other.data && _size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); + data = static_cast(mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); if (data != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(data, other.data, _size_bytes); @@ -763,7 +801,7 @@ template struct MMapFileBuffer { BONGOCAT_LOG_ERROR("file mmap failed in copy constructor"); } } - data = nullptr; + data = BONGOCAT_NULLPTR; count = 0; _size_bytes = 0; } @@ -775,7 +813,7 @@ template struct MMapFileBuffer { _fd = other._fd; _offset = other._offset; if (_size_bytes > 0) { - data = static_cast(mmap(nullptr, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); + data = static_cast(mmap(BONGOCAT_NULLPTR, _size_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, _offset)); if (data != MAP_FAILED) { if constexpr (is_trivially_copyable::value) { memcpy(data, other.data, _size_bytes); @@ -789,7 +827,7 @@ template struct MMapFileBuffer { BONGOCAT_LOG_ERROR("file mmap buffer failed in copy assignment"); } } - data = nullptr; + data = BONGOCAT_NULLPTR; count = 0; _size_bytes = 0; } @@ -802,7 +840,7 @@ template struct MMapFileBuffer { , _size_bytes(other._size_bytes) , _fd(other._fd) , _offset(other._offset) { - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other.count = 0; other._size_bytes = 0; other._fd = -1; @@ -816,7 +854,7 @@ template struct MMapFileBuffer { _size_bytes = other._size_bytes; _fd = other._fd; _offset = other._offset; - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other.count = 0; other._size_bytes = 0; other._fd = -1; @@ -835,17 +873,18 @@ template struct MMapFileBuffer { } constexpr explicit operator bool() const noexcept { - return data != nullptr && data != MAP_FAILED; + return data != BONGOCAT_NULLPTR && data != MAP_FAILED; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data != BONGOCAT_NULLPTR; } }; -template void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept { +template +void release_allocated_mmap_file_buffer(MMapFileBuffer& memory) noexcept { if (memory.data) { if constexpr (!is_trivially_destructible::value) { for (size_t i = 0; i < memory.count; i++) { @@ -853,14 +892,15 @@ template void release_allocated_mmap_file_buffer(MMapFileBuffer& } } munmap(memory.data, memory._size_bytes); - memory.data = nullptr; + memory.data = BONGOCAT_NULLPTR; memory.count = 0; memory._size_bytes = 0; memory._fd = -1; memory._offset = 0; } } -template BONGOCAT_NODISCARD inline static MMapFileBuffer make_unallocated_mmap_file_buffer() { +template +BONGOCAT_NODISCARD inline static MMapFileBuffer make_unallocated_mmap_file_buffer() { return MMapFileBuffer(); } template @@ -887,10 +927,11 @@ BONGOCAT_NODISCARD inline static MMapFileBuffer make_allocated_mmap_file_buff return ret; } -struct FileDescriptor; +class FileDescriptor; void close_fd(FileDescriptor& fd) noexcept; -struct FileDescriptor { +class FileDescriptor { +public: int _fd{-1}; constexpr FileDescriptor() = default; @@ -899,8 +940,8 @@ struct FileDescriptor { close_fd(*this); } - explicit FileDescriptor(decltype(nullptr)) noexcept {} - FileDescriptor& operator=(decltype(nullptr)) noexcept { + explicit FileDescriptor(decltype(BONGOCAT_NULLPTR)) noexcept {} + FileDescriptor& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { close_fd(*this); return *this; } @@ -937,16 +978,17 @@ struct FileDescriptor { inline void close_fd(FileDescriptor& fd) noexcept { if (fd._fd >= 0) { ::close(fd._fd); - fd._fd = -1; } + fd._fd = -1; } -struct MMapFileContent; +class MMapFileContent; void release_allocated_mmap_file_content(MMapFileContent& memory) noexcept; -struct MMapFileContent { +class MMapFileContent { +public: /// @NOTE: memory is private (not sharable within threads) - unsigned char *data{nullptr}; + unsigned char *data{BONGOCAT_NULLPTR}; off_t _size_bytes{0}; FileDescriptor _fd{-1}; off_t _offset{0}; @@ -956,8 +998,8 @@ struct MMapFileContent { release_allocated_mmap_file_content(*this); } - explicit MMapFileContent(decltype(nullptr)) noexcept {} - MMapFileContent& operator=(decltype(nullptr)) noexcept { + explicit MMapFileContent(decltype(BONGOCAT_NULLPTR)) noexcept {} + MMapFileContent& operator=(decltype(BONGOCAT_NULLPTR)) noexcept { release_allocated_mmap_file_content(*this); return *this; } @@ -980,15 +1022,15 @@ struct MMapFileContent { } _size_bytes = st.st_size - _offset; - long page_size = sysconf(_SC_PAGE_SIZE); + const long page_size = sysconf(_SC_PAGE_SIZE); assert(page_size > 0); - off_t aligned_offset = (_offset / page_size) * page_size; - off_t delta = _offset - aligned_offset; + const off_t aligned_offset = (_offset / page_size) * page_size; + const off_t delta = _offset - aligned_offset; _size_bytes = st.st_size - _offset; - size_t map_length = static_cast(_size_bytes + delta); + const size_t map_length = static_cast(_size_bytes + delta); if (_size_bytes > 0) { - void *mapped = mmap(nullptr, map_length, PROT_READ, MAP_PRIVATE, _fd._fd, aligned_offset); + void *mapped = mmap(BONGOCAT_NULLPTR, map_length, PROT_READ, MAP_PRIVATE, _fd._fd, aligned_offset); if (mapped != MAP_FAILED) { data = static_cast(mapped) + delta; return; @@ -997,7 +1039,7 @@ struct MMapFileContent { } } - data = nullptr; + data = BONGOCAT_NULLPTR; _size_bytes = 0; close_fd(fd); } @@ -1007,7 +1049,7 @@ struct MMapFileContent { , _size_bytes(other._size_bytes) , _fd(bongocat::move(other._fd)) , _offset(other._offset) { - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other._size_bytes = 0; other._fd._fd = -1; other._offset = 0; @@ -1019,7 +1061,7 @@ struct MMapFileContent { _size_bytes = other._size_bytes; _fd = bongocat::move(other._fd); _offset = other._offset; - other.data = nullptr; + other.data = BONGOCAT_NULLPTR; other._size_bytes = 0; other._fd._fd = -1; other._offset = 0; @@ -1034,22 +1076,22 @@ struct MMapFileContent { } constexpr explicit operator bool() const noexcept { - return data != nullptr && data != MAP_FAILED && _fd._fd >= 0; + return data != BONGOCAT_NULLPTR && data != MAP_FAILED && _fd._fd >= 0; } - constexpr bool operator==(decltype(nullptr)) const noexcept { - return data == nullptr; + constexpr bool operator==(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data == BONGOCAT_NULLPTR; } - constexpr bool operator!=(decltype(nullptr)) const noexcept { - return data != nullptr; + constexpr bool operator!=(decltype(BONGOCAT_NULLPTR)) const noexcept { + return data != BONGOCAT_NULLPTR; } }; inline void release_allocated_mmap_file_content(MMapFileContent& memory) noexcept { - if (memory.data) { + if (memory.data != BONGOCAT_NULLPTR) { assert(memory._size_bytes >= 0); munmap(memory.data, static_cast(memory._size_bytes)); close_fd(memory._fd); - memory.data = nullptr; + memory.data = BONGOCAT_NULLPTR; memory._size_bytes = 0; memory._offset = 0; } @@ -1106,7 +1148,7 @@ struct drain_event_result_t { int err{0}; }; inline drain_event_result_t drain_event(pollfd& pfd, int max_attempts, - [[maybe_unused]] const char *fd_name = nullptr) noexcept { + [[maybe_unused]] const char *fd_name = BONGOCAT_NULLPTR) noexcept { drain_event_result_t ret; if (pfd.revents & POLLIN) { ssize_t rc{0}; diff --git a/src/config/config.cpp b/src/config/config.cpp index 12e1fe67..60beeacd 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -296,15 +296,15 @@ static uint64_t config_validate_custom(config_t& config) { if (config._custom) { if (config.custom_sprite_sheet_settings.feature_toggle_writing_frames >= 0) { config.custom_sprite_sheet_settings.feature_toggle_writing_frames = - config.custom_sprite_sheet_settings.feature_toggle_writing_frames ? 1 : 0; + config.custom_sprite_sheet_settings.feature_toggle_writing_frames >= 1 ? 1 : 0; } if (config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random >= 0) { config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random = - config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random ? 1 : 0; + config.custom_sprite_sheet_settings.feature_toggle_writing_frames_random >= 1 ? 1 : 0; } if (config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 0) { config.custom_sprite_sheet_settings.feature_mirror_x_moving = - config.custom_sprite_sheet_settings.feature_mirror_x_moving ? 1 : 0; + config.custom_sprite_sheet_settings.feature_mirror_x_moving >= 1 ? 1 : 0; } // clamp cols @@ -562,7 +562,7 @@ static uint64_t config_validate_custom(config_t& config) { } // validate sprite sheet file - if (config.custom_sprite_sheet_filename != nullptr && strlen(config.custom_sprite_sheet_filename) != 0) { + if (config.custom_sprite_sheet_filename != BONGOCAT_NULLPTR && strlen(config.custom_sprite_sheet_filename) != 0) { constexpr size_t PNG_SIGNATURE_SIZE = 8; constexpr unsigned char PNG_SIGNATURE[PNG_SIGNATURE_SIZE] = {0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n'}; @@ -843,9 +843,9 @@ static uint64_t config_validate_enums(config_t& config) { static uint64_t config_validate_time(config_t& config) { uint64_t ret{0}; - if (config.enable_scheduled_sleep) { - const int begin_minutes = config.sleep_begin.hour * 60 + config.sleep_begin.min; - const int end_minutes = config.sleep_end.hour * 60 + config.sleep_end.min; + if (config.enable_scheduled_sleep >= 1) { + const int begin_minutes = (config.sleep_begin.hour * 60) + config.sleep_begin.min; + const int end_minutes = (config.sleep_end.hour * 60) + config.sleep_end.min; if (begin_minutes == end_minutes) { BONGOCAT_LOG_WARNING("Sleep mode is enabled, but time is equal: %02d:%02d, disable sleep mode", @@ -865,17 +865,17 @@ static uint64_t config_validate_time(config_t& config) { static bongocat_error_t config_validate(config_t& config) { uint64_t ret{0}; // Normalize boolean values - config.enable_debug = config.enable_debug ? 1 : 0; - config.invert_color = config.invert_color ? 1 : 0; - config.idle_animation = config.idle_animation ? 1 : 0; - config.enable_scheduled_sleep = config.enable_scheduled_sleep ? 1 : 0; - config.mirror_x = config.mirror_x ? 1 : 0; - config.mirror_y = config.mirror_y ? 1 : 0; - config.randomize_index = config.randomize_index ? 1 : 0; - config.randomize_on_reload = config.randomize_on_reload ? 1 : 0; - config.enable_antialiasing = config.enable_antialiasing ? 1 : 0; - config.enable_movement_debug = config.enable_movement_debug ? 1 : 0; - config.enable_hand_mapping = config.enable_hand_mapping ? 1 : 0; + config.enable_debug = config.enable_debug >= 1 ? 1 : 0; + config.invert_color = config.invert_color >= 1 ? 1 : 0; + config.idle_animation = config.idle_animation >= 1 ? 1 : 0; + config.enable_scheduled_sleep = config.enable_scheduled_sleep >= 1 ? 1 : 0; + config.mirror_x = config.mirror_x >= 1 ? 1 : 0; + config.mirror_y = config.mirror_y >= 1 ? 1 : 0; + config.randomize_index = config.randomize_index >= 1 ? 1 : 0; + config.randomize_on_reload = config.randomize_on_reload >= 1 ? 1 : 0; + config.enable_antialiasing = config.enable_antialiasing >= 1 ? 1 : 0; + config.enable_movement_debug = config.enable_movement_debug >= 1 ? 1 : 0; + config.enable_hand_mapping = config.enable_hand_mapping >= 1 ? 1 : 0; ret |= config_validate_dimensions(config); ret |= config_validate_timing(config); @@ -917,12 +917,13 @@ static bongocat_error_t config_add_keyboard_device(config_t& config, const char // Add new device path config.keyboard_devices[old_num_keyboard_devices] = strdup(device_path); - if (!config.keyboard_devices[old_num_keyboard_devices]) { + if (config.keyboard_devices[old_num_keyboard_devices] == BONGOCAT_NULLPTR) [[unlikely]] { // free new copied strings for (int i = 0; i < old_num_keyboard_devices; i++) { - if (config.keyboard_devices[i]) + if (config.keyboard_devices[i] != BONGOCAT_NULLPTR) { ::free(config.keyboard_devices[i]); - config.keyboard_devices[i] = nullptr; + config.keyboard_devices[i] = BONGOCAT_NULLPTR; + } } config.num_keyboard_devices = old_num_keyboard_devices; BONGOCAT_LOG_ERROR("Failed to copy new keyboard device path"); @@ -939,10 +940,11 @@ static void config_cleanup_devices(config_t& config) { assert(config.num_keyboard_devices >= 0); for (size_t i = 0; i < input::MAX_INPUT_DEVICES; i++) { if (i < static_cast(config.num_keyboard_devices)) { - if (config.keyboard_devices[i]) + if (config.keyboard_devices[i] != BONGOCAT_NULLPTR) { ::free(config.keyboard_devices[i]); + config.keyboard_devices[i] = BONGOCAT_NULLPTR; + } } - config.keyboard_devices[i] = nullptr; } config.num_keyboard_devices = 0; } @@ -953,8 +955,9 @@ static void config_cleanup_devices(config_t& config) { static char *config_trim_str(char *key) { char *key_start = key; - while (*key_start == ' ' || *key_start == '\t') + while (*key_start == ' ' || *key_start == '\t') { key_start++; + } char *key_end = key_start + strlen(key_start) - 1; while (key_end > key_start && (*key_end == ' ' || *key_end == '\t')) { @@ -967,7 +970,7 @@ static char *config_trim_str(char *key) { static bongocat_error_t config_parse_integer_key(config_t& config, const char *key, const char *value) { errno = 0; - char *endptr_int = nullptr; + char *endptr_int = BONGOCAT_NULLPTR; const int int_value = static_cast(strtol(value, &endptr_int, 10)); if (errno != 0 || endptr_int == value || *endptr_int != '\0') { return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; @@ -1132,7 +1135,7 @@ static bongocat_error_t config_parse_integer_key(config_t& config, const char *k static bongocat_error_t config_parse_double_key(config_t& config, const char *key, const char *value) { errno = 0; - char *endptr_double = nullptr; + char *endptr_double = BONGOCAT_NULLPTR; const double double_value = strtod(value, &endptr_double); if (errno != 0 || endptr_double == value || *endptr_double != '\0') { return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; @@ -1193,7 +1196,7 @@ static bongocat_error_t config_parse_enum_key(config_t& config, const char *key, } bongocat_error_t config_parse_time(const char *value, int& hour, int& min) { - char *endptr = nullptr; + char *endptr = BONGOCAT_NULLPTR; errno = 0; // Parse hour @@ -1218,35 +1221,35 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const load_config_overwrite_parameters_t& overwrite_parameters) { using namespace assets; if (strcmp(key, MONITOR_KEY) == 0 || strcmp(key, OUTPUT_NAME_KEY) == 0) { - if (config.output_name) { + if (config.output_name != BONGOCAT_NULLPTR) { ::free(config.output_name); - config.output_name = nullptr; + config.output_name = BONGOCAT_NULLPTR; } - if (value && value[0] != '\0') { + if (value != BONGOCAT_NULLPTR && value[0] != '\0') { config.output_name = strdup(value); - if (!config.output_name) { + if (config.output_name == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to allocate memory for interface output"); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } } else { - config.output_name = nullptr; + config.output_name = BONGOCAT_NULLPTR; } } else if (strcmp(key, CUSTOM_SPRITE_SHEET_FILENAME_KEY) == 0) { - if (config.custom_sprite_sheet_filename) { + if (config.custom_sprite_sheet_filename != BONGOCAT_NULLPTR) { ::free(config.custom_sprite_sheet_filename); - config.custom_sprite_sheet_filename = nullptr; + config.custom_sprite_sheet_filename = BONGOCAT_NULLPTR; } - if (value && value[0] != '\0') { + if (value != BONGOCAT_NULLPTR && value[0] != '\0') { config.custom_sprite_sheet_filename = strdup(value); - if (!config.custom_sprite_sheet_filename) { + if (config.custom_sprite_sheet_filename == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to allocate memory for custom sprite sheet filename"); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } } else { - config.custom_sprite_sheet_filename = nullptr; + config.custom_sprite_sheet_filename = BONGOCAT_NULLPTR; } } else if (strcmp(key, SLEEP_BEGIN_KEY) == 0) { - if (value && value[0] != '\0') { + if (value != BONGOCAT_NULLPTR && value[0] != '\0') { int hour{0}; int min{0}; if (config_parse_time(value, hour, min) != bongocat_error_t::BONGOCAT_SUCCESS) { @@ -1263,7 +1266,7 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c config.sleep_begin.min = 0; } } else if (strcmp(key, SLEEP_END_KEY) == 0) { - if (value && value[0] != '\0') { + if (value != BONGOCAT_NULLPTR && value[0] != '\0') { int hour{0}; int min{0}; if (config_parse_time(value, hour, min) != bongocat_error_t::BONGOCAT_SUCCESS) { @@ -1281,19 +1284,21 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c } } else if (strcmp(key, ANIMATION_NAME_KEY) == 0) { using namespace assets; - if (overwrite_parameters.animation_name) { + if (overwrite_parameters.animation_name != BONGOCAT_NULLPTR) { value = overwrite_parameters.animation_name; } // set config._animation_name - if (config._animation_name) + if (config._animation_name != BONGOCAT_NULLPTR) { ::free(config._animation_name); - config._animation_name = nullptr; - config._animation_name = value ? strdup(value) : nullptr; + config._animation_name = BONGOCAT_NULLPTR; + } + config._animation_name = value != BONGOCAT_NULLPTR ? strdup(value) : BONGOCAT_NULLPTR; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } // reset state config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::None; @@ -1302,7 +1307,7 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c config.animation_index = -1; // is fully name like dm:..., dm20:..., dmc:... - [[maybe_unused]] const bool is_fqn = strchr(value, ':') != nullptr; + [[maybe_unused]] const bool is_fqn = strchr(value, ':') != BONGOCAT_NULLPTR; bool animation_found = false; if constexpr (features::EnableBongocatEmbeddedAssets) { @@ -1344,9 +1349,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_dm(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_dm(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1360,9 +1366,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_dm20(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_dm20(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1376,9 +1383,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_dmx(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_dmx(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1392,9 +1400,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_dmc(config, value); if (found_index >= 0) { assert(config.animation_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_dmc(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1408,9 +1417,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_pen(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_pen(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1424,9 +1434,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_pen20(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_pen20(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1440,9 +1451,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_dmall(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_dmall(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1459,9 +1471,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c strcmp(value, CLIPPY_FQNAME) == 0) { config.animation_index = CLIPPY_ANIM_INDEX; config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(CLIPPY_FQNAME); } #ifdef FEATURE_MORE_MS_AGENT_EMBEDDED_ASSETS @@ -1471,9 +1484,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c strcmp(value, LINKS_FQNAME) == 0) { config.animation_index = LINKS_ANIM_INDEX; config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(LINKS_FQNAME); } // Rover @@ -1481,9 +1495,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c strcmp(value, ROVER_FQNAME) == 0) { config.animation_index = ROVER_ANIM_INDEX; config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(ROVER_FQNAME); } // Merlin @@ -1491,9 +1506,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c strcmp(value, MERLIN_FQNAME) == 0) { config.animation_index = MERLIN_ANIM_INDEX; config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::MsAgent; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(MERLIN_FQNAME); } #endif @@ -1509,9 +1525,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_pkmn(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_pkmn(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1528,9 +1545,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c const int found_index = config_parse_animation_name_pmd(config, value); if (found_index >= 0) { assert(found_index >= 0); - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(get_config_animation_name_pmd(static_cast(found_index)).fqname); BONGOCAT_LOG_DEBUG("Animation found for %s: %s", value, config._loaded_animation_fqname); @@ -1548,9 +1566,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c config.animation_index = MISC_NEKO_ANIM_INDEX; config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Custom; config.animation_custom_set = config_animation_custom_set_t::misc; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(MISC_NEKO_FQNAME); animation_found = config.animation_index >= 0; } @@ -1564,15 +1583,18 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c config.animation_index = CUSTOM_ANIM_INDEX; config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Custom; config.animation_custom_set = config_animation_custom_set_t::custom; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; - config._loaded_animation_fqname = - config.custom_sprite_sheet_filename ? strdup(config.custom_sprite_sheet_filename) : nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } + config._loaded_animation_fqname = config.custom_sprite_sheet_filename != BONGOCAT_NULLPTR + ? strdup(config.custom_sprite_sheet_filename) + : BONGOCAT_NULLPTR; animation_found = config.animation_index >= 0; config._custom = config.animation_index == CUSTOM_ANIM_INDEX; - if (config.custom_sprite_sheet_filename == nullptr || strlen(config.custom_sprite_sheet_filename) <= 0) { + if (config.custom_sprite_sheet_filename == BONGOCAT_NULLPTR || + strlen(config.custom_sprite_sheet_filename) <= 0) { BONGOCAT_LOG_WARNING("custom_sprite_sheet_filename required for custom sprite sheet"); animation_found = false; } @@ -1597,9 +1619,10 @@ static bongocat_error_t config_parse_string(config_t& config, const char *key, c config.animation_sprite_sheet_layout = config_animation_sprite_sheet_layout_t::Bongocat; config.animation_dm_set = config_animation_dm_set_t::None; config.animation_custom_set = config_animation_custom_set_t::None; - if (config._loaded_animation_fqname) + if (config._loaded_animation_fqname != BONGOCAT_NULLPTR) { ::free(config._loaded_animation_fqname); - config._loaded_animation_fqname = nullptr; + config._loaded_animation_fqname = BONGOCAT_NULLPTR; + } config._loaded_animation_fqname = strdup(BONGOCAT_FQNAME); } } else { @@ -1672,14 +1695,15 @@ static bongocat_error_t config_parse_file(FILE *file, config_t& config, if (sscanf(line, " %255[^=]=%4351[^\n]", key, value) == 2) { // Cut off trailing comment in value char *comment = strchr(value, '#'); - if (comment) { + if (comment != BONGOCAT_NULLPTR) { *comment = '\0'; // terminate string before '#' } - char *trimmed_key = config_trim_str(key); - char *trimmed_value = config_trim_str(value); + const char *trimmed_key = config_trim_str(key); + const char *trimmed_value = config_trim_str(value); - bongocat_error_t parse_result = config_parse_key_value(config, trimmed_key, trimmed_value, overwrite_parameters); + const bongocat_error_t parse_result = + config_parse_key_value(config, trimmed_key, trimmed_value, overwrite_parameters); if (parse_result == bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM) { BONGOCAT_LOG_WARNING("Unknown configuration key '%s' at line %d", trimmed_key, line_number); } else if (parse_result != bongocat_error_t::BONGOCAT_SUCCESS) { @@ -1696,10 +1720,10 @@ static bongocat_error_t config_parse_file(FILE *file, config_t& config, static bongocat_error_t config_parse_file(config_t& config, const char *config_file_path, load_config_overwrite_parameters_t overwrite_parameters) { - const char *file_path = config_file_path ? config_file_path : DEFAULT_CONFIG_FILE_PATH; + const char *file_path = config_file_path != BONGOCAT_NULLPTR ? config_file_path : DEFAULT_CONFIG_FILE_PATH; FILE *file = fopen(file_path, "r"); - if (!file) { + if (file == BONGOCAT_NULLPTR) { if (overwrite_parameters.strict >= 0) { BONGOCAT_LOG_INFO("Config file '%s' not found", file_path); return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; @@ -1708,9 +1732,9 @@ static bongocat_error_t config_parse_file(config_t& config, const char *config_f return bongocat_error_t::BONGOCAT_SUCCESS; } - bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); - + const bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); fclose(file); + file = BONGOCAT_NULLPTR; if (result == bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_INFO("Loaded configuration from %s", file_path); @@ -1723,7 +1747,7 @@ static bongocat_error_t config_parse_stdin(config_t& config, const load_config_overwrite_parameters_t& overwrite_parameters) { FILE *file = stdin; - bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); + const bongocat_error_t result = config_parse_file(file, config, overwrite_parameters); if (result == bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_INFO("Loaded configuration from stdin"); } @@ -1738,10 +1762,10 @@ static bongocat_error_t config_parse_stdin(config_t& config, void set_defaults(config_t& config) { config_t cfg{}; - cfg.output_name = nullptr; + cfg.output_name = BONGOCAT_NULLPTR; assert(input::MAX_INPUT_DEVICES <= INT_MAX); for (int i = 0; i < static_cast(input::MAX_INPUT_DEVICES); i++) { - cfg.keyboard_devices[i] = nullptr; + cfg.keyboard_devices[i] = BONGOCAT_NULLPTR; } cfg.num_keyboard_devices = 0; cfg.cat_x_offset = DEFAULT_CAT_X_OFFSET; @@ -1780,13 +1804,13 @@ void set_defaults(config_t& config) { cfg.randomize_on_reload = 0; cfg.movement_wait_factor = DEFAULT_MOVEMENT_WAIT_FACTOR; cfg.screen_width = 0; - cfg.custom_sprite_sheet_filename = nullptr; + cfg.custom_sprite_sheet_filename = BONGOCAT_NULLPTR; cfg.custom_sprite_sheet_settings = {}; cfg._keep_old_animation_index = false; cfg._strict = false; cfg._custom = false; - cfg._animation_name = nullptr; - cfg._loaded_animation_fqname = nullptr; + cfg._animation_name = BONGOCAT_NULLPTR; + cfg._loaded_animation_fqname = BONGOCAT_NULLPTR; config = bongocat::move(cfg); } @@ -1807,10 +1831,12 @@ static void config_log_summary(const config_t& config) { case config_animation_sprite_sheet_layout_t::None: break; case config_animation_sprite_sheet_layout_t::Bongocat: - assert(config._loaded_animation_fqname); - BONGOCAT_LOG_DEBUG(" Cat: '%s' %dx%d at offset (%d,%d)", config._loaded_animation_fqname, config.cat_height, - (config.cat_height * BONGOCAT_FRAME_WIDTH) / BONGOCAT_FRAME_HEIGHT, config.cat_x_offset, - config.cat_y_offset); + // when loaded by default, _loaded_animation_fqname is not set + // assert(config._loaded_animation_fqname); + BONGOCAT_LOG_DEBUG(" Cat: '%s' %dx%d at offset (%d,%d)", + config._loaded_animation_fqname != nullptr ? config._loaded_animation_fqname : BONGOCAT_FQNAME, + config.cat_height, (config.cat_height * BONGOCAT_FRAME_WIDTH) / BONGOCAT_FRAME_HEIGHT, + config.cat_x_offset, config.cat_y_offset); break; case config_animation_sprite_sheet_layout_t::Dm: assert(config._loaded_animation_fqname); @@ -1887,13 +1913,15 @@ created_result_t load(const char *config_file_path, load_config_overwr return result; } - if (overwrite_parameters.output_name) { - if (ret.output_name) + if (overwrite_parameters.output_name != BONGOCAT_NULLPTR) { + if (ret.output_name != BONGOCAT_NULLPTR) { ::free(ret.output_name); + ret.output_name = BONGOCAT_NULLPTR; + } ret.output_name = strdup(overwrite_parameters.output_name); } if (overwrite_parameters.randomize_index >= 0) { - ret.randomize_index = overwrite_parameters.randomize_index ? 1 : 0; + ret.randomize_index = overwrite_parameters.randomize_index >= 1 ? 1 : 0; } if (overwrite_parameters.strict >= 0) { ret._strict = overwrite_parameters.strict >= 1; diff --git a/src/config/config_watcher.cpp b/src/config/config_watcher.cpp index 1f991920..bca3a4b3 100644 --- a/src/config/config_watcher.cpp +++ b/src/config/config_watcher.cpp @@ -68,8 +68,9 @@ static void *config_watcher_thread(void *arg) { while (atomic_load(&watcher._running)) { int ret = poll(fds, fds_count, timeout_ms); if (ret < 0) { - if (errno == EINTR) + if (errno == EINTR) { continue; + } BONGOCAT_LOG_ERROR("config_watcher: Config watcher poll failed: %s", strerror(errno)); break; } @@ -205,14 +206,14 @@ static void *config_watcher_thread(void *arg) { atomic_store(&watcher._running, false); BONGOCAT_LOG_INFO("config_watcher: Config watcher stopped"); - return nullptr; + return BONGOCAT_NULLPTR; } created_result_t> create_watcher(const char *config_path) { BONGOCAT_CHECK_NULL(config_path, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) [[unlikely]] { + assert(ret != BONGOCAT_NULLPTR); + if (ret == BONGOCAT_NULLPTR) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } @@ -261,7 +262,7 @@ created_result_t> create_watcher(const char *c } void start_watcher(config_watcher_t& watcher) { - if (pthread_create(&watcher._watcher_thread, nullptr, config_watcher_thread, &watcher) != 0) { + if (pthread_create(&watcher._watcher_thread, BONGOCAT_NULLPTR, config_watcher_thread, &watcher) != 0) { atomic_store(&watcher._running, false); BONGOCAT_LOG_ERROR("Failed to create config watcher thread: %s", strerror(errno)); return; diff --git a/src/core/main.cpp b/src/core/main.cpp index 213cc2aa..e9b96230 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -55,13 +55,13 @@ struct main_context_t { AllocatedMemory animation; AllocatedMemory wayland; - const char *signal_watch_path{nullptr}; + const char *signal_watch_path{BONGOCAT_NULLPTR}; atomic_uint64_t config_generation{0}; - platform::CondVariable configs_reloaded_cond{}; + platform::CondVariable configs_reloaded_cond; platform::Mutex sync_configs; - char *pid_filename{nullptr}; - char *default_config_filename{nullptr}; + char *pid_filename{BONGOCAT_NULLPTR}; + char *default_config_filename{BONGOCAT_NULLPTR}; main_context_t() = default; ~main_context_t() { @@ -75,35 +75,47 @@ struct main_context_t { inline void stop_threads(main_context_t& context) { context.running = 0; // stop threads - if (context.animation != nullptr) + if (context.animation != BONGOCAT_NULLPTR) { atomic_store(&context.animation->anim._animation_running, false); - if (context.input != nullptr) + } + if (context.input != BONGOCAT_NULLPTR) { atomic_store(&context.input->_capture_input_running, false); - if (context.update != nullptr) + } + if (context.update != BONGOCAT_NULLPTR) { atomic_store(&context.update->_running, false); - if (context.config_watcher != nullptr) + } + if (context.config_watcher != BONGOCAT_NULLPTR) { atomic_store(&context.config_watcher->_running, false); + } // wait for threads - if (context.animation != nullptr) + if (context.animation != BONGOCAT_NULLPTR) { platform::join_thread_with_timeout(context.animation->anim._anim_thread, WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS); - if (context.input != nullptr) + } + if (context.input != BONGOCAT_NULLPTR) { platform::join_thread_with_timeout(context.input->_input_thread, WAIT_FOR_SHUTDOWN_INPUT_THREAD_MS); - if (context.update != nullptr) + } + if (context.update != BONGOCAT_NULLPTR) { platform::join_thread_with_timeout(context.update->_update_thread, WAIT_FOR_SHUTDOWN_UPDATE_THREAD_MS); - if (context.config_watcher != nullptr) + } + if (context.config_watcher != BONGOCAT_NULLPTR) { platform::join_thread_with_timeout(context.config_watcher->_watcher_thread, WAIT_FOR_SHUTDOWN_CONFIG_WATCHER_THREAD_MS); + } // stop threads - if (context.animation != nullptr) + if (context.animation != BONGOCAT_NULLPTR) { animation::stop(*context.animation); - if (context.input != nullptr) + } + if (context.input != BONGOCAT_NULLPTR) { platform::input::stop(*context.input); - if (context.update != nullptr) + } + if (context.update != BONGOCAT_NULLPTR) { platform::update::stop(*context.update); - if (context.config_watcher != nullptr) + } + if (context.config_watcher != BONGOCAT_NULLPTR) { config::stop_watcher(*context.config_watcher); + } context.config_generation = 0; } @@ -111,27 +123,35 @@ void cleanup(main_context_t& context) { stop_threads(context); // Cleanup Wayland - if (context.wayland != nullptr) + if (context.wayland != BONGOCAT_NULLPTR) { cleanup_wayland(*context.wayland); + } // remove references (avoid dangling pointers) - if (context.wayland != nullptr) - context.wayland->animation_trigger_context = nullptr; - if (context.animation != nullptr) - context.animation->_input = nullptr; + if (context.wayland != BONGOCAT_NULLPTR) { + context.wayland->animation_trigger_context = BONGOCAT_NULLPTR; + } + if (context.animation != BONGOCAT_NULLPTR) { + context.animation->_input = BONGOCAT_NULLPTR; + } // Cleanup systems - if (context.animation != nullptr) + if (context.animation != BONGOCAT_NULLPTR) { cleanup(*context.animation); - if (context.input != nullptr) + } + if (context.input != BONGOCAT_NULLPTR) { cleanup(*context.input); - if (context.update != nullptr) + } + if (context.update != BONGOCAT_NULLPTR) { cleanup(*context.update); - if (context.signal_fd._fd >= 0) + } + if (context.signal_fd._fd >= 0) { close_fd(context.signal_fd); - if (context.config_watcher != nullptr) + } + if (context.config_watcher != BONGOCAT_NULLPTR) { cleanup_watcher(*context.config_watcher); - context.signal_watch_path = nullptr; + } + context.signal_watch_path = BONGOCAT_NULLPTR; release_allocated_memory(context.config_watcher); release_allocated_memory(context.input); @@ -141,18 +161,20 @@ void cleanup(main_context_t& context) { // Cleanup configuration cleanup(context.config); - context.overwrite_config_parameters.output_name = nullptr; + context.overwrite_config_parameters.output_name = BONGOCAT_NULLPTR; // cleanup signals handler platform::close_fd(context.signal_fd); - if (context.pid_filename) + if (context.pid_filename != BONGOCAT_NULLPTR) { ::free(context.pid_filename); - context.pid_filename = nullptr; + context.pid_filename = BONGOCAT_NULLPTR; + } - if (context.default_config_filename) + if (context.default_config_filename != BONGOCAT_NULLPTR) { ::free(context.default_config_filename); - context.default_config_filename = nullptr; + context.default_config_filename = BONGOCAT_NULLPTR; + } } inline main_context_t& get_main_context() { @@ -165,12 +187,12 @@ inline main_context_t& get_main_context() { // ============================================================================= struct cli_args_t { - const char *config_file{nullptr}; + const char *config_file{BONGOCAT_NULLPTR}; bool watch_config{false}; bool toggle_mode{false}; bool show_help{false}; bool show_version{false}; - const char *output_name{nullptr}; + const char *output_name{BONGOCAT_NULLPTR}; int32_t randomize_index{-1}; int32_t strict{-1}; bool ignore_running{false}; @@ -239,8 +261,9 @@ static pid_t process_get_running_pid(const char *program_name, const char *pid_f // File is locked by another process, so it's running // We need to read the PID anyway, so let's try without lock fd = platform::FileDescriptor(::open(pid_filename, O_RDONLY)); - if (fd._fd < 0) + if (fd._fd < 0) { return -1; + } } else { return -1; } @@ -261,7 +284,7 @@ static pid_t process_get_running_pid(const char *program_name, const char *pid_f } } - char *endptr = nullptr; + char *endptr = BONGOCAT_NULLPTR; errno = 0; // Reset errno before call const auto pid = static_cast(strtol(pid_str, &endptr, 10)); if (endptr == pid_str) { @@ -275,15 +298,15 @@ static pid_t process_get_running_pid(const char *program_name, const char *pid_f char exe_path[PATH_MAX] = {0}; snprintf(exe_path, sizeof(exe_path), "/proc/%d/exe", pid); char buf[PATH_MAX] = {0}; - ssize_t len = readlink(exe_path, buf, sizeof(buf) - 1); + const ssize_t len = readlink(exe_path, buf, sizeof(buf) - 1); if (len > 0) { buf[len] = '\0'; const char *exe_basename = strrchr(buf, '/'); - exe_basename = exe_basename ? exe_basename + 1 : buf; + exe_basename = exe_basename != BONGOCAT_NULLPTR ? exe_basename + 1 : buf; const char *prog_basename = strrchr(program_name, '/'); - prog_basename = prog_basename ? prog_basename + 1 : program_name; + prog_basename = prog_basename != BONGOCAT_NULLPTR ? prog_basename + 1 : program_name; if (strcmp(exe_basename, prog_basename) != 0) { return -1; @@ -361,12 +384,12 @@ static bool config_devices_changed(const config::config_t& old_config, const con } static void config_reload_callback() { - assert(get_main_context().input != nullptr); - assert(get_main_context().animation != nullptr); - assert(get_main_context().signal_watch_path != nullptr); + assert(get_main_context().input != BONGOCAT_NULLPTR); + assert(get_main_context().animation != BONGOCAT_NULLPTR); + assert(get_main_context().signal_watch_path != BONGOCAT_NULLPTR); BONGOCAT_LOG_INFO("Reloading configuration from: %s (config_watcher=%s)", get_main_context().signal_watch_path, (get_main_context().config_watcher) ? get_main_context().config_watcher->config_path : "OFF"); - assert(get_main_context().config_watcher == nullptr || + assert(get_main_context().config_watcher == BONGOCAT_NULLPTR || strcmp(get_main_context().config_watcher->config_path, get_main_context().signal_watch_path) == 0); if (strcmp(get_main_context().signal_watch_path, "-") == 0) { @@ -391,10 +414,10 @@ static void config_reload_callback() { platform::LockGuard guard(get_main_context().sync_configs); config::config_t old_config = get_main_context().config; // keep old animation, don't randomize - if (old_config.randomize_index && new_config.randomize_index && + if (old_config.randomize_index >= 1 && new_config.randomize_index >= 1 && old_config.animation_sprite_sheet_layout == new_config.animation_sprite_sheet_layout && old_config.animation_dm_set == new_config.animation_dm_set) { - new_config._keep_old_animation_index = !new_config.randomize_on_reload; + new_config._keep_old_animation_index = new_config.randomize_on_reload <= 0; } // If successful, check if input devices changed before updating config devices_changed = config_devices_changed(old_config, new_config); @@ -473,7 +496,7 @@ static void config_reload_callback() { BONGOCAT_LOG_INFO("New screen dimensions: %dx%d", get_main_context().wayland->wayland_context._screen_width, get_main_context().wayland->wayland_context._bar_height); - assert(get_main_context().animation != nullptr); + assert(get_main_context().animation != BONGOCAT_NULLPTR); animation::trigger(*get_main_context().animation, animation::trigger_animation_cause_mask_t::UpdateConfig); // Check if input devices changed and restart monitoring if needed @@ -552,11 +575,12 @@ static char *default_config_file_path() { const char *xdg_config_home = getenv("XDG_CONFIG_HOME"); const char *home = getenv("HOME"); - if (xdg_config_home != nullptr) { - size_t len = strlen(xdg_config_home) + 1 + strlen(DEFAULT_CONF_FILENAME) + 1; + if (xdg_config_home != BONGOCAT_NULLPTR) { + const size_t len = strlen(xdg_config_home) + 1 + strlen(DEFAULT_CONF_FILENAME) + 1; char *path = static_cast(::malloc(len)); - if (!path) - return nullptr; + if (path == BONGOCAT_NULLPTR) [[unlikely]] { + return BONGOCAT_NULLPTR; + } snprintf(path, len, "%s/%s", xdg_config_home, DEFAULT_CONF_FILENAME); if (access(path, F_OK) == 0) { @@ -564,14 +588,15 @@ static char *default_config_file_path() { } free(path); - path = nullptr; + path = BONGOCAT_NULLPTR; } - if (home != nullptr) { - size_t len = strlen(home) + strlen("/.config/") + strlen(DEFAULT_CONF_FILENAME) + 1; + if (home != BONGOCAT_NULLPTR) { + const size_t len = strlen(home) + strlen("/.config/") + strlen(DEFAULT_CONF_FILENAME) + 1; char *path = static_cast(::malloc(len)); - if (!path) - return nullptr; + if (path == BONGOCAT_NULLPTR) [[unlikely]] { + return BONGOCAT_NULLPTR; + } snprintf(path, len, "%s/.config/%s", home, DEFAULT_CONF_FILENAME); if (access(path, F_OK) == 0) { @@ -579,7 +604,7 @@ static char *default_config_file_path() { } free(path); - path = nullptr; + path = BONGOCAT_NULLPTR; } // If neither env var is set, fallback to just filename in current dir @@ -602,7 +627,7 @@ static bongocat_error_t signal_setup_handlers(main_context_t& ctx) { sigaddset(&mask, SIGHUP); // Block signals globally so they are only delivered via signalfd - if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) [[unlikely]] { + if (sigprocmask(SIG_BLOCK, &mask, BONGOCAT_NULLPTR) == -1) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to block signals: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; } @@ -654,7 +679,7 @@ static bongocat_error_t system_initialize_components(main_context_t& ctx) { // Initialize Wayland { - assert(ctx.animation != nullptr); + assert(ctx.animation != BONGOCAT_NULLPTR); /// @NOTE: animation needed only for reference auto [wayland, wayland_error] = platform::wayland::create(*ctx.animation, ctx.config); if (wayland_error != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { @@ -666,8 +691,8 @@ static bongocat_error_t system_initialize_components(main_context_t& ctx) { // Setup wayland { - assert(ctx.wayland != nullptr); - assert(ctx.animation != nullptr); + assert(ctx.wayland != BONGOCAT_NULLPTR); + assert(ctx.animation != BONGOCAT_NULLPTR); bongocat_error_t setup_wayland_result = setup(*ctx.wayland, *ctx.animation); if (setup_wayland_result != bongocat_error_t::BONGOCAT_SUCCESS) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to setup wayland: %s", bongocat::error_string(setup_wayland_result)); @@ -677,9 +702,9 @@ static bongocat_error_t system_initialize_components(main_context_t& ctx) { // Start animation thread { - assert(ctx.animation != nullptr); - assert(ctx.input != nullptr); - assert(ctx.update != nullptr); + assert(ctx.animation != BONGOCAT_NULLPTR); + assert(ctx.input != BONGOCAT_NULLPTR); + assert(ctx.update != BONGOCAT_NULLPTR); bongocat_error_t start_animation_result = animation::start(*ctx.animation, *ctx.input, *ctx.update, ctx.config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); @@ -691,8 +716,8 @@ static bongocat_error_t system_initialize_components(main_context_t& ctx) { // Start input monitoring { - assert(ctx.animation != nullptr); - assert(ctx.input != nullptr); + assert(ctx.animation != BONGOCAT_NULLPTR); + assert(ctx.input != BONGOCAT_NULLPTR); bongocat_error_t start_input_result = platform::input::start(*ctx.input, *ctx.animation, ctx.config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); @@ -704,8 +729,8 @@ static bongocat_error_t system_initialize_components(main_context_t& ctx) { // Start update monitoring { - assert(ctx.animation != nullptr); - assert(ctx.update != nullptr); + assert(ctx.animation != BONGOCAT_NULLPTR); + assert(ctx.update != BONGOCAT_NULLPTR); bongocat_error_t start_update_result = platform::update::start(*ctx.update, *ctx.animation, ctx.config, get_main_context().configs_reloaded_cond, get_main_context().config_generation); @@ -739,7 +764,7 @@ static bongocat_error_t system_initialize_components(main_context_t& ctx) { static void cli_show_help(const char *program_name) { char *base_program_name = strdup(program_name); - if (!base_program_name) { + if (base_program_name == BONGOCAT_NULLPTR) [[unlikely]] { perror("strdup"); return; } @@ -834,7 +859,7 @@ static created_result_t cli_parse_arguments(int argc, char *argv[]) } else if (strcmp(argv[i], "--nr") == 0) { args.nr_set = true; if (i + 1 < argc) { - char *endptr{nullptr}; + char *endptr{BONGOCAT_NULLPTR}; args.nr = strtoll(argv[i + 1], &endptr, 10); if (*endptr != '\0' || errno == ERANGE) { BONGOCAT_LOG_ERROR("--nr option requires a valid number"); @@ -898,17 +923,18 @@ int main(int argc, char *argv[]) { .randomize_index = args.randomize_index, .strict = args.strict, }; - if (args.config_file == nullptr) { + if (args.config_file == BONGOCAT_NULLPTR) { /// @TODO: RAII default_config_filename string get_main_context().default_config_filename = default_config_file_path(); } - const char *config_file = args.config_file == nullptr ? get_main_context().default_config_filename : args.config_file; - if (args.strict) { + const char *config_file = + args.config_file == BONGOCAT_NULLPTR ? get_main_context().default_config_filename : args.config_file; + if (args.strict >= 1) { if (strcmp(config_file, "-") != 0 && access(config_file, F_OK) != 0) { BONGOCAT_LOG_ERROR("Configuration file required: %s", config_file); - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } return EXIT_FAILURE; } @@ -917,29 +943,29 @@ int main(int argc, char *argv[]) { auto [config, config_error] = config::load(config_file, ctx.overwrite_config_parameters); if (config_error != bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_ERROR("Failed to load configuration: %s", bongocat::error_string(config_error)); - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } return EXIT_FAILURE; } ctx.config = bongocat::move(config); - bongocat::error_init(ctx.config.enable_debug); + bongocat::error_init(ctx.config.enable_debug >= 1); // validate args if (config._strict) { if (args.nr_set && args.nr < 0) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } BONGOCAT_LOG_ERROR("--nr needs to be a positive number"); return EXIT_FAILURE; } - if (args.output_name_set && (!args.output_name || strlen(args.output_name) <= 0)) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.output_name_set && (args.output_name == BONGOCAT_NULLPTR || strlen(args.output_name) <= 0)) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } BONGOCAT_LOG_ERROR("--output_name value is missing"); return EXIT_FAILURE; @@ -948,37 +974,38 @@ int main(int argc, char *argv[]) { if (args.nr >= 0) { // set pid file, based on nr - const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr) + 1; + const int needed_size = snprintf(BONGOCAT_NULLPTR, 0, PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr) + 1; assert(needed_size >= 0); ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); - if (ctx.pid_filename != nullptr) { + if (ctx.pid_filename != BONGOCAT_NULLPTR) { snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_NR_TEMPLATE, args.nr); } - } else if (ctx.config.output_name && ctx.config.output_name[0] != '\0') { + } else if (ctx.config.output_name != BONGOCAT_NULLPTR && ctx.config.output_name[0] != '\0') { // set pid file, based on output_name if (!args.ignore_running) { - const int needed_size = snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_TEMPLATE, ctx.config.output_name) + 1; + const int needed_size = snprintf(BONGOCAT_NULLPTR, 0, PID_FILE_WITH_SUFFIX_TEMPLATE, ctx.config.output_name) + 1; assert(needed_size >= 0); ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); - if (ctx.pid_filename != nullptr) { + if (ctx.pid_filename != BONGOCAT_NULLPTR) { snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_TEMPLATE, ctx.config.output_name); } } else { - const int needed_size = - snprintf(nullptr, 0, PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, ctx.config.output_name, platform::slow_rand()) + 1; + const int needed_size = snprintf(BONGOCAT_NULLPTR, 0, PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, ctx.config.output_name, + platform::slow_rand()) + + 1; assert(needed_size >= 0); ctx.pid_filename = static_cast(::malloc(static_cast(needed_size))); - if (ctx.pid_filename != nullptr) { + if (ctx.pid_filename != BONGOCAT_NULLPTR) { snprintf(ctx.pid_filename, static_cast(needed_size), PID_FILE_WITH_SUFFIX_MULTI_TEMPLATE, ctx.config.output_name, platform::slow_rand()); } } - if (ctx.pid_filename == nullptr) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (ctx.pid_filename == BONGOCAT_NULLPTR) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } BONGOCAT_LOG_ERROR("Failed to allocate PID filename"); return EXIT_FAILURE; @@ -999,18 +1026,18 @@ int main(int argc, char *argv[]) { // Create PID file to track this instance const platform::FileDescriptor pid_fd = process_create_pid_file(ctx.pid_filename); if (pid_fd._fd < 0) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } BONGOCAT_LOG_ERROR("Failed to create PID file"); return EXIT_FAILURE; } if (!args.ignore_running) { if (pid_fd._fd == -2) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } BONGOCAT_LOG_ERROR("Another instance of bongocat is already running"); return EXIT_FAILURE; @@ -1021,11 +1048,11 @@ int main(int argc, char *argv[]) { // Setup signal handlers ctx.signal_watch_path = config_file; - bongocat_error_t signal_result = signal_setup_handlers(ctx); + const bongocat_error_t signal_result = signal_setup_handlers(ctx); if (signal_result != bongocat_error_t::BONGOCAT_SUCCESS) { - if (args.config_file == nullptr && get_main_context().default_config_filename) { + if (args.config_file == BONGOCAT_NULLPTR && get_main_context().default_config_filename != BONGOCAT_NULLPTR) { ::free(get_main_context().default_config_filename); - get_main_context().default_config_filename = nullptr; + get_main_context().default_config_filename = BONGOCAT_NULLPTR; } BONGOCAT_LOG_ERROR("Failed to setup signal handlers: %s", bongocat::error_string(signal_result)); return EXIT_FAILURE; @@ -1049,9 +1076,9 @@ int main(int argc, char *argv[]) { system_cleanup_and_exit(ctx, EXIT_FAILURE); } - assert(ctx.input != nullptr); - assert(ctx.animation != nullptr); - assert(ctx.wayland != nullptr); + assert(ctx.input != BONGOCAT_NULLPTR); + assert(ctx.animation != BONGOCAT_NULLPTR); + assert(ctx.wayland != BONGOCAT_NULLPTR); if (abs(ctx.config.cat_x_offset) > ctx.wayland->wayland_context._screen_width) { BONGOCAT_LOG_WARNING("cat_x_offset %d may position cat off-screen (screen width: %d)", ctx.config.cat_x_offset, @@ -1065,8 +1092,8 @@ int main(int argc, char *argv[]) { // trigger initial rendering platform::wayland::request_render(*ctx.animation); // Main Wayland event loop with graceful shutdown - assert(ctx.wayland != nullptr); - assert(ctx.input != nullptr); + assert(ctx.wayland != BONGOCAT_NULLPTR); + assert(ctx.input != BONGOCAT_NULLPTR); /// @NOTE: config_watcher os optional result = run(*ctx.wayland, ctx.running, ctx.signal_fd._fd, *ctx.input, ctx.config, ctx.config_watcher.ptr, config_reload_callback); @@ -1078,6 +1105,7 @@ int main(int argc, char *argv[]) { BONGOCAT_LOG_INFO("Main loop exited, shutting down"); system_cleanup_and_exit(ctx, EXIT_SUCCESS); + BONGOCAT_UNREACHABLE(); // Never reached // return EXIT_SUCCESS; } \ No newline at end of file diff --git a/src/graphics/animation.cpp b/src/graphics/animation.cpp index 1c9e2f8b..a6ff8bd3 100644 --- a/src/graphics/animation.cpp +++ b/src/graphics/animation.cpp @@ -68,9 +68,9 @@ static bool is_sleep_time(const config::config_t& config) { time(&raw_time); localtime_r(&raw_time, &time_info); - const int now_minutes = time_info.tm_hour * 60 + time_info.tm_min; - const int begin = config.sleep_begin.hour * 60 + config.sleep_begin.min; - const int end = config.sleep_end.hour * 60 + config.sleep_end.min; + const int now_minutes = (time_info.tm_hour * 60) + time_info.tm_min; + const int begin = (config.sleep_begin.hour * 60) + config.sleep_begin.min; + const int end = (config.sleep_end.hour * 60) + config.sleep_end.min; // Normal range (e.g., 10:00–22:00): begin < end && (now_minutes >= begin && now_minutes < end) // Overnight range (e.g., 22:00–06:00): begin > end && (now_minutes >= begin || now_minutes < end) @@ -183,7 +183,7 @@ static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_co [[maybe_unused]] const platform::update::update_context_t& upd, const animation_state_t& current_state, const animation_trigger_t& trigger, const config::config_t& current_config) { - assert(input.shm != nullptr); + assert(input.shm); const auto& input_shm = *input.shm; const auto& update_shm = *upd.shm; const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; @@ -193,17 +193,19 @@ static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_co has_flag(trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress) && trigger.any_key_press_counter > 0; const bool process_idle_animation_by_animation_speed = - current_config.idle_animation && current_config.animation_speed_ms > 0 && + current_config.idle_animation >= 1 && current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms; - const bool process_idle_animation_by_fps = current_config.idle_animation && current_config.animation_speed_ms <= 0 && + const bool process_idle_animation_by_fps = current_config.idle_animation >= 1 && + current_config.animation_speed_ms <= 0 && current_state.frame_delta_ms_counter > fps_ms; const bool process_idle_animation = process_idle_animation_by_animation_speed || process_idle_animation_by_fps; - const bool release_frame_by_animation_speed = !current_config.idle_animation && + const bool release_frame_by_animation_speed = current_config.idle_animation <= 0 && current_config.animation_speed_ms > 0 && current_state.hold_frame_ms > current_config.animation_speed_ms; - const bool release_frame_animation_by_fps = - !current_config.idle_animation && current_config.animation_speed_ms <= 0 && current_state.hold_frame_ms > fps_ms; + const bool release_frame_animation_by_fps = current_config.idle_animation <= 0 && + current_config.animation_speed_ms <= 0 && + current_state.hold_frame_ms > fps_ms; const bool release_frame_for_non_idle = release_frame_by_animation_speed || release_frame_animation_by_fps; const bool go_next_frame = (current_config.animation_speed_ms > 0 && current_state.frame_delta_ms_counter > current_config.animation_speed_ms) || @@ -277,11 +279,11 @@ static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_co .any_key_pressed = any_key_pressed, .trigger_test_animation = current_config.test_animation_interval_sec > 0 && - current_state.frame_delta_ms_counter > current_config.test_animation_interval_sec * 1000, + current_state.frame_delta_ms_counter > current_config.test_animation_interval_sec * 1000L, .check_for_idle_sleep = current_config.idle_sleep_timeout_sec > 0 && ((SLEEP_BORING_PART > 0 && - current_state.frame_delta_ms_counter > current_config.idle_sleep_timeout_sec * 1000 / SLEEP_BORING_PART && + current_state.frame_delta_ms_counter > current_config.idle_sleep_timeout_sec * 1000L / SLEEP_BORING_PART && last_key_pressed_timestamp > 0) || process_idle_animation || process_movement), .process_movement = process_movement, @@ -327,7 +329,7 @@ anim_update_animation_state(animation_shared_memory_t& anim_shm, animation_state state = new_state; if (new_state.animations_index != current_state.animations_index || rerender || moved) { anim_shm.animation_player_result = new_animation_result; - if (current_config.enable_debug) { + if (current_config.enable_debug >= 1) { BONGOCAT_LOG_VERBOSE("Animation frame change: %d", new_animation_result.sprite_sheet_col); } if (frame_changed) { @@ -366,7 +368,7 @@ anim_bongocat_process_animation(const platform::input::input_context_t& input, assert(MAX_ANIMATION_FRAMES <= INT_MAX); // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); const config::config_t& current_config = *input._local_copy_config; anim_bongocat_process_animation_result_t ret{.row_state = new_state.row_state, @@ -391,20 +393,20 @@ anim_bongocat_process_animation(const platform::input::input_context_t& input, case animation_state_row_t::StartWriting: case animation_state_row_t::Writing: case animation_state_row_t::EndWriting: - if (current_config.enable_hand_mapping) { + if (current_config.enable_hand_mapping >= 1) { switch (input.shm->hand_mapping) { case platform::input::input_hand_mapping_t::None: new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; break; case platform::input::input_hand_mapping_t::Left: new_animation_result.sprite_sheet_col = - (current_config.mirror_x) ? current_frames.animations.right_writing[new_state.animations_index] - : current_frames.animations.left_writing[new_state.animations_index]; + (current_config.mirror_x >= 1) ? current_frames.animations.right_writing[new_state.animations_index] + : current_frames.animations.left_writing[new_state.animations_index]; break; case platform::input::input_hand_mapping_t::Right: new_animation_result.sprite_sheet_col = - (current_config.mirror_x) ? current_frames.animations.left_writing[new_state.animations_index] - : current_frames.animations.right_writing[new_state.animations_index]; + (current_config.mirror_x >= 1) ? current_frames.animations.left_writing[new_state.animations_index] + : current_frames.animations.right_writing[new_state.animations_index]; break; } } else { @@ -458,7 +460,7 @@ anim_bongocat_restart_animation(animation_context_t& ctx, const platform::input: assert(MAX_ANIMATION_FRAMES <= INT_MAX); // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); const config::config_t& current_config = *input._local_copy_config; new_state.row_state = new_row_state; @@ -470,12 +472,12 @@ anim_bongocat_restart_animation(animation_context_t& ctx, const platform::input: switch (new_state.row_state) { case animation_state_row_t::Idle: new_animation_result.sprite_sheet_col = current_frames.animations.idle[new_state.animations_index]; - if (current_config.idle_frame) { + if (current_config.idle_frame >= 1) { new_animation_result.sprite_sheet_col = current_config.idle_frame; } break; case animation_state_row_t::StartWriting: { - if (current_config.enable_hand_mapping) { + if (current_config.enable_hand_mapping >= 1) { switch (input.shm->hand_mapping) { case platform::input::input_hand_mapping_t::None: new_state.animations_index = static_cast(ctx._rng.range(0, (MAX_ANIMATION_FRAMES - 1) / 2)); @@ -492,20 +494,20 @@ anim_bongocat_restart_animation(animation_context_t& ctx, const platform::input: } case animation_state_row_t::Writing: case animation_state_row_t::EndWriting: - if (current_config.enable_hand_mapping) { + if (current_config.enable_hand_mapping >= 1) { switch (input.shm->hand_mapping) { case platform::input::input_hand_mapping_t::None: new_animation_result.sprite_sheet_col = current_frames.animations.writing[new_state.animations_index]; break; case platform::input::input_hand_mapping_t::Left: new_animation_result.sprite_sheet_col = - (current_config.mirror_x) ? current_frames.animations.right_writing[new_state.animations_index] - : current_frames.animations.left_writing[new_state.animations_index]; + (current_config.mirror_x >= 1) ? current_frames.animations.right_writing[new_state.animations_index] + : current_frames.animations.left_writing[new_state.animations_index]; break; case platform::input::input_hand_mapping_t::Right: new_animation_result.sprite_sheet_col = - (current_config.mirror_x) ? current_frames.animations.left_writing[new_state.animations_index] - : current_frames.animations.right_writing[new_state.animations_index]; + (current_config.mirror_x >= 1) ? current_frames.animations.left_writing[new_state.animations_index] + : current_frames.animations.right_writing[new_state.animations_index]; break; } } else { @@ -667,11 +669,11 @@ anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::i const anim_handle_key_press_result_t& trigger_result) { using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm); + assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const auto& input_shm = *input.shm; const auto current_state = state; @@ -679,7 +681,7 @@ anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::i [[maybe_unused]] const int anim_index = anim_shm.anim_index; const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; assert(anim_shm.anim_type == config::config_animation_sprite_sheet_layout_t::Bongocat); - assert(get_current_animation(ctx).type == animation_t::Type::Bongocat); + assert(get_current_animation(ctx).type == animation_t::type_t::Bongocat); const auto& current_frames = get_current_animation(ctx).bongocat; auto new_animation_result = anim_shm.animation_player_result; @@ -729,13 +731,13 @@ anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::i anim_bongocat_process_animation(input, new_animation_result, new_state, current_state, current_frames); } - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + const bool is_sleeping_time = current_config.enable_scheduled_sleep >= 1 && is_sleep_time(current_config); // Idle Sleep if (conditions.check_for_idle_sleep) { if (!is_sleeping_time) { const platform::timestamp_ms_t now = platform::get_current_time_ms(); - const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000; + const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000L; assert(now >= last_key_pressed_timestamp); const auto sleep_timeout = now - last_key_pressed_timestamp; @@ -813,7 +815,7 @@ anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::i } // Sleep Mode - if (current_config.enable_scheduled_sleep) { + if (current_config.enable_scheduled_sleep >= 1) { if (is_sleeping_time) { if (current_state.row_state == animation_state_row_t::Idle) { anim_bongocat_restart_animation(ctx, input, animation_state_row_t::Sleep, new_animation_result, new_state, @@ -856,18 +858,18 @@ static anim_next_frame_result_t anim_bongocat_key_pressed_next_frame( using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm); + assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto current_state = state; const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Bongocat); + assert(get_current_animation(ctx).type == animation_t::type_t::Bongocat); const auto& current_frames = get_current_animation(ctx).bongocat; auto new_animation_result = anim_shm.animation_player_result; @@ -1284,7 +1286,7 @@ anim_dm_handle_movement(animation_context_t& ctx, const platform::input::input_c const dm_sprite_sheet_t& current_frames, const config::config_t& current_config) { using namespace assets; - assert(ctx.shm != nullptr); + assert(ctx.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); @@ -1492,12 +1494,12 @@ static anim_next_frame_result_t anim_dm_idle_next_frame(animation_context_t& ctx using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - assert(upd.shm != nullptr); + assert(ctx.shm); + assert(input.shm); + assert(upd.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const auto& input_shm = *input.shm; const auto& update_shm = *upd.shm; @@ -1505,7 +1507,7 @@ static anim_next_frame_result_t anim_dm_idle_next_frame(animation_context_t& ctx const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Dm); + assert(get_current_animation(ctx).type == animation_t::type_t::Dm); const auto& current_frames = get_current_animation(ctx).dm; auto new_animation_result = anim_shm.animation_player_result; @@ -1783,17 +1785,17 @@ anim_dm_key_pressed_next_frame(animation_context_t& ctx, const platform::input:: using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm); + assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const auto& input_shm = *input.shm; const auto current_state = state; const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::Dm); + assert(get_current_animation(ctx).type == animation_t::type_t::Dm); const auto& current_frames = get_current_animation(ctx).dm; auto new_animation_result = anim_shm.animation_player_result; @@ -1899,12 +1901,12 @@ static anim_next_frame_result_t anim_dm_working_next_frame(animation_context_t& using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); + assert(ctx.shm); // assert(input.shm != nullptr); - assert(upd.shm != nullptr); + assert(upd.shm); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto& update_shm = *upd.shm; @@ -1912,7 +1914,7 @@ static anim_next_frame_result_t anim_dm_working_next_frame(animation_context_t& const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Dm); + assert(get_current_animation(ctx).type == animation_t::type_t::Dm); const auto& current_frames = get_current_animation(ctx).dm; auto new_animation_result = anim_shm.animation_player_result; @@ -2281,17 +2283,17 @@ anim_pkmn_idle_next_frame(animation_context_t& ctx, [[maybe_unused]] const platf const anim_handle_key_press_result_t& trigger_result) { using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm); + assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto current_state = state; const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::Pkmn); + assert(get_current_animation(ctx).type == animation_t::type_t::Pkmn); const auto& current_frames = get_current_animation(ctx).pkmn; auto new_animation_result = anim_shm.animation_player_result; @@ -2356,18 +2358,18 @@ anim_pkmn_key_pressed_next_frame(animation_context_t& ctx, using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm); + assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto current_state = state; const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Pkmn); + assert(get_current_animation(ctx).type == animation_t::type_t::Pkmn); const auto& current_frames = get_current_animation(ctx).pkmn; auto new_animation_result = anim_shm.animation_player_result; @@ -2430,65 +2432,65 @@ anim_ms_agent_process_animation(animation_player_result_t& new_animation_result, assert(MAX_ANIMATION_FRAMES > 0); assert(MAX_ANIMATION_FRAMES <= INT_MAX); - const ms_agent_sprite_sheet_animation_section_t *section = nullptr; + const ms_agent_sprite_sheet_animation_section_t *section = BONGOCAT_NULLPTR; switch (new_state.row_state) { case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + section = current_frames.idle.valid ? ¤t_frames.idle : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + section = current_frames.happy.valid ? ¤t_frames.happy : BONGOCAT_NULLPTR; break; case animation_state_row_t::FallASleep: // use sleep animation for ms agent - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : BONGOCAT_NULLPTR; break; case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + section = current_frames.boring.valid ? ¤t_frames.boring : BONGOCAT_NULLPTR; break; case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + section = current_frames.start_working.valid ? ¤t_frames.start_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; + section = current_frames.working.valid ? ¤t_frames.working : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + section = current_frames.end_working.valid ? ¤t_frames.end_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + section = current_frames.moving.valid ? ¤t_frames.moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + section = current_frames.start_running.valid ? ¤t_frames.start_running : BONGOCAT_NULLPTR; break; case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; + section = current_frames.running.valid ? ¤t_frames.running : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + section = current_frames.end_running.valid ? ¤t_frames.end_running : BONGOCAT_NULLPTR; break; } @@ -2533,64 +2535,64 @@ anim_ms_agent_restart_animation([[maybe_unused]] animation_context_t& ctx, anima assert(MAX_ANIMATION_FRAMES > 0); assert(MAX_ANIMATION_FRAMES <= INT_MAX); - const ms_agent_sprite_sheet_animation_section_t *section = nullptr; + const ms_agent_sprite_sheet_animation_section_t *section = BONGOCAT_NULLPTR; switch (new_row_state) { case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + section = current_frames.idle.valid ? ¤t_frames.idle : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + section = current_frames.happy.valid ? ¤t_frames.happy : BONGOCAT_NULLPTR; break; case animation_state_row_t::FallASleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : BONGOCAT_NULLPTR; break; case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + section = current_frames.boring.valid ? ¤t_frames.boring : BONGOCAT_NULLPTR; break; case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + section = current_frames.start_working.valid ? ¤t_frames.start_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; + section = current_frames.working.valid ? ¤t_frames.working : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + section = current_frames.end_working.valid ? ¤t_frames.end_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + section = current_frames.moving.valid ? ¤t_frames.moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + section = current_frames.start_running.valid ? ¤t_frames.start_running : BONGOCAT_NULLPTR; break; case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; + section = current_frames.running.valid ? ¤t_frames.running : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + section = current_frames.end_running.valid ? ¤t_frames.end_running : BONGOCAT_NULLPTR; break; } @@ -2638,12 +2640,12 @@ anim_ms_agent_idle_next_frame(animation_context_t& ctx, const platform::input::i using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - // assert(upd.shm != nullptr); + assert(ctx.shm); + assert(input.shm); + // assert(upd.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const auto& input_shm = *input.shm; // const auto& update_shm = *upd.shm; @@ -2651,7 +2653,7 @@ anim_ms_agent_idle_next_frame(animation_context_t& ctx, const platform::input::i const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); + assert(get_current_animation(ctx).type == animation_t::type_t::MsAgent); const auto& current_frames = get_current_animation(ctx).ms_agent; auto new_animation_result = anim_shm.animation_player_result; @@ -2858,17 +2860,17 @@ anim_ms_agent_key_pressed_next_frame(animation_context_t& ctx, animation_state_t using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm); + assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto current_state = state; const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); + assert(get_current_animation(ctx).type == animation_t::type_t::MsAgent); const auto& current_frames = get_current_animation(ctx).ms_agent; auto new_animation_result = anim_shm.animation_player_result; @@ -2947,7 +2949,7 @@ platform::update::update_context_t& upd, animation_state_t& state) { using names const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; //const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::MsAgent); + assert(get_current_animation(ctx).type == animation_t::type_t::MsAgent); const auto& current_frames = get_current_animation(ctx).ms_agent; auto new_animation_result = anim_shm.animation_player_result; @@ -2987,70 +2989,70 @@ anim_custom_process_animation(animation_player_result_t& new_animation_result, a const custom_sprite_sheet_t& current_frames) { using namespace assets; - const custom_sprite_sheet_animation_section_t *section = nullptr; + const custom_sprite_sheet_animation_section_t *section = BONGOCAT_NULLPTR; switch (new_state.row_state) { case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + section = current_frames.idle.valid ? ¤t_frames.idle : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + section = current_frames.happy.valid ? ¤t_frames.happy : BONGOCAT_NULLPTR; break; case animation_state_row_t::FallASleep: - section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; + section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : BONGOCAT_NULLPTR; break; case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + section = current_frames.boring.valid ? ¤t_frames.boring : BONGOCAT_NULLPTR; break; case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + section = current_frames.start_working.valid ? ¤t_frames.start_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; + section = current_frames.working.valid ? ¤t_frames.working : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + section = current_frames.end_working.valid ? ¤t_frames.end_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + section = current_frames.moving.valid ? ¤t_frames.moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + section = current_frames.start_running.valid ? ¤t_frames.start_running : BONGOCAT_NULLPTR; break; case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; + section = current_frames.running.valid ? ¤t_frames.running : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + section = current_frames.end_running.valid ? ¤t_frames.end_running : BONGOCAT_NULLPTR; break; } anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (section && section->valid) { + if (section != BONGOCAT_NULLPTR && section->valid) { new_animation_result.sprite_sheet_row = section->row; new_animation_result.sprite_sheet_col = new_animation_result.sprite_sheet_col + 1; ret.status = anim_custom_process_animation_result_status_t::Updated; @@ -3080,77 +3082,77 @@ anim_custom_restart_animation([[maybe_unused]] animation_context_t& ctx, animati [[maybe_unused]] const config::config_t& current_config) { using namespace assets; - const custom_sprite_sheet_animation_section_t *section = nullptr; + const custom_sprite_sheet_animation_section_t *section = BONGOCAT_NULLPTR; switch (new_row_state) { case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + section = current_frames.idle.valid ? ¤t_frames.idle : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + section = current_frames.happy.valid ? ¤t_frames.happy : BONGOCAT_NULLPTR; break; case animation_state_row_t::FallASleep: - section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; + section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : BONGOCAT_NULLPTR; break; case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + section = current_frames.boring.valid ? ¤t_frames.boring : BONGOCAT_NULLPTR; break; case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + section = current_frames.start_working.valid ? ¤t_frames.start_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; + section = current_frames.working.valid ? ¤t_frames.working : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + section = current_frames.end_working.valid ? ¤t_frames.end_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + section = current_frames.moving.valid ? ¤t_frames.moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + section = current_frames.start_running.valid ? ¤t_frames.start_running : BONGOCAT_NULLPTR; break; case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; + section = current_frames.running.valid ? ¤t_frames.running : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + section = current_frames.end_running.valid ? ¤t_frames.end_running : BONGOCAT_NULLPTR; break; } anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (section && section->valid) { + if (section != BONGOCAT_NULLPTR && section->valid) { new_state.row_state = new_row_state; new_animation_result.sprite_sheet_row = section->row; new_animation_result.sprite_sheet_col = section->start_col; new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::None; if (new_state.row_state == animation_state_row_t::Idle) { assert(current_frames.idle.end_col >= 0); - if (current_config.idle_frame) { + if (current_config.idle_frame >= 1) { new_animation_result.sprite_sheet_col = current_config.idle_frame % (current_frames.idle.end_col + 1); } } else if (new_state.row_state == animation_state_row_t::StartMoving || @@ -3184,80 +3186,80 @@ static anim_custom_process_animation_result_t anim_custom_restart_animation( const animation_state_row_t new_row_states[new_row_states_count] = {new_row_state, fallback_row_state, end_fallback_row_state}; - const custom_sprite_sheet_animation_section_t *section = nullptr; - for (size_t i = 0; i < new_row_states_count && section == nullptr; i++) { + const custom_sprite_sheet_animation_section_t *section = BONGOCAT_NULLPTR; + for (size_t i = 0; i < new_row_states_count && section == BONGOCAT_NULLPTR; i++) { new_row_state = new_row_states[i]; switch (new_row_state) { case animation_state_row_t::Idle: - section = current_frames.idle.valid ? ¤t_frames.idle : nullptr; + section = current_frames.idle.valid ? ¤t_frames.idle : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWriting: - section = current_frames.start_writing.valid ? ¤t_frames.start_writing : nullptr; + section = current_frames.start_writing.valid ? ¤t_frames.start_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Writing: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWriting: - section = current_frames.end_writing.valid ? ¤t_frames.end_writing : nullptr; + section = current_frames.end_writing.valid ? ¤t_frames.end_writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::Happy: - section = current_frames.happy.valid ? ¤t_frames.happy : nullptr; + section = current_frames.happy.valid ? ¤t_frames.happy : BONGOCAT_NULLPTR; break; case animation_state_row_t::FallASleep: - section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : nullptr; + section = current_frames.fall_asleep.valid ? ¤t_frames.fall_asleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::Sleep: - section = current_frames.sleep.valid ? ¤t_frames.sleep : nullptr; + section = current_frames.sleep.valid ? ¤t_frames.sleep : BONGOCAT_NULLPTR; break; case animation_state_row_t::WakeUp: - section = current_frames.wake_up.valid ? ¤t_frames.wake_up : nullptr; + section = current_frames.wake_up.valid ? ¤t_frames.wake_up : BONGOCAT_NULLPTR; break; case animation_state_row_t::Boring: - section = current_frames.boring.valid ? ¤t_frames.boring : nullptr; + section = current_frames.boring.valid ? ¤t_frames.boring : BONGOCAT_NULLPTR; break; case animation_state_row_t::Test: - section = current_frames.writing.valid ? ¤t_frames.writing : nullptr; + section = current_frames.writing.valid ? ¤t_frames.writing : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartWorking: - section = current_frames.start_working.valid ? ¤t_frames.start_working : nullptr; + section = current_frames.start_working.valid ? ¤t_frames.start_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::Working: - section = current_frames.working.valid ? ¤t_frames.working : nullptr; + section = current_frames.working.valid ? ¤t_frames.working : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndWorking: - section = current_frames.end_working.valid ? ¤t_frames.end_working : nullptr; + section = current_frames.end_working.valid ? ¤t_frames.end_working : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartMoving: - section = current_frames.start_moving.valid ? ¤t_frames.start_moving : nullptr; + section = current_frames.start_moving.valid ? ¤t_frames.start_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::Moving: - section = current_frames.moving.valid ? ¤t_frames.moving : nullptr; + section = current_frames.moving.valid ? ¤t_frames.moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndMoving: - section = current_frames.end_moving.valid ? ¤t_frames.end_moving : nullptr; + section = current_frames.end_moving.valid ? ¤t_frames.end_moving : BONGOCAT_NULLPTR; break; case animation_state_row_t::StartRunning: - section = current_frames.start_running.valid ? ¤t_frames.start_running : nullptr; + section = current_frames.start_running.valid ? ¤t_frames.start_running : BONGOCAT_NULLPTR; break; case animation_state_row_t::Running: - section = current_frames.running.valid ? ¤t_frames.running : nullptr; + section = current_frames.running.valid ? ¤t_frames.running : BONGOCAT_NULLPTR; break; case animation_state_row_t::EndRunning: - section = current_frames.end_running.valid ? ¤t_frames.end_running : nullptr; + section = current_frames.end_running.valid ? ¤t_frames.end_running : BONGOCAT_NULLPTR; break; } } anim_custom_process_animation_result_t ret{.row_state = new_state.row_state, .status = anim_custom_process_animation_result_status_t::None}; - if (section && section->valid) { + if (section != BONGOCAT_NULLPTR && section->valid) { new_state.row_state = new_row_state; new_animation_result.sprite_sheet_row = section->row; new_animation_result.sprite_sheet_col = section->start_col; new_animation_result.overwrite_mirror_x = animation_player_custom_overwrite_mirror_x::None; if (new_state.row_state == animation_state_row_t::Idle) { assert(current_frames.idle.end_col >= 0); - if (current_config.idle_frame) { + if (current_config.idle_frame >= 1) { new_animation_result.sprite_sheet_col = current_config.idle_frame % (current_frames.idle.end_col + 1); } } else if (new_state.row_state == animation_state_row_t::StartMoving || @@ -3336,7 +3338,7 @@ anim_custom_handle_movement(animation_context_t& ctx, const platform::input::inp const config::config_t& current_config) { using namespace assets; - assert(ctx.shm != nullptr); + assert(ctx.shm != BONGOCAT_NULLPTR); animation_shared_memory_t& anim_shm = *ctx.shm; const auto conditions = get_anim_conditions(ctx, input, upd, current_state, trigger_result.trigger, current_config); @@ -3555,12 +3557,12 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); - assert(upd.shm != nullptr); + assert(ctx.shm != BONGOCAT_NULLPTR); + assert(input.shm != BONGOCAT_NULLPTR); + assert(upd.shm != BONGOCAT_NULLPTR); animation_shared_memory_t& anim_shm = *ctx.shm; const auto& input_shm = *input.shm; const auto& update_shm = *upd.shm; @@ -3568,7 +3570,7 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); + assert(get_current_animation(ctx).type == animation_t::type_t::Custom); const auto& current_frames = get_current_animation(ctx).custom; auto new_animation_result = anim_shm.animation_player_result; @@ -3579,7 +3581,7 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp /// @TODO: make animation fsm const platform::timestamp_ms_t now = platform::get_current_time_ms(); - const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000; + const platform::time_ms_t idle_sleep_timeout_ms = current_config.idle_sleep_timeout_sec * 1000L; assert(now >= last_key_pressed_timestamp); const auto sleep_timeout = now - last_key_pressed_timestamp; @@ -3674,13 +3676,13 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp current_frames, current_config); } } else { - if (current_config.idle_animation && conditions.go_next_frame) { + if (current_config.idle_animation >= 1 && conditions.go_next_frame) { anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); } } break; case animation_state_row_t::Idle: { - if (current_config.idle_animation && conditions.go_next_frame) { + if (current_config.idle_animation >= 1 && conditions.go_next_frame) { anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); } @@ -3692,7 +3694,7 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp if (current_frames.feature_sleep || current_frames.feature_boring) { // handle sleep - const bool is_sleeping_time = current_config.enable_scheduled_sleep && is_sleep_time(current_config); + const bool is_sleeping_time = current_config.enable_scheduled_sleep >= 1 && is_sleep_time(current_config); // Idle Sleep if (conditions.check_for_idle_sleep) { @@ -3768,7 +3770,7 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp // Sleep Mode if (current_frames.feature_sleep) { - if (current_config.enable_scheduled_sleep) { + if (current_config.enable_scheduled_sleep >= 1) { if (is_sleeping_time) { if (new_state.row_state == animation_state_row_t::Idle) { anim_custom_restart_animation(ctx, animation_state_row_t::FallASleep, animation_state_row_t::Sleep, @@ -3991,17 +3993,17 @@ anim_custom_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); - assert(input.shm != nullptr); + assert(ctx.shm != BONGOCAT_NULLPTR); + assert(input.shm != BONGOCAT_NULLPTR); animation_shared_memory_t& anim_shm = *ctx.shm; const auto& input_shm = *input.shm; const auto current_state = state; const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); + assert(get_current_animation(ctx).type == animation_t::type_t::Custom); const auto& current_frames = get_current_animation(ctx).custom; auto new_animation_result = anim_shm.animation_player_result; @@ -4134,12 +4136,12 @@ static anim_next_frame_result_t anim_custom_working_next_frame(animation_context using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); + assert(ctx.shm != BONGOCAT_NULLPTR); // assert(input.shm != nullptr); - assert(upd.shm != nullptr); + assert(upd.shm != BONGOCAT_NULLPTR); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto& update_shm = *upd.shm; @@ -4147,7 +4149,7 @@ static anim_next_frame_result_t anim_custom_working_next_frame(animation_context const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); + assert(get_current_animation(ctx).type == animation_t::type_t::Custom); const auto& current_frames = get_current_animation(ctx).custom; auto new_animation_result = anim_shm.animation_player_result; @@ -4236,12 +4238,12 @@ static anim_next_frame_result_t anim_custom_running_next_frame(animation_context using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); + assert(ctx.shm != BONGOCAT_NULLPTR); // assert(input.shm != nullptr); - assert(upd.shm != nullptr); + assert(upd.shm != BONGOCAT_NULLPTR); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; const auto& update_shm = *upd.shm; @@ -4249,7 +4251,7 @@ static anim_next_frame_result_t anim_custom_running_next_frame(animation_context const auto& current_animation_result = anim_shm.animation_player_result; [[maybe_unused]] const int anim_index = anim_shm.anim_index; // const platform::timestamp_ms_t last_key_pressed_timestamp = input_shm.last_key_pressed_timestamp; - assert(get_current_animation(ctx).type == animation_t::Type::Custom); + assert(get_current_animation(ctx).type == animation_t::type_t::Custom); const auto& current_frames = get_current_animation(ctx).custom; auto new_animation_result = anim_shm.animation_player_result; @@ -4338,12 +4340,12 @@ anim_handle_idle_animation(animation_context_t& ctx, [[maybe_unused]] const plat using namespace assets; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); // const config::config_t& current_config = *ctx._local_copy_config; - assert(input.shm != nullptr); - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; + assert(input.shm != BONGOCAT_NULLPTR); + assert(ctx.shm != BONGOCAT_NULLPTR); + const animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; // auto& animation_player_data = anim_shm.animation_player_data; // const int current_frame = animation_player_data.frame_index; @@ -4388,10 +4390,10 @@ static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_se const animation_trigger_t& trigger) { using namespace assets; - assert(animation_trigger_ctx._input != nullptr); - assert(animation_trigger_ctx._input->shm != nullptr); - assert(animation_trigger_ctx._update != nullptr); - assert(animation_trigger_ctx._update->shm != nullptr); + assert(animation_trigger_ctx._input != BONGOCAT_NULLPTR); + assert(animation_trigger_ctx._input->shm != BONGOCAT_NULLPTR); + assert(animation_trigger_ctx._update != BONGOCAT_NULLPTR); + assert(animation_trigger_ctx._update->shm != BONGOCAT_NULLPTR); animation_context_t& ctx = animation_trigger_ctx.anim; [[maybe_unused]] const platform::input::input_context_t& input = *animation_trigger_ctx._input; [[maybe_unused]] const platform::update::update_context_t& upd = *animation_trigger_ctx._update; @@ -4402,10 +4404,10 @@ static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_se // assert(animation_trigger_ctx._config != nullptr); // const config::config_t& current_config = *ctx._local_copy_config; - assert(input.shm != nullptr); - assert(upd.shm != nullptr); - assert(ctx.shm != nullptr); - animation_shared_memory_t& anim_shm = *ctx.shm; + assert(input.shm != BONGOCAT_NULLPTR); + assert(upd.shm != BONGOCAT_NULLPTR); + assert(ctx.shm != BONGOCAT_NULLPTR); + const animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; // const auto current_state = state; // const auto& current_animation_result = anim_shm.animation_player_result; @@ -4506,7 +4508,7 @@ static bool anim_update_state(animation_session_t& animation_trigger_ctx, animat platform::update::update_context_t& upd = *animation_trigger_ctx._update; animation_context_t& ctx = animation_trigger_ctx.anim; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; bool ret = false; @@ -4563,7 +4565,7 @@ static bool anim_update_state(animation_session_t& animation_trigger_ctx, animat static void anim_init_state(animation_context_t& ctx, animation_state_t& state) { // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; assert(current_config.fps > 0); @@ -4594,11 +4596,11 @@ static void *anim_thread(void *arg) { auto& trigger_ctx = *static_cast(arg); // sanity checks - assert(trigger_ctx._config != nullptr); - assert(trigger_ctx._input != nullptr); - assert(trigger_ctx._update != nullptr); - assert(trigger_ctx._configs_reloaded_cond != nullptr); - assert(trigger_ctx.anim.shm != nullptr); + assert(trigger_ctx._config != BONGOCAT_NULLPTR); + assert(trigger_ctx._input != BONGOCAT_NULLPTR); + assert(trigger_ctx._update != BONGOCAT_NULLPTR); + assert(trigger_ctx._configs_reloaded_cond != BONGOCAT_NULLPTR); + assert(trigger_ctx.anim.shm); assert(trigger_ctx.trigger_efd._fd >= 0); assert(trigger_ctx.render_efd._fd >= 0); assert(trigger_ctx.anim.update_config_efd._fd >= 0); @@ -4609,8 +4611,8 @@ static void *anim_thread(void *arg) { platform::LockGuard guard(trigger_ctx.anim.anim_lock); animation_context_t& ctx = trigger_ctx.anim; - assert(ctx.shm != nullptr); - // assert(input.shm != nullptr); + assert(ctx.shm); + // assert(input.shm); animation_shared_memory_t& anim_shm = *ctx.shm; // const auto& input_shm = *input.shm; // auto& current_state = state; @@ -4618,7 +4620,7 @@ static void *anim_thread(void *arg) { [[maybe_unused]] const int anim_index = anim_shm.anim_index; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; anim_init_state(ctx, state); @@ -4715,7 +4717,7 @@ static void *anim_thread(void *arg) { platform::time_ms_t timeout_ms; int32_t fps = 1; { - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; fps = current_config.fps; @@ -4742,8 +4744,9 @@ static void *anim_thread(void *arg) { assert(timeout_ms <= INT_MAX); const int poll_result = poll(fds, fds_count, static_cast(timeout_ms)); if (poll_result < 0) { - if (errno == EINTR) + if (errno == EINTR) { continue; // Interrupted by signal + } BONGOCAT_LOG_ERROR("animation: Poll error: %s", strerror(errno)); break; } @@ -4813,7 +4816,7 @@ static void *anim_thread(void *arg) { // Update Animations { platform::LockGuard guard(trigger_ctx.anim.anim_lock); - assert(ctx.shm != nullptr); + assert(ctx.shm); const bool frame_changed = anim_update_state(trigger_ctx, state, { .anim_cause = triggered_anim_cause, @@ -4858,9 +4861,9 @@ static void *anim_thread(void *arg) { const auto sec_diff = next_frame_time.tv_sec - now.tv_sec; const auto nsec_diff = next_frame_time.tv_nsec - now.tv_nsec; state.time_until_next_frame_ms = - static_cast(sec_diff * 1000L + (nsec_diff + 999999LL) / 1000000LL); + static_cast((sec_diff * 1000L) + (nsec_diff + 999999LL) / 1000000LL); - if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, nullptr) != 0) { + if (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next_frame_time, BONGOCAT_NULLPTR) != 0) { // Interrupted, just continue } } @@ -4872,9 +4875,9 @@ static void *anim_thread(void *arg) { // handle update config if (reload_config) { - assert(trigger_ctx._config_generation != nullptr); - assert(trigger_ctx._configs_reloaded_cond != nullptr); - assert(trigger_ctx._config != nullptr); + assert(trigger_ctx._config_generation != BONGOCAT_NULLPTR); + assert(trigger_ctx._configs_reloaded_cond != BONGOCAT_NULLPTR); + assert(trigger_ctx._config != BONGOCAT_NULLPTR); update_config(ctx, *trigger_ctx._config, new_gen); @@ -4905,7 +4908,7 @@ static void *anim_thread(void *arg) { BONGOCAT_LOG_INFO("Animation thread main loop exited"); - return nullptr; + return BONGOCAT_NULLPTR; } // ============================================================================= @@ -4919,11 +4922,11 @@ bongocat_error_t start(animation_session_t& trigger_ctx, platform::input::input_ // Initialize shared memory for local config trigger_ctx.anim._local_copy_config = platform::make_allocated_mmap(); - if (!trigger_ctx.anim._local_copy_config.ptr) { + if (!trigger_ctx.anim._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(trigger_ctx.anim._local_copy_config != nullptr); + assert(trigger_ctx.anim._local_copy_config); update_config(trigger_ctx.anim, config, atomic_load(&config_generation)); // set extern/global references @@ -4938,7 +4941,7 @@ bongocat_error_t start(animation_session_t& trigger_ctx, platform::input::input_ trigger_ctx._configs_reloaded_cond->notify_all(); // start animation thread - const int result = pthread_create(&trigger_ctx.anim._anim_thread, nullptr, anim_thread, &trigger_ctx); + const int result = pthread_create(&trigger_ctx.anim._anim_thread, BONGOCAT_NULLPTR, anim_thread, &trigger_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to create animation thread: %s", strerror(result)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -4972,11 +4975,11 @@ void trigger_update_config(animation_session_t& trigger_ctx, const config::confi BONGOCAT_NODISCARD static int rand_animation_index(animation_context_t& ctx, const config::config_t& config) { using namespace assets; - assert(ctx._local_copy_config != nullptr); - assert(ctx.shm != nullptr); + assert(ctx._local_copy_config); + assert(ctx.shm); platform::random_xoshiro128& rng = ctx._rng; - if (config.randomize_index) { + if (config.randomize_index >= 1) { if constexpr (features::EnableLazyLoadAssets) { switch (config.animation_sprite_sheet_layout) { case config::config_animation_sprite_sheet_layout_t::None: @@ -5145,7 +5148,7 @@ BONGOCAT_NODISCARD static int rand_animation_index(animation_context_t& ctx, con static void update_config_reload_sprite_sheet(animation_context_t& ctx) { using namespace assets; - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); platform::LockGuard guard(ctx.anim_lock); const auto old_anim_type = ctx.shm->anim_type; @@ -5203,8 +5206,8 @@ static void update_config_reload_sprite_sheet(animation_context_t& ctx) { static_cast(t1 - t0) / 1000000.0); } void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen) { - assert(ctx._local_copy_config != nullptr); - assert(ctx.shm != nullptr); + assert(ctx._local_copy_config); + assert(ctx.shm); *ctx._local_copy_config = config; diff --git a/src/graphics/animation_init.cpp b/src/graphics/animation_init.cpp index 3bb35c92..7d7a88f8 100644 --- a/src/graphics/animation_init.cpp +++ b/src/graphics/animation_init.cpp @@ -112,9 +112,9 @@ namespace details { created_result_t hot_load_animation(animation_context_t& ctx) { // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); + assert(ctx.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const int anim_index = anim_shm.anim_index; @@ -273,9 +273,9 @@ animation_t& get_current_animation(animation_context_t& ctx) { static animation_t none_sprite_sheet{}; // read-only config - assert(ctx._local_copy_config != nullptr); + assert(ctx._local_copy_config); // const config::config_t& current_config = *ctx._local_copy_config; - assert(ctx.shm != nullptr); + assert(ctx.shm); animation_shared_memory_t& anim_shm = *ctx.shm; const int anim_index = anim_shm.anim_index; @@ -284,7 +284,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { return none_sprite_sheet; case config::config_animation_sprite_sheet_layout_t::Bongocat: { if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Bongocat); + assert(anim_shm.anim.type == animation_t::type_t::Bongocat); return anim_shm.anim; } assert(anim_index >= 0); @@ -298,7 +298,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { return none_sprite_sheet; case config::config_animation_dm_set_t::min_dm: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } assert(anim_index >= 0); @@ -307,7 +307,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::dm: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } return static_cast(anim_index) < anim_shm.dm_anims.count @@ -315,7 +315,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::dm20: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } assert(anim_index >= 0); @@ -324,7 +324,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::dmx: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } assert(anim_index >= 0); @@ -333,7 +333,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::pen: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } return static_cast(anim_index) < anim_shm.pen_anims.count @@ -341,7 +341,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::pen20: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } assert(anim_index >= 0); @@ -350,7 +350,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::dmc: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } assert(anim_index >= 0); @@ -359,7 +359,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_dm_set_t::dmall: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Dm); + assert(anim_shm.anim.type == animation_t::type_t::Dm); return anim_shm.anim; } assert(anim_index >= 0); @@ -370,7 +370,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { } break; case config::config_animation_sprite_sheet_layout_t::Pkmn: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Pkmn); + assert(anim_shm.anim.type == animation_t::type_t::Pkmn); return anim_shm.anim; } assert(anim_index >= 0); @@ -379,7 +379,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { : none_sprite_sheet; case config::config_animation_sprite_sheet_layout_t::MsAgent: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::MsAgent); + assert(anim_shm.anim.type == animation_t::type_t::MsAgent); return anim_shm.anim; } assert(anim_index >= 0); @@ -392,7 +392,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { break; case config::config_animation_custom_set_t::misc: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Custom); + assert(anim_shm.anim.type == animation_t::type_t::Custom); return anim_shm.anim; } assert(anim_index >= 0); @@ -402,7 +402,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { break; case config::config_animation_custom_set_t::pmd: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Custom); + assert(anim_shm.anim.type == animation_t::type_t::Custom); return anim_shm.anim; } assert(anim_index >= 0); @@ -412,7 +412,7 @@ animation_t& get_current_animation(animation_context_t& ctx) { break; case config::config_animation_custom_set_t::custom: if (features::EnableLazyLoadAssets) { - assert(anim_shm.anim.type == animation_t::Type::Custom); + assert(anim_shm.anim.type == animation_t::type_t::Custom); return anim_shm.anim; } if (static_cast(anim_index) == CUSTOM_ANIM_INDEX) { @@ -433,8 +433,8 @@ created_result_t> create(const config::conf using namespace assets; BONGOCAT_LOG_INFO("Initializing animation system"); AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { + assert(ret); + if (!ret) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } @@ -446,7 +446,7 @@ created_result_t> create(const config::conf BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); // Initialize shared memory for local config ret->anim._local_copy_config = platform::make_allocated_mmap(); @@ -454,7 +454,7 @@ created_result_t> create(const config::conf BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->anim._local_copy_config != nullptr); + assert(ret->anim._local_copy_config); // config_set_defaults(*ctx._local_copy_config); *ret->anim._local_copy_config = config; ret->anim.shm->animation_player_result.sprite_sheet_col = config.idle_frame; // initial frame @@ -492,7 +492,7 @@ created_result_t> create(const config::conf // Load Bongocat if (should_load_bongocat(*ret->anim._local_copy_config)) { BONGOCAT_LOG_INFO("Load bongocat sprite sheet frames: %d", BONGOCAT_EMBEDDED_IMAGES_COUNT); - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes ctx.shm->bongocat_anims = platform::make_allocated_mmap_array(BONGOCAT_ANIM_COUNT); @@ -505,7 +505,7 @@ created_result_t> create(const config::conf // Load dm if (should_load_dm(*ret->anim._local_copy_config)) { BONGOCAT_LOG_INFO("Load dm sprite sheets: %d", DM_ANIMATIONS_COUNT); - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes if constexpr (features::EnableMinDmEmbeddedAssets) { @@ -581,7 +581,7 @@ created_result_t> create(const config::conf // Load Ms Pets (Clippy) if (should_load_ms_agent(*ret->anim._local_copy_config)) { BONGOCAT_LOG_INFO("Load MS agent sprite sheets: %d", MS_AGENTS_ANIM_COUNT); - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes ctx.shm->ms_anims = platform::make_allocated_mmap_array(MS_AGENTS_ANIM_COUNT); @@ -607,7 +607,7 @@ created_result_t> create(const config::conf // Load pkmn if (should_load_pkmn(*ret->anim._local_copy_config)) { BONGOCAT_LOG_INFO("Load pkmn sprite sheets: %d", PKMN_ANIM_COUNT); - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes ctx.shm->pkmn_anims = platform::make_allocated_mmap_array(PKMN_ANIM_COUNT); @@ -621,7 +621,7 @@ created_result_t> create(const config::conf // Load pmd (pkmn) if (should_load_pkmn(*ret->anim._local_copy_config)) { BONGOCAT_LOG_INFO("Load pmd sprite sheets: %d", PKMN_ANIM_COUNT); - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes ctx.shm->pmd_anims = platform::make_allocated_mmap_array(PMD_ANIM_COUNT); @@ -636,7 +636,7 @@ created_result_t> create(const config::conf // Load Misc Pets (neko) if (should_load_misc(*ret->anim._local_copy_config)) { BONGOCAT_LOG_INFO("Load Misc sprite sheets: %d", MISC_ANIM_COUNT); - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes ctx.shm->misc_anims = platform::make_allocated_mmap_array(MISC_ANIM_COUNT); @@ -651,7 +651,7 @@ created_result_t> create(const config::conf assert(ret->anim._local_copy_config.ptr); // Load custom sprite sheet if (should_load_custom(*ret->anim._local_copy_config)) { - assert(ret->anim.shm != nullptr); + assert(ret->anim.shm); animation_context_t& ctx = ret->anim; // alias for inits in includes assert(ctx.shm.ptr); assert(ctx._local_copy_config.ptr); diff --git a/src/graphics/bar.cpp b/src/graphics/bar.cpp index f9053b91..cff5372b 100644 --- a/src/graphics/bar.cpp +++ b/src/graphics/bar.cpp @@ -15,8 +15,8 @@ #include namespace bongocat::animation { -inline static uint32_t DEFAULT_FILL_COLOR = 0x00000000; // ARGB -inline static uint32_t DEBUG_MOVEMENT_BAR_COLOR = 0xFFFF0000; // ARGB +inline static constexpr uint32_t DEFAULT_FILL_COLOR = 0x00000000; // ARGB +inline static constexpr uint32_t DEBUG_MOVEMENT_BAR_COLOR = 0xFFFF0000; // ARGB // ============================================================================= // DRAWING MANAGEMENT @@ -52,7 +52,7 @@ cat_rect_t get_position(const platform::wayland::wayland_context_t& wayland_ctx, BONGOCAT_LOG_VERBOSE("Invalid cat_align %d", config.cat_align); break; } - const int cat_y = (wayland_ctx._bar_height - cat_height) / 2 + config.cat_y_offset; + const int cat_y = ((wayland_ctx._bar_height - cat_height) / 2) + config.cat_y_offset; return {.x = cat_x, .y = cat_y, .width = cat_width, .height = cat_height}; } @@ -66,20 +66,20 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; const animation_shared_memory_t& anim_shm = *anim.shm; uint8_t *pixels = shm_buffer.pixels.data; const size_t pixels_size = shm_buffer.pixels._size_bytes; - const sprite_sheet_animation_frame_t *region = nullptr; + const sprite_sheet_animation_frame_t *region = BONGOCAT_NULLPTR; switch (anim_shm.animation_player_result.sprite_sheet_col) { case BONGOCAT_FRAME_BOTH_UP: region = &sheet.both_up; @@ -102,13 +102,13 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - if (region) { + if (region != BONGOCAT_NULLPTR) { // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + if (current_config.enable_movement_debug >= 1 && current_config.movement_radius > 0) { cat_rect_t movement_debug_bar{}; switch (current_config.cat_align) { case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + movement_debug_bar = {.x = cat_x + (cat_width / 2) - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -118,7 +118,7 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; break; case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + movement_debug_bar = {.x = cat_x + cat_width - (current_config.movement_radius * 2), .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -142,8 +142,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w for (int32_t x = movement_debug_bar.x; x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { if (x >= 0 && y >= 0) { - size_t pi = - static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + const size_t pi = + static_cast(x) + (static_cast(y) * static_cast(wayland_ctx._screen_width)); assert(pi < total_pixels); p[pi] = fill; } @@ -152,22 +152,22 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { + if (current_config.invert_color >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); } if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { + if (current_config.mirror_x <= 0) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } else { - if (current_config.mirror_x) { + if (current_config.mirror_x >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } - if (current_config.mirror_y) { + if (current_config.mirror_y >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); } - if (current_config.enable_antialiasing) { + if (current_config.enable_antialiasing >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); } if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { @@ -191,20 +191,20 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; const animation_shared_memory_t& anim_shm = *anim.shm; uint8_t *pixels = shm_buffer.pixels.data; const size_t pixels_size = shm_buffer.pixels._size_bytes; - const sprite_sheet_animation_frame_t *region = nullptr; + const sprite_sheet_animation_frame_t *region = BONGOCAT_NULLPTR; switch (anim_shm.animation_player_result.sprite_sheet_col) { case DM_FRAME_IDLE1: region = &sheet.frames.idle_1; @@ -260,13 +260,13 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - if (region) { + if (region != BONGOCAT_NULLPTR) { // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + if (current_config.enable_movement_debug >= 1 && current_config.movement_radius > 0) { cat_rect_t movement_debug_bar{}; switch (current_config.cat_align) { case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + movement_debug_bar = {.x = cat_x + (cat_width / 2) - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -276,7 +276,7 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; break; case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + movement_debug_bar = {.x = cat_x + cat_width - (current_config.movement_radius * 2), .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -297,8 +297,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w for (int32_t x = movement_debug_bar.x; x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { if (x >= 0 && y >= 0) { - size_t pi = - static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + const size_t pi = + static_cast(x) + (static_cast(y) * static_cast(wayland_ctx._screen_width)); assert(pi < total_pixels); p[pi] = fill; } @@ -307,19 +307,19 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { + if (current_config.invert_color >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); } if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { + if (current_config.mirror_x <= 0) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } else { - if (current_config.mirror_x) { + if (current_config.mirror_x >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } - if (current_config.mirror_y) { + if (current_config.mirror_y >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); } if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { @@ -343,20 +343,20 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; const animation_shared_memory_t& anim_shm = *anim.shm; uint8_t *pixels = shm_buffer.pixels.data; const size_t pixels_size = shm_buffer.pixels._size_bytes; - const sprite_sheet_animation_frame_t *region = nullptr; + const sprite_sheet_animation_frame_t *region = BONGOCAT_NULLPTR; switch (anim_shm.animation_player_result.sprite_sheet_col) { case PKMN_FRAME_IDLE1: region = &sheet.idle_1; @@ -373,13 +373,13 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); - if (region) { + if (region != BONGOCAT_NULLPTR) { // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + if (current_config.enable_movement_debug >= 1 && current_config.movement_radius > 0) { cat_rect_t movement_debug_bar{}; switch (current_config.cat_align) { case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + movement_debug_bar = {.x = cat_x + (cat_width / 2) - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -389,7 +389,7 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; break; case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + movement_debug_bar = {.x = cat_x + cat_width - (current_config.movement_radius * 2), .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -410,8 +410,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w for (int32_t x = movement_debug_bar.x; x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { if (x >= 0 && y >= 0) { - size_t pi = - static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + const size_t pi = + static_cast(x) + (static_cast(y) * static_cast(wayland_ctx._screen_width)); assert(pi < total_pixels); p[pi] = fill; } @@ -420,19 +420,19 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { + if (current_config.invert_color >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); } if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { + if (current_config.mirror_x <= 0) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } else { - if (current_config.mirror_x) { + if (current_config.mirror_x >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } - if (current_config.mirror_y) { + if (current_config.mirror_y >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); } if (extra_drawing_option != blit_image_color_option_flags_t::Normal) { @@ -454,13 +454,13 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; // animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - assert(wayland_ctx._local_copy_config != nullptr); - // assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + // assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; // const animation_shared_memory_t& anim_shm = *anim.shm; @@ -470,16 +470,16 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w auto [cat_x, cat_y, cat_width, cat_height] = get_position(wayland_ctx, sheet, current_config); blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { + if (current_config.invert_color >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); } - if (current_config.mirror_x) { + if (current_config.mirror_x >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } - if (current_config.mirror_y) { + if (current_config.mirror_y >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); } - if (current_config.enable_antialiasing) { + if (current_config.enable_antialiasing >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); } @@ -502,13 +502,13 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; const animation_shared_memory_t& anim_shm = *anim.shm; @@ -519,11 +519,11 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w auto cat_x_with_offset = cat_x + static_cast(anim_shm.movement_offset_x); // draw debug rectangle - if (current_config.enable_movement_debug && current_config.movement_radius > 0) { + if (current_config.enable_movement_debug >= 1 && current_config.movement_radius > 0) { cat_rect_t movement_debug_bar{}; switch (current_config.cat_align) { case config::align_type_t::ALIGN_CENTER: - movement_debug_bar = {.x = cat_x + cat_width / 2 - current_config.movement_radius, + movement_debug_bar = {.x = cat_x + (cat_width / 2) - current_config.movement_radius, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -533,7 +533,7 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w .x = cat_x, .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; break; case config::align_type_t::ALIGN_RIGHT: - movement_debug_bar = {.x = cat_x + cat_width - current_config.movement_radius * 2, + movement_debug_bar = {.x = cat_x + cat_width - (current_config.movement_radius * 2), .y = 0, .width = current_config.movement_radius * 2, .height = wayland_ctx._bar_height}; @@ -554,7 +554,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w for (int32_t x = movement_debug_bar.x; x < movement_debug_bar.x + movement_debug_bar.width && x < wayland_ctx._screen_width; x++) { if (x >= 0 && y >= 0) { - size_t pi = static_cast(x) + static_cast(y) * static_cast(wayland_ctx._screen_width); + const size_t pi = + static_cast(x) + (static_cast(y) * static_cast(wayland_ctx._screen_width)); assert(pi < total_pixels); p[pi] = fill; } @@ -563,23 +564,23 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } blit_image_color_option_flags_t drawing_option = blit_image_color_option_flags_t::Normal; - if (current_config.invert_color) { + if (current_config.invert_color >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::Invert); } - if (current_config.mirror_y) { + if (current_config.mirror_y >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorY); } - if (current_config.enable_antialiasing) { + if (current_config.enable_antialiasing >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::BilinearInterpolation); } switch (overwrite_option) { case draw_sprite_overwrite_option_t::None: if (anim_shm.anim_direction >= 1.0f) { - if (!current_config.mirror_x) { + if (current_config.mirror_x <= 0) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } else { - if (current_config.mirror_x) { + if (current_config.mirror_x >= 1) { drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } } @@ -606,14 +607,14 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer) { - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; // read-only - assert(wayland_ctx._local_copy_config != nullptr); - assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; assert(shm_buffer.pixels.data); @@ -633,7 +634,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, auto *p = reinterpret_cast(pixels); const size_t total_pixels = static_cast(wayland_ctx._screen_width) * static_cast(wayland_ctx._bar_height); - if (current_config.enable_debug) { + if (current_config.enable_debug >= 1) { if (const size_t expected_bytes = total_pixels * sizeof(uint32_t); expected_bytes > pixels_size) { BONGOCAT_LOG_VERBOSE("draw_bar: pixel write would overflow buffer (expected %zu bytes, have %zu). Aborting draw.", expected_bytes, pixels_size); @@ -657,11 +658,11 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.bongocat_anims.count); } const animation_t& cat_anim = get_current_animation(anim); - assert(cat_anim.type == animation_t::Type::Bongocat); + assert(cat_anim.type == animation_t::type_t::Bongocat); const bongocat_sprite_sheet_t& sheet = cat_anim.bongocat; draw_sprite(ctx, shm_buffer, sheet, - current_config.enable_antialiasing ? blit_image_color_option_flags_t::BilinearInterpolation - : blit_image_color_option_flags_t::Normal); + current_config.enable_antialiasing >= 1 ? blit_image_color_option_flags_t::BilinearInterpolation + : blit_image_color_option_flags_t::Normal); } break; case config::config_animation_sprite_sheet_layout_t::Dm: { if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { @@ -695,7 +696,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, } } const animation_t& dm_anim = get_current_animation(anim); - assert(dm_anim.type == animation_t::Type::Dm); + assert(dm_anim.type == animation_t::type_t::Dm); const dm_sprite_sheet_t& sheet = dm_anim.dm; draw_sprite(ctx, shm_buffer, sheet); } break; @@ -704,7 +705,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pkmn_anims.count); } const animation_t& pkmn_anim = get_current_animation(anim); - assert(pkmn_anim.type == animation_t::Type::Pkmn); + assert(pkmn_anim.type == animation_t::type_t::Pkmn); const auto& sheet = pkmn_anim.pkmn; draw_sprite(ctx, shm_buffer, sheet); } break; @@ -713,7 +714,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.ms_anims.count); } const animation_t& ms_anim = get_current_animation(anim); - assert(ms_anim.type == animation_t::Type::MsAgent); + assert(ms_anim.type == animation_t::type_t::MsAgent); const ms_agent_sprite_sheet_t& sheet = ms_anim.ms_agent; const int col = anim_shm.animation_player_result.sprite_sheet_col; const int row = anim_shm.animation_player_result.sprite_sheet_row; @@ -732,7 +733,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.misc_anims.count); } const animation_t& custom_anim = get_current_animation(anim); - assert(custom_anim.type == animation_t::Type::Custom); + assert(custom_anim.type == animation_t::type_t::Custom); const custom_sprite_sheet_t& sheet = custom_anim.custom; draw_sprite(ctx, shm_buffer, sheet, col, row); } @@ -743,7 +744,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, assert(anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) < anim_shm.pmd_anims.count); } const animation_t& custom_anim = get_current_animation(anim); - assert(custom_anim.type == animation_t::Type::Custom); + assert(custom_anim.type == animation_t::type_t::Custom); const custom_sprite_sheet_t& sheet = custom_anim.custom; draw_sprite_overwrite_option_t overwrite_mirror_x{draw_sprite_overwrite_option_t::None}; /* @@ -765,7 +766,7 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, if (features::EnableCustomSpriteSheetsAssets && anim_shm.anim_index >= 0 && static_cast(anim_shm.anim_index) == assets::CUSTOM_ANIM_INDEX) { const animation_t& custom_anim = get_current_animation(anim); - assert(custom_anim.type == animation_t::Type::Custom); + assert(custom_anim.type == animation_t::type_t::Custom); const custom_sprite_sheet_t& sheet = custom_anim.custom; draw_sprite_overwrite_option_t overwrite_mirror_x{draw_sprite_overwrite_option_t::None}; switch (anim_shm.animation_player_result.overwrite_mirror_x) { @@ -800,8 +801,8 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; // read-only - assert(wayland_ctx._local_copy_config != nullptr); - // assert(anim.shm != nullptr); + assert(wayland_ctx._local_copy_config); + // assert(anim.shm); const config::config_t& current_config = *wayland_ctx._local_copy_config.ptr; if (!atomic_load(&wayland_ctx_shm.configured)) { @@ -816,7 +817,7 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { [[maybe_unused]] size_t next_buffer_index = (wayland_ctx_shm.current_buffer_index + 1) % platform::wayland::WAYLAND_NUM_BUFFERS; - platform::wayland::wayland_shm_buffer_t *shm_buffer = nullptr; + platform::wayland::wayland_shm_buffer_t *shm_buffer = BONGOCAT_NULLPTR; if constexpr (platform::wayland::WAYLAND_NUM_BUFFERS == 1) { shm_buffer = &wayland_ctx_shm.buffers[0]; if (atomic_load(&shm_buffer->busy)) { @@ -836,7 +837,7 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { next_buffer_index = (next_buffer_index + 1) % platform::wayland::WAYLAND_NUM_BUFFERS; } - if (!shm_buffer) { + if (shm_buffer == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("draw_bar: All buffers busy, skip drawing"); atomic_store(&wayland_ctx._redraw_after_frame, true); return draw_bar_result_t::Busy; @@ -844,7 +845,7 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { } assert(shm_buffer); - if (!shm_buffer->pixels.data) { + if (!shm_buffer->pixels) { BONGOCAT_LOG_VERBOSE("draw_bar: Config or pixels not ready, skipping draw"); return draw_bar_result_t::Skip; } @@ -865,7 +866,7 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { { platform::LockGuard guard(wayland_ctx._frame_cb_lock); - if (!atomic_load(&wayland_ctx._frame_pending) && !wayland_ctx._frame_cb) { + if (!atomic_load(&wayland_ctx._frame_pending) && wayland_ctx._frame_cb == BONGOCAT_NULLPTR) { wayland_ctx._frame_cb = wl_surface_frame(wayland_ctx.surface); wl_callback_add_listener(wayland_ctx._frame_cb, &platform::wayland::details::frame_listener, &ctx); atomic_store(&wayland_ctx._frame_pending, true); diff --git a/src/graphics/drawing_images.cpp b/src/graphics/drawing_images.cpp index 62aef85c..60c05183 100644 --- a/src/graphics/drawing_images.cpp +++ b/src/graphics/drawing_images.cpp @@ -35,20 +35,25 @@ static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_channels, int dest_i const int db = (dest_order == blit_image_color_order_t::RGBA) ? 2 : 0; // Store without branching - if (dest_channels >= 1) + if (dest_channels >= 1) { dest[dest_idx + dr] = r; - if (dest_channels >= 2) + } + if (dest_channels >= 2) { dest[dest_idx + dg] = g; - if (dest_channels >= 3) + } + if (dest_channels >= 3) { dest[dest_idx + db] = b; - if (dest_channels >= 4) + } + if (dest_channels >= 4) { dest[dest_idx + 3] = a; + } } void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const unsigned char *src, int src_channels, int src_idx, blit_image_color_option_flags_t options, blit_image_color_order_t dest_order, blit_image_color_order_t src_order) { - if (has_flag(options, blit_image_color_option_flags_t::Invisible)) + if (has_flag(options, blit_image_color_option_flags_t::Invisible)) { return; + } const bool invert = has_flag(options, blit_image_color_option_flags_t::Invert); // Map source channel indices for RGB @@ -57,10 +62,13 @@ void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const un const int sb = (src_order == blit_image_color_order_t::RGBA) ? 2 : 0; // Load into RGBA without branches - uint8_t r, g, b, a; + uint8_t r{0}; + uint8_t g{0}; + uint8_t b{0}; + uint8_t a{0}; if (src_channels == 1) { // 1-channel grayscale -> fill all channels with 0/255 - uint8_t v = src[src_idx] ? 255 : 0; + uint8_t v = src[src_idx] > 0 ? 255 : 0; v = apply_invert(v, invert); r = g = b = a = v; } else if (src_channels == 2) { @@ -90,25 +98,31 @@ static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(co int src_w, int src_h, int src_channels, float fx, float fy) { // Clamp coordinates to image bounds - if (fx < 0) + if (fx < 0) { fx = 0; - if (fy < 0) + } + if (fy < 0) { fy = 0; - if (fx >= static_cast(src_w - 1)) + } + if (fx >= static_cast(src_w - 1)) { fx = static_cast(src_w - 1); - if (fy >= static_cast(src_h - 1)) + } + if (fy >= static_cast(src_h - 1)) { fy = static_cast(src_h - 1); + } - int x1 = static_cast(fx); - int y1 = static_cast(fy); + const int x1 = static_cast(fx); + const int y1 = static_cast(fy); int x2 = x1 + 1; int y2 = y1 + 1; // Clamp to bounds - if (x2 >= src_w) + if (x2 >= src_w) { x2 = src_w - 1; - if (y2 >= src_h) + } + if (y2 >= src_h) { y2 = src_h - 1; + } const float dx = fx - static_cast(x1); const float dy = fy - static_cast(y1); @@ -132,9 +146,9 @@ static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(co const size_t br_idx_c = static_cast(idx_br) + static_cast(c); if (tl_idx_c < src_size && tr_idx_c < src_size && bl_idx_c < src_size && br_idx_c < src_size) { - float top = static_cast(src[tl_idx_c]) * (1.0f - dx) + static_cast(src[tr_idx_c]) * dx; - float bottom = static_cast(src[bl_idx_c]) * (1.0f - dx) + static_cast(src[br_idx_c]) * dx; - float result = top * (1.0f - dy) + bottom * dy; + const float top = static_cast(src[tl_idx_c]) * (1.0f - dx) + (static_cast(src[tr_idx_c]) * dx); + const float bottom = static_cast(src[bl_idx_c]) * (1.0f - dx) + (static_cast(src[br_idx_c]) * dx); + const float result = top * (1.0f - dy) + bottom * dy; switch (c) { case 0: @@ -162,18 +176,24 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int src_y, int frame_w, int frame_h, int offset_x, int offset_y, int target_w, int target_h, blit_image_color_order_t dest_order, blit_image_color_order_t src_order, blit_image_color_option_flags_t options) { - if (!dest || !src) + if (dest == BONGOCAT_NULLPTR || src == BONGOCAT_NULLPTR) { return; - if (dest_w <= 0 || dest_h <= 0 || src_w <= 0 || src_h <= 0) + } + if (dest_w <= 0 || dest_h <= 0 || src_w <= 0 || src_h <= 0) { return; - if (dest_channels <= 0 || src_channels <= 0) + } + if (dest_channels <= 0 || src_channels <= 0) { return; - if (target_w <= 0 || target_h <= 0) + } + if (target_w <= 0 || target_h <= 0) { return; - if (frame_w <= 0 || frame_h <= 0) + } + if (frame_w <= 0 || frame_h <= 0) { return; - if (has_flag(options, blit_image_color_option_flags_t::Invisible)) + } + if (has_flag(options, blit_image_color_option_flags_t::Invisible)) { return; + } assert(dest_w >= 0); assert(dest_h >= 0); @@ -185,8 +205,9 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, const size_t needed_dest = static_cast(dest_w) * static_cast(dest_h) * static_cast(dest_channels); const size_t needed_src = static_cast(src_w) * static_cast(src_h) * static_cast(src_channels); - if (dest_size < needed_dest || src_size < needed_src) + if (dest_size < needed_dest || src_size < needed_src) { return; + } // Clip destination rectangle const int dst_left = offset_x; @@ -196,21 +217,27 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int x0 = 0; int x1 = target_w; - if (dst_left < 0) + if (dst_left < 0) { x0 = -dst_left; - if (dst_right > dest_w) + } + if (dst_right > dest_w) { x1 = target_w - (dst_right - dest_w); - if (x0 >= x1) + } + if (x0 >= x1) { return; + } int y0 = 0; int y1 = target_h; - if (dst_top < 0) + if (dst_top < 0) { y0 = -dst_top; - if (dst_bottom > dest_h) + } + if (dst_bottom > dest_h) { y1 = target_h - (dst_bottom - dest_h); - if (y0 >= y1) + } + if (y0 >= y1) { return; + } // Fixed-point increments assert(target_w > 0); @@ -240,30 +267,31 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, const int dy = offset_y + ty; assert(dy < dest_h); - int32_t sy_fixed = src_y_start + static_cast(static_cast(ty) * inc_y); + const int32_t sy_fixed = src_y_start + static_cast(static_cast(ty) * inc_y); const int sy = sy_fixed >> FIXED_SHIFT; - if (static_cast(sy) >= static_cast(src_h)) + if (static_cast(sy) >= static_cast(src_h)) { continue; + } - uint8_t *dest_row = dest + static_cast(dy) * dest_row_bytes; - const unsigned char *src_row = src + static_cast(sy) * src_row_bytes; + const uint8_t *dest_row = dest + (static_cast(dy) * dest_row_bytes); + const unsigned char *src_row = src + (static_cast(sy) * src_row_bytes); int32_t sx_fixed = src_x_start + static_cast(static_cast(x0) * inc_x); - uint8_t *dest_ptr = dest_row + static_cast(offset_x + x0) * static_cast(dest_channels); + const uint8_t *dest_ptr = dest_row + static_cast(offset_x + x0) * static_cast(dest_channels); for (int tx = x0; tx < x1; ++tx) { const int sx = sx_fixed >> FIXED_SHIFT; if (static_cast(sx) < static_cast(src_w)) { - const uint8_t *src_pixel = src_row + static_cast(sx) * static_cast(src_channels); - int dest_idx = static_cast(dest_ptr - dest); - int src_idx = static_cast(src_pixel - src); + const uint8_t *src_pixel = src_row + (static_cast(sx) * static_cast(src_channels)); + const int dest_idx = static_cast(dest_ptr - dest); + const int src_idx = static_cast(src_pixel - src); if (use_bilinear_interpolation) { // Use bilinear interpolation for smooth scaling - float fx = static_cast(sx_fixed) / static_cast(1 << FIXED_SHIFT); - float fy = static_cast(sy_fixed) / static_cast(1 << FIXED_SHIFT); + const float fx = static_cast(sx_fixed) / static_cast(1 << FIXED_SHIFT); + const float fy = static_cast(sy_fixed) / static_cast(1 << FIXED_SHIFT); auto pixel = drawing_get_interpolated_pixel(src, src_size, src_w, src_h, src_channels, fx, fy); if (src_channels >= 4) { diff --git a/src/image_loader/bongocat/load_images_bongocat.cpp b/src/image_loader/bongocat/load_images_bongocat.cpp index 56895430..64043402 100644 --- a/src/image_loader/bongocat/load_images_bongocat.cpp +++ b/src/image_loader/bongocat/load_images_bongocat.cpp @@ -119,7 +119,7 @@ bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, ge assert(anim_index >= 0); ctx.shm->bongocat_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->bongocat_anims[static_cast(anim_index)].type == animation_t::Type::Bongocat); + assert(ctx.shm->bongocat_anims[static_cast(anim_index)].type == animation_t::type_t::Bongocat); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/custom/load_custom.cpp b/src/image_loader/custom/load_custom.cpp index f4ddf1bf..9e16d44f 100644 --- a/src/image_loader/custom/load_custom.cpp +++ b/src/image_loader/custom/load_custom.cpp @@ -21,9 +21,10 @@ created_result_t load_custom_sprite_sheet_file(const cha } void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept { platform::release_allocated_mmap_file_content(image.data); - if (image.name) + if (image.name != BONGOCAT_NULLPTR) { ::free(image.name); - image.name = nullptr; + image.name = BONGOCAT_NULLPTR; + } } created_result_t @@ -33,7 +34,7 @@ load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& s return load_custom_anim(ctx, assets::embedded_image_t{.data = sprite_sheet_image.data.data, .size = static_cast(sprite_sheet_image.data._size_bytes), - .name = sprite_sheet_image.name ? sprite_sheet_image.name : ""}, + .name = sprite_sheet_image.name != BONGOCAT_NULLPTR ? sprite_sheet_image.name : ""}, sprite_sheet_settings); } created_result_t diff --git a/src/image_loader/dm/load_images_dm.cpp b/src/image_loader/dm/load_images_dm.cpp index 84ef43aa..f3950a1c 100644 --- a/src/image_loader/dm/load_images_dm.cpp +++ b/src/image_loader/dm/load_images_dm.cpp @@ -21,7 +21,7 @@ bongocat_error_t init_dm_anim(animation_context_t& ctx, int anim_index, const as assert(anim_index >= 0); ctx.shm->dm_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->dm_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->dm_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/dm20/load_images_dm20.cpp b/src/image_loader/dm20/load_images_dm20.cpp index 05813d07..0b97b310 100644 --- a/src/image_loader/dm20/load_images_dm20.cpp +++ b/src/image_loader/dm20/load_images_dm20.cpp @@ -21,7 +21,7 @@ namespace bongocat::animation { assert(anim_index >= 0); ctx.shm->dm20_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->dm20_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->dm20_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/dmall/load_images_dmall.cpp b/src/image_loader/dmall/load_images_dmall.cpp index 5393ed14..15981e09 100644 --- a/src/image_loader/dmall/load_images_dmall.cpp +++ b/src/image_loader/dmall/load_images_dmall.cpp @@ -21,7 +21,7 @@ namespace bongocat::animation { assert(anim_index >= 0); ctx.shm->dmall_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->dmall_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->dmall_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/dmc/load_images_dmc.cpp b/src/image_loader/dmc/load_images_dmc.cpp index d219179c..e53db2dd 100644 --- a/src/image_loader/dmc/load_images_dmc.cpp +++ b/src/image_loader/dmc/load_images_dmc.cpp @@ -21,7 +21,7 @@ namespace bongocat::animation { assert(anim_index >= 0); ctx.shm->dmc_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->dmc_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->dmc_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/dmx/load_images_dmx.cpp b/src/image_loader/dmx/load_images_dmx.cpp index 092f5106..55906f3f 100644 --- a/src/image_loader/dmx/load_images_dmx.cpp +++ b/src/image_loader/dmx/load_images_dmx.cpp @@ -22,7 +22,7 @@ namespace bongocat::animation { assert(anim_index >= 0); ctx.shm->dmx_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->dmx_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->dmx_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/load_images.cpp b/src/image_loader/load_images.cpp index c1c8ecf2..959912ba 100644 --- a/src/image_loader/load_images.cpp +++ b/src/image_loader/load_images.cpp @@ -37,8 +37,8 @@ load_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite_data_siz const auto frame_height = sprite_sheet.height / frame_rows; const auto total_frames = frame_columns * frame_rows; - const auto dest_frame_width = frame_width + padding_x * 2; - const auto dest_frame_height = frame_height + padding_y * 2; + const auto dest_frame_width = frame_width + (padding_x * 2); + const auto dest_frame_height = frame_height + (padding_y * 2); const auto dest_pixels_width = dest_frame_width * frame_columns; const auto dest_pixels_height = dest_frame_height * frame_rows; assert(dest_pixels_width >= 0); @@ -67,8 +67,8 @@ load_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite_data_siz for (int col = 0; col < frame_columns; ++col) { const auto src_x = col * src_frame_width; const auto src_y = row * src_frame_height; - const auto dst_x = col * dest_frame_width + padding_x; - const auto dst_y = row * dest_frame_height + padding_y; + const auto dst_x = (col * dest_frame_width) + padding_x; + const auto dst_y = (row * dest_frame_height) + padding_y; [[maybe_unused]] const auto src_idx = (src_y * src_pixels_width + src_x) * sprite_sheet.channels; [[maybe_unused]] const auto dst_idx = (dst_y * dest_pixels_width + dst_x) * sprite_sheet.channels; assert(src_idx >= 0); @@ -112,7 +112,7 @@ load_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite_data_siz ret.image.channels = sprite_sheet.channels; // move pixels ownership into out_frames ret.image.pixels = bongocat::move(dest_pixels); - dest_pixels = nullptr; + dest_pixels = BONGOCAT_NULLPTR; ret.frame_width = dest_frame_width; ret.frame_height = dest_frame_height; ret.total_frames = total_frames; @@ -174,9 +174,10 @@ created_result_t anim_sprite_sheet_from_embedded_images( ret.image.channels = 0; for (size_t i = 0; i < loaded_images.count; i++) { - if (loaded_images[i].pixels) + if (loaded_images[i].pixels != BONGOCAT_NULLPTR) { ::free(loaded_images[i].pixels); - loaded_images[i].pixels = nullptr; + loaded_images[i].pixels = BONGOCAT_NULLPTR; + } } return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } @@ -194,11 +195,11 @@ created_result_t anim_sprite_sheet_from_embedded_images( assert(src.channels >= 0); assert(src.width >= 0); assert(src.height >= 0); - if (src.pixels && src.height > 0) { + if (src.pixels != BONGOCAT_NULLPTR && src.height > 0) { // copy pixel data of sub-region assert(src.height >= 0); for (size_t y = 0; y < static_cast(src.height); y++) { - unsigned char *dest_row = ret.image.pixels.data + ((y) * static_cast(ret.image.sprite_sheet_width) + + unsigned char *dest_row = ret.image.pixels.data + ((y * static_cast(ret.image.sprite_sheet_width)) + (frame * static_cast(max_frame_width))) * static_cast(max_channels); const unsigned char *src_row = @@ -218,9 +219,10 @@ created_result_t anim_sprite_sheet_from_embedded_images( } for (size_t i = 0; i < loaded_images.count; i++) { - if (loaded_images[i].pixels) + if (loaded_images[i].pixels != BONGOCAT_NULLPTR) { ::free(loaded_images[i].pixels); - loaded_images[i].pixels = nullptr; + loaded_images[i].pixels = BONGOCAT_NULLPTR; + } } return ret; } diff --git a/src/image_loader/load_images_hybrid.cpp b/src/image_loader/load_images_hybrid.cpp index 735d91f8..a5fe4d3b 100644 --- a/src/image_loader/load_images_hybrid.cpp +++ b/src/image_loader/load_images_hybrid.cpp @@ -47,17 +47,17 @@ #endif namespace bongocat::animation { -inline static constexpr size_t HybridImageBackendPngleThresholdBytes = 192 * 1024; // 192kb +inline static constexpr size_t HybridImageBackendPngleThresholdBytes = 192zu * 1024zu; // 192kb struct decode_state_t { - Image *image{nullptr}; + Image *image{BONGOCAT_NULLPTR}; int desired_channels{RGBA_CHANNELS}; }; BONGOCAT_NODISCARD static created_result_t load_image_pngle(const unsigned char *data, size_t size, int desired_channels) { Image ret; pngle_t *pngle = pngle_new(); - if (!pngle) { + if (pngle == BONGOCAT_NULLPTR) { return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } @@ -75,10 +75,11 @@ BONGOCAT_NODISCARD static created_result_t load_image_pngle(const unsigne img->width = static_cast(pngle_get_width(p_pngle)); img->height = static_cast(pngle_get_height(p_pngle)); img->channels = channels; // pngle always gives RGBA - size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; + const size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; img->pixels = static_cast(::malloc(buf_size)); - if (!img->pixels) + if (!img->pixels) { return; + } } unsigned char *dst = &img->pixels[(y * pngle_get_width(p_pngle) + x) * channels]; @@ -94,15 +95,18 @@ BONGOCAT_NODISCARD static created_result_t load_image_pngle(const unsigne const int fed = pngle_feed(pngle, data, size); if (fed < 0) { pngle_destroy(pngle); - if (ret.pixels) + pngle = BONGOCAT_NULLPTR; + if (ret.pixels != BONGOCAT_NULLPTR) { ::free(ret.pixels); - ret.pixels = nullptr; + ret.pixels = BONGOCAT_NULLPTR; + } return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } pngle_destroy(pngle); + pngle = BONGOCAT_NULLPTR; - if (!ret.pixels) { + if (ret.pixels == BONGOCAT_NULLPTR) { return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } @@ -125,8 +129,8 @@ BONGOCAT_NODISCARD static created_result_t load_image_stb_image(const uns int channels_in_file; ret.pixels = stbi_load_from_memory(data, static_cast(size), &ret.width, &ret.height, &channels_in_file, desired_channels); - if (ret.pixels == nullptr) { - ret.pixels = nullptr; + if (ret.pixels == BONGOCAT_NULLPTR) { + ret.pixels = BONGOCAT_NULLPTR; return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } assert(ret.width > 0); @@ -145,9 +149,10 @@ created_result_t load_image(const unsigned char *data, size_t size, int d } void cleanup_image(Image& image) { - if (image.pixels) + if (image.pixels != BONGOCAT_NULLPTR) { ::free(image.pixels); - image.pixels = nullptr; + image.pixels = BONGOCAT_NULLPTR; + } } void init_image_loader() {} diff --git a/src/image_loader/load_images_pngle.cpp b/src/image_loader/load_images_pngle.cpp index 7713801f..c2711d6a 100644 --- a/src/image_loader/load_images_pngle.cpp +++ b/src/image_loader/load_images_pngle.cpp @@ -27,13 +27,13 @@ namespace bongocat::animation { struct decode_state_t { - Image *image{nullptr}; + Image *image{BONGOCAT_NULLPTR}; int desired_channels{RGBA_CHANNELS}; }; created_result_t load_image(const unsigned char *data, size_t size, int desired_channels) { Image ret; pngle_t *pngle = pngle_new(); - if (!pngle) { + if (pngle == BONGOCAT_NULLPTR) { return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } @@ -51,10 +51,11 @@ created_result_t load_image(const unsigned char *data, size_t size, int d img->width = static_cast(pngle_get_width(p_pngle)); img->height = static_cast(pngle_get_height(p_pngle)); img->channels = channels; // pngle always gives RGBA - size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; + const size_t buf_size = pngle_get_width(p_pngle) * pngle_get_height(p_pngle) * channels; img->pixels = static_cast(::malloc(buf_size)); - if (!img->pixels) + if (img->pixels == BONGOCAT_NULLPTR) { return; + } } unsigned char *dst = &img->pixels[(y * pngle_get_width(p_pngle) + x) * channels]; @@ -70,15 +71,18 @@ created_result_t load_image(const unsigned char *data, size_t size, int d const int fed = pngle_feed(pngle, data, size); if (fed < 0) { pngle_destroy(pngle); - if (ret.pixels) + pngle = BONGOCAT_NULLPTR; + if (ret.pixels != BONGOCAT_NULLPTR) { ::free(ret.pixels); - ret.pixels = nullptr; + ret.pixels = BONGOCAT_NULLPTR; + } return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } pngle_destroy(pngle); + pngle = BONGOCAT_NULLPTR; - if (!ret.pixels) { + if (ret.pixels == BONGOCAT_NULLPTR) { return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } @@ -95,9 +99,10 @@ created_result_t load_image(const unsigned char *data, size_t size, int d } void cleanup_image(Image& image) { - if (image.pixels) + if (image.pixels != BONGOCAT_NULLPTR) { ::free(image.pixels); - image.pixels = nullptr; + image.pixels = BONGOCAT_NULLPTR; + } } void init_image_loader() {} diff --git a/src/image_loader/load_images_stb_image.cpp b/src/image_loader/load_images_stb_image.cpp index f521e92b..6c775363 100644 --- a/src/image_loader/load_images_stb_image.cpp +++ b/src/image_loader/load_images_stb_image.cpp @@ -27,8 +27,8 @@ namespace bongocat::animation { assert(size <= INT_MAX); int channels_in_file; ret.pixels = stbi_load_from_memory(data, static_cast(size), &ret.width, &ret.height, &channels_in_file, desired_channels); - if (ret.pixels == nullptr) [[unlikely]] { - ret.pixels = nullptr; + if (ret.pixels == BONGOCAT_NULLPTR) [[unlikely]] { + ret.pixels = BONGOCAT_NULLPTR; return bongocat_error_t::BONGOCAT_ERROR_IMAGE; } assert(ret.width > 0); @@ -39,8 +39,10 @@ namespace bongocat::animation { } void cleanup_image(Image& image) { - if (image.pixels) stbi_image_free(image.pixels); - image.pixels = nullptr; + if (image.pixels) { + stbi_image_free(image.pixels); + } + image.pixels = BONGOCAT_NULLPTR; } void init_image_loader() { diff --git a/src/image_loader/min_dm/load_images_min_dm.cpp b/src/image_loader/min_dm/load_images_min_dm.cpp index 90ce94da..e2fbcd9c 100644 --- a/src/image_loader/min_dm/load_images_min_dm.cpp +++ b/src/image_loader/min_dm/load_images_min_dm.cpp @@ -24,7 +24,7 @@ bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, assert(anim_index >= 0); ctx.shm->min_dm_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->min_dm_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->min_dm_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/misc/load_images_misc.cpp b/src/image_loader/misc/load_images_misc.cpp index 07fb6c7a..df93da54 100644 --- a/src/image_loader/misc/load_images_misc.cpp +++ b/src/image_loader/misc/load_images_misc.cpp @@ -23,7 +23,7 @@ bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, assert(anim_index >= 0); ctx.shm->misc_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->misc_anims[static_cast(anim_index)].type == animation_t::Type::Custom); + assert(ctx.shm->misc_anims[static_cast(anim_index)].type == animation_t::type_t::Custom); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/ms_agent/load_images_ms_agent.cpp b/src/image_loader/ms_agent/load_images_ms_agent.cpp index 04168254..044923b9 100644 --- a/src/image_loader/ms_agent/load_images_ms_agent.cpp +++ b/src/image_loader/ms_agent/load_images_ms_agent.cpp @@ -39,8 +39,8 @@ load_ms_agent_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite } */ - const auto dest_frame_width = frame_width + padding_x * 2; - const auto dest_frame_height = frame_height + padding_y * 2; + const auto dest_frame_width = frame_width + (padding_x * 2); + const auto dest_frame_height = frame_height + (padding_y * 2); const auto dest_pixels_width = dest_frame_width * frame_columns; const auto dest_pixels_height = dest_frame_height * frame_rows; assert(dest_pixels_width >= 0); @@ -69,8 +69,8 @@ load_ms_agent_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite for (int col = 0; col < frame_columns; ++col) { const auto src_x = col * src_frame_width; const auto src_y = row * src_frame_height; - const auto dst_x = col * dest_frame_width + padding_x; - const auto dst_y = row * dest_frame_height + padding_y; + const auto dst_x = (col * dest_frame_width) + padding_x; + const auto dst_y = (row * dest_frame_height) + padding_y; [[maybe_unused]] const auto src_idx = (src_y * src_pixels_width + src_x) * sprite_sheet.channels; [[maybe_unused]] const auto dst_idx = (dst_y * dest_pixels_width + dst_x) * sprite_sheet.channels; assert(src_idx >= 0); @@ -103,7 +103,7 @@ load_ms_agent_sprite_sheet_from_memory(const uint8_t *sprite_data, size_t sprite ret.image.channels = sprite_sheet.channels; // move pixels ownership into out_frames ret.image.pixels = move(dest_pixels); - dest_pixels = nullptr; + dest_pixels = BONGOCAT_NULLPTR; ret.frame_width = dest_frame_width; ret.frame_height = dest_frame_height; @@ -262,7 +262,7 @@ init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embed assert(anim_index >= 0); ctx.shm->ms_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->ms_anims[static_cast(anim_index)].type == animation_t::Type::MsAgent); + assert(ctx.shm->ms_anims[static_cast(anim_index)].type == animation_t::type_t::MsAgent); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/pen/load_images_pen.cpp b/src/image_loader/pen/load_images_pen.cpp index b8d74ba5..9e3fcba6 100644 --- a/src/image_loader/pen/load_images_pen.cpp +++ b/src/image_loader/pen/load_images_pen.cpp @@ -21,7 +21,7 @@ bongocat_error_t init_pen_anim(animation_context_t& ctx, int anim_index, const a assert(anim_index >= 0); ctx.shm->pen_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->pen_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->pen_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/pen20/load_images_pen20.cpp b/src/image_loader/pen20/load_images_pen20.cpp index a089b1ce..53dd767e 100644 --- a/src/image_loader/pen20/load_images_pen20.cpp +++ b/src/image_loader/pen20/load_images_pen20.cpp @@ -21,7 +21,7 @@ bongocat_error_t init_pen20_anim(animation_context_t& ctx, int anim_index, const assert(anim_index >= 0); ctx.shm->pen20_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->pen20_anims[static_cast(anim_index)].type == animation_t::Type::Dm); + assert(ctx.shm->pen20_anims[static_cast(anim_index)].type == animation_t::type_t::Dm); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/pkmn/load_images_pkmn.cpp b/src/image_loader/pkmn/load_images_pkmn.cpp index 15f01fe6..7f18fd4e 100644 --- a/src/image_loader/pkmn/load_images_pkmn.cpp +++ b/src/image_loader/pkmn/load_images_pkmn.cpp @@ -105,7 +105,7 @@ namespace bongocat::animation { assert(anim_index >= 0); ctx.shm->pkmn_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->pkmn_anims[static_cast(anim_index)].type == animation_t::Type::Pkmn); + assert(ctx.shm->pkmn_anims[static_cast(anim_index)].type == animation_t::type_t::Pkmn); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/image_loader/pmd/load_images_pmd.cpp b/src/image_loader/pmd/load_images_pmd.cpp index 6072e5f6..dbcf0996 100644 --- a/src/image_loader/pmd/load_images_pmd.cpp +++ b/src/image_loader/pmd/load_images_pmd.cpp @@ -20,7 +20,7 @@ namespace bongocat::animation { assert(anim_index >= 0); ctx.shm->pmd_anims[static_cast(anim_index)] = bongocat::move(result.result); - assert(ctx.shm->pmd_anims[static_cast(anim_index)].type == animation_t::Type::Custom); + assert(ctx.shm->pmd_anims[static_cast(anim_index)].type == animation_t::type_t::Custom); return bongocat_error_t::BONGOCAT_SUCCESS; } diff --git a/src/platform/input.cpp b/src/platform/input.cpp index 5863c72d..f08b2ce5 100644 --- a/src/platform/input.cpp +++ b/src/platform/input.cpp @@ -62,7 +62,7 @@ inline static constexpr size_t TEST_STDIN_BUF_LEN = 256; static input_hand_mapping_t get_hand_mapping_form_keycode([[maybe_unused]] const input_context_t& input, int keycode) { // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); // const config::config_t& current_config = *input._local_copy_config; for (size_t i = 0; i < LEN_ARRAY(INPUT_LEFT_KEYS); i++) { @@ -75,9 +75,10 @@ static input_hand_mapping_t get_hand_mapping_form_keycode([[maybe_unused]] const static void cleanup_input_devices_paths(input_context_t& input, size_t device_paths_count) { for (size_t i = 0; i < device_paths_count; i++) { - if (input._device_paths[i]) + if (input._device_paths[i] != BONGOCAT_NULLPTR) { ::free(input._device_paths[i]); - input._device_paths[i] = nullptr; + input._device_paths[i] = BONGOCAT_NULLPTR; + } } release_allocated_array(input._device_paths); } @@ -88,18 +89,20 @@ static void cleanup_input_thread_context(input_context_t& input) { release_allocated_array(input._unique_paths_indices); input._unique_paths_indices_capacity = 0; release_allocated_array(input._unique_devices); - if (input._udev_mon) + if (input._udev_mon != BONGOCAT_NULLPTR) { udev_monitor_unref(input._udev_mon); - if (input._udev) + input._udev_mon = BONGOCAT_NULLPTR; + } + if (input._udev != BONGOCAT_NULLPTR) { udev_unref(input._udev); - input._udev_mon = nullptr; - input._udev = nullptr; + input._udev = BONGOCAT_NULLPTR; + } input._udev_fd = -1; } static void cleanup_input_thread(void *arg) { assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); + const animation::animation_session_t& trigger_ctx = *static_cast(arg); assert(trigger_ctx._input); input_context_t& input = *trigger_ctx._input; @@ -126,7 +129,7 @@ inline static void trigger_key_press(animation::animation_session_t& trigger_ctx input_context_t& input = *trigger_ctx._input; // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); const config::config_t& current_config = *input._local_copy_config; int timeout = INPUT_POOL_TIMEOUT_MS; @@ -138,7 +141,7 @@ inline static void trigger_key_press(animation::animation_session_t& trigger_ctx const timestamp_ms_t now = get_current_time_ms(); const time_ms_t duration_ms = now - input._latest_kpm_update_ms; - time_ms_t min_key_press_check_time_ms = timeout * 2; + time_ms_t min_key_press_check_time_ms = timeout * 2L; if (current_config.input_fps > 0) { min_key_press_check_time_ms = 2000 / current_config.input_fps; } else if (current_config.fps > 0) { @@ -162,7 +165,7 @@ inline static void trigger_key_press(animation::animation_session_t& trigger_ctx input.shm->last_key_pressed_timestamp = now; atomic_fetch_add(&input.shm->input_counter, 1); atomic_fetch_add(&input._input_kpm_counter, 1); - if (current_config.enable_hand_mapping && keycode != 0) { + if (current_config.enable_hand_mapping >= 1 && keycode != 0) { input.shm->hand_mapping = get_hand_mapping_form_keycode(input, keycode); } else { input.shm->hand_mapping = input_hand_mapping_t::None; @@ -177,7 +180,7 @@ static FileDescriptor open_tty_nonblocking() { BONGOCAT_LOG_ERROR("dup stdin"); return FileDescriptor(fd); } - int flags = fcntl(fd, F_GETFL, 0); + const int flags = fcntl(fd, F_GETFL, 0); if (flags < 0) { BONGOCAT_LOG_ERROR("fcntl getfl"); close(fd); @@ -204,7 +207,7 @@ struct sync_devices_options_result_t { }; BONGOCAT_NODISCARD static created_result_t sync_devices(input_context_t& input, sync_devices_options_t options = {}) { - assert(input.shm != nullptr); + assert(input.shm); size_t valid_devices = 0; // Ensure buffer size @@ -238,13 +241,13 @@ sync_devices(input_context_t& input, sync_devices_options_t options = {}) { // Resolve to canonical path char resolved[PATH_MAX]; - const char *candidate = nullptr; + const char *candidate = BONGOCAT_NULLPTR; input_unique_file_type_t new_type = input_unique_file_type_t::File; struct stat lst{}; if (lstat(device_path, &lst) == 0 && S_ISLNK(lst.st_mode)) { new_type = input_unique_file_type_t::Symlink; - if (realpath(device_path, resolved)) { + if (realpath(device_path, resolved) != BONGOCAT_NULLPTR) { candidate = resolved; } else { BONGOCAT_LOG_WARNING("Broken symlink: %s", device_path); @@ -266,13 +269,14 @@ sync_devices(input_context_t& input, sync_devices_options_t options = {}) { bool duplicate = false; for (size_t j = 0; j < num_unique_devices; j++) { const input_unique_file_t& prev = input._unique_devices[j]; - if (prev.canonical_path && strcmp(prev.canonical_path, candidate) == 0) { + if (prev.canonical_path != BONGOCAT_NULLPTR && strcmp(prev.canonical_path, candidate) == 0) { duplicate = true; break; } } - if (duplicate) + if (duplicate) { continue; + } input_unique_file_t& cur = input._unique_devices[num_unique_devices]; input._unique_paths_indices[num_unique_devices] = i; @@ -280,11 +284,11 @@ sync_devices(input_context_t& input, sync_devices_options_t options = {}) { // Decide if we need to replace real_device_path bool need_reopen = false; bool need_replace = false; - if (cur.canonical_path) { + if (cur.canonical_path != BONGOCAT_NULLPTR) { need_replace = strcmp(cur.canonical_path, candidate) != 0; } if (static_cast(cur.canonical_path) != static_cast(candidate)) { - need_reopen = !cur.canonical_path && candidate; + need_reopen = cur.canonical_path == BONGOCAT_NULLPTR && candidate != BONGOCAT_NULLPTR; need_replace = true; } if (!need_reopen && cur.type != new_type) { @@ -313,8 +317,10 @@ sync_devices(input_context_t& input, sync_devices_options_t options = {}) { if (cur.fd._fd >= 0 && is_open_device_valid(cur.fd._fd)) { // Replace canonical path if changed if (need_replace) { - if (cur.canonical_path) + if (cur.canonical_path != BONGOCAT_NULLPTR) { ::free(cur.canonical_path); + cur.canonical_path = BONGOCAT_NULLPTR; + } cur.canonical_path = ::strdup(candidate); } cur.type = new_type; @@ -348,30 +354,32 @@ sync_devices(input_context_t& input, sync_devices_options_t options = {}) { } static bongocat_error_t setup_udev_monitor(input_context_t& input) { - if (input._udev_mon) + if (input._udev_mon != BONGOCAT_NULLPTR) { udev_monitor_unref(input._udev_mon); - if (input._udev) + input._udev_mon = BONGOCAT_NULLPTR; + } + if (input._udev != BONGOCAT_NULLPTR) { udev_unref(input._udev); - input._udev_mon = nullptr; - input._udev = nullptr; + input._udev = BONGOCAT_NULLPTR; + } input._udev_fd = -1; input._udev = udev_new(); - if (!input._udev) { + if (input._udev == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to init udev\n"); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } input._udev_mon = udev_monitor_new_from_netlink(input._udev, "udev"); - if (!input._udev_mon) { + if (input._udev_mon == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to create udev monitor\n"); udev_unref(input._udev); - input._udev = nullptr; + input._udev = BONGOCAT_NULLPTR; return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } // only care about input subsystem - udev_monitor_filter_add_match_subsystem_devtype(input._udev_mon, "input", nullptr); + udev_monitor_filter_add_match_subsystem_devtype(input._udev_mon, "input", BONGOCAT_NULLPTR); udev_monitor_enable_receiving(input._udev_mon); input._udev_fd = udev_monitor_get_fd(input._udev_mon); @@ -387,36 +395,36 @@ static void *input_thread(void *arg) { // wait for input context (in animation start) trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - assert(trigger_ctx._input != nullptr); + assert(trigger_ctx._input != BONGOCAT_NULLPTR); input_context_t& input = *trigger_ctx._input; // sanity checks - assert(input._config != nullptr); - assert(input._configs_reloaded_cond != nullptr); + assert(input._config != BONGOCAT_NULLPTR); + assert(input._configs_reloaded_cond != BONGOCAT_NULLPTR); assert(!input._capture_input_running); - assert(input.shm != nullptr); - assert(input._local_copy_config != nullptr); + assert(input.shm); + assert(input._local_copy_config); assert(input.update_config_efd._fd >= 0); // keep local copies of device_paths { // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); const config::config_t& current_config = *input._local_copy_config; assert(current_config.num_keyboard_devices >= 0); - int device_paths_count = current_config.num_keyboard_devices; + const int device_paths_count = current_config.num_keyboard_devices; const char *const *device_paths = current_config.keyboard_devices; assert(device_paths_count >= 0); input._device_paths = make_allocated_array(static_cast(device_paths_count)); for (size_t i = 0; i < input._device_paths.count; i++) { input._device_paths[i] = strdup(device_paths[i]); - if (!input._device_paths[i]) { + if (input._device_paths[i] == BONGOCAT_NULLPTR) { atomic_store(&input._capture_input_running, false); cleanup_input_devices_paths(input, i); cleanup_input_thread_context(input); BONGOCAT_LOG_ERROR("input: Failed to allocate memory for device_paths"); - return nullptr; + return BONGOCAT_NULLPTR; } } } @@ -432,7 +440,7 @@ static void *input_thread(void *arg) { atomic_store(&input._capture_input_running, false); cleanup_input_thread_context(input); BONGOCAT_LOG_ERROR("input: Failed to init devices and file descriptors"); - return nullptr; + return BONGOCAT_NULLPTR; } [[maybe_unused]] const auto t1 = platform::get_current_time_us(); @@ -485,10 +493,10 @@ static void *input_thread(void *arg) { bool enable_debug = false; { // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); const config::config_t& current_config = *input._local_copy_config; - enable_debug = current_config.enable_debug; + enable_debug = current_config.enable_debug >= 1; if (current_config.input_fps > 0) { timeout = 1000 / current_config.input_fps; @@ -505,7 +513,7 @@ static void *input_thread(void *arg) { pfds[fds_udev_monitor_index] = {.fd = input._udev_fd, .events = POLLIN, .revents = 0}; assert(fds_device_potential_start_index < SSIZE_MAX); - ssize_t fds_device_start_index = + const ssize_t fds_device_start_index = input._unique_devices.count > 0 ? static_cast(fds_device_potential_start_index) : -1; ssize_t fds_device_end_index = input._unique_devices.count > 0 ? fds_device_start_index : -1; nfds_t nfds = fds_device_potential_start_index; @@ -549,7 +557,7 @@ static void *input_thread(void *arg) { } { // read-only config - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); const config::config_t& current_config = *input._local_copy_config; if (device_nfds > MAX_DEVICE_FDS) { if (current_config._strict) { @@ -674,8 +682,9 @@ static void *input_thread(void *arg) { // poll events const int poll_result = poll(pfds, nfds, timeout); if (poll_result < 0) { - if (errno == EINTR) + if (errno == EINTR) { continue; // Interrupted by signal + } BONGOCAT_LOG_ERROR("input: Poll error: %s", strerror(errno)); break; } @@ -688,8 +697,9 @@ static void *input_thread(void *arg) { for (size_t i = 0; i < input._unique_devices.count; i++) { const char *device_path = input._unique_devices[i].canonical_path; bool need_reopen = false; - if (device_path == nullptr) + if (device_path == BONGOCAT_NULLPTR) { continue; + } // If an fd is already open, check if it is still valid if (input._unique_devices[i].fd._fd >= 0) { if (!is_open_device_valid(input._unique_devices[i].fd._fd)) { @@ -803,12 +813,12 @@ static void *input_thread(void *arg) { if (pfds[fds_udev_monitor_index].revents & POLLIN) { BONGOCAT_LOG_DEBUG("input: Receive udev event"); size_t attempts = 0; - udev_device *dev = nullptr; - while ((dev = udev_monitor_receive_device(input._udev_mon)) != nullptr && attempts < MAX_ATTEMPTS) { + udev_device *dev = BONGOCAT_NULLPTR; + while ((dev = udev_monitor_receive_device(input._udev_mon)) != BONGOCAT_NULLPTR && attempts < MAX_ATTEMPTS) { const char *action = udev_device_get_action(dev); const char *node = udev_device_get_devnode(dev); - if (action && node) { + if (action != BONGOCAT_NULLPTR && node != BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("input: udev %s: %s", action, node); if (strcmp(action, "add") == 0 || strcmp(action, "remove") == 0) { sync_devices_needed = true; @@ -823,7 +833,7 @@ static void *input_thread(void *arg) { } // Handle config update - assert(input._config_generation != nullptr); + assert(input._config_generation != BONGOCAT_NULLPTR); bool reload_config = false; uint64_t new_gen{atomic_load(input._config_generation)}; if (pfds[fds_update_config_index].revents & POLLIN) { @@ -836,7 +846,7 @@ static void *input_thread(void *arg) { // Handle device events { platform::LockGuard guard(input.input_lock); - assert(input.shm != nullptr); + assert(input.shm); // auto& input_shm = *input.shm; if (fds_device_start_index >= 0) { @@ -849,10 +859,12 @@ static void *input_thread(void *arg) { // handle evdev input const ssize_t rd = read(pfds[p].fd, ev, sizeof(ev)); if (rd < 0) { - if (errno == ENODEV) + if (errno == ENODEV) { sync_devices_needed = true; - if (errno == EAGAIN) + } + if (errno == EAGAIN) { continue; + } BONGOCAT_LOG_WARNING("input: Read error on fd=%d: %s", pfds[p].fd, strerror(errno)); close(pfds[p].fd); // pfds[p].fd is only a reference, reset also the owner (unique_fd) @@ -962,9 +974,9 @@ static void *input_thread(void *arg) { if (got_key) { trigger_key_press(trigger_ctx); if (enable_debug) { - size_t len = (rd > 0) ? (static_cast(rd) < TEST_STDIN_BUF_LEN ? static_cast(rd) - : TEST_STDIN_BUF_LEN - 1) - : 0; + const size_t len = (rd > 0) ? (static_cast(rd) < TEST_STDIN_BUF_LEN ? static_cast(rd) + : TEST_STDIN_BUF_LEN - 1) + : 0; buf[len] = '\0'; BONGOCAT_LOG_VERBOSE("input: stdin input: %s", buf); } @@ -991,9 +1003,9 @@ static void *input_thread(void *arg) { // handle update config if (reload_config) { - assert(input._config_generation != nullptr); - assert(input._configs_reloaded_cond != nullptr); - assert(input._config != nullptr); + assert(input._config_generation != BONGOCAT_NULLPTR); + assert(input._configs_reloaded_cond != BONGOCAT_NULLPTR); + assert(input._config != BONGOCAT_NULLPTR); update_config(input, *input._config, new_gen); @@ -1028,20 +1040,20 @@ static void *input_thread(void *arg) { // done when callback cleanup_input_thread // cleanup_input_thread_context(arg); // sanity check for clean up - assert(input._device_paths == nullptr); - assert(input._unique_devices == nullptr); - assert(input._unique_paths_indices == nullptr); + assert(input._device_paths == BONGOCAT_NULLPTR); + assert(input._unique_devices == BONGOCAT_NULLPTR); + assert(input._unique_paths_indices == BONGOCAT_NULLPTR); assert(input._unique_paths_indices_capacity == 0); BONGOCAT_LOG_INFO("Input monitoring stopped"); - return nullptr; + return BONGOCAT_NULLPTR; } created_result_t> create(const config::config_t& config) { AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { + assert(ret); + if (ret == BONGOCAT_NULLPTR) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } if (config.num_keyboard_devices <= 0) { @@ -1073,7 +1085,7 @@ created_result_t> create(const config::config_t BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->_local_copy_config != nullptr); + assert(ret->_local_copy_config); *ret->_local_copy_config = config; ret->update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); @@ -1119,7 +1131,7 @@ bongocat_error_t start(input_context_t& input, animation::animation_session_t& t BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); update_config(input, config, atomic_load(&config_generation)); // wait for animation trigger to be ready (input should be the same) @@ -1146,7 +1158,7 @@ bongocat_error_t start(input_context_t& input, animation::animation_session_t& t input._configs_reloaded_cond->notify_all(); // start input monitoring thread - const int result = pthread_create(&input._input_thread, nullptr, input_thread, &trigger_ctx); + const int result = pthread_create(&input._input_thread, BONGOCAT_NULLPTR, input_thread, &trigger_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -1186,7 +1198,7 @@ bongocat_error_t restart(input_context_t& input, animation::animation_session_t& // Start new monitoring (reuse shared memory if it exists) { LockGuard guard(input.input_lock); - if (input.shm == nullptr) { + if (!input.shm) { input.shm = make_allocated_mmap(); if (input.shm.ptr == MAP_FAILED) { BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); @@ -1202,7 +1214,7 @@ bongocat_error_t restart(input_context_t& input, animation::animation_session_t& return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } } - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); update_config(input, config, atomic_load(&config_generation)); if (input.update_config_efd._fd < 0) { @@ -1231,7 +1243,7 @@ bongocat_error_t restart(input_context_t& input, animation::animation_session_t& input._configs_reloaded_cond->notify_all(); // start input monitoring - const int result = pthread_create(&input._input_thread, nullptr, input_thread, &trigger_ctx); + const int result = pthread_create(&input._input_thread, BONGOCAT_NULLPTR, input_thread, &trigger_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -1253,9 +1265,9 @@ void stop(input_context_t& ctx) { } ctx._input_thread = 0; - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; + ctx._config = BONGOCAT_NULLPTR; + ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + ctx._config_generation = BONGOCAT_NULLPTR; ctx.config_updated.notify_all(); atomic_store(&ctx.ready, false); @@ -1275,7 +1287,7 @@ void trigger_update_config(input_context_t& input, const config::config_t& confi } void update_config(input_context_t& input, const config::config_t& config, uint64_t new_gen) { - assert(input._local_copy_config != nullptr); + assert(input._local_copy_config); *input._local_copy_config = config; diff --git a/src/platform/update.cpp b/src/platform/update.cpp index 0f9d76eb..f37c6ff8 100644 --- a/src/platform/update.cpp +++ b/src/platform/update.cpp @@ -35,7 +35,7 @@ inline static constexpr const char *FILENAME_PROC_STAT = "/proc/stat"; static void cleanup_update_thread(void *arg) { assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); + const animation::animation_session_t& trigger_ctx = *static_cast(arg); assert(trigger_ctx._update); update_context_t& input = *trigger_ctx._update; @@ -47,9 +47,10 @@ static void cleanup_update_thread(void *arg) { } static int set_nonblocking(int fd) { - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) + const int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { return -1; + } return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } @@ -71,10 +72,10 @@ static size_t get_cpu_present_last(int fd) { */ static const cpu_snapshot_t& get_latest_snapshot_unlocked(update_context_t& ctx) { - assert(ctx.shm != nullptr); + assert(ctx.shm); auto& update_shm = *ctx.shm; ctx.update_cond.timedwait([&]() { return update_shm.cpu_snapshots.stored > 0; }, COND_STORED_TIMEOUT_MS); - assert(CpuSnapshotRingBufferMaxHistory > 0); + static_assert(CpuSnapshotRingBufferMaxHistory > 0); const size_t latest = (update_shm.cpu_snapshots.head + CpuSnapshotRingBufferMaxHistory - 1) % CpuSnapshotRingBufferMaxHistory; assert(latest < CpuSnapshotRingBufferMaxHistory); @@ -90,23 +91,25 @@ static size_t parse_cpuinfo_fd(int fd, size_t cpu_present_last, cpu_stat_t *out, lseek(fd, 0, SEEK_SET); char buf[CPU_INFO_BUF] = {0}; const ssize_t len = read(fd, buf, sizeof(buf) - 1); - if (len <= 0) + if (len <= 0) { return 0; + } assert(len >= 0 && static_cast(len) < CPU_INFO_BUF); buf[len] = '\0'; ssize_t current_cpu_number = -1; size_t used = 0; - char *saveptr = nullptr; + char *saveptr = BONGOCAT_NULLPTR; char *line = strtok_r(buf, "\n", &saveptr); while (line) { - if (strncmp(line, "cpu", 3) != 0) + if (strncmp(line, "cpu", 3) != 0) { break; + } size_t line_cpu_number = 0; if (current_cpu_number >= 0) { - line_cpu_number = strtoul(line + 3, nullptr, 10); + line_cpu_number = strtoul(line + 3, BONGOCAT_NULLPTR, 10); assert(current_cpu_number >= 0); // assert(current_cpu_number <= SIZE_MAX); while (line_cpu_number > static_cast(current_cpu_number) && used < out_size) { @@ -117,18 +120,20 @@ static size_t parse_cpuinfo_fd(int fd, size_t cpu_present_last, cpu_stat_t *out, } char *p = line; - while (*p && !isspace(static_cast(*p))) + while (*p && !isspace(static_cast(*p))) { p++; - while (*p && isspace(static_cast(*p))) + } + while (*p && isspace(static_cast(*p))) { p++; + } constexpr size_t times_size = 16; size_t times[times_size] = {0}; size_t times_count = 0; char *tok = strtok(p, " \t"); while (tok && times_count < times_size) { - times[times_count++] = strtoull(tok, nullptr, 10); - tok = strtok(nullptr, " \t"); + times[times_count++] = strtoull(tok, BONGOCAT_NULLPTR, 10); + tok = strtok(BONGOCAT_NULLPTR, " \t"); } size_t idle_time = 0; @@ -136,8 +141,9 @@ static size_t parse_cpuinfo_fd(int fd, size_t cpu_present_last, cpu_stat_t *out, assert(times_size >= 5); if (times_count >= 5) { idle_time = times[3] + times[4]; - for (size_t i = 0; i < times_count; i++) + for (size_t i = 0; i < times_count; i++) { total_time += times[i]; + } } if (used < MaxCpus) { @@ -146,7 +152,7 @@ static size_t parse_cpuinfo_fd(int fd, size_t cpu_present_last, cpu_stat_t *out, } current_cpu_number++; - line = strtok_r(nullptr, "\n", &saveptr); + line = strtok_r(BONGOCAT_NULLPTR, "\n", &saveptr); } if (current_cpu_number >= 0) { @@ -178,8 +184,9 @@ static double compute_avg_cpu_usage(const cpu_snapshot_t& prev, const cpu_snapsh idle_delta += d_idle; } - if (total_delta == 0) + if (total_delta == 0) { return 0.0; + } return 100.0 * static_cast(total_delta - idle_delta) / static_cast(total_delta); } @@ -216,15 +223,15 @@ static void *update_thread(void *arg) { // wait for input context (in animation start) trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - assert(trigger_ctx._input != nullptr); + assert(trigger_ctx._input != BONGOCAT_NULLPTR); update_context_t& upd = *trigger_ctx._update; // sanity checks - assert(upd._config != nullptr); - assert(upd._configs_reloaded_cond != nullptr); + assert(upd._config != BONGOCAT_NULLPTR); + assert(upd._configs_reloaded_cond != BONGOCAT_NULLPTR); assert(!upd._running); - assert(upd.shm != nullptr); - assert(upd._local_copy_config != nullptr); + assert(upd.shm); + assert(upd._local_copy_config); assert(upd.update_config_efd._fd >= 0); assert(upd.fd_present._fd >= 0); assert(upd.fd_stat._fd >= 0); @@ -255,7 +262,7 @@ static void *update_thread(void *arg) { // bool enable_debug = false; { // read-only config - assert(upd._local_copy_config != nullptr); + assert(upd._local_copy_config); const config::config_t& current_config = *upd._local_copy_config; // enable_debug = current_config.enable_debug; @@ -284,8 +291,9 @@ static void *update_thread(void *arg) { // poll events const int poll_result = poll(pfds, nfds, static_cast(timeout)); if (poll_result < 0) { - if (errno == EINTR) + if (errno == EINTR) { continue; // Interrupted by signal + } BONGOCAT_LOG_ERROR("update: Poll error: %s", strerror(errno)); break; } @@ -302,7 +310,7 @@ static void *update_thread(void *arg) { } // Handle config update - assert(upd._config_generation != nullptr); + assert(upd._config_generation != BONGOCAT_NULLPTR); bool reload_config = false; uint64_t new_gen{atomic_load(upd._config_generation)}; if (pfds[fds_update_config_index].revents & POLLIN) { @@ -318,7 +326,7 @@ static void *update_thread(void *arg) { if (update_rate_ms > 0) { platform::LockGuard guard(upd.update_lock); - assert(upd.shm != nullptr); + assert(upd.shm); auto& update_shm = *upd.shm; const size_t count = parse_cpuinfo_fd( @@ -328,8 +336,9 @@ static void *update_thread(void *arg) { { LockGuard guard_cond(upd.update_cond._mutex); update_shm.cpu_snapshots.head = (update_shm.cpu_snapshots.head + 1) % CpuSnapshotRingBufferMaxHistory; - if (update_shm.cpu_snapshots.stored < CpuSnapshotRingBufferMaxHistory) + if (update_shm.cpu_snapshots.stored < CpuSnapshotRingBufferMaxHistory) { update_shm.cpu_snapshots.stored++; + } pthread_cond_broadcast(&upd.update_cond._cond); } } @@ -349,11 +358,11 @@ static void *update_thread(void *arg) { bool animation_triggered = false; if (update_rate_ms > 0) { LockGuard guard(upd.update_lock); - assert(upd.shm != nullptr); + assert(upd.shm); auto& update_shm = *upd.shm; // read-only config - assert(upd._local_copy_config != nullptr); + assert(upd._local_copy_config); const config::config_t& current_config = *upd._local_copy_config; update_shm.latest_snapshot = &get_latest_snapshot_unlocked(upd); @@ -406,9 +415,9 @@ static void *update_thread(void *arg) { // handle update config if (reload_config) { - assert(upd._config_generation != nullptr); - assert(upd._configs_reloaded_cond != nullptr); - assert(upd._config != nullptr); + assert(upd._config_generation != BONGOCAT_NULLPTR); + assert(upd._configs_reloaded_cond != BONGOCAT_NULLPTR); + assert(upd._config != BONGOCAT_NULLPTR); update_config(upd, *upd._config, new_gen); @@ -429,7 +438,7 @@ static void *update_thread(void *arg) { BONGOCAT_LOG_INFO("update: Update config reloaded (gen=%u)", new_gen); } - if (update_rate_ms) { + if (update_rate_ms > 0) { // sleep timespec ts; ts.tv_sec = 0; @@ -439,7 +448,7 @@ static void *update_thread(void *arg) { // animation ts.tv_nsec = animation_speed_ms * 1000 * 1000; } else if (animation_triggered) { - ts.tv_nsec = (1000 / fps) * 1000 * 1000; + ts.tv_nsec = (1000L / fps) * 1000 * 1000; } else { ts.tv_nsec = timeout * 1000 * 1000; } @@ -447,10 +456,10 @@ static void *update_thread(void *arg) { ts.tv_nsec -= 1000000000LL; ts.tv_sec += 1; } - nanosleep(&ts, nullptr); + nanosleep(&ts, BONGOCAT_NULLPTR); { LockGuard guard(upd.update_lock); - assert(upd.shm != nullptr); + assert(upd.shm); auto& update_shm = *upd.shm; update_shm.cpu_active = false; } @@ -469,13 +478,13 @@ static void *update_thread(void *arg) { BONGOCAT_LOG_INFO("Update monitoring stopped"); - return nullptr; + return BONGOCAT_NULLPTR; } created_result_t> create(const config::config_t& config) { AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { + // assert(ret != nullptr); + if (!ret) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } @@ -485,22 +494,22 @@ created_result_t> create(const config::config_ // Initialize shared memory ret->shm = make_allocated_mmap(); - if (!ret->shm.ptr) { + if (!ret->shm) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for update monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } // Initialize shared memory for local config ret->_local_copy_config = make_allocated_mmap(); - if (!ret->_local_copy_config) { + if (!ret->_local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for update monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->_local_copy_config != nullptr); + assert(ret->_local_copy_config); *ret->_local_copy_config = config; ret->update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->update_config_efd._fd < 0) { + if (ret->update_config_efd._fd < 0) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create notify pipe for update update config: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; } @@ -531,23 +540,23 @@ bongocat_error_t start(update_context_t& upd, animation::animation_session_t& tr // Initialize shared memory for key press flag upd.shm = make_allocated_mmap(); - if (!upd.shm.ptr) { + if (!upd.shm) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } // Initialize shared memory for local config upd._local_copy_config = make_allocated_mmap(); - if (!upd._local_copy_config) { + if (!upd._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(upd._local_copy_config != nullptr); + assert(upd._local_copy_config); update_config(upd, config, atomic_load(&config_generation)); // wait for animation trigger to be ready (input should be the same) - int cond_ret = trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, - COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + const int cond_ret = trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); if (cond_ret == ETIMEDOUT) { BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); } else { @@ -569,7 +578,7 @@ bongocat_error_t start(update_context_t& upd, animation::animation_session_t& tr upd._configs_reloaded_cond->notify_all(); // start update thread - const int result = pthread_create(&upd._update_thread, nullptr, update_thread, &trigger_ctx); + const int result = pthread_create(&upd._update_thread, BONGOCAT_NULLPTR, update_thread, &trigger_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -598,7 +607,7 @@ bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& { LockGuard guard(upd.update_lock); // Start new monitoring (reuse shared memory if it exists) - if (upd.shm == nullptr) { + if (!upd.shm) { upd.shm = make_allocated_mmap(); if (upd.shm.ptr == MAP_FAILED) { BONGOCAT_LOG_ERROR("Failed to create shared memory for update thread: %s", strerror(errno)); @@ -614,7 +623,7 @@ bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } } - assert(upd._local_copy_config != nullptr); + assert(upd._local_copy_config); update_config(upd, config, atomic_load(&config_generation)); if (upd.update_config_efd._fd < 0) { @@ -663,7 +672,7 @@ bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& upd._configs_reloaded_cond->notify_all(); // start update monitoring - const int result = pthread_create(&upd._update_thread, nullptr, update_thread, &trigger_ctx); + const int result = pthread_create(&upd._update_thread, BONGOCAT_NULLPTR, update_thread, &trigger_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -685,9 +694,9 @@ void stop(update_context_t& ctx) { } ctx._update_thread = 0; - ctx._config = nullptr; - ctx._configs_reloaded_cond = nullptr; - ctx._config_generation = nullptr; + ctx._config = BONGOCAT_NULLPTR; + ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + ctx._config_generation = BONGOCAT_NULLPTR; ctx.config_updated.notify_all(); atomic_store(&ctx.ready, false); @@ -704,7 +713,7 @@ void trigger_update_config(update_context_t& upd, const config::config_t& config } void update_config(update_context_t& upd, const config::config_t& config, uint64_t new_gen) { - assert(upd._local_copy_config != nullptr); + assert(upd._local_copy_config); *upd._local_copy_config = config; diff --git a/src/platform/wayland.cpp b/src/platform/wayland.cpp index 4591aee0..746b9c35 100644 --- a/src/platform/wayland.cpp +++ b/src/platform/wayland.cpp @@ -49,8 +49,8 @@ static inline constexpr auto WAYLAND_LAYER_NAME = "OVERLAY"; created_result_t> create(animation::animation_session_t& anim, const config::config_t& config) { AllocatedMemory ret = make_allocated_memory(); - assert(ret != nullptr); - if (ret == nullptr) { + assert(ret); + if (!ret) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } @@ -59,11 +59,11 @@ created_result_t> create(animation::animation // Initialize shared memory ret->wayland_context.ctx_shm = make_allocated_mmap(); - if (ret->wayland_context.ctx_shm == nullptr) { + if (!ret->wayland_context.ctx_shm) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - if (ret->wayland_context.ctx_shm != nullptr) { + if (ret->wayland_context.ctx_shm) [[unlikely]] { static_assert(WAYLAND_NUM_BUFFERS <= INT_MAX); for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { ret->wayland_context.ctx_shm->buffers[i] = {}; @@ -73,11 +73,11 @@ created_result_t> create(animation::animation // Initialize shared memory for local config ret->wayland_context._local_copy_config = make_allocated_mmap(); - if (ret->wayland_context._local_copy_config == nullptr) { + if (!ret->wayland_context._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->wayland_context._local_copy_config != nullptr); + assert(ret->wayland_context._local_copy_config); *ret->wayland_context._local_copy_config = config; ret->wayland_context._bar_height = config.overlay_height; @@ -87,19 +87,19 @@ created_result_t> create(animation::animation bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim) { ctx.animation_trigger_context = &anim; - if (ctx.wayland_context.ctx_shm == nullptr) { + if (!ctx.wayland_context.ctx_shm) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - if (!ctx.wayland_context._local_copy_config) { + if (!ctx.wayland_context._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } BONGOCAT_LOG_INFO("Initializing Wayland connection"); - ctx.wayland_context.display = wl_display_connect(nullptr); - if (!ctx.wayland_context.display) { + ctx.wayland_context.display = wl_display_connect(BONGOCAT_NULLPTR); + if (ctx.wayland_context.display == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to connect to Wayland display"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -138,23 +138,24 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int [&]() { return atomic_load(&ctx.animation_trigger_context->ready); }, COND_INIT_TIMEOUT_MS); input.init_cond.timedwait([&]() { return atomic_load(&ctx.animation_trigger_context->ready); }, COND_INIT_TIMEOUT_MS); animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; - assert(trigger_ctx._input != nullptr); + assert(trigger_ctx._input != BONGOCAT_NULLPTR); assert(trigger_ctx._input == &input); // wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm; BONGOCAT_LOG_INFO("Starting Wayland event loop"); running = 1; - while (running && wayland_ctx.display) { + while (running >= 1 && wayland_ctx.display != BONGOCAT_NULLPTR) { const time_ms_t frame_based_timeout = config.fps > 0 ? 1000 / config.fps : 0; // Periodic fullscreen check for fallback fullscreen detection timeval now{}; - gettimeofday(&now, nullptr); - const time_ms_t elapsed_ms = (now.tv_sec - ctx.fs_detector.last_check.tv_sec) * 1000L + - (now.tv_usec - ctx.fs_detector.last_check.tv_usec) / 1000L; + gettimeofday(&now, BONGOCAT_NULLPTR); + const time_ms_t elapsed_ms = ((now.tv_sec - ctx.fs_detector.last_check.tv_sec) * 1000L) + + ((now.tv_usec - ctx.fs_detector.last_check.tv_usec) / 1000L); time_ms_t fullscreen_check_interval = frame_based_timeout; - if (fullscreen_check_interval < CHECK_INTERVAL_MS) + if (fullscreen_check_interval < CHECK_INTERVAL_MS) { fullscreen_check_interval = CHECK_INTERVAL_MS; + } if (elapsed_ms >= fullscreen_check_interval) { details::fs_update_state_fallback(ctx); ctx.fs_detector.last_check = now; @@ -167,19 +168,23 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int constexpr size_t fds_wayland_index = 3; constexpr nfds_t fds_count = 4; pollfd fds[fds_count] = { - {.fd = signal_fd, .events = POLLIN, .revents = 0}, - {.fd = config_watcher ? config_watcher->reload_efd._fd : -1, .events = POLLIN, .revents = 0}, - {.fd = trigger_ctx.render_efd._fd, .events = POLLIN, .revents = 0}, - {.fd = wl_display_get_fd(wayland_ctx.display), .events = POLLIN, .revents = 0}, + {.fd = signal_fd, .events = POLLIN, .revents = 0}, + {.fd = config_watcher != BONGOCAT_NULLPTR ? config_watcher->reload_efd._fd : -1, + .events = POLLIN, + .revents = 0 }, + {.fd = trigger_ctx.render_efd._fd, .events = POLLIN, .revents = 0}, + {.fd = wl_display_get_fd(wayland_ctx.display), .events = POLLIN, .revents = 0}, }; static_assert(fds_count == LEN_ARRAY(fds)); // compute desired timeout time_ms_t timeout_ms = frame_based_timeout; - if (timeout_ms < POOL_MIN_TIMEOUT_MS) + if (timeout_ms < POOL_MIN_TIMEOUT_MS) { timeout_ms = POOL_MIN_TIMEOUT_MS; - if (timeout_ms > POOL_MAX_TIMEOUT_MS) + } + if (timeout_ms > POOL_MAX_TIMEOUT_MS) { timeout_ms = POOL_MAX_TIMEOUT_MS; + } // avoid reloading twice, by signal OR watcher bool config_reload_requested = false; @@ -223,7 +228,7 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int // signal events if (fds[fds_signals_index].revents & POLLIN) { signalfd_siginfo fdsi{}; - ssize_t s = read(fds[fds_signals_index].fd, &fdsi, sizeof(fdsi)); + const ssize_t s = read(fds[fds_signals_index].fd, &fdsi, sizeof(fdsi)); if (s != sizeof(fdsi)) { BONGOCAT_LOG_ERROR("Failed to read signal fd"); } else { @@ -237,7 +242,7 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int break; case SIGCHLD: // Handle child process termination - reap zombies - while (waitpid(-1, nullptr, WNOHANG) > 0) {} + while (waitpid(-1, BONGOCAT_NULLPTR, WNOHANG) > 0) {} break; case SIGUSR1: BONGOCAT_LOG_INFO("Received SIGUSR1, toggle bar visibility"); @@ -258,8 +263,9 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int for (size_t i = 0; i < fds_count; i++) { platform::drain_event(fds[i], MAX_ATTEMPTS); } - if (prepared_read) + if (prepared_read) { wl_display_cancel_read(wayland_ctx.display); + } render_requested = false; toggle_visibility_requested = false; break; @@ -268,7 +274,7 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int // reload config event if (fds[fds_config_reload_index].revents & POLLIN) { BONGOCAT_LOG_DEBUG("Receive reload event"); - if (config_watcher) { + if (config_watcher != BONGOCAT_NULLPTR) { platform::drain_event(fds[fds_config_reload_index], MAX_ATTEMPTS, "update config eventfd"); } config_reload_requested = true; @@ -323,16 +329,18 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int fds[fds_signals_index].revents, fds[fds_config_reload_index].revents, fds[fds_animation_render_index].revents, fds[fds_wayland_index].revents); } else if (poll_result == 0) { - if (prepared_read) + if (prepared_read) { wl_display_cancel_read(wayland_ctx.display); + } if (wl_display_dispatch_pending(wayland_ctx.display) == -1) { BONGOCAT_LOG_ERROR("Failed to dispatch pending events"); running = 0; return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } } else { - if (prepared_read) + if (prepared_read) { wl_display_cancel_read(wayland_ctx.display); + } if (errno != EINTR) { BONGOCAT_LOG_ERROR("Poll error: %s", strerror(errno)); running = 0; @@ -414,12 +422,12 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int // ============================================================================= int get_screen_width(const wayland_session_t& ctx) { - return (ctx.wayland_context._screen_info) ? ctx.wayland_context._screen_info->screen_width : 0; + return ctx.wayland_context._screen_info != BONGOCAT_NULLPTR ? ctx.wayland_context._screen_info->screen_width : 0; } void update_config(wayland_session_t& ctx, const config::config_t& config, animation::animation_session_t& trigger_ctx) { - assert(ctx.wayland_context._local_copy_config != nullptr && ctx.wayland_context._local_copy_config.ptr != MAP_FAILED); + assert(ctx.wayland_context._local_copy_config); // Check if dimensions changed - requires buffer/surface recreation const auto old_height = diff --git a/src/platform/wayland_callbacks.cpp b/src/platform/wayland_callbacks.cpp index 58629714..3ca2a586 100644 --- a/src/platform/wayland_callbacks.cpp +++ b/src/platform/wayland_callbacks.cpp @@ -37,7 +37,7 @@ namespace bongocat::platform::wayland::details { // ============================================================================= void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, const char *name) { - if (!data || !name) { + if (data == BONGOCAT_NULLPTR || name == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -54,7 +54,7 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out // assert(oref->wayland); /// Reconnection handling - if (oref->wayland) { + if (oref->wayland != BONGOCAT_NULLPTR) { wayland_context_t& wayland_ctx = oref->wayland->wayland_context; // animation_context_t& anim = *ctx.animation_context; // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; @@ -70,7 +70,7 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out bool should_reconnect = false; // Case 1: User specified an output name - match exactly - if (wayland_ctx.using_named_output && wayland_ctx._output_name_str) { + if (wayland_ctx.using_named_output && wayland_ctx._output_name_str != BONGOCAT_NULLPTR) { should_reconnect = (strcmp(name, wayland_ctx._output_name_str) == 0); } // Case 2: Using fallback (first output) - reconnect to any output @@ -95,15 +95,15 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out // event. The layer_surface_configure callback will ack and call draw_bar() // to render. if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { - assert(wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED); - if (wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED) { + assert(wayland_ctx.ctx_shm); + if (wayland_ctx.ctx_shm) { atomic_store(&wayland_ctx.ctx_shm->configured, true); } if constexpr (WAYLAND_NUM_BUFFERS != 1) { // Wait for configure event to be processed wl_display_roundtrip(wayland_ctx.display); } - if (oref->wayland->animation_trigger_context) { + if (oref->wayland->animation_trigger_context != BONGOCAT_NULLPTR) { request_render(*oref->wayland->animation_trigger_context); } BONGOCAT_LOG_INFO("Surface recreated, configure event processed"); @@ -115,7 +115,7 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out } void handle_xdg_output_logical_position(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -129,7 +129,7 @@ void handle_xdg_output_logical_position(void *data, [[maybe_unused]] zxdg_output } void handle_xdg_output_logical_size(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -142,7 +142,7 @@ void handle_xdg_output_logical_size(void *data, [[maybe_unused]] zxdg_output_v1 BONGOCAT_LOG_VERBOSE("xdg_output.logical_size: %dx%d received", width, height); } void handle_xdg_output_done(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -153,7 +153,7 @@ void handle_xdg_output_done(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out void handle_xdg_output_description(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_output, [[maybe_unused]] const char *description) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -171,7 +171,7 @@ static bool fs_update_state(wayland_session_t& ctx, bool new_state) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping update"); return false; } - if (!ctx.animation_trigger_context) { + if (ctx.animation_trigger_context == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Wayland not configured yet"); return false; } @@ -185,7 +185,7 @@ static bool fs_update_state(wayland_session_t& ctx, bool new_state) { BONGOCAT_LOG_INFO("Fullscreen state changed: %s", ctx.wayland_context._fullscreen_detected ? "detected" : "cleared"); - if (ctx.wayland_context.ctx_shm != nullptr && atomic_load(&ctx.wayland_context.ctx_shm->configured)) { + if (ctx.wayland_context.ctx_shm && atomic_load(&ctx.wayland_context.ctx_shm->configured)) { request_render(*ctx.animation_trigger_context); } else { BONGOCAT_LOG_VERBOSE("Wayland not configured yet, skipping request rendering"); @@ -245,7 +245,7 @@ static bool fs_check_status(wayland_session_t& ctx) { return false; } - if (ctx.fs_detector.manager) { + if (ctx.fs_detector.manager != BONGOCAT_NULLPTR) { return ctx.fs_detector.has_fullscreen_toplevel; } @@ -256,8 +256,9 @@ void fs_update_state_fallback(wayland_session_t& ctx) { for (size_t i = 0; i < ctx.num_toplevels; ++i) { const tracked_toplevel_t& tracked = ctx.tracked_toplevels[i]; // Skip handles that are not mapped or destroyed - if (!tracked.handle) + if (tracked.handle == BONGOCAT_NULLPTR) { continue; + } if (tracked.is_fullscreen) { // Only update overlay if on our output if (tracked.output == ctx.wayland_context.output) { @@ -296,7 +297,7 @@ update_fullscreen_state_toplevel(wayland_session_t& ctx, tracked_toplevel_t& tra // Foreign toplevel protocol event handlers void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, wl_array *state) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -343,7 +344,7 @@ void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_toplevel } void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *handle) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -353,13 +354,15 @@ void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *hand return; } - if (handle) + if (handle != BONGOCAT_NULLPTR) { zwlr_foreign_toplevel_handle_v1_destroy(handle); + handle = BONGOCAT_NULLPTR; + } // remove from tracked_toplevels if present for (size_t i = 0; i < ctx.num_toplevels; ++i) { if (ctx.tracked_toplevels[i].handle == handle) { - ctx.tracked_toplevels[i].handle = nullptr; + ctx.tracked_toplevels[i].handle = BONGOCAT_NULLPTR; // compact array to keep contiguous for (size_t j = i; j + 1 < ctx.num_toplevels; ++j) { ctx.tracked_toplevels[j] = ctx.tracked_toplevels[j + 1]; @@ -376,7 +379,7 @@ void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *hand // Minimal event handlers for unused events void fs_handle_title(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] const char *title) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -387,7 +390,7 @@ void fs_handle_title(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v void fs_handle_app_id(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] const char *app_id) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -398,7 +401,7 @@ void fs_handle_app_id(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_ void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] wl_output *output) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -423,7 +426,7 @@ void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] wl_output *output) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -436,7 +439,7 @@ void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h if (tracked.is_fullscreen && tracked.output == ctx.wayland_context.output) { fs_update_state(ctx, false); } - tracked.output = nullptr; + tracked.output = BONGOCAT_NULLPTR; break; } } @@ -445,7 +448,7 @@ void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h } void fs_handle_done(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -456,7 +459,7 @@ void fs_handle_done(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 void fs_handle_parent(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *handle, [[maybe_unused]] zwlr_foreign_toplevel_handle_v1 *parent) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -467,7 +470,7 @@ void fs_handle_parent(void *data, [[maybe_unused]] zwlr_foreign_toplevel_handle_ void fs_handle_manager_toplevel(void *data, [[maybe_unused]] zwlr_foreign_toplevel_manager_v1 *manager, zwlr_foreign_toplevel_handle_v1 *toplevel) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); return; } @@ -498,7 +501,7 @@ void fs_handle_manager_toplevel(void *data, [[maybe_unused]] zwlr_foreign_toplev } void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *manager) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -509,9 +512,11 @@ void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *ma } BONGOCAT_LOG_INFO("fs_toplevel_manager_listener.finished: Foreign toplevel manager finished"); - if (manager) + if (manager != BONGOCAT_NULLPTR) { zwlr_foreign_toplevel_manager_v1_destroy(manager); - ctx.fs_detector.manager = nullptr; + manager = BONGOCAT_NULLPTR; + } + ctx.fs_detector.manager = BONGOCAT_NULLPTR; } // ============================================================================= @@ -551,14 +556,14 @@ static void screen_calculate_dimensions(screen_info_t& screen_info) { void layer_surface_configure(void *data, zwlr_layer_surface_v1 *ls, uint32_t serial, [[maybe_unused]] uint32_t w, [[maybe_unused]] uint32_t h) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } wayland_session_t& ctx = *static_cast(data); - assert(ctx.animation_trigger_context != nullptr); - assert(ctx.wayland_context.ctx_shm != nullptr); + assert(ctx.animation_trigger_context != BONGOCAT_NULLPTR); + assert(ctx.wayland_context.ctx_shm); wayland_shared_memory_t& wayland_ctx_shm = *ctx.wayland_context.ctx_shm; zwlr_layer_surface_v1_ack_configure(ls, serial); @@ -571,11 +576,11 @@ void layer_surface_configure(void *data, zwlr_layer_surface_v1 *ls, uint32_t ser BONGOCAT_LOG_DEBUG("layer_surface.configure: Layer surface configured: %dx%d", w, h); } void layer_surface_closed(void *data, [[maybe_unused]] zwlr_layer_surface_v1 *ls) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + const wayland_session_t& ctx = *static_cast(data); if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); return; @@ -586,7 +591,7 @@ void layer_surface_closed(void *data, [[maybe_unused]] zwlr_layer_surface_v1 *ls void xdg_wm_base_ping(void *data, xdg_wm_base *wm_base, uint32_t serial) { assert(data); - [[maybe_unused]] wayland_session_t& ctx = *static_cast(data); + [[maybe_unused]] const wayland_session_t& ctx = *static_cast(data); BONGOCAT_LOG_VERBOSE("xdg_wm_base.ping: base pong %x", serial); xdg_wm_base_pong(wm_base, serial); @@ -596,7 +601,7 @@ void output_geometry(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_ [[maybe_unused]] int32_t y, [[maybe_unused]] int32_t physical_width, [[maybe_unused]] int32_t physical_height, [[maybe_unused]] int32_t subpixel, [[maybe_unused]] const char *make, [[maybe_unused]] const char *model, int32_t transform) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -616,7 +621,7 @@ void output_geometry(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_ void output_mode(void *data, [[maybe_unused]] wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, [[maybe_unused]] int32_t refresh) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -640,7 +645,7 @@ void output_mode(void *data, [[maybe_unused]] wl_output *wl_output, uint32_t fla } void output_done(void *data, [[maybe_unused]] wl_output *wl_output) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -655,7 +660,7 @@ void output_done(void *data, [[maybe_unused]] wl_output *wl_output) { } void output_scale(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] int32_t factor) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -666,7 +671,7 @@ void output_scale(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unu } void output_name(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] const char *name) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -676,7 +681,7 @@ void output_name(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unus } void output_description(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_unused]] const char *name) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -686,7 +691,7 @@ void output_description(void *data, [[maybe_unused]] wl_output *wl_output, [[may } void buffer_release(void *data, wl_buffer *buffer) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -707,7 +712,7 @@ void buffer_release(void *data, wl_buffer *buffer) { } void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); return; } @@ -727,7 +732,7 @@ void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { // animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; // wayland_shared_memory_t& wayland_ctx_shm = wayland_ctx->ctx_shm; // read-only - assert(wayland_ctx._local_copy_config != nullptr); + assert(wayland_ctx._local_copy_config); // const config::config_t& current_config = *wayland_ctx._local_copy_config; // const animation_shared_memory_t *const anim_shm = anim->shm; // assert(anim_shm); @@ -737,7 +742,7 @@ void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { platform::LockGuard guard(wayland_ctx._frame_cb_lock); if (wayland_ctx._frame_cb == cb) { wl_callback_destroy(wayland_ctx._frame_cb); - wayland_ctx._frame_cb = nullptr; + wayland_ctx._frame_cb = BONGOCAT_NULLPTR; atomic_store(&wayland_ctx._frame_pending, false); BONGOCAT_LOG_VERBOSE("wl_callback.done: frame done"); } else { @@ -768,7 +773,7 @@ void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { // ============================================================================= void registry_global(void *data, wl_registry *reg, uint32_t name, const char *iface, [[maybe_unused]] uint32_t ver) { - if (!data) { + if (data == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } @@ -791,7 +796,7 @@ void registry_global(void *data, wl_registry *reg, uint32_t name, const char *if ctx.wayland_context.xdg_wm_base = static_cast(wl_registry_bind(reg, name, &xdg_wm_base_interface, 1)); BONGOCAT_LOG_VERBOSE("wl_registry.global: xdg_wm_base registry bind"); - if (ctx.wayland_context.xdg_wm_base) { + if (ctx.wayland_context.xdg_wm_base != BONGOCAT_NULLPTR) { xdg_wm_base_add_listener(ctx.wayland_context.xdg_wm_base, &xdg_wm_base_listener, &ctx); } } else if (strcmp(iface, zxdg_output_manager_v1_interface.name) == 0) { @@ -808,7 +813,7 @@ void registry_global(void *data, wl_registry *reg, uint32_t name, const char *if // If we lost our output, get xdg_output to check if this is the one // reconnecting - if (atomic_load(&ctx.output_lost) && ctx.xdg_output_manager) { + if (atomic_load(&ctx.output_lost) && ctx.xdg_output_manager != BONGOCAT_NULLPTR) { ctx.outputs[ctx.output_count].xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx.xdg_output_manager, ctx.outputs[ctx.output_count].wl_output); ctx.outputs[ctx.output_count].received = flag_remove( @@ -825,7 +830,7 @@ void registry_global(void *data, wl_registry *reg, uint32_t name, const char *if ctx.fs_detector.manager = static_cast( wl_registry_bind(reg, name, &zwlr_foreign_toplevel_manager_v1_interface, 3)); BONGOCAT_LOG_VERBOSE("wl_registry.global: foreign_toplevel_manager (fs_detector.manager) registry bind"); - if (ctx.fs_detector.manager) { + if (ctx.fs_detector.manager != BONGOCAT_NULLPTR) { zwlr_foreign_toplevel_manager_v1_add_listener(ctx.fs_detector.manager, &fs_manager_listener, &ctx); BONGOCAT_LOG_INFO( "wl_registry.global: Foreign toplevel manager bound - using Wayland protocol for fullscreen detection"); @@ -834,7 +839,7 @@ void registry_global(void *data, wl_registry *reg, uint32_t name, const char *if } void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe_unused]] uint32_t name) { - if (!data || !registry) { + if (data == BONGOCAT_NULLPTR || registry == BONGOCAT_NULLPTR) { BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); return; } @@ -843,12 +848,12 @@ void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe // animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; - if (!wayland_ctx.ctx_shm.ptr) { + if (!wayland_ctx.ctx_shm) { BONGOCAT_LOG_WARNING("Handler called with null wayland_ctx.ctx_shm (ignored)"); return; } assert(wayland_ctx.ctx_shm.ptr); - platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; + platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm; BONGOCAT_LOG_VERBOSE("wl_registry.global_remove: registry received"); @@ -859,18 +864,18 @@ void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe atomic_store(&wayland_ctx_shm.configured, false); // Clean up the old output reference - wayland_ctx.output = nullptr; + wayland_ctx.output = BONGOCAT_NULLPTR; // Remove from outputs array for (size_t i = 0; i < ctx.output_count; ++i) { if (ctx.outputs[i].name == name) { - if (ctx.outputs[i].xdg_output) { + if (ctx.outputs[i].xdg_output != BONGOCAT_NULLPTR) { zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); - ctx.outputs[i].xdg_output = nullptr; + ctx.outputs[i].xdg_output = BONGOCAT_NULLPTR; } - if (ctx.outputs[i].wl_output) { + if (ctx.outputs[i].wl_output != BONGOCAT_NULLPTR) { wl_output_destroy(ctx.outputs[i].wl_output); - ctx.outputs[i].wl_output = nullptr; + ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; } // Shift remaining outputs for (size_t j = i; j < ctx.output_count - 1; ++j) { @@ -887,7 +892,7 @@ void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe // Helper to handle output reconnection void wayland_handle_output_reconnect(output_ref_t *oref, struct wl_output *new_output, uint32_t registry_name, - const char *output_name) { + [[maybe_unused]] const char *output_name) { assert(oref->wayland); wayland_context_t& wayland_ctx = oref->wayland->wayland_context; @@ -911,14 +916,14 @@ void wayland_handle_output_reconnect(output_ref_t *oref, struct wl_output *new_o // Recreate surface on new output if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { assert(wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED); - if (wayland_ctx.ctx_shm.ptr && wayland_ctx.ctx_shm.ptr != MAP_FAILED) { + if (wayland_ctx.ctx_shm) { atomic_store(&wayland_ctx.ctx_shm->configured, true); } BONGOCAT_LOG_INFO("Surface recreated on reconnected output"); if constexpr (WAYLAND_NUM_BUFFERS != 1) { wl_display_roundtrip(wayland_ctx.display); } - if (oref->wayland->animation_trigger_context) { + if (oref->wayland->animation_trigger_context != BONGOCAT_NULLPTR) { request_render(*oref->wayland->animation_trigger_context); } } else { diff --git a/src/platform/wayland_hyprland.cpp b/src/platform/wayland_hyprland.cpp index 3c7533c6..98ebbf8d 100644 --- a/src/platform/wayland_hyprland.cpp +++ b/src/platform/wayland_hyprland.cpp @@ -17,7 +17,7 @@ namespace bongocat::platform::wayland::hyprland { int fs_check_compositor_fallback() { FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); - if (fp) { + if (fp != BONGOCAT_NULLPTR) { bool is_fullscreen = false; char line[LINE_BUF]; @@ -27,7 +27,8 @@ int fs_check_compositor_fallback() { line[len - 1] = '\0'; } - if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || strstr(line, "fullscreen: true")) { + if (strstr(line, "fullscreen: 1") != BONGOCAT_NULLPTR || strstr(line, "fullscreen: 2") != BONGOCAT_NULLPTR || + strstr(line, "fullscreen: true") != BONGOCAT_NULLPTR) { is_fullscreen = true; BONGOCAT_LOG_DEBUG("Fullscreen detected in Hyprland"); break; @@ -43,8 +44,9 @@ int fs_check_compositor_fallback() { void update_outputs_with_monitor_ids(wayland_session_t& ctx) { FILE *fp = popen("hyprctl monitors 2>/dev/null", "r"); - if (!fp) + if (fp == BONGOCAT_NULLPTR) { return; + } char line[LINE_BUF]; while (fgets(line, LINE_BUF, fp)) { @@ -73,8 +75,9 @@ void update_outputs_with_monitor_ids(wayland_session_t& ctx) { bool get_active_window(window_info_t& win) { FILE *fp = popen("hyprctl activewindow 2>/dev/null", "r"); - if (!fp) + if (fp == BONGOCAT_NULLPTR) { return false; + } bool has_window = false; win.monitor_id = -1; @@ -83,25 +86,25 @@ bool get_active_window(window_info_t& win) { char line[LINE_BUF]; while (fgets(line, LINE_BUF, fp)) { // monitor: 0 - if (strstr(line, "monitor:")) { + if (strstr(line, "monitor:") != BONGOCAT_NULLPTR) { sscanf(line, "%*[\t ]monitor: %d", &win.monitor_id); has_window = true; } // fullscreen: 0/1/2 - if (strstr(line, "fullscreen:")) { + if (strstr(line, "fullscreen:") != BONGOCAT_NULLPTR) { int val; if (sscanf(line, "%*[\t ]fullscreen: %d", &val) == 1) { win.fullscreen = (val != 0); } } // at: X,Y - if (strstr(line, "at:")) { + if (strstr(line, "at:") != BONGOCAT_NULLPTR) { if (sscanf(line, "%*[\t ]at: [%d, %d]", &win.x, &win.y) < 2) { sscanf(line, "%*[\t ]at: %d,%d", &win.x, &win.y); } } // size: W,H - if (strstr(line, "size:")) { + if (strstr(line, "size:") != BONGOCAT_NULLPTR) { if (sscanf(line, "%*[\t ]size: [%d, %d]", &win.width, &win.height) < 2) { sscanf(line, "%*[\t ]size: %d,%d", &win.width, &win.height); } diff --git a/src/platform/wayland_setups.cpp b/src/platform/wayland_setups.cpp index 0b86ecf0..846cd64c 100644 --- a/src/platform/wayland_setups.cpp +++ b/src/platform/wayland_setups.cpp @@ -84,13 +84,13 @@ bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { wayland_context_t& wayland_ctx = ctx.wayland_context; // read-only config - assert(wayland_ctx._local_copy_config != nullptr); + assert(wayland_ctx._local_copy_config); const config::config_t& current_config = *wayland_ctx._local_copy_config; - wayland_ctx.output = nullptr; + wayland_ctx.output = BONGOCAT_NULLPTR; wayland_ctx.bound_output_name = 0; wayland_ctx.using_named_output = false; - if (current_config.output_name) { + if (current_config.output_name != BONGOCAT_NULLPTR) { for (size_t i = 0; i < ctx.output_count; ++i) { if (has_flag(ctx.outputs[i].received, output_ref_received_flags_t::Name) && strcmp(ctx.outputs[i].name_str, current_config.output_name) == 0) { @@ -104,7 +104,7 @@ bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { } } - if (!wayland_ctx.output) { + if (wayland_ctx.output == BONGOCAT_NULLPTR) { if (current_config._strict) { BONGOCAT_LOG_ERROR("Could not find output named '%s'", current_config.output_name); return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; @@ -115,7 +115,7 @@ bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { } // Fallback - if (!wayland_ctx.output && ctx.output_count > 0) { + if (wayland_ctx.output == BONGOCAT_NULLPTR && ctx.output_count > 0) { wayland_ctx.output = ctx.outputs[0].wl_output; wayland_ctx._output_name_str = ctx.outputs[0].name_str; wayland_ctx._screen_info = &ctx.screen_infos[0]; @@ -124,7 +124,8 @@ bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { BONGOCAT_LOG_WARNING("Falling back to first output: %s", wayland_ctx._output_name_str); } - if (!wayland_ctx.compositor || !wayland_ctx.shm || !wayland_ctx.layer_shell) { + if (wayland_ctx.compositor == BONGOCAT_NULLPTR || wayland_ctx.shm == BONGOCAT_NULLPTR || + wayland_ctx.layer_shell == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Missing required Wayland protocols"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -136,9 +137,9 @@ bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { screen_width = current_config.screen_width; } else { // auto-detect screen width - if (wayland_ctx.output) { + if (wayland_ctx.output != BONGOCAT_NULLPTR) { wl_display_roundtrip(wayland_ctx.display); - if (wayland_ctx._screen_info && wayland_ctx._screen_info->screen_width > 0) { + if (wayland_ctx._screen_info != BONGOCAT_NULLPTR && wayland_ctx._screen_info->screen_width > 0) { BONGOCAT_LOG_INFO("Detected screen width: %d", wayland_ctx._screen_info->screen_width); screen_width = wayland_ctx._screen_info->screen_width; } else { @@ -168,7 +169,7 @@ bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { /// @TODO: add RAII wrapper for wl_registry wl_registry *registry = wl_display_get_registry(wayland_ctx.display); - if (!registry) { + if (registry == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to get Wayland registry"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -176,7 +177,7 @@ bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { wl_registry_add_listener(registry, &details::reg_listener, &ctx); wl_display_roundtrip(wayland_ctx.display); - if (ctx.xdg_output_manager) { + if (ctx.xdg_output_manager != BONGOCAT_NULLPTR) { for (size_t i = 0; i < ctx.output_count && i < MAX_OUTPUTS; i++) { ctx.outputs[i].wayland = &ctx; ctx.outputs[i].xdg_output = @@ -196,7 +197,8 @@ bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { hyprland::update_outputs_with_monitor_ids(ctx); } - if (!wayland_ctx.compositor || !wayland_ctx.shm || !wayland_ctx.layer_shell) { + if (wayland_ctx.compositor == BONGOCAT_NULLPTR || wayland_ctx.shm == BONGOCAT_NULLPTR || + wayland_ctx.layer_shell == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Missing required Wayland protocols"); wl_registry_destroy(registry); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; @@ -209,10 +211,11 @@ bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { } // move new registry - if (wayland_ctx.registry) + if (wayland_ctx.registry != BONGOCAT_NULLPTR) { wl_registry_destroy(wayland_ctx.registry); + } wayland_ctx.registry = registry; - registry = nullptr; + registry = BONGOCAT_NULLPTR; for (size_t i = 0; i < ctx.output_count && i < MAX_OUTPUTS; i++) { ctx.outputs[i].wayland = &ctx; @@ -227,11 +230,11 @@ bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; // read-only config - assert(wayland_ctx._local_copy_config != nullptr); + assert(wayland_ctx._local_copy_config); const config::config_t& current_config = *wayland_ctx._local_copy_config; wayland_ctx.surface = wl_compositor_create_surface(wayland_ctx.compositor); - if (!wayland_ctx.surface) { + if (wayland_ctx.surface == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to create surface"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -253,7 +256,7 @@ bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { } wayland_ctx.layer_surface = zwlr_layer_shell_v1_get_layer_surface(wayland_ctx.layer_shell, wayland_ctx.surface, wayland_ctx.output, layer, WAYLAND_LAYER_NAMESPACE); - if (!wayland_ctx.layer_surface) { + if (wayland_ctx.layer_surface == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to create layer surface"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -289,9 +292,10 @@ bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { // Make surface click-through wl_region *input_region = wl_compositor_create_region(wayland_ctx.compositor); - if (input_region) { + if (input_region != BONGOCAT_NULLPTR) { wl_surface_set_input_region(wayland_ctx.surface, input_region); wl_region_destroy(input_region); + input_region = BONGOCAT_NULLPTR; } wl_surface_commit(wayland_ctx.surface); @@ -303,7 +307,7 @@ bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animation::animation_session_t& anim) { // read-only config - assert(wayland_context._local_copy_config != nullptr); + assert(wayland_context._local_copy_config); // const config::config_t& current_config = *wayland_context._local_copy_config; wayland_shared_memory_t& wayland_ctx_shm = *wayland_context.ctx_shm; @@ -336,7 +340,7 @@ bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animat } wl_shm_pool *pool = wl_shm_create_pool(wayland_context.shm, fd._fd, static_cast(total_size)); - if (!pool) { + if (pool == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to create shared memory pool"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -353,7 +357,7 @@ bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animat assert(static_cast(buffer_size) <= SIZE_MAX); wayland_ctx_shm.buffers[i].pixels = make_allocated_mmap_file_buffer_value(0, buffer_size, fd._fd, offset); - if (wayland_ctx_shm.buffers[i].pixels == nullptr) { + if (!wayland_ctx_shm.buffers[i].pixels) { BONGOCAT_LOG_ERROR("Failed to map shared memory: %s", strerror(errno)); for (size_t j = 0; j < i; j++) { cleanup_shm_buffer(wayland_ctx_shm.buffers[j]); @@ -369,7 +373,7 @@ bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animat wayland_ctx_shm.buffers[i].buffer = wl_shm_pool_create_buffer( pool, static_cast(offset), wayland_context._screen_width, wayland_context._bar_height, wayland_context._screen_width * RGBA_CHANNELS, WL_SHM_FORMAT_ARGB8888); - if (wayland_ctx_shm.buffers[i].buffer == nullptr) { + if (wayland_ctx_shm.buffers[i].buffer == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to create buffer"); for (size_t j = 0; j < i; j++) { cleanup_shm_buffer(wayland_ctx_shm.buffers[j]); diff --git a/src/platform/wayland_sway.cpp b/src/platform/wayland_sway.cpp index 1904c03c..fb386562 100644 --- a/src/platform/wayland_sway.cpp +++ b/src/platform/wayland_sway.cpp @@ -16,7 +16,7 @@ namespace bongocat::platform::wayland::sway { int fs_check_compositor_fallback() { FILE *fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); - if (fp) { + if (fp != BONGOCAT_NULLPTR) { bool is_fullscreen = false; char sway_buffer[SWAY_BUF] = {0}; diff --git a/src/utils/error.cpp b/src/utils/error.cpp index 362520a8..b38e9a70 100644 --- a/src/utils/error.cpp +++ b/src/utils/error.cpp @@ -34,7 +34,7 @@ namespace details { tm tm_info{}; char timestamp[64] = {0}; - gettimeofday(&tv, nullptr); + gettimeofday(&tv, BONGOCAT_NULLPTR); localtime_r(&tv.tv_sec, &tm_info); // Thread-safe version strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", &tm_info); @@ -77,8 +77,9 @@ namespace details { } void log_debug(const char *fmt, ...) { - if (!atomic_load(&get_debug_enabled())) + if (!atomic_load(&get_debug_enabled())) { return; + } va_list args; va_start(args, fmt); log_vprintf("DEBUG", fmt, args); @@ -86,8 +87,9 @@ namespace details { } void log_verbose(const char *fmt, ...) { - if (!atomic_load(&get_debug_enabled())) + if (!atomic_load(&get_debug_enabled())) { return; + } va_list args; va_start(args, fmt); log_vprintf("VERBOSE", fmt, args); diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp index e04587dd..cc83a0e9 100644 --- a/src/utils/memory.cpp +++ b/src/utils/memory.cpp @@ -24,15 +24,15 @@ namespace details { #ifndef NDEBUG struct allocation_record_t { - void *ptr{nullptr}; + void *ptr{BONGOCAT_NULLPTR}; size_t size{0}; const char *file{}; int line{0}; - allocation_record_t *next{nullptr}; + allocation_record_t *next{BONGOCAT_NULLPTR}; }; inline allocation_record_t *& get_allocations() { - static allocation_record_t *g_instance = nullptr; + static allocation_record_t *g_instance = BONGOCAT_NULLPTR; return g_instance; } #endif @@ -41,13 +41,13 @@ namespace details { void *malloc(size_t size) { if (size == 0) { BONGOCAT_LOG_WARNING("Attempted to allocate 0 bytes"); - return nullptr; + return BONGOCAT_NULLPTR; } void *ptr = ::malloc(size); - if (!ptr) { + if (ptr == BONGOCAT_NULLPTR) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to allocate %zu bytes", size); - return nullptr; + return BONGOCAT_NULLPTR; } #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) @@ -69,20 +69,20 @@ void *malloc(size_t size) { void *calloc(size_t count, size_t size) { if (count == 0 || size == 0) { BONGOCAT_LOG_WARNING("Attempted to allocate 0 bytes"); - return nullptr; + return BONGOCAT_NULLPTR; } // Check for overflow assert(size > 0); if (count > SIZE_MAX / size) { BONGOCAT_LOG_ERROR("Integer overflow in calloc"); - return nullptr; + return BONGOCAT_NULLPTR; } void *ptr = ::calloc(count, size); - if (!ptr) { + if (ptr == BONGOCAT_NULLPTR) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to allocate %zu bytes", count * size); - return nullptr; + return BONGOCAT_NULLPTR; } #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) @@ -105,13 +105,13 @@ void *calloc(size_t count, size_t size) { void *bongocat_realloc(void *ptr, size_t size) { if (size == 0) { bongocat::free(ptr); - return nullptr; + return BONGOCAT_NULLPTR; } void *new_ptr = ::realloc(ptr, size); - if (!new_ptr) { + if (new_ptr == BONGOCAT_NULLPTR) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to reallocate to %zu bytes", size); - return nullptr; + return BONGOCAT_NULLPTR; } // Note: We can't track size changes accurately without storing original sizes @@ -121,8 +121,9 @@ void *bongocat_realloc(void *ptr, size_t size) { } void free(void *ptr) { - if (!ptr) + if (ptr == BONGOCAT_NULLPTR) { return; + } ::free(ptr); @@ -144,7 +145,7 @@ void free(void *ptr) { memory_pool_t *memory_pool_create(size_t size, size_t alignment) { if (size == 0 || alignment == 0) { BONGOCAT_LOG_ERROR("Invalid memory pool parameters"); - return nullptr; + return BONGOCAT_NULLPTR; } // Validate alignment is a power of 2 @@ -154,13 +155,14 @@ memory_pool_t *memory_pool_create(size_t size, size_t alignment) { } auto *pool = static_cast(bongocat::malloc(sizeof(memory_pool_t))); - if (!pool) - return nullptr; + if (pool == BONGOCAT_NULLPTR) [[unlikely]] { + return BONGOCAT_NULLPTR; + } pool->data = bongocat::malloc(size); - if (!pool->data) { + if (pool->data == BONGOCAT_NULLPTR) [[unlikely]] { bongocat::free(pool); - return nullptr; + return BONGOCAT_NULLPTR; } pool->size = size; @@ -171,15 +173,16 @@ memory_pool_t *memory_pool_create(size_t size, size_t alignment) { } void *memory_pool_alloc(memory_pool_t& pool, size_t size) { - if (size == 0) - return nullptr; + if (size == 0) { + return BONGOCAT_NULLPTR; + } // Align the size const size_t aligned_size = (size + pool.alignment - 1) & ~(pool.alignment - 1); if (pool.used + aligned_size > pool.size) { BONGOCAT_LOG_ERROR("Memory pool exhausted"); - return nullptr; + return BONGOCAT_NULLPTR; } void *ptr = static_cast(pool.data) + pool.used; @@ -189,13 +192,13 @@ void *memory_pool_alloc(memory_pool_t& pool, size_t size) { } void memory_pool_reset(memory_pool_t *pool) { - if (pool) { + if (pool != BONGOCAT_NULLPTR) { pool->used = 0; } } void memory_pool_destroy(memory_pool_t *pool) { - if (pool) { + if (pool != BONGOCAT_NULLPTR) { bongocat::free(pool->data); bongocat::free(pool); } @@ -203,8 +206,9 @@ void memory_pool_destroy(memory_pool_t *pool) { #if !defined(BONGOCAT_DISABLE_MEMORY_STATISTICS) || defined(BONGOCAT_ENABLE_MEMORY_STATISTICS) void memory_get_stats(memory_stats_t *stats) { - if (!stats) + if (stats == BONGOCAT_NULLPTR) { return; + } { using namespace details; @@ -253,8 +257,9 @@ void memory_print_stats() {} #ifndef NDEBUG void *malloc_debug(size_t size, const char *file, int line) { void *ptr = bongocat::malloc(size); - if (!ptr) - return nullptr; + if (ptr == BONGOCAT_NULLPTR) { + return BONGOCAT_NULLPTR; + } { using namespace details; diff --git a/src/utils/system_memory.cpp b/src/utils/system_memory.cpp index d72c0bf8..c3270720 100644 --- a/src/utils/system_memory.cpp +++ b/src/utils/system_memory.cpp @@ -11,8 +11,9 @@ static inline constexpr time_ms_t THREAD_SlEEP_WHEN_WAITING_FOR_THREAD_MS = 100; static inline constexpr int THREAD_SLEEP_MAX_ATTEMPTS = 2048; int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms) { - if (thread == 0) + if (thread == 0) { return 0; + } timespec start{}; timespec now{}; @@ -20,23 +21,25 @@ int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms) { int attempts = 0; while (attempts < THREAD_SLEEP_MAX_ATTEMPTS) { - const int ret = pthread_tryjoin_np(thread, nullptr); + const int ret = pthread_tryjoin_np(thread, BONGOCAT_NULLPTR); if (ret == 0) { thread = 0; return 0; } - if (ret != EBUSY) + if (ret != EBUSY) { return ret; // error other than "still running" + } // Check elapsed time clock_gettime(CLOCK_MONOTONIC, &now); - const time_ms_t elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 + (now.tv_nsec - start.tv_nsec) / 1000000L; - if (elapsed_ms >= timeout_ms) + const time_ms_t elapsed_ms = ((now.tv_sec - start.tv_sec) * 1000L) + ((now.tv_nsec - start.tv_nsec) / 1000000L); + if (elapsed_ms >= timeout_ms) { return ETIMEDOUT; + } // small sleep to avoid busy waiting timespec ts = {.tv_sec = 0, .tv_nsec = 1000000L * THREAD_SlEEP_WHEN_WAITING_FOR_THREAD_MS}; - nanosleep(&ts, nullptr); + nanosleep(&ts, BONGOCAT_NULLPTR); attempts++; } @@ -44,15 +47,16 @@ int join_thread_with_timeout(pthread_t& thread, time_ms_t timeout_ms) { } int stop_thread_graceful_or_cancel(pthread_t& thread, atomic_bool& running_flag) { - if (thread == 0) + if (thread == 0) { return 0; + } atomic_store(&running_flag, false); const int ret = join_thread_with_timeout(thread, THREAD_JOIN_TIMEOUT_MS); if (thread != 0 && ret == ETIMEDOUT) { BONGOCAT_LOG_WARNING("Thread did not exit in time, cancelling: %dms", THREAD_JOIN_TIMEOUT_MS); pthread_cancel(thread); - pthread_join(thread, nullptr); + pthread_join(thread, BONGOCAT_NULLPTR); } thread = 0; diff --git a/src/utils/time.cpp b/src/utils/time.cpp index 10ab1314..49d4ef11 100644 --- a/src/utils/time.cpp +++ b/src/utils/time.cpp @@ -7,10 +7,10 @@ namespace bongocat::platform { timestamp_us_t get_current_time_us() { timeval now{}; gettimeofday(&now, nullptr); - return now.tv_sec * 1000000LL + now.tv_usec; + return (now.tv_sec * 1000000LL) + now.tv_usec; } timestamp_ms_t get_current_time_ms() { - return get_current_time_us() / 1000; + return get_current_time_us() / 1000L; } time_us_t get_uptime_us() { @@ -18,9 +18,9 @@ time_us_t get_uptime_us() { if (clock_gettime(CLOCK_BOOTTIME, &ts) != 0) { return 0; } - return ts.tv_sec * 1000000000LL + ts.tv_nsec; + return (ts.tv_sec * 1000000000LL) + ts.tv_nsec; } time_ms_t get_uptime_ms() { - return get_uptime_us() / 1000; + return get_uptime_us() / 1000L; } } // namespace bongocat::platform \ No newline at end of file From d16553c9ca3c3a75b3e22dc85c1cf2d49a8b1dba Mon Sep 17 00:00:00 2001 From: furudbat Date: Tue, 9 Dec 2025 16:43:16 +0100 Subject: [PATCH 14/18] Update README --- .clang-tidy | 6 ++ Makefile.old | 6 +- README.md | 155 +++++++++++++++++++++++++-------------------------- 3 files changed, 87 insertions(+), 80 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b796e4cf..044b6725 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -21,6 +21,8 @@ Checks: > -readability-else-after-return, -readability-uppercase-literal-suffix, -misc-use-anonymous-namespace, + -readability-non-const-parameter, + -performance-enum-size, # Only analyze project headers, not system/lib headers HeaderFilterRegex: '.*/(include|src)/.*\.(h|hpp)$' @@ -51,6 +53,10 @@ CheckOptions: value: lower_case # - key: readability-identifier-naming.EnumConstantCase # value: UPPER_CASE + - key: readability-identifier-naming.MemberIgnoredRegexp + value: '^_?[a-z]+(_[a-z0-9]+)*$' + - key: readability-identifier-naming.PublicMemberIgnoredRegexp + value: '^_?[a-z]+(_[a-z0-9]+)*$' # Naming conventions (C++ style) - key: readability-identifier-naming.NamespaceCase value: lower_case diff --git a/Makefile.old b/Makefile.old index c561d63d..85a2478e 100644 --- a/Makefile.old +++ b/Makefile.old @@ -90,14 +90,16 @@ ifeq ($(ONLY_BONGOCAT),1) C_SRC += $(SRCDIR)/embedded_assets/bongocat/bongocat_images.c CXX_SRC += $(SRCDIR)/embedded_assets/bongocat/bongocat_get_sprite_sheet.cpp $(SRCDIR)/image_loader/bongocat/load_images_bongocat.cpp else - C_SRC += $(SRCDIR)/embedded_assets/bongocat/bongocat_images.c $(SRCDIR)/embedded_assets/min_dm/min_dm_images.c $(SRCDIR)/embedded_assets/ms_agent/ms_agent_images.c + C_SRC += $(SRCDIR)/embedded_assets/bongocat/bongocat_images.c $(SRCDIR)/embedded_assets/min_dm/min_dm_images.c $(SRCDIR)/embedded_assets/ms_agent/ms_agent_images.c $(SRCDIR)/embedded_assets/pkmn/pkmn_images.c CXX_SRC += $(SRCDIR)/embedded_assets/bongocat/bongocat_get_sprite_sheet.cpp \ $(SRCDIR)/image_loader/bongocat/load_images_bongocat.cpp \ $(SRCDIR)/image_loader/min_dm/load_images_min_dm.cpp \ $(SRCDIR)/image_loader/min_dm/min_dm_load_sprite_sheet.cpp \ $(SRCDIR)/image_loader/base_dm/load_dm.cpp \ - $(SRCDIR)/embedded_assets/min_dm/min_dm_get_sprite_sheet.cpp \ + $(SRCDIR)/image_loader/pkmn/load_images_pkmn.cpp \ $(SRCDIR)/image_loader/ms_agent/load_images_ms_agent.cpp \ + $(SRCDIR)/embedded_assets/pkmn/pkmn_get_sprite_sheet.cpp \ + $(SRCDIR)/embedded_assets/min_dm/min_dm_get_sprite_sheet.cpp \ $(SRCDIR)/embedded_assets/ms_agent/embedded_assets_ms_agent.cpp endif diff --git a/README.md b/README.md index 69a8f00d..a4671918 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ _Pokemon_ - 🎲 Randomize sprite frame at start up - 🔲 React to CPU usage - ↔️ Movement on screen -- ⚡ Lightweight (~6MB RAM) +- ⚡ Lightweight (~10MB RAM) ## Quick Start @@ -129,45 +129,45 @@ keyboard_device=/dev/input/event4
Click to expand all options -| Setting | Type | Range / Options | Default | Description | -|---------------------------|---------|--------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------| -| `cat_height` | Integer | 10–200 | 40 | Height of bongo cat in pixels (width auto-calculated to maintain aspect ratio) | -| `cat_x_offset` | Integer | -16000 to 16000 | 100 | Horizontal offset from center (behavior depends on `cat_align`) | -| `cat_y_offset` | Integer | -16000 to 16000 | 10 | Vertical offset from center (positive=down, negative=up) | -| `cat_align` | String | "center", "left", "right" | "center" | Horizontal alignment of cat inside overlay bar | -| `overlay_height` | Integer | 20–300 | 50 | Height of the entire overlay bar | -| `overlay_position` | String | "top" or "bottom" | "top" | Position of overlay on screen | -| `overlay_opacity` | Integer | 0–255 | 60 | Background opacity (0=transparent, 255=opaque) | -| `overlay_layer` | String | "overlay", "top", "bottom" or "background" | "overlay" | Surface layer of overlay on screen | -| `animation_name` | String | "bongocat", ``, "clippy", `` or "neko" | "bongocat" | Name of the V-Pet sprite (see list below) | -| `invert_color` | Boolean | 0 or 1 | 0 | Invert color (useful for white digimon sprites & dark mode) | -| `idle_frame` | Integer | 0–2 (varies by sprite type) | 0 | Which frame to use when idle (sprite-specific options) | -| `idle_animation` | Boolean | 0 or 1 | 0 | Enable idle animation | -| `animation_speed` | Integer | 0–5000 | 0 | Frame duration in ms (0 = use `fps`) | -| `keypress_duration` | Integer | 50–5000 | 100 | Duration to display keypress animation (ms) | -| `mirror_x` | Boolean | 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | -| `mirror_y` | Boolean | 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | -| `test_animation_duration` | Integer | 0–5000 | 0 | Duration of test animation (ms) (deprecated, use `animation_speed`) | -| `test_animation_interval` | Integer | 0–60 | 0 | Interval for test animation in seconds (0 = disabled, deprecated) | -| `fps` | Integer | 1–144 | 60 | Animation frame rate | -| `input_fps` | Integer | 0–144 | 0 | Input thread frame rate (0 = use `fps`) | -| `enable_scheduled_sleep` | Boolean | 0 or 1 | 0 | Enable scheduled sleep mode | -| `sleep_begin` | String | "00:00" – "23:59" | "00:00" | Start time of scheduled sleep (24h format) | -| `sleep_end` | String | "00:00" – "23:59" | "00:00" | End time of scheduled sleep (24h format) | -| `idle_sleep_timeout` | Integer | 0+ | 0 | Time of inactivity before entering sleep (0 = disabled) (in seconds) | -| `happy_kpm` | Integer | 0–10000 | 0 | Minimum keystrokes per minute to trigger happy animation (0 = disabled) | -| `keyboard_device` | String | Valid `/dev/input/*` path(s) | \ | Input device path (multiple entries allowed) | -| `enable_antialiasing` | Boolean | 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling (Bongocat and MS Agent only) | -| `enable_debug` | Boolean | 0 or 1 | 0 | Enable debug logging | -| `monitor` | String | Monitor name | Auto-detect | Which monitor to display on (e.g., "eDP-1", "HDMI-A-1") | -| `random` | Boolean | 0 or 1 | 0 | Randomize `animation_index` (`animation_name` needs to be set as base sprite sheet set) | -| `random_on_reload` | Boolean | 0 or 1 | 0 | Randomize `animation_index` when reloading config (`random` needs to be `1`) | -| `update_rate` | Integer | 0–10000 | 0 | Check (CPU) states rate (0 = disabled) (in milliseconds) | -| `cpu_threshold` | Double | 0–100 | 0 | Threshold of CPU usage for triggering work animation (0 = disabled) | -| `movement_radius` | Integer | 0-8000 | 0 | Radius of moving area (0 = disabled) | -| `movement_speed` | Integer | 0–5000 | 0 | Movement speed (0 = disabled) | -| `enable_movement_debug` | Boolean | 0 or 1 | 0 | Show Movement area | -| `cpu_running_factor` | Double | 0.0–50.0 | 0 | Speed up factor for 100% CPU, it's linear so the animation slowly speed up (0 = disabled) | +| **Option** | **Values** | **Default** | **Description** | +|--------------------------|--------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------| +| `cat_height` | 10–200 | 40 | Height of bongo cat in pixels (width auto-calculated to maintain aspect ratio) | +| `cat_x_offset` | -16000 to 16000 | 100 | Horizontal offset from center (behavior depends on `cat_align`) | +| `cat_y_offset` | -16000 to 16000 | 10 | Vertical offset from center (positive=down, negative=up) | +| `cat_align` | "center", "left", "right" | "center" | Horizontal alignment of cat inside overlay bar | +| `overlay_height` | 20–300 | 50 | Height of the entire overlay bar | +| `overlay_position` | "top" or "bottom" | "top" | Position of overlay on screen | +| `overlay_opacity` | 0–255 | 60 | Background opacity (0=transparent, 255=opaque) | +| `overlay_layer` | "overlay", "top", "bottom" or "background" | "overlay" | Surface layer of overlay on screen | +| `animation_name` | "bongocat", ``, "clippy", `` or "neko" | "bongocat" | Name of the V-Pet sprite (see list below) | +| `invert_color` | 0 or 1 | 0 | Invert color (useful for white digimon sprites & dark mode) | +| `idle_frame` | 0–2 (varies by sprite type) | 0 | Which frame to use when idle (sprite-specific options) | +| `idle_animation` | 0 or 1 | 0 | Enable idle animation | +| `animation_speed` | 0–5000 | 0 | Frame duration in ms (0 = use `fps`) | +| `keypress_duration` | 50–5000 | 100 | Duration to display keypress animation (ms) | +| `mirror_x` | 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | +| `mirror_y` | 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | +| `test_animation_duration` | 0–5000 | 0 | Duration of test animation (ms) (deprecated, use `animation_speed`) | +| `test_animation_interval` | 0–60 | 0 | Interval for test animation in seconds (0 = disabled, deprecated) | +| `fps` | 1–144 | 60 | Animation frame rate | +| `input_fps` | 0–144 | 0 | Input thread frame rate (0 = use `fps`) | +| `enable_scheduled_sleep` | 0 or 1 | 0 | Enable scheduled sleep mode | +| `sleep_begin` | "00:00" – "23:59" | "00:00" | Start time of scheduled sleep (24h format) | +| `sleep_end` | "00:00" – "23:59" | "00:00" | End time of scheduled sleep (24h format) | +| `idle_sleep_timeout` | 0+ | 0 | Time of inactivity before entering sleep (0 = disabled) (in seconds) | +| `happy_kpm` | 0–10000 | 0 | Minimum keystrokes per minute to trigger happy animation (0 = disabled) | +| `keyboard_device` | Valid `/dev/input/*` path(s) | \ | Input device path (multiple entries allowed) | +| `enable_antialiasing` | 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling (Bongocat and MS Agent only) | +| `enable_debug` | 0 or 1 | 0 | Enable debug logging | +| `monitor` | Monitor name | Auto-detect | Which monitor to display on (e.g., "eDP-1", "HDMI-A-1") | +| `random` | 0 or 1 | 0 | Randomize `animation_index` (`animation_name` needs to be set as base sprite sheet set) | +| `random_on_reload` | 0 or 1 | 0 | Randomize `animation_index` when reloading config (`random` needs to be `1`) | +| `update_rate` | 0–10000 | 0 | Check (CPU) states rate (0 = disabled) (in milliseconds) | +| `cpu_threshold` | 0–100 | 0 | Threshold of CPU usage for triggering work animation (0 = disabled) | +| `movement_radius` | 0-8000 | 0 | Radius of moving area (0 = disabled) | +| `movement_speed` | 0–5000 | 0 | Movement speed (0 = disabled) | +| `enable_movement_debug` | 0 or 1 | 0 | Show Movement area | +| `cpu_running_factor` | 0.0–50.0 | 0 | Speed up factor for 100% CPU, it's linear so the animation slowly speed up (0 = disabled) | #### Available Sprites (`animation_name`) @@ -192,44 +192,43 @@ _If you build with ALL assets included you can void naming conflicts by using th #### Custom Sprite Sheet (`custom_...`) -| **Key** | **Type** | **Range / Example** | **Default** | **Description** | -| ------------------------------------- | -------- |---------------------|-------------------|--------------------------------------------------------------------------------------| -| `animation_name` | String | `"custom"` | | Must be `"custom"` for custom-options to work | -| `custom_sprite_sheet_filename` | String | Path to image file | | Path to the custom sprite sheet image (**must be png**) | -| `custom_idle_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for idle animation | -| `custom_boring_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for boring animation | -| `custom_start_writing_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for start writing animation | -| `custom_writing_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for writing animation | -| `custom_end_writing_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for end writing animation | -| `custom_happy_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for happy animation | -| `custom_asleep_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for falling asleep animation | -| `custom_sleep_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for sleeping animation | -| `custom_wake_up_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for waking up animation | -| `custom_start_working_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for start working animation | -| `custom_working_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for working animation | -| `custom_end_working_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for end working animation | -| `custom_start_moving_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for start moving animation | -| `custom_moving_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for moving animation | -| `custom_end_moving_frames` | Integer | 1-500 | 0 (disabled) | Number of frames for end moving animation | -| `custom_toggle_writing_frames` | Boolean | 0 or 1 | -1 (auto) | Toggle writing frames when writing (`custom_writing_frames` needs to be `2`) | -| `custom_toggle_writing_frames_random` | Boolean | 0 or 1 | -1 (auto) | Randomize writing frame when start writing (`custom_writing_frames` needs to be `2`) | -| `custom_mirror_x_moving` | Boolean | 0 or 1 | -1 (ignore) | Mirror frames horizontally when moving | -| `custom_idle_row` | Integer | 1-15 | -1 (auto) | Row nr for idle animation in sprite sheet | -| `custom_boring_row` | Integer | 1-15 | -1 (auto) | Row nr for boring animation | -| `custom_start_writing_row` | Integer | 1-15 | -1 (auto) | Row nr for start writing animation | -| `custom_writing_row` | Integer | 1-15 | -1 (auto) | Row nr for writing animation | -| `custom_end_writing_row` | Integer | 1-15 | -1 (auto) | Row nr for end writing animation | -| `custom_happy_row` | Integer | 1-15 | -1 (auto) | Row nr for happy animation | -| `custom_asleep_row` | Integer | 1-15 | -1 (auto) | Row nr for asleep animation | -| `custom_sleep_row` | Integer | 1-15 | -1 (auto) | Row nr for sleep animation | -| `custom_wake_up_row` | Integer | 1-15 | -1 (auto) | Row nr for wake-up animation | -| `custom_start_working_row` | Integer | 1-15 | -1 (auto) | Row nr for start working animation | -| `custom_working_row` | Integer | 1-15 | -1 (auto) | Row nr for working animation | -| `custom_end_working_row` | Integer | 1-15 | -1 (auto) | Row nr for end working animation | -| `custom_start_moving_row` | Integer | 1-15 | -1 (auto) | Row nr for start moving animation | -| `custom_moving_row` | Integer | 1-15 | -1 (auto) | Row nr for moving animation | -| `custom_end_moving_row` | Integer | 1-15 | -1 (auto) | Row nr for end moving animation | - +| **Option** | **Values** | **Default** | **Description** | +|---------------------------------------------|--------------------|------------------|--------------------------------------------------------------------------------------| +| `animation_name` | `"custom"` | | Must be `"custom"` for custom-options to work | +| `custom_sprite_sheet_filename` | Path to image file | | Path to the custom sprite sheet image (**must be png**) | +| `custom_idle_frames` | 1-500 | 0 (disabled) | Number of frames for idle animation | +| `custom_boring_frames` | 1-500 | 0 (disabled) | Number of frames for boring animation | +| `custom_start_writing_frames` | 1-500 | 0 (disabled) | Number of frames for start writing animation | +| `custom_writing_frames` | 1-500 | 0 (disabled) | Number of frames for writing animation | +| `custom_end_writing_frames` | 1-500 | 0 (disabled) | Number of frames for end writing animation | +| `custom_happy_frames` | 1-500 | 0 (disabled) | Number of frames for happy animation | +| `custom_asleep_frames` | 1-500 | 0 (disabled) | Number of frames for falling asleep animation | +| `custom_sleep_frames` | 1-500 | 0 (disabled) | Number of frames for sleeping animation | +| `custom_wake_up_frames` | 1-500 | 0 (disabled) | Number of frames for waking up animation | +| `custom_start_working_frames` | 1-500 | 0 (disabled) | Number of frames for start working animation | +| `custom_working_frames` | 1-500 | 0 (disabled) | Number of frames for working animation | +| `custom_end_working_frames` | 1-500 | 0 (disabled) | Number of frames for end working animation | +| `custom_start_moving_frames` | 1-500 | 0 (disabled) | Number of frames for start moving animation | +| `custom_moving_frames` | 1-500 | 0 (disabled) | Number of frames for moving animation | +| `custom_end_moving_frames` | 1-500 | 0 (disabled) | Number of frames for end moving animation | +| `custom_toggle_writing_frames` | 0 or 1 | -1 (auto) | Toggle writing frames when writing (`custom_writing_frames` needs to be `2`) | +| `custom_toggle_writing_frames_random` | 0 or 1 | -1 (auto) | Randomize writing frame when start writing (`custom_writing_frames` needs to be `2`) | +| `custom_mirror_x_moving` | 0 or 1 | -1 (ignore) | Mirror frames horizontally when moving | +| `custom_idle_row` | 1-15 | -1 (auto) | Row nr for idle animation in sprite sheet | +| `custom_boring_row` | 1-15 | -1 (auto) | Row nr for boring animation | +| `custom_start_writing_row` | 1-15 | -1 (auto) | Row nr for start writing animation | +| `custom_writing_row` | 1-15 | -1 (auto) | Row nr for writing animation | +| `custom_end_writing_row` | 1-15 | -1 (auto) | Row nr for end writing animation | +| `custom_happy_row` | 1-15 | -1 (auto) | Row nr for happy animation | +| `custom_asleep_row` | 1-15 | -1 (auto) | Row nr for asleep animation | +| `custom_sleep_row` | 1-15 | -1 (auto) | Row nr for sleep animation | +| `custom_wake_up_row` | 1-15 | -1 (auto) | Row nr for wake-up animation | +| `custom_start_working_row` | 1-15 | -1 (auto) | Row nr for start working animation | +| `custom_working_row` | 1-15 | -1 (auto) | Row nr for working animation | +| `custom_end_working_row` | 1-15 | -1 (auto) | Row nr for end working animation | +| `custom_start_moving_row` | 1-15 | -1 (auto) | Row nr for start moving animation | +| `custom_moving_row` | 1-15 | -1 (auto) | Row nr for moving animation | +| `custom_end_moving_row` | 1-15 | -1 (auto) | Row nr for end moving animation | See [examples](examples/custom-sprite-sheets) for more details. From 64d65d5413d18b6bb0415777f7e778b3d95874d0 Mon Sep 17 00:00:00 2001 From: furudbat Date: Tue, 9 Dec 2025 18:22:54 +0100 Subject: [PATCH 15/18] refactor: rename context classes * fix zwlr_foreign_toplevel_handle_v1_destroy in fs_handle_toplevel_closed --- include/embedded_assets/bongocat/bongocat.h | 4 +- include/graphics/animation.h | 18 +- include/graphics/animation_context.h | 90 +++--- include/graphics/animation_thread_context.h | 66 ++++ include/graphics/global_animation_session.h | 66 ---- include/image_loader/base_dm/load_dm.h | 4 +- .../bongocat/load_images_bongocat.h | 6 +- include/image_loader/custom/load_custom.h | 6 +- include/image_loader/dm/load_images_dm.h | 6 +- include/image_loader/dm20/load_images_dm20.h | 6 +- .../image_loader/dmall/load_images_dmall.h | 6 +- include/image_loader/dmc/load_images_dmc.h | 6 +- include/image_loader/dmx/load_images_dmx.h | 6 +- include/image_loader/load_images.h | 2 +- .../image_loader/min_dm/load_images_min_dm.h | 6 +- include/image_loader/misc/load_images_misc.h | 6 +- .../ms_agent/load_images_ms_agent.h | 8 +- include/image_loader/pen/load_images_pen.h | 6 +- .../image_loader/pen20/load_images_pen20.h | 6 +- include/image_loader/pkmn/load_images_pkmn.h | 8 +- include/image_loader/pmd/load_images_pmd.h | 6 +- include/platform/global_wayland_session.h | 170 ----------- include/platform/input.h | 6 +- include/platform/update.h | 6 +- include/platform/wayland.h | 19 +- include/platform/wayland_callbacks.h | 4 +- include/platform/wayland_context.h | 283 ++++++++---------- include/platform/wayland_setups.h | 12 +- include/platform/wayland_shared_memory.h | 26 +- include/platform/wayland_thread_context.h | 197 ++++++++++++ src/config/config.cpp | 2 +- src/config/config_watcher.cpp | 2 +- src/core/main.cpp | 37 +-- src/graphics/animation.cpp | 191 ++++++------ src/graphics/animation_init.cpp | 84 +++--- src/graphics/bar.cpp | 42 +-- src/graphics/bar.h | 4 +- src/graphics/drawing_images.cpp | 2 +- src/image_loader/base_dm/load_dm.cpp | 4 +- .../bongocat/load_images_bongocat.cpp | 6 +- src/image_loader/custom/load_custom.cpp | 6 +- src/image_loader/dm/dm_load_sprite_sheet.cpp | 4 +- src/image_loader/dm/load_images_dm.cpp | 4 +- .../dm20/dm20_load_sprite_sheet.cpp | 4 +- src/image_loader/dm20/load_images_dm20.cpp | 4 +- .../dmall/dmall_load_sprite_sheet.cpp | 4 +- src/image_loader/dmall/load_images_dmall.cpp | 4 +- .../dmc/dmc_load_sprite_sheet.cpp | 4 +- src/image_loader/dmc/load_images_dmc.cpp | 4 +- .../dmx/dmx_load_sprite_sheet.cpp | 4 +- src/image_loader/dmx/load_images_dmx.cpp | 4 +- src/image_loader/load_images.cpp | 2 +- .../min_dm/load_images_min_dm.cpp | 4 +- .../min_dm/min_dm_load_sprite_sheet.cpp | 4 +- src/image_loader/misc/load_images_misc.cpp | 4 +- .../misc/misc_load_sprite_sheet.cpp | 4 +- .../ms_agent/load_images_ms_agent.cpp | 8 +- src/image_loader/pen/load_images_pen.cpp | 4 +- .../pen/pen_load_sprite_sheet.cpp | 4 +- src/image_loader/pen20/load_images_pen20.cpp | 4 +- .../pen20/pen20_load_sprite_sheet.cpp | 4 +- src/image_loader/pkmn/load_images_pkmn.cpp | 6 +- .../pkmn/pkmn_load_sprite_sheet.cpp | 4 +- src/image_loader/pmd/load_images_pmd.cpp | 4 +- .../pmd/pmd_load_sprite_sheet.cpp | 4 +- src/platform/input.cpp | 56 ++-- src/platform/update.cpp | 50 ++-- src/platform/wayland.cpp | 138 ++++----- src/platform/wayland_callbacks.cpp | 126 ++++---- src/platform/wayland_hyprland.cpp | 2 +- src/platform/wayland_hyprland.h | 4 +- src/platform/wayland_setups.cpp | 21 +- src/platform/wayland_sway.h | 2 +- 73 files changed, 974 insertions(+), 966 deletions(-) create mode 100644 include/graphics/animation_thread_context.h delete mode 100644 include/graphics/global_animation_session.h delete mode 100644 include/platform/global_wayland_session.h create mode 100644 include/platform/wayland_thread_context.h diff --git a/include/embedded_assets/bongocat/bongocat.h b/include/embedded_assets/bongocat/bongocat.h index 83a286d3..7258d1dd 100644 --- a/include/embedded_assets/bongocat/bongocat.h +++ b/include/embedded_assets/bongocat/bongocat.h @@ -8,7 +8,7 @@ #include namespace bongocat::animation { -struct animation_context_t; +struct animation_thread_context_t; } namespace bongocat::assets { @@ -31,7 +31,7 @@ inline static constexpr size_t BONGOCAT_ANIMATIONS_COUNT = 1; BONGOCAT_NODISCARD extern embedded_image_t get_bongocat_sprite(size_t i); BONGOCAT_NODISCARD extern created_result_t -get_bongocat_sprite_sheet(const animation::animation_context_t& ctx, int index); +get_bongocat_sprite_sheet(const animation::animation_thread_context_t& ctx, int index); } // namespace bongocat::assets #endif // BONGOCAT_EMBEDDED_ASSETS_BONGOCAT_H diff --git a/include/graphics/animation.h b/include/graphics/animation.h index 4772fc7f..e2abe1d6 100644 --- a/include/graphics/animation.h +++ b/include/graphics/animation.h @@ -2,8 +2,8 @@ #define BONGOCAT_ANIMATION_H #include "animation_context.h" +#include "animation_thread_context.h" #include "config/config.h" -#include "global_animation_session.h" #include "platform/input_context.h" #include "platform/update_context.h" #include "utils/error.h" @@ -24,24 +24,24 @@ enum class trigger_animation_cause_mask_t : uint64_t { // ============================================================================= // Initialize animation system - must be checked -BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); +BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); // Start animation thread - must be checked -BONGOCAT_NODISCARD bongocat_error_t start(animation_session_t& ctx, platform::input::input_context_t& input, +BONGOCAT_NODISCARD bongocat_error_t start(animation_context_t& ctx, platform::input::input_context_t& input, platform::update::update_context_t& upd, const config::config_t& config, platform::CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); // Trigger key press animation -void trigger(animation_session_t& ctx, trigger_animation_cause_mask_t cause); -void trigger_update_config(animation_session_t& ctx, const config::config_t& config, uint64_t config_generation); +void trigger(animation_context_t& ctx, trigger_animation_cause_mask_t cause); +void trigger_update_config(animation_context_t& ctx, const config::config_t& config, uint64_t config_generation); -void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen); -created_result_t hot_load_animation(animation_context_t& ctx); -BONGOCAT_NODISCARD animation_t& get_current_animation(animation_context_t& ctx); +void update_config(animation_thread_context_t& ctx, const config::config_t& config, uint64_t new_gen); +created_result_t hot_load_animation(animation_thread_context_t& ctx); +BONGOCAT_NODISCARD animation_t& get_current_animation(animation_thread_context_t& ctx); namespace details { - created_result_t anim_load_custom_animation(animation_context_t& ctx, + created_result_t anim_load_custom_animation(animation_thread_context_t& ctx, const config::config_t& config); } } // namespace bongocat::animation diff --git a/include/graphics/animation_context.h b/include/graphics/animation_context.h index 2efa24e4..85e3c0ec 100644 --- a/include/graphics/animation_context.h +++ b/include/graphics/animation_context.h @@ -1,66 +1,66 @@ -#ifndef BONGOCAT_ANIMATION_CONTEXT_H -#define BONGOCAT_ANIMATION_CONTEXT_H +#ifndef BONGOCAT_ANIMATION_EVENT_CONTEXT_H +#define BONGOCAT_ANIMATION_EVENT_CONTEXT_H -#include "animation_shared_memory.h" -#include "config/config.h" -#include "utils/random.h" -#include "utils/system_memory.h" - -#include +#include "graphics/animation_thread_context.h" +#include "platform/input_context.h" +#include "platform/update_context.h" namespace bongocat::animation { -// ============================================================================= -// ANIMATION STATE -// ============================================================================= - struct animation_context_t; -void stop(animation_context_t& ctx); -// Cleanup animation resources -void cleanup(animation_context_t& ctx); +void stop(animation_context_t& anim_ctx); +void cleanup(animation_context_t& anim_ctx); struct animation_context_t { - // local copy from other thread, update after reload (shared memory) - platform::MMapMemory _local_copy_config; - platform::MMapMemory shm; + animation_thread_context_t thread_context; - // Animation system state - atomic_bool _animation_running{false}; - pthread_t _anim_thread{0}; - platform::random_xoshiro128 _rng; - // lock for shm - platform::Mutex anim_lock; + // event file descriptor + platform::FileDescriptor trigger_efd; + platform::FileDescriptor render_efd; - // config reload threading - platform::FileDescriptor update_config_efd; // get new_gen from here - atomic_uint64_t config_seen_generation{0}; - platform::CondVariable config_updated; + // globals (references) + const config::config_t *_config{BONGOCAT_NULLPTR}; + platform::CondVariable *_configs_reloaded_cond{BONGOCAT_NULLPTR}; + platform::input::input_context_t *_input{BONGOCAT_NULLPTR}; + platform::update::update_context_t *_update{BONGOCAT_NULLPTR}; + atomic_uint64_t *_config_generation{BONGOCAT_NULLPTR}; + atomic_bool ready{false}; + platform::CondVariable init_cond; animation_context_t() = default; ~animation_context_t() { cleanup(*this); } - animation_context_t(const animation_context_t&) = delete; - animation_context_t& operator=(const animation_context_t&) = delete; - animation_context_t(animation_context_t&& other) = delete; - animation_context_t& operator=(animation_context_t&& other) = delete; + animation_context_t(const animation_context_t& other) = delete; + animation_context_t& operator=(const animation_context_t& other) = delete; + animation_context_t(animation_context_t&& other) noexcept = delete; + animation_context_t& operator=(animation_context_t&& other) noexcept = delete; }; -inline void cleanup(animation_context_t& ctx) { - if (atomic_load(&ctx._animation_running)) { - stop(ctx); - // ctx.anim_lock should be unlocked - } - atomic_store(&ctx._animation_running, false); - ctx._anim_thread = 0; +inline void stop(animation_context_t& anim_ctx) { + stop(anim_ctx.thread_context); + + anim_ctx._config = BONGOCAT_NULLPTR; + anim_ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + anim_ctx._config_generation = BONGOCAT_NULLPTR; + + anim_ctx.thread_context.config_updated.notify_all(); + atomic_store(&anim_ctx.ready, false); + anim_ctx.init_cond.notify_all(); +} +inline void cleanup(animation_context_t& anim_ctx) { + cleanup(anim_ctx.thread_context); - close_fd(ctx.update_config_efd); - atomic_store(&ctx.config_seen_generation, 0); + platform::close_fd(anim_ctx.trigger_efd); + platform::close_fd(anim_ctx.render_efd); - platform::release_allocated_mmap_memory(ctx.shm); - platform::release_allocated_mmap_memory(ctx._local_copy_config); - ctx._rng = platform::random_xoshiro128(0); + anim_ctx._config = BONGOCAT_NULLPTR; + anim_ctx._input = BONGOCAT_NULLPTR; + anim_ctx._update = BONGOCAT_NULLPTR; + anim_ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; + atomic_store(&anim_ctx.ready, false); + anim_ctx.init_cond.notify_all(); } } // namespace bongocat::animation -#endif // BONGOCAT_ANIMATION_CONTEXT_H +#endif diff --git a/include/graphics/animation_thread_context.h b/include/graphics/animation_thread_context.h new file mode 100644 index 00000000..eeec8f2f --- /dev/null +++ b/include/graphics/animation_thread_context.h @@ -0,0 +1,66 @@ +#ifndef BONGOCAT_ANIMATION_CONTEXT_H +#define BONGOCAT_ANIMATION_CONTEXT_H + +#include "animation_shared_memory.h" +#include "config/config.h" +#include "utils/random.h" +#include "utils/system_memory.h" + +#include + +namespace bongocat::animation { + +// ============================================================================= +// ANIMATION STATE +// ============================================================================= + +struct animation_thread_context_t; +void stop(animation_thread_context_t& ctx); +// Cleanup animation resources +void cleanup(animation_thread_context_t& ctx); + +struct animation_thread_context_t { + // local copy from other thread, update after reload (shared memory) + platform::MMapMemory _local_copy_config; + platform::MMapMemory shm; + + // Animation system state + atomic_bool _animation_running{false}; + pthread_t _anim_thread{0}; + platform::random_xoshiro128 _rng; + // lock for shm + platform::Mutex anim_lock; + + // config reload threading + platform::FileDescriptor update_config_efd; // get new_gen from here + atomic_uint64_t config_seen_generation{0}; + platform::CondVariable config_updated; + + animation_thread_context_t() = default; + ~animation_thread_context_t() { + cleanup(*this); + } + + animation_thread_context_t(const animation_thread_context_t&) = delete; + animation_thread_context_t& operator=(const animation_thread_context_t&) = delete; + animation_thread_context_t(animation_thread_context_t&& other) = delete; + animation_thread_context_t& operator=(animation_thread_context_t&& other) = delete; +}; +inline void cleanup(animation_thread_context_t& ctx) { + if (atomic_load(&ctx._animation_running)) { + stop(ctx); + // ctx.anim_lock should be unlocked + } + atomic_store(&ctx._animation_running, false); + ctx._anim_thread = 0; + + close_fd(ctx.update_config_efd); + atomic_store(&ctx.config_seen_generation, 0); + + platform::release_allocated_mmap_memory(ctx.shm); + platform::release_allocated_mmap_memory(ctx._local_copy_config); + ctx._rng = platform::random_xoshiro128(0); +} +} // namespace bongocat::animation + +#endif // BONGOCAT_ANIMATION_CONTEXT_H diff --git a/include/graphics/global_animation_session.h b/include/graphics/global_animation_session.h deleted file mode 100644 index 561ab522..00000000 --- a/include/graphics/global_animation_session.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef BONGOCAT_ANIMATION_EVENT_CONTEXT_H -#define BONGOCAT_ANIMATION_EVENT_CONTEXT_H - -#include "graphics/animation_context.h" -#include "platform/input_context.h" -#include "platform/update_context.h" - -namespace bongocat::animation { - -struct animation_session_t; -void stop(animation_session_t& anim_ctx); -void cleanup(animation_session_t& anim_ctx); - -struct animation_session_t { - animation_context_t anim; - - // event file descriptor - platform::FileDescriptor trigger_efd; - platform::FileDescriptor render_efd; - - // globals (references) - const config::config_t *_config{BONGOCAT_NULLPTR}; - platform::CondVariable *_configs_reloaded_cond{BONGOCAT_NULLPTR}; - platform::input::input_context_t *_input{BONGOCAT_NULLPTR}; - platform::update::update_context_t *_update{BONGOCAT_NULLPTR}; - atomic_uint64_t *_config_generation{BONGOCAT_NULLPTR}; - atomic_bool ready{false}; - platform::CondVariable init_cond; - - animation_session_t() = default; - ~animation_session_t() { - cleanup(*this); - } - - animation_session_t(const animation_session_t& other) = delete; - animation_session_t& operator=(const animation_session_t& other) = delete; - animation_session_t(animation_session_t&& other) noexcept = delete; - animation_session_t& operator=(animation_session_t&& other) noexcept = delete; -}; -inline void stop(animation_session_t& anim_ctx) { - stop(anim_ctx.anim); - - anim_ctx._config = BONGOCAT_NULLPTR; - anim_ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; - anim_ctx._config_generation = BONGOCAT_NULLPTR; - - anim_ctx.anim.config_updated.notify_all(); - atomic_store(&anim_ctx.ready, false); - anim_ctx.init_cond.notify_all(); -} -inline void cleanup(animation_session_t& anim_ctx) { - cleanup(anim_ctx.anim); - - platform::close_fd(anim_ctx.trigger_efd); - platform::close_fd(anim_ctx.render_efd); - - anim_ctx._config = BONGOCAT_NULLPTR; - anim_ctx._input = BONGOCAT_NULLPTR; - anim_ctx._update = BONGOCAT_NULLPTR; - anim_ctx._configs_reloaded_cond = BONGOCAT_NULLPTR; - atomic_store(&anim_ctx.ready, false); - anim_ctx.init_cond.notify_all(); -} -} // namespace bongocat::animation - -#endif diff --git a/include/image_loader/base_dm/load_dm.h b/include/image_loader/base_dm/load_dm.h index 4360feb9..a9e590ce 100644 --- a/include/image_loader/base_dm/load_dm.h +++ b/include/image_loader/base_dm/load_dm.h @@ -5,8 +5,8 @@ #include "graphics/sprite_sheet.h" namespace bongocat::animation { -struct animation_context_t; -BONGOCAT_NODISCARD created_result_t load_dm_anim(const animation_context_t& ctx, int anim_index, +struct animation_thread_context_t; +BONGOCAT_NODISCARD created_result_t load_dm_anim(const animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); } // namespace bongocat::animation diff --git a/include/image_loader/bongocat/load_images_bongocat.h b/include/image_loader/bongocat/load_images_bongocat.h index 4635ec4b..d0fdcb83 100644 --- a/include/image_loader/bongocat/load_images_bongocat.h +++ b/include/image_loader/bongocat/load_images_bongocat.h @@ -5,12 +5,12 @@ #include "image_loader/load_images.h" namespace bongocat::animation { -struct animation_context_t; +struct animation_thread_context_t; BONGOCAT_NODISCARD created_result_t load_bongocat_anim(int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); -bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, +bongocat_error_t init_bongocat_anim(animation_thread_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count); -BONGOCAT_NODISCARD created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, +BONGOCAT_NODISCARD created_result_t load_bongocat_sprite_sheet(const animation_thread_context_t& /*ctx*/, int index); } // namespace bongocat::animation diff --git a/include/image_loader/custom/load_custom.h b/include/image_loader/custom/load_custom.h index c372f188..b36c2f7a 100644 --- a/include/image_loader/custom/load_custom.h +++ b/include/image_loader/custom/load_custom.h @@ -48,14 +48,14 @@ struct custom_image_t { } // namespace bongocat::assets namespace bongocat::animation { -struct animation_context_t; +struct animation_thread_context_t; BONGOCAT_NODISCARD created_result_t load_custom_sprite_sheet_file(const char *filename); BONGOCAT_NODISCARD created_result_t -load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, +load_custom_anim(const animation_thread_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); BONGOCAT_NODISCARD created_result_t -load_custom_anim(const animation_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, +load_custom_anim(const animation_thread_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); } // namespace bongocat::animation diff --git a/include/image_loader/dm/load_images_dm.h b/include/image_loader/dm/load_images_dm.h index 39e39387..877df87f 100644 --- a/include/image_loader/dm/load_images_dm.h +++ b/include/image_loader/dm/load_images_dm.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_dm_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_dm_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_dm_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_dm_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/dm20/load_images_dm20.h b/include/image_loader/dm20/load_images_dm20.h index c760c8f0..c631a360 100644 --- a/include/image_loader/dm20/load_images_dm20.h +++ b/include/image_loader/dm20/load_images_dm20.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_dm20_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_dm20_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_dm20_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_dm20_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/dmall/load_images_dmall.h b/include/image_loader/dmall/load_images_dmall.h index 99adada1..c36108af 100644 --- a/include/image_loader/dmall/load_images_dmall.h +++ b/include/image_loader/dmall/load_images_dmall.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_dmall_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_dmall_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_dmall_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_dmall_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/dmc/load_images_dmc.h b/include/image_loader/dmc/load_images_dmc.h index 5cdda86c..85cadb72 100644 --- a/include/image_loader/dmc/load_images_dmc.h +++ b/include/image_loader/dmc/load_images_dmc.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_dmc_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_dmc_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_dmc_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_dmc_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/dmx/load_images_dmx.h b/include/image_loader/dmx/load_images_dmx.h index 497fcb63..0281a450 100644 --- a/include/image_loader/dmx/load_images_dmx.h +++ b/include/image_loader/dmx/load_images_dmx.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_dmx_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_dmx_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_dmx_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_dmx_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/load_images.h b/include/image_loader/load_images.h index 2c231e21..bdc7fcf1 100644 --- a/include/image_loader/load_images.h +++ b/include/image_loader/load_images.h @@ -90,7 +90,7 @@ class Image { using get_sprite_callback_t = assets::embedded_image_t (*)(size_t); -struct animation_context_t; +struct animation_thread_context_t; BONGOCAT_NODISCARD created_result_t anim_sprite_sheet_from_embedded_images(get_sprite_callback_t get_sprite, size_t embedded_images_count); diff --git a/include/image_loader/min_dm/load_images_min_dm.h b/include/image_loader/min_dm/load_images_min_dm.h index dc1e1b5a..a64c5e20 100644 --- a/include/image_loader/min_dm/load_images_min_dm.h +++ b/include/image_loader/min_dm/load_images_min_dm.h @@ -5,10 +5,10 @@ #include "graphics/sprite_sheet.h" namespace bongocat::animation { -struct animation_context_t; -bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, +struct animation_thread_context_t; +bongocat_error_t init_min_dm_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); -BONGOCAT_NODISCARD created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index); +BONGOCAT_NODISCARD created_result_t load_min_dm_sprite_sheet(const animation_thread_context_t& ctx, int index); } // namespace bongocat::animation diff --git a/include/image_loader/misc/load_images_misc.h b/include/image_loader/misc/load_images_misc.h index bfce9733..b1c9122e 100644 --- a/include/image_loader/misc/load_images_misc.h +++ b/include/image_loader/misc/load_images_misc.h @@ -6,10 +6,10 @@ #include "graphics/sprite_sheet.h" namespace bongocat::animation { -struct animation_context_t; -bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, +struct animation_thread_context_t; +bongocat_error_t init_misc_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); -BONGOCAT_NODISCARD created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index); +BONGOCAT_NODISCARD created_result_t load_misc_sprite_sheet(const animation_thread_context_t& ctx, int index); } // namespace bongocat::animation diff --git a/include/image_loader/ms_agent/load_images_ms_agent.h b/include/image_loader/ms_agent/load_images_ms_agent.h index 4d06e5f5..2f66d952 100644 --- a/include/image_loader/ms_agent/load_images_ms_agent.h +++ b/include/image_loader/ms_agent/load_images_ms_agent.h @@ -6,19 +6,19 @@ #include "graphics/sprite_sheet.h" namespace bongocat::animation { -struct animation_context_t; +struct animation_thread_context_t; BONGOCAT_NODISCARD created_result_t load_ms_agent_sprite_sheet(const config::config_t& config, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); BONGOCAT_NODISCARD created_result_t -load_ms_agent_anim(const animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, +load_ms_agent_anim(const animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data); created_result_t -init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, +init_ms_agent_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data); -BONGOCAT_NODISCARD created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, +BONGOCAT_NODISCARD created_result_t load_ms_agent_sprite_sheet(const animation_thread_context_t& ctx, int index); } // namespace bongocat::animation diff --git a/include/image_loader/pen/load_images_pen.h b/include/image_loader/pen/load_images_pen.h index 94dd7217..4eadf5e5 100644 --- a/include/image_loader/pen/load_images_pen.h +++ b/include/image_loader/pen/load_images_pen.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_pen_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_pen_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_pen_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_pen_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/pen20/load_images_pen20.h b/include/image_loader/pen20/load_images_pen20.h index 70156954..61cf255a 100644 --- a/include/image_loader/pen20/load_images_pen20.h +++ b/include/image_loader/pen20/load_images_pen20.h @@ -5,8 +5,8 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_pen20_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + bongocat_error_t init_pen20_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_pen20_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_pen20_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/pkmn/load_images_pkmn.h b/include/image_loader/pkmn/load_images_pkmn.h index 93d0251a..cc61f4d5 100644 --- a/include/image_loader/pkmn/load_images_pkmn.h +++ b/include/image_loader/pkmn/load_images_pkmn.h @@ -5,10 +5,10 @@ #include "embedded_assets/embedded_image.h" namespace bongocat::animation { - struct animation_context_t; - [[nodiscard]] created_result_t load_pkmn_anim(const animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + struct animation_thread_context_t; + [[nodiscard]] created_result_t load_pkmn_anim(const animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - bongocat_error_t init_pkmn_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); + bongocat_error_t init_pkmn_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows); - [[nodiscard]] created_result_t load_pkmn_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_pkmn_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/image_loader/pmd/load_images_pmd.h b/include/image_loader/pmd/load_images_pmd.h index 43946875..88d2be18 100644 --- a/include/image_loader/pmd/load_images_pmd.h +++ b/include/image_loader/pmd/load_images_pmd.h @@ -6,8 +6,8 @@ #include "embedded_assets/custom/custom_sprite.h" namespace bongocat::animation { - struct animation_context_t; - bongocat_error_t init_pmd_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); + struct animation_thread_context_t; + bongocat_error_t init_pmd_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings); - [[nodiscard]] created_result_t load_pmd_sprite_sheet(const animation_context_t& ctx, int index); + [[nodiscard]] created_result_t load_pmd_sprite_sheet(const animation_thread_context_t& ctx, int index); } diff --git a/include/platform/global_wayland_session.h b/include/platform/global_wayland_session.h deleted file mode 100644 index 447e2910..00000000 --- a/include/platform/global_wayland_session.h +++ /dev/null @@ -1,170 +0,0 @@ -#ifndef BONGOCAT_GLOBAL_WAYLAND_CONTEXT_CONTEXT_H -#define BONGOCAT_GLOBAL_WAYLAND_CONTEXT_CONTEXT_H - -#include "graphics/animation_context.h" -#include "graphics/global_animation_session.h" -#include "platform/wayland-protocols.hpp" -#include "wayland_context.h" - -#include - -namespace bongocat::platform::wayland { -// max. windows to track for fullscreen detection -inline static constexpr size_t MAX_TOP_LEVELS = 128; -inline static constexpr size_t MAX_OUTPUTS = 8; // Maximum monitor outputs to store -inline static constexpr size_t OUTPUT_NAME_SIZE = 128; - -// ============================================================================= -// FULLSCREEN DETECTION MODULE -// ============================================================================= - -struct fullscreen_detector_t { - struct zwlr_foreign_toplevel_manager_v1 *manager{BONGOCAT_NULLPTR}; - bool has_fullscreen_toplevel{false}; - timeval last_check{}; -}; -struct tracked_toplevel_t { - struct zwlr_foreign_toplevel_handle_v1 *handle{BONGOCAT_NULLPTR}; - wl_output *output{BONGOCAT_NULLPTR}; - bool is_fullscreen{false}; -}; - -// ============================================================================= -// SCREEN DIMENSION MANAGEMENT -// ============================================================================= - -enum class screen_info_received_flags_t : uint32_t { - None = (1U << 0), - Mode = (1U << 1), - Geometry = (1U << 2), -}; -struct screen_info_t { - struct wl_output *wl_output{BONGOCAT_NULLPTR}; // ref of output - int screen_width{0}; - int screen_height{0}; - int transform{0}; - int raw_width{0}; - int raw_height{0}; - screen_info_received_flags_t received{screen_info_received_flags_t::None}; -}; - -struct wayland_session_t; - -enum class output_ref_received_flags_t : uint32_t { - None = (1u << 0), - Name = (1u << 1), - LogicalPosition = (1u << 2), - LogicalSize = (1u << 3), -}; -// Output monitor reference structure -struct output_ref_t { - struct wl_output *wl_output{BONGOCAT_NULLPTR}; - zxdg_output_v1 *xdg_output{BONGOCAT_NULLPTR}; - uint32_t name{0}; // Registry name - char name_str[OUTPUT_NAME_SIZE]{}; // From xdg-output - int32_t x{0}; - int32_t y{0}; - int32_t width{0}; - int32_t height{0}; - output_ref_received_flags_t received{output_ref_received_flags_t::None}; - // monitor ID in Hyprland - int64_t hypr_id{-1}; - // back reference - wayland_session_t *wayland{BONGOCAT_NULLPTR}; -}; - -void cleanup_wayland(wayland_session_t& ctx); - -struct wayland_session_t { - wayland_context_t wayland_context; - animation::animation_session_t *animation_trigger_context{BONGOCAT_NULLPTR}; - - tracked_toplevel_t tracked_toplevels[MAX_TOP_LEVELS]; - size_t num_toplevels{0}; - - output_ref_t outputs[MAX_OUTPUTS]; - size_t output_count{0}; - zxdg_output_manager_v1 *xdg_output_manager{BONGOCAT_NULLPTR}; - - fullscreen_detector_t fs_detector; - - screen_info_t screen_infos[MAX_OUTPUTS]; - atomic_bool ready{false}; - - // Output reconnection handling - atomic_bool output_lost{false}; // Set when our output disconnects - - wayland_session_t() { - for (size_t i = 0; i < MAX_TOP_LEVELS; i++) { - tracked_toplevels[i] = {}; - } - for (size_t i = 0; i < MAX_OUTPUTS; i++) { - outputs[i] = {}; - } - } - ~wayland_session_t() { - cleanup_wayland(*this); - } - - wayland_session_t(const wayland_session_t&) = delete; - wayland_session_t& operator=(const wayland_session_t&) = delete; - wayland_session_t(wayland_session_t&& other) noexcept = delete; - wayland_session_t& operator=(wayland_session_t&& other) noexcept = delete; -}; - -inline void cleanup_wayland(wayland_session_t& ctx) { - atomic_store(&ctx.ready, false); - - // First destroy xdg_output objects - for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].xdg_output != BONGOCAT_NULLPTR) { - zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); - ctx.outputs[i].xdg_output = BONGOCAT_NULLPTR; - } - } - - // Then destroy the manager - if (ctx.xdg_output_manager != BONGOCAT_NULLPTR) { - zxdg_output_manager_v1_destroy(ctx.xdg_output_manager); - ctx.xdg_output_manager = BONGOCAT_NULLPTR; - } - - // Finally destroy wl_output objects - for (size_t i = 0; i < ctx.output_count; ++i) { - if (ctx.outputs[i].wl_output != BONGOCAT_NULLPTR) { - wl_output_destroy(ctx.outputs[i].wl_output); - ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; - } - ctx.outputs[i] = {}; - ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; - ctx.outputs[i].wayland = BONGOCAT_NULLPTR; - } - ctx.output_count = 0; - - if (ctx.fs_detector.manager != BONGOCAT_NULLPTR) { - zwlr_foreign_toplevel_manager_v1_destroy(ctx.fs_detector.manager); - ctx.fs_detector.manager = BONGOCAT_NULLPTR; - } - - for (size_t i = 0; i < ctx.num_toplevels; ++i) { - if (ctx.tracked_toplevels[i].handle != BONGOCAT_NULLPTR) { - zwlr_foreign_toplevel_handle_v1_destroy(ctx.tracked_toplevels[i].handle); - ctx.tracked_toplevels[i].handle = BONGOCAT_NULLPTR; - } - ctx.tracked_toplevels[i] = {}; - } - ctx.num_toplevels = 0; - - ctx.fs_detector = {}; - for (size_t i = 0; i < MAX_OUTPUTS; ++i) { - ctx.screen_infos[i] = {}; - } - - // clean up wayland context - cleanup_wayland_context(ctx.wayland_context); - - ctx.animation_trigger_context = BONGOCAT_NULLPTR; -} -} // namespace bongocat::platform::wayland - -#endif diff --git a/include/platform/input.h b/include/platform/input.h index 52bcaaf9..b1696303 100644 --- a/include/platform/input.h +++ b/include/platform/input.h @@ -2,7 +2,7 @@ #define BONGOCAT_INPUT_H #include "config/config.h" -#include "graphics/global_animation_session.h" +#include "graphics/animation_context.h" #include "input_context.h" #include "utils/error.h" @@ -15,12 +15,12 @@ namespace bongocat::platform::input { BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); // Start input monitoring - must be checked -BONGOCAT_NODISCARD bongocat_error_t start(input_context_t& input, animation::animation_session_t& trigger_ctx, +BONGOCAT_NODISCARD bongocat_error_t start(input_context_t& input, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); // Restart input monitoring with new devices - must be checked -BONGOCAT_NODISCARD bongocat_error_t restart(input_context_t& input, animation::animation_session_t& trigger_ctx, +BONGOCAT_NODISCARD bongocat_error_t restart(input_context_t& input, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); void trigger_update_config(input_context_t& ctx, const config::config_t& config, uint64_t config_generation); diff --git a/include/platform/update.h b/include/platform/update.h index 031863c4..968f573f 100644 --- a/include/platform/update.h +++ b/include/platform/update.h @@ -2,16 +2,16 @@ #define BONGOCAT_UPDATE_H #include "config/config.h" -#include "graphics/global_animation_session.h" +#include "graphics/animation_context.h" #include "update_context.h" #include "utils/error.h" namespace bongocat::platform::update { BONGOCAT_NODISCARD created_result_t> create(const config::config_t& config); -BONGOCAT_NODISCARD bongocat_error_t start(update_context_t& input, animation::animation_session_t& trigger_ctx, +BONGOCAT_NODISCARD bongocat_error_t start(update_context_t& input, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); -BONGOCAT_NODISCARD bongocat_error_t restart(update_context_t& input, animation::animation_session_t& trigger_ctx, +BONGOCAT_NODISCARD bongocat_error_t restart(update_context_t& input, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation); void trigger_update_config(update_context_t& ctx, const config::config_t& config, uint64_t config_generation); diff --git a/include/platform/wayland.h b/include/platform/wayland.h index ded4f4bd..7ba3a87c 100644 --- a/include/platform/wayland.h +++ b/include/platform/wayland.h @@ -3,10 +3,10 @@ #include "config/config.h" #include "config/config_watcher.h" -#include "global_wayland_session.h" -#include "graphics/global_animation_session.h" +#include "graphics/animation_context.h" #include "utils/error.h" #include "wayland_context.h" +#include "wayland_thread_context.h" #include @@ -18,26 +18,27 @@ using config_reload_callback_t = void (*)(); // ============================================================================= // Initialize Wayland connection - must be checked -BONGOCAT_NODISCARD created_result_t> create(animation::animation_session_t& anim, - const config::config_t& config); -BONGOCAT_NODISCARD bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim); +BONGOCAT_NODISCARD created_result_t> +create(animation::animation_context_t& animation_ctx, const config::config_t& config); +BONGOCAT_NODISCARD bongocat_error_t setup(wayland_context_t& ctx, animation::animation_context_t& animation_ctx); // Run Wayland event loop - must be checked -BONGOCAT_NODISCARD bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int signal_fd, +BONGOCAT_NODISCARD bongocat_error_t run(wayland_context_t& ctx, volatile sig_atomic_t& running, int signal_fd, input::input_context_t& input, const config::config_t& config, const config::config_watcher_t *config_watcher, config_reload_callback_t config_reload_callback); // Update configuration -void update_config(wayland_session_t& ctx, const config::config_t& config, animation::animation_session_t& trigger_ctx); +void update_config(wayland_context_t& ctx, const config::config_t& config, + animation::animation_context_t& animation_ctx); // Get detected screen width -BONGOCAT_NODISCARD int get_screen_width(const wayland_session_t& ctx); +BONGOCAT_NODISCARD int get_screen_width(const wayland_context_t& ctx); // Get current layer name for logging BONGOCAT_NODISCARD const char *get_current_layer_name(); -bongocat_error_t request_render(animation::animation_session_t& trigger_ctx); +bongocat_error_t request_render(animation::animation_context_t& animation_ctx); } // namespace bongocat::platform::wayland #endif // BONGOCAT_WAYLAND_H \ No newline at end of file diff --git a/include/platform/wayland_callbacks.h b/include/platform/wayland_callbacks.h index 25cc6140..7367200e 100644 --- a/include/platform/wayland_callbacks.h +++ b/include/platform/wayland_callbacks.h @@ -1,8 +1,8 @@ #ifndef BONGOCAT_WAYLAND_CALLBACKS_H #define BONGOCAT_WAYLAND_CALLBACKS_H -#include "global_wayland_session.h" #include "wayland-protocols.hpp" +#include "wayland_context.h" #include #include @@ -59,7 +59,7 @@ inline static constexpr zwlr_foreign_toplevel_handle_v1_listener fs_toplevel_lis .parent = fs_handle_parent, }; -extern void fs_update_state_fallback(wayland_session_t& ctx); +extern void fs_update_state_fallback(wayland_context_t& ctx); extern void fs_handle_manager_toplevel(void *data, zwlr_foreign_toplevel_manager_v1 *manager, zwlr_foreign_toplevel_handle_v1 *toplevel); diff --git a/include/platform/wayland_context.h b/include/platform/wayland_context.h index 470d9f84..54f38781 100644 --- a/include/platform/wayland_context.h +++ b/include/platform/wayland_context.h @@ -1,66 +1,109 @@ -#ifndef BONGOCAT_WAYLAND_CONTEXT_H -#define BONGOCAT_WAYLAND_CONTEXT_H +#ifndef BONGOCAT_GLOBAL_WAYLAND_CONTEXT_CONTEXT_H +#define BONGOCAT_GLOBAL_WAYLAND_CONTEXT_CONTEXT_H -struct zwlr_layer_shell_v1; -struct zwlr_layer_surface_v1; -#include "config/config.h" +#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "platform/wayland-protocols.hpp" -#include "wayland_shared_memory.h" +#include "wayland_thread_context.h" -#include -#include +#include namespace bongocat::platform::wayland { -inline static constexpr int MAX_ATTEMPTS = 4096; +// max. windows to track for fullscreen detection +inline static constexpr size_t MAX_TOP_LEVELS = 128; +inline static constexpr size_t MAX_OUTPUTS = 8; // Maximum monitor outputs to store +inline static constexpr size_t OUTPUT_NAME_SIZE = 128; + +// ============================================================================= +// FULLSCREEN DETECTION MODULE +// ============================================================================= + +struct fullscreen_detector_t { + struct zwlr_foreign_toplevel_manager_v1 *manager{BONGOCAT_NULLPTR}; + bool has_fullscreen_toplevel{false}; + timeval last_check{}; +}; +struct tracked_toplevel_t { + struct zwlr_foreign_toplevel_handle_v1 *handle{BONGOCAT_NULLPTR}; + wl_output *output{BONGOCAT_NULLPTR}; + bool is_fullscreen{false}; +}; + +// ============================================================================= +// SCREEN DIMENSION MANAGEMENT +// ============================================================================= + +enum class screen_info_received_flags_t : uint32_t { + None = (1U << 0), + Mode = (1U << 1), + Geometry = (1U << 2), +}; +struct screen_info_t { + struct wl_output *wl_output{BONGOCAT_NULLPTR}; // ref of output + int screen_width{0}; + int screen_height{0}; + int transform{0}; + int raw_width{0}; + int raw_height{0}; + screen_info_received_flags_t received{screen_info_received_flags_t::None}; +}; struct wayland_context_t; -// Cleanup Wayland resources -void cleanup_wayland_context(wayland_context_t& ctx); -enum class bar_visibility_t : bool { - Hide = false, - Show = true +enum class output_ref_received_flags_t : uint32_t { + None = (1u << 0), + Name = (1u << 1), + LogicalPosition = (1u << 2), + LogicalSize = (1u << 3), +}; +// Output monitor reference structure +struct output_ref_t { + struct wl_output *wl_output{BONGOCAT_NULLPTR}; + zxdg_output_v1 *xdg_output{BONGOCAT_NULLPTR}; + uint32_t name{0}; // Registry name + char name_str[OUTPUT_NAME_SIZE]{}; // From xdg-output + int32_t x{0}; + int32_t y{0}; + int32_t width{0}; + int32_t height{0}; + output_ref_received_flags_t received{output_ref_received_flags_t::None}; + // monitor ID in Hyprland + int64_t hypr_id{-1}; + // back reference + wayland_context_t *wayland{BONGOCAT_NULLPTR}; }; -struct screen_info_t; +void cleanup_wayland(wayland_context_t& ctx); struct wayland_context_t { - wl_display *display{BONGOCAT_NULLPTR}; - wl_compositor *compositor{BONGOCAT_NULLPTR}; - wl_shm *shm{BONGOCAT_NULLPTR}; - zwlr_layer_shell_v1 *layer_shell{BONGOCAT_NULLPTR}; - struct xdg_wm_base *xdg_wm_base{BONGOCAT_NULLPTR}; - wl_output *output{BONGOCAT_NULLPTR}; - wl_surface *surface{BONGOCAT_NULLPTR}; - zwlr_layer_surface_v1 *layer_surface{BONGOCAT_NULLPTR}; + wayland_thread_context thread_context; + animation::animation_context_t *animation_context{BONGOCAT_NULLPTR}; + + tracked_toplevel_t tracked_toplevels[MAX_TOP_LEVELS]; + size_t num_toplevels{0}; + + output_ref_t outputs[MAX_OUTPUTS]; + size_t output_count{0}; + zxdg_output_manager_v1 *xdg_output_manager{BONGOCAT_NULLPTR}; + + fullscreen_detector_t fs_detector; + + screen_info_t screen_infos[MAX_OUTPUTS]; + atomic_bool ready{false}; // Output reconnection handling - struct wl_registry *registry{BONGOCAT_NULLPTR}; - uint32_t bound_output_name{0}; // Registry name of our bound output - bool using_named_output{false}; // True if user specified an output name - - // local copy from other thread, update after reload (shared memory) - MMapMemory _local_copy_config; - MMapMemory ctx_shm; - bar_visibility_t bar_visibility{bar_visibility_t::Show}; - - int32_t _bar_height{0}; - int32_t _screen_width{0}; - char *_output_name_str{ - BONGOCAT_NULLPTR}; // ref to existing name in output, Will default to automatic one if kept null - bool _fullscreen_detected{false}; - screen_info_t *_screen_info{BONGOCAT_NULLPTR}; - - // frame done callback data - wl_callback *_frame_cb{BONGOCAT_NULLPTR}; - Mutex _frame_cb_lock; - atomic_bool _frame_pending{false}; - atomic_bool _redraw_after_frame{false}; - timestamp_ms_t _last_frame_timestamp_ms{0}; - - wayland_context_t() = default; + atomic_bool _output_lost{false}; // Set when our output disconnects + + wayland_context_t() { + for (size_t i = 0; i < MAX_TOP_LEVELS; i++) { + tracked_toplevels[i] = {}; + } + for (size_t i = 0; i < MAX_OUTPUTS; i++) { + outputs[i] = {}; + } + } ~wayland_context_t() { - cleanup_wayland_context(*this); + cleanup_wayland(*this); } wayland_context_t(const wayland_context_t&) = delete; @@ -69,129 +112,59 @@ struct wayland_context_t { wayland_context_t& operator=(wayland_context_t&& other) noexcept = delete; }; -inline void cleanup_wayland_context_protocols(wayland_context_t& ctx) { - if (ctx.ctx_shm) { - atomic_store(&ctx.ctx_shm->configured, false); - } +inline void cleanup_wayland(wayland_context_t& ctx) { + atomic_store(&ctx.ready, false); - if (ctx.registry != BONGOCAT_NULLPTR) { - wl_registry_destroy(ctx.registry); - ctx.registry = BONGOCAT_NULLPTR; - } -} -inline void cleanup_wayland_context_surface(wayland_context_t& ctx) { - if (ctx.ctx_shm) { - atomic_store(&ctx.ctx_shm->configured, false); + // First destroy xdg_output objects + for (size_t i = 0; i < ctx.output_count; ++i) { + if (ctx.outputs[i].xdg_output != BONGOCAT_NULLPTR) { + zxdg_output_v1_destroy(ctx.outputs[i].xdg_output); + ctx.outputs[i].xdg_output = BONGOCAT_NULLPTR; + } } - if (ctx.layer_surface != BONGOCAT_NULLPTR) { - zwlr_layer_surface_v1_destroy(ctx.layer_surface); - ctx.layer_surface = BONGOCAT_NULLPTR; - } - if (ctx.surface != BONGOCAT_NULLPTR) { - wl_surface_destroy(ctx.surface); - ctx.surface = BONGOCAT_NULLPTR; - } -} -inline void cleanup_wayland_context_buffer(wayland_context_t& ctx) { - if (ctx.ctx_shm) { - atomic_store(&ctx.ctx_shm->configured, false); + // Then destroy the manager + if (ctx.xdg_output_manager != BONGOCAT_NULLPTR) { + zxdg_output_manager_v1_destroy(ctx.xdg_output_manager); + ctx.xdg_output_manager = BONGOCAT_NULLPTR; } - if (ctx.ctx_shm) { - for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { - cleanup_shm_buffer(ctx.ctx_shm->buffers[i]); + // Finally destroy wl_output objects + for (size_t i = 0; i < ctx.output_count; ++i) { + if (ctx.outputs[i].wl_output != BONGOCAT_NULLPTR) { + wl_output_destroy(ctx.outputs[i].wl_output); + ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; } - - ctx.ctx_shm->current_buffer_index = 0; + ctx.outputs[i] = {}; + ctx.outputs[i].wl_output = BONGOCAT_NULLPTR; + ctx.outputs[i].wayland = BONGOCAT_NULLPTR; } + ctx.output_count = 0; - ctx._screen_width = 0; - ctx._bar_height = 0; -} -inline void cleanup_wayland_context(wayland_context_t& ctx) { - if (ctx.ctx_shm) { - atomic_store(&ctx.ctx_shm->configured, false); + if (ctx.fs_detector.manager != BONGOCAT_NULLPTR) { + zwlr_foreign_toplevel_manager_v1_destroy(ctx.fs_detector.manager); + ctx.fs_detector.manager = BONGOCAT_NULLPTR; } - // drain pending events - if (ctx.display != BONGOCAT_NULLPTR) { - wl_display_flush(ctx.display); - wl_display_roundtrip(ctx.display); - int attempts = 0; - while (wl_display_dispatch_pending(ctx.display) > 0 && attempts <= MAX_ATTEMPTS) { - attempts++; - } - if (attempts >= MAX_ATTEMPTS && wl_display_dispatch_pending(ctx.display) > 0) { - BONGOCAT_LOG_ERROR("Cant fully drain wayland display, max attempts: %i", attempts); + for (size_t i = 0; i < ctx.num_toplevels; ++i) { + if (ctx.tracked_toplevels[i].handle != BONGOCAT_NULLPTR) { + zwlr_foreign_toplevel_handle_v1_destroy(ctx.tracked_toplevels[i].handle); + ctx.tracked_toplevels[i].handle = BONGOCAT_NULLPTR; } + ctx.tracked_toplevels[i] = {}; } + ctx.num_toplevels = 0; - cleanup_wayland_context_protocols(ctx); - - // release frame.done handler - atomic_store(&ctx._frame_pending, false); - atomic_store(&ctx._redraw_after_frame, false); - // ctx._frame_cb_lock should be unlocked - if (ctx._frame_cb != BONGOCAT_NULLPTR) { - wl_callback_destroy(ctx._frame_cb); - ctx._frame_cb = BONGOCAT_NULLPTR; + ctx.fs_detector = {}; + for (size_t i = 0; i < MAX_OUTPUTS; ++i) { + ctx.screen_infos[i] = {}; } - ctx._last_frame_timestamp_ms = 0; - - // surfaces - cleanup_wayland_context_surface(ctx); - if (ctx.layer_shell != BONGOCAT_NULLPTR) { - zwlr_layer_shell_v1_destroy(ctx.layer_shell); - ctx.layer_shell = BONGOCAT_NULLPTR; - } - if (ctx.xdg_wm_base != BONGOCAT_NULLPTR) { - xdg_wm_base_destroy(ctx.xdg_wm_base); - ctx.xdg_wm_base = BONGOCAT_NULLPTR; - } - if (ctx.shm != BONGOCAT_NULLPTR) { - wl_shm_destroy(ctx.shm); - ctx.shm = BONGOCAT_NULLPTR; - } - if (ctx.compositor != BONGOCAT_NULLPTR) { - wl_compositor_destroy(ctx.compositor); - ctx.compositor = BONGOCAT_NULLPTR; - } - - // release shm - cleanup_wayland_context_buffer(ctx); - release_allocated_mmap_memory(ctx.ctx_shm); - release_allocated_mmap_memory(ctx._local_copy_config); - - if (ctx.display != BONGOCAT_NULLPTR) { - wl_display_disconnect(ctx.display); - ctx.display = BONGOCAT_NULLPTR; - } + // clean up wayland context + cleanup_wayland_context(ctx.thread_context); - // Note: output is just a reference to one of the outputs[] entries - // It will be destroyed when we destroy the outputs[] array above - ctx.output = BONGOCAT_NULLPTR; - ctx.bound_output_name = 0; - ctx.using_named_output = false; - - // Reset state - ctx.display = BONGOCAT_NULLPTR; - ctx.compositor = BONGOCAT_NULLPTR; - ctx.shm = BONGOCAT_NULLPTR; - ctx.layer_shell = BONGOCAT_NULLPTR; - ctx.xdg_wm_base = BONGOCAT_NULLPTR; - ctx.output = BONGOCAT_NULLPTR; - ctx.surface = BONGOCAT_NULLPTR; - ctx.layer_surface = BONGOCAT_NULLPTR; - ctx._output_name_str = BONGOCAT_NULLPTR; - ctx._frame_pending = false; - ctx._redraw_after_frame = false; - ctx._bar_height = 0; - ctx._screen_width = 0; - ctx._fullscreen_detected = false; - ctx._screen_info = BONGOCAT_NULLPTR; + ctx.animation_context = BONGOCAT_NULLPTR; } } // namespace bongocat::platform::wayland -#endif // BONGOCAT_WAYLAND_CONTEXT_H \ No newline at end of file +#endif diff --git a/include/platform/wayland_setups.h b/include/platform/wayland_setups.h index 5dd98d7c..b64e7be3 100644 --- a/include/platform/wayland_setups.h +++ b/include/platform/wayland_setups.h @@ -2,7 +2,7 @@ #define BONGOCAT_WAYLAND_SETUPS_H #include "graphics/animation.h" -#include "platform/global_wayland_session.h" +#include "platform/wayland_context.h" #include "platform/wayland_shared_memory.h" namespace bongocat::platform::wayland::details { @@ -10,11 +10,11 @@ namespace bongocat::platform::wayland::details { // Create shared memory buffer - returns fd or -1 on error BONGOCAT_NODISCARD FileDescriptor create_shm(off_t size); -BONGOCAT_NODISCARD bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx); -BONGOCAT_NODISCARD bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx); -BONGOCAT_NODISCARD bongocat_error_t wayland_setup_surface(wayland_session_t& ctx); -BONGOCAT_NODISCARD bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, - animation::animation_session_t& anim); +BONGOCAT_NODISCARD bongocat_error_t wayland_setup_protocols(wayland_context_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_update_screen_width(wayland_context_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_setup_surface(wayland_context_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_setup_buffer(wayland_thread_context& wayland_context, + animation::animation_context_t& animation_ctx); } // namespace bongocat::platform::wayland::details #endif \ No newline at end of file diff --git a/include/platform/wayland_shared_memory.h b/include/platform/wayland_shared_memory.h index c062903b..7cd33594 100644 --- a/include/platform/wayland_shared_memory.h +++ b/include/platform/wayland_shared_memory.h @@ -1,7 +1,7 @@ #ifndef BONGOCAT_WAYLAND_SHARED_MEMORY_H #define BONGOCAT_WAYLAND_SHARED_MEMORY_H -#include "graphics/global_animation_session.h" +#include "graphics/animation_context.h" #include #include @@ -14,7 +14,7 @@ inline static constexpr size_t WAYLAND_NUM_BUFFERS = 1; struct wayland_shm_buffer_t; void cleanup_shm_buffer(wayland_shm_buffer_t& buffer); -struct wayland_context_t; +struct wayland_thread_context; struct wayland_shm_buffer_t { wl_buffer *buffer{BONGOCAT_NULLPTR}; @@ -24,8 +24,8 @@ struct wayland_shm_buffer_t { size_t index{0}; // index track from wayland_shared_memory_t.buffers // extra context for listeners - animation::animation_session_t *_animation_trigger_context{BONGOCAT_NULLPTR}; - wayland_context_t *_wayland_context{BONGOCAT_NULLPTR}; // parent ref. for buffer_release + animation::animation_context_t *_animation_context{BONGOCAT_NULLPTR}; + wayland_thread_context *_wayland_thread_context{BONGOCAT_NULLPTR}; // parent ref. for buffer_release wayland_shm_buffer_t() = default; ~wayland_shm_buffer_t() { @@ -39,14 +39,14 @@ struct wayland_shm_buffer_t { : buffer(other.buffer) , pixels(bongocat::move(other.pixels)) , index(other.index) - , _animation_trigger_context(other._animation_trigger_context) - , _wayland_context(other._wayland_context) { + , _animation_context(other._animation_context) + , _wayland_thread_context(other._wayland_thread_context) { atomic_store(&busy, atomic_load(&other.busy)); atomic_store(&pending, atomic_load(&other.pending)); other.buffer = BONGOCAT_NULLPTR; other.index = 0; - other._animation_trigger_context = BONGOCAT_NULLPTR; + other._animation_context = BONGOCAT_NULLPTR; atomic_store(&other.busy, false); atomic_store(&other.pending, false); } @@ -57,13 +57,13 @@ struct wayland_shm_buffer_t { atomic_store(&busy, atomic_load(&other.busy)); atomic_store(&pending, atomic_load(&other.pending)); index = other.index; - _animation_trigger_context = other._animation_trigger_context; - _wayland_context = other._wayland_context; + _animation_context = other._animation_context; + _wayland_thread_context = other._wayland_thread_context; other.buffer = BONGOCAT_NULLPTR; other.index = 0; - other._animation_trigger_context = BONGOCAT_NULLPTR; - other._wayland_context = BONGOCAT_NULLPTR; + other._animation_context = BONGOCAT_NULLPTR; + other._wayland_thread_context = BONGOCAT_NULLPTR; atomic_store(&other.busy, false); atomic_store(&other.pending, false); } @@ -127,8 +127,8 @@ inline void cleanup_shm_buffer(wayland_shm_buffer_t& buffer) { release_allocated_mmap_file_buffer(buffer.pixels); atomic_store(&buffer.busy, false); buffer.index = 0; - buffer._animation_trigger_context = BONGOCAT_NULLPTR; - buffer._wayland_context = BONGOCAT_NULLPTR; + buffer._animation_context = BONGOCAT_NULLPTR; + buffer._wayland_thread_context = BONGOCAT_NULLPTR; } } // namespace bongocat::platform::wayland diff --git a/include/platform/wayland_thread_context.h b/include/platform/wayland_thread_context.h new file mode 100644 index 00000000..9a9c3aaa --- /dev/null +++ b/include/platform/wayland_thread_context.h @@ -0,0 +1,197 @@ +#ifndef BONGOCAT_WAYLAND_CONTEXT_H +#define BONGOCAT_WAYLAND_CONTEXT_H + +struct zwlr_layer_shell_v1; +struct zwlr_layer_surface_v1; +#include "config/config.h" +#include "platform/wayland-protocols.hpp" +#include "wayland_shared_memory.h" + +#include +#include + +namespace bongocat::platform::wayland { +inline static constexpr int MAX_ATTEMPTS = 4096; + +struct wayland_thread_context; +// Cleanup Wayland resources +void cleanup_wayland_context(wayland_thread_context& ctx); + +enum class bar_visibility_t : bool { + Hide = false, + Show = true +}; + +struct screen_info_t; + +struct wayland_thread_context { + wl_display *display{BONGOCAT_NULLPTR}; + wl_compositor *compositor{BONGOCAT_NULLPTR}; + wl_shm *shm{BONGOCAT_NULLPTR}; + zwlr_layer_shell_v1 *layer_shell{BONGOCAT_NULLPTR}; + struct xdg_wm_base *xdg_wm_base{BONGOCAT_NULLPTR}; + wl_output *output{BONGOCAT_NULLPTR}; + wl_surface *surface{BONGOCAT_NULLPTR}; + zwlr_layer_surface_v1 *layer_surface{BONGOCAT_NULLPTR}; + struct wl_registry *registry{BONGOCAT_NULLPTR}; + + // Output reconnection handling + uint32_t bound_output_name{0}; // Registry name of our bound output + bool using_named_output{false}; // True if user specified an output name + + // local copy from other thread, update after reload (shared memory) + MMapMemory _local_copy_config; + MMapMemory ctx_shm; + bar_visibility_t bar_visibility{bar_visibility_t::Show}; + + int32_t _bar_height{0}; + int32_t _screen_width{0}; + // ref to existing name in output, Will default to automatic one if kept null + char *_output_name_str{BONGOCAT_NULLPTR}; + bool _fullscreen_detected{false}; + screen_info_t *_screen_info{BONGOCAT_NULLPTR}; + + // frame done callback data + wl_callback *_frame_cb{BONGOCAT_NULLPTR}; + Mutex _frame_cb_lock; + atomic_bool _frame_pending{false}; + atomic_bool _redraw_after_frame{false}; + timestamp_ms_t _last_frame_timestamp_ms{0}; + + wayland_thread_context() = default; + ~wayland_thread_context() { + cleanup_wayland_context(*this); + } + + wayland_thread_context(const wayland_thread_context&) = delete; + wayland_thread_context& operator=(const wayland_thread_context&) = delete; + wayland_thread_context(wayland_thread_context&& other) noexcept = delete; + wayland_thread_context& operator=(wayland_thread_context&& other) noexcept = delete; +}; + +inline void cleanup_wayland_context_protocols(wayland_thread_context& ctx) { + if (ctx.ctx_shm) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + if (ctx.registry != BONGOCAT_NULLPTR) { + wl_registry_destroy(ctx.registry); + ctx.registry = BONGOCAT_NULLPTR; + } +} +inline void cleanup_wayland_context_surface(wayland_thread_context& ctx) { + if (ctx.ctx_shm) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + if (ctx.layer_surface != BONGOCAT_NULLPTR) { + zwlr_layer_surface_v1_destroy(ctx.layer_surface); + ctx.layer_surface = BONGOCAT_NULLPTR; + } + if (ctx.surface != BONGOCAT_NULLPTR) { + wl_surface_destroy(ctx.surface); + ctx.surface = BONGOCAT_NULLPTR; + } +} +inline void cleanup_wayland_context_buffer(wayland_thread_context& ctx) { + if (ctx.ctx_shm) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + if (ctx.ctx_shm) { + for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { + cleanup_shm_buffer(ctx.ctx_shm->buffers[i]); + } + + ctx.ctx_shm->current_buffer_index = 0; + } + + ctx._screen_width = 0; + ctx._bar_height = 0; +} +inline void cleanup_wayland_context(wayland_thread_context& ctx) { + if (ctx.ctx_shm) { + atomic_store(&ctx.ctx_shm->configured, false); + } + + // drain pending events + if (ctx.display != BONGOCAT_NULLPTR) { + wl_display_flush(ctx.display); + wl_display_roundtrip(ctx.display); + int attempts = 0; + while (wl_display_dispatch_pending(ctx.display) > 0 && attempts <= MAX_ATTEMPTS) { + attempts++; + } + if (attempts >= MAX_ATTEMPTS && wl_display_dispatch_pending(ctx.display) > 0) { + BONGOCAT_LOG_ERROR("Cant fully drain wayland display, max attempts: %i", attempts); + } + } + + cleanup_wayland_context_protocols(ctx); + + // release frame.done handler + atomic_store(&ctx._frame_pending, false); + atomic_store(&ctx._redraw_after_frame, false); + // ctx._frame_cb_lock should be unlocked + if (ctx._frame_cb != BONGOCAT_NULLPTR) { + wl_callback_destroy(ctx._frame_cb); + ctx._frame_cb = BONGOCAT_NULLPTR; + } + ctx._last_frame_timestamp_ms = 0; + + // surfaces + cleanup_wayland_context_surface(ctx); + + if (ctx.layer_shell != BONGOCAT_NULLPTR) { + zwlr_layer_shell_v1_destroy(ctx.layer_shell); + ctx.layer_shell = BONGOCAT_NULLPTR; + } + if (ctx.xdg_wm_base != BONGOCAT_NULLPTR) { + xdg_wm_base_destroy(ctx.xdg_wm_base); + ctx.xdg_wm_base = BONGOCAT_NULLPTR; + } + if (ctx.shm != BONGOCAT_NULLPTR) { + wl_shm_destroy(ctx.shm); + ctx.shm = BONGOCAT_NULLPTR; + } + if (ctx.compositor != BONGOCAT_NULLPTR) { + wl_compositor_destroy(ctx.compositor); + ctx.compositor = BONGOCAT_NULLPTR; + } + + // release shm + cleanup_wayland_context_buffer(ctx); + release_allocated_mmap_memory(ctx.ctx_shm); + release_allocated_mmap_memory(ctx._local_copy_config); + + if (ctx.display != BONGOCAT_NULLPTR) { + wl_display_disconnect(ctx.display); + ctx.display = BONGOCAT_NULLPTR; + } + + // Note: output is just a reference to one of the outputs[] entries + // It will be destroyed when we destroy the outputs[] array above + ctx.output = BONGOCAT_NULLPTR; + ctx.bound_output_name = 0; + ctx.using_named_output = false; + + // Reset state + ctx.display = BONGOCAT_NULLPTR; + ctx.compositor = BONGOCAT_NULLPTR; + ctx.shm = BONGOCAT_NULLPTR; + ctx.layer_shell = BONGOCAT_NULLPTR; + ctx.xdg_wm_base = BONGOCAT_NULLPTR; + ctx.output = BONGOCAT_NULLPTR; + ctx.surface = BONGOCAT_NULLPTR; + ctx.layer_surface = BONGOCAT_NULLPTR; + ctx._output_name_str = BONGOCAT_NULLPTR; + ctx._frame_pending = false; + ctx._redraw_after_frame = false; + ctx._bar_height = 0; + ctx._screen_width = 0; + ctx._fullscreen_detected = false; + ctx._screen_info = BONGOCAT_NULLPTR; +} +} // namespace bongocat::platform::wayland + +#endif // BONGOCAT_WAYLAND_CONTEXT_H \ No newline at end of file diff --git a/src/config/config.cpp b/src/config/config.cpp index 60beeacd..c60af081 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -9,7 +9,7 @@ #include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "embedded_assets/pkmn/pkmn_sprite.h" #include "embedded_assets/pmd/pmd_sprite.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/embedded_assets_dms.h" #include "graphics/embedded_assets_pkmn.h" #include "utils/error.h" diff --git a/src/config/config_watcher.cpp b/src/config/config_watcher.cpp index bca3a4b3..0df9b50b 100644 --- a/src/config/config_watcher.cpp +++ b/src/config/config_watcher.cpp @@ -1,6 +1,6 @@ #include "config/config_watcher.h" -#include "platform/wayland_context.h" +#include "platform/wayland_thread_context.h" #include "utils/error.h" #include "utils/system_memory.h" #include "utils/time.h" diff --git a/src/core/main.cpp b/src/core/main.cpp index e9b96230..d96e048c 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -52,8 +52,8 @@ struct main_context_t { AllocatedMemory config_watcher; AllocatedMemory input; AllocatedMemory update; - AllocatedMemory animation; - AllocatedMemory wayland; + AllocatedMemory animation; + AllocatedMemory wayland; const char *signal_watch_path{BONGOCAT_NULLPTR}; atomic_uint64_t config_generation{0}; @@ -76,7 +76,7 @@ inline void stop_threads(main_context_t& context) { context.running = 0; // stop threads if (context.animation != BONGOCAT_NULLPTR) { - atomic_store(&context.animation->anim._animation_running, false); + atomic_store(&context.animation->thread_context._animation_running, false); } if (context.input != BONGOCAT_NULLPTR) { atomic_store(&context.input->_capture_input_running, false); @@ -90,7 +90,8 @@ inline void stop_threads(main_context_t& context) { // wait for threads if (context.animation != BONGOCAT_NULLPTR) { - platform::join_thread_with_timeout(context.animation->anim._anim_thread, WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS); + platform::join_thread_with_timeout(context.animation->thread_context._anim_thread, + WAIT_FOR_SHUTDOWN_ANIMATION_THREAD_MS); } if (context.input != BONGOCAT_NULLPTR) { platform::join_thread_with_timeout(context.input->_input_thread, WAIT_FOR_SHUTDOWN_INPUT_THREAD_MS); @@ -129,7 +130,7 @@ void cleanup(main_context_t& context) { // remove references (avoid dangling pointers) if (context.wayland != BONGOCAT_NULLPTR) { - context.wayland->animation_trigger_context = BONGOCAT_NULLPTR; + context.wayland->animation_context = BONGOCAT_NULLPTR; } if (context.animation != BONGOCAT_NULLPTR) { context.animation->_input = BONGOCAT_NULLPTR; @@ -459,10 +460,10 @@ static void config_reload_callback() { atomic_load(&get_main_context().update->config_seen_generation) >= new_gen; }, COND_RELOAD_CONFIG_TIMEOUT_MS); - timedwait_result |= get_main_context().animation->anim.config_updated.timedwait( + timedwait_result |= get_main_context().animation->thread_context.config_updated.timedwait( [&] { - return !atomic_load(&get_main_context().animation->anim._animation_running) || - atomic_load(&get_main_context().animation->anim.config_seen_generation) >= new_gen; + return !atomic_load(&get_main_context().animation->thread_context._animation_running) || + atomic_load(&get_main_context().animation->thread_context.config_seen_generation) >= new_gen; }, COND_RELOAD_CONFIG_TIMEOUT_MS); @@ -476,8 +477,8 @@ static void config_reload_callback() { if (atomic_load(&get_main_context().update->_running)) { atomic_store(&get_main_context().update->config_seen_generation, new_gen); } - if (atomic_load(&get_main_context().animation->anim._animation_running)) { - atomic_store(&get_main_context().animation->anim.config_seen_generation, new_gen); + if (atomic_load(&get_main_context().animation->thread_context._animation_running)) { + atomic_store(&get_main_context().animation->thread_context.config_seen_generation, new_gen); } BONGOCAT_LOG_VERBOSE("timedwait timeouted, sync all config gen: %d", timedwait_result); } @@ -486,15 +487,15 @@ static void config_reload_callback() { BONGOCAT_LOG_VERBOSE("Input: config gen: %d", atomic_load(&get_main_context().input->config_seen_generation)); BONGOCAT_LOG_VERBOSE("Update: config gen: %d", atomic_load(&get_main_context().update->config_seen_generation)); BONGOCAT_LOG_VERBOSE("Animation: config gen: %d", - atomic_load(&get_main_context().animation->anim.config_seen_generation)); + atomic_load(&get_main_context().animation->thread_context.config_seen_generation)); BONGOCAT_LOG_VERBOSE("Main: config gen: %d", atomic_load(&get_main_context().config_generation)); } // Tell workers they can continue get_main_context().configs_reloaded_cond.notify_all(); BONGOCAT_LOG_INFO("Configuration reloaded successfully!"); - BONGOCAT_LOG_INFO("New screen dimensions: %dx%d", get_main_context().wayland->wayland_context._screen_width, - get_main_context().wayland->wayland_context._bar_height); + BONGOCAT_LOG_INFO("New screen dimensions: %dx%d", get_main_context().wayland->thread_context._screen_width, + get_main_context().wayland->thread_context._bar_height); assert(get_main_context().animation != BONGOCAT_NULLPTR); animation::trigger(*get_main_context().animation, animation::trigger_animation_cause_mask_t::UpdateConfig); @@ -527,7 +528,7 @@ static void config_reload_callback() { // Wait for (new) threads to be ready // wait for context - if (atomic_load(&get_main_context().animation->anim._animation_running)) { + if (atomic_load(&get_main_context().animation->thread_context._animation_running)) { get_main_context().animation->init_cond.timedwait( [&]() { return !atomic_load(&get_main_context().input->_capture_input_running) || @@ -551,7 +552,7 @@ static void config_reload_callback() { COND_INIT_TIMEOUT_MS); } BONGOCAT_LOG_VERBOSE("Animation: running %d (ready=%d)", - atomic_load(&get_main_context().animation->anim._animation_running), + atomic_load(&get_main_context().animation->thread_context._animation_running), atomic_load(&get_main_context().animation->ready)); BONGOCAT_LOG_VERBOSE("Input: running %d (ready=%d)", atomic_load(&get_main_context().input->_capture_input_running), atomic_load(&get_main_context().input->ready)); @@ -1080,12 +1081,12 @@ int main(int argc, char *argv[]) { assert(ctx.animation != BONGOCAT_NULLPTR); assert(ctx.wayland != BONGOCAT_NULLPTR); - if (abs(ctx.config.cat_x_offset) > ctx.wayland->wayland_context._screen_width) { + if (abs(ctx.config.cat_x_offset) > ctx.wayland->thread_context._screen_width) { BONGOCAT_LOG_WARNING("cat_x_offset %d may position cat off-screen (screen width: %d)", ctx.config.cat_x_offset, - ctx.wayland->wayland_context._screen_width); + ctx.wayland->thread_context._screen_width); } - BONGOCAT_LOG_INFO("Bar dimensions: %dx%d", ctx.wayland->wayland_context._screen_width, ctx.config.overlay_height); + BONGOCAT_LOG_INFO("Bar dimensions: %dx%d", ctx.wayland->thread_context._screen_width, ctx.config.overlay_height); BONGOCAT_LOG_INFO("Bongo Cat Overlay configured successfully"); diff --git a/src/graphics/animation.cpp b/src/graphics/animation.cpp index a6ff8bd3..5ba4e133 100644 --- a/src/graphics/animation.cpp +++ b/src/graphics/animation.cpp @@ -9,7 +9,7 @@ #include "embedded_assets/ms_agent/ms_agent.hpp" #include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "embedded_assets/pkmn/pkmn_sprite.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/embedded_assets_dms.h" #include "graphics/embedded_assets_pkmn.h" #include "image_loader/bongocat/load_images_bongocat.h" @@ -178,7 +178,7 @@ struct anim_conditions_t { bool is_running{false}; bool continue_writing{false}; }; -static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_context_t& ctx, +static anim_conditions_t get_anim_conditions([[maybe_unused]] const animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, const animation_state_t& current_state, const animation_trigger_t& trigger, @@ -451,7 +451,7 @@ anim_bongocat_process_animation(const platform::input::input_context_t& input, return ret; } static anim_bongocat_process_animation_result_t -anim_bongocat_restart_animation(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_bongocat_restart_animation(animation_thread_context_t& ctx, const platform::input::input_context_t& input, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, const bongocat_sprite_sheet_t& current_frames) { @@ -644,7 +644,7 @@ BONGOCAT_FRAME_BOTH_DOWN; */ static anim_bongocat_process_animation_result_t anim_bongocat_start_or_process_animation( - animation_context_t& ctx, const platform::input::input_context_t& input, animation_state_row_t new_row_state, + animation_thread_context_t& ctx, const platform::input::input_context_t& input, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, const animation_state_t& current_state, const bongocat_sprite_sheet_t& current_frames) { if (current_state.row_state != new_row_state) { @@ -664,7 +664,7 @@ static anim_bongocat_process_animation_result_t anim_bongocat_start_or_process_a } static anim_next_frame_result_t -anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_bongocat_idle_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { using namespace assets; @@ -853,7 +853,7 @@ anim_bongocat_idle_next_frame(animation_context_t& ctx, const platform::input::i } static anim_next_frame_result_t anim_bongocat_key_pressed_next_frame( - animation_context_t& ctx, animation_state_t& state, const platform::input::input_context_t& input, + animation_thread_context_t& ctx, animation_state_t& state, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, const animation_trigger_t& trigger) { using namespace assets; @@ -1012,7 +1012,7 @@ static anim_dm_process_animation_result_t anim_dm_process_animation(animation_pl return ret; } static anim_dm_process_animation_result_t -anim_dm_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, +anim_dm_restart_animation([[maybe_unused]] animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, const dm_sprite_sheet_t& current_frames, const config::config_t& current_config) { @@ -1076,7 +1076,7 @@ anim_dm_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_s return ret; } static anim_dm_process_animation_result_t -anim_dm_show_single_frame([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, +anim_dm_show_single_frame([[maybe_unused]] animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, [[maybe_unused]] const dm_sprite_sheet_t& current_frames, @@ -1259,7 +1259,7 @@ anim_dm_show_single_frame([[maybe_unused]] animation_context_t& ctx, animation_s } static anim_dm_process_animation_result_t -anim_dm_start_or_process_animation(animation_context_t& ctx, animation_state_row_t new_row_state, +anim_dm_start_or_process_animation(animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, const animation_state_t& current_state, const dm_sprite_sheet_t& current_frames, const config::config_t& current_config) { @@ -1279,7 +1279,7 @@ anim_dm_start_or_process_animation(animation_context_t& ctx, animation_state_row } static anim_dm_process_animation_result_t -anim_dm_handle_movement(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_dm_handle_movement(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_player_result_t& new_animation_result, animation_state_t& new_state, const anim_handle_key_press_result_t& trigger_result, const animation_state_t& current_state, @@ -1486,7 +1486,7 @@ anim_dm_handle_movement(animation_context_t& ctx, const platform::input::input_c return ret; } -static anim_next_frame_result_t anim_dm_idle_next_frame(animation_context_t& ctx, +static anim_next_frame_result_t anim_dm_idle_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, @@ -1779,7 +1779,7 @@ static anim_next_frame_result_t anim_dm_idle_next_frame(animation_context_t& ctx } static anim_next_frame_result_t -anim_dm_key_pressed_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_dm_key_pressed_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const animation_trigger_t& trigger) { using namespace assets; @@ -1893,7 +1893,7 @@ anim_dm_key_pressed_next_frame(animation_context_t& ctx, const platform::input:: current_state, current_config); } -static anim_next_frame_result_t anim_dm_working_next_frame(animation_context_t& ctx, +static anim_next_frame_result_t anim_dm_working_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, const platform::update::update_context_t& upd, animation_state_t& state, @@ -2096,7 +2096,7 @@ static anim_pkmn_process_animation_result_t anim_pkmn_process_animation(animatio return ret; } static anim_pkmn_process_animation_result_t -anim_pkmn_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, +anim_pkmn_restart_animation([[maybe_unused]] animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, const pkmn_sprite_sheet_t& current_frames, const config::config_t& current_config) { @@ -2258,7 +2258,7 @@ PKMN_FRAME_IDLE2; */ static anim_pkmn_process_animation_result_t -anim_pkmn_start_or_process_animation(animation_context_t& ctx, animation_state_row_t new_row_state, +anim_pkmn_start_or_process_animation(animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, const animation_state_t& current_state, const pkmn_sprite_sheet_t& current_frames, const config::config_t& current_config) { @@ -2278,7 +2278,8 @@ anim_pkmn_start_or_process_animation(animation_context_t& ctx, animation_state_r } static anim_next_frame_result_t -anim_pkmn_idle_next_frame(animation_context_t& ctx, [[maybe_unused]] const platform::input::input_context_t& input, +anim_pkmn_idle_next_frame(animation_thread_context_t& ctx, + [[maybe_unused]] const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { using namespace assets; @@ -2351,7 +2352,7 @@ anim_pkmn_idle_next_frame(animation_context_t& ctx, [[maybe_unused]] const platf } static anim_next_frame_result_t -anim_pkmn_key_pressed_next_frame(animation_context_t& ctx, +anim_pkmn_key_pressed_next_frame(animation_thread_context_t& ctx, [[maybe_unused]] const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const animation_trigger_t& trigger) { @@ -2526,7 +2527,7 @@ anim_ms_agent_process_animation(animation_player_result_t& new_animation_result, return ret; } static anim_ms_agent_process_animation_result_t -anim_ms_agent_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, +anim_ms_agent_restart_animation([[maybe_unused]] animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, const ms_agent_sprite_sheet_t& current_frames, @@ -2614,10 +2615,12 @@ anim_ms_agent_restart_animation([[maybe_unused]] animation_context_t& ctx, anima return ret; } -static anim_ms_agent_process_animation_result_t anim_ms_agent_start_or_process_animation( - animation_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, - animation_state_t& new_state, const animation_state_t& current_state, const ms_agent_sprite_sheet_t& current_frames, - const config::config_t& current_config) { +static anim_ms_agent_process_animation_result_t +anim_ms_agent_start_or_process_animation(animation_thread_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, + const ms_agent_sprite_sheet_t& current_frames, + const config::config_t& current_config) { if (current_state.row_state != new_row_state) { auto result = anim_ms_agent_process_animation(new_animation_result, new_state, current_state, current_frames); if (result.status == anim_ms_agent_process_animation_result_status_t::Looped || @@ -2634,7 +2637,7 @@ static anim_ms_agent_process_animation_result_t anim_ms_agent_start_or_process_a } static anim_next_frame_result_t -anim_ms_agent_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_ms_agent_idle_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { using namespace assets; @@ -2853,7 +2856,7 @@ anim_ms_agent_idle_next_frame(animation_context_t& ctx, const platform::input::i } static anim_next_frame_result_t -anim_ms_agent_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& state, +anim_ms_agent_key_pressed_next_frame(animation_thread_context_t& ctx, animation_state_t& state, [[maybe_unused]] const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, [[maybe_unused]] const animation_trigger_t& trigger) { @@ -3075,7 +3078,7 @@ anim_custom_process_animation(animation_player_result_t& new_animation_result, a return ret; } static anim_custom_process_animation_result_t -anim_custom_restart_animation([[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, +anim_custom_restart_animation([[maybe_unused]] animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, @@ -3175,7 +3178,7 @@ anim_custom_restart_animation([[maybe_unused]] animation_context_t& ctx, animati return ret; } static anim_custom_process_animation_result_t anim_custom_restart_animation( - [[maybe_unused]] animation_context_t& ctx, animation_state_row_t new_row_state, + [[maybe_unused]] animation_thread_context_t& ctx, animation_state_row_t new_row_state, const animation_state_row_t fallback_row_state, const animation_state_row_t end_fallback_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, [[maybe_unused]] const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, @@ -3281,10 +3284,12 @@ static anim_custom_process_animation_result_t anim_custom_restart_animation( return ret; } -static anim_custom_process_animation_result_t anim_custom_start_or_process_animation( - animation_context_t& ctx, animation_state_row_t new_row_state, animation_player_result_t& new_animation_result, - animation_state_t& new_state, const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, - const config::config_t& current_config) { +static anim_custom_process_animation_result_t +anim_custom_start_or_process_animation(animation_thread_context_t& ctx, animation_state_row_t new_row_state, + animation_player_result_t& new_animation_result, animation_state_t& new_state, + const animation_state_t& current_state, + const custom_sprite_sheet_t& current_frames, + const config::config_t& current_config) { if (current_state.row_state != new_row_state) { auto result = anim_custom_process_animation(new_animation_result, new_state, current_state, current_frames); if (result.status == anim_custom_process_animation_result_status_t::Looped || @@ -3302,7 +3307,7 @@ static anim_custom_process_animation_result_t anim_custom_start_or_process_anima current_frames, current_config); } static anim_custom_process_animation_result_t anim_custom_start_or_process_animation( - animation_context_t& ctx, animation_state_row_t new_row_state, animation_state_row_t fallback_row_state, + animation_thread_context_t& ctx, animation_state_row_t new_row_state, animation_state_row_t fallback_row_state, animation_player_result_t& new_animation_result, animation_state_t& new_state, const animation_state_t& current_state, const custom_sprite_sheet_t& current_frames, const config::config_t& current_config) { @@ -3330,7 +3335,7 @@ static anim_custom_process_animation_result_t anim_custom_start_or_process_anima } static anim_custom_process_animation_result_t -anim_custom_handle_movement(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_custom_handle_movement(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_player_result_t& new_animation_result, animation_state_t& new_state, const anim_handle_key_press_result_t& trigger_result, @@ -3551,7 +3556,7 @@ anim_custom_handle_movement(animation_context_t& ctx, const platform::input::inp return ret; } static anim_next_frame_result_t -anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::input_context_t& input, +anim_custom_idle_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { using namespace assets; @@ -3986,7 +3991,7 @@ anim_custom_idle_next_frame(animation_context_t& ctx, const platform::input::inp current_state, current_config); } static anim_next_frame_result_t -anim_custom_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& state, +anim_custom_key_pressed_next_frame(animation_thread_context_t& ctx, animation_state_t& state, [[maybe_unused]] const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, [[maybe_unused]] const animation_trigger_t& trigger) { @@ -4128,7 +4133,7 @@ anim_custom_key_pressed_next_frame(animation_context_t& ctx, animation_state_t& current_state, current_config); } -static anim_next_frame_result_t anim_custom_working_next_frame(animation_context_t& ctx, +static anim_next_frame_result_t anim_custom_working_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, const platform::update::update_context_t& upd, animation_state_t& state, @@ -4230,7 +4235,7 @@ static anim_next_frame_result_t anim_custom_working_next_frame(animation_context current_state, current_config); } -static anim_next_frame_result_t anim_custom_running_next_frame(animation_context_t& ctx, +static anim_next_frame_result_t anim_custom_running_next_frame(animation_thread_context_t& ctx, const platform::input::input_context_t& input, const platform::update::update_context_t& upd, animation_state_t& state, @@ -4334,7 +4339,8 @@ static anim_next_frame_result_t anim_custom_running_next_frame(animation_context #endif static anim_next_frame_result_t -anim_handle_idle_animation(animation_context_t& ctx, [[maybe_unused]] const platform::input::input_context_t& input, +anim_handle_idle_animation(animation_thread_context_t& ctx, + [[maybe_unused]] const platform::input::input_context_t& input, [[maybe_unused]] const platform::update::update_context_t& upd, animation_state_t& state, const anim_handle_key_press_result_t& trigger_result) { using namespace assets; @@ -4385,18 +4391,18 @@ anim_handle_idle_animation(animation_context_t& ctx, [[maybe_unused]] const plat return {}; } -static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_session_t& animation_trigger_ctx, +static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_context_t& animation_ctx, animation_state_t& state, const animation_trigger_t& trigger) { using namespace assets; - assert(animation_trigger_ctx._input != BONGOCAT_NULLPTR); - assert(animation_trigger_ctx._input->shm != BONGOCAT_NULLPTR); - assert(animation_trigger_ctx._update != BONGOCAT_NULLPTR); - assert(animation_trigger_ctx._update->shm != BONGOCAT_NULLPTR); - animation_context_t& ctx = animation_trigger_ctx.anim; - [[maybe_unused]] const platform::input::input_context_t& input = *animation_trigger_ctx._input; - [[maybe_unused]] const platform::update::update_context_t& upd = *animation_trigger_ctx._update; + assert(animation_ctx._input != BONGOCAT_NULLPTR); + assert(animation_ctx._input->shm != BONGOCAT_NULLPTR); + assert(animation_ctx._update != BONGOCAT_NULLPTR); + assert(animation_ctx._update->shm != BONGOCAT_NULLPTR); + animation_thread_context_t& ctx = animation_ctx.thread_context; + [[maybe_unused]] const platform::input::input_context_t& input = *animation_ctx._input; + [[maybe_unused]] const platform::update::update_context_t& upd = *animation_ctx._update; // read-only config // assert(ctx._local_copy_config != nullptr); @@ -4501,12 +4507,12 @@ static anim_handle_key_press_result_t anim_handle_animation_trigger(animation_se return {.trigger = trigger, .update_frame_result = update_frame_result}; } -static bool anim_update_state(animation_session_t& animation_trigger_ctx, animation_state_t& state, +static bool anim_update_state(animation_context_t& animation_ctx, animation_state_t& state, const animation_trigger_t& trigger) { - assert(animation_trigger_ctx._input); - platform::input::input_context_t& input = *animation_trigger_ctx._input; - platform::update::update_context_t& upd = *animation_trigger_ctx._update; - animation_context_t& ctx = animation_trigger_ctx.anim; + assert(animation_ctx._input); + platform::input::input_context_t& input = *animation_ctx._input; + platform::update::update_context_t& upd = *animation_ctx._update; + animation_thread_context_t& ctx = animation_ctx.thread_context; // read-only config assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; @@ -4523,7 +4529,7 @@ static bool anim_update_state(animation_session_t& animation_trigger_ctx, animat { platform::LockGuard input_guard(input.input_lock); platform::LockGuard update_guard(upd.update_lock); - trigger_result = anim_handle_animation_trigger(animation_trigger_ctx, state, trigger); + trigger_result = anim_handle_animation_trigger(animation_ctx, state, trigger); idle_update_result = anim_handle_idle_animation(ctx, input, upd, state, trigger_result); key_pressed = has_flag(trigger_result.trigger.anim_cause, trigger_animation_cause_mask_t::KeyPress) && trigger_result.trigger.any_key_press_counter > 0; @@ -4563,7 +4569,7 @@ static bool anim_update_state(animation_session_t& animation_trigger_ctx, animat // ANIMATION THREAD MANAGEMENT MODULE // ============================================================================= -static void anim_init_state(animation_context_t& ctx, animation_state_t& state) { +static void anim_init_state(animation_thread_context_t& ctx, animation_state_t& state) { // read-only config assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; @@ -4580,11 +4586,11 @@ static void anim_init_state(animation_context_t& ctx, animation_state_t& state) static void cleanup_anim_thread(void *arg) { assert(arg); - animation_session_t& trigger_ctx = *static_cast(arg); + animation_context_t& animation_context = *static_cast(arg); - atomic_store(&trigger_ctx.anim._animation_running, false); + atomic_store(&animation_context.thread_context._animation_running, false); - trigger_ctx.anim.config_updated.notify_all(); + animation_context.thread_context.config_updated.notify_all(); BONGOCAT_LOG_INFO("Animation thread cleanup completed (via pthread_cancel)"); } @@ -4593,23 +4599,23 @@ static void *anim_thread(void *arg) { using namespace assets; assert(arg); - auto& trigger_ctx = *static_cast(arg); + auto& trigger_ctx = *static_cast(arg); // sanity checks assert(trigger_ctx._config != BONGOCAT_NULLPTR); assert(trigger_ctx._input != BONGOCAT_NULLPTR); assert(trigger_ctx._update != BONGOCAT_NULLPTR); assert(trigger_ctx._configs_reloaded_cond != BONGOCAT_NULLPTR); - assert(trigger_ctx.anim.shm); + assert(trigger_ctx.thread_context.shm); assert(trigger_ctx.trigger_efd._fd >= 0); assert(trigger_ctx.render_efd._fd >= 0); - assert(trigger_ctx.anim.update_config_efd._fd >= 0); + assert(trigger_ctx.thread_context.update_config_efd._fd >= 0); // init animation state animation_state_t state; { - platform::LockGuard guard(trigger_ctx.anim.anim_lock); - animation_context_t& ctx = trigger_ctx.anim; + platform::LockGuard guard(trigger_ctx.thread_context.anim_lock); + animation_thread_context_t& ctx = trigger_ctx.thread_context; assert(ctx.shm); // assert(input.shm); @@ -4707,11 +4713,11 @@ static void *anim_thread(void *arg) { timespec next_frame_time{}; clock_gettime(CLOCK_MONOTONIC, &next_frame_time); - atomic_store(&trigger_ctx.anim._animation_running, true); - while (atomic_load(&trigger_ctx.anim._animation_running)) { + atomic_store(&trigger_ctx.thread_context._animation_running, true); + while (atomic_load(&trigger_ctx.thread_context._animation_running)) { pthread_testcancel(); // optional, but makes cancellation more responsive - animation_context_t& ctx = trigger_ctx.anim; + animation_thread_context_t& ctx = trigger_ctx.thread_context; // read from config platform::time_ms_t timeout_ms; @@ -4737,8 +4743,8 @@ static void *anim_thread(void *arg) { constexpr size_t fds_animation_trigger_index = 1; constexpr nfds_t fds_count = 2; pollfd fds[fds_count] = { - {.fd = trigger_ctx.anim.update_config_efd._fd, .events = POLLIN, .revents = 0}, - {.fd = trigger_ctx.trigger_efd._fd, .events = POLLIN, .revents = 0}, + {.fd = trigger_ctx.thread_context.update_config_efd._fd, .events = POLLIN, .revents = 0}, + {.fd = trigger_ctx.trigger_efd._fd, .events = POLLIN, .revents = 0}, }; assert(timeout_ms <= INT_MAX); @@ -4815,7 +4821,7 @@ static void *anim_thread(void *arg) { // Update Animations { - platform::LockGuard guard(trigger_ctx.anim.anim_lock); + platform::LockGuard guard(trigger_ctx.thread_context.anim_lock); assert(ctx.shm); const bool frame_changed = anim_update_state(trigger_ctx, state, { @@ -4888,14 +4894,16 @@ static void *anim_thread(void *arg) { BONGOCAT_LOG_WARNING("animation: Timed out waiting for reload eventfd: %s", strerror(errno)); } if constexpr (features::Debug) { - if (atomic_load(&trigger_ctx.anim.config_seen_generation) < atomic_load(trigger_ctx._config_generation)) { + if (atomic_load(&trigger_ctx.thread_context.config_seen_generation) < + atomic_load(trigger_ctx._config_generation)) { BONGOCAT_LOG_VERBOSE( "animation: trigger_ctx.anim.config_seen_generation < trigger_ctx._config_generation; %d < %d", - atomic_load(&trigger_ctx.anim.config_seen_generation), atomic_load(trigger_ctx._config_generation)); + atomic_load(&trigger_ctx.thread_context.config_seen_generation), + atomic_load(trigger_ctx._config_generation)); } } // assert(atomic_load(&trigger_ctx.anim.config_seen_generation) >= atomic_load(trigger_ctx._config_generation)); - atomic_store(&trigger_ctx.anim.config_seen_generation, atomic_load(trigger_ctx._config_generation)); + atomic_store(&trigger_ctx.thread_context.config_seen_generation, atomic_load(trigger_ctx._config_generation)); BONGOCAT_LOG_INFO("animation: Animation config reloaded (gen=%u)", new_gen); } } @@ -4915,33 +4923,34 @@ static void *anim_thread(void *arg) { // PUBLIC API IMPLEMENTATION // ============================================================================= -bongocat_error_t start(animation_session_t& trigger_ctx, platform::input::input_context_t& input, +bongocat_error_t start(animation_context_t& animation_ctx, platform::input::input_context_t& input, platform::update::update_context_t& upd, const config::config_t& config, platform::CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { BONGOCAT_LOG_INFO("Starting animation thread"); // Initialize shared memory for local config - trigger_ctx.anim._local_copy_config = platform::make_allocated_mmap(); - if (!trigger_ctx.anim._local_copy_config) [[unlikely]] { + animation_ctx.thread_context._local_copy_config = platform::make_allocated_mmap(); + if (!animation_ctx.thread_context._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for input monitoring: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(trigger_ctx.anim._local_copy_config); - update_config(trigger_ctx.anim, config, atomic_load(&config_generation)); + assert(animation_ctx.thread_context._local_copy_config); + update_config(animation_ctx.thread_context, config, atomic_load(&config_generation)); // set extern/global references - trigger_ctx._input = &input; - trigger_ctx._update = &upd; - trigger_ctx._config = &config; - trigger_ctx._configs_reloaded_cond = &configs_reloaded_cond; - trigger_ctx._config_generation = &config_generation; - atomic_store(&trigger_ctx.ready, true); - trigger_ctx.init_cond.notify_all(); + animation_ctx._input = &input; + animation_ctx._update = &upd; + animation_ctx._config = &config; + animation_ctx._configs_reloaded_cond = &configs_reloaded_cond; + animation_ctx._config_generation = &config_generation; + atomic_store(&animation_ctx.ready, true); + animation_ctx.init_cond.notify_all(); - trigger_ctx._configs_reloaded_cond->notify_all(); + animation_ctx._configs_reloaded_cond->notify_all(); // start animation thread - const int result = pthread_create(&trigger_ctx.anim._anim_thread, BONGOCAT_NULLPTR, anim_thread, &trigger_ctx); + const int result = + pthread_create(&animation_ctx.thread_context._anim_thread, BONGOCAT_NULLPTR, anim_thread, &animation_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to create animation thread: %s", strerror(result)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -4951,29 +4960,29 @@ bongocat_error_t start(animation_session_t& trigger_ctx, platform::input::input_ return bongocat_error_t::BONGOCAT_SUCCESS; } -void trigger(animation_session_t& trigger_ctx, trigger_animation_cause_mask_t cause) { +void trigger(animation_context_t& animation_ctx, trigger_animation_cause_mask_t cause) { const auto u = static_cast(cause); - if (write(trigger_ctx.trigger_efd._fd, &u, sizeof(uint64_t)) >= 0) { + if (write(animation_ctx.trigger_efd._fd, &u, sizeof(uint64_t)) >= 0) { BONGOCAT_LOG_VERBOSE("Write animation trigger event: %zu", cause); } else { BONGOCAT_LOG_ERROR("Failed to write to notify pipe in animation: %s", strerror(errno)); } } -void trigger_update_config(animation_session_t& trigger_ctx, const config::config_t& config, +void trigger_update_config(animation_context_t& animation_ctx, const config::config_t& config, uint64_t config_generation) { // assert(trigger_ctx.anim._local_copy_config != nullptr); // assert(trigger_ctx.anim.shm != nullptr); - trigger_ctx._config = &config; - if (write(trigger_ctx.anim.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { + animation_ctx._config = &config; + if (write(animation_ctx.thread_context.update_config_efd._fd, &config_generation, sizeof(uint64_t)) >= 0) { BONGOCAT_LOG_VERBOSE("Write animation trigger update config"); } else { BONGOCAT_LOG_ERROR("Failed to write to notify pipe in animation: %s", strerror(errno)); } } -BONGOCAT_NODISCARD static int rand_animation_index(animation_context_t& ctx, const config::config_t& config) { +BONGOCAT_NODISCARD static int rand_animation_index(animation_thread_context_t& ctx, const config::config_t& config) { using namespace assets; assert(ctx._local_copy_config); assert(ctx.shm); @@ -5146,7 +5155,7 @@ BONGOCAT_NODISCARD static int rand_animation_index(animation_context_t& ctx, con return config.animation_index; } -static void update_config_reload_sprite_sheet(animation_context_t& ctx) { +static void update_config_reload_sprite_sheet(animation_thread_context_t& ctx) { using namespace assets; assert(ctx._local_copy_config); @@ -5205,7 +5214,7 @@ static void update_config_reload_sprite_sheet(animation_context_t& ctx) { BONGOCAT_LOG_DEBUG("Update sprite sheet; load assets in %.3fms (%.6fsec)", static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); } -void update_config(animation_context_t& ctx, const config::config_t& config, uint64_t new_gen) { +void update_config(animation_thread_context_t& ctx, const config::config_t& config, uint64_t new_gen) { assert(ctx._local_copy_config); assert(ctx.shm); diff --git a/src/graphics/animation_init.cpp b/src/graphics/animation_init.cpp index 7d7a88f8..b0655d60 100644 --- a/src/graphics/animation_init.cpp +++ b/src/graphics/animation_init.cpp @@ -1,5 +1,5 @@ #include "graphics/animation.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "platform/wayland.h" #include "utils/memory.h" @@ -82,7 +82,7 @@ namespace bongocat::animation { } namespace details { - created_result_t anim_load_custom_animation(animation_context_t& ctx, + created_result_t anim_load_custom_animation(animation_thread_context_t& ctx, const config::config_t& config) { BONGOCAT_CHECK_NULL(config.custom_sprite_sheet_filename, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); if (strlen(config.custom_sprite_sheet_filename) <= 0) { @@ -110,7 +110,7 @@ namespace details { } } // namespace details -created_result_t hot_load_animation(animation_context_t& ctx) { +created_result_t hot_load_animation(animation_thread_context_t& ctx) { // read-only config assert(ctx._local_copy_config); const config::config_t& current_config = *ctx._local_copy_config; @@ -267,7 +267,7 @@ created_result_t hot_load_animation(animation_context_t& ctx) { return ret; } -animation_t& get_current_animation(animation_context_t& ctx) { +animation_t& get_current_animation(animation_thread_context_t& ctx) { using namespace assets; // fallback sprite static animation_t none_sprite_sheet{}; @@ -429,10 +429,10 @@ animation_t& get_current_animation(animation_context_t& ctx) { // PUBLIC API IMPLEMENTATION // ============================================================================= -created_result_t> create(const config::config_t& config) { +created_result_t> create(const config::config_t& config) { using namespace assets; BONGOCAT_LOG_INFO("Initializing animation system"); - AllocatedMemory ret = make_allocated_memory(); + AllocatedMemory ret = make_allocated_memory(); assert(ret); if (!ret) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; @@ -441,23 +441,23 @@ created_result_t> create(const config::conf ret->_config = &config; // Initialize shared memory - ret->anim.shm = platform::make_allocated_mmap(); - if (!ret->anim.shm) { + ret->thread_context.shm = platform::make_allocated_mmap(); + if (!ret->thread_context.shm) { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->anim.shm); + assert(ret->thread_context.shm); // Initialize shared memory for local config - ret->anim._local_copy_config = platform::make_allocated_mmap(); - if (!ret->anim._local_copy_config) { + ret->thread_context._local_copy_config = platform::make_allocated_mmap(); + if (!ret->thread_context._local_copy_config) { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->anim._local_copy_config); + assert(ret->thread_context._local_copy_config); // config_set_defaults(*ctx._local_copy_config); - *ret->anim._local_copy_config = config; - ret->anim.shm->animation_player_result.sprite_sheet_col = config.idle_frame; // initial frame + *ret->thread_context._local_copy_config = config; + ret->thread_context.shm->animation_player_result.sprite_sheet_col = config.idle_frame; // initial frame ret->trigger_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); if (ret->trigger_efd._fd < 0) { @@ -471,8 +471,8 @@ created_result_t> create(const config::conf return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; } - ret->anim.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); - if (ret->anim.update_config_efd._fd < 0) { + ret->thread_context.update_config_efd = platform::FileDescriptor(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); + if (ret->thread_context.update_config_efd._fd < 0) { BONGOCAT_LOG_ERROR("Failed to create notify pipe for input update config: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; } @@ -480,20 +480,20 @@ created_result_t> create(const config::conf [[maybe_unused]] const auto t0 = platform::get_current_time_us(); // Load embedded images/animations if constexpr (features::EnableLazyLoadAssets) { - hot_load_animation(ret->anim); + hot_load_animation(ret->thread_context); } /// @TODO: async assets load // Initialize embedded images/animations if constexpr (!features::EnableLazyLoadAssets || features::EnablePreloadAssets) { - assert(ret->anim._local_copy_config.ptr); + assert(ret->thread_context._local_copy_config.ptr); // preload assets if constexpr (features::EnableBongocatEmbeddedAssets) { // Load Bongocat - if (should_load_bongocat(*ret->anim._local_copy_config)) { + if (should_load_bongocat(*ret->thread_context._local_copy_config)) { BONGOCAT_LOG_INFO("Load bongocat sprite sheet frames: %d", BONGOCAT_EMBEDDED_IMAGES_COUNT); - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes ctx.shm->bongocat_anims = platform::make_allocated_mmap_array(BONGOCAT_ANIM_COUNT); @@ -503,10 +503,10 @@ created_result_t> create(const config::conf if constexpr (features::EnableDmEmbeddedAssets) { // Load dm - if (should_load_dm(*ret->anim._local_copy_config)) { + if (should_load_dm(*ret->thread_context._local_copy_config)) { BONGOCAT_LOG_INFO("Load dm sprite sheets: %d", DM_ANIMATIONS_COUNT); - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes if constexpr (features::EnableMinDmEmbeddedAssets) { BONGOCAT_LOG_INFO("Init min_dm sprite sheets: %d", MIN_DM_ANIM_COUNT); @@ -579,10 +579,10 @@ created_result_t> create(const config::conf if constexpr (features::EnableMsAgentEmbeddedAssets) { // Load Ms Pets (Clippy) - if (should_load_ms_agent(*ret->anim._local_copy_config)) { + if (should_load_ms_agent(*ret->thread_context._local_copy_config)) { BONGOCAT_LOG_INFO("Load MS agent sprite sheets: %d", MS_AGENTS_ANIM_COUNT); - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes ctx.shm->ms_anims = platform::make_allocated_mmap_array(MS_AGENTS_ANIM_COUNT); @@ -605,10 +605,10 @@ created_result_t> create(const config::conf if constexpr (features::EnablePkmnEmbeddedAssets) { // Load pkmn - if (should_load_pkmn(*ret->anim._local_copy_config)) { + if (should_load_pkmn(*ret->thread_context._local_copy_config)) { BONGOCAT_LOG_INFO("Load pkmn sprite sheets: %d", PKMN_ANIM_COUNT); - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes ctx.shm->pkmn_anims = platform::make_allocated_mmap_array(PKMN_ANIM_COUNT); #ifdef FEATURE_PKMN_EMBEDDED_ASSETS @@ -619,10 +619,10 @@ created_result_t> create(const config::conf } if constexpr (features::EnablePmdEmbeddedAssets) { // Load pmd (pkmn) - if (should_load_pkmn(*ret->anim._local_copy_config)) { + if (should_load_pkmn(*ret->thread_context._local_copy_config)) { BONGOCAT_LOG_INFO("Load pmd sprite sheets: %d", PKMN_ANIM_COUNT); - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes ctx.shm->pmd_anims = platform::make_allocated_mmap_array(PMD_ANIM_COUNT); #ifdef FEATURE_PMD_EMBEDDED_ASSETS @@ -634,10 +634,10 @@ created_result_t> create(const config::conf if constexpr (features::EnableMiscEmbeddedAssets) { // Load Misc Pets (neko) - if (should_load_misc(*ret->anim._local_copy_config)) { + if (should_load_misc(*ret->thread_context._local_copy_config)) { BONGOCAT_LOG_INFO("Load Misc sprite sheets: %d", MISC_ANIM_COUNT); - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes ctx.shm->misc_anims = platform::make_allocated_mmap_array(MISC_ANIM_COUNT); @@ -648,11 +648,11 @@ created_result_t> create(const config::conf } if constexpr (features::EnableCustomSpriteSheetsAssets) { - assert(ret->anim._local_copy_config.ptr); + assert(ret->thread_context._local_copy_config.ptr); // Load custom sprite sheet - if (should_load_custom(*ret->anim._local_copy_config)) { - assert(ret->anim.shm); - animation_context_t& ctx = ret->anim; // alias for inits in includes + if (should_load_custom(*ret->thread_context._local_copy_config)) { + assert(ret->thread_context.shm); + animation_thread_context_t& ctx = ret->thread_context; // alias for inits in includes assert(ctx.shm.ptr); assert(ctx._local_copy_config.ptr); @@ -674,14 +674,14 @@ created_result_t> create(const config::conf [[maybe_unused]] const auto t1 = platform::get_current_time_us(); // init anim - ret->anim._rng = platform::random_xoshiro128(platform::slow_rand()); + ret->thread_context._rng = platform::random_xoshiro128(platform::slow_rand()); BONGOCAT_LOG_INFO("Animation system initialized successfully with embedded assets; load assets in %.3fms (%.6fsec)", static_cast(t1 - t0) / 1000.0, static_cast(t1 - t0) / 1000000.0); return ret; } -void stop(animation_context_t& ctx) { +void stop(animation_thread_context_t& ctx) { atomic_store(&ctx._animation_running, false); if (ctx._anim_thread) { BONGOCAT_LOG_DEBUG("Stopping animation thread"); diff --git a/src/graphics/bar.cpp b/src/graphics/bar.cpp index cff5372b..2d0df259 100644 --- a/src/graphics/bar.cpp +++ b/src/graphics/bar.cpp @@ -3,7 +3,7 @@ #include "embedded_assets/bongocat/bongocat.h" #include "embedded_assets/misc/misc.hpp" #include "graphics/animation.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/drawing.h" #include "graphics/embedded_assets_dms.h" #include "graphics/embedded_assets_pkmn.h" @@ -31,7 +31,7 @@ struct cat_rect_t { template /// @TODO: required SpriteSheet must be _sprite_sheet_t -cat_rect_t get_position(const platform::wayland::wayland_context_t& wayland_ctx, const SpriteSheet& sheet, +cat_rect_t get_position(const platform::wayland::wayland_thread_context& wayland_ctx, const SpriteSheet& sheet, const config::config_t& config) { const int cat_height = config.cat_height; const int cat_width = static_cast(static_cast(cat_height) * (static_cast(sheet.frame_width) / @@ -58,7 +58,7 @@ cat_rect_t get_position(const platform::wayland::wayland_context_t& wayland_ctx, } /// @TODO: make draw_sprite more generic (template?) -void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, +void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const bongocat_sprite_sheet_t& sheet, blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { using namespace assets; @@ -66,8 +66,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; + const platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; + animation_thread_context_t& anim = ctx.animation_context->thread_context; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; @@ -183,7 +183,7 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } } -void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, +void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const dm_sprite_sheet_t& sheet, blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { using namespace assets; @@ -191,8 +191,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; + const platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; + animation_thread_context_t& anim = ctx.animation_context->thread_context; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; @@ -335,7 +335,7 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } } -void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, +void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const pkmn_sprite_sheet_t& sheet, blit_image_color_option_flags_t extra_drawing_option = blit_image_color_option_flags_t::Normal) { using namespace assets; @@ -343,8 +343,8 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w return; } - const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; + const platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; + animation_thread_context_t& anim = ctx.animation_context->thread_context; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; @@ -448,13 +448,13 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w } } -void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, +void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const ms_agent_sprite_sheet_t& sheet, int col, int row) { if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { return; } - const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + const platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; @@ -495,15 +495,15 @@ enum class draw_sprite_overwrite_option_t : uint32_t { MovementNoMirror = (1 << 0), MovementMirror = (1 << 1), }; -void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, +void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const custom_sprite_sheet_t& sheet, int col, int row, draw_sprite_overwrite_option_t overwrite_option = draw_sprite_overwrite_option_t::None) { if (sheet.frame_width <= 0 || sheet.frame_height <= 0) { return; } - const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; + const platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; + animation_thread_context_t& anim = ctx.animation_context->thread_context; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; @@ -605,10 +605,10 @@ void draw_sprite(platform::wayland::wayland_session_t& ctx, platform::wayland::w drawing_option); } -static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, +static bool draw_bar_on_buffer(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer) { - const platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; - animation_context_t& anim = ctx.animation_trigger_context->anim; + const platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; + animation_thread_context_t& anim = ctx.animation_context->thread_context; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; // platform::wayland::wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm.ptr; @@ -794,8 +794,8 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_session_t& ctx, return true; } -draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx) { - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; +draw_bar_result_t draw_bar(platform::wayland::wayland_context_t& ctx) { + platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; platform::wayland::wayland_shared_memory_t& wayland_ctx_shm = *wayland_ctx.ctx_shm.ptr; diff --git a/src/graphics/bar.h b/src/graphics/bar.h index 9158cc6c..f8f43bfc 100644 --- a/src/graphics/bar.h +++ b/src/graphics/bar.h @@ -1,7 +1,7 @@ #ifndef BONGOCAT_ANIMATION_BAR_H #define BONGOCAT_ANIMATION_BAR_H -#include "platform/global_wayland_session.h" +#include "platform/wayland_context.h" namespace bongocat::animation { enum class draw_bar_result_t : uint8_t { @@ -12,7 +12,7 @@ enum class draw_bar_result_t : uint8_t { }; // Draw the overlay bar -draw_bar_result_t draw_bar(platform::wayland::wayland_session_t& ctx); +draw_bar_result_t draw_bar(platform::wayland::wayland_context_t& ctx); } // namespace bongocat::animation #endif // BONGOCAT_ANIMATION_BAR_H \ No newline at end of file diff --git a/src/graphics/drawing_images.cpp b/src/graphics/drawing_images.cpp index 60c05183..c8eb6b34 100644 --- a/src/graphics/drawing_images.cpp +++ b/src/graphics/drawing_images.cpp @@ -1,4 +1,4 @@ -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/drawing.h" #include diff --git a/src/image_loader/base_dm/load_dm.cpp b/src/image_loader/base_dm/load_dm.cpp index a871474a..22f556f3 100644 --- a/src/image_loader/base_dm/load_dm.cpp +++ b/src/image_loader/base_dm/load_dm.cpp @@ -1,11 +1,11 @@ #include "image_loader/base_dm/load_dm.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/load_images.h" namespace bongocat::animation { -created_result_t load_dm_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, +created_result_t load_dm_anim(const animation_thread_context_t& ctx, [[maybe_unused]] int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; diff --git a/src/image_loader/bongocat/load_images_bongocat.cpp b/src/image_loader/bongocat/load_images_bongocat.cpp index 64043402..1f82c8c7 100644 --- a/src/image_loader/bongocat/load_images_bongocat.cpp +++ b/src/image_loader/bongocat/load_images_bongocat.cpp @@ -1,7 +1,7 @@ #include "embedded_assets/bongocat/bongocat.h" #include "embedded_assets/bongocat/bongocat.hpp" #include "graphics/animation.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/drawing.h" #include "image_loader/load_images.h" #include "utils/memory.h" @@ -94,7 +94,7 @@ load_bongocat_anim([[maybe_unused]] int anim_index, get_sprite_callback_t get_sp return ret; } -bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, +bongocat_error_t init_bongocat_anim(animation_thread_context_t& ctx, int anim_index, get_sprite_callback_t get_sprite, size_t embedded_images_count) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); @@ -124,7 +124,7 @@ bongocat_error_t init_bongocat_anim(animation_context_t& ctx, int anim_index, ge return bongocat_error_t::BONGOCAT_SUCCESS; } -created_result_t load_bongocat_sprite_sheet(const animation_context_t& /*ctx*/, int index) { +created_result_t load_bongocat_sprite_sheet(const animation_thread_context_t& /*ctx*/, int index) { using namespace assets; using namespace animation; switch (index) { diff --git a/src/image_loader/custom/load_custom.cpp b/src/image_loader/custom/load_custom.cpp index 9e16d44f..383e312d 100644 --- a/src/image_loader/custom/load_custom.cpp +++ b/src/image_loader/custom/load_custom.cpp @@ -1,6 +1,6 @@ #include "image_loader/custom/load_custom.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/load_images.h" namespace bongocat::animation { @@ -28,7 +28,7 @@ void free_custom_sprite_sheet_file(assets::custom_image_t& image) noexcept { } created_result_t -load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, +load_custom_anim(const animation_thread_context_t& ctx, const assets::custom_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { assert(sprite_sheet_image.data._size_bytes >= 0); return load_custom_anim(ctx, @@ -38,7 +38,7 @@ load_custom_anim(const animation_context_t& ctx, const assets::custom_image_t& s sprite_sheet_settings); } created_result_t -load_custom_anim(const animation_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, +load_custom_anim(const animation_thread_context_t& ctx, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { using namespace assets; BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/dm/dm_load_sprite_sheet.cpp b/src/image_loader/dm/dm_load_sprite_sheet.cpp index 4932f310..c941b296 100644 --- a/src/image_loader/dm/dm_load_sprite_sheet.cpp +++ b/src/image_loader/dm/dm_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/dm/dm.hpp" @@ -8,7 +8,7 @@ #include "image_loader/dm/load_images_dm.h" namespace bongocat::animation { - created_result_t load_dm_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_dm_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case DM_AGUMON_ANIM_INDEX: return load_dm_anim(ctx, DM_AGUMON_ANIM_INDEX, get_dm_sprite_sheet(DM_AGUMON_ANIM_INDEX), DM_AGUMON_SPRITE_SHEET_COLS, DM_AGUMON_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/dm/load_images_dm.cpp b/src/image_loader/dm/load_images_dm.cpp index f3950a1c..74ba4dda 100644 --- a/src/image_loader/dm/load_images_dm.cpp +++ b/src/image_loader/dm/load_images_dm.cpp @@ -1,11 +1,11 @@ #include "load_images_dm.h" #include "embedded_assets/dm/dm.hpp" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/load_images.h" #include "image_loader/base_dm/load_dm.h" namespace bongocat::animation { -bongocat_error_t init_dm_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { +bongocat_error_t init_dm_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/dm20/dm20_load_sprite_sheet.cpp b/src/image_loader/dm20/dm20_load_sprite_sheet.cpp index 2c5a06c9..e6a38039 100644 --- a/src/image_loader/dm20/dm20_load_sprite_sheet.cpp +++ b/src/image_loader/dm20/dm20_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/dm20/dm20.hpp" @@ -8,7 +8,7 @@ #include "image_loader/dm20/load_images_dm20.h" namespace bongocat::animation { - created_result_t load_dm20_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_dm20_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case DM20_AEGISDRAMON_ANIM_INDEX: return load_dm_anim(ctx, DM20_AEGISDRAMON_ANIM_INDEX, get_dm20_sprite_sheet(DM20_AEGISDRAMON_ANIM_INDEX), DM20_AEGISDRAMON_SPRITE_SHEET_COLS, DM20_AEGISDRAMON_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/dm20/load_images_dm20.cpp b/src/image_loader/dm20/load_images_dm20.cpp index 0b97b310..1c31899e 100644 --- a/src/image_loader/dm20/load_images_dm20.cpp +++ b/src/image_loader/dm20/load_images_dm20.cpp @@ -1,11 +1,11 @@ #include "load_images_dm20.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/dm20/dm20.hpp" namespace bongocat::animation { - bongocat_error_t init_dm20_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { + bongocat_error_t init_dm20_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/dmall/dmall_load_sprite_sheet.cpp b/src/image_loader/dmall/dmall_load_sprite_sheet.cpp index 154a57a4..ad50beb5 100644 --- a/src/image_loader/dmall/dmall_load_sprite_sheet.cpp +++ b/src/image_loader/dmall/dmall_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/dmall/dmall.hpp" @@ -8,7 +8,7 @@ #include "image_loader/dmall/load_images_dmall.h" namespace bongocat::animation { - created_result_t load_dmall_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_dmall_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case DMALL_AEGISDRAMON_ANIM_INDEX: return load_dm_anim(ctx, DMALL_AEGISDRAMON_ANIM_INDEX, get_dmall_sprite_sheet(DMALL_AEGISDRAMON_ANIM_INDEX), DMALL_AEGISDRAMON_SPRITE_SHEET_COLS, DMALL_AEGISDRAMON_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/dmall/load_images_dmall.cpp b/src/image_loader/dmall/load_images_dmall.cpp index 15981e09..54c3ae4f 100644 --- a/src/image_loader/dmall/load_images_dmall.cpp +++ b/src/image_loader/dmall/load_images_dmall.cpp @@ -1,11 +1,11 @@ #include "load_images_dmall.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/dmall/dmall.hpp" namespace bongocat::animation { - bongocat_error_t init_dmall_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { + bongocat_error_t init_dmall_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/dmc/dmc_load_sprite_sheet.cpp b/src/image_loader/dmc/dmc_load_sprite_sheet.cpp index 337a1ad7..2ccbac6b 100644 --- a/src/image_loader/dmc/dmc_load_sprite_sheet.cpp +++ b/src/image_loader/dmc/dmc_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/dmc/dmc.hpp" @@ -8,7 +8,7 @@ #include "image_loader/dmc/load_images_dmc.h" namespace bongocat::animation { - created_result_t load_dmc_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_dmc_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case DMC_AGUMON_ANIM_INDEX: return load_dm_anim(ctx, DMC_AGUMON_ANIM_INDEX, get_dmc_sprite_sheet(DMC_AGUMON_ANIM_INDEX), DMC_AGUMON_SPRITE_SHEET_COLS, DMC_AGUMON_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/dmc/load_images_dmc.cpp b/src/image_loader/dmc/load_images_dmc.cpp index e53db2dd..d3622439 100644 --- a/src/image_loader/dmc/load_images_dmc.cpp +++ b/src/image_loader/dmc/load_images_dmc.cpp @@ -1,11 +1,11 @@ #include "load_images_dmc.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/dmc/dmc.hpp" namespace bongocat::animation { - bongocat_error_t init_dmc_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { + bongocat_error_t init_dmc_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/dmx/dmx_load_sprite_sheet.cpp b/src/image_loader/dmx/dmx_load_sprite_sheet.cpp index 3fd03ef8..c068f93b 100644 --- a/src/image_loader/dmx/dmx_load_sprite_sheet.cpp +++ b/src/image_loader/dmx/dmx_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/dmx/dmx.hpp" @@ -8,7 +8,7 @@ #include "image_loader/dmx/load_images_dmx.h" namespace bongocat::animation { - created_result_t load_dmx_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_dmx_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case DMX_AGUMON_BLACK_X_ANIM_INDEX: return load_dm_anim(ctx, DMX_AGUMON_BLACK_X_ANIM_INDEX, get_dmx_sprite_sheet(DMX_AGUMON_BLACK_X_ANIM_INDEX), DMX_AGUMON_BLACK_X_SPRITE_SHEET_COLS, DMX_AGUMON_BLACK_X_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/dmx/load_images_dmx.cpp b/src/image_loader/dmx/load_images_dmx.cpp index 55906f3f..0c4f1059 100644 --- a/src/image_loader/dmx/load_images_dmx.cpp +++ b/src/image_loader/dmx/load_images_dmx.cpp @@ -1,12 +1,12 @@ #include "load_images_dmx.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/dmx/dmx.hpp" #include "utils/error.h" namespace bongocat::animation { - bongocat_error_t init_dmx_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { + bongocat_error_t init_dmx_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/load_images.cpp b/src/image_loader/load_images.cpp index 959912ba..26a863c6 100644 --- a/src/image_loader/load_images.cpp +++ b/src/image_loader/load_images.cpp @@ -1,7 +1,7 @@ #include "image_loader/load_images.h" #include "graphics/animation.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/drawing.h" #include "utils/memory.h" diff --git a/src/image_loader/min_dm/load_images_min_dm.cpp b/src/image_loader/min_dm/load_images_min_dm.cpp index e2fbcd9c..b91b7374 100644 --- a/src/image_loader/min_dm/load_images_min_dm.cpp +++ b/src/image_loader/min_dm/load_images_min_dm.cpp @@ -2,11 +2,11 @@ #include "embedded_assets/embedded_image.h" #include "embedded_assets/min_dm/min_dm.hpp" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/base_dm/load_dm.h" namespace bongocat::animation { -bongocat_error_t init_min_dm_anim(animation_context_t& ctx, int anim_index, +bongocat_error_t init_min_dm_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; diff --git a/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp b/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp index 341922cf..ca68650e 100644 --- a/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp +++ b/src/image_loader/min_dm/min_dm_load_sprite_sheet.cpp @@ -2,13 +2,13 @@ #include "embedded_assets/embedded_image.h" #include "embedded_assets/min_dm/min_dm.hpp" #include "embedded_assets/min_dm/min_dm_sprite.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "image_loader/min_dm/load_images_min_dm.h" namespace bongocat::animation { -created_result_t load_min_dm_sprite_sheet(const animation_context_t& ctx, int index) { +created_result_t load_min_dm_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace animation; using namespace assets; switch (index) { diff --git a/src/image_loader/misc/load_images_misc.cpp b/src/image_loader/misc/load_images_misc.cpp index df93da54..837b4524 100644 --- a/src/image_loader/misc/load_images_misc.cpp +++ b/src/image_loader/misc/load_images_misc.cpp @@ -2,11 +2,11 @@ #include "embedded_assets/embedded_image.h" #include "embedded_assets/misc/misc.hpp" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/custom/load_custom.h" namespace bongocat::animation { -bongocat_error_t init_misc_anim(animation_context_t& ctx, int anim_index, +bongocat_error_t init_misc_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { using namespace assets; diff --git a/src/image_loader/misc/misc_load_sprite_sheet.cpp b/src/image_loader/misc/misc_load_sprite_sheet.cpp index f63b806a..16e1c2b3 100644 --- a/src/image_loader/misc/misc_load_sprite_sheet.cpp +++ b/src/image_loader/misc/misc_load_sprite_sheet.cpp @@ -2,12 +2,12 @@ #include "embedded_assets/embedded_image.h" #include "embedded_assets/misc/misc.hpp" #include "embedded_assets/misc/misc_sprite.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/custom/load_custom.h" namespace bongocat::animation { -created_result_t load_misc_sprite_sheet(const animation_context_t& ctx, int index) { +created_result_t load_misc_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace animation; using namespace assets; switch (index) { diff --git a/src/image_loader/ms_agent/load_images_ms_agent.cpp b/src/image_loader/ms_agent/load_images_ms_agent.cpp index 044923b9..e0facc72 100644 --- a/src/image_loader/ms_agent/load_images_ms_agent.cpp +++ b/src/image_loader/ms_agent/load_images_ms_agent.cpp @@ -1,7 +1,7 @@ #include "embedded_assets/ms_agent/ms_agent.hpp" #include "embedded_assets/ms_agent/ms_agent_sprite.h" #include "graphics/animation.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/drawing.h" #include "image_loader/load_images.h" #include "utils/memory.h" @@ -134,7 +134,7 @@ created_result_t load_ms_agent_sprite_sheet(const confi } created_result_t -load_ms_agent_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, +load_ms_agent_anim(const animation_thread_context_t& ctx, [[maybe_unused]] int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data) { using namespace assets; @@ -241,7 +241,7 @@ load_ms_agent_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_ind } created_result_t -init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, +init_ms_agent_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows, const assets::ms_agent_animation_indices_t& animation_data) { using namespace assets; @@ -267,7 +267,7 @@ init_ms_agent_anim(animation_context_t& ctx, int anim_index, const assets::embed return bongocat_error_t::BONGOCAT_SUCCESS; } -created_result_t load_ms_agent_sprite_sheet(const animation_context_t& ctx, int index) { +created_result_t load_ms_agent_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; using namespace animation; switch (index) { diff --git a/src/image_loader/pen/load_images_pen.cpp b/src/image_loader/pen/load_images_pen.cpp index 9e3fcba6..e3767f6e 100644 --- a/src/image_loader/pen/load_images_pen.cpp +++ b/src/image_loader/pen/load_images_pen.cpp @@ -1,11 +1,11 @@ #include "load_images_pen.h" #include "embedded_assets/pen/pen.hpp" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/load_images.h" #include "image_loader/base_dm/load_dm.h" namespace bongocat::animation { -bongocat_error_t init_pen_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { +bongocat_error_t init_pen_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/pen/pen_load_sprite_sheet.cpp b/src/image_loader/pen/pen_load_sprite_sheet.cpp index fc8864aa..b8e9cd46 100644 --- a/src/image_loader/pen/pen_load_sprite_sheet.cpp +++ b/src/image_loader/pen/pen_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/pen/pen.hpp" @@ -8,7 +8,7 @@ #include "image_loader/pen/load_images_pen.h" namespace bongocat::animation { - created_result_t load_pen_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_pen_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case PEN_AERO_V_DRAMON_ANIM_INDEX: return load_dm_anim(ctx, PEN_AERO_V_DRAMON_ANIM_INDEX, get_pen_sprite_sheet(PEN_AERO_V_DRAMON_ANIM_INDEX), PEN_AERO_V_DRAMON_SPRITE_SHEET_COLS, PEN_AERO_V_DRAMON_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/pen20/load_images_pen20.cpp b/src/image_loader/pen20/load_images_pen20.cpp index 53dd767e..a509301b 100644 --- a/src/image_loader/pen20/load_images_pen20.cpp +++ b/src/image_loader/pen20/load_images_pen20.cpp @@ -1,11 +1,11 @@ #include "load_images_pen20.h" #include "embedded_assets/pen20/pen20.hpp" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/load_images.h" #include "image_loader/base_dm/load_dm.h" namespace bongocat::animation { -bongocat_error_t init_pen20_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { +bongocat_error_t init_pen20_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/pen20/pen20_load_sprite_sheet.cpp b/src/image_loader/pen20/pen20_load_sprite_sheet.cpp index cd7f02f7..b94aac08 100644 --- a/src/image_loader/pen20/pen20_load_sprite_sheet.cpp +++ b/src/image_loader/pen20/pen20_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/pen20/pen20.hpp" @@ -8,7 +8,7 @@ #include "image_loader/pen20/load_images_pen20.h" namespace bongocat::animation { - created_result_t load_pen20_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_pen20_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case PEN20_AERO_V_DRAMON_ANIM_INDEX: return load_dm_anim(ctx, PEN20_AERO_V_DRAMON_ANIM_INDEX, get_pen20_sprite_sheet(PEN20_AERO_V_DRAMON_ANIM_INDEX), PEN20_AERO_V_DRAMON_SPRITE_SHEET_COLS, PEN20_AERO_V_DRAMON_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/pkmn/load_images_pkmn.cpp b/src/image_loader/pkmn/load_images_pkmn.cpp index 7f18fd4e..89248d83 100644 --- a/src/image_loader/pkmn/load_images_pkmn.cpp +++ b/src/image_loader/pkmn/load_images_pkmn.cpp @@ -1,5 +1,5 @@ #include "load_images_pkmn.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/base_dm/load_dm.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/pkmn/pkmn.hpp" @@ -7,7 +7,7 @@ #include "utils/error.h" namespace bongocat::animation { - created_result_t load_pkmn_anim(const animation_context_t& ctx, [[maybe_unused]] int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { + created_result_t load_pkmn_anim(const animation_thread_context_t& ctx, [[maybe_unused]] int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); @@ -89,7 +89,7 @@ namespace bongocat::animation { return ret; } - bongocat_error_t init_pkmn_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { + bongocat_error_t init_pkmn_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, int sprite_sheet_cols, int sprite_sheet_rows) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/pkmn/pkmn_load_sprite_sheet.cpp b/src/image_loader/pkmn/pkmn_load_sprite_sheet.cpp index 56934d9c..d272fa3c 100644 --- a/src/image_loader/pkmn/pkmn_load_sprite_sheet.cpp +++ b/src/image_loader/pkmn/pkmn_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "embedded_assets/pkmn/pkmn.hpp" #include "embedded_assets/embedded_image.h" @@ -7,7 +7,7 @@ #include "image_loader/pkmn/load_images_pkmn.h" namespace bongocat::animation { - created_result_t load_pkmn_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_pkmn_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case PKMN_BULBASAUR_ANIM_INDEX: return load_pkmn_anim(ctx, PKMN_BULBASAUR_ANIM_INDEX, get_pkmn_sprite_sheet(PKMN_BULBASAUR_ANIM_INDEX), PKMN_BULBASAUR_SPRITE_SHEET_COLS, PKMN_BULBASAUR_SPRITE_SHEET_ROWS); diff --git a/src/image_loader/pmd/load_images_pmd.cpp b/src/image_loader/pmd/load_images_pmd.cpp index dbcf0996..564bcb45 100644 --- a/src/image_loader/pmd/load_images_pmd.cpp +++ b/src/image_loader/pmd/load_images_pmd.cpp @@ -1,11 +1,11 @@ #include "load_images_pmd.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "image_loader/custom/load_custom.h" #include "embedded_assets/embedded_image.h" #include "embedded_assets/pmd/pmd.hpp" namespace bongocat::animation { - bongocat_error_t init_pmd_anim(animation_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { + bongocat_error_t init_pmd_anim(animation_thread_context_t& ctx, int anim_index, const assets::embedded_image_t& sprite_sheet_image, const assets::custom_animation_settings_t& sprite_sheet_settings) { using namespace assets; BONGOCAT_CHECK_NULL(ctx.shm.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); BONGOCAT_CHECK_NULL(ctx._local_copy_config.ptr, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); diff --git a/src/image_loader/pmd/pmd_load_sprite_sheet.cpp b/src/image_loader/pmd/pmd_load_sprite_sheet.cpp index aa69ea58..1dc59b40 100644 --- a/src/image_loader/pmd/pmd_load_sprite_sheet.cpp +++ b/src/image_loader/pmd/pmd_load_sprite_sheet.cpp @@ -1,5 +1,5 @@ #include "core/bongocat.h" -#include "graphics/animation_context.h" +#include "graphics/animation_thread_context.h" #include "graphics/sprite_sheet.h" #include "image_loader/custom/load_custom.h" #include "embedded_assets/pmd/pmd.hpp" @@ -8,7 +8,7 @@ #include "image_loader/pmd/load_images_pmd.h" namespace bongocat::animation { - created_result_t load_pmd_sprite_sheet(const animation_context_t& ctx, int index) { + created_result_t load_pmd_sprite_sheet(const animation_thread_context_t& ctx, int index) { using namespace assets; switch (index) { case PMD_BULBASAUR_ANIM_INDEX: return load_custom_anim(ctx, get_pmd_sprite_sheet(PMD_BULBASAUR_ANIM_INDEX), get_pmd_sprite_sheet_settings(PMD_BULBASAUR_ANIM_INDEX)); diff --git a/src/platform/input.cpp b/src/platform/input.cpp index f08b2ce5..c61a9df5 100644 --- a/src/platform/input.cpp +++ b/src/platform/input.cpp @@ -102,15 +102,15 @@ static void cleanup_input_thread_context(input_context_t& input) { static void cleanup_input_thread(void *arg) { assert(arg); - const animation::animation_session_t& trigger_ctx = *static_cast(arg); - assert(trigger_ctx._input); - input_context_t& input = *trigger_ctx._input; + const animation::animation_context_t& animation_context = *static_cast(arg); + assert(animation_context._input); + input_context_t& input = *animation_context._input; atomic_store(&input._capture_input_running, false); input.config_updated.notify_all(); - cleanup_input_thread_context(*trigger_ctx._input); + cleanup_input_thread_context(*animation_context._input); BONGOCAT_LOG_INFO("Input thread cleanup completed (via pthread_cancel)"); } @@ -123,10 +123,10 @@ inline static bool is_open_device_valid(int fd) { struct stat fd_st{}; return fd >= 0 && fstat(fd, &fd_st) == 0 && (S_ISCHR(fd_st.st_mode) && !S_ISLNK(fd_st.st_mode)); } -inline static void trigger_key_press(animation::animation_session_t& trigger_ctx, int keycode = 0) { - assert(trigger_ctx._input); +inline static void trigger_key_press(animation::animation_context_t& animation_ctx, int keycode = 0) { + assert(animation_ctx._input); // animation_context_t& anim = trigger_ctx.anim; - input_context_t& input = *trigger_ctx._input; + input_context_t& input = *animation_ctx._input; // read-only config assert(input._local_copy_config); @@ -170,7 +170,7 @@ inline static void trigger_key_press(animation::animation_session_t& trigger_ctx } else { input.shm->hand_mapping = input_hand_mapping_t::None; } - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::KeyPress); + animation::trigger(animation_ctx, animation::trigger_animation_cause_mask_t::KeyPress); } // for testing @@ -388,15 +388,15 @@ static bongocat_error_t setup_udev_monitor(input_context_t& input) { static void *input_thread(void *arg) { assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); + animation::animation_context_t& animation_ctx = *static_cast(arg); // from thread context // animation_context_t& anim = trigger_ctx.anim; // wait for input context (in animation start) - trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, - COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - assert(trigger_ctx._input != BONGOCAT_NULLPTR); - input_context_t& input = *trigger_ctx._input; + animation_ctx.init_cond.timedwait([&]() { return atomic_load(&animation_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + assert(animation_ctx._input != BONGOCAT_NULLPTR); + input_context_t& input = *animation_ctx._input; // sanity checks assert(input._config != BONGOCAT_NULLPTR); @@ -461,7 +461,7 @@ static void *input_thread(void *arg) { } // trigger initial render - wayland::request_render(trigger_ctx); + wayland::request_render(animation_ctx); pthread_cleanup_push(cleanup_input_thread, arg); @@ -918,7 +918,7 @@ static void *input_thread(void *arg) { const timestamp_ms_t now = get_current_time_ms(); if (key_pressed) { - trigger_key_press(trigger_ctx, captured_keycode); + trigger_key_press(animation_ctx, captured_keycode); } else { input.shm->any_key_pressed = 0; input.shm->hand_mapping = input_hand_mapping_t::None; @@ -972,7 +972,7 @@ static void *input_thread(void *arg) { } if (got_key) { - trigger_key_press(trigger_ctx); + trigger_key_press(animation_ctx); if (enable_debug) { const size_t len = (rd > 0) ? (static_cast(rd) < TEST_STDIN_BUF_LEN ? static_cast(rd) : TEST_STDIN_BUF_LEN - 1) @@ -1098,7 +1098,7 @@ created_result_t> create(const config::config_t return ret; } -bongocat_error_t start(input_context_t& input, animation::animation_session_t& trigger_ctx, +bongocat_error_t start(input_context_t& input, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { if (config.num_keyboard_devices < 0) { @@ -1135,20 +1135,20 @@ bongocat_error_t start(input_context_t& input, animation::animation_session_t& t update_config(input, config, atomic_load(&config_generation)); // wait for animation trigger to be ready (input should be the same) - int cond_ret = trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, - COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + int cond_ret = animation_ctx.init_cond.timedwait([&]() { return atomic_load(&animation_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); if (cond_ret == ETIMEDOUT) { BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); } else { - assert(trigger_ctx._input == &input); + assert(animation_ctx._input == &input); } // set extern/global references { // guard for anim_update_state - LockGuard guard(trigger_ctx.anim.anim_lock); - trigger_ctx._input = &input; + LockGuard guard(animation_ctx.thread_context.anim_lock); + animation_ctx._input = &input; } - trigger_ctx.init_cond.notify_all(); + animation_ctx.init_cond.notify_all(); input._config = &config; input._configs_reloaded_cond = &configs_reloaded_cond; input._config_generation = &config_generation; @@ -1158,7 +1158,7 @@ bongocat_error_t start(input_context_t& input, animation::animation_session_t& t input._configs_reloaded_cond->notify_all(); // start input monitoring thread - const int result = pthread_create(&input._input_thread, BONGOCAT_NULLPTR, input_thread, &trigger_ctx); + const int result = pthread_create(&input._input_thread, BONGOCAT_NULLPTR, input_thread, &animation_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -1168,7 +1168,7 @@ bongocat_error_t start(input_context_t& input, animation::animation_session_t& t return bongocat_error_t::BONGOCAT_SUCCESS; } -bongocat_error_t restart(input_context_t& input, animation::animation_session_t& trigger_ctx, +bongocat_error_t restart(input_context_t& input, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { BONGOCAT_LOG_INFO("Restarting input monitoring system"); @@ -1232,8 +1232,8 @@ bongocat_error_t restart(input_context_t& input, animation::animation_session_t& // set extern/global references { // guard for anim_update_state - LockGuard guard(trigger_ctx.anim.anim_lock); - trigger_ctx._input = &input; + LockGuard guard(animation_ctx.thread_context.anim_lock); + animation_ctx._input = &input; } input._config = &config; input._configs_reloaded_cond = &configs_reloaded_cond; @@ -1243,7 +1243,7 @@ bongocat_error_t restart(input_context_t& input, animation::animation_session_t& input._configs_reloaded_cond->notify_all(); // start input monitoring - const int result = pthread_create(&input._input_thread, BONGOCAT_NULLPTR, input_thread, &trigger_ctx); + const int result = pthread_create(&input._input_thread, BONGOCAT_NULLPTR, input_thread, &animation_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start input monitoring thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; diff --git a/src/platform/update.cpp b/src/platform/update.cpp index f37c6ff8..12780099 100644 --- a/src/platform/update.cpp +++ b/src/platform/update.cpp @@ -35,9 +35,9 @@ inline static constexpr const char *FILENAME_PROC_STAT = "/proc/stat"; static void cleanup_update_thread(void *arg) { assert(arg); - const animation::animation_session_t& trigger_ctx = *static_cast(arg); - assert(trigger_ctx._update); - update_context_t& input = *trigger_ctx._update; + const animation::animation_context_t& animation_context = *static_cast(arg); + assert(animation_context._update); + update_context_t& input = *animation_context._update; atomic_store(&input._running, false); @@ -216,15 +216,15 @@ static double compute_max_cpu_usage(const cpu_snapshot_t& prev, const cpu_snapsh static void *update_thread(void *arg) { assert(arg); - animation::animation_session_t& trigger_ctx = *static_cast(arg); + animation::animation_context_t& animation_ctx = *static_cast(arg); // from thread context // animation_context_t& anim = trigger_ctx.anim; // wait for input context (in animation start) - trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, - COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); - assert(trigger_ctx._input != BONGOCAT_NULLPTR); - update_context_t& upd = *trigger_ctx._update; + animation_ctx.init_cond.timedwait([&]() { return atomic_load(&animation_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + assert(animation_ctx._input != BONGOCAT_NULLPTR); + update_context_t& upd = *animation_ctx._update; // sanity checks assert(upd._config != BONGOCAT_NULLPTR); @@ -237,7 +237,7 @@ static void *update_thread(void *arg) { assert(upd.fd_stat._fd >= 0); // trigger initial render - wayland::request_render(trigger_ctx); + wayland::request_render(animation_ctx); pthread_cleanup_push(cleanup_update_thread, arg); @@ -393,9 +393,9 @@ static void *update_thread(void *arg) { if (!update_shm.cpu_active || crossed_delta) { BONGOCAT_LOG_VERBOSE("update: avg. CPU: %.2f (max: %.2f)", update_shm.avg_cpu_usage, update_shm.max_cpu_usage); - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); + animation::trigger(animation_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); if (lower_threshold) { - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); + animation::trigger(animation_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); } animation_triggered = true; } @@ -403,8 +403,8 @@ static void *update_thread(void *arg) { } else { if (update_shm.cpu_active) { update_shm.cpu_active = false; - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); - animation::trigger(trigger_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); + animation::trigger(animation_ctx, animation::trigger_animation_cause_mask_t::CpuUpdate); + animation::trigger(animation_ctx, animation::trigger_animation_cause_mask_t::IdleUpdate); } } } @@ -533,7 +533,7 @@ created_result_t> create(const config::config_ return ret; } -bongocat_error_t start(update_context_t& upd, animation::animation_session_t& trigger_ctx, +bongocat_error_t start(update_context_t& upd, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { BONGOCAT_LOG_INFO("Initializing update monitoring"); @@ -555,20 +555,20 @@ bongocat_error_t start(update_context_t& upd, animation::animation_session_t& tr update_config(upd, config, atomic_load(&config_generation)); // wait for animation trigger to be ready (input should be the same) - const int cond_ret = trigger_ctx.init_cond.timedwait([&]() { return atomic_load(&trigger_ctx.ready); }, - COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); + const int cond_ret = animation_ctx.init_cond.timedwait([&]() { return atomic_load(&animation_ctx.ready); }, + COND_ANIMATION_TRIGGER_INIT_TIMEOUT_MS); if (cond_ret == ETIMEDOUT) { BONGOCAT_LOG_ERROR("Failed to initialize input monitoring: waiting for animation thread to start in time"); } else { - assert(trigger_ctx._update == &upd); + assert(animation_ctx._update == &upd); } // set extern/global references { // guard for anim_update_state - LockGuard guard(trigger_ctx.anim.anim_lock); - trigger_ctx._update = &upd; + LockGuard guard(animation_ctx.thread_context.anim_lock); + animation_ctx._update = &upd; } - trigger_ctx.init_cond.notify_all(); + animation_ctx.init_cond.notify_all(); upd._config = &config; upd._configs_reloaded_cond = &configs_reloaded_cond; upd._config_generation = &config_generation; @@ -578,7 +578,7 @@ bongocat_error_t start(update_context_t& upd, animation::animation_session_t& tr upd._configs_reloaded_cond->notify_all(); // start update thread - const int result = pthread_create(&upd._update_thread, BONGOCAT_NULLPTR, update_thread, &trigger_ctx); + const int result = pthread_create(&upd._update_thread, BONGOCAT_NULLPTR, update_thread, &animation_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; @@ -588,7 +588,7 @@ bongocat_error_t start(update_context_t& upd, animation::animation_session_t& tr return bongocat_error_t::BONGOCAT_SUCCESS; } -bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& trigger_ctx, +bongocat_error_t restart(update_context_t& upd, animation::animation_context_t& animation_ctx, const config::config_t& config, CondVariable& configs_reloaded_cond, atomic_uint64_t& config_generation) { BONGOCAT_LOG_INFO("Restarting update system"); @@ -661,8 +661,8 @@ bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& // set extern/global references { // guard for anim_update_state - LockGuard guard(trigger_ctx.anim.anim_lock); - trigger_ctx._update = &upd; + LockGuard guard(animation_ctx.thread_context.anim_lock); + animation_ctx._update = &upd; } upd._config = &config; upd._configs_reloaded_cond = &configs_reloaded_cond; @@ -672,7 +672,7 @@ bongocat_error_t restart(update_context_t& upd, animation::animation_session_t& upd._configs_reloaded_cond->notify_all(); // start update monitoring - const int result = pthread_create(&upd._update_thread, BONGOCAT_NULLPTR, update_thread, &trigger_ctx); + const int result = pthread_create(&upd._update_thread, BONGOCAT_NULLPTR, update_thread, &animation_ctx); if (result != 0) { BONGOCAT_LOG_ERROR("Failed to start update thread: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_THREAD; diff --git a/src/platform/wayland.cpp b/src/platform/wayland.cpp index 746b9c35..5de338b9 100644 --- a/src/platform/wayland.cpp +++ b/src/platform/wayland.cpp @@ -2,8 +2,8 @@ #include "../graphics/bar.h" #include "graphics/animation.h" -#include "platform/global_wayland_session.h" #include "platform/wayland-protocols.hpp" +#include "platform/wayland_context.h" #include "platform/wayland_shared_memory.h" #include "utils/memory.h" #include "wayland_hyprland.h" @@ -46,60 +46,60 @@ static inline constexpr auto WAYLAND_LAYER_NAME = "OVERLAY"; // MAIN WAYLAND INTERFACE IMPLEMENTATION // ============================================================================= -created_result_t> create(animation::animation_session_t& anim, +created_result_t> create(animation::animation_context_t& animation_ctx, const config::config_t& config) { - AllocatedMemory ret = make_allocated_memory(); + AllocatedMemory ret = make_allocated_memory(); assert(ret); if (!ret) [[unlikely]] { return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - ret->animation_trigger_context = &anim; - ret->wayland_context._bar_height = DEFAULT_BAR_HEIGHT; + ret->animation_context = &animation_ctx; + ret->thread_context._bar_height = DEFAULT_BAR_HEIGHT; // Initialize shared memory - ret->wayland_context.ctx_shm = make_allocated_mmap(); - if (!ret->wayland_context.ctx_shm) [[unlikely]] { + ret->thread_context.ctx_shm = make_allocated_mmap(); + if (!ret->thread_context.ctx_shm) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - if (ret->wayland_context.ctx_shm) [[unlikely]] { + if (ret->thread_context.ctx_shm) [[unlikely]] { static_assert(WAYLAND_NUM_BUFFERS <= INT_MAX); for (size_t i = 0; i < WAYLAND_NUM_BUFFERS; i++) { - ret->wayland_context.ctx_shm->buffers[i] = {}; + ret->thread_context.ctx_shm->buffers[i] = {}; } - atomic_store(&ret->wayland_context.ctx_shm->configured, false); + atomic_store(&ret->thread_context.ctx_shm->configured, false); } // Initialize shared memory for local config - ret->wayland_context._local_copy_config = make_allocated_mmap(); - if (!ret->wayland_context._local_copy_config) [[unlikely]] { + ret->thread_context._local_copy_config = make_allocated_mmap(); + if (!ret->thread_context._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - assert(ret->wayland_context._local_copy_config); - *ret->wayland_context._local_copy_config = config; - ret->wayland_context._bar_height = config.overlay_height; + assert(ret->thread_context._local_copy_config); + *ret->thread_context._local_copy_config = config; + ret->thread_context._bar_height = config.overlay_height; return ret; } -bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& anim) { - ctx.animation_trigger_context = &anim; +bongocat_error_t setup(wayland_context_t& ctx, animation::animation_context_t& animation_ctx) { + ctx.animation_context = &animation_ctx; - if (!ctx.wayland_context.ctx_shm) [[unlikely]] { + if (!ctx.thread_context.ctx_shm) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } - if (!ctx.wayland_context._local_copy_config) [[unlikely]] { + if (!ctx.thread_context._local_copy_config) [[unlikely]] { BONGOCAT_LOG_ERROR("Failed to create shared memory for animation system: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_MEMORY; } BONGOCAT_LOG_INFO("Initializing Wayland connection"); - ctx.wayland_context.display = wl_display_connect(BONGOCAT_NULLPTR); - if (ctx.wayland_context.display == BONGOCAT_NULLPTR) { + ctx.thread_context.display = wl_display_connect(BONGOCAT_NULLPTR); + if (ctx.thread_context.display == BONGOCAT_NULLPTR) { BONGOCAT_LOG_ERROR("Failed to connect to Wayland display"); return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } @@ -112,34 +112,34 @@ bongocat_error_t setup(wayland_session_t& ctx, animation::animation_session_t& a if (result != bongocat_error_t::BONGOCAT_SUCCESS) { return result; } - result = details::wayland_setup_buffer(ctx.wayland_context, *ctx.animation_trigger_context); + result = details::wayland_setup_buffer(ctx.thread_context, *ctx.animation_context); if (result != bongocat_error_t::BONGOCAT_SUCCESS) { return result; } atomic_store(&ctx.ready, true); - BONGOCAT_LOG_INFO("Wayland initialization complete (%dx%d buffer)", ctx.wayland_context._screen_width, - ctx.wayland_context._bar_height); + BONGOCAT_LOG_INFO("Wayland initialization complete (%dx%d buffer)", ctx.thread_context._screen_width, + ctx.thread_context._bar_height); return bongocat_error_t::BONGOCAT_SUCCESS; } -bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int signal_fd, +bongocat_error_t run(wayland_context_t& ctx, volatile sig_atomic_t& running, int signal_fd, input::input_context_t& input, const config::config_t& config, const config::config_watcher_t *config_watcher, config_reload_callback_t config_reload_callback) { BONGOCAT_CHECK_NULL(config_reload_callback, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); - BONGOCAT_CHECK_NULL(ctx.animation_trigger_context, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); + BONGOCAT_CHECK_NULL(ctx.animation_context, bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM); // from thread context - wayland_context_t& wayland_ctx = ctx.wayland_context; + wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = trigger_ctx.anim; // wait for context - ctx.animation_trigger_context->init_cond.timedwait( - [&]() { return atomic_load(&ctx.animation_trigger_context->ready); }, COND_INIT_TIMEOUT_MS); - input.init_cond.timedwait([&]() { return atomic_load(&ctx.animation_trigger_context->ready); }, COND_INIT_TIMEOUT_MS); - animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; - assert(trigger_ctx._input != BONGOCAT_NULLPTR); - assert(trigger_ctx._input == &input); + ctx.animation_context->init_cond.timedwait([&]() { return atomic_load(&ctx.animation_context->ready); }, + COND_INIT_TIMEOUT_MS); + input.init_cond.timedwait([&]() { return atomic_load(&ctx.animation_context->ready); }, COND_INIT_TIMEOUT_MS); + animation::animation_context_t& animation_ctx = *ctx.animation_context; + assert(animation_ctx._input != BONGOCAT_NULLPTR); + assert(animation_ctx._input == &input); // wayland_shared_memory_t *wayland_ctx_shm = wayland_ctx.ctx_shm; BONGOCAT_LOG_INFO("Starting Wayland event loop"); @@ -172,7 +172,7 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int {.fd = config_watcher != BONGOCAT_NULLPTR ? config_watcher->reload_efd._fd : -1, .events = POLLIN, .revents = 0 }, - {.fd = trigger_ctx.render_efd._fd, .events = POLLIN, .revents = 0}, + {.fd = animation_ctx.render_efd._fd, .events = POLLIN, .revents = 0}, {.fd = wl_display_get_fd(wayland_ctx.display), .events = POLLIN, .revents = 0}, }; static_assert(fds_count == LEN_ARRAY(fds)); @@ -383,7 +383,7 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int } else { if (!atomic_exchange(&wayland_ctx._redraw_after_frame, true)) { BONGOCAT_LOG_VERBOSE("Queued redraw after frame"); - request_render(trigger_ctx); + request_render(animation_ctx); } else { const auto draw_bar_result = animation::draw_bar(ctx); needs_flush = draw_bar_result == animation::draw_bar_result_t::FlushNeeded; @@ -421,88 +421,88 @@ bongocat_error_t run(wayland_session_t& ctx, volatile sig_atomic_t& running, int // PUBLIC API IMPLEMENTATION // ============================================================================= -int get_screen_width(const wayland_session_t& ctx) { - return ctx.wayland_context._screen_info != BONGOCAT_NULLPTR ? ctx.wayland_context._screen_info->screen_width : 0; +int get_screen_width(const wayland_context_t& ctx) { + return ctx.thread_context._screen_info != BONGOCAT_NULLPTR ? ctx.thread_context._screen_info->screen_width : 0; } -void update_config(wayland_session_t& ctx, const config::config_t& config, - animation::animation_session_t& trigger_ctx) { - assert(ctx.wayland_context._local_copy_config); +void update_config(wayland_context_t& ctx, const config::config_t& config, + animation::animation_context_t& animation_ctx) { + assert(ctx.thread_context._local_copy_config); // Check if dimensions changed - requires buffer/surface recreation const auto old_height = - ctx.wayland_context._local_copy_config ? ctx.wayland_context._local_copy_config->overlay_height : 0; - const auto old_width = ctx.wayland_context._screen_width; + ctx.thread_context._local_copy_config ? ctx.thread_context._local_copy_config->overlay_height : 0; + const auto old_width = ctx.thread_context._screen_width; // update old config - *ctx.wayland_context._local_copy_config = config; + *ctx.thread_context._local_copy_config = config; - const bool dimensions_changed = (old_height != ctx.wayland_context._local_copy_config->overlay_height) || - (ctx.wayland_context._local_copy_config->screen_width > 0 && - old_width != ctx.wayland_context._local_copy_config->screen_width); + const bool dimensions_changed = (old_height != ctx.thread_context._local_copy_config->overlay_height) || + (ctx.thread_context._local_copy_config->screen_width > 0 && + old_width != ctx.thread_context._local_copy_config->screen_width); - if (dimensions_changed && old_height > 0 && old_width > 0 && ctx.wayland_context.ctx_shm) { + if (dimensions_changed && old_height > 0 && old_width > 0 && ctx.thread_context.ctx_shm) { // ~~Lock animation mutex to prevent draw_bar() during config update // This is critical - animation thread must not access buffer while we // recreate it~~ // not sure if this is needed, animation thread don't touch the bar directly, // it just triggers a rerender - platform::LockGuard anim_guard(trigger_ctx.anim.anim_lock); + platform::LockGuard anim_guard(animation_ctx.thread_context.anim_lock); BONGOCAT_LOG_INFO("Dimensions changed (%dx%d -> %dx%d), recreating buffer...", old_width, old_height, - ctx.wayland_context._local_copy_config->screen_width, - ctx.wayland_context._local_copy_config->overlay_height); + ctx.thread_context._local_copy_config->screen_width, + ctx.thread_context._local_copy_config->overlay_height); // Mark as not configured first - assert(ctx.wayland_context.ctx_shm); - atomic_store(&ctx.wayland_context.ctx_shm->configured, false); + assert(ctx.thread_context.ctx_shm); + atomic_store(&ctx.thread_context.ctx_shm->configured, false); if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_ERROR("Failed to update width for bar"); return; } - ctx.wayland_context._bar_height = config.overlay_height; + ctx.thread_context._bar_height = config.overlay_height; - if (ctx.wayland_context._screen_width > 0 && ctx.wayland_context._bar_height > 0) { + if (ctx.thread_context._screen_width > 0 && ctx.thread_context._bar_height > 0) { // Cleanup old buffer - cleanup_wayland_context_buffer(ctx.wayland_context); + cleanup_wayland_context_buffer(ctx.thread_context); // Cleanup old surface - cleanup_wayland_context_surface(ctx.wayland_context); + cleanup_wayland_context_surface(ctx.thread_context); if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_ERROR("Failed to update width for bar"); return; } - ctx.wayland_context._bar_height = config.overlay_height; + ctx.thread_context._bar_height = config.overlay_height; // Recreate surface and buffer with new dimensions if (details::wayland_setup_surface(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_ERROR("Failed to recreate surface after config change"); return; } - if (details::wayland_setup_buffer(ctx.wayland_context, trigger_ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (details::wayland_setup_buffer(ctx.thread_context, animation_ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { BONGOCAT_LOG_ERROR("Failed to recreate buffer after config change"); return; } - atomic_store(&ctx.wayland_context.ctx_shm->configured, true); + atomic_store(&ctx.thread_context.ctx_shm->configured, true); // Wait for new configure event - wl_display_roundtrip(ctx.wayland_context.display); + wl_display_roundtrip(ctx.thread_context.display); - BONGOCAT_LOG_INFO("Buffer recreated successfully (%dx%d)", ctx.wayland_context._local_copy_config->screen_width, - ctx.wayland_context._local_copy_config->overlay_height); + BONGOCAT_LOG_INFO("Buffer recreated successfully (%dx%d)", ctx.thread_context._local_copy_config->screen_width, + ctx.thread_context._local_copy_config->overlay_height); } else { - BONGOCAT_LOG_ERROR("Buffer recreated failed (%dx%d)", ctx.wayland_context._local_copy_config->screen_width, - ctx.wayland_context._local_copy_config->overlay_height); + BONGOCAT_LOG_ERROR("Buffer recreated failed (%dx%d)", ctx.thread_context._local_copy_config->screen_width, + ctx.thread_context._local_copy_config->overlay_height); } } /// @NOTE: assume animation has the same local copy as wayland config // animation_update_config(anim, config); - if (atomic_load(&ctx.wayland_context.ctx_shm->configured)) { - request_render(trigger_ctx); + if (atomic_load(&ctx.thread_context.ctx_shm->configured)) { + request_render(animation_ctx); } } @@ -510,13 +510,13 @@ const char *get_current_layer_name() { return WAYLAND_LAYER_NAME; } -bongocat_error_t request_render(animation::animation_session_t& trigger_ctx) { - if (trigger_ctx.render_efd._fd < 0) { +bongocat_error_t request_render(animation::animation_context_t& animation_ctx) { + if (animation_ctx.render_efd._fd < 0) { return bongocat_error_t::BONGOCAT_ERROR_INVALID_PARAM; } constexpr uint64_t u = 1; - const ssize_t s = write(trigger_ctx.render_efd._fd, &u, sizeof(u)); + const ssize_t s = write(animation_ctx.render_efd._fd, &u, sizeof(u)); if (s != sizeof(u)) { BONGOCAT_LOG_WARNING("Failed to write render eventfd: %s", strerror(errno)); return bongocat_error_t::BONGOCAT_ERROR_FILE_IO; diff --git a/src/platform/wayland_callbacks.cpp b/src/platform/wayland_callbacks.cpp index 3ca2a586..1091e9f2 100644 --- a/src/platform/wayland_callbacks.cpp +++ b/src/platform/wayland_callbacks.cpp @@ -2,9 +2,9 @@ #include "../graphics/bar.h" #include "graphics/animation.h" -#include "platform/global_wayland_session.h" #include "platform/wayland-protocols.hpp" #include "platform/wayland.h" +#include "platform/wayland_context.h" #include "platform/wayland_setups.h" #include "platform/wayland_shared_memory.h" #include "utils/memory.h" @@ -55,7 +55,7 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out /// Reconnection handling if (oref->wayland != BONGOCAT_NULLPTR) { - wayland_context_t& wayland_ctx = oref->wayland->wayland_context; + wayland_thread_context& wayland_ctx = oref->wayland->thread_context; // animation_context_t& anim = *ctx.animation_context; // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; @@ -64,7 +64,7 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out // const config::config_t& current_config = *wayland_ctx._local_copy_config; // Check if this is the output we're waiting for (reconnection case) - if (!atomic_load(&oref->wayland->output_lost)) { + if (!atomic_load(&oref->wayland->_output_lost)) { return; } @@ -88,7 +88,7 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out // Set new output wayland_ctx.output = oref->wl_output; wayland_ctx.bound_output_name = oref->name; - atomic_store(&oref->wayland->output_lost, false); + atomic_store(&oref->wayland->_output_lost, false); // Recreate surface on new output // Note: wayland_setup_surface already commits, triggering a configure @@ -103,8 +103,8 @@ void handle_xdg_output_name(void *data, [[maybe_unused]] zxdg_output_v1 *xdg_out // Wait for configure event to be processed wl_display_roundtrip(wayland_ctx.display); } - if (oref->wayland->animation_trigger_context != BONGOCAT_NULLPTR) { - request_render(*oref->wayland->animation_trigger_context); + if (oref->wayland->animation_context != BONGOCAT_NULLPTR) { + request_render(*oref->wayland->animation_context); } BONGOCAT_LOG_INFO("Surface recreated, configure event processed"); } else { @@ -166,12 +166,12 @@ void handle_xdg_output_description(void *data, [[maybe_unused]] zxdg_output_v1 * // FULLSCREEN DETECTION IMPLEMENTATION // ============================================================================= -static bool fs_update_state(wayland_session_t& ctx, bool new_state) { +static bool fs_update_state(wayland_context_t& ctx, bool new_state) { if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping update"); return false; } - if (ctx.animation_trigger_context == BONGOCAT_NULLPTR) { + if (ctx.animation_context == BONGOCAT_NULLPTR) { BONGOCAT_LOG_VERBOSE("Wayland not configured yet"); return false; } @@ -180,13 +180,12 @@ static bool fs_update_state(wayland_session_t& ctx, bool new_state) { if (new_state != ctx.fs_detector.has_fullscreen_toplevel) { ctx.fs_detector.has_fullscreen_toplevel = new_state; - ctx.wayland_context._fullscreen_detected = new_state; + ctx.thread_context._fullscreen_detected = new_state; - BONGOCAT_LOG_INFO("Fullscreen state changed: %s", - ctx.wayland_context._fullscreen_detected ? "detected" : "cleared"); + BONGOCAT_LOG_INFO("Fullscreen state changed: %s", ctx.thread_context._fullscreen_detected ? "detected" : "cleared"); - if (ctx.wayland_context.ctx_shm && atomic_load(&ctx.wayland_context.ctx_shm->configured)) { - request_render(*ctx.animation_trigger_context); + if (ctx.thread_context.ctx_shm && atomic_load(&ctx.thread_context.ctx_shm->configured)) { + request_render(*ctx.animation_context); } else { BONGOCAT_LOG_VERBOSE("Wayland not configured yet, skipping request rendering"); } @@ -198,12 +197,12 @@ static bool fs_update_state(wayland_session_t& ctx, bool new_state) { } namespace hyprland { - static int fs_update_state(wayland_session_t& ctx) { + static int fs_update_state(wayland_context_t& ctx) { if (wayland::hyprland::window_info_t win; wayland::hyprland::get_active_window(win)) { bool fullscreen_on_same_output = false; for (size_t i = 0; i < ctx.output_count; i++) { if (ctx.outputs[i].hypr_id == win.monitor_id) { - if (ctx.wayland_context.output == ctx.outputs[i].wl_output) { + if (ctx.thread_context.output == ctx.outputs[i].wl_output) { fullscreen_on_same_output = true; break; } @@ -239,7 +238,7 @@ static bool fs_check_compositor_fallback() { return false; } -static bool fs_check_status(wayland_session_t& ctx) { +static bool fs_check_status(wayland_context_t& ctx) { if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); return false; @@ -252,7 +251,7 @@ static bool fs_check_status(wayland_session_t& ctx) { return fs_check_compositor_fallback(); } -void fs_update_state_fallback(wayland_session_t& ctx) { +void fs_update_state_fallback(wayland_context_t& ctx) { for (size_t i = 0; i < ctx.num_toplevels; ++i) { const tracked_toplevel_t& tracked = ctx.tracked_toplevels[i]; // Skip handles that are not mapped or destroyed @@ -261,7 +260,7 @@ void fs_update_state_fallback(wayland_session_t& ctx) { } if (tracked.is_fullscreen) { // Only update overlay if on our output - if (tracked.output == ctx.wayland_context.output) { + if (tracked.output == ctx.thread_context.output) { fs_update_state(ctx, true); return; } @@ -269,7 +268,7 @@ void fs_update_state_fallback(wayland_session_t& ctx) { } const bool new_state = fs_check_status(ctx); - if (new_state != ctx.wayland_context._fullscreen_detected) { + if (new_state != ctx.thread_context._fullscreen_detected) { fs_update_state(ctx, new_state); } } @@ -279,13 +278,13 @@ struct update_fullscreen_state_toplevel_result_t { bool changed{false}; }; static update_fullscreen_state_toplevel_result_t -update_fullscreen_state_toplevel(wayland_session_t& ctx, tracked_toplevel_t& tracked, bool is_fullscreen) { +update_fullscreen_state_toplevel(wayland_context_t& ctx, tracked_toplevel_t& tracked, bool is_fullscreen) { bool state_changed = tracked.is_fullscreen != is_fullscreen; tracked.is_fullscreen = is_fullscreen; /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert // Only trigger overlay update if this fullscreen window is on our output - if (tracked.output == ctx.wayland_context.output && state_changed) { + if (tracked.output == ctx.thread_context.output && state_changed) { state_changed = fs_update_state(ctx, is_fullscreen); BONGOCAT_LOG_VERBOSE("Fullscreen state updated for window %p: %d", static_cast(tracked.handle), is_fullscreen); @@ -301,7 +300,7 @@ void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_toplevel BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); return; @@ -348,21 +347,19 @@ void fs_handle_toplevel_closed(void *data, zwlr_foreign_toplevel_handle_v1 *hand BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); return; } - if (handle != BONGOCAT_NULLPTR) { - zwlr_foreign_toplevel_handle_v1_destroy(handle); - handle = BONGOCAT_NULLPTR; - } - // remove from tracked_toplevels if present for (size_t i = 0; i < ctx.num_toplevels; ++i) { if (ctx.tracked_toplevels[i].handle == handle) { - ctx.tracked_toplevels[i].handle = BONGOCAT_NULLPTR; + if (ctx.tracked_toplevels[i].handle != BONGOCAT_NULLPTR) { + zwlr_foreign_toplevel_handle_v1_destroy(ctx.tracked_toplevels[i].handle); + ctx.tracked_toplevels[i].handle = BONGOCAT_NULLPTR; + } // compact array to keep contiguous for (size_t j = i; j + 1 < ctx.num_toplevels; ++j) { ctx.tracked_toplevels[j] = ctx.tracked_toplevels[j + 1]; @@ -405,7 +402,7 @@ void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); for (size_t i = 0; i < ctx.num_toplevels; i++) { auto& tracked = ctx.tracked_toplevels[i]; @@ -413,7 +410,7 @@ void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_enter: update tracked_toplevels[%i] output", i); tracked.output = output; if (tracked.is_fullscreen) { - if (tracked.output == ctx.wayland_context.output) { + if (tracked.output == ctx.thread_context.output) { fs_update_state(ctx, true); } } @@ -430,13 +427,13 @@ void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); for (size_t i = 0; i < ctx.num_toplevels; i++) { auto& tracked = ctx.tracked_toplevels[i]; if (tracked.handle == handle && tracked.output == output) { BONGOCAT_LOG_VERBOSE("fs_toplevel_listener.output_leave: update tracked_toplevels[%i] output", i); - if (tracked.is_fullscreen && tracked.output == ctx.wayland_context.output) { + if (tracked.is_fullscreen && tracked.output == ctx.thread_context.output) { fs_update_state(ctx, false); } tracked.output = BONGOCAT_NULLPTR; @@ -474,7 +471,7 @@ void fs_handle_manager_toplevel(void *data, [[maybe_unused]] zwlr_foreign_toplev BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); BONGOCAT_LOG_VERBOSE("fs_toplevel_manager_listener.toplevel: toplevel received"); @@ -505,7 +502,7 @@ void fs_handle_manager_finished(void *data, zwlr_foreign_toplevel_manager_v1 *ma BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); return; @@ -560,17 +557,17 @@ void layer_surface_configure(void *data, zwlr_layer_surface_v1 *ls, uint32_t ser BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); - assert(ctx.animation_trigger_context != BONGOCAT_NULLPTR); - assert(ctx.wayland_context.ctx_shm); - wayland_shared_memory_t& wayland_ctx_shm = *ctx.wayland_context.ctx_shm; + assert(ctx.animation_context != BONGOCAT_NULLPTR); + assert(ctx.thread_context.ctx_shm); + wayland_shared_memory_t& wayland_ctx_shm = *ctx.thread_context.ctx_shm; zwlr_layer_surface_v1_ack_configure(ls, serial); atomic_store(&wayland_ctx_shm.configured, true); if (atomic_load(&ctx.ready)) { // trigger initial rendering - request_render(*ctx.animation_trigger_context); + request_render(*ctx.animation_context); } BONGOCAT_LOG_DEBUG("layer_surface.configure: Layer surface configured: %dx%d", w, h); @@ -580,7 +577,7 @@ void layer_surface_closed(void *data, [[maybe_unused]] zwlr_layer_surface_v1 *ls BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - const wayland_session_t& ctx = *static_cast(data); + const wayland_context_t& ctx = *static_cast(data); if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_VERBOSE("Wayland configured yet, skipping handling"); return; @@ -591,7 +588,7 @@ void layer_surface_closed(void *data, [[maybe_unused]] zwlr_layer_surface_v1 *ls void xdg_wm_base_ping(void *data, xdg_wm_base *wm_base, uint32_t serial) { assert(data); - [[maybe_unused]] const wayland_session_t& ctx = *static_cast(data); + [[maybe_unused]] const wayland_context_t& ctx = *static_cast(data); BONGOCAT_LOG_VERBOSE("xdg_wm_base.ping: base pong %x", serial); xdg_wm_base_pong(wm_base, serial); @@ -605,7 +602,7 @@ void output_geometry(void *data, [[maybe_unused]] wl_output *wl_output, [[maybe_ BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); for (size_t i = 0; i < MAX_OUTPUTS; i++) { if (ctx.screen_infos[i].wl_output == wl_output) { @@ -625,7 +622,7 @@ void output_mode(void *data, [[maybe_unused]] wl_output *wl_output, uint32_t fla BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); BONGOCAT_LOG_VERBOSE("wl_output.mode: mode received: %u", flags); @@ -649,7 +646,7 @@ void output_done(void *data, [[maybe_unused]] wl_output *wl_output) { BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); for (size_t i = 0; i < MAX_OUTPUTS; i++) { if (ctx.screen_infos[i].wl_output == wl_output) { @@ -716,18 +713,18 @@ void frame_done(void *data, wl_callback *cb, [[maybe_unused]] uint32_t time) { BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); return; } - auto& ctx = *static_cast(data); + auto& ctx = *static_cast(data); if (!atomic_load(&ctx.ready)) { BONGOCAT_LOG_WARNING("Wayland configured yet, skipping handling"); return; } - if (!ctx.animation_trigger_context) { + if (!ctx.animation_context) { BONGOCAT_LOG_WARNING("Wayland configured yet, skipping handling"); return; } - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = *ctx->animation_context; // animation::animation_session_t& trigger_ctx = *ctx.animation_trigger_context; // wayland_shared_memory_t& wayland_ctx_shm = wayland_ctx->ctx_shm; @@ -777,27 +774,26 @@ void registry_global(void *data, wl_registry *reg, uint32_t name, const char *if BONGOCAT_LOG_VERBOSE("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); + wayland_context_t& ctx = *static_cast(data); BONGOCAT_LOG_VERBOSE("wl_registry.global: registry received: %s", iface); if (strcmp(iface, wl_compositor_interface.name) == 0) { - ctx.wayland_context.compositor = + ctx.thread_context.compositor = static_cast(wl_registry_bind(reg, name, &wl_compositor_interface, 4)); BONGOCAT_LOG_VERBOSE("wl_registry.global: compositor registry bind"); } else if (strcmp(iface, wl_shm_interface.name) == 0) { - ctx.wayland_context.shm = static_cast(wl_registry_bind(reg, name, &wl_shm_interface, 1)); + ctx.thread_context.shm = static_cast(wl_registry_bind(reg, name, &wl_shm_interface, 1)); BONGOCAT_LOG_VERBOSE("wl_registry.global: shm registry bind"); } else if (strcmp(iface, zwlr_layer_shell_v1_interface.name) == 0) { - ctx.wayland_context.layer_shell = + ctx.thread_context.layer_shell = static_cast(wl_registry_bind(reg, name, &zwlr_layer_shell_v1_interface, 1)); BONGOCAT_LOG_VERBOSE("wl_registry.global: layer_shell registry bind"); } else if (strcmp(iface, xdg_wm_base_interface.name) == 0) { - ctx.wayland_context.xdg_wm_base = - static_cast(wl_registry_bind(reg, name, &xdg_wm_base_interface, 1)); + ctx.thread_context.xdg_wm_base = static_cast(wl_registry_bind(reg, name, &xdg_wm_base_interface, 1)); BONGOCAT_LOG_VERBOSE("wl_registry.global: xdg_wm_base registry bind"); - if (ctx.wayland_context.xdg_wm_base != BONGOCAT_NULLPTR) { - xdg_wm_base_add_listener(ctx.wayland_context.xdg_wm_base, &xdg_wm_base_listener, &ctx); + if (ctx.thread_context.xdg_wm_base != BONGOCAT_NULLPTR) { + xdg_wm_base_add_listener(ctx.thread_context.xdg_wm_base, &xdg_wm_base_listener, &ctx); } } else if (strcmp(iface, zxdg_output_manager_v1_interface.name) == 0) { ctx.xdg_output_manager = @@ -813,7 +809,7 @@ void registry_global(void *data, wl_registry *reg, uint32_t name, const char *if // If we lost our output, get xdg_output to check if this is the one // reconnecting - if (atomic_load(&ctx.output_lost) && ctx.xdg_output_manager != BONGOCAT_NULLPTR) { + if (atomic_load(&ctx._output_lost) && ctx.xdg_output_manager != BONGOCAT_NULLPTR) { ctx.outputs[ctx.output_count].xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx.xdg_output_manager, ctx.outputs[ctx.output_count].wl_output); ctx.outputs[ctx.output_count].received = flag_remove( @@ -843,8 +839,8 @@ void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe BONGOCAT_LOG_WARNING("Handler called with null data (ignored)"); return; } - wayland_session_t& ctx = *static_cast(data); - platform::wayland::wayland_context_t& wayland_ctx = ctx.wayland_context; + wayland_context_t& ctx = *static_cast(data); + platform::wayland::wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = ctx.animation_trigger_context->anim; // animation_trigger_context_t *trigger_ctx = ctx.animation_trigger_context; @@ -858,9 +854,9 @@ void registry_remove(void *data, [[maybe_unused]] wl_registry *registry, [[maybe BONGOCAT_LOG_VERBOSE("wl_registry.global_remove: registry received"); // Check if the removed global is our bound output - if (name == ctx.wayland_context.bound_output_name && ctx.wayland_context.bound_output_name != 0) { + if (name == ctx.thread_context.bound_output_name && ctx.thread_context.bound_output_name != 0) { BONGOCAT_LOG_VERBOSE("Bound output disconnected (registry name %u)", name); - atomic_store(&ctx.output_lost, true); + atomic_store(&ctx._output_lost, true); atomic_store(&wayland_ctx_shm.configured, false); // Clean up the old output reference @@ -895,7 +891,7 @@ void wayland_handle_output_reconnect(output_ref_t *oref, struct wl_output *new_o [[maybe_unused]] const char *output_name) { assert(oref->wayland); - wayland_context_t& wayland_ctx = oref->wayland->wayland_context; + wayland_thread_context& wayland_ctx = oref->wayland->thread_context; // animation_context_t& anim = *ctx.animation_context; // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; @@ -911,7 +907,7 @@ void wayland_handle_output_reconnect(output_ref_t *oref, struct wl_output *new_o // Set new output wayland_ctx.output = new_output; wayland_ctx.bound_output_name = registry_name; - atomic_store(&oref->wayland->output_lost, false); + atomic_store(&oref->wayland->_output_lost, false); // Recreate surface on new output if (wayland_setup_surface(*oref->wayland) == bongocat_error_t::BONGOCAT_SUCCESS) { @@ -923,8 +919,8 @@ void wayland_handle_output_reconnect(output_ref_t *oref, struct wl_output *new_o if constexpr (WAYLAND_NUM_BUFFERS != 1) { wl_display_roundtrip(wayland_ctx.display); } - if (oref->wayland->animation_trigger_context != BONGOCAT_NULLPTR) { - request_render(*oref->wayland->animation_trigger_context); + if (oref->wayland->animation_context != BONGOCAT_NULLPTR) { + request_render(*oref->wayland->animation_context); } } else { BONGOCAT_LOG_ERROR("Failed to recreate surface on reconnected output"); diff --git a/src/platform/wayland_hyprland.cpp b/src/platform/wayland_hyprland.cpp index 98ebbf8d..996496dd 100644 --- a/src/platform/wayland_hyprland.cpp +++ b/src/platform/wayland_hyprland.cpp @@ -42,7 +42,7 @@ int fs_check_compositor_fallback() { return -1; } -void update_outputs_with_monitor_ids(wayland_session_t& ctx) { +void update_outputs_with_monitor_ids(wayland_context_t& ctx) { FILE *fp = popen("hyprctl monitors 2>/dev/null", "r"); if (fp == BONGOCAT_NULLPTR) { return; diff --git a/src/platform/wayland_hyprland.h b/src/platform/wayland_hyprland.h index 90fc7126..7667dbdc 100644 --- a/src/platform/wayland_hyprland.h +++ b/src/platform/wayland_hyprland.h @@ -1,6 +1,6 @@ #pragma once -#include "platform/global_wayland_session.h" +#include "platform/wayland_context.h" namespace bongocat::platform::wayland::hyprland { @@ -17,7 +17,7 @@ struct window_info_t { extern int fs_check_compositor_fallback(); -extern void update_outputs_with_monitor_ids(wayland_session_t& ctx); +extern void update_outputs_with_monitor_ids(wayland_context_t& ctx); extern bool get_active_window(window_info_t& win); } // namespace bongocat::platform::wayland::hyprland diff --git a/src/platform/wayland_setups.cpp b/src/platform/wayland_setups.cpp index 846cd64c..0d258e8f 100644 --- a/src/platform/wayland_setups.cpp +++ b/src/platform/wayland_setups.cpp @@ -2,9 +2,9 @@ #include "../graphics/bar.h" #include "graphics/animation.h" -#include "platform/global_wayland_session.h" #include "platform/wayland-protocols.hpp" #include "platform/wayland.h" +#include "platform/wayland_context.h" #include "platform/wayland_shared_memory.h" #include "utils/memory.h" #include "wayland_hyprland.h" @@ -80,8 +80,8 @@ FileDescriptor create_shm(off_t size) { // MAIN WAYLAND INTERFACE IMPLEMENTATION // ============================================================================= -bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { - wayland_context_t& wayland_ctx = ctx.wayland_context; +bongocat_error_t wayland_update_screen_width(wayland_context_t& ctx) { + wayland_thread_context& wayland_ctx = ctx.thread_context; // read-only config assert(wayland_ctx._local_copy_config); @@ -158,8 +158,8 @@ bongocat_error_t wayland_update_screen_width(wayland_session_t& ctx) { return bongocat_error_t::BONGOCAT_SUCCESS; } -bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { - wayland_context_t& wayland_ctx = ctx.wayland_context; +bongocat_error_t wayland_setup_protocols(wayland_context_t& ctx) { + wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = *ctx.animation_context; // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; @@ -224,8 +224,8 @@ bongocat_error_t wayland_setup_protocols(wayland_session_t& ctx) { return bongocat_error_t::BONGOCAT_SUCCESS; } -bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { - wayland_context_t& wayland_ctx = ctx.wayland_context; +bongocat_error_t wayland_setup_surface(wayland_context_t& ctx) { + wayland_thread_context& wayland_ctx = ctx.thread_context; // animation_context_t& anim = *ctx.animation_context; // animation_trigger_context_t& trigger_ctx = *ctx.animation_trigger_context; @@ -305,7 +305,8 @@ bongocat_error_t wayland_setup_surface(wayland_session_t& ctx) { return bongocat_error_t::BONGOCAT_SUCCESS; } -bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animation::animation_session_t& anim) { +bongocat_error_t wayland_setup_buffer(wayland_thread_context& wayland_context, + animation::animation_context_t& animation_ctx) { // read-only config assert(wayland_context._local_copy_config); // const config::config_t& current_config = *wayland_context._local_copy_config; @@ -387,8 +388,8 @@ bongocat_error_t wayland_setup_buffer(wayland_context_t& wayland_context, animat wayland_ctx_shm.buffers[i].index = i; atomic_store(&wayland_ctx_shm.buffers[i].busy, false); atomic_store(&wayland_ctx_shm.buffers[i].pending, false); - wayland_ctx_shm.buffers[i]._animation_trigger_context = &anim; - wayland_ctx_shm.buffers[i]._wayland_context = &wayland_context; + wayland_ctx_shm.buffers[i]._animation_context = &animation_ctx; + wayland_ctx_shm.buffers[i]._wayland_thread_context = &wayland_context; wl_buffer_add_listener(wayland_ctx_shm.buffers[i].buffer, &details::buffer_listener, &wayland_ctx_shm.buffers[i]); } diff --git a/src/platform/wayland_sway.h b/src/platform/wayland_sway.h index 4b0d5bf9..3169d37b 100644 --- a/src/platform/wayland_sway.h +++ b/src/platform/wayland_sway.h @@ -1,6 +1,6 @@ #pragma once -#include "platform/global_wayland_session.h" +#include "platform/wayland_context.h" namespace bongocat::platform::wayland::sway { From 7b1bebdefe4a892f8a8455be4ad1fd232722d10a Mon Sep 17 00:00:00 2001 From: furudbat Date: Tue, 9 Dec 2025 23:53:32 +0100 Subject: [PATCH 16/18] fix: make build --- Makefile | 5 +++-- Makefile.old | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 11fd5f84..c08c9681 100644 --- a/Makefile +++ b/Makefile @@ -51,9 +51,10 @@ pack: release doc doc: release cmake --build build --target manpages +SOURCES = $(shell find $(SRCDIR) -name '*.cpp' ! -path '*/embedded_assets/**/*.c??' ! -path '*/image_loader/*.c' ! -path '*/image_loader/**/*.cpp') # Static analysis analyze: - clang-tidy $(SOURCES) -- $(CFLAGS) + clang-tidy -p build $(SOURCES) # Memory check (requires valgrind) memcheck: debug @@ -84,7 +85,7 @@ format-check: # Static analysis with clang-tidy (uses .clang-tidy config) lint: protocols @echo "Running static analysis..." - @clang-tidy $(PROJECT_SOURCES) -- $(CFLAGS) $(CXXFLAGS) 2>/dev/null || true + @clang-tidy -p build $(PROJECT_SOURCES) 2>/dev/null || true @echo "Static analysis complete." # Alias for lint diff --git a/Makefile.old b/Makefile.old index 85a2478e..464226f0 100644 --- a/Makefile.old +++ b/Makefile.old @@ -207,6 +207,7 @@ uninstall: rm -f $(DESTDIR)/usr/local/bin/bongocat-find-devices rm -rf $(DESTDIR)/usr/local/share/bongocat +SOURCES = $(CXX_SRC) $(C_SRC) # Static analysis analyze: clang-tidy $(SOURCES) -- $(CFLAGS) $(CXXFLAGS) From bce5efa0afd23b4c1b3ac3881f6d9de7cb426850 Mon Sep 17 00:00:00 2001 From: furudbat Date: Wed, 10 Dec 2025 12:30:06 +0100 Subject: [PATCH 17/18] fix: anti-aliasing --- CHANGELOG.md | 1 + README.md | 78 ++++++------ include/graphics/drawing.h | 4 + scripts/test_bongocat_8.sh | 20 ++- src/config/config.cpp | 8 +- src/graphics/bar.cpp | 40 ++++-- src/graphics/drawing_images.cpp | 214 ++++++++++++++++++++++++++++---- 7 files changed, 285 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f9e920..66f0283a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. - pull from upstream [1.3.1](https://github.com/saatvik333/wayland-bongocat/releases/tag/v1.3.1) - **Keyboard Hand Mapping** - Left half of keyboard triggers left cat hand, right half triggers right hand - New config option `enable_hand_mapping=1` (enabled by default) + - Box filter + alpha blending --- diff --git a/README.md b/README.md index a4671918..2f398bc7 100644 --- a/README.md +++ b/README.md @@ -129,45 +129,45 @@ keyboard_device=/dev/input/event4
Click to expand all options -| **Option** | **Values** | **Default** | **Description** | -|--------------------------|--------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------| -| `cat_height` | 10–200 | 40 | Height of bongo cat in pixels (width auto-calculated to maintain aspect ratio) | -| `cat_x_offset` | -16000 to 16000 | 100 | Horizontal offset from center (behavior depends on `cat_align`) | -| `cat_y_offset` | -16000 to 16000 | 10 | Vertical offset from center (positive=down, negative=up) | -| `cat_align` | "center", "left", "right" | "center" | Horizontal alignment of cat inside overlay bar | -| `overlay_height` | 20–300 | 50 | Height of the entire overlay bar | -| `overlay_position` | "top" or "bottom" | "top" | Position of overlay on screen | -| `overlay_opacity` | 0–255 | 60 | Background opacity (0=transparent, 255=opaque) | -| `overlay_layer` | "overlay", "top", "bottom" or "background" | "overlay" | Surface layer of overlay on screen | -| `animation_name` | "bongocat", ``, "clippy", `` or "neko" | "bongocat" | Name of the V-Pet sprite (see list below) | -| `invert_color` | 0 or 1 | 0 | Invert color (useful for white digimon sprites & dark mode) | -| `idle_frame` | 0–2 (varies by sprite type) | 0 | Which frame to use when idle (sprite-specific options) | -| `idle_animation` | 0 or 1 | 0 | Enable idle animation | -| `animation_speed` | 0–5000 | 0 | Frame duration in ms (0 = use `fps`) | -| `keypress_duration` | 50–5000 | 100 | Duration to display keypress animation (ms) | -| `mirror_x` | 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | -| `mirror_y` | 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | -| `test_animation_duration` | 0–5000 | 0 | Duration of test animation (ms) (deprecated, use `animation_speed`) | -| `test_animation_interval` | 0–60 | 0 | Interval for test animation in seconds (0 = disabled, deprecated) | -| `fps` | 1–144 | 60 | Animation frame rate | -| `input_fps` | 0–144 | 0 | Input thread frame rate (0 = use `fps`) | -| `enable_scheduled_sleep` | 0 or 1 | 0 | Enable scheduled sleep mode | -| `sleep_begin` | "00:00" – "23:59" | "00:00" | Start time of scheduled sleep (24h format) | -| `sleep_end` | "00:00" – "23:59" | "00:00" | End time of scheduled sleep (24h format) | -| `idle_sleep_timeout` | 0+ | 0 | Time of inactivity before entering sleep (0 = disabled) (in seconds) | -| `happy_kpm` | 0–10000 | 0 | Minimum keystrokes per minute to trigger happy animation (0 = disabled) | -| `keyboard_device` | Valid `/dev/input/*` path(s) | \ | Input device path (multiple entries allowed) | -| `enable_antialiasing` | 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling (Bongocat and MS Agent only) | -| `enable_debug` | 0 or 1 | 0 | Enable debug logging | -| `monitor` | Monitor name | Auto-detect | Which monitor to display on (e.g., "eDP-1", "HDMI-A-1") | -| `random` | 0 or 1 | 0 | Randomize `animation_index` (`animation_name` needs to be set as base sprite sheet set) | -| `random_on_reload` | 0 or 1 | 0 | Randomize `animation_index` when reloading config (`random` needs to be `1`) | -| `update_rate` | 0–10000 | 0 | Check (CPU) states rate (0 = disabled) (in milliseconds) | -| `cpu_threshold` | 0–100 | 0 | Threshold of CPU usage for triggering work animation (0 = disabled) | -| `movement_radius` | 0-8000 | 0 | Radius of moving area (0 = disabled) | -| `movement_speed` | 0–5000 | 0 | Movement speed (0 = disabled) | -| `enable_movement_debug` | 0 or 1 | 0 | Show Movement area | -| `cpu_running_factor` | 0.0–50.0 | 0 | Speed up factor for 100% CPU, it's linear so the animation slowly speed up (0 = disabled) | +| **Option** | **Values** | **Default** | **Description** | +|---------------------------|--------------------------------------------------------------------|---------------|-------------------------------------------------------------------------------------------| +| `cat_height` | 8–1024 | 40 | Height of bongo cat in pixels (width auto-calculated to maintain aspect ratio) | +| `cat_x_offset` | -16000 to 16000 | 100 | Horizontal offset from center (behavior depends on `cat_align`) | +| `cat_y_offset` | -16000 to 16000 | 10 | Vertical offset from center (positive=down, negative=up) | +| `cat_align` | "center", "left", "right" | "center" | Horizontal alignment of cat inside overlay bar | +| `overlay_height` | 16–2560 | 50 | Height of the entire overlay bar | +| `overlay_position` | "top" or "bottom" | "top" | Position of overlay on screen | +| `overlay_opacity` | 0–255 | 60 | Background opacity (0=transparent, 255=opaque) | +| `overlay_layer` | "overlay", "top", "bottom" or "background" | "overlay" | Surface layer of overlay on screen | +| `animation_name` | "bongocat", ``, "clippy", `` or "neko" | "bongocat" | Name of the V-Pet sprite (see list below) | +| `invert_color` | 0 or 1 | 0 | Invert color (useful for white digimon sprites & dark mode) | +| `idle_frame` | 0–2 (varies by sprite type) | 0 | Which frame to use when idle (sprite-specific options) | +| `idle_animation` | 0 or 1 | 0 | Enable idle animation | +| `animation_speed` | 0–5000 | 0 | Frame duration in ms (0 = use `fps`) | +| `keypress_duration` | 50–5000 | 100 | Duration to display keypress animation (ms) | +| `mirror_x` | 0 or 1 | 0 | Flip cat horizontally (mirror across Y axis) | +| `mirror_y` | 0 or 1 | 0 | Flip cat vertically (mirror across X axis) | +| `test_animation_duration` | 0–5000 | 0 | Duration of test animation (ms) (deprecated, use `animation_speed`) | +| `test_animation_interval` | 0–60 | 0 | Interval for test animation in seconds (0 = disabled, deprecated) | +| `fps` | 1–144 | 60 | Animation frame rate | +| `input_fps` | 0–144 | 0 | Input thread frame rate (0 = use `fps`) | +| `enable_scheduled_sleep` | 0 or 1 | 0 | Enable scheduled sleep mode | +| `sleep_begin` | "00:00" – "23:59" | "00:00" | Start time of scheduled sleep (24h format) | +| `sleep_end` | "00:00" – "23:59" | "00:00" | End time of scheduled sleep (24h format) | +| `idle_sleep_timeout` | 0+ | 0 | Time of inactivity before entering sleep (0 = disabled) (in seconds) | +| `happy_kpm` | 0–10000 | 0 | Minimum keystrokes per minute to trigger happy animation (0 = disabled) | +| `keyboard_device` | Valid `/dev/input/*` path(s) | \ | Input device path (multiple entries allowed) | +| `enable_antialiasing` | 0 or 1 | 1 | Enable bilinear interpolation for smooth scaling (Bongocat and MS Agent only) | +| `enable_debug` | 0 or 1 | 0 | Enable debug logging | +| `monitor` | Monitor name | Auto-detect | Which monitor to display on (e.g., "eDP-1", "HDMI-A-1") | +| `random` | 0 or 1 | 0 | Randomize `animation_index` (`animation_name` needs to be set as base sprite sheet set) | +| `random_on_reload` | 0 or 1 | 0 | Randomize `animation_index` when reloading config (`random` needs to be `1`) | +| `update_rate` | 0–10000 | 0 | Check (CPU) states rate (0 = disabled) (in milliseconds) | +| `cpu_threshold` | 0–100 | 0 | Threshold of CPU usage for triggering work animation (0 = disabled) | +| `movement_radius` | 0-8000 | 0 | Radius of moving area (0 = disabled) | +| `movement_speed` | 0–5000 | 0 | Movement speed (0 = disabled) | +| `enable_movement_debug` | 0 or 1 | 0 | Show Movement area | +| `cpu_running_factor` | 0.0–50.0 | 0 | Speed up factor for 100% CPU, it's linear so the animation slowly speed up (0 = disabled) | #### Available Sprites (`animation_name`) diff --git a/include/graphics/drawing.h b/include/graphics/drawing.h index cc293603..20ee42ec 100644 --- a/include/graphics/drawing.h +++ b/include/graphics/drawing.h @@ -12,6 +12,7 @@ enum class blit_image_color_option_flags_t : uint32_t { MirrorX = (1u << 3), MirrorY = (1u << 4), BilinearInterpolation = (1u << 5), + DisableThresholdAlpha = (1u << 6), }; enum class blit_image_color_order_t : uint8_t { RGBA, @@ -25,6 +26,9 @@ enum class blit_image_color_order_t : uint8_t { void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const unsigned char *src, int src_channels, int src_idx, blit_image_color_option_flags_t option, blit_image_color_order_t dest_order, blit_image_color_order_t src_order); +void drawing_blend_pixel(uint8_t *dest, int dest_channels, int dest_idx, uint8_t src_r, uint8_t src_g, uint8_t src_b, + uint8_t src_a, int src_channels, blit_image_color_option_flags_t options, + blit_image_color_order_t dest_order, blit_image_color_order_t src_order); // Blit scaled image to destination buffer void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int dest_channels, diff --git a/scripts/test_bongocat_8.sh b/scripts/test_bongocat_8.sh index 72a250ef..6b68425c 100755 --- a/scripts/test_bongocat_8.sh +++ b/scripts/test_bongocat_8.sh @@ -15,6 +15,7 @@ cp $OG_CONFIG $CONFIG sed -i -E 's/^cat_height=[0-9]+/cat_height=96/' "$CONFIG" sed -i -E 's/^overlay_height=[0-9]+/overlay_height=128/' "$CONFIG" +sed -i -E 's/^enable_antialiasing=[0-9]+/enable_antialiasing=1/' "$CONFIG" if [[ $# -ge 1 ]]; then PID="$1" @@ -24,7 +25,7 @@ if [[ $# -ge 1 ]]; then echo "[TEST] Using provided PID = $PID" else echo "[TEST] Starting program..." - "$PROGRAM" --config "$CONFIG" --ignore-running --strict & + "$PROGRAM" --config "$CONFIG" --ignore-running & PID=$! echo "[TEST] Program PID = $PID" sleep 5 @@ -57,6 +58,7 @@ sleep 7 echo "[TEST] Change overlay settings" echo "[INFO] Set overlay_height" +sed -i -E 's/^cat_height=[0-9]+/cat_height=96/' "$CONFIG" sed -i -E 's/^overlay_height=[0-9]+/overlay_height=100/' "$CONFIG" echo "[INFO] Send SIGUSR2" kill -USR2 "$PID" # Reload config @@ -87,6 +89,19 @@ echo "[INFO] Send SIGUSR2" kill -USR2 "$PID" # Reload config sleep 10 +echo "[INFO] Set max size" +sed -i -E 's/^cat_height=[0-9]+/cat_height=1024/' "$CONFIG" +sed -i -E 's/^overlay_height=[0-9]+/overlay_height=2560/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 15 +echo "[INFO] Set min size" +sed -i -E 's/^cat_height=[0-9]+/cat_height=16/' "$CONFIG" +sed -i -E 's/^overlay_height=[0-9]+/overlay_height=32/' "$CONFIG" +echo "[INFO] Send SIGUSR2" +kill -USR2 "$PID" # Reload config +sleep 15 + sleep 20 # --- verify running --- @@ -97,6 +112,9 @@ else exit 1 fi +echo "[INFO] Set overlay_height" +sed -i -E 's/^cat_height=[0-9]+/cat_height=128/' "$CONFIG" +sed -i -E 's/^overlay_height=[0-9]+/overlay_height=256/' "$CONFIG" echo "[TEST] Set Monitor" echo "[INFO] Set monitor" sed -i -E 's/^monitor=.*/monitor=HDMI-A-1/' "$CONFIG" diff --git a/src/config/config.cpp b/src/config/config.cpp index c60af081..278167a1 100644 --- a/src/config/config.cpp +++ b/src/config/config.cpp @@ -53,10 +53,10 @@ // ============================================================================= namespace bongocat::config { -static inline constexpr int MIN_CAT_HEIGHT = 10; -static inline constexpr int MAX_CAT_HEIGHT = 200; -static inline constexpr int MIN_OVERLAY_HEIGHT = 20; -static inline constexpr int MAX_OVERLAY_HEIGHT = 300; +static inline constexpr int MIN_CAT_HEIGHT = 8; +static inline constexpr int MAX_CAT_HEIGHT = 1024; +static inline constexpr int MIN_OVERLAY_HEIGHT = 16; +static inline constexpr int MAX_OVERLAY_HEIGHT = 2560; static inline constexpr int MIN_FPS = 1; static inline constexpr int MAX_FPS = 144; static inline constexpr int MIN_DURATION_MS = 10; diff --git a/src/graphics/bar.cpp b/src/graphics/bar.cpp index 2d0df259..015b985c 100644 --- a/src/graphics/bar.cpp +++ b/src/graphics/bar.cpp @@ -494,6 +494,7 @@ enum class draw_sprite_overwrite_option_t : uint32_t { None = 0, MovementNoMirror = (1 << 0), MovementMirror = (1 << 1), + DisableThresholdAlpha = (1 << 2), }; void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::wayland_shm_buffer_t& shm_buffer, const custom_sprite_sheet_t& sheet, int col, int row, @@ -595,6 +596,9 @@ void draw_sprite(platform::wayland::wayland_context_t& ctx, platform::wayland::w drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::MirrorX); } break; + case draw_sprite_overwrite_option_t::DisableThresholdAlpha: + drawing_option = flag_add(drawing_option, blit_image_color_option_flags_t::DisableThresholdAlpha); + break; } blit_image_scaled(pixels, pixels_size, wayland_ctx._screen_width, wayland_ctx._bar_height, BGRA_CHANNELS, @@ -746,20 +750,21 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_context_t& ctx, const animation_t& custom_anim = get_current_animation(anim); assert(custom_anim.type == animation_t::type_t::Custom); const custom_sprite_sheet_t& sheet = custom_anim.custom; - draw_sprite_overwrite_option_t overwrite_mirror_x{draw_sprite_overwrite_option_t::None}; + draw_sprite_overwrite_option_t overwrite_drawing_options{draw_sprite_overwrite_option_t::None}; + // Mirroring for pmd sprite sheets not needed /* switch (anim_shm.animation_player_result.overwrite_mirror_x) { case animation_player_custom_overwrite_mirror_x::None: break; case animation_player_custom_overwrite_mirror_x::NoMirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementNoMirror; - break; - case animation_player_custom_overwrite_mirror_x::Mirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementMirror; - break; + overwrite_drawing_options = + flag_add(overwrite_drawing_options,draw_sprite_overwrite_option_t::MovementNoMirror); break; case + animation_player_custom_overwrite_mirror_x::Mirror: overwrite_drawing_options = + flag_add(overwrite_drawing_options,draw_sprite_overwrite_option_t::MovementMirror); break; } */ - draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_mirror_x); + + draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_drawing_options); } break; case config::config_animation_custom_set_t::custom: @@ -768,18 +773,24 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_context_t& ctx, const animation_t& custom_anim = get_current_animation(anim); assert(custom_anim.type == animation_t::type_t::Custom); const custom_sprite_sheet_t& sheet = custom_anim.custom; - draw_sprite_overwrite_option_t overwrite_mirror_x{draw_sprite_overwrite_option_t::None}; + + draw_sprite_overwrite_option_t overwrite_drawing_options{draw_sprite_overwrite_option_t::None}; switch (anim_shm.animation_player_result.overwrite_mirror_x) { case animation_player_custom_overwrite_mirror_x::None: break; case animation_player_custom_overwrite_mirror_x::NoMirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementNoMirror; + overwrite_drawing_options = + flag_add(overwrite_drawing_options, draw_sprite_overwrite_option_t::MovementNoMirror); break; case animation_player_custom_overwrite_mirror_x::Mirror: - overwrite_mirror_x = draw_sprite_overwrite_option_t::MovementMirror; + overwrite_drawing_options = + flag_add(overwrite_drawing_options, draw_sprite_overwrite_option_t::MovementMirror); break; } - draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_mirror_x); + overwrite_drawing_options = + flag_add(overwrite_drawing_options, draw_sprite_overwrite_option_t::DisableThresholdAlpha); + + draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_drawing_options); } break; } @@ -850,6 +861,9 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_context_t& ctx) { return draw_bar_result_t::Skip; } + // for calc render time + [[maybe_unused]] const auto t0 = platform::get_current_time_us(); + BONGOCAT_LOG_VERBOSE("draw_bar: using buffer %zu", next_buffer_index); if constexpr (platform::wayland::WAYLAND_NUM_BUFFERS > 1) { wayland_ctx_shm.current_buffer_index = next_buffer_index; @@ -903,6 +917,10 @@ draw_bar_result_t draw_bar(platform::wayland::wayland_context_t& ctx) { wayland_ctx._last_frame_timestamp_ms = now; } + [[maybe_unused]] const auto t1 = platform::get_current_time_us(); + BONGOCAT_LOG_VERBOSE("draw_bar: time %.3fms (%.6fsec)", static_cast(t1 - t0) / 1000.0, + static_cast(t1 - t0) / 1000000.0); + return draw_bar_result_t::NoFlushNeeded; } } // namespace bongocat::animation diff --git a/src/graphics/drawing_images.cpp b/src/graphics/drawing_images.cpp index c8eb6b34..f435854b 100644 --- a/src/graphics/drawing_images.cpp +++ b/src/graphics/drawing_images.cpp @@ -48,6 +48,7 @@ static void drawing_copy_pixel_rgba(uint8_t *dest, int dest_channels, int dest_i dest[dest_idx + 3] = a; } } + void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const unsigned char *src, int src_channels, int src_idx, blit_image_color_option_flags_t options, blit_image_color_order_t dest_order, blit_image_color_order_t src_order) { @@ -87,16 +88,78 @@ void drawing_copy_pixel(uint8_t *dest, int dest_channels, int dest_idx, const un drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, r, g, b, a); } -struct drawing_get_interpolated_pixel_result_t { +void drawing_blend_pixel(uint8_t *dest, int dest_channels, int dest_idx, uint8_t src_r, uint8_t src_g, uint8_t src_b, + uint8_t src_a, int src_channels, blit_image_color_option_flags_t options, + blit_image_color_order_t dest_order, blit_image_color_order_t src_order) { + if (has_flag(options, blit_image_color_option_flags_t::Invisible)) { + return; + } + const bool invert = has_flag(options, blit_image_color_option_flags_t::Invert); + + // Map source channel indices for RGB + const uint8_t sr = (src_order == blit_image_color_order_t::RGBA) ? src_r : src_b; + const uint8_t sg = src_g; + const uint8_t sb = (src_order == blit_image_color_order_t::RGBA) ? src_b : src_r; + const uint8_t sa = src_a; + + // Skip fully transparent pixels + if (sa == 0) { + return; + } + + // Load into RGBA without branches + uint8_t r{0}; + uint8_t g{0}; + uint8_t b{0}; + uint8_t a{0}; + if (src_channels == 1) { + // 1-channel grayscale -> fill all channels with 0/255 + uint8_t v = sr > 0 ? 255 : 0; + v = apply_invert(v, invert); + r = g = b = a = v; + } else if (src_channels == 2) { + // 2-channel grayscale + alpha (alpha ignored in original) + const uint8_t gray = apply_invert(sr, invert); + r = g = b = gray; + a = 255; + } else { + // Fully opaque - direct copy (fast path) + if (src_a == 255) { + // RGB / RGBA + r = apply_invert(sr, invert); + g = apply_invert(sg, invert); + b = apply_invert(sb, invert); + a = 255; // Alpha not inverted + } else { + // Alpha blend: out = src * alpha + dest * (1 - alpha) + const float alpha = static_cast(sa) / 255.0f; + const float inv_alpha = 1.0f - alpha; + + // Map source channel indices for RGB + const uint8_t dr = (dest_order == blit_image_color_order_t::RGBA) ? dest[dest_idx + 0] : dest[dest_idx + 2]; + const uint8_t dg = dest[dest_idx + 1]; + const uint8_t db = (dest_order == blit_image_color_order_t::RGBA) ? dest[dest_idx + 2] : dest[dest_idx + 0]; + // const uint8_t da = dest[dest_idx + 3]; + + r = static_cast(lroundf((sr * alpha) + (dr * inv_alpha))); + g = static_cast(lroundf((sg * alpha) + (dg * inv_alpha))); + b = static_cast(lroundf((sb * alpha) + (db * inv_alpha))); + a = 255; // Alpha not inverted + } + } + + drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, r, g, b, a); +} + +struct drawing_get_pixel_result_t { unsigned char r{0}; unsigned char g{0}; unsigned char b{0}; unsigned char a{0}; }; // Bilinear interpolation for smooth scaling -static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(const unsigned char *src, size_t src_size, - int src_w, int src_h, int src_channels, - float fx, float fy) { +static drawing_get_pixel_result_t drawing_get_interpolated_pixel(const unsigned char *src, size_t src_size, int src_w, + int src_h, int src_channels, float fx, float fy) { // Clamp coordinates to image bounds if (fx < 0) { fx = 0; @@ -134,7 +197,7 @@ static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(co const int idx_br = (y2 * src_w + x2) * src_channels; // bottom-right // Interpolate each channel - drawing_get_interpolated_pixel_result_t ret; + drawing_get_pixel_result_t ret; for (int c = 0; c < src_channels; c++) { assert(idx_tl >= 0); assert(idx_tr >= 0); @@ -146,22 +209,22 @@ static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(co const size_t br_idx_c = static_cast(idx_br) + static_cast(c); if (tl_idx_c < src_size && tr_idx_c < src_size && bl_idx_c < src_size && br_idx_c < src_size) { - const float top = static_cast(src[tl_idx_c]) * (1.0f - dx) + (static_cast(src[tr_idx_c]) * dx); - const float bottom = static_cast(src[bl_idx_c]) * (1.0f - dx) + (static_cast(src[br_idx_c]) * dx); - const float result = top * (1.0f - dy) + bottom * dy; + const float top = (static_cast(src[tl_idx_c]) * (1.0f - dx)) + (static_cast(src[tr_idx_c]) * dx); + const float bottom = (static_cast(src[bl_idx_c]) * (1.0f - dx)) + (static_cast(src[br_idx_c]) * dx); + const float result = (top * (1.0f - dy)) + (bottom * dy); switch (c) { case 0: - ret.r = static_cast(result + 0.5f); + ret.r = static_cast(lroundf(result)); break; // R case 1: - ret.g = static_cast(result + 0.5f); + ret.g = static_cast(lroundf(result)); break; // G case 2: - ret.b = static_cast(result + 0.5f); + ret.b = static_cast(lroundf(result)); break; // B case 3: - ret.a = static_cast(result + 0.5f); + ret.a = static_cast(lroundf(result)); break; // A default: break; @@ -171,6 +234,102 @@ static drawing_get_interpolated_pixel_result_t drawing_get_interpolated_pixel(co return ret; } +// Box filter for high-quality downscaling - averages all source pixels that +// map to a destination pixel. Produces much smoother results than bilinear +// when shrinking images significantly. +static drawing_get_pixel_result_t drawing_get_box_filtered_pixel(const unsigned char *src, size_t src_size, int src_w, + int src_h, int src_channels, float fx, float fy) { + // Clamp coordinates to image bounds + if (fx < 0) { + fx = 0; + } + if (fy < 0) { + fy = 0; + } + if (fx >= static_cast(src_w - 1)) { + fx = static_cast(src_w - 1); + } + if (fy >= static_cast(src_h - 1)) { + fy = static_cast(src_h - 1); + } + + const int x1 = static_cast(fx); + const int y1 = static_cast(fy); + int x2 = x1 + 1; + int y2 = y1 + 1; + + // Clamp to bounds + if (x2 >= src_w) { + x2 = src_w - 1; + } + if (y2 >= src_h) { + y2 = src_h - 1; + } + + // Indices of 2×2 block + const int idx_tl = (y1 * src_w + x1) * src_channels; + const int idx_tr = (y1 * src_w + x2) * src_channels; + const int idx_bl = (y2 * src_w + x1) * src_channels; + const int idx_br = (y2 * src_w + x2) * src_channels; + + float sum_r = 0.0f; + float sum_g = 0.0f; + float sum_b = 0.0f; + float sum_a = 0.0f; + int count = 0; + + // Accumulate function (manual inline) + const int base_indices[4] = {idx_tl, idx_tr, idx_bl, idx_br}; + + assert(src_channels >= 0); + for (int i = 0; i < src_channels; i++) { + const int base = base_indices[i]; + if (base < 0) { + continue; + } + + for (int c = 0; c < src_channels; c++) { + const size_t idx_c = static_cast(base) + static_cast(c); + if (idx_c >= src_size) { + continue; + } + + const float v = src[idx_c]; + switch (c) { + case 0: + sum_r += v; + break; + case 1: + sum_g += v; + break; + case 2: + sum_b += v; + break; + case 3: + sum_a += v; + break; + } + } + count++; + } + + if (count == 0) { + count = 1; + } + + drawing_get_pixel_result_t ret; + // Average + ret.r = static_cast(lroundf(sum_r / static_cast(count))); + ret.g = static_cast(lroundf(sum_g / static_cast(count))); + ret.b = static_cast(lroundf(sum_b / static_cast(count))); + if (src_channels == 4) { + ret.a = static_cast(lroundf(sum_a / static_cast(count))); + } else { + ret.a = 255; + } + return ret; +} + void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int dest_channels, const unsigned char *src, size_t src_size, int src_w, int src_h, int src_channels, int src_x, int src_y, int frame_w, int frame_h, int offset_x, int offset_y, int target_w, int target_h, @@ -248,12 +407,18 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, int32_t src_x_start = src_x << FIXED_SHIFT; int32_t src_y_start = src_y << FIXED_SHIFT; + const bool use_bilinear_interpolation = has_flag(options, blit_image_color_option_flags_t::BilinearInterpolation); + const bool mirror_x = has_flag(options, blit_image_color_option_flags_t::MirrorX); + const bool mirror_y = has_flag(options, blit_image_color_option_flags_t::MirrorY); + const bool disable_threshold_alpha = + use_bilinear_interpolation || has_flag(options, blit_image_color_option_flags_t::DisableThresholdAlpha); + // MirrorX / MirrorY affect direction and start point - if (has_flag(options, blit_image_color_option_flags_t::MirrorX)) { + if (mirror_x) { src_x_start = (src_x + frame_w - 1) << FIXED_SHIFT; inc_x = -inc_x; } - if (has_flag(options, blit_image_color_option_flags_t::MirrorY)) { + if (mirror_y) { src_y_start = (src_y + frame_h - 1) << FIXED_SHIFT; inc_y = -inc_y; } @@ -261,7 +426,7 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, const size_t src_row_bytes = static_cast(src_w) * static_cast(src_channels); const size_t dest_row_bytes = static_cast(dest_w) * static_cast(dest_channels); - const bool use_bilinear_interpolation = has_flag(options, blit_image_color_option_flags_t::BilinearInterpolation); + const bool is_downscaling = (target_w < src_w) || (target_h < src_h); for (int ty = y0; ty < y1; ++ty) { const int dy = offset_y + ty; @@ -278,9 +443,11 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, const unsigned char *src_row = src + (static_cast(sy) * src_row_bytes); int32_t sx_fixed = src_x_start + static_cast(static_cast(x0) * inc_x); - const uint8_t *dest_ptr = dest_row + static_cast(offset_x + x0) * static_cast(dest_channels); + const uint8_t *dest_ptr = dest_row + (static_cast(offset_x + x0) * static_cast(dest_channels)); for (int tx = x0; tx < x1; ++tx) { + // const int dx = offset_x + tx; + // assert(dx < dest_w); const int sx = sx_fixed >> FIXED_SHIFT; if (static_cast(sx) < static_cast(src_w)) { @@ -293,18 +460,15 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, const float fx = static_cast(sx_fixed) / static_cast(1 << FIXED_SHIFT); const float fy = static_cast(sy_fixed) / static_cast(1 << FIXED_SHIFT); - auto pixel = drawing_get_interpolated_pixel(src, src_size, src_w, src_h, src_channels, fx, fy); - if (src_channels >= 4) { - if (src_pixel[3] > THRESHOLD_ALPHA) { - drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, pixel.r, pixel.g, pixel.b, pixel.a); - } - } else { - drawing_copy_pixel_rgba(dest, dest_channels, dest_idx, dest_order, pixel.r, pixel.g, pixel.b, 255); - } + const drawing_get_pixel_result_t pixel = + (is_downscaling) ? drawing_get_box_filtered_pixel(src, src_size, src_w, src_h, src_channels, fx, fy) + : drawing_get_interpolated_pixel(src, src_size, src_w, src_h, src_channels, fx, fy); + drawing_blend_pixel(dest, dest_channels, dest_idx, pixel.r, pixel.g, pixel.b, pixel.a, 4, options, dest_order, + blit_image_color_order_t::RGBA); } else { // Use nearest-neighbor scaling (original behavior) if (src_channels >= 4) { - if (src_pixel[3] > THRESHOLD_ALPHA) { + if (disable_threshold_alpha || src_pixel[3] > THRESHOLD_ALPHA) { drawing_copy_pixel(dest, dest_channels, dest_idx, src, src_channels, src_idx, options, dest_order, src_order); } From 38470cd77dc054d64dd5cf33135bdadeaeaa13fb Mon Sep 17 00:00:00 2001 From: furudbat Date: Wed, 10 Dec 2025 13:23:08 +0100 Subject: [PATCH 18/18] fix: monitor switching on config reload --- include/platform/wayland_setups.h | 2 +- src/platform/wayland.cpp | 36 +++++++++++++++++++++++++++---- src/platform/wayland_setups.cpp | 4 ++-- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/include/platform/wayland_setups.h b/include/platform/wayland_setups.h index b64e7be3..dbd4ffcd 100644 --- a/include/platform/wayland_setups.h +++ b/include/platform/wayland_setups.h @@ -11,7 +11,7 @@ namespace bongocat::platform::wayland::details { BONGOCAT_NODISCARD FileDescriptor create_shm(off_t size); BONGOCAT_NODISCARD bongocat_error_t wayland_setup_protocols(wayland_context_t& ctx); -BONGOCAT_NODISCARD bongocat_error_t wayland_update_screen_width(wayland_context_t& ctx); +BONGOCAT_NODISCARD bongocat_error_t wayland_update_screen_info(wayland_context_t& ctx); BONGOCAT_NODISCARD bongocat_error_t wayland_setup_surface(wayland_context_t& ctx); BONGOCAT_NODISCARD bongocat_error_t wayland_setup_buffer(wayland_thread_context& wayland_context, animation::animation_context_t& animation_ctx); diff --git a/src/platform/wayland.cpp b/src/platform/wayland.cpp index 5de338b9..9c9a3282 100644 --- a/src/platform/wayland.cpp +++ b/src/platform/wayland.cpp @@ -433,6 +433,9 @@ void update_config(wayland_context_t& ctx, const config::config_t& config, const auto old_height = ctx.thread_context._local_copy_config ? ctx.thread_context._local_copy_config->overlay_height : 0; const auto old_width = ctx.thread_context._screen_width; + char *old_screen_name = ctx.thread_context._output_name_str != BONGOCAT_NULLPTR + ? strdup(ctx.thread_context._output_name_str) + : BONGOCAT_NULLPTR; // update old config *ctx.thread_context._local_copy_config = config; @@ -440,13 +443,17 @@ void update_config(wayland_context_t& ctx, const config::config_t& config, const bool dimensions_changed = (old_height != ctx.thread_context._local_copy_config->overlay_height) || (ctx.thread_context._local_copy_config->screen_width > 0 && old_width != ctx.thread_context._local_copy_config->screen_width); + const bool change_screen = + ctx.thread_context._local_copy_config->output_name != BONGOCAT_NULLPTR && + (strcmp(old_screen_name, ctx.thread_context._local_copy_config->output_name) != 0 || + strcmp(ctx.thread_context._output_name_str, ctx.thread_context._local_copy_config->output_name) != 0); - if (dimensions_changed && old_height > 0 && old_width > 0 && ctx.thread_context.ctx_shm) { + if (((dimensions_changed && old_height > 0 && old_width > 0) || change_screen) && ctx.thread_context.ctx_shm) { // ~~Lock animation mutex to prevent draw_bar() during config update // This is critical - animation thread must not access buffer while we // recreate it~~ // not sure if this is needed, animation thread don't touch the bar directly, - // it just triggers a rerender + // it just triggers a rerender event platform::LockGuard anim_guard(animation_ctx.thread_context.anim_lock); BONGOCAT_LOG_INFO("Dimensions changed (%dx%d -> %dx%d), recreating buffer...", old_width, old_height, @@ -457,7 +464,11 @@ void update_config(wayland_context_t& ctx, const config::config_t& config, assert(ctx.thread_context.ctx_shm); atomic_store(&ctx.thread_context.ctx_shm->configured, false); - if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (details::wayland_update_screen_info(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (old_screen_name != BONGOCAT_NULLPTR) { + ::free(old_screen_name); + old_screen_name = BONGOCAT_NULLPTR; + } BONGOCAT_LOG_ERROR("Failed to update width for bar"); return; } @@ -470,7 +481,11 @@ void update_config(wayland_context_t& ctx, const config::config_t& config, // Cleanup old surface cleanup_wayland_context_surface(ctx.thread_context); - if (details::wayland_update_screen_width(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (details::wayland_update_screen_info(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (old_screen_name != BONGOCAT_NULLPTR) { + ::free(old_screen_name); + old_screen_name = BONGOCAT_NULLPTR; + } BONGOCAT_LOG_ERROR("Failed to update width for bar"); return; } @@ -478,10 +493,18 @@ void update_config(wayland_context_t& ctx, const config::config_t& config, // Recreate surface and buffer with new dimensions if (details::wayland_setup_surface(ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (old_screen_name != BONGOCAT_NULLPTR) { + ::free(old_screen_name); + old_screen_name = BONGOCAT_NULLPTR; + } BONGOCAT_LOG_ERROR("Failed to recreate surface after config change"); return; } if (details::wayland_setup_buffer(ctx.thread_context, animation_ctx) != bongocat_error_t::BONGOCAT_SUCCESS) { + if (old_screen_name != BONGOCAT_NULLPTR) { + ::free(old_screen_name); + old_screen_name = BONGOCAT_NULLPTR; + } BONGOCAT_LOG_ERROR("Failed to recreate buffer after config change"); return; } @@ -499,6 +522,11 @@ void update_config(wayland_context_t& ctx, const config::config_t& config, } } + if (old_screen_name != nullptr) { + ::free(old_screen_name); + old_screen_name = nullptr; + } + /// @NOTE: assume animation has the same local copy as wayland config // animation_update_config(anim, config); if (atomic_load(&ctx.thread_context.ctx_shm->configured)) { diff --git a/src/platform/wayland_setups.cpp b/src/platform/wayland_setups.cpp index 0d258e8f..99db2f20 100644 --- a/src/platform/wayland_setups.cpp +++ b/src/platform/wayland_setups.cpp @@ -80,7 +80,7 @@ FileDescriptor create_shm(off_t size) { // MAIN WAYLAND INTERFACE IMPLEMENTATION // ============================================================================= -bongocat_error_t wayland_update_screen_width(wayland_context_t& ctx) { +bongocat_error_t wayland_update_screen_info(wayland_context_t& ctx) { wayland_thread_context& wayland_ctx = ctx.thread_context; // read-only config @@ -204,7 +204,7 @@ bongocat_error_t wayland_setup_protocols(wayland_context_t& ctx) { return bongocat_error_t::BONGOCAT_ERROR_WAYLAND; } - const bongocat_error_t result_update_screen_width = wayland_update_screen_width(ctx); + const bongocat_error_t result_update_screen_width = wayland_update_screen_info(ctx); if (result_update_screen_width != bongocat_error_t::BONGOCAT_SUCCESS) { wl_registry_destroy(registry); return result_update_screen_width;