diff --git a/CMakeLists.txt b/CMakeLists.txt index f2939040..892160a4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -280,6 +280,7 @@ protocolNew("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml" " protocolNew("staging/xdg-activation/xdg-activation-v1.xml" "xdg-activation-v1" false) protocolNew("staging/ext-idle-notify/ext-idle-notify-v1.xml" "ext-idle-notify-v1" false) protocolNew("staging/ext-session-lock/ext-session-lock-v1.xml" "ext-session-lock-v1" false) +protocolNew("subprojects/hyprland-protocols/protocols/hyprland-focus-grab-v1.xml" "hyprland-focus-grab-v1" true) # tools add_subdirectory(hyprctl) diff --git a/protocols/meson.build b/protocols/meson.build index 38b973af..2b85e410 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -57,6 +57,7 @@ new_protocols = [ [wl_protocol_dir, 'staging/xdg-activation/xdg-activation-v1.xml'], [wl_protocol_dir, 'staging/ext-idle-notify/ext-idle-notify-v1.xml'], [wl_protocol_dir, 'staging/ext-session-lock/ext-session-lock-v1.xml'], + [hl_protocol_dir, 'protocols/hyprland-focus-grab-v1.xml'], ] wl_protos_src = [] diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 8ec2a35f..771e0130 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -22,6 +22,7 @@ #include "../protocols/InputMethodV2.hpp" #include "../protocols/VirtualKeyboard.hpp" #include "../protocols/VirtualPointer.hpp" +#include "../protocols/FocusGrab.hpp" CProtocolManager::CProtocolManager() { @@ -47,6 +48,7 @@ CProtocolManager::CProtocolManager() { PROTO::ime = std::make_unique(&zwp_input_method_manager_v2_interface, 1, "IMEv2"); PROTO::virtualKeyboard = std::make_unique(&zwp_virtual_keyboard_manager_v1_interface, 1, "VirtualKeyboard"); PROTO::virtualPointer = std::make_unique(&zwlr_virtual_pointer_manager_v1_interface, 2, "VirtualPointer"); + 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..48c1ef5a --- /dev/null +++ b/src/protocols/FocusGrab.cpp @@ -0,0 +1,307 @@ +#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); + } else { + // the last grabbed window should retain keybaord 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) { this->addSurface(wlr_surface_from_resource(surface)); }); + resource->setRemoveSurface([this](CHyprlandFocusGrabV1* pMgr, wl_resource* surface) { this->removeSurface(wlr_surface_from_resource(surface)); }); + resource->setCommit([this](CHyprlandFocusGrabV1* pMgr) { this->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*) { this->finish(true); }, this, "CFocusGrab"); + + hyprListener_keyboardGrabStarted.initCallback( + &g_pCompositor->m_sSeat.seat->events.keyboard_grab_begin, [this](void*, void*) { this->finish(true); }, this, "CFocusGrab"); + + hyprListener_touchGrabStarted.initCallback( + &g_pCompositor->m_sSeat.seat->events.touch_grab_begin, [this](void*, void*) { this->finish(true); }, this, "CFocusGrab"); + } + + // Ensure new surfaces are focused if under the mouse when comitted. + g_pInputManager->refocus(); +} + +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 == &this->m_sPointerGrab) { + wlr_seat_pointer_end_grab(g_pCompositor->m_sSeat.seat); + hadGrab = true; + } + + if (g_pCompositor->m_sSeat.seat->keyboard_state.grab == &this->m_sKeyboardGrab) { + wlr_seat_keyboard_end_grab(g_pCompositor->m_sSeat.seat); + hadGrab = true; + } + + if (g_pCompositor->m_sSeat.seat->touch_state.grab == &this->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::commit() { + auto surfacesChanged = 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; + break; + default: break; + } + + iter++; + } + + if (surfacesChanged) { + if (!m_mSurfaces.empty()) { + start(); + } else { + finish(false); + } + } +} + +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) { this->onManagerResourceDestroy(p->resource()); }); + RESOURCE->setCreateGrab([this](CHyprlandFocusGrabManagerV1* pMgr, uint32_t id) { this->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..7a902115 --- /dev/null +++ b/src/protocols/FocusGrab.hpp @@ -0,0 +1,76 @@ +#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 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..8af1ce2d 160000 --- a/subprojects/hyprland-protocols +++ b/subprojects/hyprland-protocols @@ -1 +1 @@ -Subproject commit 0c2ce70625cb30aef199cb388f99e19a61a6ce03 +Subproject commit 8af1ce2df94fbb58120a03f9ea1a659cfa115f61