From 090358d0d19cc65208641eaefa0a905e99145730 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Fri, 3 May 2024 16:22:21 -0700 Subject: [PATCH] protocols/focus_grab: try to pick surface for keyboard focus --- src/desktop/LayerSurface.hpp | 5 +- src/desktop/Popup.cpp | 4 + src/desktop/Popup.hpp | 42 ++++---- src/protocols/FocusGrab.cpp | 185 +++++++++++++++++++++++++++++++++-- src/protocols/FocusGrab.hpp | 4 + 5 files changed, 208 insertions(+), 32 deletions(-) diff --git a/src/desktop/LayerSurface.hpp b/src/desktop/LayerSurface.hpp index f1540be5..a10a9af8 100644 --- a/src/desktop/LayerSurface.hpp +++ b/src/desktop/LayerSurface.hpp @@ -32,6 +32,7 @@ class CLayerSurface { wlr_layer_surface_v1* layerSurface; wl_list link; + std::unique_ptr popupHead; bool keyboardExclusive = false; @@ -69,8 +70,6 @@ class CLayerSurface { void onCommit(); private: - std::unique_ptr popupHead; - DYNLISTENER(destroyLayerSurface); DYNLISTENER(mapLayerSurface); DYNLISTENER(unmapLayerSurface); @@ -82,4 +81,4 @@ class CLayerSurface { bool operator==(const CLayerSurface& rhs) const { return layerSurface == rhs.layerSurface && monitorID == rhs.monitorID; } -}; \ No newline at end of file +}; diff --git a/src/desktop/Popup.cpp b/src/desktop/Popup.cpp index 3cfebe1b..0f20c058 100644 --- a/src/desktop/Popup.cpp +++ b/src/desktop/Popup.cpp @@ -36,6 +36,10 @@ CPopup::~CPopup() { hyprListener_destroyPopup.removeCallback(); } +wlr_xdg_popup* CPopup::wlr() { + return this->m_pWLR; +} + static void onNewPopup(void* owner, void* data) { const auto POPUP = (CPopup*)owner; POPUP->onNewPopup((wlr_xdg_popup*)data); diff --git a/src/desktop/Popup.hpp b/src/desktop/Popup.hpp index 16f011eb..e02c2acd 100644 --- a/src/desktop/Popup.hpp +++ b/src/desktop/Popup.hpp @@ -15,21 +15,23 @@ class CPopup { ~CPopup(); - Vector2D coordsRelativeToParent(); - Vector2D coordsGlobal(); + wlr_xdg_popup* wlr(); + Vector2D coordsRelativeToParent(); + Vector2D coordsGlobal(); - Vector2D size(); + Vector2D size(); - void onNewPopup(wlr_xdg_popup* popup); - void onDestroy(); - void onMap(); - void onUnmap(); - void onCommit(bool ignoreSiblings = false); - void onReposition(); + void onNewPopup(wlr_xdg_popup* popup); + void onDestroy(); + void onMap(); + void onUnmap(); + void onCommit(bool ignoreSiblings = false); + void onReposition(); - void recheckTree(); + void recheckTree(); - CWLSurface m_sWLSurface; + CWLSurface m_sWLSurface; + std::vector> m_vChildren; private: // T1 owners, each popup has to have one of these @@ -37,20 +39,18 @@ class CPopup { PHLLS m_pLayerOwner; // T2 owners - CPopup* m_pParent = nullptr; + CPopup* m_pParent = nullptr; - wlr_xdg_popup* m_pWLR = nullptr; + wlr_xdg_popup* m_pWLR = nullptr; - Vector2D m_vLastSize = {}; - Vector2D m_vLastPos = {}; + Vector2D m_vLastSize = {}; + Vector2D m_vLastPos = {}; - bool m_bRequestedReposition = false; + bool m_bRequestedReposition = false; - bool m_bInert = false; + bool m_bInert = false; - // - std::vector> m_vChildren; - std::unique_ptr m_pSubsurfaceHead; + std::unique_ptr m_pSubsurfaceHead; // signals DYNLISTENER(newPopup); @@ -67,4 +67,4 @@ class CPopup { Vector2D localToGlobal(const Vector2D& rel); Vector2D t1ParentCoords(); -}; \ No newline at end of file +}; diff --git a/src/protocols/FocusGrab.cpp b/src/protocols/FocusGrab.cpp index 5be30bef..d5d0e867 100644 --- a/src/protocols/FocusGrab.cpp +++ b/src/protocols/FocusGrab.cpp @@ -1,10 +1,17 @@ #include "FocusGrab.hpp" #include "Compositor.hpp" +#include "desktop/LayerSurface.hpp" +#include "desktop/WLSurface.hpp" +#include "desktop/Workspace.hpp" +#include "render/decorations/CHyprGroupBarDecoration.hpp" +#include "wlr-layer-shell-unstable-v1-protocol.h" +#include #include #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)) @@ -59,11 +66,12 @@ static void focus_grab_keyboard_enter(wlr_seat_keyboard_grab* grab, wlr_surface* 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. + // 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); + // focus will be cleared from non whitelisted surfaces if no + // whitelisted surfaces will accept it, so this should not reset the grab. } static void focus_grab_keyboard_key(wlr_seat_keyboard_grab* grab, uint32_t time, uint32_t key, uint32_t state) { @@ -182,16 +190,11 @@ void CFocusGrab::start() { hyprListener_touchGrabStarted.initCallback( &g_pCompositor->m_sSeat.seat->events.touch_grab_begin, [this](void*, void*) { this->finish(true); }, this, "CFocusGrab"); - - // If the current keyboard focus surface isn't whitelisted, kick it out. - auto focusedSurface = g_pCompositor->m_sSeat.seat->keyboard_state.focused_surface; - if (focusedSurface != nullptr && !isSurfaceComitted(focusedSurface)) { - wlr_seat_keyboard_clear_focus(g_pCompositor->m_sSeat.seat); - } } // Ensure new surfaces are focused if under the mouse when comitted. g_pInputManager->refocus(); + refocusKeyboard(); } void CFocusGrab::finish(bool sendCleared) { @@ -253,6 +256,172 @@ void CFocusGrab::eraseSurface(wlr_surface* surface) { commit(); } +bool CFocusGrab::refocusKeyboardTestSurface(wlr_surface* surface) { + if (!isSurfaceComitted(surface)) + return false; + + const auto KEYBOARD = wlr_seat_get_keyboard(g_pCompositor->m_sSeat.seat); + uint32_t keycodes[32] = {0}; + + wlr_seat_keyboard_enter(g_pCompositor->m_sSeat.seat, surface, keycodes, 0, &KEYBOARD->modifiers); + return true; +} + +bool CFocusGrab::refocusKeyboardTestPopupTree(UP& popup) { + for (auto& popup : popup->m_vChildren) { + if (refocusKeyboardTestPopupTree(popup)) + return true; + } + + auto wlrPopup = popup->wlr(); + if (!wlrPopup) + return false; + + return refocusKeyboardTestSurface(wlrPopup->base->surface); +} + +void CFocusGrab::refocusKeyboard() { + // If there is no kb focused surface or the current kb focused surface is not whitelisted, replace it. + auto keyboardSurface = g_pCompositor->m_sSeat.seat->keyboard_state.focused_surface; + if (keyboardSurface != nullptr && isSurfaceComitted(keyboardSurface)) + return; + + auto testLayers = [&](std::vector& lsl, std::function f) { + for (auto& ls : lsl | std::views::reverse) { + if (ls->fadingOut || !ls->layerSurface || (ls->layerSurface && !ls->layerSurface->surface->mapped) || ls->alpha.value() == 0.f) + continue; + + if (ls->layerSurface->current.keyboard_interactive == ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE) + continue; + + if (f(ls)) + return true; + } + + return false; + }; + + auto testWindow = [&](PHLWINDOW& window) { + if (refocusKeyboardTestPopupTree(window->m_pPopupHead)) + return true; + + if (refocusKeyboardTestSurface(window->m_pWLSurface.wlr())) + return true; + + return false; + }; + + auto testWindows = [&](std::function f) { + for (auto& window : g_pCompositor->m_vWindows | std::views::reverse) { + if (!window->m_bIsMapped || window->isHidden() || window->m_sAdditionalConfigData.noFocus) + continue; + + if (f(window) && refocusKeyboardTestPopupTree(window->m_pPopupHead)) + return true; + } + + for (auto& window : g_pCompositor->m_vWindows | std::views::reverse) { + if (!window->m_bIsMapped || window->isHidden() || window->m_sAdditionalConfigData.noFocus) + continue; + + if (f(window) && refocusKeyboardTestSurface(window->m_pWLSurface.wlr())) + return true; + } + + return false; + }; + + auto testMonitor = [&](CMonitor* monitor) { + // layer popups + for (auto& lsl : monitor->m_aLayerSurfaceLayers | std::views::reverse) { + auto ret = testLayers(lsl, [&](auto& layer) { return refocusKeyboardTestPopupTree(layer->popupHead); }); + + if (ret) + return true; + } + + // overlay and top layers + auto layerCallback = [&](auto& layer) { return refocusKeyboardTestSurface(layer->layerSurface->surface); }; + + if (testLayers(monitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY], layerCallback)) + return true; + if (testLayers(monitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP], layerCallback)) + return true; + + auto workspace = monitor->activeWorkspace; + + // fullscreen windows + if (workspace->m_bHasFullscreenWindow && workspace->m_efFullscreenMode == FULLSCREEN_FULL) { + auto window = g_pCompositor->getFullscreenWindowOnWorkspace(workspace->m_iID); + if (window) { + if (testWindow(window)) + return true; + } + } + + // pinned windows + if (testWindows([&](auto& window) { return window->m_bPinned; })) + return true; + + // windows on the active special workspace + if (monitor->activeSpecialWorkspace) { + auto ret = testWindows([&](auto& window) { return window->m_pWorkspace == monitor->activeSpecialWorkspace; }); + + if (ret) + return true; + } + + if (workspace->m_bHasFullscreenWindow) { + // windows created over a fullscreen window + auto ret = testWindows([&](auto& window) { return window->m_pWorkspace == workspace && window->m_bCreatedOverFullscreen; }); + + if (ret) + return true; + + // the fullscreen window + auto window = g_pCompositor->getFullscreenWindowOnWorkspace(workspace->m_iID); + + if (testWindow(window)) + return true; + } + + // floating windows + bool ret = testWindows([&](auto& window) { return window->m_pWorkspace == workspace && window->m_bIsFloating; }); + + if (ret) + return true; + + // tiled windows + ret = testWindows([&](auto& window) { return window->m_pWorkspace == workspace; }); + + if (ret) + return true; + + // bottom and background layers + if (testLayers(monitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM], layerCallback)) + return true; + if (testLayers(monitor->m_aLayerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND], layerCallback)) + return true; + + return false; + }; + + auto activeMonitor = g_pCompositor->getMonitorFromCursor(); + if (testMonitor(activeMonitor)) + return; + + for (auto& monitor : g_pCompositor->m_vMonitors) { + if (monitor.get() == activeMonitor) + continue; + + if (testMonitor(monitor.get())) + return; + } + + // no applicable surfaces found, at least remove focus from any non whitelisted surfaces. + wlr_seat_keyboard_clear_focus(g_pCompositor->m_sSeat.seat); +} + void CFocusGrab::commit() { auto surfacesChanged = false; for (auto iter = m_mSurfaces.begin(); iter != m_mSurfaces.end();) { diff --git a/src/protocols/FocusGrab.hpp b/src/protocols/FocusGrab.hpp index 7a902115..1fb4e3c3 100644 --- a/src/protocols/FocusGrab.hpp +++ b/src/protocols/FocusGrab.hpp @@ -1,6 +1,7 @@ #pragma once #include "WaylandProtocol.hpp" +#include "desktop/Popup.hpp" #include "hyprland-focus-grab-v1.hpp" #include "macros.hpp" #include @@ -39,6 +40,9 @@ class CFocusGrab { void addSurface(wlr_surface* surface); void removeSurface(wlr_surface* surface); void eraseSurface(wlr_surface* surface); + void refocusKeyboard(); + bool refocusKeyboardTestSurface(wlr_surface* surface); + bool refocusKeyboardTestPopupTree(UP& popup); void commit(); SP resource;