diff --git a/dots/.config/hypr/hyprland/execs.conf b/dots/.config/hypr/hyprland/execs.conf index 252e3afa8..e4f33f6aa 100644 --- a/dots/.config/hypr/hyprland/execs.conf +++ b/dots/.config/hypr/hyprland/execs.conf @@ -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 diff --git a/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml b/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml index 5c8124e0a..3a78e86ce 100644 --- a/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml +++ b/dots/.config/quickshell/ii/modules/cheatsheet/CheatsheetKeybinds.qml @@ -14,9 +14,50 @@ Item { property real padding: 4 implicitWidth: row.implicitWidth + padding * 2 implicitHeight: row.implicitHeight + padding * 2 + // Excellent symbol explaination and source : + // http://xahlee.info/comp/unicode_computing_symbols.html + // https://www.nerdfonts.com/cheat-sheet + property var macSymbolMap: ({ + "Ctrl": "󰘴", + "Alt": "󰘵", + "Shift": "󰘶", + "Space": "󱁐", + "Tab": "↹", + "Equal": "󰇼", + "Minus": "", + "Print": "", + "BackSpace": "󰭜", + "Delete": "⌦", + "Return": "󰌑", + "Period": ".", + "Escape": "⎋" + }) + property var functionSymbolMap: ({ + "F1": "󱊫", + "F2": "󱊬", + "F3": "󱊭", + "F4": "󱊮", + "F5": "󱊯", + "F6": "󱊰", + "F7": "󱊱", + "F8": "󱊲", + "F9": "󱊳", + "F10": "󱊴", + "F11": "󱊵", + "F12": "󱊶", + }) + + property var mouseSymbolMap: ({ + "mouse_up": "󱕐", + "mouse_down": "󱕑", + "mouse:272": "L󰍽", + "mouse:273": "R󰍽", + "Scroll ↑/↓": "󱕒", + "Page_↑/↓": "⇞/⇟", + }) property var keyBlacklist: ["Super_L"] - property var keySubstitutions: ({ + property var keySubstitutions: Object.assign({ "Super": "󰖳", "mouse_up": "Scroll ↓", // ikr, weird "mouse_down": "Scroll ↑", // trust me bro @@ -27,7 +68,14 @@ Item { "Hash": "#", "Return": "Enter", // "Shift": "", - }) + }, + !!Config.options.cheatsheet.superKey ? { + "Super": Config.options.cheatsheet.superKey, + }: {}, + Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {}, + Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {}, + Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {}, + ) Row { // Keybind columns id: row @@ -77,6 +125,17 @@ Item { var result = []; for (var i = 0; i < keybindSection.modelData.keybinds.length; i++) { const keybind = keybindSection.modelData.keybinds[i]; + + if (!Config.options.cheatsheet.splitButtons) { + + for (var j = 0; j < keybind.mods.length; j++) { + keybind.mods[j] = keySubstitutions[keybind.mods[j]] || keybind.mods[j]; + } + keybind.mods = [keybind.mods.join(' ') ] + keybind.mods[0] += !keyBlacklist.includes(keybind.key) && keybind.mods[0].length ? ' ' : '' + keybind.mods[0] += !keyBlacklist.includes(keybind.key) ? (keySubstitutions[keybind.key] || keybind.key) : '' + } + result.push({ "type": "keys", "mods": keybind.mods, @@ -108,17 +167,19 @@ Item { delegate: KeyboardKey { required property var modelData key: keySubstitutions[modelData] || modelData + pixelSize: Config.options.cheatsheet.fontSize.key } } StyledText { id: keybindPlus - visible: !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0 + visible: Config.options.cheatsheet.splitButtons && !keyBlacklist.includes(modelData.key) && modelData.mods.length > 0 text: "+" } KeyboardKey { id: keybindKey - visible: !keyBlacklist.includes(modelData.key) + visible: Config.options.cheatsheet.splitButtons && !keyBlacklist.includes(modelData.key) key: keySubstitutions[modelData.key] || modelData.key + pixelSize: Config.options.cheatsheet.fontSize.key color: Appearance.colors.colOnLayer0 } } @@ -134,7 +195,7 @@ Item { StyledText { id: commentText anchors.centerIn: parent - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller text: modelData.comment } } @@ -152,4 +213,4 @@ Item { } } -} \ No newline at end of file +} diff --git a/dots/.config/quickshell/ii/modules/common/Config.qml b/dots/.config/quickshell/ii/modules/common/Config.qml index 8e5d588ec..363c4c906 100644 --- a/dots/.config/quickshell/ii/modules/common/Config.qml +++ b/dots/.config/quickshell/ii/modules/common/Config.qml @@ -267,6 +267,22 @@ Singleton { property int suspend: 3 } + property JsonObject cheatsheet: JsonObject { + // Use a nerdfont to see the icons + // 0: 󰖳 | 1: 󰌽 | 2: 󰘳 | 3:  | 4: 󰨡 + // 5:  | 6:  | 7: 󰣇 | 8:  | 9:  + // 10:  | 11:  | 12:  | 13:  | 14: 󱄛 + property string superKey: "󰖳" + property bool useMacSymbol: false + property bool splitButtons: true + property bool useMouseSymbol: false + property bool useFnSymbol: false + property JsonObject fontSize: JsonObject { + property int key: Appearance.font.pixelSize.smaller + property int comment: Appearance.font.pixelSize.smaller + } + } + property JsonObject conflictKiller: JsonObject { property bool autoKillNotificationDaemons: false property bool autoKillTrays: false @@ -364,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 { @@ -395,6 +412,7 @@ Singleton { property JsonObject resources: JsonObject { property int updateInterval: 3000 + property int historyLength: 60 } property JsonObject musicRecognition: JsonObject { diff --git a/dots/.config/quickshell/ii/modules/common/Persistent.qml b/dots/.config/quickshell/ii/modules/common/Persistent.qml index 5335deb9d..2969919c8 100644 --- a/dots/.config/quickshell/ii/modules/common/Persistent.qml +++ b/dots/.config/quickshell/ii/modules/common/Persistent.qml @@ -80,7 +80,7 @@ Singleton { } property JsonObject overlay: JsonObject { - property list open: ["crosshair"] + property list open: ["crosshair", "recorder", "volumeMixer", "resources"] property JsonObject crosshair: JsonObject { property bool pinned: false property bool clickthrough: true @@ -90,14 +90,28 @@ Singleton { property JsonObject recorder: JsonObject { property bool pinned: false property bool clickthrough: false - property real x: 100 - property real y: 130 + property real x: 80 + property real y: 80 + } + property JsonObject resources: JsonObject { + property bool pinned: false + property bool clickthrough: true + property real x: 1500 + property real y: 770 + property int tabIndex: 0 } property JsonObject volumeMixer: JsonObject { property bool pinned: false property bool clickthrough: false - property real x: 100 - property real y: 320 + property real x: 80 + 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 } } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/Graph.qml b/dots/.config/quickshell/ii/modules/common/widgets/Graph.qml new file mode 100644 index 000000000..4747e5665 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/Graph.qml @@ -0,0 +1,51 @@ +import QtQuick +import qs.modules.common +import qs.modules.common.functions + +/* + * Simple one value line graph + */ +Canvas { + id: root + + enum Alignment { Left, Right } + + required property list values + property int points: values.length + property color color: Appearance.colors.colPrimary + property real fillOpacity: 0.5 + property var alignment: Graph.Alignment.Left + + onValuesChanged: root.requestPaint() + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, width, height) + if (!root.values || root.values.length < 2) + return + + var n = root.points + var dx = width / (n - 1) + ctx.strokeStyle = root.color + ctx.fillStyle = ColorUtils.transparentize(root.color, 1 - root.fillOpacity) + ctx.lineWidth = 2 + ctx.beginPath() + for (var i = 0; i < n; ++i) { + var valueIndex = (root.alignment === Graph.Alignment.Right) ? root.values.length - n + i : i + if (valueIndex < 0 || valueIndex >= root.values.length) { + continue; // No data for this point + } + var x = i * dx + var norm = root.values[valueIndex] // already in 0-1 range + var y = height - norm * height + if (valueIndex === 0) { + ctx.moveTo(x, height) + ctx.lineTo(x, y) + } else { + ctx.lineTo(x, y) + } + } + ctx.stroke() + ctx.lineTo(width, height) + ctx.fill() + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml index de9ce5603..080439613 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml @@ -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 { 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/common/widgets/KeyboardKey.qml b/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml index 14c75c62d..cdb287aca 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml @@ -11,6 +11,7 @@ Rectangle { property real extraBottomBorderWidth: 2 property color borderColor: Appearance.colors.colOnLayer0 property real borderRadius: 5 + property real pixelSize: Appearance.font.pixelSize.smaller property color keyColor: Appearance.m3colors.m3surfaceContainerLow implicitWidth: keyFace.implicitWidth + borderWidth * 2 implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth @@ -35,7 +36,7 @@ Rectangle { id: keyText anchors.centerIn: parent font.family: Appearance.font.family.monospace - font.pixelSize: Appearance.font.pixelSize.smaller + font.pixelSize: root.pixelSize text: key } } diff --git a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml new file mode 100644 index 000000000..dcb490048 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml @@ -0,0 +1,53 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import qs.modules.common +import qs.modules.common.models + +TabBar { + id: root + property real indicatorPadding: 8 + Layout.fillWidth: true + + background: Item { + WheelHandler { + onWheel: (event) => { + if (event.angleDelta.y < 0) root.incrementCurrentIndex(); + else if (event.angleDelta.y > 0) root.decrementCurrentIndex(); + } + acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad + } + + Rectangle { + id: activeIndicator + z: 9999 + anchors.bottom: parent.bottom + topLeftRadius: height + topRightRadius: height + bottomLeftRadius: 0 + bottomRightRadius: 0 + color: Appearance.colors.colPrimary + // Animation + property real baseWidth: root.width / root.count + AnimatedTabIndexPair { + id: idxPair + index: root.currentIndex + } + height: 3 + x: Math.min(idxPair.idx1, idxPair.idx2) * baseWidth + root.indicatorPadding + width: ((Math.max(idxPair.idx1, idxPair.idx2) + 1) * baseWidth - root.indicatorPadding) - x + } + + Rectangle { // Tabbar bottom border + id: tabBarBottomBorder + z: 9998 + anchors.bottom: parent.bottom + height: 1 + anchors { + left: parent.left + right: parent.right + } + color: Appearance.colors.colOutlineVariant + } + } +} diff --git a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml index b1036c777..b0bc9b4de 100644 --- a/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml +++ b/dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml @@ -1,6 +1,6 @@ import qs.modules.common -import qs.modules.common.widgets import qs.modules.common.functions +import qs.modules.common.widgets import Qt5Compat.GraphicalEffects import QtQuick import QtQuick.Controls @@ -10,14 +10,12 @@ TabButton { id: root property string buttonText property string buttonIcon - property bool selected: false property int rippleDuration: 1200 - height: buttonBackground.height property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2 - property color colBackground: ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1) - property color colBackgroundHover: Appearance.colors.colLayer1Hover - property color colRipple: Appearance.colors.colLayer1Active + property color colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer) + property color colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, root.checked ? 1 : 0.95) + property color colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95) PointingHandInteraction {} @@ -91,8 +89,12 @@ TabButton { background: Rectangle { id: buttonBackground + anchors { + fill: parent + margins: 3 + } radius: Appearance?.rounding.normal - implicitHeight: 37 + implicitHeight: 42 color: (root.hovered ? root.colBackgroundHover : root.colBackground) layer.enabled: true layer.effect: OpacityMask { @@ -156,8 +158,8 @@ TabButton { verticalAlignment: Text.AlignVCenter text: buttonIcon iconSize: Appearance.font.pixelSize.huge - fill: selected ? 1 : 0 - color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 + fill: root.checked ? 1 : 0 + color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) } @@ -167,7 +169,7 @@ TabButton { id: buttonTextWidget verticalAlignment: Text.AlignVCenter font.pixelSize: Appearance.font.pixelSize.small - color: selected ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 + color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1 text: buttonText Behavior on color { animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this) diff --git a/dots/.config/quickshell/ii/modules/overlay/Overlay.qml b/dots/.config/quickshell/ii/modules/overlay/Overlay.qml index 5054b9d1c..71ba510c6 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,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 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 22746c4de..b228deda6 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayContext.qml @@ -6,9 +6,11 @@ Singleton { id: root readonly property list availableWidgets: [ - { identifier: "crosshair", materialSymbol: "point_scan" }, - { identifier: "volumeMixer", materialSymbol: "volume_up" }, { identifier: "recorder", materialSymbol: "screen_record" }, + { identifier: "volumeMixer", materialSymbol: "volume_up" }, + { identifier: "crosshair", materialSymbol: "point_scan" }, + { identifier: "fpsLimiter", materialSymbol: "animation" }, + { identifier: "resources", materialSymbol: "browse_activity" } ] readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0 diff --git a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml index 39df62ac3..fc75d1455 100644 --- a/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml +++ b/dots/.config/quickshell/ii/modules/overlay/OverlayWidgetDelegateChooser.qml @@ -8,7 +8,9 @@ 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 DelegateChooser { id: root @@ -16,5 +18,7 @@ DelegateChooser { DelegateChoice { roleValue: "crosshair"; Crosshair {} } DelegateChoice { roleValue: "volumeMixer"; VolumeMixer {} } + DelegateChoice { roleValue: "fpsLimiter"; FpsLimiter {} } DelegateChoice { roleValue: "recorder"; Recorder {} } + DelegateChoice { roleValue: "resources"; Resources {} } } diff --git a/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml b/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml index 5e6a0ce3f..a8b6fdbaf 100644 --- a/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml +++ b/dots/.config/quickshell/ii/modules/overlay/StyledOverlayWidget.qml @@ -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 @@ -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,25 +120,23 @@ 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 + Layout.fillWidth: true + property real padding: 6 implicitWidth: titleBarRow.implicitWidth + padding * 2 implicitHeight: titleBarRow.implicitHeight + padding * 2 - color: Appearance.m3colors.m3surfaceContainer - border.color: Appearance.colors.colOutlineVariant - border.width: 1 + color: root.fancyBorders ? "transparent" : Appearance.colors.colLayer1 + // border.color: Appearance.colors.colOutlineVariant + // border.width: 1 RowLayout { id: titleBarRow @@ -136,8 +144,9 @@ AbstractOverlayWidget { fill: parent margins: titleBar.padding leftMargin: titleBar.padding + 8 + bottomMargin: root.fancyBorders ? 0 : titleBar.padding } - spacing: 0 + spacing: 2 MaterialSymbol { text: root.materialSymbol @@ -153,6 +162,7 @@ AbstractOverlayWidget { } TitlebarButton { + visible: root.showCenterButton materialSymbol: "recenter" onClicked: root.center() StyledToolTip { @@ -161,6 +171,7 @@ AbstractOverlayWidget { } TitlebarButton { + visible: (root.pinned && root.showClickabilityButton) materialSymbol: "mouse" toggled: !root.clickthrough onClicked: root.toggleClickthrough() @@ -191,7 +202,10 @@ AbstractOverlayWidget { // Content Item { id: contentContainer - anchors.horizontalCenter: parent.horizontalCenter + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: root.fancyBorders ? root.padding : 0 + Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter implicitWidth: root.contentItem.implicitWidth implicitHeight: root.contentItem.implicitHeight children: [root.contentItem] diff --git a/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml b/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml index abb1cbf26..4a92aa43e 100644 --- a/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml +++ b/dots/.config/quickshell/ii/modules/overlay/crosshair/Crosshair.qml @@ -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 + } } 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..7628d58be --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiter.qml @@ -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 + } +} 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..a67bf40c1 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/fpsLimiter/FpsLimiterContent.qml @@ -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(); + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml b/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml index d32adedea..e853819b4 100644 --- a/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml +++ b/dots/.config/quickshell/ii/modules/overlay/recorder/Recorder.qml @@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound import QtQuick import QtQuick.Layouts import Quickshell -import Quickshell.Hyprland import qs import qs.modules.common import qs.modules.common.widgets @@ -14,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 diff --git a/dots/.config/quickshell/ii/modules/overlay/resources/Resources.qml b/dots/.config/quickshell/ii/modules/overlay/resources/Resources.qml new file mode 100644 index 000000000..fe32d2a91 --- /dev/null +++ b/dots/.config/quickshell/ii/modules/overlay/resources/Resources.qml @@ -0,0 +1,135 @@ +pragma ComponentBehavior: Bound +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import Quickshell +import Quickshell.Hyprland +import Qt5Compat.GraphicalEffects +import Qt.labs.synchronizer +import qs +import qs.services +import qs.modules.common +import qs.modules.common.widgets +import qs.modules.overlay + +StyledOverlayWidget { + id: root + property list resources: [ + { + "icon": "planner_review", + "name": Translation.tr("CPU"), + "history": ResourceUsage.cpuUsageHistory, + "maxAvailableString": ResourceUsage.maxAvailableCpuString + }, + { + "icon": "memory", + "name": Translation.tr("RAM"), + "history": ResourceUsage.memoryUsageHistory, + "maxAvailableString": ResourceUsage.maxAvailableMemoryString + }, + { + "icon": "swap_horiz", + "name": Translation.tr("Swap"), + "history": ResourceUsage.swapUsageHistory, + "maxAvailableString": ResourceUsage.maxAvailableSwapString + }, + ] + + contentItem: Rectangle { + id: contentItem + anchors.centerIn: parent + color: Appearance.m3colors.m3surfaceContainer + radius: root.contentRadius + property real padding: 4 + implicitWidth: 350 + implicitHeight: 200 + // implicitHeight: contentColumn.implicitHeight + padding * 2 + ColumnLayout { + id: contentColumn + anchors { + fill: parent + margins: parent.padding + } + spacing: 8 + + SecondaryTabBar { + id: tabBar + + currentIndex: Persistent.states.overlay.resources.tabIndex + onCurrentIndexChanged: { + Persistent.states.overlay.resources.tabIndex = tabBar.currentIndex; + } + + Repeater { + model: root.resources.length + delegate: SecondaryTabButton { + required property int index + property var modelData: root.resources[index] + buttonIcon: modelData.icon + buttonText: modelData.name + } + } + } + + ResourceSummary { + Layout.margins: 8 + history: root.resources[tabBar.currentIndex]?.history ?? [] + maxAvailableString: root.resources[tabBar.currentIndex]?.maxAvailableString ?? "--" + } + } + } + + component ResourceSummary: RowLayout { + id: resourceSummary + required property list history + required property string maxAvailableString + Layout.fillWidth: true + Layout.fillHeight: true + spacing: 12 + + ColumnLayout { + spacing: 2 + StyledText { + text: (resourceSummary.history[resourceSummary.history.length - 1] * 100).toFixed(1) + "%" + font { + family: Appearance.font.family.numbers + variableAxes: Appearance.font.variableAxes.numbers + pixelSize: Appearance.font.pixelSize.huge + } + } + StyledText { + text: Translation.tr("of %1").arg(resourceSummary.maxAvailableString) + font { + // family: Appearance.font.family.numbers + // variableAxes: Appearance.font.variableAxes.numbers + pixelSize: Appearance.font.pixelSize.smallie + } + color: Appearance.colors.colSubtext + } + Item { + Layout.fillHeight: true + } + } + Rectangle { + id: graphBg + Layout.fillWidth: true + Layout.fillHeight: true + radius: Appearance.rounding.small + color: Appearance.colors.colSecondaryContainer + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: graphBg.width + height: graphBg.height + radius: graphBg.radius + } + } + Graph { + anchors.fill: parent + values: root.resources[tabBar.currentIndex]?.history ?? [] + points: ResourceUsage.historyLength + alignment: Graph.Alignment.Right + } + } + } +} diff --git a/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml b/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml index 14aa53006..68c572579 100644 --- a/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml +++ b/dots/.config/quickshell/ii/modules/overlay/volumeMixer/VolumeMixer.qml @@ -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,49 @@ StyledOverlayWidget { contentItem: Rectangle { anchors.centerIn: parent color: Appearance.m3colors.m3surfaceContainer + radius: root.contentRadius property real padding: 16 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 + + VolumeDialogContent { isSink: true } + VolumeDialogContent { isSink: false } + } + } } } diff --git a/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml b/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml index 811cf7ca6..7d51dbe12 100644 --- a/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml @@ -90,4 +90,6 @@ ContentPage { } } + + } diff --git a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml index c26cce455..c57406a7f 100644 --- a/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml +++ b/dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml @@ -7,6 +7,98 @@ import qs.modules.common.widgets ContentPage { forceWidth: true + ContentSection { + icon: "keyboard" + title: Translation.tr("Cheat sheet") + + ContentSubsection { + title: Translation.tr("Super key symbol") + tooltip: Translation.tr("You can also manually edit cheatsheet.superKey") + ConfigSelectionArray { + currentValue: Config.options.cheatsheet.superKey + onSelected: newValue => { + Config.options.cheatsheet.superKey = newValue; + } + // Use a nerdfont to see the icons + options: ([ + "󰖳", "", "󰨡", "", "󰌽", "󰣇", "", "", "", + "", "", "󱄛", "", "", "⌘", "󰀲", "󰟍", "" + ]).map(icon => { return { + displayName: icon, + value: icon + } + }) + } + } + + ConfigSwitch { + buttonIcon: "󰘵" + text: Translation.tr("Use macOS-like symbols for mods keys") + checked: Config.options.cheatsheet.useMacSymbol + onCheckedChanged: { + Config.options.cheatsheet.useMacSymbol = checked; + } + StyledToolTip { + text: Translation.tr("e.g. 󰘴 for Ctrl, 󰘵 for Alt, 󰘶 for Shift, etc") + } + } + + ConfigSwitch { + buttonIcon: "󱊶" + text: Translation.tr("Use symbols for function keys") + checked: Config.options.cheatsheet.useFnSymbol + onCheckedChanged: { + Config.options.cheatsheet.useFnSymbol = checked; + } + StyledToolTip { + text: Translation.tr("e.g. 󱊫 for F1, 󱊶 for F12") + } + } + ConfigSwitch { + buttonIcon: "󰍽" + text: Translation.tr("Use symbols for mouse") + checked: Config.options.cheatsheet.useMouseSymbol + onCheckedChanged: { + Config.options.cheatsheet.useMouseSymbol = checked; + } + StyledToolTip { + text: Translation.tr("Replace 󱕐 for \"Scroll ↓\", 󱕑 \"Scroll ↑\", L󰍽 \"LMB\", R󰍽 \"RMB\", 󱕒 \"Scroll ↑/↓\" and ⇞/⇟ for \"Page_↑/↓\"") + } + } + ConfigSwitch { + buttonIcon: "highlight_keyboard_focus" + text: Translation.tr("Split buttons") + checked: Config.options.cheatsheet.splitButtons + onCheckedChanged: { + Config.options.cheatsheet.splitButtons = checked; + } + StyledToolTip { + text: Translation.tr("Display modifiers and keys in multiple keycap (e.g., \"Ctrl + A\" instead of \"Ctrl A\" or \"󰘴 + A\" instead of \"󰘴 A\")") + } + + } + + ConfigSpinBox { + text: Translation.tr("Keybind font size") + value: Config.options.cheatsheet.fontSize.key + from: 8 + to: 30 + stepSize: 1 + onValueChanged: { + Config.options.cheatsheet.fontSize.key = value; + } + } + ConfigSpinBox { + text: Translation.tr("Description font size") + value: Config.options.cheatsheet.fontSize.comment + from: 8 + to: 30 + stepSize: 1 + onValueChanged: { + Config.options.cheatsheet.fontSize.comment = value; + } + } + } ContentSection { icon: "call_to_action" title: Translation.tr("Dock") @@ -647,4 +739,5 @@ ContentPage { } } } + } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml b/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml index ca7723db3..a62a9616c 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/pomodoro/PomodoroWidget.qml @@ -7,7 +7,6 @@ import QtQuick.Layouts Item { id: root - property int currentTab: 0 property var tabButtonList: [ {"name": Translation.tr("Pomodoro"), "icon": "search_activity"}, {"name": Translation.tr("Stopwatch"), "icon": "timer"} @@ -17,20 +16,20 @@ Item { Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs if (event.key === Qt.Key_PageDown) { - currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + tabBar.incrementCurrentIndex(); } else if (event.key === Qt.Key_PageUp) { - currentTab = Math.max(currentTab - 1, 0) + tabBar.decrementCurrentIndex(); } event.accepted = true } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S - if (currentTab === 0) { + if (tabBar.currentIndex === 0) { TimerService.togglePomodoro() } else { TimerService.toggleStopwatch() } event.accepted = true } else if (event.key === Qt.Key_R) { // Reset with R - if (currentTab === 0) { + if (tabBar.currentIndex === 0) { TimerService.resetPomodoro() } else { TimerService.stopwatchReset() @@ -46,82 +45,19 @@ Item { anchors.fill: parent spacing: 0 - TabBar { + SecondaryTabBar { id: tabBar - Layout.fillWidth: true - currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex - - background: Item { - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) - else if (event.angleDelta.y > 0) - tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } + currentIndex: swipeView.currentIndex Repeater { model: root.tabButtonList delegate: SecondaryTabButton { - selected: (index == currentTab) buttonText: modelData.name buttonIcon: modelData.icon } } } - Item { // Tab indicator - id: tabIndicator - Layout.fillWidth: true - height: 3 - property bool enableIndicatorAnimation: false - Connections { - target: root - function onCurrentTabChanged() { - tabIndicator.enableIndicatorAnimation = true - } - } - - Rectangle { - id: indicator - property int tabCount: root.tabButtonList.length - property real fullTabSize: root.width / tabCount; - property real targetWidth: tabBar.contentItem.children[0].children[tabBar.currentIndex].tabContentWidth - - implicitWidth: targetWidth - anchors { - top: parent.top - bottom: parent.bottom - } - - x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 - - color: Appearance.colors.colPrimary - radius: Appearance.rounding.full - - Behavior on x { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Behavior on implicitWidth { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - } - } - - Rectangle { // Tabbar bottom border - id: tabBarBottomBorder - Layout.fillWidth: true - height: 1 - color: Appearance.colors.colOutlineVariant - } - SwipeView { id: swipeView Layout.topMargin: 10 @@ -129,11 +65,7 @@ Item { Layout.fillHeight: true spacing: 10 clip: true - currentIndex: currentTab - onCurrentIndexChanged: { - tabIndicator.enableIndicatorAnimation = true - currentTab = currentIndex - } + currentIndex: tabBar.currentIndex // Tabs PomodoroTimer {} diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml b/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml index 99b0100f5..36c885523 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/todo/TodoWidget.qml @@ -7,7 +7,6 @@ import QtQuick.Layouts Item { id: root - property int currentTab: 0 property var tabButtonList: [{"icon": "checklist", "name": Translation.tr("Unfinished")}, {"name": Translation.tr("Done"), "icon": "check_circle"}] property bool showAddDialog: false property int dialogMargins: 20 @@ -17,9 +16,9 @@ Item { Keys.onPressed: (event) => { if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { if (event.key === Qt.Key_PageDown) { - currentTab = Math.min(currentTab + 1, root.tabButtonList.length - 1) + tabBar.incrementCurrentIndex(); } else if (event.key === Qt.Key_PageUp) { - currentTab = Math.max(currentTab - 1, 0) + tabBar.decrementCurrentIndex(); } event.accepted = true; } @@ -39,82 +38,19 @@ Item { anchors.fill: parent spacing: 0 - TabBar { + SecondaryTabBar { id: tabBar - Layout.fillWidth: true - currentIndex: currentTab - onCurrentIndexChanged: currentTab = currentIndex - - background: Item { - WheelHandler { - onWheel: (event) => { - if (event.angleDelta.y < 0) - tabBar.currentIndex = Math.min(tabBar.currentIndex + 1, root.tabButtonList.length - 1) - else if (event.angleDelta.y > 0) - tabBar.currentIndex = Math.max(tabBar.currentIndex - 1, 0) - } - acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad - } - } + currentIndex: swipeView.currentIndex Repeater { model: root.tabButtonList delegate: SecondaryTabButton { - selected: (index == currentTab) buttonText: modelData.name buttonIcon: modelData.icon } } } - Item { // Tab indicator - id: tabIndicator - Layout.fillWidth: true - height: 3 - property bool enableIndicatorAnimation: false - Connections { - target: root - function onCurrentTabChanged() { - tabIndicator.enableIndicatorAnimation = true - } - } - - Rectangle { - id: indicator - property int tabCount: root.tabButtonList.length - property real fullTabSize: root.width / tabCount; - property real targetWidth: tabBar?.contentItem?.children[0]?.children[tabBar.currentIndex]?.tabContentWidth ?? 0 - - implicitWidth: targetWidth - anchors { - top: parent.top - bottom: parent.bottom - } - - x: tabBar.currentIndex * fullTabSize + (fullTabSize - targetWidth) / 2 - - color: Appearance.colors.colPrimary - radius: Appearance.rounding.full - - Behavior on x { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - - Behavior on implicitWidth { - enabled: tabIndicator.enableIndicatorAnimation - animation: Appearance.animation.elementMove.numberAnimation.createObject(this) - } - } - } - - Rectangle { // Tabbar bottom border - id: tabBarBottomBorder - Layout.fillWidth: true - height: 1 - color: Appearance.colors.colOutlineVariant - } - SwipeView { id: swipeView Layout.topMargin: 10 @@ -122,11 +58,7 @@ Item { Layout.fillHeight: true spacing: 10 clip: true - currentIndex: currentTab - onCurrentIndexChanged: { - tabIndicator.enableIndicatorAnimation = true - currentTab = currentIndex - } + currentIndex: tabBar.currentIndex // To Do tab TaskList { @@ -215,7 +147,7 @@ Item { Todo.addTask(todoInput.text) todoInput.text = "" root.showAddDialog = false - root.currentTab = 0 // Show unfinished tasks + tabBar.setCurrentIndex(0) // Show unfinished tasks } } diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml index 56f524968..a276128d9 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeDialogContent.qml @@ -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 { diff --git a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml index 503f83af0..a00335fb0 100644 --- a/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml +++ b/dots/.config/quickshell/ii/modules/sidebarRight/volumeMixer/VolumeMixerEntry.qml @@ -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 } diff --git a/dots/.config/quickshell/ii/services/ResourceUsage.qml b/dots/.config/quickshell/ii/services/ResourceUsage.qml index 650528408..df823adf7 100644 --- a/dots/.config/quickshell/ii/services/ResourceUsage.qml +++ b/dots/.config/quickshell/ii/services/ResourceUsage.qml @@ -10,17 +10,55 @@ import Quickshell.Io * Simple polled resource usage service with RAM, Swap, and CPU usage. */ Singleton { - property double memoryTotal: 1 - property double memoryFree: 1 - property double memoryUsed: memoryTotal - memoryFree - property double memoryUsedPercentage: memoryUsed / memoryTotal - property double swapTotal: 1 - property double swapFree: 1 - property double swapUsed: swapTotal - swapFree - property double swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0 - property double cpuUsage: 0 + id: root + property real memoryTotal: 1 + property real memoryFree: 0 + property real memoryUsed: memoryTotal - memoryFree + property real memoryUsedPercentage: memoryUsed / memoryTotal + property real swapTotal: 1 + property real swapFree: 0 + property real swapUsed: swapTotal - swapFree + property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0 + property real cpuUsage: 0 property var previousCpuStats + property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal) + property string maxAvailableSwapString: kbToGbString(ResourceUsage.swapTotal) + property string maxAvailableCpuString: "--" + + readonly property int historyLength: Config?.options.resources.historyLength ?? 60 + property list cpuUsageHistory: [] + property list memoryUsageHistory: [] + property list swapUsageHistory: [] + + function kbToGbString(kb) { + return (kb / (1024 * 1024)).toFixed(1) + " GB"; + } + + function updateMemoryUsageHistory() { + memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage] + if (memoryUsageHistory.length > historyLength) { + memoryUsageHistory.shift() + } + } + function updateSwapUsageHistory() { + swapUsageHistory = [...swapUsageHistory, swapUsedPercentage] + if (swapUsageHistory.length > historyLength) { + swapUsageHistory.shift() + } + } + function updateCpuUsageHistory() { + cpuUsageHistory = [...cpuUsageHistory, cpuUsage] + if (cpuUsageHistory.length > historyLength) { + cpuUsageHistory.shift() + } + } + function updateHistories() { + updateMemoryUsageHistory() + updateSwapUsageHistory() + updateCpuUsageHistory() + } + Timer { interval: 1 running: true @@ -53,10 +91,24 @@ Singleton { previousCpuStats = { total, idle } } + + root.updateHistories() interval = Config.options?.resources?.updateInterval ?? 3000 } } FileView { id: fileMeminfo; path: "/proc/meminfo" } FileView { id: fileStat; path: "/proc/stat" } + + 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" + } + } + } }