Merge branch 'end-4:main' into fedora

This commit is contained in:
ririko6z
2025-11-08 13:47:13 +08:00
committed by GitHub
19 changed files with 283 additions and 62 deletions
+2
View File
@@ -23,3 +23,5 @@ exec-once = wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsCo
# Cursor
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 bool openingZoomAnimation: true
property bool darkenScreen: true
property real clickthroughOpacity: 0.7
}
property JsonObject overview: JsonObject {
@@ -85,7 +85,7 @@ Singleton {
property bool pinned: false
property bool clickthrough: true
property real x: 835
property real y: 490
property real y: 483
}
property JsonObject recorder: JsonObject {
property bool pinned: false
@@ -104,7 +104,14 @@ Singleton {
property bool pinned: false
property bool clickthrough: false
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 {
id: buttonBackground
topLeftRadius: root.leftRadius
@@ -130,6 +130,9 @@ Button {
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
border.width: root.tabbedTo ? 2 : 0
border.color: Appearance.colors.colSecondary
}
contentItem: StyledText {
@@ -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 {} }
}
@@ -21,6 +21,9 @@ AbstractOverlayWidget {
id: root
required property Item contentItem
property bool fancyBorders: true
property bool showCenterButton: false
property bool showClickabilityButton: true
required property var modelData
readonly property string identifier: modelData.identifier
@@ -29,6 +32,8 @@ AbstractOverlayWidget {
property var persistentStateEntry: Persistent.states.overlay[identifier]
property real radius: Appearance.rounding.windowRounding
property real minWidth: 250
property real padding: 6
property real contentRadius: radius - padding
draggable: GlobalStates.overlayOpen
x: Math.round(persistentStateEntry.x) // Round or it'll be blurry
@@ -38,9 +43,10 @@ AbstractOverlayWidget {
drag {
minimumX: 0
minimumY: 0
maximumX: root.parent.width - root.width
maximumY: root.parent.height - root.height
maximumX: root.parent?.width - root.width
maximumY: root.parent?.height - root.height
}
opacity: (GlobalStates.overlayOpen || !clickthrough) ? 1.0 : Config.options.overlay.clickthroughOpacity
// Guarded states & registration funcs
property bool open: Persistent.states.overlay.open
@@ -83,7 +89,7 @@ AbstractOverlayWidget {
function center() {
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.y = targetY
root.savePosition(targetX, targetY)
@@ -96,11 +102,15 @@ AbstractOverlayWidget {
Rectangle {
id: border
anchors.fill: parent
color: "transparent"
color: (root.fancyBorders && GlobalStates.overlayOpen) ? Appearance.colors.colLayer1 : "transparent"
radius: root.radius
border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)
border.width: 1
Behavior on color {
animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)
}
layer.enabled: GlobalStates.overlayOpen
layer.effect: OpacityMask {
maskSource: Rectangle {
@@ -110,37 +120,34 @@ AbstractOverlayWidget {
}
}
Column {
ColumnLayout {
id: contentColumn
z: -1
z: root.fancyBorders ? 0 : -1
anchors.fill: parent
spacing: 0
// Title bar
Rectangle {
id: titleBar
opacity: GlobalStates.overlayOpen ? 1 : 0
anchors {
left: parent.left
right: parent.right
}
property real padding: 2
implicitWidth: titleBarRow.implicitWidth + padding * 2
implicitHeight: titleBarRow.implicitHeight + padding * 2
color: Appearance.m3colors.m3surfaceContainer
border.color: Appearance.colors.colOutlineVariant
border.width: 1
Layout.fillWidth: true
implicitWidth: titleBarRow.implicitWidth + root.padding * 2
implicitHeight: titleBarRow.implicitHeight + root.padding * 2
color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1
// border.color: Appearance.colors.colOutlineVariant
// border.width: 1
RowLayout {
id: titleBarRow
anchors {
fill: parent
margins: titleBar.padding
leftMargin: titleBar.padding + 8
margins: root.padding
}
spacing: 0
spacing: 2
MaterialSymbol {
text: root.materialSymbol
Layout.leftMargin: 6
iconSize: 20
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: 4
@@ -153,6 +160,7 @@ AbstractOverlayWidget {
}
TitlebarButton {
visible: root.showCenterButton
materialSymbol: "recenter"
onClicked: root.center()
StyledToolTip {
@@ -161,6 +169,7 @@ AbstractOverlayWidget {
}
TitlebarButton {
visible: (root.pinned && root.showClickabilityButton)
materialSymbol: "mouse"
toggled: !root.clickthrough
onClicked: root.toggleClickthrough()
@@ -191,7 +200,11 @@ AbstractOverlayWidget {
// Content
Item {
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
implicitHeight: root.contentItem.implicitHeight
children: [root.contentItem]
@@ -1,9 +1,18 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.modules.common
import qs.modules.overlay
StyledOverlayWidget {
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 {
id: contentItem
anchors.centerIn: parent
radius: root.contentRadius
color: Appearance.m3colors.m3surfaceContainer
property real padding: 8
implicitHeight: contentColumn.implicitHeight + padding * 2
@@ -75,7 +76,7 @@ StyledOverlayWidget {
colRipple: Appearance.colors.colLayer3Active
onClicked: {
GlobalStates.overlayOpen = false;
Qt.openUrlExternally(Directories.videos);
Qt.openUrlExternally(`file://${Config.options.screenRecord.savePath}`);
}
contentItem: Row {
anchors.centerIn: parent
@@ -39,6 +39,7 @@ StyledOverlayWidget {
id: contentItem
anchors.centerIn: parent
color: Appearance.m3colors.m3surfaceContainer
radius: root.contentRadius
property real padding: 4
implicitWidth: 350
implicitHeight: 200
@@ -49,7 +50,7 @@ StyledOverlayWidget {
fill: parent
margins: parent.padding
}
spacing: 10
spacing: 8
SecondaryTabBar {
id: tabBar
@@ -1,7 +1,10 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import qs.services
import qs.modules.common
import qs.modules.common.widgets
import qs.modules.overlay
import qs.modules.sidebarRight.volumeMixer
@@ -10,15 +13,69 @@ StyledOverlayWidget {
contentItem: Rectangle {
anchors.centerIn: parent
color: Appearance.m3colors.m3surfaceContainer
property real padding: 16
radius: root.contentRadius
property real padding: 6
implicitHeight: 600
implicitWidth: 350
VolumeDialogContent {
anchors.fill: parent
anchors.margins: parent.padding
isSink: true
}
ColumnLayout {
id: contentColumn
anchors {
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.leftMargin: 0
Layout.rightMargin: 0
color: Appearance.colors.colOutlineVariant
}
DialogSectionListView {
@@ -56,6 +57,7 @@ ColumnLayout {
Layout.topMargin: -22
Layout.leftMargin: 0
Layout.rightMargin: 0
color: Appearance.colors.colOutlineVariant
}
DialogSectionListView {
@@ -28,10 +28,10 @@ Item {
sourceSize.height: size
source: {
let icon;
icon = AppSearch.guessIcon(root.node.properties["application.icon-name"]);
icon = AppSearch.guessIcon(root.node?.properties["application.icon-name"] ?? "");
if (AppSearch.iconExists(icon))
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");
}
}
@@ -47,7 +47,7 @@ Item {
elide: Text.ElideRight
text: {
// 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"];
return media != undefined ? `${app} ${media}` : app;
}
@@ -55,7 +55,7 @@ Item {
StyledSlider {
id: slider
value: root.node.audio.volume
value: root.node?.audio.volume ?? 0
onMoved: root.node.audio.volume = value
configuration: StyledSlider.Configuration.S
}
@@ -67,7 +67,6 @@ Singleton {
// Reload files
fileMeminfo.reload()
fileStat.reload()
fileCpuinfo.reload()
// Parse memory and swap usage
const textMeminfo = fileMeminfo.text()
@@ -93,29 +92,6 @@ Singleton {
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()
interval = Config.options?.resources?.updateInterval ?? 3000
}
@@ -123,5 +99,16 @@ Singleton {
FileView { id: fileMeminfo; path: "/proc/meminfo" }
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"
}
}
}
}