protocols/focus_grab: try to pick surface for keyboard focus

This commit is contained in:
outfoxxed 2024-05-03 16:22:21 -07:00
parent 3da58270c6
commit 090358d0d1
No known key found for this signature in database
GPG key ID: 4C88A185FB89301E
5 changed files with 208 additions and 32 deletions

View file

@ -32,6 +32,7 @@ class CLayerSurface {
wlr_layer_surface_v1* layerSurface;
wl_list link;
std::unique_ptr<CPopup> popupHead;
bool keyboardExclusive = false;
@ -69,8 +70,6 @@ class CLayerSurface {
void onCommit();
private:
std::unique_ptr<CPopup> popupHead;
DYNLISTENER(destroyLayerSurface);
DYNLISTENER(mapLayerSurface);
DYNLISTENER(unmapLayerSurface);

View file

@ -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);

View file

@ -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<std::unique_ptr<CPopup>> 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<std::unique_ptr<CPopup>> m_vChildren;
std::unique_ptr<CSubsurface> m_pSubsurfaceHead;
std::unique_ptr<CSubsurface> m_pSubsurfaceHead;
// signals
DYNLISTENER(newPopup);

View file

@ -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 <functional>
#include <hyprland-focus-grab-v1.hpp>
#include <managers/input/InputManager.hpp>
#include <cstdint>
#include <memory>
#include <wayland-server.h>
#include <wayland-util.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))
@ -59,11 +66,12 @@ static void focus_grab_keyboard_enter(wlr_seat_keyboard_grab* grab, wlr_surface*
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.
// 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);
// 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<CPopup>& 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<PHLLS>& lsl, std::function<bool(PHLLS&)> 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<bool(PHLWINDOW&)> 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();) {

View file

@ -1,6 +1,7 @@
#pragma once
#include "WaylandProtocol.hpp"
#include "desktop/Popup.hpp"
#include "hyprland-focus-grab-v1.hpp"
#include "macros.hpp"
#include <cstdint>
@ -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<CPopup>& popup);
void commit();
SP<CHyprlandFocusGrabV1> resource;