diff --git a/quickshell/Common/DankModalWindow.qml b/quickshell/Common/DankModalWindow.qml new file mode 100644 index 00000000..7487350b --- /dev/null +++ b/quickshell/Common/DankModalWindow.qml @@ -0,0 +1,373 @@ +pragma Singleton +pragma ComponentBehavior: Bound + +import QtQuick +import Quickshell +import Quickshell.Wayland +import Quickshell.Hyprland +import qs.Services +import qs.Widgets + +Singleton { + id: root + + property var activeModal: null + property bool windowsVisible: false + property var targetScreen: null + readonly property bool hasActiveModal: activeModal !== null + readonly property bool shouldShowModal: hasActiveModal + readonly property var screen: backgroundWindow.screen + readonly property real dpr: screen ? CompositorService.getScreenScale(screen) : 1 + readonly property real shadowBuffer: 5 + + property bool wantsToHide: false + + property real cachedModalWidth: 400 + property real cachedModalHeight: 300 + property real cachedModalX: 0 + property real cachedModalY: 0 + property var cachedModal: null + property int cachedAnimationDuration: Theme.shortDuration + property var cachedEnterCurve: Theme.expressiveCurves.expressiveFastSpatial + property var cachedExitCurve: Theme.expressiveCurves.expressiveFastSpatial + property real cachedScaleCollapsed: 0.96 + + readonly property real modalWidth: cachedModalWidth + readonly property real modalHeight: cachedModalHeight + readonly property real modalX: cachedModalX + readonly property real modalY: cachedModalY + + Connections { + target: root.cachedModal + function onModalWidthChanged() { + if (!root.hasActiveModal) + return; + root.cachedModalWidth = Theme.px(root.cachedModal.modalWidth, root.dpr); + root.cachedModalX = root.calculateX(root.cachedModal); + } + function onModalHeightChanged() { + if (!root.hasActiveModal) + return; + root.cachedModalHeight = Theme.px(root.cachedModal.modalHeight, root.dpr); + root.cachedModalY = root.calculateY(root.cachedModal); + } + } + + onScreenChanged: { + if (!cachedModal || !screen) + return; + cachedModalWidth = Theme.px(cachedModal.modalWidth, dpr); + cachedModalHeight = Theme.px(cachedModal.modalHeight, dpr); + cachedModalX = calculateX(cachedModal); + cachedModalY = calculateY(cachedModal); + } + + function showModal(modal) { + wantsToHide = false; + targetScreen = CompositorService.focusedScreen; + + activeModal = modal; + cachedModal = modal; + windowsVisible = true; + cachedModalWidth = Theme.px(modal.modalWidth, dpr); + cachedModalHeight = Theme.px(modal.modalHeight, dpr); + cachedModalX = calculateX(modal); + cachedModalY = calculateY(modal); + cachedAnimationDuration = modal.animationDuration ?? Theme.shortDuration; + cachedEnterCurve = modal.animationEnterCurve ?? Theme.expressiveCurves.expressiveFastSpatial; + cachedExitCurve = modal.animationExitCurve ?? Theme.expressiveCurves.expressiveFastSpatial; + cachedScaleCollapsed = modal.animationScaleCollapsed ?? 0.96; + + if (modal.directContent) + Qt.callLater(focusDirectContent); + } + + function focusDirectContent() { + if (!hasActiveModal) + return; + if (!cachedModal?.directContent) + return; + cachedModal.directContent.forceActiveFocus(); + } + + function hideModal() { + wantsToHide = true; + Qt.callLater(completeHide); + } + + function completeHide() { + if (!wantsToHide) + return; + activeModal = null; + wantsToHide = false; + } + + function hideModalInstant() { + wantsToHide = false; + activeModal = null; + windowsVisible = false; + cleanupInputMethod(); + } + + function onCloseAnimationFinished() { + if (hasActiveModal) + return; + if (cachedModal && typeof cachedModal.onFullyClosed === "function") + cachedModal.onFullyClosed(); + cleanupInputMethod(); + windowsVisible = false; + } + + function cleanupInputMethod() { + if (!Qt.inputMethod) + return; + Qt.inputMethod.hide(); + Qt.inputMethod.reset(); + } + + function calculateX(m) { + const screen = backgroundWindow.screen; + if (!screen) + return 0; + const w = Theme.px(m.modalWidth, dpr); + switch (m.positioning) { + case "center": + return Theme.snap((screen.width - w) / 2, dpr); + case "top-right": + return Theme.snap(Math.max(Theme.spacingL, screen.width - w - Theme.spacingL), dpr); + case "custom": + return Theme.snap(m.customPosition.x, dpr); + default: + return 0; + } + } + + function calculateY(m) { + const screen = backgroundWindow.screen; + if (!screen) + return 0; + const h = Theme.px(m.modalHeight, dpr); + switch (m.positioning) { + case "center": + return Theme.snap((screen.height - h) / 2, dpr); + case "top-right": + return Theme.snap(Theme.barHeight + Theme.spacingXS, dpr); + case "custom": + return Theme.snap(m.customPosition.y, dpr); + default: + return 0; + } + } + + PanelWindow { + id: backgroundWindow + visible: root.windowsVisible + screen: root.targetScreen + color: "transparent" + + WlrLayershell.namespace: "dms:modal:background" + WlrLayershell.layer: WlrLayer.Top + WlrLayershell.exclusiveZone: -1 + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None + + anchors { + top: true + left: true + right: true + bottom: true + } + + mask: Region { + item: backgroundMaskRect + intersection: Intersection.Xor + } + + Item { + id: backgroundMaskRect + x: root.shouldShowModal ? root.modalX : 0 + y: root.shouldShowModal ? root.modalY : 0 + width: root.shouldShowModal ? root.modalWidth : (backgroundWindow.screen?.width ?? 1920) + height: root.shouldShowModal ? root.modalHeight : (backgroundWindow.screen?.height ?? 1080) + } + + MouseArea { + anchors.fill: parent + enabled: root.windowsVisible + onClicked: mouse => { + if (!root.cachedModal || !root.shouldShowModal) + return; + if (!(root.cachedModal.closeOnBackgroundClick ?? true)) + return; + const outside = mouse.x < root.modalX || mouse.x > root.modalX + root.modalWidth || mouse.y < root.modalY || mouse.y > root.modalY + root.modalHeight; + if (!outside) + return; + root.cachedModal.backgroundClicked(); + } + } + + Rectangle { + anchors.fill: parent + color: "black" + opacity: root.shouldShowModal && SettingsData.modalDarkenBackground ? (root.cachedModal?.backgroundOpacity ?? 0.5) : 0 + visible: SettingsData.modalDarkenBackground + + Behavior on opacity { + NumberAnimation { + duration: root.cachedAnimationDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve + } + } + } + } + + PanelWindow { + id: contentWindow + visible: root.windowsVisible + screen: root.targetScreen + color: "transparent" + + WlrLayershell.namespace: root.cachedModal?.layerNamespace ?? "dms:modal" + WlrLayershell.layer: WlrLayer.Overlay + WlrLayershell.exclusiveZone: -1 + WlrLayershell.keyboardFocus: { + if (!root.hasActiveModal) + return WlrKeyboardFocus.None; + if (root.cachedModal?.customKeyboardFocus !== null && root.cachedModal?.customKeyboardFocus !== undefined) + return root.cachedModal.customKeyboardFocus; + if (CompositorService.isHyprland) + return WlrKeyboardFocus.OnDemand; + return WlrKeyboardFocus.Exclusive; + } + + anchors { + left: true + top: true + } + + WlrLayershell.margins { + left: Math.max(0, Theme.snap(root.modalX - root.shadowBuffer, root.dpr)) + top: Math.max(0, Theme.snap(root.modalY - root.shadowBuffer, root.dpr)) + } + + implicitWidth: root.modalWidth + (root.shadowBuffer * 2) + implicitHeight: root.modalHeight + (root.shadowBuffer * 2) + + mask: Region { + item: contentMaskRect + } + + Item { + id: contentMaskRect + x: root.shadowBuffer + y: root.shadowBuffer + width: root.shouldShowModal ? root.modalWidth : 0 + height: root.shouldShowModal ? root.modalHeight : 0 + } + + HyprlandFocusGrab { + windows: [contentWindow] + active: CompositorService.isHyprland && root.hasActiveModal && (root.cachedModal?.shouldHaveFocus ?? false) + } + + Item { + id: contentContainer + x: root.shadowBuffer + y: root.shadowBuffer + width: root.modalWidth + height: root.modalHeight + + readonly property bool hasDirectContent: root.cachedModal ? (root.cachedModal.directContent !== null && root.cachedModal.directContent !== undefined) : false + + opacity: root.shouldShowModal ? 1 : 0 + scale: root.shouldShowModal ? 1 : root.cachedScaleCollapsed + + onHasDirectContentChanged: { + if (!hasDirectContent) + return; + const dc = root.cachedModal.directContent; + if (dc.parent === directContentWrapper) + return; + dc.parent = directContentWrapper; + dc.anchors.fill = directContentWrapper; + } + + Behavior on opacity { + NumberAnimation { + id: opacityAnimation + duration: root.cachedAnimationDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve + onRunningChanged: { + if (running || root.shouldShowModal) + return; + root.onCloseAnimationFinished(); + } + } + } + + Behavior on scale { + NumberAnimation { + id: scaleAnimation + duration: root.cachedAnimationDuration + easing.type: Easing.BezierSpline + easing.bezierCurve: root.shouldShowModal ? root.cachedEnterCurve : root.cachedExitCurve + } + } + + DankRectangle { + anchors.fill: parent + color: root.cachedModal?.backgroundColor ?? Theme.surfaceContainer + borderColor: root.cachedModal?.borderColor ?? Theme.outlineMedium + borderWidth: root.cachedModal?.borderWidth ?? 1 + radius: root.cachedModal?.cornerRadius ?? Theme.cornerRadius + z: -1 + } + + FocusScope { + id: modalFocusScope + anchors.fill: parent + focus: root.hasActiveModal + + Keys.onEscapePressed: event => { + if (!root.cachedModal?.closeOnEscapeKey) + return; + root.cachedModal.close(); + event.accepted = true; + } + + Keys.forwardTo: contentContainer.hasDirectContent ? [directContentWrapper] : (contentLoader.item ? [contentLoader.item] : []) + + Item { + id: directContentWrapper + anchors.fill: parent + visible: contentContainer.hasDirectContent + focus: contentContainer.hasDirectContent && root.hasActiveModal + } + + Loader { + id: contentLoader + anchors.fill: parent + active: !contentContainer.hasDirectContent && root.windowsVisible + asynchronous: false + sourceComponent: root.cachedModal?.content ?? null + visible: !contentContainer.hasDirectContent + focus: !contentContainer.hasDirectContent && root.hasActiveModal + onLoaded: { + if (!item) + return; + if (root.cachedModal) + root.cachedModal.loadedContent = item; + if (root.hasActiveModal) + item.forceActiveFocus(); + } + onActiveChanged: { + if (active || !root.cachedModal) + return; + root.cachedModal.loadedContent = null; + } + } + } + } + } +} diff --git a/quickshell/DMSShell.qml b/quickshell/DMSShell.qml index 36e9ebf6..e0fa098a 100644 --- a/quickshell/DMSShell.qml +++ b/quickshell/DMSShell.qml @@ -25,6 +25,8 @@ import qs.Services Item { id: root + readonly property bool _forceDisplayService: DisplayService.brightnessAvailable !== undefined + Instantiator { id: daemonPluginInstantiator asynchronous: true diff --git a/quickshell/Modals/BluetoothPairingModal.qml b/quickshell/Modals/BluetoothPairingModal.qml index dab5310d..3030369a 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 @@ -10,11 +9,6 @@ DankModal { layerNamespace: "dms:bluetooth-pairing" - HyprlandFocusGrab { - windows: [root.contentWindow] - active: CompositorService.isHyprland && root.shouldHaveFocus - } - property string deviceName: "" property string deviceAddress: "" property string requestType: "" diff --git a/quickshell/Modals/Clipboard/ClipboardContent.qml b/quickshell/Modals/Clipboard/ClipboardContent.qml index 835400ac..5e86d225 100644 --- a/quickshell/Modals/Clipboard/ClipboardContent.qml +++ b/quickshell/Modals/Clipboard/ClipboardContent.qml @@ -1,10 +1,9 @@ import QtQuick import qs.Common -import qs.Services import qs.Widgets import qs.Modals.Clipboard -Item { +FocusScope { id: clipboardContent required property var modal @@ -15,6 +14,7 @@ Item { property alias clipboardListView: clipboardListView anchors.fill: parent + focus: true Column { anchors.fill: parent @@ -31,14 +31,13 @@ Item { onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onClearAllClicked: { clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () { - modal.clearAll() - modal.hide() - }, function () {}) + modal.clearAll(); + modal.hide(); + }, function () {}); } onCloseClicked: modal.hide() } - // Search Field DankTextField { id: searchField width: parent.width @@ -47,27 +46,24 @@ Item { showClearButton: true focus: true ignoreTabKeys: true - keyForwardTargets: [modal.modalFocusScope] onTextChanged: { - modal.searchText = text - modal.updateFilteredModel() + modal.searchText = text; + modal.updateFilteredModel(); } - Keys.onEscapePressed: function (event) { - modal.hide() - event.accepted = true - } - Component.onCompleted: { - Qt.callLater(function () { - forceActiveFocus() - }) + Keys.onPressed: event => { + if (event.key === Qt.Key_Escape) { + modal.hide(); + event.accepted = true; + return; + } + modal.keyboardController?.handleKey(event); } + Component.onCompleted: Qt.callLater(() => forceActiveFocus()) Connections { target: modal function onOpened() { - Qt.callLater(function () { - searchField.forceActiveFocus() - }) + Qt.callLater(() => searchField.forceActiveFocus()); } } } @@ -97,21 +93,21 @@ Item { function ensureVisible(index) { if (index < 0 || index >= count) { - return + return; } - const itemHeight = ClipboardConstants.itemHeight + spacing - const itemY = index * itemHeight - const itemBottom = itemY + itemHeight + const itemHeight = ClipboardConstants.itemHeight + spacing; + const itemY = index * itemHeight; + const itemBottom = itemY + itemHeight; if (itemY < contentY) { - contentY = itemY + contentY = itemY; } else if (itemBottom > contentY + height) { - contentY = itemBottom - height + contentY = itemBottom - height; } } onCurrentIndexChanged: { if (clipboardContent.modal && clipboardContent.modal.keyboardNavigationActive && currentIndex >= 0) { - ensureVisible(currentIndex) + ensureVisible(currentIndex); } } diff --git a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml index 74a773bc..77224e69 100644 --- a/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml +++ b/quickshell/Modals/Clipboard/ClipboardHistoryModal.qml @@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell -import Quickshell.Hyprland import Quickshell.Io import qs.Common import qs.Modals.Common @@ -13,11 +12,6 @@ DankModal { layerNamespace: "dms:clipboard" - HyprlandFocusGrab { - windows: [clipboardHistoryModal.contentWindow] - active: CompositorService.isHyprland && clipboardHistoryModal.shouldHaveFocus - } - property int totalCount: 0 property var clipboardEntries: [] property string searchText: "" @@ -148,11 +142,10 @@ DankModal { borderWidth: 1 enableShadow: true onBackgroundClicked: hide() - modalFocusScope.Keys.onPressed: function (event) { - keyboardController.handleKey(event); - } content: clipboardContent + property alias keyboardController: keyboardController + ClipboardKeyboardController { id: keyboardController modal: clipboardHistoryModal @@ -165,13 +158,11 @@ DankModal { onVisibleChanged: { if (visible) { clipboardHistoryModal.shouldHaveFocus = false; - } else if (clipboardHistoryModal.shouldBeVisible) { - clipboardHistoryModal.shouldHaveFocus = true; - clipboardHistoryModal.modalFocusScope.forceActiveFocus(); - if (clipboardHistoryModal.contentLoader.item && clipboardHistoryModal.contentLoader.item.searchField) { - clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus(); - } + return; } + if (!clipboardHistoryModal.shouldBeVisible) + return; + clipboardHistoryModal.shouldHaveFocus = true; } } diff --git a/quickshell/Modals/Common/ConfirmModal.qml b/quickshell/Modals/Common/ConfirmModal.qml index b9c9b908..72fdfb8f 100644 --- a/quickshell/Modals/Common/ConfirmModal.qml +++ b/quickshell/Modals/Common/ConfirmModal.qml @@ -61,29 +61,21 @@ DankModal { shouldBeVisible: false allowStacking: true modalWidth: 350 - modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 160 + modalHeight: 160 enableShadow: true shouldHaveFocus: true onBackgroundClicked: { close(); - if (onCancel) { + if (onCancel) onCancel(); - } } - onOpened: { - Qt.callLater(function () { - modalFocusScope.forceActiveFocus(); - modalFocusScope.focus = true; - shouldHaveFocus = true; - }); - } - modalFocusScope.Keys.onPressed: function (event) { + + function handleKey(event) { switch (event.key) { case Qt.Key_Escape: close(); - if (onCancel) { + if (onCancel) onCancel(); - } event.accepted = true; break; case Qt.Key_Left: @@ -99,46 +91,46 @@ DankModal { event.accepted = true; break; case Qt.Key_N: - if (event.modifiers & Qt.ControlModifier) { - keyboardNavigation = true; - selectedButton = (selectedButton + 1) % 2; - event.accepted = true; - } + if (!(event.modifiers & Qt.ControlModifier)) + return; + keyboardNavigation = true; + selectedButton = (selectedButton + 1) % 2; + event.accepted = true; break; case Qt.Key_P: - if (event.modifiers & Qt.ControlModifier) { - keyboardNavigation = true; - selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2; - event.accepted = true; - } + if (!(event.modifiers & Qt.ControlModifier)) + return; + keyboardNavigation = true; + selectedButton = selectedButton === -1 ? 1 : (selectedButton - 1 + 2) % 2; + event.accepted = true; break; case Qt.Key_J: - if (event.modifiers & Qt.ControlModifier) { - keyboardNavigation = true; - selectedButton = 1; - event.accepted = true; - } + if (!(event.modifiers & Qt.ControlModifier)) + return; + keyboardNavigation = true; + selectedButton = 1; + event.accepted = true; break; case Qt.Key_K: - if (event.modifiers & Qt.ControlModifier) { - keyboardNavigation = true; - selectedButton = 0; - event.accepted = true; - } + if (!(event.modifiers & Qt.ControlModifier)) + return; + keyboardNavigation = true; + selectedButton = 0; + event.accepted = true; break; case Qt.Key_H: - if (event.modifiers & Qt.ControlModifier) { - keyboardNavigation = true; - selectedButton = 0; - event.accepted = true; - } + if (!(event.modifiers & Qt.ControlModifier)) + return; + keyboardNavigation = true; + selectedButton = 0; + event.accepted = true; break; case Qt.Key_L: - if (event.modifiers & Qt.ControlModifier) { - keyboardNavigation = true; - selectedButton = 1; - event.accepted = true; - } + if (!(event.modifiers & Qt.ControlModifier)) + return; + keyboardNavigation = true; + selectedButton = 1; + event.accepted = true; break; case Qt.Key_Tab: keyboardNavigation = true; @@ -147,9 +139,9 @@ DankModal { break; case Qt.Key_Return: case Qt.Key_Enter: - if (selectedButton !== -1) { + if (selectedButton !== -1) selectButton(); - } else { + else { selectedButton = 1; selectButton(); } @@ -159,10 +151,13 @@ DankModal { } content: Component { - Item { + FocusScope { anchors.fill: parent + focus: true implicitHeight: mainColumn.implicitHeight + Keys.onPressed: event => root.handleKey(event) + Column { id: mainColumn anchors.left: parent.left diff --git a/quickshell/Modals/Common/DankModal.qml b/quickshell/Modals/Common/DankModal.qml index 550cc676..b283858b 100644 --- a/quickshell/Modals/Common/DankModal.qml +++ b/quickshell/Modals/Common/DankModal.qml @@ -1,24 +1,19 @@ import QtQuick -import Quickshell -import Quickshell.Wayland import qs.Common -import qs.Services -import qs.Widgets Item { id: root property string layerNamespace: "dms:modal" - property alias content: contentLoader.sourceComponent - property alias contentLoader: contentLoader + property Component content: null property Item directContent: null + property Item loadedContent: null + readonly property var contentLoader: QtObject { + readonly property var item: root.directContent ?? root.loadedContent + } property real modalWidth: 400 property real modalHeight: 300 - property var targetScreen - readonly property var effectiveScreen: targetScreen || contentWindow.screen - readonly property real screenWidth: effectiveScreen?.width - readonly property real screenHeight: effectiveScreen?.height - readonly property real dpr: effectiveScreen ? CompositorService.getScreenScale(effectiveScreen) : 1 + property var targetScreen: null property bool showBackground: true property real backgroundOpacity: 0.5 property string positioning: "center" @@ -36,7 +31,6 @@ Item { property real borderWidth: 1 property real cornerRadius: Theme.cornerRadius property bool enableShadow: false - property alias modalFocusScope: focusScope property bool shouldBeVisible: false property bool shouldHaveFocus: shouldBeVisible property bool allowFocusOverride: false @@ -45,48 +39,41 @@ Item { property bool keepPopoutsOpen: false property var customKeyboardFocus: null property bool useOverlayLayer: false - readonly property alias contentWindow: contentWindow - readonly property alias backgroundWindow: backgroundWindow signal opened signal dialogClosed signal backgroundClicked - property bool animationsEnabled: true - readonly property bool useBackgroundWindow: true + onBackgroundClicked: { + if (closeOnBackgroundClick) + close(); + } function open() { ModalManager.openModal(root); - closeTimer.stop(); shouldBeVisible = true; - contentWindow.visible = false; - if (useBackgroundWindow) - backgroundWindow.visible = true; - Qt.callLater(() => { - contentWindow.visible = true; - shouldHaveFocus = false; - Qt.callLater(() => { - shouldHaveFocus = Qt.binding(() => shouldBeVisible); - }); - }); + shouldHaveFocus = true; + DankModalWindow.showModal(root); + opened(); + } + + function openCentered() { + positioning = "center"; + open(); } function close() { shouldBeVisible = false; shouldHaveFocus = false; - closeTimer.restart(); + DankModalWindow.hideModal(); + dialogClosed(); } function instantClose() { - animationsEnabled = false; shouldBeVisible = false; shouldHaveFocus = false; - closeTimer.stop(); - contentWindow.visible = false; - if (useBackgroundWindow) - backgroundWindow.visible = false; + DankModalWindow.hideModalInstant(); dialogClosed(); - Qt.callLater(() => animationsEnabled = true); } function toggle() { @@ -96,322 +83,9 @@ Item { Connections { target: ModalManager function onCloseAllModalsExcept(excludedModal) { - if (excludedModal !== root && !allowStacking && shouldBeVisible) { - close(); - } - } - } - - Timer { - id: closeTimer - interval: animationDuration + 120 - onTriggered: { - if (!shouldBeVisible) { - contentWindow.visible = false; - if (useBackgroundWindow) - backgroundWindow.visible = false; - dialogClosed(); - } - } - } - - readonly property real shadowBuffer: 5 - readonly property real alignedWidth: Theme.px(modalWidth, dpr) - readonly property real alignedHeight: Theme.px(modalHeight, dpr) - - readonly property real alignedX: Theme.snap((() => { - switch (positioning) { - case "center": - return (screenWidth - alignedWidth) / 2; - case "top-right": - return Math.max(Theme.spacingL, screenWidth - alignedWidth - Theme.spacingL); - case "custom": - return customPosition.x; - default: - return 0; - } - })(), dpr) - - readonly property real alignedY: Theme.snap((() => { - switch (positioning) { - case "center": - return (screenHeight - alignedHeight) / 2; - case "top-right": - return Theme.barHeight + Theme.spacingXS; - case "custom": - return customPosition.y; - default: - return 0; - } - })(), dpr) - - PanelWindow { - id: backgroundWindow - visible: false - color: "transparent" - - WlrLayershell.namespace: root.layerNamespace + ":background" - WlrLayershell.layer: WlrLayershell.Top - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.None - - anchors { - top: true - left: true - right: true - bottom: true - } - - mask: Region { - item: Rectangle { - x: root.alignedX - y: root.alignedY - width: root.shouldBeVisible ? root.alignedWidth : 0 - height: root.shouldBeVisible ? root.alignedHeight : 0 - } - intersection: Intersection.Xor - } - - MouseArea { - anchors.fill: parent - enabled: root.closeOnBackgroundClick && root.shouldBeVisible - onClicked: mouse => { - const clickX = mouse.x; - const clickY = mouse.y; - const outsideContent = clickX < root.alignedX || clickX > root.alignedX + root.alignedWidth || clickY < root.alignedY || clickY > root.alignedY + root.alignedHeight; - - if (!outsideContent) - return; - root.backgroundClicked(); - } - } - - Rectangle { - id: background - anchors.fill: parent - color: "black" - opacity: root.showBackground && SettingsData.modalDarkenBackground ? (root.shouldBeVisible ? root.backgroundOpacity : 0) : 0 - visible: root.showBackground && SettingsData.modalDarkenBackground - - Behavior on opacity { - enabled: root.animationsEnabled - NumberAnimation { - duration: root.animationDuration - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - } - } - - PanelWindow { - id: contentWindow - visible: false - color: "transparent" - - WlrLayershell.namespace: root.layerNamespace - WlrLayershell.layer: { - if (root.useOverlayLayer) - return WlrLayershell.Overlay; - switch (Quickshell.env("DMS_MODAL_LAYER")) { - case "bottom": - console.error("DankModal: 'bottom' layer is not valid for modals. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "background": - console.error("DankModal: 'background' layer is not valid for modals. Defaulting to 'top' layer."); - return WlrLayershell.Top; - case "overlay": - return WlrLayershell.Overlay; - default: - return WlrLayershell.Top; - } - } - WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: { - if (customKeyboardFocus !== null) - return customKeyboardFocus; - if (!shouldHaveFocus) - return WlrKeyboardFocus.None; - if (CompositorService.isHyprland) - return WlrKeyboardFocus.OnDemand; - return WlrKeyboardFocus.Exclusive; - } - - anchors { - left: true - top: true - } - - WlrLayershell.margins { - left: Math.max(0, Theme.snap(root.alignedX - shadowBuffer, dpr)) - top: Math.max(0, Theme.snap(root.alignedY - shadowBuffer, dpr)) - } - - implicitWidth: root.alignedWidth + (shadowBuffer * 2) - implicitHeight: root.alignedHeight + (shadowBuffer * 2) - - onVisibleChanged: { - if (visible) { - opened(); - } else { - if (Qt.inputMethod) { - Qt.inputMethod.hide(); - Qt.inputMethod.reset(); - } - } - } - - Item { - id: modalContainer - x: shadowBuffer - y: shadowBuffer - width: root.alignedWidth - height: root.alignedHeight - - readonly property bool slide: root.animationType === "slide" - readonly property real offsetX: slide ? 15 : 0 - readonly property real offsetY: slide ? -30 : root.animationOffset - - property real animX: 0 - property real animY: 0 - property real scaleValue: root.animationScaleCollapsed - - onOffsetXChanged: animX = Theme.snap(root.shouldBeVisible ? 0 : offsetX, root.dpr) - onOffsetYChanged: animY = Theme.snap(root.shouldBeVisible ? 0 : offsetY, root.dpr) - - Connections { - target: root - function onShouldBeVisibleChanged() { - modalContainer.animX = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetX, root.dpr); - modalContainer.animY = Theme.snap(root.shouldBeVisible ? 0 : modalContainer.offsetY, root.dpr); - modalContainer.scaleValue = root.shouldBeVisible ? 1.0 : root.animationScaleCollapsed; - } - } - - Behavior on animX { - enabled: root.animationsEnabled - NumberAnimation { - duration: root.animationDuration - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - - Behavior on animY { - enabled: root.animationsEnabled - NumberAnimation { - duration: root.animationDuration - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - - Behavior on scaleValue { - enabled: root.animationsEnabled - NumberAnimation { - duration: root.animationDuration - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - - Item { - id: contentContainer - anchors.centerIn: parent - width: parent.width - height: parent.height - clip: false - - Item { - id: animatedContent - anchors.fill: parent - clip: false - opacity: root.shouldBeVisible ? 1 : 0 - scale: modalContainer.scaleValue - x: Theme.snap(modalContainer.animX, root.dpr) + (parent.width - width) * (1 - modalContainer.scaleValue) * 0.5 - y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5 - - Behavior on opacity { - enabled: root.animationsEnabled - NumberAnimation { - duration: animationDuration - easing.type: Easing.BezierSpline - easing.bezierCurve: root.shouldBeVisible ? root.animationEnterCurve : root.animationExitCurve - } - } - - DankRectangle { - anchors.fill: parent - color: root.backgroundColor - borderColor: root.borderColor - borderWidth: root.borderWidth - radius: root.cornerRadius - } - - FocusScope { - anchors.fill: parent - focus: root.shouldBeVisible - clip: false - - Item { - id: directContentWrapper - anchors.fill: parent - visible: root.directContent !== null - focus: true - clip: false - - Component.onCompleted: { - if (root.directContent) { - root.directContent.parent = directContentWrapper; - root.directContent.anchors.fill = directContentWrapper; - Qt.callLater(() => root.directContent.forceActiveFocus()); - } - } - - Connections { - target: root - function onDirectContentChanged() { - if (root.directContent) { - root.directContent.parent = directContentWrapper; - root.directContent.anchors.fill = directContentWrapper; - Qt.callLater(() => root.directContent.forceActiveFocus()); - } - } - } - } - - Loader { - id: contentLoader - anchors.fill: parent - active: root.directContent === null && (root.keepContentLoaded || root.shouldBeVisible || contentWindow.visible) - asynchronous: false - focus: true - clip: false - visible: root.directContent === null - - onLoaded: { - if (item) { - Qt.callLater(() => item.forceActiveFocus()); - } - } - } - } - } - } - } - - FocusScope { - id: focusScope - objectName: "modalFocusScope" - anchors.fill: parent - visible: root.shouldBeVisible || contentWindow.visible - focus: root.shouldBeVisible - Keys.onEscapePressed: event => { - if (root.closeOnEscapeKey && shouldHaveFocus) { - root.close(); - event.accepted = true; - } - } + if (excludedModal === root || allowStacking || !shouldBeVisible) + return; + close(); } } } diff --git a/quickshell/Modals/DankColorPickerModal.qml b/quickshell/Modals/DankColorPickerModal.qml index 24a48fba..ff1ee1ea 100644 --- a/quickshell/Modals/DankColorPickerModal.qml +++ b/quickshell/Modals/DankColorPickerModal.qml @@ -1,6 +1,5 @@ import QtQuick import Quickshell -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -11,11 +10,6 @@ DankModal { layerNamespace: "dms:color-picker" - HyprlandFocusGrab { - windows: [root.contentWindow] - active: CompositorService.isHyprland && 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/KeybindsModal.qml b/quickshell/Modals/KeybindsModal.qml index 9605f5e0..3567ddbf 100644 --- a/quickshell/Modals/KeybindsModal.qml +++ b/quickshell/Modals/KeybindsModal.qml @@ -1,5 +1,4 @@ import QtQuick -import Quickshell.Hyprland import qs.Common import qs.Modals.Common import qs.Services @@ -17,12 +16,6 @@ DankModal { modalWidth: _maxW modalHeight: _maxH onBackgroundClicked: close() - onOpened: () => Qt.callLater(() => modalFocusScope.forceActiveFocus()) - - HyprlandFocusGrab { - windows: [root.contentWindow] - active: CompositorService.isHyprland && root.shouldHaveFocus - } function scrollDown() { if (!root.activeFlickable) @@ -40,25 +33,35 @@ DankModal { root.activeFlickable.contentY = newY; } - modalFocusScope.Keys.onPressed: event => { - if (event.key === Qt.Key_J && event.modifiers & Qt.ControlModifier) { - scrollDown(); - event.accepted = true; - } else if (event.key === Qt.Key_K && event.modifiers & Qt.ControlModifier) { - scrollUp(); - event.accepted = true; - } else if (event.key === Qt.Key_Down) { - scrollDown(); - event.accepted = true; - } else if (event.key === Qt.Key_Up) { - scrollUp(); - event.accepted = true; - } - } - content: Component { - Item { + FocusScope { anchors.fill: parent + focus: true + + Keys.onPressed: event => { + switch (event.key) { + case Qt.Key_J: + if (!(event.modifiers & Qt.ControlModifier)) + return; + root.scrollDown(); + event.accepted = true; + break; + case Qt.Key_K: + if (!(event.modifiers & Qt.ControlModifier)) + return; + root.scrollUp(); + event.accepted = true; + break; + case Qt.Key_Down: + root.scrollDown(); + event.accepted = true; + break; + case Qt.Key_Up: + root.scrollUp(); + event.accepted = true; + break; + } + } Column { anchors.fill: parent diff --git a/quickshell/Modals/NotificationModal.qml b/quickshell/Modals/NotificationModal.qml index 1d0b51a2..25049f12 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: CompositorService.isHyprland && notificationModal.shouldHaveFocus - } - property bool notificationModalOpen: false property var notificationListRef: null @@ -61,9 +55,6 @@ DankModal { modalHeight: 700 visible: false onBackgroundClicked: hide() - onOpened: () => { - Qt.callLater(() => modalFocusScope.forceActiveFocus()); - } onShouldBeVisibleChanged: shouldBeVisible => { if (!shouldBeVisible) { notificationModalOpen = false; @@ -71,7 +62,6 @@ DankModal { NotificationService.onOverlayClose(); } } - modalFocusScope.Keys.onPressed: event => modalKeyboardController.handleKey(event) NotificationKeyboardController { id: modalKeyboardController @@ -101,10 +91,12 @@ DankModal { } content: Component { - Item { + FocusScope { id: notificationKeyHandler - anchors.fill: parent + focus: true + + Keys.onPressed: event => modalKeyboardController.handleKey(event) Column { anchors.fill: parent diff --git a/quickshell/Modals/PowerMenuModal.qml b/quickshell/Modals/PowerMenuModal.qml index d92dabc5..2e29f2c2 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: CompositorService.isHyprland && root.shouldHaveFocus - } - property int selectedIndex: 0 property int selectedRow: 0 property int selectedCol: 0 @@ -275,34 +269,11 @@ DankModal { } else { selectedIndex = defaultIndex; } - Qt.callLater(() => modalFocusScope.forceActiveFocus()); } onDialogClosed: () => { cancelHold(); } Component.onCompleted: updateVisibleActions() - modalFocusScope.Keys.onPressed: event => { - if (event.isAutoRepeat) { - event.accepted = true; - return; - } - if (SettingsData.powerMenuGridLayout) { - handleGridNavigation(event, true); - } else { - handleListNavigation(event, true); - } - } - modalFocusScope.Keys.onReleased: event => { - if (event.isAutoRepeat) { - event.accepted = true; - return; - } - if (SettingsData.powerMenuGridLayout) { - handleGridNavigation(event, false); - } else { - handleListNavigation(event, false); - } - } function handleListNavigation(event, isPressed) { if (!isPressed) { @@ -481,10 +452,33 @@ DankModal { } content: Component { - Item { + FocusScope { anchors.fill: parent + focus: true implicitHeight: (SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + Theme.spacingL * 2 + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0) + Keys.onPressed: event => { + if (event.isAutoRepeat) { + event.accepted = true; + return; + } + if (SettingsData.powerMenuGridLayout) + root.handleGridNavigation(event, true); + else + root.handleListNavigation(event, true); + } + + Keys.onReleased: event => { + if (event.isAutoRepeat) { + event.accepted = true; + return; + } + if (SettingsData.powerMenuGridLayout) + root.handleGridNavigation(event, false); + else + root.handleListNavigation(event, false); + } + Grid { id: buttonGrid visible: SettingsData.powerMenuGridLayout diff --git a/quickshell/Modals/Spotlight/SpotlightContent.qml b/quickshell/Modals/Spotlight/SpotlightContent.qml index 7bb40fa5..12380f44 100644 --- a/quickshell/Modals/Spotlight/SpotlightContent.qml +++ b/quickshell/Modals/Spotlight/SpotlightContent.qml @@ -51,7 +51,21 @@ Item { anchors.fill: parent focus: true clip: false + + onActiveFocusChanged: { + if (!activeFocus) + return; + if (!searchField) + return; + searchField.forceActiveFocus(); + } Keys.onPressed: event => { + const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item; + if (menu?.visible) { + menu.handleKey(event); + return; + } + if (event.key === Qt.Key_Escape) { if (parentModal) parentModal.hide(); @@ -197,7 +211,6 @@ Item { parent: spotlightKeyHandler appLauncher: spotlightKeyHandler.appLauncher - parentHandler: spotlightKeyHandler searchField: spotlightKeyHandler.searchField visible: false z: 1000 @@ -218,8 +231,6 @@ Item { sourceComponent: Component { SpotlightContextMenu { appLauncher: spotlightKeyHandler.appLauncher - parentHandler: spotlightKeyHandler - parentModal: spotlightKeyHandler.parentModal } } } @@ -280,6 +291,12 @@ Item { updateSearchMode(); } Keys.onPressed: event => { + const menu = spotlightKeyHandler.usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item; + if (menu?.visible) { + menu.handleKey(event); + return; + } + if (event.key === Qt.Key_Escape) { if (parentModal) parentModal.hide(); @@ -312,7 +329,7 @@ Item { Row { id: viewModeButtons spacing: Theme.spacingXS - visible: searchMode === "apps" && appLauncher.model.count > 0 + visible: searchMode === "apps" anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter diff --git a/quickshell/Modals/Spotlight/SpotlightContextMenu.qml b/quickshell/Modals/Spotlight/SpotlightContextMenu.qml index 9a72d252..5bb507e2 100644 --- a/quickshell/Modals/Spotlight/SpotlightContextMenu.qml +++ b/quickshell/Modals/Spotlight/SpotlightContextMenu.qml @@ -1,7 +1,6 @@ import QtQuick import Quickshell import Quickshell.Wayland -import Quickshell.Widgets import qs.Common import qs.Modals.Spotlight @@ -11,54 +10,66 @@ PanelWindow { WlrLayershell.namespace: "dms:spotlight-context-menu" WlrLayershell.layer: WlrLayershell.Overlay WlrLayershell.exclusiveZone: -1 - WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive + WlrLayershell.keyboardFocus: WlrKeyboardFocus.None property var appLauncher: null - property var parentHandler: null - property var parentModal: null property real menuPositionX: 0 property real menuPositionY: 0 - + readonly property real shadowBuffer: 5 - - screen: parentModal?.effectiveScreen + + screen: DankModalWindow.targetScreen function show(x, y, app, fromKeyboard) { fromKeyboard = fromKeyboard || false; menuContent.currentApp = app; - + let screenX = x; let screenY = y; - - if (parentModal) { - if (fromKeyboard) { - screenX = x + parentModal.alignedX; - screenY = y + parentModal.alignedY; - } else { - screenX = x + (parentModal.alignedX - shadowBuffer); - screenY = y + (parentModal.alignedY - shadowBuffer); - } + + const modalX = DankModalWindow.modalX; + const modalY = DankModalWindow.modalY; + + if (fromKeyboard) { + screenX = x + modalX; + screenY = y + modalY; + } else { + screenX = x + (modalX - shadowBuffer); + screenY = y + (modalY - shadowBuffer); } - + menuPositionX = screenX; menuPositionY = screenY; - + menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1; menuContent.keyboardNavigation = true; visible = true; - - if (parentHandler) { - parentHandler.enabled = false; + } + + function handleKey(event) { + switch (event.key) { + case Qt.Key_Down: + menuContent.selectNext(); + event.accepted = true; + break; + case Qt.Key_Up: + menuContent.selectPrevious(); + event.accepted = true; + break; + case Qt.Key_Return: + case Qt.Key_Enter: + menuContent.activateSelected(); + event.accepted = true; + break; + case Qt.Key_Escape: + case Qt.Key_Menu: + hide(); + event.accepted = true; + break; } - Qt.callLater(() => { - menuContent.keyboardHandler.forceActiveFocus(); - }); } function hide() { - if (parentHandler) { - parentHandler.enabled = true; - } visible = false; } @@ -71,11 +82,6 @@ PanelWindow { bottom: true } - onVisibleChanged: { - if (!visible && parentHandler) { - parentHandler.enabled = true; - } - } SpotlightContextMenuContent { id: menuContent diff --git a/quickshell/Modals/Spotlight/SpotlightContextMenuPopup.qml b/quickshell/Modals/Spotlight/SpotlightContextMenuPopup.qml index 16f25766..93159ec0 100644 --- a/quickshell/Modals/Spotlight/SpotlightContextMenuPopup.qml +++ b/quickshell/Modals/Spotlight/SpotlightContextMenuPopup.qml @@ -8,51 +8,57 @@ Popup { id: root property var appLauncher: null - property var parentHandler: null property var searchField: null function show(x, y, app, fromKeyboard) { fromKeyboard = fromKeyboard || false; menuContent.currentApp = app; - + root.x = x + 4; root.y = y + 4; - + menuContent.selectedMenuIndex = fromKeyboard ? 0 : -1; menuContent.keyboardNavigation = true; - - if (parentHandler) { - parentHandler.enabled = false; - } - + open(); } - - onOpened: { - Qt.callLater(() => { - menuContent.keyboardHandler.forceActiveFocus(); - }); + + function handleKey(event) { + switch (event.key) { + case Qt.Key_Down: + menuContent.selectNext(); + event.accepted = true; + break; + case Qt.Key_Up: + menuContent.selectPrevious(); + event.accepted = true; + break; + case Qt.Key_Return: + case Qt.Key_Enter: + menuContent.activateSelected(); + event.accepted = true; + break; + case Qt.Key_Escape: + case Qt.Key_Menu: + hide(); + event.accepted = true; + break; + } } function hide() { - if (parentHandler) { - parentHandler.enabled = true; - } close(); } width: menuContent.implicitWidth height: menuContent.implicitHeight padding: 0 - closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + closePolicy: Popup.CloseOnPressOutside modal: true dim: false background: Item {} onClosed: { - if (parentHandler) { - parentHandler.enabled = true; - } if (searchField) { Qt.callLater(() => { searchField.forceActiveFocus(); diff --git a/quickshell/Modals/Spotlight/SpotlightModal.qml b/quickshell/Modals/Spotlight/SpotlightModal.qml index ad28adf0..734f5628 100644 --- a/quickshell/Modals/Spotlight/SpotlightModal.qml +++ b/quickshell/Modals/Spotlight/SpotlightModal.qml @@ -1,20 +1,13 @@ import QtQuick -import Quickshell.Hyprland import Quickshell.Io import qs.Common import qs.Modals.Common -import qs.Services DankModal { id: spotlightModal layerNamespace: "dms:spotlight" - HyprlandFocusGrab { - windows: [spotlightModal.contentWindow] - active: CompositorService.isHyprland && spotlightModal.shouldHaveFocus - } - property bool spotlightOpen: false property alias spotlightContent: spotlightContentInstance property bool openedFromOverview: false @@ -23,32 +16,18 @@ DankModal { openedFromOverview = false; spotlightOpen = true; open(); - - Qt.callLater(() => { - if (spotlightContent && spotlightContent.searchField) { - spotlightContent.searchField.forceActiveFocus(); - } - }); } function showWithQuery(query) { if (spotlightContent) { - if (spotlightContent.appLauncher) { + if (spotlightContent.appLauncher) spotlightContent.appLauncher.searchQuery = query; - } - if (spotlightContent.searchField) { + if (spotlightContent.searchField) spotlightContent.searchField.text = query; - } } spotlightOpen = true; open(); - - Qt.callLater(() => { - if (spotlightContent && spotlightContent.searchField) { - spotlightContent.searchField.forceActiveFocus(); - } - }); } function hide() { @@ -57,23 +36,24 @@ DankModal { close(); } - onDialogClosed: { - if (spotlightContent) { - if (spotlightContent.appLauncher) { - spotlightContent.appLauncher.searchQuery = ""; - spotlightContent.appLauncher.selectedIndex = 0; - spotlightContent.appLauncher.setCategory(I18n.tr("All")); - } - if (spotlightContent.fileSearchController) { - spotlightContent.fileSearchController.reset(); - } - if (spotlightContent.resetScroll) { - spotlightContent.resetScroll(); - } - if (spotlightContent.searchField) { - spotlightContent.searchField.text = ""; - } + function onFullyClosed() { + resetContent(); + } + + function resetContent() { + if (!spotlightContent) + return; + if (spotlightContent.appLauncher) { + spotlightContent.appLauncher.searchQuery = ""; + spotlightContent.appLauncher.selectedIndex = 0; + spotlightContent.appLauncher.setCategory(I18n.tr("All")); } + if (spotlightContent.fileSearchController) + spotlightContent.fileSearchController.reset(); + if (spotlightContent.resetScroll) + spotlightContent.resetScroll(); + if (spotlightContent.searchField) + spotlightContent.searchField.text = ""; } function toggle() { @@ -94,16 +74,10 @@ DankModal { enableShadow: true keepContentLoaded: true onVisibleChanged: () => { - if (visible && !spotlightOpen) { + if (!visible) + return; + if (!spotlightOpen) show(); - } - if (visible && spotlightContent) { - Qt.callLater(() => { - if (spotlightContent.searchField) { - spotlightContent.searchField.forceActiveFocus(); - } - }); - } } onBackgroundClicked: () => { return hide(); diff --git a/quickshell/Modules/Settings/PluginBrowser.qml b/quickshell/Modules/Settings/PluginBrowser.qml index d46502de..f19a4d41 100644 --- a/quickshell/Modules/Settings/PluginBrowser.qml +++ b/quickshell/Modules/Settings/PluginBrowser.qml @@ -95,10 +95,6 @@ FloatingWindow { if (!parentModal) return; parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); - Qt.callLater(() => { - if (parentModal.modalFocusScope) - parentModal.modalFocusScope.forceActiveFocus(); - }); } objectName: "pluginBrowser" diff --git a/quickshell/Modules/Settings/WidgetSelectionPopup.qml b/quickshell/Modules/Settings/WidgetSelectionPopup.qml index 91c64e0f..b60716ba 100644 --- a/quickshell/Modules/Settings/WidgetSelectionPopup.qml +++ b/quickshell/Modules/Settings/WidgetSelectionPopup.qml @@ -82,10 +82,6 @@ FloatingWindow { if (!parentModal) return; parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); - Qt.callLater(() => { - if (parentModal && parentModal.modalFocusScope) - parentModal.modalFocusScope.forceActiveFocus(); - }); } objectName: "widgetSelectionPopup" @@ -112,10 +108,6 @@ FloatingWindow { if (!parentModal) return; parentModal.shouldHaveFocus = Qt.binding(() => parentModal.shouldBeVisible); - Qt.callLater(() => { - if (parentModal && parentModal.modalFocusScope) - parentModal.modalFocusScope.forceActiveFocus(); - }); } FocusScope { diff --git a/quickshell/Services/CompositorService.qml b/quickshell/Services/CompositorService.qml index 58d950dc..fbcaca73 100644 --- a/quickshell/Services/CompositorService.qml +++ b/quickshell/Services/CompositorService.qml @@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound import QtQuick import Quickshell +import Quickshell.I3 import Quickshell.Wayland import Quickshell.Hyprland import qs.Common @@ -23,6 +24,31 @@ Singleton { readonly property string labwcPid: Quickshell.env("LABWC_PID") property bool useNiriSorting: isNiri && NiriService + readonly property string focusedScreenName: { + if (isHyprland && Hyprland.focusedMonitor) + return Hyprland.focusedMonitor.name; + if (isNiri && NiriService.currentOutput) + return NiriService.currentOutput; + if (isDwl && DwlService.activeOutput) + return DwlService.activeOutput; + if (isSway) { + const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true); + if (focusedWs?.monitor?.name) + return focusedWs.monitor.name; + } + return Quickshell.screens[0]?.name ?? ""; + } + + readonly property var focusedScreen: { + if (!focusedScreenName) + return Quickshell.screens[0] ?? null; + for (const s of Quickshell.screens) { + if (s.name === focusedScreenName) + return s; + } + return Quickshell.screens[0] ?? null; + } + property var sortedToplevels: [] property bool _sortScheduled: false diff --git a/quickshell/Services/DisplayService.qml b/quickshell/Services/DisplayService.qml index 898902c1..0f59cfd2 100644 --- a/quickshell/Services/DisplayService.qml +++ b/quickshell/Services/DisplayService.qml @@ -666,11 +666,7 @@ Singleton { return; } - if (SessionData.nightModeAutoEnabled) { - startAutomation(); - } else { - applyNightModeDirectly(); - } + evaluateNightMode(); }); } } @@ -680,7 +676,7 @@ Singleton { Timer { id: restartTimer property string nextAction: "" - interval: 100 + interval: 250 repeat: false onTriggered: {