forked from Shinonome/dots-hyprland
Merge branch 'end-4:main' into fedora
This commit is contained in:
@@ -23,3 +23,5 @@ exec-once = wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsCo
|
|||||||
# Cursor
|
# Cursor
|
||||||
exec-once = hyprctl setcursor Bibata-Modern-Classic 24
|
exec-once = hyprctl setcursor Bibata-Modern-Classic 24
|
||||||
|
|
||||||
|
# Fix dock pinned apps not launching properly (https://github.com/end-4/dots-hyprland/issues/2200)
|
||||||
|
exec-once = sleep 3.5 && hyprctl reload && sleep 0.5 && touch ~/.config/quickshell/ii/shell.qml
|
||||||
|
|||||||
@@ -380,6 +380,7 @@ Singleton {
|
|||||||
property JsonObject overlay: JsonObject {
|
property JsonObject overlay: JsonObject {
|
||||||
property bool openingZoomAnimation: true
|
property bool openingZoomAnimation: true
|
||||||
property bool darkenScreen: true
|
property bool darkenScreen: true
|
||||||
|
property real clickthroughOpacity: 0.7
|
||||||
}
|
}
|
||||||
|
|
||||||
property JsonObject overview: JsonObject {
|
property JsonObject overview: JsonObject {
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Singleton {
|
|||||||
property bool pinned: false
|
property bool pinned: false
|
||||||
property bool clickthrough: true
|
property bool clickthrough: true
|
||||||
property real x: 835
|
property real x: 835
|
||||||
property real y: 490
|
property real y: 483
|
||||||
}
|
}
|
||||||
property JsonObject recorder: JsonObject {
|
property JsonObject recorder: JsonObject {
|
||||||
property bool pinned: false
|
property bool pinned: false
|
||||||
@@ -104,7 +104,14 @@ Singleton {
|
|||||||
property bool pinned: false
|
property bool pinned: false
|
||||||
property bool clickthrough: false
|
property bool clickthrough: false
|
||||||
property real x: 80
|
property real x: 80
|
||||||
property real y: 250
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ Button {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool tabbedTo: root.focus && (focusReason === Qt.TabFocusReason || focusReason === Qt.BacktabFocusReason)
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
id: buttonBackground
|
id: buttonBackground
|
||||||
topLeftRadius: root.leftRadius
|
topLeftRadius: root.leftRadius
|
||||||
@@ -130,6 +130,9 @@ Button {
|
|||||||
Behavior on color {
|
Behavior on color {
|
||||||
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
border.width: root.tabbedTo ? 2 : 0
|
||||||
|
border.color: Appearance.colors.colSecondary
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: StyledText {
|
contentItem: StyledText {
|
||||||
|
|||||||
@@ -18,5 +18,6 @@ ToolbarButton {
|
|||||||
iconSize: 22
|
iconSize: 22
|
||||||
text: iconBtn.text
|
text: iconBtn.text
|
||||||
color: iconBtn.colText
|
color: iconBtn.colText
|
||||||
|
animateChange: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ Scope {
|
|||||||
exclusionMode: ExclusionMode.Ignore
|
exclusionMode: ExclusionMode.Ignore
|
||||||
WlrLayershell.namespace: "quickshell:overlay"
|
WlrLayershell.namespace: "quickshell:overlay"
|
||||||
WlrLayershell.layer: WlrLayer.Overlay
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
WlrLayershell.keyboardFocus: GlobalStates.overlayOpen ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||||
visible: true
|
visible: true
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
@@ -43,6 +43,30 @@ Scope {
|
|||||||
right: true
|
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 {
|
OverlayContent {
|
||||||
id: overlayContent
|
id: overlayContent
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import qs.modules.overlay.crosshair
|
|||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
focus: true
|
||||||
readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true
|
readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true
|
||||||
|
|
||||||
Keys.onPressed: (event) => { // Esc to close
|
Keys.onPressed: (event) => { // Esc to close
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Singleton {
|
|||||||
{ identifier: "recorder", materialSymbol: "screen_record" },
|
{ identifier: "recorder", materialSymbol: "screen_record" },
|
||||||
{ identifier: "volumeMixer", materialSymbol: "volume_up" },
|
{ identifier: "volumeMixer", materialSymbol: "volume_up" },
|
||||||
{ identifier: "crosshair", materialSymbol: "point_scan" },
|
{ identifier: "crosshair", materialSymbol: "point_scan" },
|
||||||
|
{ identifier: "fpsLimiter", materialSymbol: "animation" },
|
||||||
{ identifier: "resources", materialSymbol: "browse_activity" }
|
{ identifier: "resources", materialSymbol: "browse_activity" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import Quickshell
|
|||||||
import Quickshell.Bluetooth
|
import Quickshell.Bluetooth
|
||||||
import qs.modules.overlay.crosshair
|
import qs.modules.overlay.crosshair
|
||||||
import qs.modules.overlay.volumeMixer
|
import qs.modules.overlay.volumeMixer
|
||||||
|
import qs.modules.overlay.fpsLimiter
|
||||||
import qs.modules.overlay.recorder
|
import qs.modules.overlay.recorder
|
||||||
import qs.modules.overlay.resources
|
import qs.modules.overlay.resources
|
||||||
|
|
||||||
@@ -17,6 +18,7 @@ DelegateChooser {
|
|||||||
|
|
||||||
DelegateChoice { roleValue: "crosshair"; Crosshair {} }
|
DelegateChoice { roleValue: "crosshair"; Crosshair {} }
|
||||||
DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} }
|
DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} }
|
||||||
|
DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} }
|
||||||
DelegateChoice { roleValue: "recorder"; Recorder {} }
|
DelegateChoice { roleValue: "recorder"; Recorder {} }
|
||||||
DelegateChoice { roleValue: "resources"; Resources {} }
|
DelegateChoice { roleValue: "resources"; Resources {} }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ AbstractOverlayWidget {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property Item contentItem
|
required property Item contentItem
|
||||||
|
property bool fancyBorders: true
|
||||||
|
property bool showCenterButton: false
|
||||||
|
property bool showClickabilityButton: true
|
||||||
|
|
||||||
required property var modelData
|
required property var modelData
|
||||||
readonly property string identifier: modelData.identifier
|
readonly property string identifier: modelData.identifier
|
||||||
@@ -29,6 +32,8 @@ AbstractOverlayWidget {
|
|||||||
property var persistentStateEntry: Persistent.states.overlay[identifier]
|
property var persistentStateEntry: Persistent.states.overlay[identifier]
|
||||||
property real radius: Appearance.rounding.windowRounding
|
property real radius: Appearance.rounding.windowRounding
|
||||||
property real minWidth: 250
|
property real minWidth: 250
|
||||||
|
property real padding: 6
|
||||||
|
property real contentRadius: radius - padding
|
||||||
|
|
||||||
draggable: GlobalStates.overlayOpen
|
draggable: GlobalStates.overlayOpen
|
||||||
x: Math.round(persistentStateEntry.x) // Round or it'll be blurry
|
x: Math.round(persistentStateEntry.x) // Round or it'll be blurry
|
||||||
@@ -38,9 +43,10 @@ AbstractOverlayWidget {
|
|||||||
drag {
|
drag {
|
||||||
minimumX: 0
|
minimumX: 0
|
||||||
minimumY: 0
|
minimumY: 0
|
||||||
maximumX: root.parent.width - root.width
|
maximumX: root.parent?.width - root.width
|
||||||
maximumY: root.parent.height - root.height
|
maximumY: root.parent?.height - root.height
|
||||||
}
|
}
|
||||||
|
opacity: (GlobalStates.overlayOpen || !clickthrough) ? 1.0 : Config.options.overlay.clickthroughOpacity
|
||||||
|
|
||||||
// Guarded states & registration funcs
|
// Guarded states & registration funcs
|
||||||
property bool open: Persistent.states.overlay.open
|
property bool open: Persistent.states.overlay.open
|
||||||
@@ -83,7 +89,7 @@ AbstractOverlayWidget {
|
|||||||
|
|
||||||
function center() {
|
function center() {
|
||||||
const targetX = (root.parent.width - contentColumn.width) / 2
|
const targetX = (root.parent.width - contentColumn.width) / 2
|
||||||
const targetY = (root.parent.height - contentItem.height) / 2 - titleBar.implicitHeight
|
const targetY = (root.parent.height - contentItem.height) / 2 - titleBar.implicitHeight + border.border.width
|
||||||
root.x = targetX
|
root.x = targetX
|
||||||
root.y = targetY
|
root.y = targetY
|
||||||
root.savePosition(targetX, targetY)
|
root.savePosition(targetX, targetY)
|
||||||
@@ -96,11 +102,15 @@ AbstractOverlayWidget {
|
|||||||
Rectangle {
|
Rectangle {
|
||||||
id: border
|
id: border
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "transparent"
|
color: (root.fancyBorders && GlobalStates.overlayOpen) ? Appearance.colors.colLayer1 : "transparent"
|
||||||
radius: root.radius
|
radius: root.radius
|
||||||
border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)
|
border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
|
||||||
|
}
|
||||||
|
|
||||||
layer.enabled: GlobalStates.overlayOpen
|
layer.enabled: GlobalStates.overlayOpen
|
||||||
layer.effect: OpacityMask {
|
layer.effect: OpacityMask {
|
||||||
maskSource: Rectangle {
|
maskSource: Rectangle {
|
||||||
@@ -110,37 +120,34 @@ AbstractOverlayWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
ColumnLayout {
|
||||||
id: contentColumn
|
id: contentColumn
|
||||||
z: -1
|
z: root.fancyBorders ? 0 : -1
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
// Title bar
|
// Title bar
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: titleBar
|
id: titleBar
|
||||||
opacity: GlobalStates.overlayOpen ? 1 : 0
|
opacity: GlobalStates.overlayOpen ? 1 : 0
|
||||||
anchors {
|
Layout.fillWidth: true
|
||||||
left: parent.left
|
implicitWidth: titleBarRow.implicitWidth + root.padding * 2
|
||||||
right: parent.right
|
implicitHeight: titleBarRow.implicitHeight + root.padding * 2
|
||||||
}
|
color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1
|
||||||
property real padding: 2
|
// border.color: Appearance.colors.colOutlineVariant
|
||||||
implicitWidth: titleBarRow.implicitWidth + padding * 2
|
// border.width: 1
|
||||||
implicitHeight: titleBarRow.implicitHeight + padding * 2
|
|
||||||
color: Appearance.m3colors.m3surfaceContainer
|
|
||||||
border.color: Appearance.colors.colOutlineVariant
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: titleBarRow
|
id: titleBarRow
|
||||||
anchors {
|
anchors {
|
||||||
fill: parent
|
fill: parent
|
||||||
margins: titleBar.padding
|
margins: root.padding
|
||||||
leftMargin: titleBar.padding + 8
|
|
||||||
}
|
}
|
||||||
spacing: 0
|
spacing: 2
|
||||||
|
|
||||||
MaterialSymbol {
|
MaterialSymbol {
|
||||||
text: root.materialSymbol
|
text: root.materialSymbol
|
||||||
|
Layout.leftMargin: 6
|
||||||
iconSize: 20
|
iconSize: 20
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
Layout.rightMargin: 4
|
Layout.rightMargin: 4
|
||||||
@@ -153,6 +160,7 @@ AbstractOverlayWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TitlebarButton {
|
TitlebarButton {
|
||||||
|
visible: root.showCenterButton
|
||||||
materialSymbol: "recenter"
|
materialSymbol: "recenter"
|
||||||
onClicked: root.center()
|
onClicked: root.center()
|
||||||
StyledToolTip {
|
StyledToolTip {
|
||||||
@@ -161,6 +169,7 @@ AbstractOverlayWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TitlebarButton {
|
TitlebarButton {
|
||||||
|
visible: (root.pinned && root.showClickabilityButton)
|
||||||
materialSymbol: "mouse"
|
materialSymbol: "mouse"
|
||||||
toggled: !root.clickthrough
|
toggled: !root.clickthrough
|
||||||
onClicked: root.toggleClickthrough()
|
onClicked: root.toggleClickthrough()
|
||||||
@@ -191,7 +200,11 @@ AbstractOverlayWidget {
|
|||||||
// Content
|
// Content
|
||||||
Item {
|
Item {
|
||||||
id: contentContainer
|
id: contentContainer
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.margins: root.fancyBorders ? root.padding : 0
|
||||||
|
Layout.topMargin: -border.border.width // Border of a rectangle is drawn inside its bounds, so we do this to make the gap not too big
|
||||||
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||||
implicitWidth: root.contentItem.implicitWidth
|
implicitWidth: root.contentItem.implicitWidth
|
||||||
implicitHeight: root.contentItem.implicitHeight
|
implicitHeight: root.contentItem.implicitHeight
|
||||||
children: [root.contentItem]
|
children: [root.contentItem]
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
import qs.modules.overlay
|
import qs.modules.overlay
|
||||||
|
|
||||||
StyledOverlayWidget {
|
StyledOverlayWidget {
|
||||||
id: root
|
id: root
|
||||||
contentItem: CrosshairContent {}
|
fancyBorders: false // Crosshair should be see-through
|
||||||
|
showCenterButton: true
|
||||||
|
opacity: 1 // The crosshair itself already has transparency if configured
|
||||||
|
showClickabilityButton: false
|
||||||
|
clickthrough: true
|
||||||
|
|
||||||
|
contentItem: CrosshairContent {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ StyledOverlayWidget {
|
|||||||
contentItem: Rectangle {
|
contentItem: Rectangle {
|
||||||
id: contentItem
|
id: contentItem
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
radius: root.contentRadius
|
||||||
color: Appearance.m3colors.m3surfaceContainer
|
color: Appearance.m3colors.m3surfaceContainer
|
||||||
property real padding: 8
|
property real padding: 8
|
||||||
implicitHeight: contentColumn.implicitHeight + padding * 2
|
implicitHeight: contentColumn.implicitHeight + padding * 2
|
||||||
@@ -75,7 +76,7 @@ StyledOverlayWidget {
|
|||||||
colRipple: Appearance.colors.colLayer3Active
|
colRipple: Appearance.colors.colLayer3Active
|
||||||
onClicked: {
|
onClicked: {
|
||||||
GlobalStates.overlayOpen = false;
|
GlobalStates.overlayOpen = false;
|
||||||
Qt.openUrlExternally(Directories.videos);
|
Qt.openUrlExternally(`file://${Config.options.screenRecord.savePath}`);
|
||||||
}
|
}
|
||||||
contentItem: Row {
|
contentItem: Row {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ StyledOverlayWidget {
|
|||||||
id: contentItem
|
id: contentItem
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: Appearance.m3colors.m3surfaceContainer
|
color: Appearance.m3colors.m3surfaceContainer
|
||||||
|
radius: root.contentRadius
|
||||||
property real padding: 4
|
property real padding: 4
|
||||||
implicitWidth: 350
|
implicitWidth: 350
|
||||||
implicitHeight: 200
|
implicitHeight: 200
|
||||||
@@ -49,7 +50,7 @@ StyledOverlayWidget {
|
|||||||
fill: parent
|
fill: parent
|
||||||
margins: parent.padding
|
margins: parent.padding
|
||||||
}
|
}
|
||||||
spacing: 10
|
spacing: 8
|
||||||
|
|
||||||
SecondaryTabBar {
|
SecondaryTabBar {
|
||||||
id: tabBar
|
id: tabBar
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.services
|
||||||
import qs.modules.common
|
import qs.modules.common
|
||||||
|
import qs.modules.common.widgets
|
||||||
import qs.modules.overlay
|
import qs.modules.overlay
|
||||||
import qs.modules.sidebarRight.volumeMixer
|
import qs.modules.sidebarRight.volumeMixer
|
||||||
|
|
||||||
@@ -10,15 +13,69 @@ StyledOverlayWidget {
|
|||||||
contentItem: Rectangle {
|
contentItem: Rectangle {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: Appearance.m3colors.m3surfaceContainer
|
color: Appearance.m3colors.m3surfaceContainer
|
||||||
property real padding: 16
|
radius: root.contentRadius
|
||||||
|
property real padding: 6
|
||||||
implicitHeight: 600
|
implicitHeight: 600
|
||||||
implicitWidth: 350
|
implicitWidth: 350
|
||||||
|
|
||||||
VolumeDialogContent {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
id: contentColumn
|
||||||
anchors.margins: parent.padding
|
anchors {
|
||||||
isSink: true
|
fill: parent
|
||||||
}
|
margins: parent.padding
|
||||||
|
}
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
SecondaryTabBar {
|
||||||
|
id: tabBar
|
||||||
|
|
||||||
|
currentIndex: Persistent.states.overlay.volumeMixer.tabIndex
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
Persistent.states.overlay.volumeMixer.tabIndex = tabBar.currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
SecondaryTabButton {
|
||||||
|
buttonIcon: "media_output"
|
||||||
|
buttonText: Translation.tr("Output")
|
||||||
|
}
|
||||||
|
SecondaryTabButton {
|
||||||
|
buttonIcon: "mic"
|
||||||
|
buttonText: Translation.tr("Input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwipeView {
|
||||||
|
id: swipeView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
currentIndex: Persistent.states.overlay.volumeMixer.tabIndex
|
||||||
|
onCurrentIndexChanged: {
|
||||||
|
Persistent.states.overlay.volumeMixer.tabIndex = swipeView.currentIndex;
|
||||||
|
}
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
PaddedVolumeDialogContent {
|
||||||
|
isSink: true
|
||||||
|
}
|
||||||
|
PaddedVolumeDialogContent {
|
||||||
|
isSink: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
component PaddedVolumeDialogContent: Item {
|
||||||
|
id: paddedVolumeDialogContent
|
||||||
|
property alias isSink: volDialogContent.isSink
|
||||||
|
property real padding: 12
|
||||||
|
implicitWidth: volDialogContent.implicitWidth + padding * 2
|
||||||
|
implicitHeight: volDialogContent.implicitHeight + padding * 2
|
||||||
|
|
||||||
|
VolumeDialogContent {
|
||||||
|
id: volDialogContent
|
||||||
|
anchors {
|
||||||
|
fill: parent
|
||||||
|
margins: paddedVolumeDialogContent.padding
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ ColumnLayout {
|
|||||||
Layout.topMargin: -22
|
Layout.topMargin: -22
|
||||||
Layout.leftMargin: 0
|
Layout.leftMargin: 0
|
||||||
Layout.rightMargin: 0
|
Layout.rightMargin: 0
|
||||||
|
color: Appearance.colors.colOutlineVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogSectionListView {
|
DialogSectionListView {
|
||||||
@@ -56,6 +57,7 @@ ColumnLayout {
|
|||||||
Layout.topMargin: -22
|
Layout.topMargin: -22
|
||||||
Layout.leftMargin: 0
|
Layout.leftMargin: 0
|
||||||
Layout.rightMargin: 0
|
Layout.rightMargin: 0
|
||||||
|
color: Appearance.colors.colOutlineVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
DialogSectionListView {
|
DialogSectionListView {
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ Item {
|
|||||||
sourceSize.height: size
|
sourceSize.height: size
|
||||||
source: {
|
source: {
|
||||||
let icon;
|
let icon;
|
||||||
icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]);
|
icon = AppSearch.guessIcon(root.node?.properties["application.icon-name"] ?? "");
|
||||||
if (AppSearch.iconExists(icon))
|
if (AppSearch.iconExists(icon))
|
||||||
return Quickshell.iconPath(icon, "image-missing");
|
return Quickshell.iconPath(icon, "image-missing");
|
||||||
icon = AppSearch.guessIcon(root.node.properties["node.name"]);
|
icon = AppSearch.guessIcon(root.node?.properties["node.name"] ?? "");
|
||||||
return Quickshell.iconPath(icon, "image-missing");
|
return Quickshell.iconPath(icon, "image-missing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ Item {
|
|||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
text: {
|
text: {
|
||||||
// application.name -> description -> name
|
// application.name -> description -> name
|
||||||
const app = root.node.properties["application.name"] ?? (root.node.description != "" ? root.node.description : root.node.name);
|
const app = root.node?.properties["application.name"] ?? (root.node.description != "" ? root.node.description : root.node.name);
|
||||||
const media = root.node.properties["media.name"];
|
const media = root.node.properties["media.name"];
|
||||||
return media != undefined ? `${app} • ${media}` : app;
|
return media != undefined ? `${app} • ${media}` : app;
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ Item {
|
|||||||
|
|
||||||
StyledSlider {
|
StyledSlider {
|
||||||
id: slider
|
id: slider
|
||||||
value: root.node.audio.volume
|
value: root.node?.audio.volume ?? 0
|
||||||
onMoved: root.node.audio.volume = value
|
onMoved: root.node.audio.volume = value
|
||||||
configuration: StyledSlider.Configuration.S
|
configuration: StyledSlider.Configuration.S
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ Singleton {
|
|||||||
// Reload files
|
// Reload files
|
||||||
fileMeminfo.reload()
|
fileMeminfo.reload()
|
||||||
fileStat.reload()
|
fileStat.reload()
|
||||||
fileCpuinfo.reload()
|
|
||||||
|
|
||||||
// Parse memory and swap usage
|
// Parse memory and swap usage
|
||||||
const textMeminfo = fileMeminfo.text()
|
const textMeminfo = fileMeminfo.text()
|
||||||
@@ -93,29 +92,6 @@ Singleton {
|
|||||||
previousCpuStats = { total, idle }
|
previousCpuStats = { total, idle }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse max CPU frequency
|
|
||||||
const textCpuinfo = fileCpuinfo.text()
|
|
||||||
// Try to find 'cpu max MHz', fallback to highest 'cpu MHz'
|
|
||||||
let maxMHz = 0
|
|
||||||
let match
|
|
||||||
// Try cpu max MHz (modern kernels)
|
|
||||||
match = textCpuinfo.match(/cpu max MHz\s*:\s*([\d.]+)/)
|
|
||||||
if (match) {
|
|
||||||
maxMHz = Number(match[1])
|
|
||||||
} else {
|
|
||||||
// Fallback: find all cpu MHz lines and take the max
|
|
||||||
let mhzRegex = /cpu MHz\s*:\s*([\d.]+)/g
|
|
||||||
let mhzMatch
|
|
||||||
let mhzList = []
|
|
||||||
while ((mhzMatch = mhzRegex.exec(textCpuinfo)) !== null) {
|
|
||||||
mhzList.push(Number(mhzMatch[1]))
|
|
||||||
}
|
|
||||||
if (mhzList.length > 0) {
|
|
||||||
maxMHz = Math.max.apply(null, mhzList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root.maxAvailableCpuString = maxMHz > 0 ? (maxMHz / 1000).toFixed(1) + "GHz" : "--"
|
|
||||||
|
|
||||||
root.updateHistories()
|
root.updateHistories()
|
||||||
interval = Config.options?.resources?.updateInterval ?? 3000
|
interval = Config.options?.resources?.updateInterval ?? 3000
|
||||||
}
|
}
|
||||||
@@ -123,5 +99,16 @@ Singleton {
|
|||||||
|
|
||||||
FileView { id: fileMeminfo; path: "/proc/meminfo" }
|
FileView { id: fileMeminfo; path: "/proc/meminfo" }
|
||||||
FileView { id: fileStat; path: "/proc/stat" }
|
FileView { id: fileStat; path: "/proc/stat" }
|
||||||
FileView { id: fileCpuinfo; path: "/proc/cpuinfo" }
|
|
||||||
|
Process {
|
||||||
|
id: findCpuMaxFreqProc
|
||||||
|
command: ["bash", "-c", "lscpu | grep 'CPU max MHz' | awk '{print $4}'"]
|
||||||
|
running: true
|
||||||
|
stdout: StdioCollector {
|
||||||
|
id: outputCollector
|
||||||
|
onStreamFinished: {
|
||||||
|
root.maxAvailableCpuString = (parseFloat(outputCollector.text) / 1000).toFixed(0) + " GHz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user