diff --git a/quickshell/Common/KeyboardFocus.qml b/quickshell/Common/KeyboardFocus.qml new file mode 100644 index 00000000..f7a52141 --- /dev/null +++ b/quickshell/Common/KeyboardFocus.qml @@ -0,0 +1,44 @@ +pragma Singleton + +import QtQuick +import Quickshell +import Quickshell.Wayland +import qs.Services + +// Manages keyboard focus policy for popouts, modals, and Hyprland focus grabs +Singleton { + id: root + + function keyboardFocus(active, customFocus) { + if (PopoutManager.screenshotActive) + return WlrKeyboardFocus.None; + if (customFocus !== null && customFocus !== undefined) + return customFocus; + if (!active) + return WlrKeyboardFocus.None; + if (CompositorService.useHyprlandFocusGrab) + return WlrKeyboardFocus.OnDemand; + return WlrKeyboardFocus.Exclusive; + } + + function wantsGrab(active, customFocus) { + return CompositorService.useHyprlandFocusGrab && keyboardFocus(active, customFocus) === WlrKeyboardFocus.OnDemand; + } + + property list barWindows: [] + + function registerBarWindow(window) { + if (!window || barWindows.indexOf(window) !== -1) + return; + barWindows = barWindows.concat([window]); + } + + function unregisterBarWindow(window) { + const idx = barWindows.indexOf(window); + if (idx === -1) + return; + const next = barWindows.slice(); + next.splice(idx, 1); + barWindows = next; + } +} diff --git a/quickshell/Modals/BluetoothPairingModal.qml b/quickshell/Modals/BluetoothPairingModal.qml index 21b0b411..054d1f9a 100644 --- a/quickshell/Modals/BluetoothPairingModal.qml +++ b/quickshell/Modals/BluetoothPairingModal.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -11,11 +10,6 @@ DankModal { layerNamespace: "dms:bluetooth-pairing" - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - property string deviceName: "" property string deviceAddress: "" property string requestType: "" diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml index d40c3134..393e77fc 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml @@ -1,7 +1,6 @@ pragma ComponentBehavior: Bound import QtQuick -import Quickshell.Hyprland import qs.Common import qs.Modals.Clipboard import qs.Modals.Common @@ -12,11 +11,6 @@ DankModal { layerNamespace: "dms:clipboard" - HyprlandFocusGrab { - windows: [clipboardHistoryModal.contentWindow] - active: clipboardHistoryModal.useHyprlandFocusGrab && clipboardHistoryModal.shouldHaveFocus - } - function toggle() { if (shouldBeVisible) { hide(); diff --git a/quickshell/Modals/Common/DankModal.qml b/quickshell/Modals/Common/DankModal.qml index 11e74a6d..30865cbc 100644 --- a/quickshell/Modals/Common/DankModal.qml +++ b/quickshell/Modals/Common/DankModal.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell.Hyprland import qs.Common import qs.Services @@ -52,6 +53,22 @@ Item { focus: true anchors.fill: parent } + + // One focus grab for every modal; on Hyprland this is what delivers + // keyboard focus to the OnDemand surface, identically in both modes. + // The clickCatcher is whitelisted so an outside click is delivered to + // it (closing the modal) instead of being consumed clearing the grab. + HyprlandFocusGrab { + windows: { + const list = []; + if (root.contentWindow) + list.push(root.contentWindow); + if (root.clickCatcher) + list.push(root.clickCatcher); + return list; + } + active: KeyboardFocus.wantsGrab(root.shouldHaveFocus, root.customKeyboardFocus) + } readonly property var contentWindow: impl.item ? impl.item.contentWindow : null readonly property var clickCatcher: impl.item ? impl.item.clickCatcher : null readonly property var effectiveScreen: impl.item ? impl.item.effectiveScreen : null diff --git a/quickshell/Modals/Common/DankModalConnected.qml b/quickshell/Modals/Common/DankModalConnected.qml index 5c1f372b..07feb34f 100644 --- a/quickshell/Modals/Common/DankModalConnected.qml +++ b/quickshell/Modals/Common/DankModalConnected.qml @@ -509,15 +509,7 @@ Item { "error": true }) WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldHaveFocus) - return WlrKeyboardFocus.None; - if (root.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus) anchors { left: true diff --git a/quickshell/Modals/Common/DankModalStandalone.qml b/quickshell/Modals/Common/DankModalStandalone.qml index 3bf96a75..d91c5344 100644 --- a/quickshell/Modals/Common/DankModalStandalone.qml +++ b/quickshell/Modals/Common/DankModalStandalone.qml @@ -259,15 +259,7 @@ Item { "error": true }) WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldHaveFocus) - return WlrKeyboardFocus.None; - if (root.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldHaveFocus, customKeyboardFocus) anchors { left: true diff --git a/quickshell/Modals/DankColorPickerModal.qml b/quickshell/Modals/DankColorPickerModal.qml index f868db62..f7cbbb12 100644 --- a/quickshell/Modals/DankColorPickerModal.qml +++ b/quickshell/Modals/DankColorPickerModal.qml @@ -1,6 +1,5 @@ import QtQuick import Quickshell -import Quickshell.Hyprland import Quickshell.Io import qs.Common import qs.Modals.Common @@ -13,11 +12,6 @@ DankModal { layerNamespace: "dms:color-picker" - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - property string pickerTitle: I18n.tr("Choose Color") property color selectedColor: SessionData.recentColors.length > 0 ? SessionData.recentColors[0] : Theme.primary property var onColorSelectedCallback: null diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml index bc4cd745..48b81435 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalConnected.qml @@ -441,8 +441,6 @@ Item { spotlightOpen = true; backgroundWindow.visible = true; contentWindow.visible = true; - if (useHyprlandFocusGrab) - focusGrab.active = true; // Load content and initialize (but no forceActiveFocus — that's deferred) _ensureContentLoadedAndInitialize(query || "", mode || ""); @@ -485,7 +483,6 @@ Item { keyboardActive = false; spotlightOpen = false; - focusGrab.active = false; ModalManager.closeModal(modalHandle); closeCleanupTimer.start(); } @@ -541,7 +538,7 @@ Item { HyprlandFocusGrab { id: focusGrab windows: [contentWindow] - active: false + active: root.useHyprlandFocusGrab && root.spotlightOpen onCleared: { if (spotlightOpen) { @@ -685,7 +682,7 @@ Item { WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null) anchors { left: true diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml index 44e5e83b..fc907205 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalSpotlight.qml @@ -164,8 +164,6 @@ Item { openedFromOverview = false; keyboardActive = true; ModalManager.openModal(modalHandle); - if (useHyprlandFocusGrab) - focusGrab.active = true; _ensureContentLoadedAndInitialize(query || "", mode || ""); } @@ -201,7 +199,6 @@ Item { contentVisible = false; keyboardActive = false; spotlightOpen = false; - focusGrab.active = false; ModalManager.closeModal(modalHandle); closeCleanupTimer.start(); } @@ -231,7 +228,7 @@ Item { HyprlandFocusGrab { id: focusGrab windows: [launcherWindow] - active: false + active: root.useHyprlandFocusGrab && root.keyboardActive onCleared: { if (spotlightOpen) hide(); @@ -337,7 +334,7 @@ Item { WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null) anchors { top: true diff --git a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml index 69f1944e..51a6ab98 100644 --- a/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml +++ b/quickshell/Modals/DankLauncherV2/DankLauncherV2ModalStandalone.qml @@ -172,8 +172,6 @@ Item { keyboardActive = true; ModalManager.openModal(modalHandle); - if (useHyprlandFocusGrab) - focusGrab.active = true; _ensureContentLoadedAndInitialize(query || "", mode || ""); } @@ -211,7 +209,6 @@ Item { keyboardActive = false; spotlightOpen = false; - focusGrab.active = false; ModalManager.closeModal(modalHandle); closeCleanupTimer.start(); @@ -262,7 +259,7 @@ Item { HyprlandFocusGrab { id: focusGrab windows: [launcherWindow] - active: false + active: root.useHyprlandFocusGrab && root.keyboardActive onCleared: { if (spotlightOpen) { @@ -373,7 +370,7 @@ Item { WlrLayershell.namespace: "dms:spotlight" WlrLayershell.layer: root.effectiveLauncherLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: PopoutManager.screenshotActive ? WlrKeyboardFocus.None : (keyboardActive ? (root.useHyprlandFocusGrab ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive) : WlrKeyboardFocus.None) + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(keyboardActive, null) anchors { top: true diff --git a/quickshell/Modals/KeybindsModal.qml b/quickshell/Modals/KeybindsModal.qml index 64b01773..78d2f330 100644 --- a/quickshell/Modals/KeybindsModal.qml +++ b/quickshell/Modals/KeybindsModal.qml @@ -1,7 +1,6 @@ import QtQml import QtQuick import QtQuick.Layouts -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -29,11 +28,6 @@ DankModal { KeybindsService.loadCheatsheet(); } - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - function scrollDown() { if (!root.activeFlickable) return; diff --git a/quickshell/Modals/MuxModal.qml b/quickshell/Modals/MuxModal.qml index 56ffe9e7..b216bc0c 100644 --- a/quickshell/Modals/MuxModal.qml +++ b/quickshell/Modals/MuxModal.qml @@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Controls import QtQuick.Layouts -import Quickshell.Hyprland import Quickshell.Io import Quickshell import qs.Common @@ -45,12 +44,6 @@ DankModal { } } - HyprlandFocusGrab { - id: grab - windows: [muxModal.contentWindow] - active: CompositorService.isHyprland && muxModal.shouldHaveFocus - } - function toggle() { if (shouldBeVisible) { hide(); diff --git a/quickshell/Modals/NotificationModal.qml b/quickshell/Modals/NotificationModal.qml index f0e5e1a2..40523b77 100644 --- a/quickshell/Modals/NotificationModal.qml +++ b/quickshell/Modals/NotificationModal.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell.Hyprland import Quickshell.Io import qs.Common import qs.Modals.Common @@ -11,11 +10,6 @@ DankModal { layerNamespace: "dms:notification-center-modal" - HyprlandFocusGrab { - windows: [notificationModal.contentWindow] - active: notificationModal.useHyprlandFocusGrab && notificationModal.shouldHaveFocus - } - property bool notificationModalOpen: false property var notificationListRef: null property var historyListRef: null diff --git a/quickshell/Modals/PowerMenuModal.qml b/quickshell/Modals/PowerMenuModal.qml index 8a6473a5..e4836f7d 100644 --- a/quickshell/Modals/PowerMenuModal.qml +++ b/quickshell/Modals/PowerMenuModal.qml @@ -1,7 +1,6 @@ import QtQuick import QtQuick.Effects import Quickshell -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -13,11 +12,6 @@ DankModal { layerNamespace: "dms:power-menu" keepPopoutsOpen: true - HyprlandFocusGrab { - windows: [root.contentWindow] - active: root.useHyprlandFocusGrab && root.shouldHaveFocus - } - property int selectedIndex: 0 property int selectedRow: 0 property int selectedCol: 0 diff --git a/quickshell/Modules/ControlCenter/ControlCenterPopout.qml b/quickshell/Modules/ControlCenter/ControlCenterPopout.qml index 6c80e3dc..29bae752 100644 --- a/quickshell/Modules/ControlCenter/ControlCenterPopout.qml +++ b/quickshell/Modules/ControlCenter/ControlCenterPopout.qml @@ -109,15 +109,7 @@ DankPopout { close(); } - customKeyboardFocus: { - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (anyModalOpen) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null onBackgroundClicked: close() diff --git a/quickshell/Modules/DankBar/DankBarWindow.qml b/quickshell/Modules/DankBar/DankBarWindow.qml index 8cca42dc..38aa9474 100644 --- a/quickshell/Modules/DankBar/DankBarWindow.qml +++ b/quickshell/Modules/DankBar/DankBarWindow.qml @@ -9,6 +9,8 @@ PanelWindow { id: barWindow readonly property var log: Log.scoped("DankBarWindow") + Component.onDestruction: KeyboardFocus.unregisterBarWindow(barWindow) + required property var rootWindow required property var barConfig property var modelData: item @@ -555,6 +557,7 @@ PanelWindow { color: "transparent" Component.onCompleted: { + KeyboardFocus.registerBarWindow(barWindow); updateGpuTempConfig(); _updateBackgroundAlpha(); _updateHasMaximizedToplevel(); @@ -956,8 +959,13 @@ PanelWindow { acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton onClicked: { const screenName = barWindow.screen?.name; - if (screenName && PopoutManager.currentPopoutsByScreen[screenName]) + if (!screenName) + return; + if (PopoutManager.currentPopoutsByScreen[screenName]) PopoutManager.closeAllPopouts(); + if (ModalManager.currentModalsByScreen[screenName]) + ModalManager.closeAllModalsExcept(null); + TrayMenuManager.closeAllMenus(); } } diff --git a/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml b/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml index faf67fc0..7f7011f2 100644 --- a/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml +++ b/quickshell/Modules/DankBar/Popouts/SystemUpdatePopout.qml @@ -38,15 +38,7 @@ DankPopout { backgroundInteractive: !anyModalOpen - customKeyboardFocus: { - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (anyModalOpen) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + customKeyboardFocus: anyModalOpen ? WlrKeyboardFocus.None : null Connections { target: SystemUpdateService diff --git a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml index 1d30e13e..6fd746fb 100644 --- a/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml +++ b/quickshell/Modules/DankBar/Widgets/SystemTrayBar.qml @@ -980,21 +980,13 @@ BasePill { screen: root.parentScreen WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (!root.menuOpen) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(root.menuOpen, null) WlrLayershell.namespace: "dms:tray-overflow-menu" color: "transparent" HyprlandFocusGrab { - windows: [overflowMenu] - active: CompositorService.useHyprlandFocusGrab && root.useOverflowPopup && root.menuOpen + windows: [overflowMenu].concat(KeyboardFocus.barWindows) + active: root.useOverflowPopup && KeyboardFocus.wantsGrab(root.menuOpen, null) } Connections { @@ -1051,32 +1043,21 @@ BasePill { "leftBar": 0, "rightBar": 0 }) - readonly property real effectiveBarSize: root.barThickness + root.barSpacing + readonly property real maskX: _overflowDismissZone.x + readonly property real maskY: _overflowDismissZone.y + readonly property real maskWidth: _overflowDismissZone.width + readonly property real maskHeight: _overflowDismissZone.height - readonly property real maskX: { - const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0; - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarX, adjacentLeftBar); - } - - readonly property real maskY: { - const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0; - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarY, adjacentTopBar); - } - - readonly property real maskWidth: { - const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0; - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar); - return Math.max(100, width - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0; - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar); - return Math.max(100, height - maskY - bottomExclusion); + DismissZone { + id: _overflowDismissZone + barPosition: overflowMenu.barPosition + barX: overflowMenu.barX + barY: overflowMenu.barY + barWidth: overflowMenu.barWidth + barHeight: overflowMenu.barHeight + screenWidth: overflowMenu.width + screenHeight: overflowMenu.height + adjacentBarInfo: overflowMenu.adjacentBarInfo } mask: Region { @@ -1444,20 +1425,12 @@ BasePill { screen: menuRoot.parentScreen WlrLayershell.layer: root.barUsesOverlayLayer ? WlrLayershell.Overlay : WlrLayershell.Top WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (!menuRoot.showMenu) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(menuRoot.showMenu, null) color: "transparent" HyprlandFocusGrab { - windows: [menuWindow] - active: CompositorService.useHyprlandFocusGrab && menuRoot.showMenu + windows: [menuWindow].concat(KeyboardFocus.barWindows) + active: KeyboardFocus.wantsGrab(menuRoot.showMenu, null) } anchors { @@ -1496,32 +1469,21 @@ BasePill { "leftBar": 0, "rightBar": 0 }) - readonly property real effectiveBarSize: root.barThickness + root.barSpacing + readonly property real maskX: _menuDismissZone.x + readonly property real maskY: _menuDismissZone.y + readonly property real maskWidth: _menuDismissZone.width + readonly property real maskHeight: _menuDismissZone.height - readonly property real maskX: { - const triggeringBarX = (barPosition === 2) ? effectiveBarSize : 0; - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarX, adjacentLeftBar); - } - - readonly property real maskY: { - const triggeringBarY = (barPosition === 0) ? effectiveBarSize : 0; - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarY, adjacentTopBar); - } - - readonly property real maskWidth: { - const triggeringBarRight = (barPosition === 3) ? effectiveBarSize : 0; - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRight, adjacentRightBar); - return Math.max(100, width - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const triggeringBarBottom = (barPosition === 1) ? effectiveBarSize : 0; - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottom, adjacentBottomBar); - return Math.max(100, height - maskY - bottomExclusion); + DismissZone { + id: _menuDismissZone + barPosition: menuWindow.barPosition + barX: menuWindow.barX + barY: menuWindow.barY + barWidth: menuWindow.barWidth + barHeight: menuWindow.barHeight + screenWidth: menuWindow.width + screenHeight: menuWindow.height + adjacentBarInfo: menuWindow.adjacentBarInfo } mask: Region { diff --git a/quickshell/Widgets/DankPopout.qml b/quickshell/Widgets/DankPopout.qml index 117b28fc..938e3fe3 100644 --- a/quickshell/Widgets/DankPopout.qml +++ b/quickshell/Widgets/DankPopout.qml @@ -1,4 +1,5 @@ import QtQuick +import Quickshell.Hyprland import qs.Common import qs.Services @@ -50,6 +51,24 @@ Item { readonly property var contentLoader: impl.item ? impl.item.contentLoader : _fallbackContentLoader readonly property var overlayLoader: impl.item ? impl.item.overlayLoader : _fallbackOverlayLoader readonly property var backgroundWindow: impl.item ? impl.item.backgroundWindow : null + readonly property var contentWindow: impl.item ? impl.item.contentWindow : null + + // On Hyprland the OnDemand content surface only receives keyboard focus + // through a grab; everywhere else Exclusive focus covers this. Both + // popout windows plus every bar are whitelisted so clicks on them are + // delivered normally (dismiss MouseAreas, widget-to-widget transfer) + // instead of being consumed clearing the grab. + HyprlandFocusGrab { + windows: { + const list = []; + if (root.contentWindow) + list.push(root.contentWindow); + if (root.backgroundWindow && root.backgroundWindow !== root.contentWindow) + list.push(root.backgroundWindow); + return list.concat(KeyboardFocus.barWindows); + } + active: KeyboardFocus.wantsGrab(root.shouldBeVisible, root.customKeyboardFocus) + } Loader { id: _fallbackContentLoader diff --git a/quickshell/Widgets/DankPopoutConnected.qml b/quickshell/Widgets/DankPopoutConnected.qml index b2d3d576..91e75754 100644 --- a/quickshell/Widgets/DankPopoutConnected.qml +++ b/quickshell/Widgets/DankPopoutConnected.qml @@ -17,6 +17,7 @@ Item { property Component overlayContent: null property alias overlayLoader: overlayLoader readonly property alias backgroundWindow: contentWindow + readonly property alias contentWindow: contentWindow property real popupWidth: 400 property real popupHeight: 300 property real triggerX: 0 @@ -758,31 +759,21 @@ Item { } })(), dpr) - readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 - readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 - readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 - readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 + readonly property real maskX: _dismissZone.x + readonly property real maskY: _dismissZone.y + readonly property real maskWidth: _dismissZone.width + readonly property real maskHeight: _dismissZone.height - readonly property real maskX: { - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarLeftExclusion, adjacentLeftBar); - } - - readonly property real maskY: { - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarTopExclusion, adjacentTopBar); - } - - readonly property real maskWidth: { - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar); - return Math.max(100, screenWidth - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar); - return Math.max(100, screenHeight - maskY - bottomExclusion); + DismissZone { + id: _dismissZone + barPosition: root.effectiveBarPosition + barX: root.barX + barY: root.barY + barWidth: root.barWidth + barHeight: root.barHeight + screenWidth: root.screenWidth + screenHeight: root.screenHeight + adjacentBarInfo: root.adjacentBarInfo } PanelWindow { @@ -814,17 +805,7 @@ Item { WlrLayershell.namespace: root.layerNamespace WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus) readonly property bool _fullHeight: root.fullHeightSurface anchors { diff --git a/quickshell/Widgets/DankPopoutStandalone.qml b/quickshell/Widgets/DankPopoutStandalone.qml index 91ac2660..a5464f06 100644 --- a/quickshell/Widgets/DankPopoutStandalone.qml +++ b/quickshell/Widgets/DankPopoutStandalone.qml @@ -18,6 +18,7 @@ Item { property Component overlayContent: null property alias overlayLoader: overlayLoader readonly property alias backgroundWindow: backgroundWindow + readonly property alias contentWindow: contentWindow property real popupWidth: 400 property real popupHeight: 300 property real triggerX: 0 @@ -494,31 +495,21 @@ Item { } })(), dpr) - readonly property real triggeringBarLeftExclusion: (effectiveBarPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 - readonly property real triggeringBarTopExclusion: (effectiveBarPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 - readonly property real triggeringBarRightExclusion: (effectiveBarPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 - readonly property real triggeringBarBottomExclusion: (effectiveBarPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 + readonly property real maskX: _dismissZone.x + readonly property real maskY: _dismissZone.y + readonly property real maskWidth: _dismissZone.width + readonly property real maskHeight: _dismissZone.height - readonly property real maskX: { - const adjacentLeftBar = adjacentBarInfo?.leftBar ?? 0; - return Math.max(triggeringBarLeftExclusion, adjacentLeftBar); - } - - readonly property real maskY: { - const adjacentTopBar = adjacentBarInfo?.topBar ?? 0; - return Math.max(triggeringBarTopExclusion, adjacentTopBar); - } - - readonly property real maskWidth: { - const adjacentRightBar = adjacentBarInfo?.rightBar ?? 0; - const rightExclusion = Math.max(triggeringBarRightExclusion, adjacentRightBar); - return Math.max(100, screenWidth - maskX - rightExclusion); - } - - readonly property real maskHeight: { - const adjacentBottomBar = adjacentBarInfo?.bottomBar ?? 0; - const bottomExclusion = Math.max(triggeringBarBottomExclusion, adjacentBottomBar); - return Math.max(100, screenHeight - maskY - bottomExclusion); + DismissZone { + id: _dismissZone + barPosition: root.effectiveBarPosition + barX: root.barX + barY: root.barY + barWidth: root.barWidth + barHeight: root.barHeight + screenWidth: root.screenWidth + screenHeight: root.screenHeight + adjacentBarInfo: root.adjacentBarInfo } PanelWindow { @@ -612,17 +603,7 @@ Item { WlrLayershell.namespace: root.layerNamespace WlrLayershell.layer: root.effectivePopoutLayer WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (PopoutManager.screenshotActive) - return WlrKeyboardFocus.None; - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldBeVisible) - return WlrKeyboardFocus.None; - if (CompositorService.useHyprlandFocusGrab) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } + WlrLayershell.keyboardFocus: KeyboardFocus.keyboardFocus(shouldBeVisible, customKeyboardFocus) anchors { left: true diff --git a/quickshell/Widgets/DismissZone.qml b/quickshell/Widgets/DismissZone.qml new file mode 100644 index 00000000..24b81a0d --- /dev/null +++ b/quickshell/Widgets/DismissZone.qml @@ -0,0 +1,26 @@ +import QtQuick +import qs.Common + +// Defines the screen area (excluding bars) that dismisses popouts +QtObject { + id: root + + property int barPosition: 0 + property real barX: 0 + property real barY: 0 + property real barWidth: 0 + property real barHeight: 0 + property real screenWidth: 0 + property real screenHeight: 0 + property var adjacentBarInfo: null + + readonly property real _leftExclusion: (barPosition === SettingsData.Position.Left && barWidth > 0) ? Math.max(0, barX + barWidth) : 0 + readonly property real _topExclusion: (barPosition === SettingsData.Position.Top && barHeight > 0) ? Math.max(0, barY + barHeight) : 0 + readonly property real _rightExclusion: (barPosition === SettingsData.Position.Right && barWidth > 0) ? Math.max(0, screenWidth - barX) : 0 + readonly property real _bottomExclusion: (barPosition === SettingsData.Position.Bottom && barHeight > 0) ? Math.max(0, screenHeight - barY) : 0 + + readonly property real x: Math.max(_leftExclusion, adjacentBarInfo?.leftBar ?? 0) + readonly property real y: Math.max(_topExclusion, adjacentBarInfo?.topBar ?? 0) + readonly property real width: Math.max(100, screenWidth - x - Math.max(_rightExclusion, adjacentBarInfo?.rightBar ?? 0)) + readonly property real height: Math.max(100, screenHeight - y - Math.max(_bottomExclusion, adjacentBarInfo?.bottomBar ?? 0)) +}