diff --git a/CMakeLists.txt b/CMakeLists.txt index dfda4d41560..16419981b21 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,7 @@ pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-server wayland-client wayland-cursor wayland-protocols cairo pango pangocairo pixman-1 libdrm libinput hwdata libseat libdisplay-info libliftoff libudev gbm - hyprwayland-scanner>=0.3.1 hyprlang>=0.3.2 hyprcursor>=0.1.7 + hyprwayland-scanner>=0.3.2 hyprlang>=0.3.2 hyprcursor>=0.1.7 ) file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") @@ -250,7 +250,6 @@ target_link_libraries(Hyprland ) protocol("protocols/idle.xml" "idle" true) -protocol("protocols/pointer-constraints-unstable-v1.xml" "pointer-constraints-unstable-v1" true) protocol("protocols/tablet-unstable-v2.xml" "tablet-unstable-v2" true) protocol("protocols/wlr-layer-shell-unstable-v1.xml" "wlr-layer-shell-unstable-v1" true) protocol("protocols/wlr-output-power-management-unstable-v1.xml" "wlr-output-power-management-unstable-v1" true) @@ -275,6 +274,7 @@ protocolNew("staging/ext-foreign-toplevel-list/ext-foreign-toplevel-list-v1.xml" protocolNew("unstable/pointer-gestures/pointer-gestures-unstable-v1.xml" "pointer-gestures-unstable-v1" false) protocolNew("unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml" "keyboard-shortcuts-inhibit-unstable-v1" false) protocolNew("unstable/text-input/text-input-unstable-v3.xml" "text-input-unstable-v3" false) +protocolNew("unstable/pointer-constraints/pointer-constraints-unstable-v1.xml" "pointer-constraints-unstable-v1" false) # tools add_subdirectory(hyprctl) diff --git a/flake.lock b/flake.lock index 3dbdf755b08..04b4f6f821e 100644 --- a/flake.lock +++ b/flake.lock @@ -82,11 +82,11 @@ ] }, "locked": { - "lastModified": 1713989318, - "narHash": "sha256-WSsEQQxZQ+bsAWRhi1iXvP8sxgRyNtY3X1V3CfFdP5Q=", + "lastModified": 1714171579, + "narHash": "sha256-eaWDIvt8ufUKKz3Lc2a3PyemLJG1m9RYlF+HP3hWbaw=", "owner": "hyprwm", "repo": "hyprwayland-scanner", - "rev": "1cfe2d26a82ce794fd33ec06fa022e68501c5a45", + "rev": "126dad854f22fe30e6b82cd21808e76903d90ac5", "type": "github" }, "original": { diff --git a/protocols/meson.build b/protocols/meson.build index 7556fffca02..d5f2af6456d 100644 --- a/protocols/meson.build +++ b/protocols/meson.build @@ -30,7 +30,6 @@ protocols = [ ['wlr-layer-shell-unstable-v1.xml'], ['wlr-output-power-management-unstable-v1.xml'], ['wlr-screencopy-unstable-v1.xml'], - ['pointer-constraints-unstable-v1.xml'], ['tablet-unstable-v2.xml'], ['idle.xml'], [hl_protocol_dir, 'protocols/hyprland-toplevel-export-v1.xml'], @@ -52,6 +51,7 @@ new_protocols = [ [wl_protocol_dir, 'unstable/pointer-gestures/pointer-gestures-unstable-v1.xml'], [wl_protocol_dir, 'unstable/keyboard-shortcuts-inhibit/keyboard-shortcuts-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/text-input/text-input-unstable-v3.xml'], + [wl_protocol_dir, 'unstable/pointer-constraints/pointer-constraints-unstable-v1.xml'], ] wl_protos_src = [] diff --git a/protocols/pointer-constraints-unstable-v1.xml b/protocols/pointer-constraints-unstable-v1.xml deleted file mode 100644 index efd64b6603c..00000000000 --- a/protocols/pointer-constraints-unstable-v1.xml +++ /dev/null @@ -1,339 +0,0 @@ - - - - - Copyright © 2014 Jonas Ådahl - Copyright © 2015 Red Hat Inc. - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice (including the next - paragraph) shall be included in all copies or substantial portions of the - Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - - - - This protocol specifies a set of interfaces used for adding constraints to - the motion of a pointer. Possible constraints include confining pointer - motions to a given region, or locking it to its current position. - - In order to constrain the pointer, a client must first bind the global - interface "wp_pointer_constraints" which, if a compositor supports pointer - constraints, is exposed by the registry. Using the bound global object, the - client uses the request that corresponds to the type of constraint it wants - to make. See wp_pointer_constraints for more details. - - Warning! The protocol described in this file is experimental and backward - incompatible changes may be made. Backward compatible changes may be added - together with the corresponding interface version bump. Backward - incompatible changes are done by bumping the version number in the protocol - and interface names and resetting the interface version. Once the protocol - is to be declared stable, the 'z' prefix and the version number in the - protocol and interface names are removed and the interface version number is - reset. - - - - - The global interface exposing pointer constraining functionality. It - exposes two requests: lock_pointer for locking the pointer to its - position, and confine_pointer for locking the pointer to a region. - - The lock_pointer and confine_pointer requests create the objects - wp_locked_pointer and wp_confined_pointer respectively, and the client can - use these objects to interact with the lock. - - For any surface, only one lock or confinement may be active across all - wl_pointer objects of the same seat. If a lock or confinement is requested - when another lock or confinement is active or requested on the same surface - and with any of the wl_pointer objects of the same seat, an - 'already_constrained' error will be raised. - - - - - These errors can be emitted in response to wp_pointer_constraints - requests. - - - - - - - These values represent different lifetime semantics. They are passed - as arguments to the factory requests to specify how the constraint - lifetimes should be managed. - - - - A oneshot pointer constraint will never reactivate once it has been - deactivated. See the corresponding deactivation event - (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for - details. - - - - - A persistent pointer constraint may again reactivate once it has - been deactivated. See the corresponding deactivation event - (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for - details. - - - - - - - Used by the client to notify the server that it will no longer use this - pointer constraints object. - - - - - - The lock_pointer request lets the client request to disable movements of - the virtual pointer (i.e. the cursor), effectively locking the pointer - to a position. This request may not take effect immediately; in the - future, when the compositor deems implementation-specific constraints - are satisfied, the pointer lock will be activated and the compositor - sends a locked event. - - The protocol provides no guarantee that the constraints are ever - satisfied, and does not require the compositor to send an error if the - constraints cannot ever be satisfied. It is thus possible to request a - lock that will never activate. - - There may not be another pointer constraint of any kind requested or - active on the surface for any of the wl_pointer objects of the seat of - the passed pointer when requesting a lock. If there is, an error will be - raised. See general pointer lock documentation for more details. - - The intersection of the region passed with this request and the input - region of the surface is used to determine where the pointer must be - in order for the lock to activate. It is up to the compositor whether to - warp the pointer or require some kind of user interaction for the lock - to activate. If the region is null the surface input region is used. - - A surface may receive pointer focus without the lock being activated. - - The request creates a new object wp_locked_pointer which is used to - interact with the lock as well as receive updates about its state. See - the the description of wp_locked_pointer for further information. - - Note that while a pointer is locked, the wl_pointer objects of the - corresponding seat will not emit any wl_pointer.motion events, but - relative motion events will still be emitted via wp_relative_pointer - objects of the same seat. wl_pointer.axis and wl_pointer.button events - are unaffected. - - - - - - - - - - - The confine_pointer request lets the client request to confine the - pointer cursor to a given region. This request may not take effect - immediately; in the future, when the compositor deems implementation- - specific constraints are satisfied, the pointer confinement will be - activated and the compositor sends a confined event. - - The intersection of the region passed with this request and the input - region of the surface is used to determine where the pointer must be - in order for the confinement to activate. It is up to the compositor - whether to warp the pointer or require some kind of user interaction for - the confinement to activate. If the region is null the surface input - region is used. - - The request will create a new object wp_confined_pointer which is used - to interact with the confinement as well as receive updates about its - state. See the the description of wp_confined_pointer for further - information. - - - - - - - - - - - - The wp_locked_pointer interface represents a locked pointer state. - - While the lock of this object is active, the wl_pointer objects of the - associated seat will not emit any wl_pointer.motion events. - - This object will send the event 'locked' when the lock is activated. - Whenever the lock is activated, it is guaranteed that the locked surface - will already have received pointer focus and that the pointer will be - within the region passed to the request creating this object. - - To unlock the pointer, send the destroy request. This will also destroy - the wp_locked_pointer object. - - If the compositor decides to unlock the pointer the unlocked event is - sent. See wp_locked_pointer.unlock for details. - - When unlocking, the compositor may warp the cursor position to the set - cursor position hint. If it does, it will not result in any relative - motion events emitted via wp_relative_pointer. - - If the surface the lock was requested on is destroyed and the lock is not - yet activated, the wp_locked_pointer object is now defunct and must be - destroyed. - - - - - Destroy the locked pointer object. If applicable, the compositor will - unlock the pointer. - - - - - - Set the cursor position hint relative to the top left corner of the - surface. - - If the client is drawing its own cursor, it should update the position - hint to the position of its own cursor. A compositor may use this - information to warp the pointer upon unlock in order to avoid pointer - jumps. - - The cursor position hint is double buffered. The new hint will only take - effect when the associated surface gets it pending state applied. See - wl_surface.commit for details. - - - - - - - - Set a new region used to lock the pointer. - - The new lock region is double-buffered. The new lock region will - only take effect when the associated surface gets its pending state - applied. See wl_surface.commit for details. - - For details about the lock region, see wp_locked_pointer. - - - - - - - Notification that the pointer lock of the seat's pointer is activated. - - - - - - Notification that the pointer lock of the seat's pointer is no longer - active. If this is a oneshot pointer lock (see - wp_pointer_constraints.lifetime) this object is now defunct and should - be destroyed. If this is a persistent pointer lock (see - wp_pointer_constraints.lifetime) this pointer lock may again - reactivate in the future. - - - - - - - The wp_confined_pointer interface represents a confined pointer state. - - This object will send the event 'confined' when the confinement is - activated. Whenever the confinement is activated, it is guaranteed that - the surface the pointer is confined to will already have received pointer - focus and that the pointer will be within the region passed to the request - creating this object. It is up to the compositor to decide whether this - requires some user interaction and if the pointer will warp to within the - passed region if outside. - - To unconfine the pointer, send the destroy request. This will also destroy - the wp_confined_pointer object. - - If the compositor decides to unconfine the pointer the unconfined event is - sent. The wp_confined_pointer object is at this point defunct and should - be destroyed. - - - - - Destroy the confined pointer object. If applicable, the compositor will - unconfine the pointer. - - - - - - Set a new region used to confine the pointer. - - The new confine region is double-buffered. The new confine region will - only take effect when the associated surface gets its pending state - applied. See wl_surface.commit for details. - - If the confinement is active when the new confinement region is applied - and the pointer ends up outside of newly applied region, the pointer may - warped to a position within the new confinement region. If warped, a - wl_pointer.motion event will be emitted, but no - wp_relative_pointer.relative_motion event. - - The compositor may also, instead of using the new region, unconfine the - pointer. - - For details about the confine region, see wp_confined_pointer. - - - - - - - Notification that the pointer confinement of the seat's pointer is - activated. - - - - - - Notification that the pointer confinement of the seat's pointer is no - longer active. If this is a oneshot pointer confinement (see - wp_pointer_constraints.lifetime) this object is now defunct and should - be destroyed. If this is a persistent pointer confinement (see - wp_pointer_constraints.lifetime) this pointer confinement may again - reactivate in the future. - - - - - diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 3d441a95b69..3e9cf24bfc9 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -14,6 +14,7 @@ #include #include "helpers/VarList.hpp" #include "protocols/FractionalScale.hpp" +#include "protocols/PointerConstraints.hpp" int handleCritSignal(int signo, void* data) { Debug::log(LOG, "Hyprland received signal {}", signo); @@ -211,8 +212,6 @@ void CCompositor::initServer() { m_sWLROutputMgr = wlr_output_manager_v1_create(m_sWLDisplay); - m_sWLRPointerConstraints = wlr_pointer_constraints_v1_create(m_sWLDisplay); - m_sWLRVKeyboardMgr = wlr_virtual_keyboard_manager_v1_create(m_sWLDisplay); m_sWLRVirtPtrMgr = wlr_virtual_pointer_manager_v1_create(m_sWLDisplay); @@ -281,7 +280,6 @@ void CCompositor::initAllSignals() { addWLSignal(&m_sWLROutputLayout->events.change, &Events::listen_change, m_sWLROutputLayout, "OutputLayout"); addWLSignal(&m_sWLROutputMgr->events.apply, &Events::listen_outputMgrApply, m_sWLROutputMgr, "OutputMgr"); addWLSignal(&m_sWLROutputMgr->events.test, &Events::listen_outputMgrTest, m_sWLROutputMgr, "OutputMgr"); - addWLSignal(&m_sWLRPointerConstraints->events.new_constraint, &Events::listen_newConstraint, m_sWLRPointerConstraints, "PointerConstraints"); addWLSignal(&m_sWLRVirtPtrMgr->events.new_virtual_pointer, &Events::listen_newVirtPtr, m_sWLRVirtPtrMgr, "VirtPtrMgr"); addWLSignal(&m_sWLRVKeyboardMgr->events.new_virtual_keyboard, &Events::listen_newVirtualKeyboard, m_sWLRVKeyboardMgr, "VKeyboardMgr"); addWLSignal(&m_sWLRRenderer->events.destroy, &Events::listen_RendererDestroy, m_sWLRRenderer, "WLRRenderer"); @@ -328,7 +326,6 @@ void CCompositor::removeAllSignals() { removeWLSignal(&Events::listen_change); removeWLSignal(&Events::listen_outputMgrApply); removeWLSignal(&Events::listen_outputMgrTest); - removeWLSignal(&Events::listen_newConstraint); removeWLSignal(&Events::listen_newVirtPtr); removeWLSignal(&Events::listen_newVirtualKeyboard); removeWLSignal(&Events::listen_RendererDestroy); diff --git a/src/Compositor.hpp b/src/Compositor.hpp index c0845664627..34086466b18 100644 --- a/src/Compositor.hpp +++ b/src/Compositor.hpp @@ -62,7 +62,6 @@ class CCompositor { wlr_presentation* m_sWLRPresentation; wlr_egl* m_sWLREGL; int m_iDRMFD; - wlr_pointer_constraints_v1* m_sWLRPointerConstraints; wlr_server_decoration_manager* m_sWLRServerDecoMgr; wlr_virtual_pointer_manager_v1* m_sWLRVirtPtrMgr; wlr_tablet_manager_v2* m_sWLRTabletManager; diff --git a/src/desktop/Constraint.cpp b/src/desktop/Constraint.cpp deleted file mode 100644 index 428c87452fe..00000000000 --- a/src/desktop/Constraint.cpp +++ /dev/null @@ -1,142 +0,0 @@ -#include "Constraint.hpp" -#include "WLSurface.hpp" -#include "../Compositor.hpp" -#include "../config/ConfigValue.hpp" - -CConstraint::CConstraint(wlr_pointer_constraint_v1* constraint, CWLSurface* owner) : m_pOwner(owner), m_pConstraint(constraint) { - RASSERT(!constraint->data, "CConstraint: attempted to duplicate ownership"); - - constraint->data = this; - initSignals(); - - m_vCursorPosOnActivate = g_pInputManager->getMouseCoordsInternal(); - - g_pInputManager->m_vConstraints.push_back(this); - - if (g_pCompositor->m_pLastFocus == m_pOwner->wlr()) - activate(); -} - -CConstraint::~CConstraint() { - std::erase(g_pInputManager->m_vConstraints, this); -} - -static void onConstraintDestroy(void* owner, void* data) { - const auto CONSTRAINT = (CConstraint*)owner; - CONSTRAINT->onDestroy(); -} - -static void onConstraintSetRegion(void* owner, void* data) { - const auto CONSTRAINT = (CConstraint*)owner; - CONSTRAINT->onSetRegion(); -} - -void CConstraint::initSignals() { - hyprListener_setConstraintRegion.initCallback(&m_pConstraint->events.set_region, ::onConstraintSetRegion, this, "CConstraint"); - hyprListener_destroyConstraint.initCallback(&m_pConstraint->events.destroy, ::onConstraintDestroy, this, "CConstraint"); -} - -void CConstraint::onDestroy() { - hyprListener_setConstraintRegion.removeCallback(); - hyprListener_destroyConstraint.removeCallback(); - - if (active() && isLocked()) - g_pCompositor->warpCursorTo(logicPositionHint(), true); - - // this is us - m_pOwner->m_pConstraint.reset(); -} - -void CConstraint::onSetRegion() { - if (!m_bActive) - return; - - m_rRegion.set(&m_pConstraint->region); - m_vPositionHint = m_rRegion.closestPoint(m_vPositionHint); - g_pInputManager->simulateMouseMovement(); // to warp the cursor if anything's amiss -} - -void CConstraint::onCommit() { - if (!m_bActive) - return; - - const auto COMMITTED = m_pConstraint->current.committed; - - if (COMMITTED & WLR_POINTER_CONSTRAINT_V1_STATE_CURSOR_HINT) { - static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); - - m_bHintSet = true; - - float scale = 1.f; - const auto PWINDOW = m_pOwner->getWindow(); - if (PWINDOW) { - const auto ISXWL = PWINDOW->m_bIsX11; - scale = ISXWL && *PXWLFORCESCALEZERO ? PWINDOW->m_fX11SurfaceScaledBy : 1.f; - } - - m_vPositionHint = {m_pConstraint->current.cursor_hint.x / scale, m_pConstraint->current.cursor_hint.y / scale}; - g_pInputManager->simulateMouseMovement(); - } - - if (COMMITTED & WLR_POINTER_CONSTRAINT_V1_STATE_REGION) - onSetRegion(); -} - -CRegion CConstraint::logicConstraintRegion() { - CRegion rg = m_rRegion; - const auto SURFBOX = m_pOwner->getSurfaceBoxGlobal(); - const auto CONSTRAINTPOS = SURFBOX.has_value() ? SURFBOX->pos() : Vector2D{}; - rg.translate(CONSTRAINTPOS); - return rg; -} - -CWLSurface* CConstraint::owner() { - return m_pOwner; -} - -bool CConstraint::isLocked() { - return m_pConstraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED; -} - -Vector2D CConstraint::logicPositionHint() { - const auto SURFBOX = m_pOwner->getSurfaceBoxGlobal(); - const auto CONSTRAINTPOS = SURFBOX.has_value() ? SURFBOX->pos() : Vector2D{}; - - return m_bHintSet ? CONSTRAINTPOS + m_vPositionHint : m_vCursorPosOnActivate; -} - -void CConstraint::deactivate() { - if (!m_bActive) - return; - - m_bActive = false; - - if (isLocked()) - g_pCompositor->warpCursorTo(logicPositionHint(), true); - - if (m_pConstraint->lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) - m_bDead = true; - - wlr_pointer_constraint_v1_send_deactivated(m_pConstraint); -} - -void CConstraint::activate() { - if (m_bActive || m_bDead) - return; - - m_bActive = true; - - // TODO: hack, probably not a super duper great idea - if (g_pCompositor->m_sSeat.seat->pointer_state.focused_surface != m_pOwner->wlr()) { - const auto SURFBOX = m_pOwner->getSurfaceBoxGlobal(); - const auto LOCAL = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{}; - wlr_seat_pointer_enter(g_pCompositor->m_sSeat.seat, m_pOwner->wlr(), LOCAL.x, LOCAL.y); - } - - g_pCompositor->warpCursorTo(logicPositionHint(), true); - wlr_pointer_constraint_v1_send_activated(m_pConstraint); -} - -bool CConstraint::active() { - return m_bActive; -} diff --git a/src/desktop/Constraint.hpp b/src/desktop/Constraint.hpp deleted file mode 100644 index 34690217f6c..00000000000 --- a/src/desktop/Constraint.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include "../includes.hpp" -#include "../helpers/Region.hpp" -#include "../helpers/WLListener.hpp" - -class CWLSurface; - -class CConstraint { - public: - CConstraint(wlr_pointer_constraint_v1* constraint, CWLSurface* owner); - ~CConstraint(); - - void onCommit(); - void onDestroy(); - void onSetRegion(); - CRegion logicConstraintRegion(); - bool isLocked(); - Vector2D logicPositionHint(); - - void deactivate(); - void activate(); - bool active(); - - CWLSurface* owner(); - - private: - bool m_bActive = false; - CWLSurface* m_pOwner = nullptr; - wlr_pointer_constraint_v1* m_pConstraint; - - CRegion m_rRegion; - bool m_bHintSet = false; - Vector2D m_vPositionHint = {-1, -1}; - Vector2D m_vCursorPosOnActivate = {-1, -1}; - - // for oneshot constraints that have been activated once - bool m_bDead = false; - - DYNLISTENER(destroyConstraint); - DYNLISTENER(setConstraintRegion); - - void initSignals(); -}; \ No newline at end of file diff --git a/src/desktop/WLSurface.cpp b/src/desktop/WLSurface.cpp index b102c5ce229..4c2f6c84521 100644 --- a/src/desktop/WLSurface.cpp +++ b/src/desktop/WLSurface.cpp @@ -103,6 +103,8 @@ void CWLSurface::destroy() { if (!m_pWLRSurface) return; + events.destroy.emit(); + m_pConstraint.reset(); hyprListener_destroy.removeCallback(); @@ -182,15 +184,14 @@ std::optional CWLSurface::getSurfaceBoxGlobal() { return {}; } -void CWLSurface::appendConstraint(wlr_pointer_constraint_v1* constraint) { - m_pConstraint = std::make_unique(constraint, this); +void CWLSurface::appendConstraint(std::weak_ptr constraint) { + m_pConstraint = constraint; } void CWLSurface::onCommit() { - if (m_pConstraint) - m_pConstraint->onCommit(); + ; } -CConstraint* CWLSurface::constraint() { - return m_pConstraint.get(); +std::shared_ptr CWLSurface::constraint() { + return m_pConstraint.lock(); } diff --git a/src/desktop/WLSurface.hpp b/src/desktop/WLSurface.hpp index a47b2bddc8e..d018d11f63d 100644 --- a/src/desktop/WLSurface.hpp +++ b/src/desktop/WLSurface.hpp @@ -2,12 +2,13 @@ #include "../defines.hpp" #include "../helpers/Region.hpp" -#include "Constraint.hpp" +#include "../helpers/signal/Signal.hpp" class CWindow; struct SLayerSurface; class CSubsurface; class CPopup; +class CPointerConstraint; class CWLSurface { public: @@ -42,9 +43,9 @@ class CWLSurface { CSubsurface* getSubsurface(); // desktop components misc utils - std::optional getSurfaceBoxGlobal(); - void appendConstraint(wlr_pointer_constraint_v1* constraint); - CConstraint* constraint(); + std::optional getSurfaceBoxGlobal(); + void appendConstraint(std::weak_ptr constraint); + std::shared_ptr constraint(); // allow stretching. Useful for plugins. bool m_bFillIgnoreSmall = false; @@ -84,6 +85,10 @@ class CWLSurface { // used by the alpha-modifier protocol float m_pAlphaModifier = 1.F; + struct { + CSignal destroy; + } events; + private: bool m_bInert = true; @@ -95,14 +100,14 @@ class CWLSurface { CSubsurface* m_pSubsurfaceOwner = nullptr; // - std::unique_ptr m_pConstraint; + std::weak_ptr m_pConstraint; - void destroy(); - void init(); - bool desktopComponent(); + void destroy(); + void init(); + bool desktopComponent(); DYNLISTENER(destroy); DYNLISTENER(commit); - friend class CConstraint; + friend class CPointerConstraint; }; \ No newline at end of file diff --git a/src/events/Devices.cpp b/src/events/Devices.cpp index de5de7f8aa1..43433ba2298 100644 --- a/src/events/Devices.cpp +++ b/src/events/Devices.cpp @@ -93,21 +93,6 @@ void Events::listener_newInput(wl_listener* listener, void* data) { g_pInputManager->updateCapabilities(); } -void Events::listener_newConstraint(wl_listener* listener, void* data) { - const auto PCONSTRAINT = (wlr_pointer_constraint_v1*)data; - - Debug::log(LOG, "New mouse constraint at {:x}", (uintptr_t)PCONSTRAINT); - - const auto SURFACE = CWLSurface::surfaceFromWlr(PCONSTRAINT->surface); - - if (!SURFACE) { - Debug::log(ERR, "Refusing a constraint from an unassigned wl_surface {:x}", (uintptr_t)PCONSTRAINT->surface); - return; - } - - SURFACE->appendConstraint(PCONSTRAINT); -} - void Events::listener_newVirtPtr(wl_listener* listener, void* data) { const auto EV = (wlr_virtual_pointer_v1_new_pointer_event*)data; const auto POINTER = EV->new_pointer; diff --git a/src/events/Events.hpp b/src/events/Events.hpp index 69ca82d035c..2f52f11d472 100644 --- a/src/events/Events.hpp +++ b/src/events/Events.hpp @@ -65,8 +65,6 @@ namespace Events { DYNLISTENFUNC(keyboardMod); DYNLISTENFUNC(keyboardDestroy); - LISTENER(newConstraint); - // Various LISTENER(requestMouse); LISTENER(requestSetSel); diff --git a/src/helpers/Region.cpp b/src/helpers/Region.cpp index 502bc19869e..bcbf672dde4 100644 --- a/src/helpers/Region.cpp +++ b/src/helpers/Region.cpp @@ -8,7 +8,7 @@ CRegion::CRegion() { pixman_region32_init(&m_rRegion); } -CRegion::CRegion(pixman_region32_t* ref) { +CRegion::CRegion(const pixman_region32_t* const ref) { pixman_region32_init(&m_rRegion); pixman_region32_copy(&m_rRegion, ref); } diff --git a/src/helpers/Region.hpp b/src/helpers/Region.hpp index 7ae334f7e1e..27f460f455c 100644 --- a/src/helpers/Region.hpp +++ b/src/helpers/Region.hpp @@ -11,7 +11,7 @@ class CRegion { /* Create an empty region */ CRegion(); /* Create from a reference. Copies, does not own. */ - CRegion(pixman_region32_t* ref); + CRegion(const pixman_region32_t* const ref); /* Create from a box */ CRegion(double x, double y, double w, double h); /* Create from a wlr_box */ diff --git a/src/includes.hpp b/src/includes.hpp index 1935a268dfd..7f679f978a7 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -80,7 +80,6 @@ extern "C" { #include #include #include -#include #include #include #include diff --git a/src/managers/ProtocolManager.cpp b/src/managers/ProtocolManager.cpp index 7d93d46a036..cffc6c83a13 100644 --- a/src/managers/ProtocolManager.cpp +++ b/src/managers/ProtocolManager.cpp @@ -14,6 +14,7 @@ #include "../protocols/ForeignToplevelWlr.hpp" #include "../protocols/ShortcutsInhibit.hpp" #include "../protocols/TextInputV3.hpp" +#include "../protocols/PointerConstraints.hpp" #include "tearing-control-v1.hpp" #include "fractional-scale-v1.hpp" @@ -29,6 +30,7 @@ #include "wlr-foreign-toplevel-management-unstable-v1.hpp" #include "keyboard-shortcuts-inhibit-unstable-v1.hpp" #include "text-input-unstable-v3.hpp" +#include "pointer-constraints-unstable-v1.hpp" CProtocolManager::CProtocolManager() { @@ -46,6 +48,7 @@ CProtocolManager::CProtocolManager() { PROTO::foreignToplevelWlr = std::make_unique(&zwlr_foreign_toplevel_manager_v1_interface, 3, "ForeignToplevelWlr"); PROTO::shortcutsInhibit = std::make_unique(&zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1, "ShortcutsInhibit"); PROTO::textInputV3 = std::make_unique(&zwp_text_input_manager_v3_interface, 1, "TextInputV3"); + PROTO::constraints = std::make_unique(&zwp_pointer_constraints_v1_interface, 1, "PointerConstraints"); // Old protocol implementations. // TODO: rewrite them to use hyprwayland-scanner. diff --git a/src/managers/input/InputManager.cpp b/src/managers/input/InputManager.cpp index b5d6bb5c331..b930838b954 100644 --- a/src/managers/input/InputManager.cpp +++ b/src/managers/input/InputManager.cpp @@ -7,6 +7,7 @@ #include "../../protocols/CursorShape.hpp" #include "../../protocols/IdleInhibit.hpp" #include "../../protocols/RelativePointer.hpp" +#include "../../protocols/PointerConstraints.hpp" CInputManager::CInputManager() { m_sListeners.setCursorShape = PROTO::cursorShape->events.setShape.registerListener([this](std::any data) { @@ -193,7 +194,7 @@ void CInputManager::mouseMoveUnified(uint32_t time, bool refocus) { return; } else - Debug::log(ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", (uintptr_t)SURF, (uintptr_t)CONSTRAINT); + Debug::log(ERR, "BUG THIS: Null SURF/CONSTRAINT in mouse refocus. Ignoring constraints. {:x} {:x}", (uintptr_t)SURF, (uintptr_t)CONSTRAINT.get()); } // update stuff @@ -1298,16 +1299,26 @@ void CInputManager::unconstrainMouse() { return; for (auto& c : m_vConstraints) { - if (!c->active()) + const auto C = c.lock(); + + if (!C) + continue; + + if (!C->isActive()) continue; - c->deactivate(); + C->deactivate(); } } bool CInputManager::isConstrained() { for (auto& c : m_vConstraints) { - if (!c->active() || c->owner()->wlr() != g_pCompositor->m_pLastFocus) + const auto C = c.lock(); + + if (!C) + continue; + + if (!C->isActive() || C->owner()->wlr() != g_pCompositor->m_pLastFocus) continue; return true; diff --git a/src/managers/input/InputManager.hpp b/src/managers/input/InputManager.hpp index bb5d1cf74b2..2795d41004d 100644 --- a/src/managers/input/InputManager.hpp +++ b/src/managers/input/InputManager.hpp @@ -8,7 +8,7 @@ #include "InputMethodRelay.hpp" #include "../../helpers/signal/Listener.hpp" -class CConstraint; +class CPointerConstraint; class CWindow; class CIdleInhibitor; @@ -141,27 +141,28 @@ class CInputManager { std::deque m_dExclusiveLSes; // constraints - std::vector m_vConstraints; + std::vector> m_vConstraints; - void newTabletTool(wlr_input_device*); - void newTabletPad(wlr_input_device*); - void focusTablet(STablet*, wlr_tablet_tool*, bool motion = false); - void newIdleInhibitor(std::any); - void recheckIdleInhibitorStatus(); + // + void newTabletTool(wlr_input_device*); + void newTabletPad(wlr_input_device*); + void focusTablet(STablet*, wlr_tablet_tool*, bool motion = false); + void newIdleInhibitor(std::any); + void recheckIdleInhibitorStatus(); - void onSwipeBegin(wlr_pointer_swipe_begin_event*); - void onSwipeEnd(wlr_pointer_swipe_end_event*); - void onSwipeUpdate(wlr_pointer_swipe_update_event*); + void onSwipeBegin(wlr_pointer_swipe_begin_event*); + void onSwipeEnd(wlr_pointer_swipe_end_event*); + void onSwipeUpdate(wlr_pointer_swipe_update_event*); - SSwipeGesture m_sActiveSwipe; + SSwipeGesture m_sActiveSwipe; - SKeyboard* m_pActiveKeyboard = nullptr; + SKeyboard* m_pActiveKeyboard = nullptr; - CTimer m_tmrLastCursorMovement; + CTimer m_tmrLastCursorMovement; - CInputMethodRelay m_sIMERelay; + CInputMethodRelay m_sIMERelay; - void updateKeyboardsLeds(wlr_input_device* pKeyboard); + void updateKeyboardsLeds(wlr_input_device* pKeyboard); // for shared mods uint32_t accumulateModsFromAllKBs(); diff --git a/src/protocols/PointerConstraints.cpp b/src/protocols/PointerConstraints.cpp new file mode 100644 index 00000000000..ab2285494ef --- /dev/null +++ b/src/protocols/PointerConstraints.cpp @@ -0,0 +1,260 @@ +#include "PointerConstraints.hpp" +#include "../desktop/WLSurface.hpp" +#include "../Compositor.hpp" +#include "../config/ConfigValue.hpp" + +#define LOGM PROTO::constraints->protoLog + +CPointerConstraint::CPointerConstraint(SP resource_, wlr_surface* surf, wl_resource* region_, zwpPointerConstraintsV1Lifetime lifetime) : + resourceL(resource_), locked(true) { + if (!resource_->resource()) + return; + + resource_->setOnDestroy([this](CZwpLockedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); + resource_->setDestroy([this](CZwpLockedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); + + pHLSurface = CWLSurface::surfaceFromWlr(surf); + + if (!pHLSurface) + return; + + if (region_) + region.set(wlr_region_from_resource(region_)); + + resource_->setSetRegion([this](CZwpLockedPointerV1* p, wl_resource* region) { onSetRegion(region); }); + resource_->setSetCursorPositionHint([this](CZwpLockedPointerV1* p, wl_fixed_t x, wl_fixed_t y) { + static auto PXWLFORCESCALEZERO = CConfigValue("xwayland:force_zero_scaling"); + + if (!pHLSurface) + return; + + hintSet = true; + + float scale = 1.f; + const auto PWINDOW = pHLSurface->getWindow(); + if (PWINDOW) { + const auto ISXWL = PWINDOW->m_bIsX11; + scale = ISXWL && *PXWLFORCESCALEZERO ? PWINDOW->m_fX11SurfaceScaledBy : 1.f; + } + + positionHint = {wl_fixed_to_double(x) / scale, wl_fixed_to_double(y) / scale}; + g_pInputManager->simulateMouseMovement(); + }); + + sharedConstructions(); +} + +CPointerConstraint::CPointerConstraint(SP resource_, wlr_surface* surf, wl_resource* region_, zwpPointerConstraintsV1Lifetime lifetime) : + resourceC(resource_), locked(false) { + if (!resource_->resource()) + return; + + resource_->setOnDestroy([this](CZwpConfinedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); + resource_->setDestroy([this](CZwpConfinedPointerV1* p) { PROTO::constraints->destroyPointerConstraint(this); }); + + pHLSurface = CWLSurface::surfaceFromWlr(surf); + + if (!pHLSurface) + return; + + if (region_) + region.set(wlr_region_from_resource(region_)); + + resource_->setSetRegion([this](CZwpConfinedPointerV1* p, wl_resource* region) { onSetRegion(region); }); + + sharedConstructions(); +} + +CPointerConstraint::~CPointerConstraint() { + std::erase_if(g_pInputManager->m_vConstraints, [this](const auto& c) { + const auto SHP = c.lock(); + return !SHP || SHP.get() == this; + }); + + if (pHLSurface) + pHLSurface->m_pConstraint.reset(); +} + +void CPointerConstraint::sharedConstructions() { + if (pHLSurface) { + listeners.destroySurface = pHLSurface->events.destroy.registerListener([this](std::any d) { + pHLSurface = nullptr; + if (active) + deactivate(); + + std::erase_if(g_pInputManager->m_vConstraints, [this](const auto& c) { + const auto SHP = c.lock(); + return !SHP || SHP.get() == this; + }); + }); + } + + cursorPosOnActivate = g_pInputManager->getMouseCoordsInternal(); + + if (g_pCompositor->m_pLastFocus == pHLSurface->wlr()) + activate(); +} + +bool CPointerConstraint::good() { + return locked ? resourceL->resource() : resourceC->resource(); +} + +void CPointerConstraint::deactivate() { + if (!active) + return; + + if (locked) + resourceL->sendUnlocked(); + else + resourceC->sendUnconfined(); + + active = false; + + if (locked) + g_pCompositor->warpCursorTo(logicPositionHint(), true); + + if (lifetime == ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT) { + dead = true; + // remove from inputmgr + std::erase_if(g_pInputManager->m_vConstraints, [this](const auto& c) { + const auto SHP = c.lock(); + return !SHP || SHP.get() == this; + }); + } +} + +void CPointerConstraint::activate() { + if (dead || active) + return; + + // TODO: hack, probably not a super duper great idea + if (g_pCompositor->m_sSeat.seat->pointer_state.focused_surface != pHLSurface->wlr()) { + const auto SURFBOX = pHLSurface->getSurfaceBoxGlobal(); + const auto LOCAL = SURFBOX.has_value() ? logicPositionHint() - SURFBOX->pos() : Vector2D{}; + wlr_seat_pointer_enter(g_pCompositor->m_sSeat.seat, pHLSurface->wlr(), LOCAL.x, LOCAL.y); + } + + g_pCompositor->warpCursorTo(logicPositionHint(), true); + + if (locked) + resourceL->sendLocked(); + else + resourceC->sendConfined(); + + active = true; + + g_pInputManager->simulateMouseMovement(); +} + +bool CPointerConstraint::isActive() { + return active; +} + +void CPointerConstraint::onSetRegion(wl_resource* wlRegion) { + if (!wlRegion) { + region.clear(); + return; + } + + const auto REGION = wlr_region_from_resource(wlRegion); + + region.set(REGION); + positionHint = region.closestPoint(positionHint); + g_pInputManager->simulateMouseMovement(); // to warp the cursor if anything's amiss +} + +CWLSurface* CPointerConstraint::owner() { + return pHLSurface; +} + +CRegion CPointerConstraint::logicConstraintRegion() { + CRegion rg = region; + const auto SURFBOX = pHLSurface->getSurfaceBoxGlobal(); + const auto CONSTRAINTPOS = SURFBOX.has_value() ? SURFBOX->pos() : Vector2D{}; + rg.translate(CONSTRAINTPOS); + return rg; +} + +bool CPointerConstraint::isLocked() { + return locked; +} + +Vector2D CPointerConstraint::logicPositionHint() { + if (!pHLSurface) + return {}; + + const auto SURFBOX = pHLSurface->getSurfaceBoxGlobal(); + const auto CONSTRAINTPOS = SURFBOX.has_value() ? SURFBOX->pos() : Vector2D{}; + + return hintSet ? CONSTRAINTPOS + positionHint : (locked ? CONSTRAINTPOS + SURFBOX->size() / 2.f : cursorPosOnActivate); +} + +CPointerConstraintsProtocol::CPointerConstraintsProtocol(const wl_interface* iface, const int& ver, const std::string& name) : IWaylandProtocol(iface, ver, name) { + ; +} + +void CPointerConstraintsProtocol::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](CZwpPointerConstraintsV1* p) { this->onManagerResourceDestroy(p->resource()); }); + + RESOURCE->setDestroy([this](CZwpPointerConstraintsV1* pMgr) { this->onManagerResourceDestroy(pMgr->resource()); }); + RESOURCE->setConfinePointer([this](CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, + zwpPointerConstraintsV1Lifetime lifetime) { this->onConfinePointer(pMgr, id, surface, pointer, region, lifetime); }); + RESOURCE->setLockPointer([this](CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, + zwpPointerConstraintsV1Lifetime lifetime) { this->onLockPointer(pMgr, id, surface, pointer, region, lifetime); }); +} + +void CPointerConstraintsProtocol::onManagerResourceDestroy(wl_resource* res) { + std::erase_if(m_vManagers, [&](const auto& other) { return other->resource() == res; }); +} + +void CPointerConstraintsProtocol::destroyPointerConstraint(CPointerConstraint* hyprland🥚) { + std::erase_if(m_vConstraints, [&](const auto& other) { return other.get() == hyprland🥚; }); +} + +void CPointerConstraintsProtocol::onNewConstraint(SP constraint, CZwpPointerConstraintsV1* pMgr) { + if (!constraint->good()) { + LOGM(ERR, "Couldn't create constraint??"); + wl_resource_post_no_memory(pMgr->resource()); + m_vConstraints.pop_back(); + return; + } + + if (!constraint->owner()) { + LOGM(ERR, "New constraint has no CWLSurface owner??"); + return; + } + + const auto OWNER = constraint->owner(); + + const auto DUPES = std::count_if(m_vConstraints.begin(), m_vConstraints.end(), [OWNER](const auto& c) { return c->owner() == OWNER; }); + + if (DUPES > 1) { + LOGM(ERR, "Constraint for surface duped"); + wl_resource_post_error(pMgr->resource(), ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED, "Surface already confined"); + m_vConstraints.pop_back(); + return; + } + + OWNER->appendConstraint(constraint); + + g_pInputManager->m_vConstraints.push_back(constraint); +} + +void CPointerConstraintsProtocol::onLockPointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, + zwpPointerConstraintsV1Lifetime lifetime) { + const auto CLIENT = wl_resource_get_client(pMgr->resource()); + const auto RESOURCE = m_vConstraints.emplace_back(std::make_shared( + std::make_shared(CLIENT, wl_resource_get_version(pMgr->resource()), id), wlr_surface_from_resource(surface), region, lifetime)); + + onNewConstraint(RESOURCE, pMgr); +} + +void CPointerConstraintsProtocol::onConfinePointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, + zwpPointerConstraintsV1Lifetime lifetime) { + const auto CLIENT = wl_resource_get_client(pMgr->resource()); + const auto RESOURCE = m_vConstraints.emplace_back(std::make_shared( + std::make_shared(CLIENT, wl_resource_get_version(pMgr->resource()), id), wlr_surface_from_resource(surface), region, lifetime)); + + onNewConstraint(RESOURCE, pMgr); +} diff --git a/src/protocols/PointerConstraints.hpp b/src/protocols/PointerConstraints.hpp new file mode 100644 index 00000000000..93e57c46163 --- /dev/null +++ b/src/protocols/PointerConstraints.hpp @@ -0,0 +1,80 @@ +#pragma once + +#pragma once + +#include +#include +#include +#include "WaylandProtocol.hpp" +#include "pointer-constraints-unstable-v1.hpp" +#include "../helpers/Vector2D.hpp" +#include "../helpers/Region.hpp" +#include "../helpers/signal/Listener.hpp" + +class CWLSurface; + +class CPointerConstraint { + public: + CPointerConstraint(SP resource_, wlr_surface* surf, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime); + CPointerConstraint(SP resource_, wlr_surface* surf, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime); + ~CPointerConstraint(); + + bool good(); + + void deactivate(); + void activate(); + bool isActive(); + + CWLSurface* owner(); + + CRegion logicConstraintRegion(); + bool isLocked(); + Vector2D logicPositionHint(); + + private: + SP resourceL; + SP resourceC; + wl_client* pClient = nullptr; + + CWLSurface* pHLSurface = nullptr; + + CRegion region; + bool hintSet = false; + Vector2D positionHint = {-1, -1}; + Vector2D cursorPosOnActivate = {-1, -1}; + bool active = false; + bool locked = false; + bool dead = false; + zwpPointerConstraintsV1Lifetime lifetime = ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT; + + void sharedConstructions(); + void onSetRegion(wl_resource* region); + + struct { + CHyprSignalListener destroySurface; + } listeners; +}; + +class CPointerConstraintsProtocol : public IWaylandProtocol { + public: + CPointerConstraintsProtocol(const wl_interface* iface, const int& ver, 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 destroyPointerConstraint(CPointerConstraint* constraint); + void onLockPointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime); + void onConfinePointer(CZwpPointerConstraintsV1* pMgr, uint32_t id, wl_resource* surface, wl_resource* pointer, wl_resource* region, zwpPointerConstraintsV1Lifetime lifetime); + void onNewConstraint(SP constraint, CZwpPointerConstraintsV1* pMgr); + + // + std::vector> m_vManagers; + std::vector> m_vConstraints; + + friend class CPointerConstraint; +}; + +namespace PROTO { + inline UP constraints; +}; \ No newline at end of file