From b4697235c041d9cc07f9b6cae080b1249d2956ae Mon Sep 17 00:00:00 2001 From: ferreo Date: Mon, 14 Jul 2025 20:40:43 +0100 Subject: [PATCH 1/2] feat: Add music and sysinfo to top bar (togglable) - also a bunch of misc fixes --- Bar/Bar.qml | 16 +++ Bar/Modules/Applauncher.qml | 11 +- Bar/Modules/Media.qml | 125 ++++++++++++++++ Bar/Modules/SystemInfo.qml | 81 +++++++++++ Bar/Modules/Volume.qml | 6 +- Components/Cava.qml | 8 +- Components/CircularProgressBar.qml | 3 +- Components/CircularSpectrum.qml | 20 +-- Programs/zigstat | Bin 0 -> 36664 bytes README.md | 1 + Services/MusicManager.qml | 157 +++++++++++++++++++++ Services/Sysinfo.qml | 47 ++++++ Settings/Settings.qml | 11 +- Settings/Theme.json | 4 +- Settings/Theme.qml | 13 +- Templates/templates/quickshell.json | 8 +- Widgets/LockScreen.qml | 131 +++++++++-------- Widgets/Sidebar/Config.qml | 94 ++++++------ Widgets/Sidebar/Config/ProfileSettings.qml | 118 +++++++++++++++- Widgets/Sidebar/Config/SettingsModal.qml | 14 +- Widgets/Sidebar/Panel/BluetoothPanel.qml | 2 +- Widgets/Sidebar/Panel/Music.qml | 141 ++++-------------- Widgets/Sidebar/Panel/PanelPopup.qml | 4 +- Widgets/Sidebar/Panel/QuickAccess.qml | 36 ++--- Widgets/Sidebar/Panel/SystemMonitor.qml | 128 +++-------------- Widgets/Sidebar/Panel/WallpaperPanel.qml | 2 +- Widgets/Sidebar/Panel/Weather.qml | 2 +- Widgets/Sidebar/Panel/WifiPanel.qml | 2 +- shell.qml | 9 +- 29 files changed, 795 insertions(+), 399 deletions(-) create mode 100644 Bar/Modules/Media.qml create mode 100644 Bar/Modules/SystemInfo.qml create mode 100755 Programs/zigstat create mode 100644 Services/MusicManager.qml create mode 100644 Services/Sysinfo.qml diff --git a/Bar/Bar.qml b/Bar/Bar.qml index 63b4692..098aa1d 100644 --- a/Bar/Bar.qml +++ b/Bar/Bar.qml @@ -82,6 +82,22 @@ Scope { anchors.left: parent.left } + Row { + id: leftWidgetsRow + anchors.verticalCenter: barBackground.verticalCenter + anchors.left: barBackground.left + anchors.leftMargin: 18 + spacing: 12 + + SystemInfo { + anchors.verticalCenter: parent.verticalCenter + } + + Media { + anchors.verticalCenter: parent.verticalCenter + } + } + ActiveWindow {} Workspace { diff --git a/Bar/Modules/Applauncher.qml b/Bar/Modules/Applauncher.qml index 301a06a..b772331 100644 --- a/Bar/Modules/Applauncher.qml +++ b/Bar/Modules/Applauncher.qml @@ -153,13 +153,6 @@ PanelWindow { anchors.margins: 32 spacing: 18 - Rectangle { - Layout.fillWidth: true - height: 1.5 - color: Theme.outline - opacity: 0.10 - } - // Search Bar Rectangle { id: searchBar @@ -372,7 +365,7 @@ PanelWindow { size: 1.1 fillColor: Theme.backgroundPrimary anchors.top: root.top - offsetX: 397 + offsetX: 396 offsetY: 0 } @@ -382,7 +375,7 @@ PanelWindow { size: 1.1 fillColor: Theme.backgroundPrimary anchors.top: root.top - offsetX: -397 + offsetX: -396 offsetY: 0 } } \ No newline at end of file diff --git a/Bar/Modules/Media.qml b/Bar/Modules/Media.qml new file mode 100644 index 0000000..cc8dd41 --- /dev/null +++ b/Bar/Modules/Media.qml @@ -0,0 +1,125 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import Qt5Compat.GraphicalEffects +import qs.Settings +import qs.Services +import qs.Components + +Item { + id: mediaControl + width: visible ? mediaRow.width : 0 + height: 32 + visible: Settings.showMediaInBar && MusicManager.currentPlayer + + RowLayout { + id: mediaRow + height: parent.height + spacing: 8 + + Item { + id: albumArtContainer + width: 24 + height: 24 + Layout.alignment: Qt.AlignVCenter + + // Circular spectrum visualizer + CircularSpectrum { + id: spectrum + values: MusicManager.cavaValues + anchors.centerIn: parent + innerRadius: 10 + outerRadius: 18 + fillColor: Theme.accentPrimary + strokeColor: Theme.accentPrimary + strokeWidth: 0 + z: 0 + } + + // Album art image + Rectangle { + id: albumArtwork + width: 20 + height: 20 + anchors.centerIn: parent + radius: 12 // circle + color: Qt.darker(Theme.surface, 1.1) + border.color: Qt.rgba(Theme.accentPrimary.r, Theme.accentPrimary.g, Theme.accentPrimary.b, 0.3) + border.width: 1 + z: 1 + + Image { + id: albumArt + anchors.fill: parent + anchors.margins: 1 + fillMode: Image.PreserveAspectCrop + smooth: true + cache: false + asynchronous: true + sourceSize.width: 24 + sourceSize.height: 24 + source: MusicManager.trackArtUrl + visible: source.toString() !== "" + + // Rounded corners using layer + layer.enabled: true + layer.effect: OpacityMask { + cached: true + maskSource: Rectangle { + width: albumArt.width + height: albumArt.height + radius: albumArt.width / 2 // circle + visible: false + } + } + } + + // Fallback icon + Text { + anchors.centerIn: parent + text: "music_note" + font.family: "Material Symbols Outlined" + font.pixelSize: 14 + color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.4) + visible: !albumArt.visible + } + + // Play/Pause overlay (only visible on hover) + Rectangle { + anchors.fill: parent + radius: parent.radius + color: Qt.rgba(0, 0, 0, 0.5) + visible: playButton.containsMouse + z: 2 + + Text { + anchors.centerIn: parent + text: MusicManager.isPlaying ? "pause" : "play_arrow" + font.family: "Material Symbols Outlined" + font.pixelSize: 14 + color: "white" + } + } + + MouseArea { + id: playButton + anchors.fill: parent + hoverEnabled: true + enabled: MusicManager.canPlay || MusicManager.canPause + onClicked: MusicManager.playPause() + } + } + } + + // Track info + Text { + text: MusicManager.trackTitle + " - " + MusicManager.trackArtist + color: Theme.textPrimary + font.family: Theme.fontFamily + font.pixelSize: 12 + elide: Text.ElideRight + Layout.maximumWidth: 300 + Layout.alignment: Qt.AlignVCenter + } + } +} diff --git a/Bar/Modules/SystemInfo.qml b/Bar/Modules/SystemInfo.qml new file mode 100644 index 0000000..48a99b1 --- /dev/null +++ b/Bar/Modules/SystemInfo.qml @@ -0,0 +1,81 @@ +import QtQuick +import Quickshell +import qs.Settings +import qs.Services + +Row { + id: layout + spacing: 10 + visible: Settings.showSystemInfoInBar + + Row { + id: cpuUsageLayout + spacing: 6 + + Text { + id: cpuUsageIcon + font.family: "Material Symbols Outlined" + font.pixelSize: Theme.fontSizeBody + text: "speed" + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + color: Theme.accentPrimary + } + + Text { + id: cpuUsageText + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeSmall + color: Theme.textPrimary + text: Sysinfo.cpuUsageStr + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + } + } + + // CPU Temperature Component + Row { + id: cpuTempLayout + spacing: 3 + Text { + font.family: "Material Symbols Outlined" + font.pixelSize: Theme.fontSizeBody + text: "thermometer" + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + color: Theme.accentPrimary + } + + Text { + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeSmall + color: Theme.textPrimary + text: Sysinfo.cpuTempStr + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + } + } + + // Memory Usage Component + Row { + id: memoryUsageLayout + spacing: 3 + Text { + font.family: "Material Symbols Outlined" + font.pixelSize: Theme.fontSizeBody + text: "memory" + color: Theme.accentPrimary + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + } + + Text { + font.family: Theme.fontFamily + font.pixelSize: Theme.fontSizeSmall + color: Theme.textPrimary + text: Sysinfo.memoryUsageStr + anchors.verticalCenter: parent.verticalCenter + verticalAlignment: Text.AlignVCenter + } + } +} diff --git a/Bar/Modules/Volume.qml b/Bar/Modules/Volume.qml index ad93fe7..7e342a5 100644 --- a/Bar/Modules/Volume.qml +++ b/Bar/Modules/Volume.qml @@ -47,13 +47,13 @@ Item { hoverEnabled: true acceptedButtons: Qt.NoButton // Accept wheel events only propagateComposedEvents: true - onWheel: { + onWheel:(wheel) => { if (!shell) return; let step = 5; if (wheel.angleDelta.y > 0) { - shell.volume = Math.min(100, shell.volume + step); + shell.updateVolume(Math.min(100, shell.volume + step)); } else if (wheel.angleDelta.y < 0) { - shell.volume = Math.max(0, shell.volume - step); + shell.updateVolume(Math.max(0, shell.volume - step)); } } } diff --git a/Components/Cava.qml b/Components/Cava.qml index 0e9f7d9..e22da5e 100644 --- a/Components/Cava.qml +++ b/Components/Cava.qml @@ -2,11 +2,13 @@ import QtQuick import Quickshell import Quickshell.Io import qs.Components +import qs.Services Scope { id: root property int count: 32 property int noiseReduction: 60 + property bool cavaRunning: false property string channels: "mono" // or stereo property string monoOption: "average" // or left or right property var config: ({ @@ -21,15 +23,11 @@ Scope { }) property var values: Array(count).fill(0) // 0 <= value <= 1 - onConfigChanged: { - process.running = false - process.running = true - } - Process { property int index: 0 id: process stdinEnabled: true + running: MusicManager.isPlaying command: ["cava", "-p", "/dev/stdin"] onExited: { stdinEnabled = true; index = 0 } onStarted: { diff --git a/Components/CircularProgressBar.qml b/Components/CircularProgressBar.qml index 7950bc0..075f201 100644 --- a/Components/CircularProgressBar.qml +++ b/Components/CircularProgressBar.qml @@ -12,7 +12,8 @@ Rectangle { property color progressColor: Theme.accentPrimary property int strokeWidth: 6 property bool showText: true - property string text: Math.round(progress * 100) + "%" + property string units: "%" + property string text: Math.round(progress * 100) + units property int textSize: 10 property color textColor: Theme.textPrimary diff --git a/Components/CircularSpectrum.qml b/Components/CircularSpectrum.qml index 4e36168..04ebf56 100644 --- a/Components/CircularSpectrum.qml +++ b/Components/CircularSpectrum.qml @@ -5,26 +5,20 @@ Item { id: root property int innerRadius: 34 property int outerRadius: 48 - property int barCount: 40 property color fillColor: "#fff" property color strokeColor: "#fff" property int strokeWidth: 0 + property var values: [] width: outerRadius * 2 height: outerRadius * 2 - // Cava input - Cava { - id: cava - count: root.barCount - } - Repeater { - model: root.barCount + model: root.values.length Rectangle { - property real value: cava.values[index] - property real angle: (index / root.barCount) * 360 - width: Math.max(2, (root.innerRadius * 2 * Math.PI) / root.barCount - 4) + property real value: root.values[index] + property real angle: (index / root.values.length) * 360 + width: Math.max(2, (root.innerRadius * 2 * Math.PI) / root.values.length - 4) height: value * (root.outerRadius - root.innerRadius) radius: width / 2 color: root.fillColor @@ -32,8 +26,8 @@ Item { border.width: root.strokeWidth antialiasing: true - x: root.width / 2 + (root.innerRadius) * Math.cos(Math.PI/2 + 2 * Math.PI * index / root.barCount) - width / 2 - y: root.height / 2 - (root.innerRadius) * Math.sin(Math.PI/2 + 2 * Math.PI * index / root.barCount) - height + x: root.width / 2 + (root.innerRadius) * Math.cos(Math.PI/2 + 2 * Math.PI * index / root.values.length) - width / 2 + y: root.height / 2 - (root.innerRadius) * Math.sin(Math.PI/2 + 2 * Math.PI * index / root.values.length) - height transform: Rotation { origin.x: width / 2 diff --git a/Programs/zigstat b/Programs/zigstat new file mode 100755 index 0000000000000000000000000000000000000000..4130452d076ecb5aa3ef53fd9545d3f443be2334 GIT binary patch literal 36664 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 290d03f..c6d886f 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ You will need to install a few things to get everything working: - `swww` to add fancy wallpaper animations (optional) - `wallust` to theme the setup based on wallpaper (optional) +zigstat is bundled - source can be found [here](https://git.pika-os.com/wm-packages/pikabar/src/branch/main/src/zigstat). --- ## Known issues diff --git a/Services/MusicManager.qml b/Services/MusicManager.qml new file mode 100644 index 0000000..27f0226 --- /dev/null +++ b/Services/MusicManager.qml @@ -0,0 +1,157 @@ +pragma Singleton +import QtQuick +import Quickshell +import Quickshell.Services.Mpris +import qs.Settings +import qs.Components + +Singleton { + id: manager + + // Properties + property var currentPlayer: null + property real currentPosition: 0 + property int selectedPlayerIndex: 0 + property bool isPlaying: currentPlayer ? currentPlayer.isPlaying : false + property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "Unknown Track") : "" + property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "Unknown Artist") : "" + property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "Unknown Album") : "" + property string trackArtUrl: currentPlayer ? (currentPlayer.trackArtUrl || "") : "" + property real trackLength: currentPlayer ? currentPlayer.length : 0 + property bool canPlay: currentPlayer ? currentPlayer.canPlay : false + property bool canPause: currentPlayer ? currentPlayer.canPause : false + property bool canGoNext: currentPlayer ? currentPlayer.canGoNext : false + property bool canGoPrevious: currentPlayer ? currentPlayer.canGoPrevious : false + property bool canSeek: currentPlayer ? currentPlayer.canSeek : false + property bool hasPlayer: getAvailablePlayers().length > 0 + + // Initialize + Item { + Component.onCompleted: { + updateCurrentPlayer() + } + } + + // Returns available MPRIS players + function getAvailablePlayers() { + if (!Mpris.players || !Mpris.players.values) { + return [] + } + + let allPlayers = Mpris.players.values + let controllablePlayers = [] + + for (let i = 0; i < allPlayers.length; i++) { + let player = allPlayers[i] + if (player && player.canControl) { + controllablePlayers.push(player) + } + } + + return controllablePlayers + } + + // Returns active player or first available + function findActivePlayer() { + let availablePlayers = getAvailablePlayers() + if (availablePlayers.length === 0) { + return null + } + + // Use selected player if valid, otherwise use first available + if (selectedPlayerIndex < availablePlayers.length) { + return availablePlayers[selectedPlayerIndex] + } else { + selectedPlayerIndex = 0 + return availablePlayers[0] + } + } + + // Updates currentPlayer and currentPosition + function updateCurrentPlayer() { + let newPlayer = findActivePlayer() + if (newPlayer !== currentPlayer) { + currentPlayer = newPlayer + currentPosition = currentPlayer ? currentPlayer.position : 0 + } + } + + // Player control functions + function playPause() { + if (currentPlayer) { + if (currentPlayer.isPlaying) { + currentPlayer.pause() + } else { + currentPlayer.play() + } + } + } + + function play() { + if (currentPlayer && currentPlayer.canPlay) { + currentPlayer.play() + } + } + + function pause() { + if (currentPlayer && currentPlayer.canPause) { + currentPlayer.pause() + } + } + + function next() { + if (currentPlayer && currentPlayer.canGoNext) { + currentPlayer.next() + } + } + + function previous() { + if (currentPlayer && currentPlayer.canGoPrevious) { + currentPlayer.previous() + } + } + + function seek(position) { + if (currentPlayer && currentPlayer.canSeek) { + currentPlayer.position = position + currentPosition = position + } + } + + function seekByRatio(ratio) { + if (currentPlayer && currentPlayer.canSeek && currentPlayer.length > 0) { + let seekPosition = ratio * currentPlayer.length + currentPlayer.position = seekPosition + currentPosition = seekPosition + } + } + + // Updates progress bar every second + Timer { + id: positionTimer + interval: 1000 + running: currentPlayer && currentPlayer.isPlaying && currentPlayer.length > 0 + repeat: true + onTriggered: { + if (currentPlayer && currentPlayer.isPlaying) { + currentPosition = currentPlayer.position + } + } + } + + // Reacts to player list changes + Connections { + target: Mpris.players + function onValuesChanged() { + updateCurrentPlayer() + } + } + + Cava { + id: cava + count: 44 + } + + // Expose cava values + property alias cavaValues: cava.values +} diff --git a/Services/Sysinfo.qml b/Services/Sysinfo.qml new file mode 100644 index 0000000..9a168c3 --- /dev/null +++ b/Services/Sysinfo.qml @@ -0,0 +1,47 @@ +pragma Singleton +import QtQuick +import Qt.labs.folderlistmodel +import Quickshell +import Quickshell.Io +import qs.Settings + +Singleton { + id: manager + + property string updateInterval: "2s" + property string cpuUsageStr: "" + property string cpuTempStr: "" + property string memoryUsageStr: "" + property string memoryUsagePerStr: "" + property real cpuUsage: 0 + property real memoryUsage: 0 + property real cpuTemp: 0 + property real diskUsage: 0 + property real memoryUsagePer: 0 + property string diskUsageStr: "" + + Process { + id: zigstatProcess + running: true + command: [Quickshell.shellRoot + "/Programs/zigstat", updateInterval] + stdout: SplitParser { + onRead: function (line) { + try { + const data = JSON.parse(line); + cpuUsage = +data.cpu; + cpuTemp = +data.cputemp; + memoryUsage = +data.mem; + memoryUsagePer = +data.memper; + diskUsage = +data.diskper; + cpuUsageStr = data.cpu + "%"; + cpuTempStr = data.cputemp + "°C"; + memoryUsageStr = data.mem + "G"; + memoryUsagePerStr = data.memper + "%"; + diskUsageStr = data.diskper + "%"; + } catch (e) { + console.error("Failed to parse zigstat output:", e); + } + } + } + } +} \ No newline at end of file diff --git a/Settings/Settings.qml b/Settings/Settings.qml index 6ff0e62..e78f201 100644 --- a/Settings/Settings.qml +++ b/Settings/Settings.qml @@ -14,7 +14,7 @@ QtObject { } property string weatherCity: "Dinslaken" property string profileImage: "/home/" + Quickshell.env("USER") + "/.face" - property bool useFahrenheit + property bool useFahrenheit: false property string wallpaperFolder: "/usr/share/wallpapers" property string currentWallpaper: "" property string videoPath: "~/Videos/" @@ -22,6 +22,8 @@ QtObject { property bool useSWWW: false property bool randomWallpaper: false property bool useWallpaperTheme: false + property bool showSystemInfoInBar: true + property bool showMediaInBar: false property int wallpaperInterval: 300 property string wallpaperResize: "crop" property int transitionFps: 60 @@ -43,6 +45,10 @@ QtObject { videoPath = settings.value("videoPath", videoPath) let showActiveWindowIconFlag = settings.value("showActiveWindowIconFlag", "false") showActiveWindowIcon = showActiveWindowIconFlag === "true" + let showSystemInfoInBarFlag = settings.value("showSystemInfoInBarFlag", "true") + showSystemInfoInBar = showSystemInfoInBarFlag === "true" + let showMediaInBarFlag = settings.value("showMediaInBarFlag", "true") + showMediaInBar = showMediaInBarFlag === "true" let useSWWWFlag = settings.value("useSWWWFlag", "false") useSWWW = useSWWWFlag === "true" let randomWallpaperFlag = settings.value("randomWallpaperFlag", "false") @@ -54,6 +60,7 @@ QtObject { transitionFps = settings.value("transitionFps", transitionFps) transitionType = settings.value("transitionType", transitionType) transitionDuration = settings.value("transitionDuration", transitionDuration) + WallpaperManager.setCurrentWallpaper(currentWallpaper, true); } @@ -65,6 +72,8 @@ QtObject { settings.setValue("currentWallpaper", currentWallpaper) settings.setValue("videoPath", videoPath) settings.setValue("showActiveWindowIconFlag", showActiveWindowIcon ? "true" : "false") + settings.setValue("showSystemInfoInBarFlag", showSystemInfoInBar ? "true" : "false") + settings.setValue("showMediaInBarFlag", showMediaInBar ? "true" : "false") settings.setValue("useSWWWFlag", useSWWW ? "true" : "false") settings.setValue("randomWallpaperFlag", randomWallpaper ? "true" : "false") settings.setValue("useWallpaperThemeFlag", useWallpaperTheme ? "true" : "false") diff --git a/Settings/Theme.json b/Settings/Theme.json index 8cbefc2..41c634a 100644 --- a/Settings/Theme.json +++ b/Settings/Theme.json @@ -23,6 +23,6 @@ "onAccent": "#0E0F10", "outline": "#565758", - "shadow": "#0E0F10B3", - "overlay": "#0E0F10CC" + "shadow": "#0E0F10", + "overlay": "#0E0F10" } diff --git a/Settings/Theme.qml b/Settings/Theme.qml index 99daf3f..ac3b6a5 100644 --- a/Settings/Theme.qml +++ b/Settings/Theme.qml @@ -6,6 +6,10 @@ import Quickshell.Io Singleton { id: root + + function applyOpacity(color, opacity) { + return color.replace("#", "#" + opacity); + } // FileView to load theme data from JSON file FileView { @@ -50,8 +54,8 @@ Singleton { property string outline: "#44485A" // Shadows & Overlays - property string shadow: "#000000B3" - property string overlay: "#11121ACC" + property string shadow: "#000000" + property string overlay: "#11121A" } } @@ -87,8 +91,8 @@ Singleton { property color outline: themeData.outline // Shadows & Overlays - property color shadow: themeData.shadow - property color overlay: themeData.overlay + property color shadow: applyOpacity(themeData.shadow, "B3") + property color overlay: applyOpacity(themeData.overlay, "66") // Font Properties property string fontFamily: "Roboto" // Family for all text @@ -98,3 +102,4 @@ Singleton { property int fontSizeSmall: 14 // Small text like clock, labels property int fontSizeCaption: 12 // Captions and fine print } + diff --git a/Templates/templates/quickshell.json b/Templates/templates/quickshell.json index 58d6afd..16aef1c 100644 --- a/Templates/templates/quickshell.json +++ b/Templates/templates/quickshell.json @@ -14,8 +14,8 @@ "accentSecondary": "{{ color4 | lighten(0.2) }}", "accentTertiary": "{{ color4 | darken(0.2) }}", - "error": "{{ color5 | darken(0.1) }}", - "warning": "{{ color5 | lighten(0.1) }}", + "error": "{{ color5 | lighten(0.1) }}", + "warning": "{{ color5 | lighten(0.3) }}", "highlight": "{{ color4 | lighten(0.4) }}", "rippleEffect": "{{ color4 | lighten(0.1) }}", @@ -23,6 +23,6 @@ "onAccent": "{{ background }}", "outline": "{{ background | lighten(0.3) }}", - "shadow": "{{ background }}B3", - "overlay": "{{ background }}CC" + "shadow": "{{ background }}", + "overlay": "{{ background }}" } diff --git a/Widgets/LockScreen.qml b/Widgets/LockScreen.qml index 04830bc..78ab2ef 100644 --- a/Widgets/LockScreen.qml +++ b/Widgets/LockScreen.qml @@ -28,31 +28,36 @@ WlSessionLock { // On component completed, fetch weather data Component.onCompleted: { - fetchWeatherData() + fetchWeatherData(); } // Weather fetching function function fetchWeatherData() { - WeatherHelper.fetchCityWeather(weatherCity, - function(result) { - weatherData = result.weather; - weatherError = ""; - }, - function(err) { - weatherError = err; - } - ); + WeatherHelper.fetchCityWeather(weatherCity, function (result) { + weatherData = result.weather; + weatherError = ""; + }, function (err) { + weatherError = err; + }); } function materialSymbolForCode(code) { - if (code === 0) return "sunny"; - if (code === 1 || code === 2) return "partly_cloudy_day"; - if (code === 3) return "cloud"; - if (code >= 45 && code <= 48) return "foggy"; - if (code >= 51 && code <= 67) return "rainy"; - if (code >= 71 && code <= 77) return "weather_snowy"; - if (code >= 80 && code <= 82) return "rainy"; - if (code >= 95 && code <= 99) return "thunderstorm"; + if (code === 0) + return "sunny"; + if (code === 1 || code === 2) + return "partly_cloudy_day"; + if (code === 3) + return "cloud"; + if (code >= 45 && code <= 48) + return "foggy"; + if (code >= 51 && code <= 67) + return "rainy"; + if (code >= 71 && code <= 77) + return "weather_snowy"; + if (code >= 80 && code <= 82) + return "rainy"; + if (code >= 95 && code <= 99) + return "thunderstorm"; return "cloud"; } @@ -72,12 +77,12 @@ WlSessionLock { console.log("Starting PAM authentication..."); lock.authenticating = true; lock.errorMessage = ""; - - console.log("[LockScreen] About to create PAM context with userName:", Quickshell.env("USER")) + + console.log("[LockScreen] About to create PAM context with userName:", Quickshell.env("USER")); var pam = Qt.createQmlObject('import Quickshell.Services.Pam; PamContext { config: "login"; user: "' + Quickshell.env("USER") + '" }', lock); console.log("PamContext created", pam); - - pam.onCompleted.connect(function(result) { + + pam.onCompleted.connect(function (result) { console.log("PAM completed with result:", result); lock.authenticating = false; if (result === PamResult.Success) { @@ -92,30 +97,30 @@ WlSessionLock { } pam.destroy(); }); - - pam.onError.connect(function(error) { + + pam.onError.connect(function (error) { console.log("PAM error:", error); lock.authenticating = false; lock.errorMessage = pam.message || "Authentication error."; lock.password = ""; pam.destroy(); }); - - pam.onPamMessage.connect(function() { + + pam.onPamMessage.connect(function () { console.log("PAM message:", pam.message, "isError:", pam.messageIsError); if (pam.messageIsError) { lock.errorMessage = pam.message; } }); - - pam.onResponseRequiredChanged.connect(function() { + + pam.onResponseRequiredChanged.connect(function () { console.log("PAM response required:", pam.responseRequired); if (pam.responseRequired && lock.authenticating) { console.log("Responding to PAM with password"); pam.respond(lock.password); } }); - + var started = pam.start(); console.log("PAM start result:", started); } @@ -151,7 +156,7 @@ WlSessionLock { height: 80 radius: 40 color: Theme.accentPrimary - + Image { id: avatarImage anchors.fill: parent @@ -222,10 +227,10 @@ WlSessionLock { echoMode: TextInput.Password passwordCharacter: "●" passwordMaskDelay: 0 - + text: lock.password onTextChanged: lock.password = text - + // Placeholder text Text { anchors.centerIn: parent @@ -237,30 +242,36 @@ WlSessionLock { } // Handle Enter key - Keys.onPressed: function(event) { + Keys.onPressed: function (event) { if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { - lock.unlockAttempt() + lock.unlockAttempt(); } } Component.onCompleted: { - forceActiveFocus() + forceActiveFocus(); } } } // Error message - Text { + Rectangle { + id: errorMessageRect Layout.alignment: Qt.AlignHCenter - text: lock.errorMessage - color: Theme.error - font.family: Theme.fontFamily - font.pixelSize: 14 + width: parent.width * 0.8 + height: 44 + color: Theme.overlay + radius: 22 visible: lock.errorMessage !== "" - opacity: lock.errorMessage !== "" ? 1 : 0 - Behavior on opacity { - NumberAnimation { duration: 200 } + Text { + anchors.centerIn: parent + text: lock.errorMessage + color: Theme.error + font.family: Theme.fontFamily + font.pixelSize: 14 + opacity: 1 + visible: lock.errorMessage !== "" } } @@ -292,13 +303,15 @@ WlSessionLock { hoverEnabled: true onClicked: { if (!lock.authenticating) { - lock.unlockAttempt() + lock.unlockAttempt(); } } } Behavior on opacity { - NumberAnimation { duration: 200 } + NumberAnimation { + duration: 200 + } } } } @@ -344,7 +357,7 @@ WlSessionLock { verticalAlignment: Text.AlignVCenter } Text { - text: weatherData && weatherData.current_weather ? (Settings.useFahrenheit ? `${Math.round(weatherData.current_weather.temperature * 9/5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : (Settings.useFahrenheit ? "--°F" : "--°C") + text: weatherData && weatherData.current_weather ? (Settings.useFahrenheit ? `${Math.round(weatherData.current_weather.temperature * 9 / 5 + 32)}°F` : `${Math.round(weatherData.current_weather.temperature)}°C`) : (Settings.useFahrenheit ? "--°F" : "--°C") font.family: Theme.fontFamily font.pixelSize: 18 color: Theme.textSecondary @@ -369,8 +382,8 @@ WlSessionLock { running: true repeat: true onTriggered: { - timeText.text = Qt.formatDateTime(new Date(), "HH:mm") - dateText.text = Qt.formatDateTime(new Date(), "dddd, MMMM d") + timeText.text = Qt.formatDateTime(new Date(), "HH:mm"); + dateText.text = Qt.formatDateTime(new Date(), "dddd, MMMM d"); } } @@ -380,7 +393,7 @@ WlSessionLock { running: true repeat: true onTriggered: { - fetchWeatherData() + fetchWeatherData(); } } @@ -392,7 +405,9 @@ WlSessionLock { spacing: 12 // Shutdown Rectangle { - width: 48; height: 48; radius: 24 + width: 48 + height: 48 + radius: 24 color: shutdownArea.containsMouse ? Theme.error : "transparent" border.color: Theme.error border.width: 1 @@ -401,7 +416,7 @@ WlSessionLock { anchors.fill: parent hoverEnabled: true onClicked: { - Qt.createQmlObject('import Quickshell.Io; Process { command: ["shutdown", "-h", "now"]; running: true }', lock) + Qt.createQmlObject('import Quickshell.Io; Process { command: ["shutdown", "-h", "now"]; running: true }', lock); } } Text { @@ -414,7 +429,9 @@ WlSessionLock { } // Reboot Rectangle { - width: 48; height: 48; radius: 24 + width: 48 + height: 48 + radius: 24 color: rebootArea.containsMouse ? Theme.accentPrimary : "transparent" border.color: Theme.accentPrimary border.width: 1 @@ -423,7 +440,7 @@ WlSessionLock { anchors.fill: parent hoverEnabled: true onClicked: { - Qt.createQmlObject('import Quickshell.Io; Process { command: ["reboot"]; running: true }', lock) + Qt.createQmlObject('import Quickshell.Io; Process { command: ["reboot"]; running: true }', lock); } } Text { @@ -436,7 +453,9 @@ WlSessionLock { } // Logout Rectangle { - width: 48; height: 48; radius: 24 + width: 48 + height: 48 + radius: 24 color: logoutArea.containsMouse ? Theme.accentSecondary : "transparent" border.color: Theme.accentSecondary border.width: 1 @@ -445,7 +464,7 @@ WlSessionLock { anchors.fill: parent hoverEnabled: true onClicked: { - Qt.createQmlObject('import Quickshell.Io; Process { command: ["loginctl", "terminate-user", "' + Quickshell.env("USER") + '"]; running: true }', lock) + Qt.createQmlObject('import Quickshell.Io; Process { command: ["loginctl", "terminate-user", "' + Quickshell.env("USER") + '"]; running: true }', lock); } } Text { @@ -458,4 +477,4 @@ WlSessionLock { } } } -} \ No newline at end of file +} diff --git a/Widgets/Sidebar/Config.qml b/Widgets/Sidebar/Config.qml index e4bd633..c36c89c 100644 --- a/Widgets/Sidebar/Config.qml +++ b/Widgets/Sidebar/Config.qml @@ -10,7 +10,7 @@ import qs.Settings Rectangle { id: settingsModal anchors.centerIn: parent - color: Settings.Theme.backgroundPrimary + color: Theme.backgroundPrimary radius: 20 visible: false z: 100 @@ -36,15 +36,15 @@ Rectangle { text: "settings" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeHeader - color: Settings.Theme.accentPrimary + color: Theme.accentPrimary } Text { text: "Settings" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeHeader font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary Layout.fillWidth: true } @@ -52,8 +52,8 @@ Rectangle { width: 36 height: 36 radius: 18 - color: closeButtonArea.containsMouse ? Settings.Theme.accentPrimary : "transparent" - border.color: Settings.Theme.accentPrimary + color: closeButtonArea.containsMouse ? Theme.accentPrimary : "transparent" + border.color: Theme.accentPrimary border.width: 1 Text { @@ -61,7 +61,7 @@ Rectangle { text: "close" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody - color: closeButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.accentPrimary + color: closeButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary } MouseArea { @@ -77,7 +77,7 @@ Rectangle { Rectangle { Layout.fillWidth: true Layout.preferredHeight: 180 - color: Settings.Theme.surface + color: Theme.surface radius: 18 ColumnLayout { @@ -94,15 +94,15 @@ Rectangle { text: "wb_sunny" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody - color: Settings.Theme.accentPrimary + color: Theme.accentPrimary } Text { text: "Weather Settings" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeBody font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary Layout.fillWidth: true } } @@ -114,18 +114,18 @@ Rectangle { Text { text: "City" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary } Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 radius: 8 - color: Settings.Theme.surfaceVariant - border.color: cityInput.activeFocus ? Settings.Theme.accentPrimary : Settings.Theme.outline + color: Theme.surfaceVariant + border.color: cityInput.activeFocus ? Theme.accentPrimary : Theme.outline border.width: 1 TextInput { @@ -139,9 +139,9 @@ Rectangle { anchors.topMargin: 6 anchors.bottomMargin: 6 text: tempWeatherCity - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall - color: Settings.Theme.textPrimary + color: Theme.textPrimary verticalAlignment: TextInput.AlignVCenter clip: true focus: true @@ -170,10 +170,10 @@ Rectangle { Text { text: "Temperature Unit" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary } Item { @@ -186,8 +186,8 @@ Rectangle { width: 52 height: 32 radius: 16 - color: Settings.Theme.accentPrimary - border.color: Settings.Theme.accentPrimary + color: Theme.accentPrimary + border.color: Theme.accentPrimary border.width: 2 Rectangle { @@ -195,8 +195,8 @@ Rectangle { width: 28 height: 28 radius: 14 - color: Settings.Theme.surface - border.color: Settings.Theme.outline + color: Theme.surface + border.color: Theme.outline border.width: 1 y: 2 x: tempUseFahrenheit ? customSwitch.width - width - 2 : 2 @@ -204,10 +204,10 @@ Rectangle { Text { anchors.centerIn: parent text: tempUseFahrenheit ? "°F" : "°C" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeCaption font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary } Behavior on x { @@ -230,7 +230,7 @@ Rectangle { Rectangle { Layout.fillWidth: true Layout.preferredHeight: 140 - color: Settings.Theme.surface + color: Theme.surface radius: 18 anchors.left: parent.left anchors.right: parent.right @@ -253,15 +253,15 @@ Rectangle { text: "person" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody - color: Settings.Theme.accentPrimary + color: Theme.accentPrimary } Text { text: "Profile Image" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeBody font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary Layout.fillWidth: true } } @@ -275,8 +275,8 @@ Rectangle { width: 36 height: 36 radius: 18 - color: Settings.Theme.surfaceVariant - border.color: profileImageInput.activeFocus ? Settings.Theme.accentPrimary : Settings.Theme.outline + color: Theme.surfaceVariant + border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline border.width: 1 Image { @@ -306,7 +306,7 @@ Rectangle { text: "person" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody - color: Settings.Theme.accentPrimary + color: Theme.accentPrimary visible: tempProfileImage === "" } } @@ -316,8 +316,8 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 radius: 8 - color: Settings.Theme.surfaceVariant - border.color: profileImageInput.activeFocus ? Settings.Theme.accentPrimary : Settings.Theme.outline + color: Theme.surfaceVariant + border.color: profileImageInput.activeFocus ? Theme.accentPrimary : Theme.outline border.width: 1 TextInput { @@ -331,9 +331,9 @@ Rectangle { anchors.topMargin: 6 anchors.bottomMargin: 6 text: tempProfileImage - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall - color: Settings.Theme.textPrimary + color: Theme.textPrimary verticalAlignment: TextInput.AlignVCenter clip: true focus: true @@ -359,7 +359,7 @@ Rectangle { Rectangle { Layout.fillWidth: true Layout.preferredHeight: 100 - color: Settings.Theme.surface + color: Theme.surface radius: 18 ColumnLayout { @@ -375,14 +375,14 @@ Rectangle { text: "image" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody - color: Settings.Theme.accentPrimary + color: Theme.accentPrimary } Text { text: "Wallpaper Folder" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeBody font.bold: true - color: Settings.Theme.textPrimary + color: Theme.textPrimary Layout.fillWidth: true } } @@ -392,8 +392,8 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: 40 radius: 8 - color: Settings.Theme.surfaceVariant - border.color: wallpaperFolderInput.activeFocus ? Settings.Theme.accentPrimary : Settings.Theme.outline + color: Theme.surfaceVariant + border.color: wallpaperFolderInput.activeFocus ? Theme.accentPrimary : Theme.outline border.width: 1 TextInput { @@ -407,9 +407,9 @@ Rectangle { anchors.topMargin: 6 anchors.bottomMargin: 6 text: tempWallpaperFolder - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall - color: Settings.Theme.textPrimary + color: Theme.textPrimary verticalAlignment: TextInput.AlignVCenter clip: true selectByMouse: true @@ -435,7 +435,7 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: 44 radius: 12 - color: applyButtonArea.containsMouse ? Settings.Theme.accentPrimary : Settings.Theme.accentPrimary + color: applyButtonArea.containsMouse ? Theme.accentPrimary : Theme.accentPrimary border.color: "transparent" border.width: 0 opacity: 1.0 @@ -443,10 +443,10 @@ Rectangle { Text { anchors.centerIn: parent text: "Apply Changes" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.bold: true - color: applyButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.onAccent + color: applyButtonArea.containsMouse ? Theme.onAccent : Theme.onAccent } MouseArea { diff --git a/Widgets/Sidebar/Config/ProfileSettings.qml b/Widgets/Sidebar/Config/ProfileSettings.qml index 016ec8b..6dc642b 100644 --- a/Widgets/Sidebar/Config/ProfileSettings.qml +++ b/Widgets/Sidebar/Config/ProfileSettings.qml @@ -7,7 +7,7 @@ import qs.Settings Rectangle { id: profileSettingsCard Layout.fillWidth: true - Layout.preferredHeight: 200 + Layout.preferredHeight: 300 color: Theme.surface radius: 18 border.color: "transparent" @@ -15,6 +15,10 @@ Rectangle { Layout.bottomMargin: 16 property bool showActiveWindowIcon: false signal showAWIconChanged(bool showActiveWindowIcon) + property bool showSystemInfoInBar: true + signal showSystemInfoChanged(bool showSystemInfoInBar) + property bool showMediaInBar: false + signal showMediaChanged(bool showMediaInBar) ColumnLayout { anchors.fill: parent @@ -125,7 +129,6 @@ Rectangle { } } - // Show Active Window Icon Setting RowLayout { spacing: 8 @@ -148,8 +151,8 @@ Rectangle { width: 52 height: 32 radius: 16 - color: Theme.accentPrimary - border.color: Theme.accentPrimary + color: showActiveWindowIcon ? Theme.accentPrimary : Theme.surfaceVariant + border.color: showActiveWindowIcon ? Theme.accentPrimary : Theme.outline border.width: 2 Rectangle { @@ -177,6 +180,110 @@ Rectangle { } } + // Show System Info In Bar Setting + RowLayout { + spacing: 8 + Layout.fillWidth: true + + Text { + text: "Show System Info In Bar" + font.pixelSize: 13 + font.bold: true + color: Theme.textPrimary + Layout.alignment: Qt.AlignVCenter + } + + Item { + Layout.fillWidth: true + } + + // Custom Material 3 Switch + Rectangle { + id: customSwitch2 + width: 52 + height: 32 + radius: 16 + color: showSystemInfoInBar ? Theme.accentPrimary : Theme.surfaceVariant + border.color: showSystemInfoInBar ? Theme.accentPrimary : Theme.outline + border.width: 2 + + Rectangle { + id: thumb2 + width: 28 + height: 28 + radius: 14 + color: Theme.surface + border.color: Theme.outline + border.width: 1 + y: 2 + x: showSystemInfoInBar ? customSwitch2.width - width - 2 : 2 + + Behavior on x { + NumberAnimation { duration: 200; easing.type: Easing.OutCubic } + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + showSystemInfoChanged(!showSystemInfoInBar) + } + } + } + } + + // Show Media In Bar Setting + RowLayout { + spacing: 8 + Layout.fillWidth: true + + Text { + text: "Show Media In Bar" + font.pixelSize: 13 + font.bold: true + color: Theme.textPrimary + Layout.alignment: Qt.AlignVCenter + } + + Item { + Layout.fillWidth: true + } + + // Custom Material 3 Switch + Rectangle { + id: customSwitch3 + width: 52 + height: 32 + radius: 16 + color: showMediaInBar ? Theme.accentPrimary : Theme.surfaceVariant + border.color: showMediaInBar ? Theme.accentPrimary : Theme.outline + border.width: 2 + + Rectangle { + id: thumb3 + width: 28 + height: 28 + radius: 14 + color: Theme.surface + border.color: Theme.outline + border.width: 1 + y: 2 + x: showMediaInBar ? customSwitch3.width - width - 2 : 2 + + Behavior on x { + NumberAnimation { duration: 200; easing.type: Easing.OutCubic } + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + showMediaChanged(!showMediaInBar) + } + } + } + } + // Video Path Input Row RowLayout { spacing: 8 @@ -184,7 +291,8 @@ Rectangle { Text { text: "Video Path" - font.pixelSize: 14 + font.pixelSize: 13 + font.bold: true color: Theme.textPrimary Layout.alignment: Qt.AlignVCenter } diff --git a/Widgets/Sidebar/Config/SettingsModal.qml b/Widgets/Sidebar/Config/SettingsModal.qml index 05311c1..7ea62bc 100644 --- a/Widgets/Sidebar/Config/SettingsModal.qml +++ b/Widgets/Sidebar/Config/SettingsModal.qml @@ -9,7 +9,7 @@ import qs.Services PanelWindow { id: settingsModal implicitWidth: 480 - implicitHeight: 720 + implicitHeight: 800 visible: false color: "transparent" anchors.top: true @@ -35,6 +35,8 @@ PanelWindow { property int tempTransitionFps: Settings.transitionFps property string tempTransitionType: Settings.transitionType property real tempTransitionDuration: Settings.transitionDuration + property bool tempShowSystemInfoInBar: Settings.showSystemInfoInBar + property bool tempShowMediaInBar: Settings.showMediaInBar Rectangle { anchors.fill: parent @@ -141,6 +143,14 @@ PanelWindow { onShowAWIconChanged: function (showActiveWindowIcon) { tempShowActiveWindowIcon = showActiveWindowIcon; } + showSystemInfoInBar: tempShowSystemInfoInBar + onShowSystemInfoChanged: function (showSystemInfoInBar) { + tempShowSystemInfoInBar = showSystemInfoInBar; + } + showMediaInBar: tempShowMediaInBar + onShowMediaChanged: function (showMediaInBar) { + tempShowMediaInBar = showMediaInBar; + } } } CollapsibleCategory { @@ -224,6 +234,8 @@ PanelWindow { Settings.transitionFps = tempTransitionFps; Settings.transitionType = tempTransitionType; Settings.transitionDuration = tempTransitionDuration; + Settings.showSystemInfoInBar = tempShowSystemInfoInBar; + Settings.showMediaInBar = tempShowMediaInBar; Settings.saveSettings(); if (typeof weather !== 'undefined' && weather) { weather.fetchCityWeather(); diff --git a/Widgets/Sidebar/Panel/BluetoothPanel.qml b/Widgets/Sidebar/Panel/BluetoothPanel.qml index 7820dec..4848e0c 100644 --- a/Widgets/Sidebar/Panel/BluetoothPanel.qml +++ b/Widgets/Sidebar/Panel/BluetoothPanel.qml @@ -73,7 +73,7 @@ Item { PanelWindow { id: bluetoothPanelModal implicitWidth: 480 - implicitHeight: 720 + implicitHeight: 800 visible: false color: "transparent" anchors.top: true diff --git a/Widgets/Sidebar/Panel/Music.qml b/Widgets/Sidebar/Panel/Music.qml index ef5a215..06dd621 100644 --- a/Widgets/Sidebar/Panel/Music.qml +++ b/Widgets/Sidebar/Panel/Music.qml @@ -2,90 +2,16 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import Qt5Compat.GraphicalEffects -import Quickshell.Services.Mpris import qs.Settings import qs.Components -import QtQuick +import qs.Services Rectangle { id: musicCard width: 360 - height: 200 + height: 250 color: "transparent" - property var currentPlayer: null - property real currentPosition: 0 - property int selectedPlayerIndex: 0 - - // Returns available MPRIS players - function getAvailablePlayers() { - if (!Mpris.players || !Mpris.players.values) { - return [] - } - - let allPlayers = Mpris.players.values - let controllablePlayers = [] - - for (let i = 0; i < allPlayers.length; i++) { - let player = allPlayers[i] - if (player && player.canControl) { - controllablePlayers.push(player) - } - } - - return controllablePlayers - } - - // Returns active player or first available - function findActivePlayer() { - let availablePlayers = getAvailablePlayers() - if (availablePlayers.length === 0) { - return null - } - - // Use selected player if valid, otherwise use first available - if (selectedPlayerIndex < availablePlayers.length) { - return availablePlayers[selectedPlayerIndex] - } else { - selectedPlayerIndex = 0 - return availablePlayers[0] - } - } - - // Updates currentPlayer and currentPosition - function updateCurrentPlayer() { - let newPlayer = findActivePlayer() - if (newPlayer !== currentPlayer) { - currentPlayer = newPlayer - currentPosition = currentPlayer ? currentPlayer.position : 0 - } - } - - // Updates progress bar every second - Timer { - id: positionTimer - interval: 1000 - running: currentPlayer && currentPlayer.isPlaying && currentPlayer.length > 0 - repeat: true - onTriggered: { - if (currentPlayer && currentPlayer.isPlaying) { - currentPosition = currentPlayer.position - } - } - } - - // Reacts to player list changes - Connections { - target: Mpris.players - function onValuesChanged() { - updateCurrentPlayer() - } - } - - Component.onCompleted: { - updateCurrentPlayer() - } - Rectangle { id: card anchors.fill: parent @@ -96,7 +22,7 @@ Rectangle { Item { width: parent.width height: parent.height - visible: !currentPlayer + visible: !MusicManager.currentPlayer ColumnLayout { anchors.centerIn: parent @@ -111,7 +37,7 @@ Rectangle { } Text { - text: getAvailablePlayers().length > 0 ? "No controllable player selected" : "No music player detected" + text: MusicManager.hasPlayer ? "No controllable player selected" : "No music player detected" color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.6) font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall @@ -141,6 +67,7 @@ Rectangle { // Spectrum visualizer CircularSpectrum { id: spectrum + values: MusicManager.cavaValues anchors.centerIn: parent innerRadius: 30 // just outside 60x60 album art outerRadius: 48 // how far bars extend @@ -170,7 +97,7 @@ Rectangle { asynchronous: true sourceSize.width: 60 sourceSize.height: 60 - source: currentPlayer ? (currentPlayer.trackArtUrl || "") : "" + source: MusicManager.trackArtUrl visible: source.toString() !== "" // Rounded corners using layer @@ -204,7 +131,7 @@ Rectangle { spacing: 4 Text { - text: currentPlayer ? (currentPlayer.trackTitle || "Unknown Track") : "" + text: MusicManager.trackTitle color: Theme.textPrimary font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall @@ -216,7 +143,7 @@ Rectangle { } Text { - text: currentPlayer ? (currentPlayer.trackArtist || "Unknown Artist") : "" + text: MusicManager.trackArtist color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.8) font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeCaption @@ -225,7 +152,7 @@ Rectangle { } Text { - text: currentPlayer ? (currentPlayer.trackAlbum || "Unknown Album") : "" + text: MusicManager.trackAlbum color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.6) font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeCaption @@ -244,8 +171,8 @@ Rectangle { color: Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.15) Layout.fillWidth: true - property real progressRatio: currentPlayer && currentPlayer.length > 0 ? - (currentPosition / currentPlayer.length) : 0 + property real progressRatio: MusicManager.trackLength > 0 ? + (MusicManager.currentPosition / MusicManager.trackLength) : 0 Rectangle { id: progressFill @@ -272,7 +199,7 @@ Rectangle { x: Math.max(0, Math.min(parent.width - width, progressFill.width - width/2)) anchors.verticalCenter: parent.verticalCenter - visible: currentPlayer && currentPlayer.length > 0 + visible: MusicManager.trackLength > 0 scale: progressMouseArea.containsMouse || progressMouseArea.pressed ? 1.2 : 1.0 Behavior on scale { @@ -285,23 +212,17 @@ Rectangle { id: progressMouseArea anchors.fill: parent hoverEnabled: true - enabled: currentPlayer && currentPlayer.length > 0 && currentPlayer.canSeek + enabled: MusicManager.trackLength > 0 && MusicManager.canSeek onClicked: function(mouse) { - if (currentPlayer && currentPlayer.length > 0) { - let ratio = mouse.x / width - let seekPosition = ratio * currentPlayer.length - currentPlayer.position = seekPosition - currentPosition = seekPosition - } + let ratio = mouse.x / width + MusicManager.seekByRatio(ratio) } onPositionChanged: function(mouse) { - if (pressed && currentPlayer && currentPlayer.length > 0) { + if (pressed) { let ratio = Math.max(0, Math.min(1, mouse.x / width)) - let seekPosition = ratio * currentPlayer.length - currentPlayer.position = seekPosition - currentPosition = seekPosition + MusicManager.seekByRatio(ratio) } } } @@ -326,8 +247,8 @@ Rectangle { id: previousButton anchors.fill: parent hoverEnabled: true - enabled: currentPlayer && currentPlayer.canGoPrevious - onClicked: if (currentPlayer) currentPlayer.previous() + enabled: MusicManager.canGoPrevious + onClicked: MusicManager.previous() } Text { @@ -352,21 +273,13 @@ Rectangle { id: playButton anchors.fill: parent hoverEnabled: true - enabled: currentPlayer && (currentPlayer.canPlay || currentPlayer.canPause) - onClicked: { - if (currentPlayer) { - if (currentPlayer.isPlaying) { - currentPlayer.pause() - } else { - currentPlayer.play() - } - } - } + enabled: MusicManager.canPlay || MusicManager.canPause + onClicked: MusicManager.playPause() } Text { anchors.centerIn: parent - text: currentPlayer && currentPlayer.isPlaying ? "pause" : "play_arrow" + text: MusicManager.isPlaying ? "pause" : "play_arrow" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody color: playButton.enabled ? Theme.accentPrimary : Qt.rgba(Theme.textPrimary.r, Theme.textPrimary.g, Theme.textPrimary.b, 0.3) @@ -386,8 +299,8 @@ Rectangle { id: nextButton anchors.fill: parent hoverEnabled: true - enabled: currentPlayer && currentPlayer.canGoNext - onClicked: if (currentPlayer) currentPlayer.next() + enabled: MusicManager.canGoNext + onClicked: MusicManager.next() } Text { @@ -401,10 +314,4 @@ Rectangle { } } } - - // Audio Visualizer (Cava) - Cava { - id: cava - count: 64 - } } \ No newline at end of file diff --git a/Widgets/Sidebar/Panel/PanelPopup.qml b/Widgets/Sidebar/Panel/PanelPopup.qml index df631ff..27df543 100644 --- a/Widgets/Sidebar/Panel/PanelPopup.qml +++ b/Widgets/Sidebar/Panel/PanelPopup.qml @@ -10,7 +10,7 @@ import qs.Components PanelWindow { id: panelPopup implicitWidth: 500 - implicitHeight: 750 + implicitHeight: 800 visible: false color: "transparent" screen: modelData @@ -30,7 +30,6 @@ PanelWindow { slideAnim.from = width; slideAnim.to = 0; slideAnim.running = true; - if (systemMonitor) systemMonitor.startMonitoring(); if (weather) weather.startWeatherFetch(); if (systemWidget) systemWidget.panelVisible = true; if (quickAccessWidget) quickAccessWidget.panelVisible = true; @@ -56,7 +55,6 @@ PanelWindow { if (panelPopup.slideOffset === panelPopup.width) { panelPopup.visible = false; // Stop monitoring and background tasks when hidden - if (systemMonitor) systemMonitor.stopMonitoring(); if (weather) weather.stopWeatherFetch(); if (systemWidget) systemWidget.panelVisible = false; if (quickAccessWidget) quickAccessWidget.panelVisible = false; diff --git a/Widgets/Sidebar/Panel/QuickAccess.qml b/Widgets/Sidebar/Panel/QuickAccess.qml index 8aa9bb7..efe071b 100644 --- a/Widgets/Sidebar/Panel/QuickAccess.qml +++ b/Widgets/Sidebar/Panel/QuickAccess.qml @@ -4,7 +4,7 @@ import QtQuick.Controls import Qt5Compat.GraphicalEffects import Quickshell import Quickshell.Io -import "root:/Settings" as Settings +import qs.Settings Rectangle { id: quickAccessWidget @@ -24,7 +24,7 @@ Rectangle { Rectangle { id: card anchors.fill: parent - color: Settings.Theme.surface + color: Theme.surface radius: 18 RowLayout { @@ -38,8 +38,8 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: 44 radius: 12 - color: settingsButtonArea.containsMouse ? Settings.Theme.accentPrimary : "transparent" - border.color: Settings.Theme.accentPrimary + color: settingsButtonArea.containsMouse ? Theme.accentPrimary : "transparent" + border.color: Theme.accentPrimary border.width: 1 RowLayout { @@ -51,15 +51,15 @@ Rectangle { text: "settings" font.family: settingsButtonArea.containsMouse ? "Material Symbols Rounded" : "Material Symbols Outlined" font.pixelSize: 16 - color: settingsButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.accentPrimary + color: settingsButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary } Text { text: "Settings" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: 14 font.bold: true - color: settingsButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.textPrimary + color: settingsButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary Layout.fillWidth: true } } @@ -80,9 +80,9 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: 44 radius: 12 - color: isRecording ? Settings.Theme.accentPrimary : - (recorderButtonArea.containsMouse ? Settings.Theme.accentPrimary : "transparent") - border.color: Settings.Theme.accentPrimary + color: isRecording ? Theme.accentPrimary : + (recorderButtonArea.containsMouse ? Theme.accentPrimary : "transparent") + border.color: Theme.accentPrimary border.width: 1 RowLayout { @@ -94,15 +94,15 @@ Rectangle { text: isRecording ? "radio_button_checked" : "radio_button_unchecked" font.family: (isRecording || recorderButtonArea.containsMouse) ? "Material Symbols Rounded" : "Material Symbols Outlined" font.pixelSize: 16 - color: isRecording || recorderButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.accentPrimary + color: isRecording || recorderButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary } Text { text: isRecording ? "End" : "Record" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: 14 font.bold: true - color: isRecording || recorderButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.textPrimary + color: isRecording || recorderButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary Layout.fillWidth: true } } @@ -127,8 +127,8 @@ Rectangle { Layout.fillWidth: true Layout.preferredHeight: 44 radius: 12 - color: wallpaperButtonArea.containsMouse ? Settings.Theme.accentPrimary : "transparent" - border.color: Settings.Theme.accentPrimary + color: wallpaperButtonArea.containsMouse ? Theme.accentPrimary : "transparent" + border.color: Theme.accentPrimary border.width: 1 RowLayout { @@ -140,15 +140,15 @@ Rectangle { text: "image" font.family: "Material Symbols Outlined" font.pixelSize: 16 - color: wallpaperButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.accentPrimary + color: wallpaperButtonArea.containsMouse ? Theme.onAccent : Theme.accentPrimary } Text { text: "Wallpaper" - font.family: Settings.Theme.fontFamily + font.family: Theme.fontFamily font.pixelSize: 14 font.bold: true - color: wallpaperButtonArea.containsMouse ? Settings.Theme.onAccent : Settings.Theme.textPrimary + color: wallpaperButtonArea.containsMouse ? Theme.onAccent : Theme.textPrimary Layout.fillWidth: true } } diff --git a/Widgets/Sidebar/Panel/SystemMonitor.qml b/Widgets/Sidebar/Panel/SystemMonitor.qml index c63c27a..ba2ebf5 100644 --- a/Widgets/Sidebar/Panel/SystemMonitor.qml +++ b/Widgets/Sidebar/Panel/SystemMonitor.qml @@ -2,116 +2,22 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import Quickshell.Io -import "root:/Settings" as Settings -import "root:/Components" as Components +import qs.Components +import qs.Services +import qs.Settings Rectangle { id: systemMonitor width: 70 - height: 200 + height: 250 color: "transparent" - property real cpuUsage: 0 - property real memoryUsage: 0 - property real diskUsage: 0 property bool isVisible: false - Timer { - id: cpuTimer - interval: 2000 - repeat: true - running: isVisible - onTriggered: cpuInfo.running = true - } - - Timer { - id: memoryTimer - interval: 3000 - repeat: true - running: isVisible - onTriggered: memoryInfo.running = true - } - - Timer { - id: diskTimer - interval: 5000 - repeat: true - running: isVisible - onTriggered: diskInfo.running = true - } - - // Process for getting CPU usage - Process { - id: cpuInfo - command: ["sh", "-c", "top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | awk -F'%' '{print $1}'"] - running: false - - stdout: SplitParser { - onRead: data => { - let usage = parseFloat(data.trim()) - if (!isNaN(usage)) { - systemMonitor.cpuUsage = usage - } - cpuInfo.running = false - } - } - } - - // Process for getting memory usage - Process { - id: memoryInfo - command: ["sh", "-c", "free | grep Mem | awk '{print int($3/$2 * 100)}'"] - running: false - - stdout: SplitParser { - onRead: data => { - let usage = parseFloat(data.trim()) - if (!isNaN(usage)) { - systemMonitor.memoryUsage = usage - } - memoryInfo.running = false - } - } - } - - // Process for getting disk usage - Process { - id: diskInfo - command: ["sh", "-c", "df / | tail -1 | awk '{print int($5)}'"] - running: false - - stdout: SplitParser { - onRead: data => { - let usage = parseFloat(data.trim()) - if (!isNaN(usage)) { - systemMonitor.diskUsage = usage - } - diskInfo.running = false - } - } - } - - // Function to start monitoring - function startMonitoring() { - isVisible = true - // Trigger initial readings - cpuInfo.running = true - memoryInfo.running = true - diskInfo.running = true - } - - // Function to stop monitoring - function stopMonitoring() { - isVisible = false - cpuInfo.running = false - memoryInfo.running = false - diskInfo.running = false - } - Rectangle { id: card anchors.fill: parent - color: Settings.Theme.surface + color: Theme.surface radius: 18 ColumnLayout { @@ -121,8 +27,8 @@ Rectangle { Layout.alignment: Qt.AlignVCenter // CPU Usage - Components.CircularProgressBar { - progress: cpuUsage / 100 + CircularProgressBar { + progress: Sysinfo.cpuUsage / 100 size: 50 strokeWidth: 4 hasNotch: true @@ -131,9 +37,21 @@ Rectangle { Layout.alignment: Qt.AlignHCenter } + // Cpu Temp + CircularProgressBar { + progress: Sysinfo.cpuTemp / 100 + size: 50 + strokeWidth: 4 + hasNotch: true + units: "°C" + notchIcon: "thermometer" + notchIconSize: 14 + Layout.alignment: Qt.AlignHCenter + } + // Memory Usage - Components.CircularProgressBar { - progress: memoryUsage / 100 + CircularProgressBar { + progress: Sysinfo.memoryUsagePer / 100 size: 50 strokeWidth: 4 hasNotch: true @@ -143,8 +61,8 @@ Rectangle { } // Disk Usage - Components.CircularProgressBar { - progress: diskUsage / 100 + CircularProgressBar { + progress: Sysinfo.diskUsage / 100 size: 50 strokeWidth: 4 hasNotch: true diff --git a/Widgets/Sidebar/Panel/WallpaperPanel.qml b/Widgets/Sidebar/Panel/WallpaperPanel.qml index 46c9fbb..f881d54 100644 --- a/Widgets/Sidebar/Panel/WallpaperPanel.qml +++ b/Widgets/Sidebar/Panel/WallpaperPanel.qml @@ -10,7 +10,7 @@ import qs.Services PanelWindow { id: wallpaperPanelModal implicitWidth: 480 - implicitHeight: 720 + implicitHeight: 800 visible: false color: "transparent" anchors.top: true diff --git a/Widgets/Sidebar/Panel/Weather.qml b/Widgets/Sidebar/Panel/Weather.qml index d2b474d..da28eb0 100644 --- a/Widgets/Sidebar/Panel/Weather.qml +++ b/Widgets/Sidebar/Panel/Weather.qml @@ -2,7 +2,7 @@ import QtQuick 2.15 import QtQuick.Layouts 1.15 import QtQuick.Controls 2.15 import qs.Settings -import "root:/Helpers/Weather.js" as WeatherHelper +import "../../../Helpers/Weather.js" as WeatherHelper Rectangle { id: weatherRoot diff --git a/Widgets/Sidebar/Panel/WifiPanel.qml b/Widgets/Sidebar/Panel/WifiPanel.qml index ca82650..7ddbc5d 100644 --- a/Widgets/Sidebar/Panel/WifiPanel.qml +++ b/Widgets/Sidebar/Panel/WifiPanel.qml @@ -335,7 +335,7 @@ Item { PanelWindow { id: wifiPanelModal implicitWidth: 480 - implicitHeight: 720 + implicitHeight: 800 visible: false color: "transparent" anchors.top: true diff --git a/shell.qml b/shell.qml index 0b94c67..5a9a270 100644 --- a/shell.qml +++ b/shell.qml @@ -15,6 +15,13 @@ Scope { property alias appLauncherPanel: appLauncherPanel + function updateVolume(vol) { + volume = vol; + if (defaultAudioSink && defaultAudioSink.audio) { + defaultAudioSink.audio.volume = vol / 100; + } + } + Component.onCompleted: { Quickshell.shell = root; } @@ -48,7 +55,7 @@ Scope { } property var defaultAudioSink: Pipewire.defaultAudioSink - property int volume: defaultAudioSink && defaultAudioSink.audio ? Math.round(defaultAudioSink.audio.volume * 100) : 0 + property int volume: defaultAudioSink && defaultAudioSink.audio && defaultAudioSink.audio.volume ? Math.round(defaultAudioSink.audio.volume * 100) : 0 PwObjectTracker { objects: [Pipewire.defaultAudioSink] From f2964d033a67ca3e7dd528baf7ca99cca9e6c72e Mon Sep 17 00:00:00 2001 From: ferreo Date: Mon, 14 Jul 2025 20:41:56 +0100 Subject: [PATCH 2/2] feat: Add music and sysinfo to top bar (togglable) - also a bunch of misc fixes --- Components/Cava.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/Components/Cava.qml b/Components/Cava.qml index e22da5e..679ceef 100644 --- a/Components/Cava.qml +++ b/Components/Cava.qml @@ -8,7 +8,6 @@ Scope { id: root property int count: 32 property int noiseReduction: 60 - property bool cavaRunning: false property string channels: "mono" // or stereo property string monoOption: "average" // or left or right property var config: ({