aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoroutfoxxed <[email protected]>2024-05-04 19:14:35 -0700
committerGitHub <[email protected]>2024-05-05 03:14:35 +0100
commitaaf35b9f1f0ce49080f2a0b1e7943336b1ba6057 (patch)
tree5e0b23e9b44ee1eabe68d6739c5d1d58ae558037
parent62eadad20fe10ffaf13d9c65ee68608996d93df0 (diff)
downloadHyprland-aaf35b9f1f0ce49080f2a0b1e7943336b1ba6057.tar.gz
Hyprland-aaf35b9f1f0ce49080f2a0b1e7943336b1ba6057.zip
protocols: add hyprland_focus_grab_v1 implementation (#5850)
* protocols: add hyprland_focus_grab_v1 implementation * protocols/focus_grab: fix keyboard focus staying on unlisted windows When creating a focus grab with layershell surfaces, the last active toplevel kept keyboard focus. * protocols/focus_grab: fix formatting * protocols/focus_grab: try to pick surface for keyboard focus * focus_grab: update keyboard focus to match spec * Revert "protocols/focus_grab: try to pick surface for keyboard focus" This reverts commit 090358d0d19cc65208641eaefa0a905e99145730. * protocols/focus_grab: fix issues and match new spec * kde-server-decoration: move to new impl * protocols/focus_grab: review fixup * Update hyprland-protocols --------- Co-authored-by: Vaxry <[email protected]>
-rwxr-xr-xCMakeLists.txt1
-rw-r--r--flake.lock6
-rw-r--r--protocols/meson.build1
-rw-r--r--src/managers/ProtocolManager.cpp2
-rw-r--r--src/protocols/FocusGrab.cpp325
-rw-r--r--src/protocols/FocusGrab.hpp77
m---------subprojects/hyprland-protocols0
7 files changed, 409 insertions, 3 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a4e400d1..cdf72fe0 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -266,6 +266,7 @@ protocolNew("protocols/wlr-virtual-pointer-unstable-v1.xml" "wlr-virtual-pointer
protocolNew("protocols/input-method-unstable-v2.xml" "input-method-unstable-v2" true)
protocolNew("protocols/wlr-output-management-unstable-v1.xml" "wlr-output-management-unstable-v1" true)
protocolNew("protocols/kde-server-decoration.xml" "kde-server-decoration" true)
+protocolNew("subprojects/hyprland-protocols/protocols/hyprland-focus-grab-v1.xml" "hyprland-focus-grab-v1" true)
protocolNew("staging/tearing-control/tearing-control-v1.xml" "tearing-control-v1" false)
protocolNew("staging/fractional-scale/fractional-scale-v1.xml" "fractional-scale-v1" false)
protocolNew("unstable/xdg-output/xdg-output-unstable-v1.xml" "xdg-output-unstable-v1" false)
diff --git a/flake.lock b/flake.lock
index 2e8ccf87..4dc48dc9 100644
--- a/flake.lock
+++ b/flake.lock
@@ -36,11 +36,11 @@
]
},
"locked": {
- "lastModified": 1691753796,
- "narHash": "sha256-zOEwiWoXk3j3+EoF3ySUJmberFewWlagvewDRuWYAso=",
+ "lastModified": 1714869498,
+ "narHash": "sha256-vbLVOWvQqo4n1yvkg/Q70VTlPbMmTiCQfNTgcWDCfJM=",
"owner": "hyprwm",
"repo": "hyprland-protocols",
- "rev": "0c2ce70625cb30aef199cb388f99e19a61a6ce03",
+ "rev": "e06482e0e611130cd1929f75e8c1cf679e57d161",
"type": "github"
},
"original": {
diff --git a/protocols/meson.build b/protocols/meson.build
index 1c9013f0..f6c54d4d 100644
--- a/protocols/meson.build
+++ b/protocols/meson.build
@@ -43,6 +43,7 @@ new_protocols = [
['wlr-virtual-pointer-unstable-v1.xml'],
['wlr-output-management-unstable-v1.xml'],
['kde-server-decoration.xml'],
+ [hl_protocol_dir, 'protocols/hyprland-focus-grab-v1.xml'],
[wl_protocol_dir, 'staging/tearing-control/tearing-control-v1.xml'],
[wl_protocol_dir, 'staging/fractional-scale/fractional-scale-v1.xml'],
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp
index 47372d01..ee27d35d 100644
--- a/src/managers/ProtocolManager.cpp
+++ b/src/managers/ProtocolManager.cpp
@@ -24,6 +24,7 @@
#include "../protocols/VirtualPointer.hpp"
#include "../protocols/OutputManagement.hpp"
#include "../protocols/ServerDecorationKDE.hpp"
+#include "../protocols/FocusGrab.hpp"
CProtocolManager::CProtocolManager() {
@@ -51,6 +52,7 @@ CProtocolManager::CProtocolManager() {
PROTO::virtualPointer = std::make_unique<CVirtualPointerProtocol>(&zwlr_virtual_pointer_manager_v1_interface, 2, "VirtualPointer");
PROTO::outputManagement = std::make_unique<COutputManagementProtocol>(&zwlr_output_manager_v1_interface, 4, "OutputManagement");
PROTO::serverDecorationKDE = std::make_unique<CServerDecorationKDEProtocol>(&org_kde_kwin_server_decoration_manager_interface, 1, "ServerDecorationKDE");
+ PROTO::focusGrab = std::make_unique<CFocusGrabProtocol>(&hyprland_focus_grab_manager_v1_interface, 1, "FocusGrab");
// Old protocol implementations.
// TODO: rewrite them to use hyprwayland-scanner.
diff --git a/src/protocols/FocusGrab.cpp b/src/protocols/FocusGrab.cpp
new file mode 100644
index 00000000..61c17573
--- /dev/null
+++ b/src/protocols/FocusGrab.cpp
@@ -0,0 +1,325 @@
+#include "FocusGrab.hpp"
+#include "Compositor.hpp"
+#include <hyprland-focus-grab-v1.hpp>
+#include <managers/input/InputManager.hpp>
+#include <cstdint>
+#include <memory>
+#include <wayland-server.h>
+
+static void focus_grab_pointer_enter(wlr_seat_pointer_grab* grab, wlr_surface* surface, double sx, double sy) {
+ if (static_cast<CFocusGrab*>(grab->data)->isSurfaceComitted(surface))
+ wlr_seat_pointer_enter(grab->seat, surface, sx, sy);
+ else
+ wlr_seat_pointer_clear_focus(grab->seat);
+}
+
+static void focus_grab_pointer_clear_focus(wlr_seat_pointer_grab* grab) {
+ wlr_seat_pointer_clear_focus(grab->seat);
+}
+
+static void focus_grab_pointer_motion(wlr_seat_pointer_grab* grab, uint32_t time, double sx, double sy) {
+ wlr_seat_pointer_send_motion(grab->seat, time, sx, sy);
+}
+
+static uint32_t focus_grab_pointer_button(wlr_seat_pointer_grab* grab, uint32_t time, uint32_t button, wl_pointer_button_state state) {
+ uint32_t serial = wlr_seat_pointer_send_button(grab->seat, time, button, state);
+
+ if (serial)
+ return serial;
+ else {
+ static_cast<CFocusGrab*>(grab->data)->finish(true);
+ return 0;
+ }
+}
+
+static void focus_grab_pointer_axis(wlr_seat_pointer_grab* grab, uint32_t time, enum wl_pointer_axis orientation, double value, int32_t value_discrete,
+ enum wl_pointer_axis_source source, enum wl_pointer_axis_relative_direction relative_direction) {
+ wlr_seat_pointer_send_axis(grab->seat, time, orientation, value, value_discrete, source, relative_direction);
+}
+
+static void focus_grab_pointer_frame(wlr_seat_pointer_grab* grab) {
+ wlr_seat_pointer_send_frame(grab->seat);
+}
+
+static void focus_grab_pointer_cancel(wlr_seat_pointer_grab* grab) {
+ static_cast<CFocusGrab*>(grab->data)->finish(true);
+}
+
+static const wlr_pointer_grab_interface focus_grab_pointer_impl = {
+ .enter = focus_grab_pointer_enter,
+ .clear_focus = focus_grab_pointer_clear_focus,
+ .motion = focus_grab_pointer_motion,
+ .button = focus_grab_pointer_button,
+ .axis = focus_grab_pointer_axis,
+ .frame = focus_grab_pointer_frame,
+ .cancel = focus_grab_pointer_cancel,
+};
+
+static void focus_grab_keyboard_enter(wlr_seat_keyboard_grab* grab, wlr_surface* surface, const uint32_t keycodes[], size_t num_keycodes, const wlr_keyboard_modifiers* modifiers) {
+ if (static_cast<CFocusGrab*>(grab->data)->isSurfaceComitted(surface))
+ wlr_seat_keyboard_enter(grab->seat, surface, keycodes, num_keycodes, modifiers);
+
+ // otherwise the last grabbed window should retain keyboard focus.
+}
+
+static void focus_grab_keyboard_clear_focus(wlr_seat_keyboard_grab* grab) {
+ static_cast<CFocusGrab*>(grab->data)->finish(true);
+}
+
+static void focus_grab_keyboard_key(wlr_seat_keyboard_grab* grab, uint32_t time, uint32_t key, uint32_t state) {
+ wlr_seat_keyboard_send_key(grab->seat, time, key, state);
+}
+
+static void focus_grab_keyboard_modifiers(wlr_seat_keyboard_grab* grab, const wlr_keyboard_modifiers* modifiers) {
+ wlr_seat_keyboard_send_modifiers(grab->seat, modifiers);
+}
+
+static void focus_grab_keyboard_cancel(wlr_seat_keyboard_grab* grab) {
+ static_cast<CFocusGrab*>(grab->data)->finish(true);
+}
+
+static const wlr_keyboard_grab_interface focus_grab_keyboard_impl = {
+ .enter = focus_grab_keyboard_enter,
+ .clear_focus = focus_grab_keyboard_clear_focus,
+ .key = focus_grab_keyboard_key,
+ .modifiers = focus_grab_keyboard_modifiers,
+ .cancel = focus_grab_keyboard_cancel,
+};
+
+static uint32_t focus_grab_touch_down(wlr_seat_touch_grab* grab, uint32_t time, wlr_touch_point* point) {
+ if (!static_cast<CFocusGrab*>(grab->data)->isSurfaceComitted(point->surface))
+ return 0;
+
+ return wlr_seat_touch_send_down(grab->seat, point->surface, time, point->touch_id, point->sx, point->sy);
+}
+
+static void focus_grab_touch_up(wlr_seat_touch_grab* grab, uint32_t time, wlr_touch_point* point) {
+ wlr_seat_touch_send_up(grab->seat, time, point->touch_id);
+}
+
+static void focus_grab_touch_motion(wlr_seat_touch_grab* grab, uint32_t time, wlr_touch_point* point) {
+ wlr_seat_touch_send_motion(grab->seat, time, point->touch_id, point->sx, point->sy);
+}
+
+static void focus_grab_touch_enter(wlr_seat_touch_grab* grab, uint32_t time, wlr_touch_point* point) {}
+
+static void focus_grab_touch_frame(wlr_seat_touch_grab* grab) {
+ wlr_seat_touch_send_frame(grab->seat);
+}
+
+static void focus_grab_touch_cancel(wlr_seat_touch_grab* grab) {
+ static_cast<CFocusGrab*>(grab->data)->finish(true);
+}
+
+static const wlr_touch_grab_interface focus_grab_touch_impl = {
+ .down = focus_grab_touch_down,
+ .up = focus_grab_touch_up,
+ .motion = focus_grab_touch_motion,
+ .enter = focus_grab_touch_enter,
+ .frame = focus_grab_touch_frame,
+ .cancel = focus_grab_touch_cancel,
+};
+
+CFocusGrabSurfaceState::CFocusGrabSurfaceState(CFocusGrab* grab, wlr_surface* surface) {
+ hyprListener_surfaceDestroy.initCallback(
+ &surface->events.destroy, [=](void*, void*) { grab->eraseSurface(surface); }, this, "CFocusGrab");
+}
+
+CFocusGrabSurfaceState::~CFocusGrabSurfaceState() {
+ hyprListener_surfaceDestroy.removeCallback();
+}
+
+CFocusGrab::CFocusGrab(SP<CHyprlandFocusGrabV1> resource_) : resource(resource_) {
+ if (!resource->resource())
+ return;
+
+ m_sPointerGrab.interface = &focus_grab_pointer_impl;
+ m_sPointerGrab.data = this;
+
+ m_sKeyboardGrab.interface = &focus_grab_keyboard_impl;
+ m_sKeyboardGrab.data = this;
+
+ m_sTouchGrab.interface = &focus_grab_touch_impl;
+ m_sTouchGrab.data = this;
+
+ resource->setDestroy([this](CHyprlandFocusGrabV1* pMgr) { PROTO::focusGrab->destroyGrab(this); });
+ resource->setOnDestroy([this](CHyprlandFocusGrabV1* pMgr) { PROTO::focusGrab->destroyGrab(this); });
+ resource->setAddSurface([this](CHyprlandFocusGrabV1* pMgr, wl_resource* surface) { addSurface(wlr_surface_from_resource(surface)); });
+ resource->setRemoveSurface([this](CHyprlandFocusGrabV1* pMgr, wl_resource* surface) { removeSurface(wlr_surface_from_resource(surface)); });
+ resource->setCommit([this](CHyprlandFocusGrabV1* pMgr) { commit(); });
+}
+
+CFocusGrab::~CFocusGrab() {
+ finish(false);
+}
+
+bool CFocusGrab::good() {
+ return resource->resource();
+}
+
+bool CFocusGrab::isSurfaceComitted(wlr_surface* surface) {
+ auto iter = m_mSurfaces.find(surface);
+ if (iter == m_mSurfaces.end())
+ return false;
+
+ return iter->second->state == CFocusGrabSurfaceState::Comitted;
+}
+
+void CFocusGrab::start() {
+ if (!m_bGrabActive) {
+ wlr_seat_pointer_start_grab(g_pCompositor->m_sSeat.seat, &m_sPointerGrab);
+ wlr_seat_keyboard_start_grab(g_pCompositor->m_sSeat.seat, &m_sKeyboardGrab);
+ wlr_seat_touch_start_grab(g_pCompositor->m_sSeat.seat, &m_sTouchGrab);
+ m_bGrabActive = true;
+
+ // Ensure the grab ends if another grab begins, including from xdg_popup::grab.
+
+ hyprListener_pointerGrabStarted.initCallback(
+ &g_pCompositor->m_sSeat.seat->events.pointer_grab_begin, [this](void*, void*) { finish(true); }, this, "CFocusGrab");
+
+ hyprListener_keyboardGrabStarted.initCallback(
+ &g_pCompositor->m_sSeat.seat->events.keyboard_grab_begin, [this](void*, void*) { finish(true); }, this, "CFocusGrab");
+
+ hyprListener_touchGrabStarted.initCallback(
+ &g_pCompositor->m_sSeat.seat->events.touch_grab_begin, [this](void*, void*) { finish(true); }, this, "CFocusGrab");
+ }
+
+ // Ensure new surfaces are focused if under the mouse when comitted.
+ g_pInputManager->simulateMouseMovement();
+ refocusKeyboard();
+}
+
+void CFocusGrab::finish(bool sendCleared) {
+ if (m_bGrabActive) {
+ m_bGrabActive = false;
+ hyprListener_pointerGrabStarted.removeCallback();
+ hyprListener_keyboardGrabStarted.removeCallback();
+ hyprListener_touchGrabStarted.removeCallback();
+
+ // Only clear grabs that belong to this focus grab. When superseded by another grab
+ // or xdg_popup grab we might not own the current grab.
+
+ bool hadGrab = false;
+ if (g_pCompositor->m_sSeat.seat->pointer_state.grab == &m_sPointerGrab) {
+ wlr_seat_pointer_end_grab(g_pCompositor->m_sSeat.seat);
+ hadGrab = true;
+ }
+
+ if (g_pCompositor->m_sSeat.seat->keyboard_state.grab == &m_sKeyboardGrab) {
+ wlr_seat_keyboard_end_grab(g_pCompositor->m_sSeat.seat);
+ hadGrab = true;
+ }
+
+ if (g_pCompositor->m_sSeat.seat->touch_state.grab == &m_sTouchGrab) {
+ wlr_seat_touch_end_grab(g_pCompositor->m_sSeat.seat);
+ hadGrab = true;
+ }
+
+ m_mSurfaces.clear();
+
+ if (sendCleared)
+ resource->sendCleared();
+
+ // Ensure surfaces under the mouse when the grab ends get focus.
+ if (hadGrab)
+ g_pInputManager->refocus();
+ }
+}
+
+void CFocusGrab::addSurface(wlr_surface* surface) {
+ auto iter = m_mSurfaces.find(surface);
+ if (iter == m_mSurfaces.end())
+ m_mSurfaces.emplace(surface, std::make_unique<CFocusGrabSurfaceState>(this, surface));
+}
+
+void CFocusGrab::removeSurface(wlr_surface* surface) {
+ auto iter = m_mSurfaces.find(surface);
+ if (iter != m_mSurfaces.end()) {
+ if (iter->second->state == CFocusGrabSurfaceState::PendingAddition)
+ m_mSurfaces.erase(iter);
+ else
+ iter->second->state = CFocusGrabSurfaceState::PendingRemoval;
+ }
+}
+
+void CFocusGrab::eraseSurface(wlr_surface* surface) {
+ removeSurface(surface);
+ commit();
+}
+
+void CFocusGrab::refocusKeyboard() {
+ auto keyboardSurface = g_pCompositor->m_sSeat.seat->keyboard_state.focused_surface;
+ if (keyboardSurface != nullptr && isSurfaceComitted(keyboardSurface))
+ return;
+
+ wlr_surface* surface = nullptr;
+ for (auto& [surf, state] : m_mSurfaces) {
+ if (state->state == CFocusGrabSurfaceState::Comitted) {
+ surface = surf;
+ break;
+ }
+ }
+
+ if (surface)
+ g_pCompositor->focusSurface(surface);
+ else
+ Debug::log(ERR, "CFocusGrab::refocusKeyboard called with no committed surfaces. This should never happen.");
+}
+
+void CFocusGrab::commit() {
+ auto surfacesChanged = false;
+ auto anyComitted = false;
+ for (auto iter = m_mSurfaces.begin(); iter != m_mSurfaces.end();) {
+ switch (iter->second->state) {
+ case CFocusGrabSurfaceState::PendingRemoval:
+ iter = m_mSurfaces.erase(iter);
+ surfacesChanged = true;
+ continue;
+ case CFocusGrabSurfaceState::PendingAddition:
+ iter->second->state = CFocusGrabSurfaceState::Comitted;
+ surfacesChanged = true;
+ anyComitted = true;
+ break;
+ case CFocusGrabSurfaceState::Comitted: anyComitted = true; break;
+ }
+
+ iter++;
+ }
+
+ if (surfacesChanged) {
+ if (anyComitted)
+ start();
+ else
+ finish(true);
+ }
+}
+
+CFocusGrabProtocol::CFocusGrabProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) {
+ ;
+}
+
+void CFocusGrabProtocol::bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id) {
+ const auto RESOURCE = m_vManagers.emplace_back(std::make_unique<CHyprlandFocusGrabManagerV1>(client, ver, id)).get();
+ RESOURCE->setOnDestroy([this](CHyprlandFocusGrabManagerV1* p) { onManagerResourceDestroy(p->resource()); });
+
+ RESOURCE->setDestroy([this](CHyprlandFocusGrabManagerV1* p) { onManagerResourceDestroy(p->resource()); });
+ RESOURCE->setCreateGrab([this](CHyprlandFocusGrabManagerV1* pMgr, uint32_t id) { onCreateGrab(pMgr, id); });
+}
+
+void CFocusGrabProtocol::onManagerResourceDestroy(wl_resource* res) {
+ std::erase_if(m_vManagers, [&](const auto& other) { return other->resource() == res; });
+}
+
+void CFocusGrabProtocol::destroyGrab(CFocusGrab* grab) {
+ std::erase_if(m_vGrabs, [&](const auto& other) { return other.get() == grab; });
+}
+
+void CFocusGrabProtocol::onCreateGrab(CHyprlandFocusGrabManagerV1* pMgr, uint32_t id) {
+ m_vGrabs.push_back(std::make_unique<CFocusGrab>(std::make_shared<CHyprlandFocusGrabV1>(pMgr->client(), pMgr->version(), id)));
+ const auto RESOURCE = m_vGrabs.back().get();
+
+ if (!RESOURCE->good()) {
+ pMgr->noMemory();
+ m_vGrabs.pop_back();
+ }
+}
diff --git a/src/protocols/FocusGrab.hpp b/src/protocols/FocusGrab.hpp
new file mode 100644
index 00000000..11c26774
--- /dev/null
+++ b/src/protocols/FocusGrab.hpp
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "WaylandProtocol.hpp"
+#include "hyprland-focus-grab-v1.hpp"
+#include "macros.hpp"
+#include <cstdint>
+#include <unordered_map>
+#include <vector>
+
+class CFocusGrab;
+
+class CFocusGrabSurfaceState {
+ public:
+ CFocusGrabSurfaceState(CFocusGrab* grab, wlr_surface* surface);
+ ~CFocusGrabSurfaceState();
+
+ enum State {
+ PendingAddition,
+ PendingRemoval,
+ Comitted,
+ } state = PendingAddition;
+
+ private:
+ DYNLISTENER(surfaceDestroy);
+};
+
+class CFocusGrab {
+ public:
+ CFocusGrab(SP<CHyprlandFocusGrabV1> resource_);
+ ~CFocusGrab();
+
+ bool good();
+ bool isSurfaceComitted(wlr_surface* surface);
+
+ void start();
+ void finish(bool sendCleared);
+
+ private:
+ void addSurface(wlr_surface* surface);
+ void removeSurface(wlr_surface* surface);
+ void eraseSurface(wlr_surface* surface);
+ void refocusKeyboard();
+ void commit();
+
+ SP<CHyprlandFocusGrabV1> resource;
+ std::unordered_map<wlr_surface*, UP<CFocusGrabSurfaceState>> m_mSurfaces;
+ wlr_seat_pointer_grab m_sPointerGrab;
+ wlr_seat_keyboard_grab m_sKeyboardGrab;
+ wlr_seat_touch_grab m_sTouchGrab;
+ bool m_bGrabActive = false;
+
+ DYNLISTENER(pointerGrabStarted);
+ DYNLISTENER(keyboardGrabStarted);
+ DYNLISTENER(touchGrabStarted);
+ friend class CFocusGrabSurfaceState;
+};
+
+class CFocusGrabProtocol : public IWaylandProtocol {
+ public:
+ CFocusGrabProtocol(const wl_interface* iface, const int& var, const std::string& name);
+
+ virtual void bindManager(wl_client* client, void* data, uint32_t ver, uint32_t id);
+
+ private:
+ void onManagerResourceDestroy(wl_resource* res);
+ void destroyGrab(CFocusGrab* grab);
+ void onCreateGrab(CHyprlandFocusGrabManagerV1* pMgr, uint32_t id);
+
+ std::vector<UP<CHyprlandFocusGrabManagerV1>> m_vManagers;
+ std::vector<UP<CFocusGrab>> m_vGrabs;
+
+ friend class CFocusGrab;
+};
+
+namespace PROTO {
+ inline UP<CFocusGrabProtocol> focusGrab;
+}
diff --git a/subprojects/hyprland-protocols b/subprojects/hyprland-protocols
-Subproject 0c2ce70625cb30aef199cb388f99e19a61a6ce0
+Subproject e06482e0e611130cd1929f75e8c1cf679e57d16