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(&zwlr_virtual_pointer_manager_v1_interface, 2, "VirtualPointer"); PROTO::outputManagement = std::make_unique(&zwlr_output_manager_v1_interface, 4, "OutputManagement"); PROTO::serverDecorationKDE = std::make_unique(&org_kde_kwin_server_decoration_manager_interface, 1, "ServerDecorationKDE"); + PROTO::focusGrab = std::make_unique(&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 +#include +#include +#include +#include + +static void focus_grab_pointer_enter(wlr_seat_pointer_grab* grab, wlr_surface* surface, double sx, double sy) { + if (static_cast(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(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(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(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(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(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(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(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 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(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(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(std::make_shared(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 +#include +#include + +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 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 resource; + std::unordered_map> 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> m_vManagers; + std::vector> m_vGrabs; + + friend class CFocusGrab; +}; + +namespace PROTO { + inline UP focusGrab; +} diff --git a/subprojects/hyprland-protocols b/subprojects/hyprland-protocols index 0c2ce706..e06482e0 160000 --- a/subprojects/hyprland-protocols +++ b/subprojects/hyprland-protocols @@ -1 +1 @@ -Subproject commit 0c2ce70625cb30aef199cb388f99e19a61a6ce03 +Subproject commit e06482e0e611130cd1929f75e8c1cf679e57d161