diff --git a/meson.build b/meson.build index 21469c9cff..05119f2090 100644 --- a/meson.build +++ b/meson.build @@ -1133,16 +1133,16 @@ if build_tests 'battery_hook_state', 'button_layout', 'cairo_text_renderer', - 'calendar_cache_permissions', - 'cli_help', - 'cli_parse', - 'cli_schema', 'caldav_client', + 'calendar_cache_permissions', 'calendar_credential_store', 'calendar_discovery_state', 'calendar_view_passed_event', 'capsule_group_lane_override', 'capsule_group_reconcile', + 'cli_help', + 'cli_parse', + 'cli_schema', 'clipboard_service', 'clipboard_storage_permissions', 'config_migration', @@ -1172,34 +1172,34 @@ if build_tests 'icon_resolver', 'image_file_loader_data_uri', 'image_source_log', + 'inotify', 'input_area', 'input_dispatcher', - 'input_readline_shortcuts', 'input_password_mode', - 'inotify', + 'input_readline_shortcuts', 'ipc_service', 'jxl_decoder', - 'keyboard_layout_label', 'kde_color_scheme', + 'keyboard_layout_label', 'lane_selection_token', - 'log', 'location_service', + 'log', 'math_provider', 'monitor_selector', 'network_manager_security', + 'niri_workspace_backend', 'notification_dnd', 'notification_filter', 'notification_history_dismiss', 'notification_history_utf8', - 'niri_workspace_backend', 'path_browse', 'plugin_bindings', 'plugin_catalog', 'plugin_git_export', 'plugin_i18n', 'plugin_lifecycle', - 'plugin_process', 'plugin_manifest', + 'plugin_process', 'plugin_source_locks', 'plugin_source_paths', 'process', @@ -1208,6 +1208,7 @@ if build_tests 'security_primitives', 'state_store', 'string_utils', + 'surface_blur_region', 'system_monitor_service', 'taskbar_widget', 'template_apply_notify', @@ -1220,12 +1221,12 @@ if build_tests 'upower_charge_limit', 'upower_device_catalog', 'virtual_grid_view_gesture', - 'webp_decoder', + 'wallpaper_shuffle_state', 'wayland_output_scale', 'wayland_toplevels_identity', + 'webp_decoder', 'widget_action', 'widget_definition', - 'wallpaper_shuffle_state', 'workspace_alert_service', ] diff --git a/src/wayland/surface.cpp b/src/wayland/surface.cpp index d8a8ceabbd..0e1c2d7103 100644 --- a/src/wayland/surface.cpp +++ b/src/wayland/surface.cpp @@ -676,13 +676,34 @@ bool Surface::prepareBlurEffect() { return true; } +bool Surface::regionIntersectsBounds(const std::vector& rects, std::uint32_t width, std::uint32_t height) { + if (width == 0 || height == 0) { + return false; + } + for (const auto& r : rects) { + const std::int64_t right = static_cast(r.x) + r.width; + const std::int64_t bottom = static_cast(r.y) + r.height; + if (r.width > 0 + && r.height > 0 + && right > 0 + && bottom > 0 + && static_cast(r.x) < width + && static_cast(r.y) < height) { + return true; + } + } + return false; +} + void Surface::setBlurRegion(const std::vector& rects) { if (!prepareBlurEffect()) { return; } + // Hyprland renders a fully off-surface non-empty blur region as full-surface blur, so send null instead. + const bool hasVisibleRegion = regionIntersectsBounds(rects, m_width, m_height); wl_region* region = nullptr; - if (!rects.empty()) { + if (hasVisibleRegion) { region = wl_compositor_create_region(m_connection.compositor()); if (region == nullptr) { traceSurfaceEvent(*this, "blur-set-skip-region-failed"); @@ -692,7 +713,11 @@ void Surface::setBlurRegion(const std::vector& rects) { wl_region_add(region, r.x, r.y, r.width, r.height); } } - traceBlurRegionEvent(*this, rects.empty() ? "blur-set-empty" : "blur-set", rects); + const char* traceLabel = "blur-set"; + if (!hasVisibleRegion) { + traceLabel = rects.empty() ? "blur-set-empty" : "blur-set-offsurface"; + } + traceBlurRegionEvent(*this, traceLabel, rects); ext_background_effect_surface_v1_set_blur_region(m_backgroundEffect, region); if (region != nullptr) { wl_region_destroy(region); diff --git a/src/wayland/surface.h b/src/wayland/surface.h index 2f0890b37d..a959d94735 100644 --- a/src/wayland/surface.h +++ b/src/wayland/surface.h @@ -113,6 +113,9 @@ class Surface { static std::vector tessellateRotatedRoundedRect( float centerX, float centerY, float width, float height, float radius, float rotationRad, int stripPx = 1 ); + // True when any rect covers at least one pixel of a `width` x `height` surface. + // Rects are surface-local, so they may legitimately sit partly or fully outside. + static bool regionIntersectsBounds(const std::vector& rects, std::uint32_t width, std::uint32_t height); void requestUpdate(); void requestUpdateOnly(); void requestLayout(); diff --git a/tests/surface_blur_region_test.cpp b/tests/surface_blur_region_test.cpp new file mode 100644 index 0000000000..ef482101ab --- /dev/null +++ b/tests/surface_blur_region_test.cpp @@ -0,0 +1,10 @@ +#include "test_check.h" +#include "wayland/surface.h" + +int main() { + TEST_CHECK(!Surface::regionIntersectsBounds({InputRect{8, -39, 3056, 35}}, 3072, 49)); + + TEST_CHECK(Surface::regionIntersectsBounds({InputRect{8, -34, 3056, 35}}, 3072, 49)); + + return 0; +}