From 62431fa165e206ca720daf391aa9b424d6d9e933 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Thu, 30 Apr 2026 14:41:53 +0530 Subject: [PATCH 1/2] Extending the wayfire-shell-unstable-v2 protocol - Added create_positioned_hotspot request to zwf_output_v2 interface. - Added proximity_changed event to zwf_hotspot_v2 interface. - All hotspots (edge, corner, positioned) will now get proximity_changed signals --- plugins/protocols/wayfire-shell.cpp | 199 +++++++++++++++++++++++----- proto/wayfire-shell-unstable-v2.xml | 127 ++++++++++++++---- 2 files changed, 267 insertions(+), 59 deletions(-) diff --git a/plugins/protocols/wayfire-shell.cpp b/plugins/protocols/wayfire-shell.cpp index ee8f38710..8f5e2fc30 100644 --- a/plugins/protocols/wayfire-shell.cpp +++ b/plugins/protocols/wayfire-shell.cpp @@ -27,7 +27,9 @@ class wfs_hotspot { private: wf::geometry_t hotspot_geometry; + wf::point_t trigger = {-1, -1}; // (-1,-1) means not a positioned hotspot + uint32_t threshold; bool hotspot_triggered = false; wf::wl_idle_call idle_check_input; wf::wl_timer timer; @@ -71,34 +73,29 @@ class wfs_hotspot void process_input_motion(wf::point_t gc) { - if (!(hotspot_geometry & gc)) + if (is_cursor_in_hotspot(gc)) + { + if (hotspot_triggered) + { + send_proximity_if_needed(gc); + } else if (!timer.is_connected()) + { + timer.set_timeout(timeout_ms, [=] () + { + hotspot_triggered = true; + zwf_hotspot_v2_send_enter(hotspot_resource); + send_proximity_if_needed(gc); + }); + } + } else { if (hotspot_triggered) { zwf_hotspot_v2_send_leave(hotspot_resource); + hotspot_triggered = false; } - /* Cursor outside of the hotspot */ - hotspot_triggered = false; timer.disconnect(); - - return; - } - - if (hotspot_triggered) - { - /* Hotspot was already triggered, wait for the next time the cursor - * enters the hotspot area to trigger again */ - return; - } - - if (!timer.is_connected()) - { - timer.set_timeout(timeout_ms, [=] () - { - hotspot_triggered = true; - zwf_hotspot_v2_send_enter(hotspot_resource); - }); } } @@ -127,6 +124,72 @@ class wfs_hotspot return slot; } + uint32_t calculate_proximity(const wf::point_t& cursor) const + { + if ((trigger.x == -1) && (trigger.y == -1)) + { + // Check each edge in the hotspot_geometry + // For edge hotspots, distance is perpendicular distance to that edge + // For corner hotspots, we need Euclidean distance to the corner + bool is_corner = (hotspot_geometry.width == (int)threshold && + hotspot_geometry.height == (int)threshold); + + if (is_corner) + { + // Corner hotspot: distance to the corner point + double dx = cursor.x - hotspot_geometry.x; + double dy = cursor.y - hotspot_geometry.y; + return std::min((uint32_t)std::sqrt(dx * dx + dy * dy), threshold); + } else + { + // Edge hotspot: perpendicular distance to the edge + if (hotspot_geometry.width == (int)threshold) // Top or bottom edge + { + return std::min((uint32_t)std::abs(cursor.y - hotspot_geometry.y), threshold); + } else // Left or right edge + { + return std::min((uint32_t)std::abs(cursor.x - hotspot_geometry.x), threshold); + } + } + } else + { + // Positioned hotspot: Euclidean distance to trigger point + double dx = cursor.x - trigger.x; + double dy = cursor.y - trigger.y; + uint32_t distance = (uint32_t)std::sqrt(dx * dx + dy * dy); + return std::min(distance, threshold); + } + } + + bool is_cursor_in_hotspot(const wf::point_t& cursor) const + { + if ((trigger.x == -1) && (trigger.y == -1)) + { + // Legacy edge/corner: rectangle containment + return (hotspot_geometry & cursor); + } else + { + // Positioned: Euclidean distance check to trigger point + double dx = cursor.x - trigger.x; + double dy = cursor.y - trigger.y; + return (dx * dx + dy * dy) <= (threshold * threshold); + } + } + + void send_proximity_if_needed(const wf::point_t& cursor) + { + if (wl_resource_get_version(hotspot_resource) < 2) + { + return; + } + + if (hotspot_triggered) + { + uint32_t proximity = calculate_proximity(cursor); + zwf_hotspot_v2_send_proximity_changed(hotspot_resource, proximity); + } + } + wfs_hotspot(const wfs_hotspot &) = delete; wfs_hotspot(wfs_hotspot &&) = delete; wfs_hotspot& operator =(const wfs_hotspot&) = delete; @@ -137,18 +200,63 @@ class wfs_hotspot * Create a new hotspot. * It is guaranteedd that edge_mask contains at most 2 non-opposing edges. */ - wfs_hotspot(wf::output_t *output, uint32_t edge_mask, - uint32_t distance, uint32_t timeout, wl_client *client, uint32_t id) + wfs_hotspot(wf::output_t *output, uint32_t edge_mask, uint32_t threshold, uint32_t timeout, + wl_client *client, uint32_t id, uint32_t position = UINT32_MAX) { + this->threshold = threshold; this->timeout_ms = timeout; - this->hotspot_geometry = - calculate_hotspot_geometry(output, edge_mask, distance); - hotspot_resource = - wl_resource_create(client, &zwf_hotspot_v2_interface, 1, id); + hotspot_resource = wl_resource_create(client, &zwf_hotspot_v2_interface, 3, id); wl_resource_set_implementation(hotspot_resource, NULL, this, handle_hotspot_destroy); + wf::geometry_t output_geom = output->get_layout_geometry(); + + if (position == UINT32_MAX) + { + // Legacy edge/corner hotspot + this->hotspot_geometry = calculate_hotspot_geometry(output, edge_mask, threshold); + // trigger remains (-1, -1) + } else + { + // Positioned hotspot (single edge) + // Validate edge_mask is a single edge + if ((edge_mask == 0) || ((edge_mask & (edge_mask - 1)) != 0)) + { + // Create dummy resource on error + hotspot_resource = wl_resource_create(client, &zwf_hotspot_v2_interface, 3, id); + wl_resource_set_implementation(hotspot_resource, NULL, NULL, NULL); + return; + } + + // Calculate trigger point + if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) + { + trigger.x = output_geom.x + std::min(position, (uint32_t)output_geom.width - 1); + trigger.y = output_geom.y; + } else if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) + { + trigger.x = output_geom.x + std::min(position, (uint32_t)output_geom.width - 1); + trigger.y = output_geom.y + output_geom.height - 1; + } else if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) + { + trigger.x = output_geom.x; + trigger.y = output_geom.y + std::min(position, (uint32_t)output_geom.height - 1); + } else if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) + { + trigger.x = output_geom.x + output_geom.width - 1; + trigger.y = output_geom.y + std::min(position, (uint32_t)output_geom.height - 1); + } + + // Bounding box for quick rejection + this->hotspot_geometry = { + (int)trigger.x - (int)threshold, + (int)trigger.y - (int)threshold, + (int)threshold * 2, + (int)threshold * 2 + }; + } + // setup output destroy listener on_output_removed.set_callback([this, output] (wf::output_removed_signal *ev) { @@ -156,6 +264,7 @@ class wfs_hotspot { /* Make hotspot inactive by setting the region to empty */ hotspot_geometry = {0, 0, 0, 0}; + trigger = {-1, -1}; process_input_motion({0, 0}); } }); @@ -184,11 +293,14 @@ static void handle_zwf_output_inhibit_output_done(wl_client*, wl_resource *resource); static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id); +static void handle_zwf_output_create_positioned_hotspot(wl_client*, wl_resource *resource, + uint32_t edge, uint32_t position, uint32_t radius, uint32_t timeout, uint32_t id); static struct zwf_output_v2_interface zwf_output_impl = { .inhibit_output = handle_zwf_output_inhibit_output, .inhibit_output_done = handle_zwf_output_inhibit_output_done, .create_hotspot = handle_zwf_output_create_hotspot, + .create_positioned_hotspot = handle_zwf_output_create_positioned_hotspot, }; /** @@ -252,8 +364,7 @@ class wfs_output this->shell_resource = shell_resource; resource = - wl_resource_create(client, &zwf_output_v2_interface, - std::min(wl_resource_get_version(shell_resource), 2), id); + wl_resource_create(client, &zwf_output_v2_interface, 3, id); wl_resource_set_implementation(resource, &zwf_output_impl, this, handle_output_destroy); output->connect(&on_fullscreen_layer_focused); output->connect(&on_toggle_menu); @@ -297,7 +408,7 @@ class wfs_output } void create_hotspot(uint32_t hotspot, uint32_t threshold, uint32_t timeout, - uint32_t id) + uint32_t id, uint32_t position = UINT32_MAX) { if (!this->output) { @@ -311,9 +422,22 @@ class wfs_output return; } - // will be auto-deleted when the resource is destroyed by the client + if (position != UINT32_MAX) + { + // Validate edge is a single edge + if ((hotspot == 0) || ((hotspot & (hotspot - 1)) != 0) || + ((hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) && + (hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) && + (hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) && + (hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT))) + { + wl_resource_post_error(this->resource, 0, "Invalid edge for positioned hotspot"); + return; + } + } + new wfs_hotspot(this->output, hotspot, threshold, timeout, - wl_resource_get_client(this->resource), id); + wl_resource_get_client(this->resource), id, position); } }; @@ -337,6 +461,13 @@ static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, output->create_hotspot(hotspot, threshold, timeout, id); } +static void handle_zwf_output_create_positioned_hotspot(wl_client*, wl_resource *resource, + uint32_t edge, uint32_t position, uint32_t threshold, uint32_t timeout, uint32_t id) +{ + auto output = (wfs_output*)wl_resource_get_user_data(resource); + output->create_hotspot(edge, threshold, timeout, id, position); +} + static void handle_output_destroy(wl_resource *resource) { auto *output = (wfs_output*)wl_resource_get_user_data(resource); @@ -372,7 +503,7 @@ class wfs_surface wfs_surface(wayfire_view view, wl_client *client, int id) { this->view = view; - resource = wl_resource_create(client, &zwf_surface_v2_interface, 1, id); + resource = wl_resource_create(client, &zwf_surface_v2_interface, 3, id); wl_resource_set_implementation(resource, &zwf_surface_impl, this, handle_surface_destroy); view->connect(&on_unmap); } @@ -446,7 +577,7 @@ wayfire_shell *wayfire_shell_create(wl_display *display) wayfire_shell *ws = new wayfire_shell; ws->shell_manager = wl_global_create(display, - &zwf_shell_manager_v2_interface, 2, NULL, bind_zwf_shell_manager); + &zwf_shell_manager_v2_interface, 3, NULL, bind_zwf_shell_manager); if (ws->shell_manager == NULL) { diff --git a/proto/wayfire-shell-unstable-v2.xml b/proto/wayfire-shell-unstable-v2.xml index a6d1d9d93..6a05f19a7 100644 --- a/proto/wayfire-shell-unstable-v2.xml +++ b/proto/wayfire-shell-unstable-v2.xml @@ -1,6 +1,6 @@ - + - + This protocol provides additional events and requests for special DE clients like panels, docks, etc. @@ -9,19 +9,19 @@ - - - + + + - - - + + + - + Represents a single output. Each output is managed independently from the others. @@ -65,14 +65,14 @@ - - - - + + + + - + A hotspot on the output is an edge or a corner region of the output where the mouse or touch point has been residing for a given amount of time. @@ -80,12 +80,18 @@ The hotspot can be used for example for autohiding panels, where the panel is shown when the input hovers on the edge of the output for a specific amount of time. + + For edge hotspots (single edge), the entire edge region of 'threshold' + pixels depth triggers the hotspot. + + For corner hotspots (two adjacent edges), the corner region of + 'threshold' pixels by 'threshold' pixels triggers the hotspot. - - - - + + + + @@ -96,15 +102,53 @@ Emitted using an activator binding. + + + + + Creates a hotspot at a specific point along a single output edge. + The hotspot is a semicircular region that extends inward from the edge. + + The edge parameter must contain exactly one edge (top/bottom/left/right). + + For left/right edges: position is measured in pixels from the top edge. + For top/bottom edges: position is measured in pixels from the left edge. + + If position exceeds the edge length, it is clamped to the edge length. + The radius defines the size of the semicircular hotspot region. + + + + + + + - - + + + Represents a region on an output that triggers when the input pointer + remains within it for a specified timeout period. + + Hotspots can be either edge-based (full edge or corner) or positioned + (a specific point on an edge). + + The lifecycle of a hotspot interaction is: + 1. Pointer enters hotspot area -> timeout timer starts + 2. After timeout, enter event is sent + 3. While pointer remains inside hotspot area, proximity_changed events + are sent as the pointer moves + 4. Pointer leaves hotspot area -> leave event is sent + - + Means that the mouse and/or touch finger was inside the indicated - hotspot for the given amount of time. + hotspot for the given amount of timeout time. + + After receiving this event, the client can expect proximity_changed + events as the pointer moves within the hotspot, and eventually a + leave event when the pointer exits. Emitted at most once for each entry of the input inside the hotspot. @@ -115,15 +159,48 @@ This event indicates that the mouse or touch point has left the hotspot area. + After this event, no further proximity_changed events will be sent + until a new enter event. + Emitted only once after each enter. + + + + Sent after an enter event and before the corresponding leave event, + whenever the input pointer moves within the hotspot area. + + Proximity indicates the current distance in pixels from the input pointer + to the trigger point of the hotspot: + - 0: Input pointer is exactly at the trigger point + - threshold: Input pointer is at the outer edge of the hotspot radius + (just entered the hotspot area) + - Values in between: Linear distance from trigger point + + This allows clients to implement pixel-perfect smooth feedback without + needing to know the threshold value used to create the hotspot. + + For standard edge/corner hotspots (created via create_hotspot), the + "threshold" is the distance from the edge parameter. The trigger point + is defined as the closest point on the edge to the input pointer. + + For positioned hotspots (created via create_positioned_hotspot), the + threshold is the radius parameter, and the trigger point is the exact + point on the edge specified by position. + + The compositor should send this event at the same frequency as pointer + motion events to ensure smooth animations. + + + + - - + + - + From 5916ac1002e9010d43dbcba25023655cc0474969 Mon Sep 17 00:00:00 2001 From: Marcus Britanicus Date: Fri, 1 May 2026 10:59:10 +0530 Subject: [PATCH 2/2] Adding support for an arbitrary hotspot in the screen - Replaced create_positioned_hotspot(...) with create_custom_hotspot(...) --- plugins/protocols/wayfire-shell.cpp | 345 +++++++++++++++------------- proto/wayfire-shell-unstable-v2.xml | 84 ++++--- 2 files changed, 228 insertions(+), 201 deletions(-) diff --git a/plugins/protocols/wayfire-shell.cpp b/plugins/protocols/wayfire-shell.cpp index 8f5e2fc30..a4dd54b69 100644 --- a/plugins/protocols/wayfire-shell.cpp +++ b/plugins/protocols/wayfire-shell.cpp @@ -26,8 +26,8 @@ static void handle_hotspot_destroy(wl_resource *resource); class wfs_hotspot { private: + wf::geometry_t trigger; wf::geometry_t hotspot_geometry; - wf::point_t trigger = {-1, -1}; // (-1,-1) means not a positioned hotspot uint32_t threshold; bool hotspot_triggered = false; @@ -99,81 +99,73 @@ class wfs_hotspot } } - wf::geometry_t calculate_hotspot_geometry(wf::output_t *output, - uint32_t edge_mask, uint32_t distance) const + wf::geometry_t calculate_hotspot_geometry(wf::output_t *output, const wf::geometry_t& trigger_rect, + uint32_t threshold) const { - wf::geometry_t slot = output->get_layout_geometry(); - if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) + // Get output geometry in global coordinates + wf::geometry_t output_geom = output->get_layout_geometry(); + + // Expand core rectangle by threshold in all directions + wf::geometry_t expanded = { + trigger_rect.x - (int)threshold, + trigger_rect.y - (int)threshold, + trigger_rect.width + (int)threshold * 2, + trigger_rect.height + (int)threshold * 2 + }; + + // Clamp to output boundaries + wf::geometry_t result = wf::geometry_intersection(expanded, output_geom); + + return result; + } + + uint32_t calculate_proximity(const wf::point_t& cursor) const + { + const auto& rect = trigger; + int dx = 0, dy = 0; + + if (cursor.x < rect.x) { - slot.height = distance; - } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) + dx = rect.x - cursor.x; + } else if ((rect.width > 0) && (cursor.x > rect.x + rect.width)) { - slot.y += slot.height - distance; - slot.height = distance; + dx = cursor.x - (rect.x + rect.width); } - if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) + if (cursor.y < rect.y) { - slot.width = distance; - } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) + dy = rect.y - cursor.y; + } else if ((rect.height > 0) && (cursor.y > rect.y + rect.height)) { - slot.x += slot.width - distance; - slot.width = distance; + dy = cursor.y - (rect.y + rect.height); } - return slot; - } - - uint32_t calculate_proximity(const wf::point_t& cursor) const - { - if ((trigger.x == -1) && (trigger.y == -1)) + // Inside the core rectangle + if ((dx == 0) && (dy == 0)) { - // Check each edge in the hotspot_geometry - // For edge hotspots, distance is perpendicular distance to that edge - // For corner hotspots, we need Euclidean distance to the corner - bool is_corner = (hotspot_geometry.width == (int)threshold && - hotspot_geometry.height == (int)threshold); + return 0; + } - if (is_corner) - { - // Corner hotspot: distance to the corner point - double dx = cursor.x - hotspot_geometry.x; - double dy = cursor.y - hotspot_geometry.y; - return std::min((uint32_t)std::sqrt(dx * dx + dy * dy), threshold); - } else - { - // Edge hotspot: perpendicular distance to the edge - if (hotspot_geometry.width == (int)threshold) // Top or bottom edge - { - return std::min((uint32_t)std::abs(cursor.y - hotspot_geometry.y), threshold); - } else // Left or right edge - { - return std::min((uint32_t)std::abs(cursor.x - hotspot_geometry.x), threshold); - } - } + // Euclidean distance to corner + if ((dx > 0) && (dy > 0)) + { + return std::min((uint32_t)std::sqrt(dx * dx + dy * dy), threshold); } else { - // Positioned hotspot: Euclidean distance to trigger point - double dx = cursor.x - trigger.x; - double dy = cursor.y - trigger.y; - uint32_t distance = (uint32_t)std::sqrt(dx * dx + dy * dy); - return std::min(distance, threshold); + return std::min((uint32_t)(dx + dy), threshold); } } bool is_cursor_in_hotspot(const wf::point_t& cursor) const { - if ((trigger.x == -1) && (trigger.y == -1)) - { - // Legacy edge/corner: rectangle containment - return (hotspot_geometry & cursor); - } else + // Quick rejection using bounding box + if (!(hotspot_geometry & cursor)) { - // Positioned: Euclidean distance check to trigger point - double dx = cursor.x - trigger.x; - double dy = cursor.y - trigger.y; - return (dx * dx + dy * dy) <= (threshold * threshold); + return false; } + + // Exact distance check + return calculate_proximity(cursor) <= threshold; } void send_proximity_if_needed(const wf::point_t& cursor) @@ -190,82 +182,35 @@ class wfs_hotspot } } - wfs_hotspot(const wfs_hotspot &) = delete; - wfs_hotspot(wfs_hotspot &&) = delete; + wfs_hotspot(const wfs_hotspot&) = delete; + wfs_hotspot(wfs_hotspot&&) = delete; wfs_hotspot& operator =(const wfs_hotspot&) = delete; wfs_hotspot& operator =(wfs_hotspot&&) = delete; public: + /** * Create a new hotspot. * It is guaranteedd that edge_mask contains at most 2 non-opposing edges. */ - wfs_hotspot(wf::output_t *output, uint32_t edge_mask, uint32_t threshold, uint32_t timeout, - wl_client *client, uint32_t id, uint32_t position = UINT32_MAX) + wfs_hotspot(wf::output_t *output, wf::geometry_t trigger, uint32_t threshold, uint32_t timeout, + wl_client *client, uint32_t id) : trigger(trigger), threshold(threshold), timeout_ms(timeout) { - this->threshold = threshold; - this->timeout_ms = timeout; + // Calculate expanded bounding box + this->hotspot_geometry = calculate_hotspot_geometry(output, trigger, threshold); + // Create resource and setup signals (same as before) hotspot_resource = wl_resource_create(client, &zwf_hotspot_v2_interface, 3, id); - wl_resource_set_implementation(hotspot_resource, NULL, this, - handle_hotspot_destroy); - - wf::geometry_t output_geom = output->get_layout_geometry(); - - if (position == UINT32_MAX) - { - // Legacy edge/corner hotspot - this->hotspot_geometry = calculate_hotspot_geometry(output, edge_mask, threshold); - // trigger remains (-1, -1) - } else - { - // Positioned hotspot (single edge) - // Validate edge_mask is a single edge - if ((edge_mask == 0) || ((edge_mask & (edge_mask - 1)) != 0)) - { - // Create dummy resource on error - hotspot_resource = wl_resource_create(client, &zwf_hotspot_v2_interface, 3, id); - wl_resource_set_implementation(hotspot_resource, NULL, NULL, NULL); - return; - } + wl_resource_set_implementation(hotspot_resource, NULL, this, handle_hotspot_destroy); - // Calculate trigger point - if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) - { - trigger.x = output_geom.x + std::min(position, (uint32_t)output_geom.width - 1); - trigger.y = output_geom.y; - } else if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) - { - trigger.x = output_geom.x + std::min(position, (uint32_t)output_geom.width - 1); - trigger.y = output_geom.y + output_geom.height - 1; - } else if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) - { - trigger.x = output_geom.x; - trigger.y = output_geom.y + std::min(position, (uint32_t)output_geom.height - 1); - } else if (edge_mask == ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) - { - trigger.x = output_geom.x + output_geom.width - 1; - trigger.y = output_geom.y + std::min(position, (uint32_t)output_geom.height - 1); - } - - // Bounding box for quick rejection - this->hotspot_geometry = { - (int)trigger.x - (int)threshold, - (int)trigger.y - (int)threshold, - (int)threshold * 2, - (int)threshold * 2 - }; - } - - // setup output destroy listener + // output destroy handler on_output_removed.set_callback([this, output] (wf::output_removed_signal *ev) { if (ev->output == output) { - /* Make hotspot inactive by setting the region to empty */ - hotspot_geometry = {0, 0, 0, 0}; - trigger = {-1, -1}; - process_input_motion({0, 0}); + this->hotspot_geometry = {0, 0, 0, 0}; + this->trigger = {0, 0, 0, 0}; + process_input_motion({-1, -1}); } }); @@ -281,6 +226,7 @@ class wfs_hotspot static void handle_hotspot_destroy(wl_resource *resource) { auto *hotspot = (wfs_hotspot*)wl_resource_get_user_data(resource); + delete hotspot; wl_resource_set_user_data(resource, nullptr); @@ -289,18 +235,18 @@ static void handle_hotspot_destroy(wl_resource *resource) /* ------------------------------ wfs_output -------------------------------- */ static void handle_output_destroy(wl_resource *resource); static void handle_zwf_output_inhibit_output(wl_client*, wl_resource *resource); -static void handle_zwf_output_inhibit_output_done(wl_client*, - wl_resource *resource); -static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, - uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id); -static void handle_zwf_output_create_positioned_hotspot(wl_client*, wl_resource *resource, - uint32_t edge, uint32_t position, uint32_t radius, uint32_t timeout, uint32_t id); +static void handle_zwf_output_inhibit_output_done(wl_client*, wl_resource *resource); +static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, uint32_t hotspot, + uint32_t threshold, uint32_t timeout, uint32_t id); +static void handle_zwf_output_create_custom_hotspot(wl_client*, wl_resource *resource, uint32_t x, uint32_t y, + uint32_t w, uint32_t h, uint32_t threshold, + uint32_t timeout, uint32_t id); static struct zwf_output_v2_interface zwf_output_impl = { .inhibit_output = handle_zwf_output_inhibit_output, .inhibit_output_done = handle_zwf_output_inhibit_output_done, .create_hotspot = handle_zwf_output_create_hotspot, - .create_positioned_hotspot = handle_zwf_output_create_positioned_hotspot, + .create_custom_hotspot = handle_zwf_output_create_custom_hotspot, }; /** @@ -382,8 +328,8 @@ class wfs_output disconnect_from_output(); } - wfs_output(const wfs_output &) = delete; - wfs_output(wfs_output &&) = delete; + wfs_output(const wfs_output&) = delete; + wfs_output(wfs_output&&) = delete; wfs_output& operator =(const wfs_output&) = delete; wfs_output& operator =(wfs_output&&) = delete; @@ -407,70 +353,136 @@ class wfs_output } } - void create_hotspot(uint32_t hotspot, uint32_t threshold, uint32_t timeout, - uint32_t id, uint32_t position = UINT32_MAX) + wf::geometry_t calculate_trigger_geometry(uint32_t edge_mask) { - if (!this->output) + wf::geometry_t output_geom = this->output->get_layout_geometry(); + int edge_count = 0; + + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) { - // It can happen that the client requests a hotspot immediately after an output is destroyed - - // this is an inherent race condition because the compositor and client are not in sync. - // - // In this case, we create a dummy hotspot resource to avoid Wayland protocol errors. - auto resource = wl_resource_create( - wl_resource_get_client(this->resource), &zwf_hotspot_v2_interface, 1, id); - wl_resource_set_implementation(resource, NULL, NULL, NULL); - return; + edge_count++; + } + + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) + { + edge_count++; + } + + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) + { + edge_count++; + } + + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) + { + edge_count++; } - if (position != UINT32_MAX) + if (edge_count == 1) { - // Validate edge is a single edge - if ((hotspot == 0) || ((hotspot & (hotspot - 1)) != 0) || - ((hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) && - (hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) && - (hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) && - (hotspot != ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT))) + // Single edge: rectangle with zero thickness + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_TOP) { - wl_resource_post_error(this->resource, 0, "Invalid edge for positioned hotspot"); - return; + return {output_geom.x, output_geom.y, output_geom.width, 0}; + } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) + { + return {output_geom.x, output_geom.y + output_geom.height - 1, output_geom.width, 0}; + } else if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_LEFT) + { + return {output_geom.x, output_geom.y, 0, output_geom.height}; + } else // RIGHT + { + return {output_geom.x + output_geom.width - 1, output_geom.y, 0, output_geom.height}; } + } else if (edge_count == 2) + { + // Corner: zero-area point at the corner + int x = output_geom.x; + int y = output_geom.y; + + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_RIGHT) + { + x = output_geom.x + output_geom.width - 1; + } + + if (edge_mask & ZWF_OUTPUT_V2_HOTSPOT_EDGE_BOTTOM) + { + y = output_geom.y + output_geom.height - 1; + } + + return {x, y, 0, 0}; } - new wfs_hotspot(this->output, hotspot, threshold, timeout, - wl_resource_get_client(this->resource), id, position); + // Invalid + return {0, 0, 0, 0}; + } + + wf::geometry_t calculate_trigger_geometry(wf::geometry_t rect) + { + wf::geometry_t output_geom = this->output->get_layout_geometry(); + + wf::geometry_t trigger = { + output_geom.x + rect.x, + output_geom.y + rect.y, + rect.width, + rect.height + }; + + return trigger; + } + + void create_hotspot(const wf::geometry_t& trigger_rect, uint32_t threshold, uint32_t timeout, uint32_t id) + { + if (!this->output) + { + auto resource = wl_resource_create(wl_resource_get_client( + this->resource), &zwf_hotspot_v2_interface, 3, id); + wl_resource_set_implementation(resource, NULL, NULL, NULL); + return; + } + + new wfs_hotspot(this->output, trigger_rect, threshold, timeout, wl_resource_get_client( + this->resource), id); } }; static void handle_zwf_output_inhibit_output(wl_client*, wl_resource *resource) { auto output = (wfs_output*)wl_resource_get_user_data(resource); + output->inhibit_output(); } -static void handle_zwf_output_inhibit_output_done( - wl_client*, wl_resource *resource) +static void handle_zwf_output_inhibit_output_done(wl_client*, wl_resource *resource) { auto output = (wfs_output*)wl_resource_get_user_data(resource); + output->inhibit_output_done(); } static void handle_zwf_output_create_hotspot(wl_client*, wl_resource *resource, - uint32_t hotspot, uint32_t threshold, uint32_t timeout, uint32_t id) + uint32_t edge_mask, uint32_t threshold, uint32_t timeout, uint32_t id) { auto output = (wfs_output*)wl_resource_get_user_data(resource); - output->create_hotspot(hotspot, threshold, timeout, id); + wf::geometry_t trigger = output->calculate_trigger_geometry(edge_mask); + + output->create_hotspot(trigger, threshold, timeout, id); } -static void handle_zwf_output_create_positioned_hotspot(wl_client*, wl_resource *resource, - uint32_t edge, uint32_t position, uint32_t threshold, uint32_t timeout, uint32_t id) +static void handle_zwf_output_create_custom_hotspot(wl_client*, wl_resource *resource, + uint32_t x, uint32_t y, uint32_t width, uint32_t height, + uint32_t threshold, uint32_t timeout, uint32_t id) { auto output = (wfs_output*)wl_resource_get_user_data(resource); - output->create_hotspot(edge, threshold, timeout, id, position); + wf::geometry_t trigger = output->calculate_trigger_geometry({(int)x, (int)y, (int)width, (int)height}); + + output->create_hotspot(trigger, threshold, timeout, id); } static void handle_output_destroy(wl_resource *resource) { auto *output = (wfs_output*)wl_resource_get_user_data(resource); + delete output; wl_resource_set_user_data(resource, nullptr); @@ -478,8 +490,7 @@ static void handle_output_destroy(wl_resource *resource) /* ------------------------------ wfs_surface ------------------------------- */ static void handle_surface_destroy(wl_resource *resource); -static void handle_zwf_surface_interactive_move(wl_client*, - wl_resource *resource); +static void handle_zwf_surface_interactive_move(wl_client*, wl_resource *resource); static struct zwf_surface_v2_interface zwf_surface_impl = { .interactive_move = handle_zwf_surface_interactive_move, @@ -518,18 +529,20 @@ class wfs_surface static void handle_zwf_surface_interactive_move(wl_client*, wl_resource *resource) { auto surface = (wfs_surface*)wl_resource_get_user_data(resource); + surface->interactive_move(); } static void handle_surface_destroy(wl_resource *resource) { auto surface = (wfs_surface*)wl_resource_get_user_data(resource); + delete surface; wl_resource_set_user_data(resource, nullptr); } -static void zwf_shell_manager_get_wf_output(wl_client *client, - wl_resource *resource, wl_resource *output, uint32_t id) +static void zwf_shell_manager_get_wf_output(wl_client *client, wl_resource *resource, wl_resource *output, + uint32_t id) { auto wlr_out = (wlr_output*)wl_resource_get_user_data(output); auto wo = wf::get_core().output_layout->find_output(wlr_out); @@ -541,10 +554,11 @@ static void zwf_shell_manager_get_wf_output(wl_client *client, } } -static void zwf_shell_manager_get_wf_surface(wl_client *client, - wl_resource *resource, wl_resource *surface, uint32_t id) +static void zwf_shell_manager_get_wf_surface(wl_client *client, wl_resource *resource, wl_resource *surface, + uint32_t id) { auto view = wf::wl_surface_to_wayfire_view(surface); + if (view) { /* Will be freed when the resource is destroyed */ @@ -561,10 +575,9 @@ const struct zwf_shell_manager_v2_interface zwf_shell_manager_v2_impl = void bind_zwf_shell_manager(wl_client *client, void *data, uint32_t version, uint32_t id) { - auto resource = - wl_resource_create(client, &zwf_shell_manager_v2_interface, version, id); - wl_resource_set_implementation(resource, - &zwf_shell_manager_v2_impl, NULL, NULL); + auto resource = wl_resource_create(client, &zwf_shell_manager_v2_interface, version, id); + + wl_resource_set_implementation(resource, &zwf_shell_manager_v2_impl, NULL, NULL); } struct wayfire_shell @@ -576,8 +589,8 @@ wayfire_shell *wayfire_shell_create(wl_display *display) { wayfire_shell *ws = new wayfire_shell; - ws->shell_manager = wl_global_create(display, - &zwf_shell_manager_v2_interface, 3, NULL, bind_zwf_shell_manager); + ws->shell_manager = wl_global_create(display, &zwf_shell_manager_v2_interface, 3, NULL, + bind_zwf_shell_manager); if (ws->shell_manager == NULL) { diff --git a/proto/wayfire-shell-unstable-v2.xml b/proto/wayfire-shell-unstable-v2.xml index 6a05f19a7..b9ad0fc31 100644 --- a/proto/wayfire-shell-unstable-v2.xml +++ b/proto/wayfire-shell-unstable-v2.xml @@ -1,4 +1,4 @@ - + @@ -9,15 +9,15 @@ - - - + + + - - - + + + @@ -65,10 +65,10 @@ - - - - + + + + @@ -88,10 +88,10 @@ 'threshold' pixels by 'threshold' pixels triggers the hotspot. - - - - + + + + @@ -104,24 +104,38 @@ - - - Creates a hotspot at a specific point along a single output edge. - The hotspot is a semicircular region that extends inward from the edge. - - The edge parameter must contain exactly one edge (top/bottom/left/right). - - For left/right edges: position is measured in pixels from the top edge. - For top/bottom edges: position is measured in pixels from the left edge. - - If position exceeds the edge length, it is clamped to the edge length. - The radius defines the size of the semicircular hotspot region. + + + Creates a hotspot for an arbitrary axis-aligned rectangle on the output. + The rectangle is defined in output-local coordinates with (x, y) as the + top-left corner, and (w, h) as width and height. + + The hotspot activates when the input pointer comes within 'threshold' + pixels of any point on the rectangle's boundary. The proximity event + reports the exact distance (0 to threshold) from the pointer to the + nearest point on the rectangle boundary. + + This is a generalization of edge and corner hotspots: + - A top edge hotspot is a rectangle covering the entire top edge + with w = output.width, h = threshold, positioned at (0, 0) + - A corner hotspot is a square of size threshold x threshold + positioned at the output corner + + Use cases include: + - Trigger zones that aren't aligned to output edges + - Complex panel layouts where activation regions are around specific UI elements + - Desktop widgets that expand/collapse based on pointer approach + + Note: The rectangle should be within the output bounds. The compositor + may clamp it to output geometry or ignore rectangles entirely outside. - - - - - + + + + + + + @@ -193,14 +207,14 @@ motion events to ensure smooth animations. - + - + - +