Feature (Overlay): MangoHud Fps Limiter widget (#2388)

This commit is contained in:
end-4
2025-11-07 21:06:55 +01:00
committed by GitHub
8 changed files with 145 additions and 1 deletions
@@ -107,6 +107,12 @@ Singleton {
property real y: 280
property int tabIndex: 0
}
property JsonObject fpsLimiter: JsonObject {
property bool pinned: false
property bool clickthrough: false
property real x: 1576
property real y: 630
}
}
property JsonObject timer: JsonObject {
@@ -18,5 +18,6 @@ ToolbarButton {
iconSize: 22
text: iconBtn.text
color: iconBtn.colText
animateChange: true
}
}
@@ -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,30 @@ Scope {
right: true
}
HyprlandFocusGrab {
id: grab
windows: [overlayWindow]
active: false
onCleared: () => {
if (!active) GlobalStates.overlayOpen = false;
}
}
Connections {
target: GlobalStates
function onOverlayOpenChanged() {
delayedGrabTimer.restart();
}
}
Timer {
id: delayedGrabTimer
interval: Appearance.animation.elementMoveFast.duration
onTriggered: {
grab.active = GlobalStates.overlayOpen;
}
}
OverlayContent {
id: overlayContent
anchors.fill: parent
@@ -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
@@ -9,6 +9,7 @@ Singleton {
{ identifier: "recorder", materialSymbol: "screen_record" },
{ identifier: "volumeMixer", materialSymbol: "volume_up" },
{ identifier: "crosshair", materialSymbol: "point_scan" },
{ identifier: "fpsLimiter", materialSymbol: "animation" },
{ identifier: "resources", materialSymbol: "browse_activity" }
]
@@ -8,6 +8,7 @@ import Quickshell
import Quickshell.Bluetooth
import qs.modules.overlay.crosshair
import qs.modules.overlay.volumeMixer
import qs.modules.overlay.fpsLimiter
import qs.modules.overlay.recorder
import qs.modules.overlay.resources
@@ -17,6 +18,7 @@ DelegateChooser {
DelegateChoice { roleValue: "crosshair"; Crosshair {} }
DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} }
DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} }
DelegateChoice { roleValue: "recorder"; Recorder {} }
DelegateChoice { roleValue: "resources"; Resources {} }
}
@@ -0,0 +1,12 @@
import QtQuick
import Quickshell
import qs.modules.common
import qs.modules.overlay
StyledOverlayWidget {
id: root
title: "MangoHud FPS"
contentItem: FpsLimiterContent {
radius: root.contentRadius
}
}
@@ -0,0 +1,97 @@
import qs.services
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
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)
Timer {
id: iconResetTimer
interval: 1000
onTriggered: {
root.currentState = FpsLimiterContent.State.Normal;
}
}
function applyLimit() {
var fpsValue = parseInt(fpsField.text);
if (isNaN(fpsValue) || fpsValue < 0) {
root.currentState = FpsLimiterContent.State.Error;
iconResetTimer.restart();
fpsField.text = "";
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();
root.currentState = FpsLimiterContent.State.Success;
iconResetTimer.restart();
// Clear the field after applying
fpsField.text = "";
}
Process {
id: fpsSetter
}
RowLayout {
id: content
anchors.centerIn: parent
spacing: 4
ToolbarTextField {
id: fpsField
Layout.fillWidth: true
Layout.preferredWidth: 200
placeholderText: root.currentState === FpsLimiterContent.State.Error ? Translation.tr("Enter a valid number") : Translation.tr("Set FPS limit")
inputMethodHints: Qt.ImhDigitsOnly
focus: true
onAccepted: {
root.applyLimit();
}
}
IconToolbarButton {
id: applyButton
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();
}
}
}
}