From 60144ca3debb8a2ad979ccd384b4fe1867a5e42b Mon Sep 17 00:00:00 2001 From: reakjra Date: Thu, 6 Nov 2025 14:23:13 +0100 Subject: [PATCH 01/10] Feature (Overlay): MangoHud Fps Limiter widget --- .../ii/modules/common/Persistent.qml | 6 ++ .../quickshell/ii/modules/overlay/Overlay.qml | 12 ++- .../ii/modules/overlay/OverlayContent.qml | 1 + .../ii/modules/overlay/OverlayContext.qml | 1 + .../overlay/OverlayWidgetDelegateChooser.qml | 2 + .../modules/overlay/fpsLimiter/FpsLimiter.qml | 10 ++ .../overlay/fpsLimiter/FpsLimiterContent.qml | 92 +++++++++++++++++++ 7 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml create mode 100644 dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml diff --git a/dots/.config/quickshell/ii/modules/common/Persistent.qml b/dots/.config/quickshell/ii/modules/common/Persistent.qml index 814938452..7e077b2c3 100644 --- a/dots/.config/quickshell/ii/modules/common/Persistent.qml +++ b/dots/.config/quickshell/ii/modules/common/Persistent.qml @@ -93,6 +93,12 @@ Singleton { property real x: 55 property real y: 188 } + property JsonObject fpsLimiter: JsonObject { + property bool pinned: false + property bool clickthrough: false + property real x: 100 + property real y: 100 + } } property JsonObject timer: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml index 5054b9d1c..2fd33fcd7 100644 --- a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml +++ b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml @@ -25,7 +25,7 @@ Scope { exclusionMode: ExclusionMode.Ignore WlrLayershell.namespace: "quickshell:overlay" WlrLayershell.layer: WlrLayer.Overlay - WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + WlrLayershell.keyboardFocus: GlobalStates.overlayOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None visible: true color: "transparent" @@ -43,6 +43,16 @@ Scope { right: true } + HyprlandFocusGrab { + id: grab + windows: [overlayWindow] + active: GlobalStates.overlayOpen + onCleared: () => { + if (!active) + GlobalStates.overlayOpen = false; + } + } + OverlayContent { id: overlayContent anchors.fill: parent diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml index 838267b80..546962185 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContent.qml @@ -12,6 +12,7 @@ import qs.modules.overlay.crosshair Item { id: root + focus: true readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true Keys.onPressed: (event) => { // Esc to close diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml index 1417c8467..cc54d379f 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml @@ -7,6 +7,7 @@ Singleton { readonly property list availableWidgets: [ { identifier: "crosshair", materialSymbol: "point_scan" }, + { identifier: "fpsLimiter", materialSymbol: "speed" }, { identifier: "volumeMixer", materialSymbol: "volume_up" } ] diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml index e7420f625..dffdfaaee 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml @@ -8,6 +8,7 @@ import Quickshell import Quickshell.Bluetooth import qs.modules.overlay.crosshair import qs.modules.overlay.volumeMixer +import qs.modules.overlay.fpsLimiter DelegateChooser { id: root @@ -15,4 +16,5 @@ DelegateChooser { DelegateChoice { roleValue: "crosshair"; Crosshair {} } DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} } + DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} } } diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml new file mode 100644 index 000000000..fe3ab9df4 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml @@ -0,0 +1,10 @@ +import QtQuick +import Quickshell +import qs.modules.common +import qs.modules.overlay + +StyledOverlayWidget { + id: root + title: "FPS Limiter" + contentItem: FpsLimiterContent {} +} diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml new file mode 100644 index 000000000..fae1aca78 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import Quickshell +import Quickshell.Io +import qs.modules.common +import qs.modules.common.widgets + +Rectangle { + id: root + color: Appearance.m3colors.m3surfaceContainer + property real padding: 20 + implicitWidth: contentColumn.implicitWidth + padding * 2 + implicitHeight: contentColumn.implicitHeight + padding * 2 + + function applyLimit() { + var fpsValue = parseInt(fpsField.text); + if (isNaN(fpsValue) || fpsValue < 0) { + return; + } + + var cfgPaths = [ + "~/.config/MangoHud/MangoHud.conf", + ]; // MangoHud config files + + var updateCommands = cfgPaths.map(path => { + return "if grep -q '^fps_limit=' " + path + "; " + + "then sed -i 's/^fps_limit=.*/fps_limit=" + fpsValue + "/' " + path + "; " + + "else echo 'fps_limit=" + fpsValue + "' >> " + path + "; fi"; + }).join("; "); + + var cmd = updateCommands + "; pkill -SIGUSR2 mangohud"; + + fpsSetter.command = ["bash", "-c", cmd]; + fpsSetter.startDetached(); + + // Clear the field after applying + fpsField.text = ""; + } + + Keys.onPressed: event => { + if (event.key === Qt.Key_Escape) { + fpsField.text = ""; + event.accepted = true; + } + } + + ColumnLayout { + id: contentColumn + anchors.centerIn: parent + spacing: 15 + + RowLayout { + Layout.fillWidth: true + spacing: 10 + + ToolbarTextField { + id: fpsField + Layout.fillWidth: true + Layout.preferredWidth: 200 + placeholderText: qsTr("Set FPS limit (e.g. 80)") + inputMethodHints: Qt.ImhDigitsOnly + focus: true + + Keys.onReturnPressed: { + root.applyLimit(); + event.accepted = true; + } + } + + RippleButton { + id: applyButton + implicitWidth: 36 + implicitHeight: 36 + buttonRadius: Appearance.rounding.full + onClicked: { + root.applyLimit(); + } + contentItem: MaterialSymbol { + anchors.centerIn: parent + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Appearance.font.pixelSize.title + text: "keyboard_return" + } + } + } + + Process { + id: fpsSetter + } + } +} From f4c32f89f2df6337ed71e14b4703f837e030821b Mon Sep 17 00:00:00 2001 From: reakjra Date: Thu, 6 Nov 2025 22:41:14 +0100 Subject: [PATCH 02/10] Added proposed fixes and implemented some animations+error text --- .../quickshell/ii/modules/overlay/Overlay.qml | 11 +++++ .../ii/modules/overlay/OverlayContext.qml | 2 +- .../modules/overlay/fpsLimiter/FpsLimiter.qml | 2 +- .../overlay/fpsLimiter/FpsLimiterContent.qml | 48 +++++++++++++++++-- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml index 2fd33fcd7..efb37cd89 100644 --- a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml +++ b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml @@ -47,12 +47,23 @@ Scope { id: grab windows: [overlayWindow] active: GlobalStates.overlayOpen + onActiveChanged: { + if (active) { + // Quick workspace flick to unlock cursor from game's relative mode + cursorUnlockProcess.running = true; + } + } onCleared: () => { if (!active) GlobalStates.overlayOpen = false; } } + Process { + id: cursorUnlockProcess + command: ["bash", "-c", "ws=$(hyprctl activeworkspace -j | jq -r '.id'); hyprctl --batch \"keyword animations:enabled 0 ; dispatch workspace empty ; dispatch workspace $ws ; keyword animations:enabled 1\""] + } + OverlayContent { id: overlayContent anchors.fill: parent diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml index cc54d379f..dcfeae099 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml @@ -7,7 +7,7 @@ Singleton { readonly property list availableWidgets: [ { identifier: "crosshair", materialSymbol: "point_scan" }, - { identifier: "fpsLimiter", materialSymbol: "speed" }, + { identifier: "fpsLimiter", materialSymbol: "animation" }, { identifier: "volumeMixer", materialSymbol: "volume_up" } ] diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml index fe3ab9df4..24cac5387 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml @@ -5,6 +5,6 @@ import qs.modules.overlay StyledOverlayWidget { id: root - title: "FPS Limiter" + title: "MangoHud FPS" contentItem: FpsLimiterContent {} } diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml index fae1aca78..c83e83de9 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -1,3 +1,4 @@ +import qs.services import QtQuick import QtQuick.Layouts import QtQuick.Controls @@ -10,12 +11,33 @@ Rectangle { id: root color: Appearance.m3colors.m3surfaceContainer property real padding: 20 + property bool showCheckIcon: false + property bool showError: false implicitWidth: contentColumn.implicitWidth + padding * 2 implicitHeight: contentColumn.implicitHeight + padding * 2 + Timer { + id: iconResetTimer + interval: 1000 + onTriggered: { + root.showCheckIcon = false; + } + } + + Timer { + id: errorResetTimer + interval: 1000 + onTriggered: { + root.showError = false; + } + } + function applyLimit() { var fpsValue = parseInt(fpsField.text); if (isNaN(fpsValue) || fpsValue < 0) { + root.showError = true; + errorResetTimer.restart(); + fpsField.text = ""; return; } @@ -34,6 +56,9 @@ Rectangle { fpsSetter.command = ["bash", "-c", cmd]; fpsSetter.startDetached(); + root.showCheckIcon = true; + iconResetTimer.restart(); + // Clear the field after applying fpsField.text = ""; } @@ -41,7 +66,7 @@ Rectangle { Keys.onPressed: event => { if (event.key === Qt.Key_Escape) { fpsField.text = ""; - event.accepted = true; + event.onAccepted(); } } @@ -58,13 +83,13 @@ Rectangle { id: fpsField Layout.fillWidth: true Layout.preferredWidth: 200 - placeholderText: qsTr("Set FPS limit (e.g. 80)") + placeholderText: root.showError ? Translation.tr("Insert a valid number!") : Translation.tr("Set FPS limit (e.g. 80)") inputMethodHints: Qt.ImhDigitsOnly focus: true Keys.onReturnPressed: { root.applyLimit(); - event.accepted = true; + event.onAccepted(); } } @@ -80,7 +105,22 @@ Rectangle { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.title - text: "keyboard_return" + text: root.showError ? "close" : (root.showCheckIcon ? "check" : "save") + rotation: (root.showCheckIcon || root.showError) ? 360 : 0 + color: root.showError ? "#ef5350" : (root.showCheckIcon ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurface) + + Behavior on rotation { + NumberAnimation { + duration: 200 + easing.type: Easing.OutCubic + } + } + + Behavior on color { + ColorAnimation { + duration: 200 + } + } } } } From 7f49daf422a0f5f4dcbd2f1eaffe0cf9a6301f01 Mon Sep 17 00:00:00 2001 From: reakjra Date: Thu, 6 Nov 2025 22:44:40 +0100 Subject: [PATCH 03/10] undo personal edits by mistake --- .../.config/quickshell/ii/modules/overlay/Overlay.qml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml index efb37cd89..2fd33fcd7 100644 --- a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml +++ b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml @@ -47,23 +47,12 @@ Scope { id: grab windows: [overlayWindow] active: GlobalStates.overlayOpen - onActiveChanged: { - if (active) { - // Quick workspace flick to unlock cursor from game's relative mode - cursorUnlockProcess.running = true; - } - } onCleared: () => { if (!active) GlobalStates.overlayOpen = false; } } - Process { - id: cursorUnlockProcess - command: ["bash", "-c", "ws=$(hyprctl activeworkspace -j | jq -r '.id'); hyprctl --batch \"keyword animations:enabled 0 ; dispatch workspace empty ; dispatch workspace $ws ; keyword animations:enabled 1\""] - } - OverlayContent { id: overlayContent anchors.fill: parent From e50ea627e80df3b417cb51d3a0ba4e9c2b105bba Mon Sep 17 00:00:00 2001 From: reakjra Date: Fri, 7 Nov 2025 16:28:45 +0100 Subject: [PATCH 04/10] Fix non-sensical logic, timers and animations. --- .../overlay/fpsLimiter/FpsLimiterContent.qml | 55 ++++++------------- 1 file changed, 17 insertions(+), 38 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml index c83e83de9..f971258aa 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -10,9 +10,8 @@ import qs.modules.common.widgets Rectangle { id: root color: Appearance.m3colors.m3surfaceContainer - property real padding: 20 - property bool showCheckIcon: false - property bool showError: false + property real padding: 16 + property string iconState: "normal" implicitWidth: contentColumn.implicitWidth + padding * 2 implicitHeight: contentColumn.implicitHeight + padding * 2 @@ -20,23 +19,15 @@ Rectangle { id: iconResetTimer interval: 1000 onTriggered: { - root.showCheckIcon = false; - } - } - - Timer { - id: errorResetTimer - interval: 1000 - onTriggered: { - root.showError = false; + root.iconState = "normal"; } } function applyLimit() { var fpsValue = parseInt(fpsField.text); if (isNaN(fpsValue) || fpsValue < 0) { - root.showError = true; - errorResetTimer.restart(); + root.iconState = "error"; + iconResetTimer.restart(); fpsField.text = ""; return; } @@ -56,20 +47,13 @@ Rectangle { fpsSetter.command = ["bash", "-c", cmd]; fpsSetter.startDetached(); - root.showCheckIcon = true; + root.iconState = "success"; iconResetTimer.restart(); // Clear the field after applying fpsField.text = ""; } - Keys.onPressed: event => { - if (event.key === Qt.Key_Escape) { - fpsField.text = ""; - event.onAccepted(); - } - } - ColumnLayout { id: contentColumn anchors.centerIn: parent @@ -82,21 +66,21 @@ Rectangle { ToolbarTextField { id: fpsField Layout.fillWidth: true - Layout.preferredWidth: 200 - placeholderText: root.showError ? Translation.tr("Insert a valid number!") : Translation.tr("Set FPS limit (e.g. 80)") + Layout.preferredWidth: 180 + placeholderText: root.iconState === "error" ? Translation.tr("Insert a valid number") : Translation.tr("Set FPS limit (e.g. 80)") inputMethodHints: Qt.ImhDigitsOnly focus: true - Keys.onReturnPressed: { + onAccepted: { root.applyLimit(); - event.onAccepted(); + event.accepted = true; } } RippleButton { id: applyButton - implicitWidth: 36 - implicitHeight: 36 + implicitWidth: 25 + implicitHeight: 25 buttonRadius: Appearance.rounding.full onClicked: { root.applyLimit(); @@ -105,21 +89,16 @@ Rectangle { anchors.centerIn: parent horizontalAlignment: Text.AlignHCenter font.pixelSize: Appearance.font.pixelSize.title - text: root.showError ? "close" : (root.showCheckIcon ? "check" : "save") - rotation: (root.showCheckIcon || root.showError) ? 360 : 0 - color: root.showError ? "#ef5350" : (root.showCheckIcon ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurface) + text: root.iconState === "error" ? "close" : (root.iconState === "success" ? "check" : "save") + rotation: root.iconState !== "normal" ? 360 : 0 + color: root.iconState === "error" ? "#ef5350" : (root.iconState === "success" ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurface) Behavior on rotation { - NumberAnimation { - duration: 200 - easing.type: Easing.OutCubic - } + animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) } Behavior on color { - ColorAnimation { - duration: 200 - } + animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } } } From 23b471edc2002368e8c37d720cb75418be2a6527 Mon Sep 17 00:00:00 2001 From: reakjra Date: Fri, 7 Nov 2025 16:34:29 +0100 Subject: [PATCH 05/10] Fix non-sensical logic, timers and animations. --- .../ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml index f971258aa..74173db18 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -73,7 +73,6 @@ Rectangle { onAccepted: { root.applyLimit(); - event.accepted = true; } } From daa4dd7b0fddaf8edd6420dac1994ff7433bffcd Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:33:54 +0100 Subject: [PATCH 06/10] overlay: fps limiter: fix weird container and button size --- .../ii/modules/common/Persistent.qml | 4 +- .../common/widgets/IconToolbarButton.qml | 1 + .../modules/overlay/fpsLimiter/FpsLimiter.qml | 4 +- .../overlay/fpsLimiter/FpsLimiterContent.qml | 71 +++++++------------ 4 files changed, 31 insertions(+), 49 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/common/Persistent.qml b/dots/.config/quickshell/ii/modules/common/Persistent.qml index c4c0b2f1a..3d75d11ef 100644 --- a/dots/.config/quickshell/ii/modules/common/Persistent.qml +++ b/dots/.config/quickshell/ii/modules/common/Persistent.qml @@ -109,8 +109,8 @@ Singleton { property JsonObject fpsLimiter: JsonObject { property bool pinned: false property bool clickthrough: false - property real x: 100 - property real y: 100 + property real x: 1600 + property real y: 630 } } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml index df87dcb0e..6e2fd4166 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml @@ -18,5 +18,6 @@ ToolbarButton { iconSize: 22 text: iconBtn.text color: iconBtn.colText + animateChange: true } } diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml index 24cac5387..7628d58be 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml @@ -6,5 +6,7 @@ import qs.modules.overlay StyledOverlayWidget { id: root title: "MangoHud FPS" - contentItem: FpsLimiterContent {} + contentItem: FpsLimiterContent { + radius: root.contentRadius + } } diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml index 74173db18..5857c5a21 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -12,8 +12,9 @@ Rectangle { color: Appearance.m3colors.m3surfaceContainer property real padding: 16 property string iconState: "normal" - implicitWidth: contentColumn.implicitWidth + padding * 2 - implicitHeight: contentColumn.implicitHeight + padding * 2 + anchors.fill: parent + implicitWidth: content.implicitWidth + (padding * 2) + implicitHeight: content.implicitHeight + (padding * 2) Timer { id: iconResetTimer @@ -54,57 +55,35 @@ Rectangle { fpsField.text = ""; } - ColumnLayout { - id: contentColumn + Process { + id: fpsSetter + } + + RowLayout { + id: content anchors.centerIn: parent - spacing: 15 + spacing: 4 - RowLayout { + ToolbarTextField { + id: fpsField Layout.fillWidth: true - spacing: 10 + Layout.preferredWidth: 200 + placeholderText: root.iconState === "error" ? Translation.tr("Insert a valid number") : Translation.tr("Set FPS limit (e.g. 80)") + inputMethodHints: Qt.ImhDigitsOnly + focus: true - ToolbarTextField { - id: fpsField - Layout.fillWidth: true - Layout.preferredWidth: 180 - placeholderText: root.iconState === "error" ? Translation.tr("Insert a valid number") : Translation.tr("Set FPS limit (e.g. 80)") - inputMethodHints: Qt.ImhDigitsOnly - focus: true - - onAccepted: { - root.applyLimit(); - } - } - - RippleButton { - id: applyButton - implicitWidth: 25 - implicitHeight: 25 - buttonRadius: Appearance.rounding.full - onClicked: { - root.applyLimit(); - } - contentItem: MaterialSymbol { - anchors.centerIn: parent - horizontalAlignment: Text.AlignHCenter - font.pixelSize: Appearance.font.pixelSize.title - text: root.iconState === "error" ? "close" : (root.iconState === "success" ? "check" : "save") - rotation: root.iconState !== "normal" ? 360 : 0 - color: root.iconState === "error" ? "#ef5350" : (root.iconState === "success" ? Appearance.m3colors.m3primary : Appearance.m3colors.m3onSurface) - - Behavior on rotation { - animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this) - } - - Behavior on color { - animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) - } - } + onAccepted: { + root.applyLimit(); } } - Process { - id: fpsSetter + IconToolbarButton { + id: applyButton + text: root.iconState === "error" ? "close" : (root.iconState === "success" ? "check" : "save") + enabled: root.iconState === "normal" && fpsField.text.length > 0 + onClicked: { + root.applyLimit(); + } } } } From a91fe7db309920e927297b3d6c958cd1a79ca969 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:49:48 +0100 Subject: [PATCH 07/10] overlay: fps limiter: use enums for states --- .../overlay/fpsLimiter/FpsLimiterContent.qml | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml index 5857c5a21..a67bf40c1 100644 --- a/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -9,10 +9,13 @@ import qs.modules.common.widgets Rectangle { id: root - color: Appearance.m3colors.m3surfaceContainer - property real padding: 16 - property string iconState: "normal" + + enum State { Normal, Success, Error } + anchors.fill: parent + property real padding: 16 + property var currentState: FpsLimiterContent.State.Normal + color: Appearance.m3colors.m3surfaceContainer implicitWidth: content.implicitWidth + (padding * 2) implicitHeight: content.implicitHeight + (padding * 2) @@ -20,14 +23,14 @@ Rectangle { id: iconResetTimer interval: 1000 onTriggered: { - root.iconState = "normal"; + root.currentState = FpsLimiterContent.State.Normal; } } function applyLimit() { var fpsValue = parseInt(fpsField.text); if (isNaN(fpsValue) || fpsValue < 0) { - root.iconState = "error"; + root.currentState = FpsLimiterContent.State.Error; iconResetTimer.restart(); fpsField.text = ""; return; @@ -48,7 +51,7 @@ Rectangle { fpsSetter.command = ["bash", "-c", cmd]; fpsSetter.startDetached(); - root.iconState = "success"; + root.currentState = FpsLimiterContent.State.Success; iconResetTimer.restart(); // Clear the field after applying @@ -68,7 +71,7 @@ Rectangle { id: fpsField Layout.fillWidth: true Layout.preferredWidth: 200 - placeholderText: root.iconState === "error" ? Translation.tr("Insert a valid number") : Translation.tr("Set FPS limit (e.g. 80)") + placeholderText: root.currentState === FpsLimiterContent.State.Error ? Translation.tr("Enter a valid number") : Translation.tr("Set FPS limit") inputMethodHints: Qt.ImhDigitsOnly focus: true @@ -79,8 +82,13 @@ Rectangle { IconToolbarButton { id: applyButton - text: root.iconState === "error" ? "close" : (root.iconState === "success" ? "check" : "save") - enabled: root.iconState === "normal" && fpsField.text.length > 0 + text: switch (root.currentState) { + case FpsLimiterContent.State.Error: return "close"; + case FpsLimiterContent.State.Success: return "check"; + case FpsLimiterContent.State.Normal: + default: return "save"; + } + enabled: root.currentState === FpsLimiterContent.State.Normal && fpsField.text.length > 0 onClicked: { root.applyLimit(); } From 917fae6d4f92d71d004a3970cbb7d45ea42071b7 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 7 Nov 2025 20:54:23 +0100 Subject: [PATCH 08/10] overlay: fps limiter: adjust default position --- dots/.config/quickshell/ii/modules/common/Persistent.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/common/Persistent.qml b/dots/.config/quickshell/ii/modules/common/Persistent.qml index 3d75d11ef..33ea644dd 100644 --- a/dots/.config/quickshell/ii/modules/common/Persistent.qml +++ b/dots/.config/quickshell/ii/modules/common/Persistent.qml @@ -109,7 +109,7 @@ Singleton { property JsonObject fpsLimiter: JsonObject { property bool pinned: false property bool clickthrough: false - property real x: 1600 + property real x: 1576 property real y: 630 } } From 20b3d2498ed819d69f4561b0fc10ffb82d358171 Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:05:59 +0100 Subject: [PATCH 09/10] overlay: add delay to focus grab --- .../quickshell/ii/modules/overlay/Overlay.qml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml index 2fd33fcd7..5401e2f28 100644 --- a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml +++ b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml @@ -46,10 +46,24 @@ Scope { HyprlandFocusGrab { id: grab windows: [overlayWindow] - active: GlobalStates.overlayOpen + active: false onCleared: () => { - if (!active) - GlobalStates.overlayOpen = false; + if (!active) GlobalStates.overlayOpen = false; + } + } + + Connections { + target: GlobalStates + function onOverlayOpenChanged() { + delayedGrabTimer.start(); + } + } + + Timer { + id: delayedGrabTimer + interval: Appearance.animation.elementMoveFast.duration + onTriggered: { + grab.active = GlobalStates.overlayOpen; } } From 3fe8377309c41f0b9828d1edf8e4b74303cfe06c Mon Sep 17 00:00:00 2001 From: end-4 <97237370+end-4@users.noreply.github.com> Date: Fri, 7 Nov 2025 21:06:31 +0100 Subject: [PATCH 10/10] make timer restart instead of start --- dots/.config/quickshell/ii/modules/overlay/Overlay.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml index 5401e2f28..71ba510c6 100644 --- a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml +++ b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml @@ -55,7 +55,7 @@ Scope { Connections { target: GlobalStates function onOverlayOpenChanged() { - delayedGrabTimer.start(); + delayedGrabTimer.restart(); } }