From 90d13670e70a41f9788b9434b05b6f63d5545d7c Mon Sep 17 00:00:00 2001 From: furudbat Date: Sun, 24 Aug 2025 17:48:21 +0200 Subject: [PATCH 1/6] wip: toplevel fullscreen detection (#38) # Conflicts: # include/core/bongocat.h --- include/core/bongocat.h | 3 +- src/platform/wayland.c | 92 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 3 deletions(-) diff --git a/include/core/bongocat.h b/include/core/bongocat.h index 26a5cb55..cf3bde23 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -29,7 +29,8 @@ #define NUM_FRAMES 3 #define DEFAULT_SCREEN_WIDTH 1920 #define DEFAULT_BAR_HEIGHT 40 -#define MAX_OUTPUTS 8 // Maximum monitor outputs to store +#define MAX_OUTPUTS 8 // Maximum monitor outputs to store +#define MAX_TOPLEVELS 512 #define CAT_IMAGE_WIDTH 864 #define CAT_IMAGE_HEIGHT 360 diff --git a/src/platform/wayland.c b/src/platform/wayland.c index 98092f0d..121f91a7 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -85,8 +85,17 @@ typedef struct { struct timeval last_check; } fullscreen_detector_t; +typedef struct { + struct zwlr_foreign_toplevel_handle_v1 *handle; + struct wl_output *output; + bool is_fullscreen; +} tracked_toplevel_t; + static fullscreen_detector_t fs_detector = {0}; +static tracked_toplevel_t track_toplevels[MAX_TOPLEVELS] = {0}; +static size_t track_toplevels_count = 0; + // ============================================================================= // FULLSCREEN DETECTION IMPLEMENTATION // ============================================================================= @@ -159,6 +168,19 @@ static bool fs_check_status(void) { return fs_check_compositor_fallback(); } +static bool update_fullscreen_state_toplevel(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 == output && state_changed) { + fs_update_state(is_fullscreen); + return true; + } + + return false; +} // Foreign toplevel protocol event handlers static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state) { @@ -173,6 +195,16 @@ static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_ha break; } } + + /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert + for (size_t i = 0; i < track_toplevels_count; i++) { + if (track_toplevels[i].handle == handle) { + bool output_found = update_fullscreen_state_toplevel(&track_toplevels[i], is_fullscreen); + if (output_found) { + return; + } + } + } fs_update_state(is_fullscreen); } @@ -180,6 +212,21 @@ static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_ha static void fs_handle_toplevel_closed(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { (void)data; zwlr_foreign_toplevel_handle_v1_destroy(handle); + + // remove from track_toplevels if present + for (size_t i = 0; i < track_toplevels_count; ++i) { + if (track_toplevels[i].handle == handle) { + track_toplevels[i].handle = NULL; + track_toplevels[i].output = NULL; + track_toplevels[i].is_fullscreen = false; + // compact array to keep contiguous + for (size_t j = i; j + 1 < track_toplevels_count; ++j) { + track_toplevels[j] = track_toplevels[j+1]; + } + track_toplevels_count--; + break; + } + } } // Minimal event handlers for unused events @@ -192,11 +239,33 @@ static void fs_handle_app_id(void *data, struct zwlr_foreign_toplevel_handle_v1 } 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; + (void)data; + + for (size_t i = 0; i < track_toplevels_count; i++) { + if (track_toplevels[i].handle == handle) { + track_toplevels[i].output = output; + if (track_toplevels[i].is_fullscreen) { + if (track_toplevels[i].output == output) { + fs_update_state(true); + } + } + break; + } + } } 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; + (void)data; + + for (size_t i = 0; i < track_toplevels_count; i++) { + if (track_toplevels[i].handle == handle && track_toplevels[i].output == output) { + if (track_toplevels[i].is_fullscreen && track_toplevels[i].output == output) { + fs_update_state(false); + } + track_toplevels[i].output = NULL; + break; + } + } } static void fs_handle_done(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle) { @@ -223,6 +292,25 @@ static void fs_handle_manager_toplevel(void *data, struct zwlr_foreign_toplevel_ (void)data; (void)manager; zwlr_foreign_toplevel_handle_v1_add_listener(toplevel, &fs_toplevel_listener, NULL); + + if (track_toplevels_count < MAX_TOPLEVELS) { + bool already_tracked = false; + for (size_t i = 0; i < track_toplevels_count; i++) { + if (track_toplevels[i].handle == toplevel) { + already_tracked = true; + break; + } + } + if (!already_tracked) { + track_toplevels[track_toplevels_count].handle = toplevel; + track_toplevels[track_toplevels_count].output = NULL; + track_toplevels[track_toplevels_count].is_fullscreen = false; + track_toplevels_count++; + } + } else { + bongocat_log_error("toplevel tracker is full, %zu max: %d", track_toplevels_count, MAX_TOPLEVELS); + } + bongocat_log_debug("New toplevel registered for fullscreen monitoring"); } From 0ed8f6bfcb2687b193addf6b55ad3bc05d92157a Mon Sep 17 00:00:00 2001 From: furudbat Date: Sun, 24 Aug 2025 17:41:28 +0200 Subject: [PATCH 2/6] feat(hypr): toplevel fullscreen detection (#38) --- include/core/bongocat.h | 7 +- src/platform/wayland.c | 160 +++++++++++++++++++++++++++++++++------- 2 files changed, 139 insertions(+), 28 deletions(-) diff --git a/include/core/bongocat.h b/include/core/bongocat.h index cf3bde23..022506db 100644 --- a/include/core/bongocat.h +++ b/include/core/bongocat.h @@ -52,9 +52,12 @@ typedef struct { 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 + uint32_t name; // Registry name + char name_str[128]; // From xdg-output bool name_received; + int x, y; + int width, height; + int hypr_id; // monitor ID in Hyprland } output_ref_t; // Config watcher function declarations diff --git a/src/platform/wayland.c b/src/platform/wayland.c index 121f91a7..967a5f49 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -52,15 +52,31 @@ static struct zxdg_output_manager_v1 *xdg_output_manager = NULL; 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); } static void handle_xdg_output_logical_position(void *data, struct zxdg_output_v1 *xdg_output, - int32_t x, int32_t y) {} + int32_t x, int32_t y) { + output_ref_t *oref = data; + + oref->x = x; + oref->y = y; + + bongocat_log_debug("xdg-output logical position received: %d,%d", x, y); +} static void handle_xdg_output_logical_size(void *data, struct zxdg_output_v1 *xdg_output, - int32_t width, int32_t height) {} +int32_t width, int32_t height) { + output_ref_t *oref = data; + + oref->width = width; + oref->height = height; + + bongocat_log_debug("xdg-output logical size received: %dx%d", width, height); +} 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) { @@ -96,6 +112,13 @@ static fullscreen_detector_t fs_detector = {0}; static tracked_toplevel_t track_toplevels[MAX_TOPLEVELS] = {0}; static size_t track_toplevels_count = 0; +typedef struct { + int monitor_id; // monitor number in Hyprland + int x, y; + int width, height; + bool fullscreen; +} window_info_t; + // ============================================================================= // FULLSCREEN DETECTION IMPLEMENTATION // ============================================================================= @@ -114,34 +137,84 @@ static void fs_update_state(bool new_state) { } } -static bool fs_check_compositor_fallback(void) { - bongocat_log_debug("Using compositor-specific fullscreen detection"); - - // Try Hyprland first +static void hypr_update_outputs_with_monitor_ids(void) { + FILE *fp = popen("hyprctl monitors 2>/dev/null", "r"); + if (!fp) return; + + char line[512]; + while (fgets(line, sizeof(line), fp)) { + int id = -1; + char name[256]; + 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 < output_count; i++) { + // match by xdg-output name + if (outputs[i].name_received && strcmp(outputs[i].name_str, name) == 0) { + outputs[i].hypr_id = id; + bongocat_log_debug("Mapped xdg-output '%s' to Hyprland ID %d\n", name, id); + break; + } + } + } + } + + pclose(fp); +} + +static bool hypr_get_active_window(window_info_t *win) { 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 (!fp) return false; + + char line[512]; + bool has_window = false; + win->monitor_id = -1; + win->fullscreen = false; + + while (fgets(line, sizeof(line), 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); } - - if (strstr(line, "fullscreen: 1") || strstr(line, "fullscreen: 2") || - strstr(line, "fullscreen: true")) { - is_fullscreen = true; - bongocat_log_debug("Fullscreen detected in Hyprland"); - break; + } + // 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); } } - pclose(fp); - return is_fullscreen; + // 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; +} + +static bool fs_check_compositor_fallback(void) { + bongocat_log_debug("Using compositor-specific fullscreen detection"); + + // Try Hyprland first + window_info_t win; + if (hypr_get_active_window(&win)) { + return win.fullscreen; } // Try Sway as fallback - fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); + FILE *fp = popen("swaymsg -t get_tree 2>/dev/null", "r"); if (fp) { char sway_buffer[4096]; bool is_fullscreen = false; @@ -181,6 +254,28 @@ static bool update_fullscreen_state_toplevel(tracked_toplevel_t *tracked, bool i return false; } +static bool hypr_fs_update_state(void) { + window_info_t win; + if (hypr_get_active_window(&win)) { + bool fullscreen_on_same_output = false; + for (size_t i = 0; i < output_count; i++) { + if (outputs[i].hypr_id == win.monitor_id) { + if (output == outputs[i].wl_output) { + fullscreen_on_same_output = true; + break; + } + } + } + if (fullscreen_on_same_output) { + fs_update_state(win.fullscreen); + } else { + fs_update_state(false); + } + return true; + } + + return false; +} // Foreign toplevel protocol event handlers static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_handle_v1 *handle, struct wl_array *state) { @@ -196,7 +291,7 @@ static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_ha } } - /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggert + /// @NOTE: tracked.output can always be NULL when no output.enter/output.leave event were triggerd for (size_t i = 0; i < track_toplevels_count; i++) { if (track_toplevels[i].handle == handle) { bool output_found = update_fullscreen_state_toplevel(&track_toplevels[i], is_fullscreen); @@ -205,7 +300,13 @@ static void fs_handle_toplevel_state(void *data, struct zwlr_foreign_toplevel_ha } } } - + + // check for hyprland + if (hypr_fs_update_state()) { + return; + } + + // Fallback: global update fs_update_state(is_fullscreen); } @@ -567,11 +668,18 @@ static bongocat_error_t wayland_setup_protocols(void) { 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); + outputs[i].x = 0; + outputs[i].y = 0; + outputs[i].width = 0; + outputs[i].height = 0; + outputs[i].hypr_id = -1; zxdg_output_v1_add_listener(outputs[i].xdg_output, &xdg_output_listener, &outputs[i]); } // Wait for all xdg_output events wl_display_roundtrip(display); + + hypr_update_outputs_with_monitor_ids(); } output = NULL; @@ -582,7 +690,7 @@ static bongocat_error_t wayland_setup_protocols(void) { output = outputs[i].wl_output; bongocat_log_info("Matched output: %s", outputs[i].name_str); break; - } + } } if (!output) { From 2148e1f25e2dc320faad58d87061a574ca380b8d Mon Sep 17 00:00:00 2001 From: furudbat Date: Mon, 15 Dec 2025 22:21:26 +0100 Subject: [PATCH 3/6] Merge branch 'main' of https://github.com/saatvik333/wayland-bongocat into bugfix/issue-38-fullscreen-multi-monitor --- src/platform/wayland.c | 59 +++++++----------------------------------- 1 file changed, 10 insertions(+), 49 deletions(-) diff --git a/src/platform/wayland.c b/src/platform/wayland.c index 3cb694a3..d6bf1412 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -59,7 +59,6 @@ static bool using_named_output = static char *bound_screen_name = NULL; BONGOCAT_NODISCARD static struct wl_output *wayland_find_new_output(void) { - struct wl_output *matching_wl_output = NULL; if (current_config->output_name) { for (size_t i = 0; i < output_count; ++i) { if (outputs[i].name_received && @@ -184,8 +183,10 @@ static void handle_xdg_output_name(void *data, } } -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_position(void *data, + struct zxdg_output_v1 *xdg_output + __attribute__((unused)), + int32_t x, int32_t y) { // Defensive null check if (!data) { return; @@ -199,7 +200,7 @@ static void handle_xdg_output_logical_position( bongocat_log_debug("xdg-output logical position received: %d,%d", x, y); } static void handle_xdg_output_logical_size(void *data, - struct zxdg_output_v1 *xdg_output, + struct zxdg_output_v1 *xdg_output __attribute__((unused)), int32_t width, int32_t height) { // Defensive null check if (!data) { @@ -213,15 +214,12 @@ static void handle_xdg_output_logical_size(void *data, bongocat_log_debug("xdg-output logical size received: %dx%d", width, height); } -static void handle_xdg_output_done(void *data, - struct zxdg_output_v1 *xdg_output) {} +static void handle_xdg_output_done(void *data __attribute__((unused)), + struct zxdg_output_v1 *xdg_output __attribute__((unused))) {} -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 __attribute__((unused)), + struct zxdg_output_v1 *xdg_output __attribute__((unused)), + const char *description __attribute__((unused))) { } static const struct zxdg_output_v1_listener xdg_output_listener = { @@ -349,36 +347,6 @@ static bool hypr_get_active_window(window_info_t *win) { return has_window; } -static bool fs_check_compositor_fallback(void) { - bongocat_log_debug("Using compositor-specific fullscreen detection"); - - // Try Hyprland first - window_info_t win; - if (hypr_get_active_window(&win)) { - return win.fullscreen; - } - - // Try Sway as fallback - FILE *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; -} - // Foreign toplevel protocol event handlers // Each toplevel tracks its fullscreen and activated state typedef struct { @@ -389,13 +357,6 @@ typedef struct { // Track the currently active toplevel's fullscreen state static bool active_toplevel_fullscreen = false; -static bool fs_check_status(void) { - if (fs_detector.manager) { - return fs_detector.has_fullscreen_toplevel; - } - return fs_check_compositor_fallback(); -} - static bool update_fullscreen_state_toplevel(tracked_toplevel_t *tracked, bool is_fullscreen, bool is_activated) { From 9307bfe3f237ec7ec4aff96c2b0aae355c2f2b41 Mon Sep 17 00:00:00 2001 From: furudbat Date: Mon, 22 Dec 2025 10:06:13 +0100 Subject: [PATCH 4/6] chore: format --- src/graphics/animation.cpp | 33 ++++++++++++++---------------- src/graphics/bar.cpp | 4 +++- src/graphics/drawing_images.cpp | 4 ++-- src/platform/wayland_callbacks.cpp | 30 +++++++++++++++------------ 4 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/graphics/animation.cpp b/src/graphics/animation.cpp index 71965fa3..980f7a40 100644 --- a/src/graphics/animation.cpp +++ b/src/graphics/animation.cpp @@ -3862,8 +3862,8 @@ anim_custom_idle_next_frame(animation_thread_context_t& ctx, const platform::inp if (conditions.is_moving) { if (conditions.go_next_frame) { animation_result = anim_custom_start_or_process_animation( - ctx, animation_state_row_t::FallASleepIdle, animation_state_row_t::IdleSleep, new_animation_result, - new_state, current_state, current_frames, current_config); + ctx, animation_state_row_t::FallASleepIdle, animation_state_row_t::IdleSleep, + new_animation_result, new_state, current_state, current_frames, current_config); } } else { animation_result = anim_custom_restart_animation( @@ -4356,14 +4356,12 @@ static anim_next_frame_result_t anim_custom_working_next_frame(animation_thread_ if (animation_result.row_state != animation_state_row_t::IdleSleep) { ctx.shm->last_wakeup_timestamp = platform::get_current_time_ms(); } - BONGOCAT_LOG_VERBOSE("Start Working: %d %d; %d%%", above_threshold, lower_threshold, - update_shm.avg_cpu_usage); + BONGOCAT_LOG_VERBOSE("Start Working: %d %d; %d%%", above_threshold, lower_threshold, update_shm.avg_cpu_usage); } else if (start_above_threshold && conditions.ready_to_work) { 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); + 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 (above_threshold) { if (conditions.is_working) { if (update_shm.cpu_active && current_state.row_state == animation_state_row_t::StartWorking) { @@ -4464,21 +4462,20 @@ static anim_next_frame_result_t anim_custom_running_next_frame(animation_thread_ if (start_above_threshold && conditions.ready_to_work) { 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); + 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 (above_threshold && 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); + 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); + 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, diff --git a/src/graphics/bar.cpp b/src/graphics/bar.cpp index 407bda89..96740c24 100644 --- a/src/graphics/bar.cpp +++ b/src/graphics/bar.cpp @@ -789,7 +789,9 @@ static bool draw_bar_on_buffer(platform::wayland::wayland_context_t& ctx, } /// @TODO: add option for disablen/enablen alpha threshold overwrite_drawing_options = - current_config.enable_antialiasing >= 1 ? flag_add(overwrite_drawing_options, draw_sprite_overwrite_option_t::DisableThresholdAlpha) : flag_remove(overwrite_drawing_options, draw_sprite_overwrite_option_t::DisableThresholdAlpha); + current_config.enable_antialiasing >= 1 + ? flag_add(overwrite_drawing_options, draw_sprite_overwrite_option_t::DisableThresholdAlpha) + : flag_remove(overwrite_drawing_options, draw_sprite_overwrite_option_t::DisableThresholdAlpha); draw_sprite(ctx, shm_buffer, sheet, col, row, overwrite_drawing_options); } diff --git a/src/graphics/drawing_images.cpp b/src/graphics/drawing_images.cpp index 86a0d40b..215c7913 100644 --- a/src/graphics/drawing_images.cpp +++ b/src/graphics/drawing_images.cpp @@ -465,8 +465,8 @@ void blit_image_scaled(uint8_t *dest, size_t dest_size, int dest_w, int dest_h, 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); + 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 { diff --git a/src/platform/wayland_callbacks.cpp b/src/platform/wayland_callbacks.cpp index e5c9aec2..d284f1e6 100644 --- a/src/platform/wayland_callbacks.cpp +++ b/src/platform/wayland_callbacks.cpp @@ -211,11 +211,11 @@ namespace hyprland { } } if (find_output) { - details::fs_update_state(ctx, { .is_fullscreen = win.fullscreen, .is_activated = true }); + details::fs_update_state(ctx, {.is_fullscreen = win.fullscreen, .is_activated = true}); return win.fullscreen ? 1 : 0; } - details::fs_update_state(ctx, { .is_fullscreen = false, .is_activated = true }); + details::fs_update_state(ctx, {.is_fullscreen = false, .is_activated = true}); return 0; } @@ -263,7 +263,7 @@ void fs_update_state_fallback(wayland_context_t& ctx) { if (tracked.is_fullscreen) { // Only update overlay if on our output if (tracked.output == ctx.thread_context.output) { - fs_update_state(ctx, { .is_fullscreen = true, .is_activated = true }); + fs_update_state(ctx, {.is_fullscreen = true, .is_activated = true}); return; } } @@ -271,7 +271,7 @@ void fs_update_state_fallback(wayland_context_t& ctx) { const bool new_fullscreen = fs_check_status(ctx); if (new_fullscreen != ctx.thread_context._fullscreen_detected) { - fs_update_state(ctx, { .is_fullscreen = new_fullscreen, .is_activated = true }); + fs_update_state(ctx, {.is_fullscreen = new_fullscreen, .is_activated = true}); } } @@ -280,7 +280,8 @@ struct update_fullscreen_state_toplevel_result_t { bool changed{false}; }; static update_fullscreen_state_toplevel_result_t -update_fullscreen_state_toplevel(wayland_context_t& ctx, tracked_toplevel_t& tracked, update_fullscreen_state_toplevel_state_t state) { +update_fullscreen_state_toplevel(wayland_context_t& ctx, tracked_toplevel_t& tracked, + update_fullscreen_state_toplevel_state_t state) { bool state_changed = tracked.is_fullscreen != state.is_fullscreen || tracked.is_activated != state.is_activated; tracked.is_fullscreen = state.is_fullscreen; tracked.is_activated = state.is_activated; @@ -289,8 +290,8 @@ update_fullscreen_state_toplevel(wayland_context_t& ctx, tracked_toplevel_t& tra // Only trigger overlay update if this fullscreen window is on our output if (tracked.output == ctx.thread_context.output && state_changed) { state_changed = fs_update_state(ctx, state); - BONGOCAT_LOG_VERBOSE("Fullscreen state updated for window %p: (fullscreen=%d;activated=%d)", static_cast(tracked.handle), - state.is_fullscreen, state.is_activated); + BONGOCAT_LOG_VERBOSE("Fullscreen state updated for window %p: (fullscreen=%d;activated=%d)", + static_cast(tracked.handle), state.is_fullscreen, state.is_activated); return {.output_found = true, .changed = state_changed}; } @@ -325,10 +326,12 @@ void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_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 = is_fullscreen, .is_activated = is_activated }); + auto [output_found, changed] = update_fullscreen_state_toplevel( + ctx, ctx.tracked_toplevels[i], {.is_fullscreen = is_fullscreen, .is_activated = is_activated}); if (output_found) { if (changed) { - BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: (fullscreen=%d;activated=%d)", is_fullscreen, is_activated); + BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: (fullscreen=%d;activated=%d)", + is_fullscreen, is_activated); } return; } @@ -342,9 +345,10 @@ void fs_handle_toplevel_state(void *data, [[maybe_unused]] zwlr_foreign_toplevel } // Fallback for when no toplevel was found - const bool changed = fs_update_state(ctx, { .is_fullscreen = is_fullscreen, .is_activated = is_activated }); + const bool changed = fs_update_state(ctx, {.is_fullscreen = is_fullscreen, .is_activated = is_activated}); if (changed) { - BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: (fullscreen=%d;activated=%d)", is_fullscreen, is_activated); + BONGOCAT_LOG_VERBOSE("fs_handle_toplevel.state: Update fullscreen state: (fullscreen=%d;activated=%d)", + is_fullscreen, is_activated); } } @@ -417,7 +421,7 @@ void fs_handle_output_enter(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h tracked.output = output; if (tracked.is_fullscreen) { if (tracked.output == ctx.thread_context.output) { - fs_update_state(ctx, { .is_fullscreen = true, .is_activated = true }); + fs_update_state(ctx, {.is_fullscreen = true, .is_activated = true}); } } break; @@ -440,7 +444,7 @@ void fs_handle_output_leave(void *data, [[maybe_unused]] zwlr_foreign_toplevel_h 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.thread_context.output) { - fs_update_state(ctx, { .is_fullscreen = false, .is_activated = false }); + fs_update_state(ctx, {.is_fullscreen = false, .is_activated = false}); } tracked.output = BONGOCAT_NULLPTR; break; From 769cb731c88e369fabe5d4bd0484b43786fe1a2a Mon Sep 17 00:00:00 2001 From: furudbat Date: Wed, 7 Jan 2026 16:41:53 +0100 Subject: [PATCH 5/6] fix: hide on fullscreen (#38) --- src/platform/wayland.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/platform/wayland.c b/src/platform/wayland.c index d6bf1412..8a69f9ec 100644 --- a/src/platform/wayland.c +++ b/src/platform/wayland.c @@ -200,7 +200,8 @@ static void handle_xdg_output_logical_position(void *data, bongocat_log_debug("xdg-output logical position received: %d,%d", x, y); } static void handle_xdg_output_logical_size(void *data, - struct zxdg_output_v1 *xdg_output __attribute__((unused)), + struct zxdg_output_v1 *xdg_output + __attribute__((unused)), int32_t width, int32_t height) { // Defensive null check if (!data) { @@ -215,12 +216,14 @@ static void handle_xdg_output_logical_size(void *data, bongocat_log_debug("xdg-output logical size received: %dx%d", width, height); } static void handle_xdg_output_done(void *data __attribute__((unused)), - struct zxdg_output_v1 *xdg_output __attribute__((unused))) {} + struct zxdg_output_v1 *xdg_output + __attribute__((unused))) {} static void handle_xdg_output_description(void *data __attribute__((unused)), - struct zxdg_output_v1 *xdg_output __attribute__((unused)), - const char *description __attribute__((unused))) { -} + struct zxdg_output_v1 *xdg_output + __attribute__((unused)), + const char *description + __attribute__((unused))) {} static const struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = handle_xdg_output_logical_position, @@ -379,9 +382,11 @@ static bool hypr_fs_update_state(toplevel_data_t *toplevel_data) { window_info_t win; if (hypr_get_active_window(&win)) { bool found_output = false; + struct wl_output *found_wl_output = NULL; for (size_t i = 0; i < output_count; i++) { if (outputs[i].hypr_id == win.monitor_id) { if (output == outputs[i].wl_output) { + found_wl_output = output; found_output = true; break; } @@ -393,14 +398,12 @@ static bool hypr_fs_update_state(toplevel_data_t *toplevel_data) { toplevel_data->is_fullscreen = win.fullscreen; active_toplevel_fullscreen = win.fullscreen; - fs_update_state(win.fullscreen); - } else { - // active window is not on the same screen as bongocat - toplevel_data->is_activated = false; - toplevel_data->is_fullscreen = false; - active_toplevel_fullscreen = false; - fs_update_state(false); + if (current_screen_info && + current_screen_info->wl_output == found_wl_output) { + fs_update_state(win.fullscreen); + } } + return true; } From 13d349d55934b5b4925694c48b1882640d9c3f28 Mon Sep 17 00:00:00 2001 From: furudbat Date: Wed, 7 Jan 2026 17:45:40 +0100 Subject: [PATCH 6/6] fix: hide on fullscreen (#38) --- src/platform/wayland_callbacks.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/platform/wayland_callbacks.cpp b/src/platform/wayland_callbacks.cpp index d284f1e6..d10c86a5 100644 --- a/src/platform/wayland_callbacks.cpp +++ b/src/platform/wayland_callbacks.cpp @@ -202,20 +202,21 @@ namespace hyprland { static int fs_update_state(wayland_context_t& ctx) { if (wayland::hyprland::window_info_t win; wayland::hyprland::get_active_window(win)) { bool find_output = false; + wl_output *found_wl_output = nullptr; for (size_t i = 0; i < ctx.output_count; i++) { if (ctx.outputs[i].hypr_id == win.monitor_id) { if (ctx.thread_context.output == ctx.outputs[i].wl_output) { + found_wl_output = ctx.outputs[i].wl_output; find_output = true; break; } } } - if (find_output) { + if (find_output && ctx.thread_context.output == found_wl_output) { details::fs_update_state(ctx, {.is_fullscreen = win.fullscreen, .is_activated = true}); - return win.fullscreen ? 1 : 0; + return win.fullscreen ? 2 : 1; } - details::fs_update_state(ctx, {.is_fullscreen = false, .is_activated = true}); return 0; }