1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

Compare commits

...

4 Commits

Author SHA1 Message Date
purian23
6b8c35c27b feat: DMS Greeter for Ubuntu 2025-11-28 16:32:48 -05:00
bbedward
dd409b4d1c osd/audio: bind audio change to pipewire, suppress OSDs on startup and
resume from suspend
2025-11-28 11:05:53 -05:00
bbedward
94a1aebe2b dgop: use dgop for uptime 2025-11-28 10:41:59 -05:00
bbedward
d3030c3ec6 color picker: fall back to niri picker when on niri
fixes #828
2025-11-28 09:47:19 -05:00
27 changed files with 1106 additions and 815 deletions

View File

@@ -4,7 +4,7 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
package: package:
description: 'Package to upload (dms, dms-git, or all)' description: 'Package to upload (dms, dms-git, dms-greeter, or all)'
required: false required: false
default: 'dms-git' default: 'dms-git'
rebuild_release: rebuild_release:
@@ -83,6 +83,12 @@ jobs:
echo "Uploading dms-git to PPA..." echo "Uploading dms-git to PPA..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-git" dms-git questing bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-git" dms-git questing
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "Uploading dms-greeter to PPA..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
bash distro/scripts/ppa-upload.sh "distro/ubuntu/dms-greeter" danklinux questing
else else
PPA_NAME="$PACKAGES" PPA_NAME="$PACKAGES"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -101,10 +107,13 @@ jobs:
if [[ "$PACKAGES" == "all" ]]; then if [[ "$PACKAGES" == "all" ]]; then
echo "- **PPA dms**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY echo "- **PPA dms**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
echo "- **PPA dms-git**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY echo "- **PPA dms-git**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
echo "- **PPA danklinux**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms" ]]; then elif [[ "$PACKAGES" == "dms" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms-git" ]]; then elif [[ "$PACKAGES" == "dms-git" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms-git/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms-greeter" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
fi fi
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then if [[ -n "${{ steps.packages.outputs.version }}" ]]; then

View File

@@ -182,8 +182,23 @@ case "$PACKAGE_NAME" in
fi fi
fi fi
;; ;;
dms-greeter)
GIT_REPO="AvengeMedia/DankMaterialShell"
info "Downloading source for dms-greeter..."
VERSION=$(dpkg-parsechangelog -S Version | sed 's/-[^-]*$//' | sed 's/ppa[0-9]*$//')
if [ ! -f "dms-greeter-source.tar.gz" ]; then
info "Downloading dms-greeter source..."
if wget -O dms-greeter-source.tar.gz "https://github.com/AvengeMedia/DankMaterialShell/archive/refs/tags/v${VERSION}.tar.gz"; then
success "source tarball downloaded"
else
error "Failed to download dms-greeter-source.tar.gz"
exit 1
fi
fi
;;
danksearch) danksearch)
# danksearch uses pre-built binary from releases, like dgop # danksearch uses pre-built binary from releases
GIT_REPO="AvengeMedia/danksearch" GIT_REPO="AvengeMedia/danksearch"
;; ;;
dgop) dgop)

View File

@@ -224,6 +224,13 @@ if [ "$KEEP_BUILDS" = "false" ]; then
REMOVED=$((REMOVED + 1)) REMOVED=$((REMOVED + 1))
fi fi
;; ;;
dms-greeter)
# Remove downloaded source
if [ -f "$PACKAGE_DIR/dms-greeter-source.tar.gz" ]; then
rm -f "$PACKAGE_DIR/dms-greeter-source.tar.gz"
REMOVED=$((REMOVED + 1))
fi
;;
esac esac
if [ $REMOVED -gt 0 ]; then if [ $REMOVED -gt 0 ]; then

View File

@@ -0,0 +1,5 @@
dms-greeter (0.6.2ppa3) questing; urgency=medium
* Rebuild for packaging fixes (ppa3)
-- Avenge Media <AvengeMedia.US@gmail.com> Thu, 27 Nov 2025 23:38:37 -0500

View File

@@ -0,0 +1,23 @@
Source: dms-greeter
Section: x11
Priority: optional
Maintainer: Avenge Media <AvengeMedia.US@gmail.com>
Build-Depends: debhelper-compat (= 13)
Standards-Version: 4.6.2
Homepage: https://github.com/AvengeMedia/DankMaterialShell
Vcs-Browser: https://github.com/AvengeMedia/DankMaterialShell
Vcs-Git: https://github.com/AvengeMedia/DankMaterialShell.git
Package: dms-greeter
Architecture: all
Depends: ${misc:Depends},
greetd,
quickshell-git | quickshell
Recommends: niri | hyprland | sway
Description: DankMaterialShell greeter for greetd
DankMaterialShell greeter for greetd login manager. A modern, Material Design 3
inspired greeter interface built with Quickshell for Wayland compositors.
.
Supports multiple compositors including Niri, Hyprland, and Sway with automatic
compositor detection and configuration. Features session selection, user
authentication, and dynamic theming.

View File

@@ -0,0 +1,27 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: dms-greeter
Upstream-Contact: Avenge Media LLC <AvengeMedia.US@gmail.com>
Source: https://github.com/AvengeMedia/DankMaterialShell
Files: *
Copyright: 2025 Avenge Media LLC
License: MIT
License: MIT
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1 @@
dms-greeter_0.6.2ppa3_source.buildinfo x11 optional

View File

@@ -0,0 +1,108 @@
#!/bin/sh
set -e
case "$1" in
configure)
# Create greeter user/group if they don't exist
if ! getent group greeter >/dev/null; then
addgroup --system greeter
fi
if ! getent passwd greeter >/dev/null; then
adduser --system --ingroup greeter --home /var/lib/greeter \
--shell /bin/bash --gecos "System Greeter" greeter
fi
if [ -d /var/cache/dms-greeter ]; then
chown -R greeter:greeter /var/cache/dms-greeter 2>/dev/null || true
fi
if [ -d /var/lib/greeter ]; then
chown -R greeter:greeter /var/lib/greeter 2>/dev/null || true
fi
# Check and set graphical.target as default
CURRENT_TARGET=$(systemctl get-default 2>/dev/null || echo "unknown")
if [ "$CURRENT_TARGET" != "graphical.target" ]; then
systemctl set-default graphical.target >/dev/null 2>&1 || true
TARGET_STATUS="Set to graphical.target (was: $CURRENT_TARGET) ✓"
else
TARGET_STATUS="Already graphical.target ✓"
fi
GREETD_CONFIG="/etc/greetd/config.toml"
CONFIG_STATUS="Not modified (already configured)"
# Check if niri or hyprland exists
COMPOSITOR="niri"
if ! command -v niri >/dev/null 2>&1; then
if command -v Hyprland >/dev/null 2>&1; then
COMPOSITOR="hyprland"
fi
fi
# If config doesn't exist, create a default one
if [ ! -f "$GREETD_CONFIG" ]; then
mkdir -p /etc/greetd
cat > "$GREETD_CONFIG" << 'GREETD_EOF'
[terminal]
vt = 1
[default_session]
user = "greeter"
command = "/usr/bin/dms-greeter --command COMPOSITOR_PLACEHOLDER"
GREETD_EOF
sed -i "s|COMPOSITOR_PLACEHOLDER|$COMPOSITOR|" "$GREETD_CONFIG"
CONFIG_STATUS="Created new config with $COMPOSITOR ✓"
elif ! grep -q "dms-greeter" "$GREETD_CONFIG"; then
# Backup existing config
BACKUP_FILE="${GREETD_CONFIG}.backup-$(date +%Y%m%d-%H%M%S)"
cp "$GREETD_CONFIG" "$BACKUP_FILE" 2>/dev/null || true
# Update command in default_session section
sed -i "/^\[default_session\]/,/^\[/ s|^command =.*|command = \"/usr/bin/dms-greeter --command $COMPOSITOR\"|" "$GREETD_CONFIG"
sed -i '/^\[default_session\]/,/^\[/ s|^user =.*|user = "greeter"|' "$GREETD_CONFIG"
CONFIG_STATUS="Updated existing config (backed up) with $COMPOSITOR ✓"
fi
# Only show banner on initial install
if [ -z "$2" ]; then
cat << 'EOF'
=========================================================================
DMS Greeter Installation Complete!
=========================================================================
Status:
EOF
echo " ✓ Greetd config: $CONFIG_STATUS"
echo " ✓ Default target: $TARGET_STATUS"
cat << 'EOF'
✓ Greeter user: Created
✓ Greeter directories: /var/cache/dms-greeter, /var/lib/greeter
Next steps:
1. Enable the greeter:
dms greeter enable
(This will automatically disable conflicting display managers,
set graphical.target, and enable greetd)
2. Sync your theme with the greeter (optional):
dms greeter sync
3. Check your setup:
dms greeter status
Ready to test? Run: sudo systemctl start greetd
Documentation: https://danklinux.com/docs/dankgreeter/
=========================================================================
EOF
fi
;;
esac
#DEBHELPER#
exit 0

View File

@@ -0,0 +1,14 @@
#!/bin/sh
set -e
case "$1" in
purge)
# Remove greeter cache directory on purge
rm -rf /var/cache/dms-greeter 2>/dev/null || true
;;
esac
#DEBHELPER#
exit 0

View File

@@ -0,0 +1,55 @@
#!/usr/bin/make -f
export DH_VERBOSE = 1
# Extract version from debian/changelog
DEB_VERSION := $(shell dpkg-parsechangelog -S Version)
# Get upstream version (strip -1ppa1 suffix)
UPSTREAM_VERSION := $(shell echo $(DEB_VERSION) | sed 's/-[^-]*$$//')
BASE_VERSION := $(shell echo $(UPSTREAM_VERSION) | sed 's/ppa[0-9]*$$//' | sed 's/+git.*//')
%:
dh $@
override_dh_auto_build:
# All files are included in source package
test -f dms-greeter-source.tar.gz || (echo "ERROR: dms-greeter-source.tar.gz not found!" && exit 1)
# Extract source tarball
tar -xzf dms-greeter-source.tar.gz
# Find the extracted directory
SOURCE_DIR=$$(find . -maxdepth 1 -type d -name "DankMaterialShell*" | head -n1); \
if [ -n "$$SOURCE_DIR" ]; then \
ln -sf $$SOURCE_DIR DankMaterialShell-$(BASE_VERSION); \
fi
override_dh_auto_install:
# Install greeter files to shared data location
mkdir -p debian/dms-greeter/usr/share/quickshell/dms-greeter
cp -r DankMaterialShell-$(BASE_VERSION)/quickshell/* debian/dms-greeter/usr/share/quickshell/dms-greeter/
# Install launcher script
install -Dm755 DankMaterialShell-$(BASE_VERSION)/quickshell/Modules/Greetd/assets/dms-greeter \
debian/dms-greeter/usr/bin/dms-greeter
# Install documentation
install -Dm644 DankMaterialShell-$(BASE_VERSION)/quickshell/Modules/Greetd/README.md \
debian/dms-greeter/usr/share/doc/dms-greeter/README.md
# Install LICENSE file
install -Dm644 DankMaterialShell-$(BASE_VERSION)/LICENSE \
debian/dms-greeter/usr/share/doc/dms-greeter/LICENSE
# Create cache directory structure (will be created by postinst)
mkdir -p debian/dms-greeter/var/cache/dms-greeter
# Remove build and development files
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/core
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/distro
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/.git*
rm -f debian/dms-greeter/usr/share/quickshell/dms-greeter/.gitignore
rm -rf debian/dms-greeter/usr/share/quickshell/dms-greeter/.github
override_dh_auto_clean:
rm -rf DankMaterialShell-*
dh_auto_clean

View File

@@ -0,0 +1 @@
3.0 (native)

View File

@@ -0,0 +1 @@
dms-greeter-source.tar.gz

View File

@@ -0,0 +1,3 @@
# Include files that are normally excluded by .gitignore
# These are needed for the build process on Launchpad (which has no internet access)
tar-ignore = !dms-greeter-source.tar.gz

View File

@@ -21,6 +21,22 @@ Singleton {
property bool isLightMode: false property bool isLightMode: false
property bool doNotDisturb: false property bool doNotDisturb: false
property bool isSwitchingMode: false property bool isSwitchingMode: false
property bool suppressOSD: true
Timer {
id: osdSuppressTimer
interval: 2000
running: true
onTriggered: root.suppressOSD = false
}
Connections {
target: SessionService
function onSessionResumed() {
root.suppressOSD = true;
osdSuppressTimer.restart();
}
}
property string wallpaperPath: "" property string wallpaperPath: ""
property bool perMonitorWallpaper: false property bool perMonitorWallpaper: false

View File

@@ -51,6 +51,7 @@ Item {
signal dialogClosed signal dialogClosed
signal backgroundClicked signal backgroundClicked
property bool animationsEnabled: true
readonly property bool useBackgroundWindow: true readonly property bool useBackgroundWindow: true
function open() { function open() {
@@ -75,6 +76,18 @@ Item {
closeTimer.restart(); closeTimer.restart();
} }
function instantClose() {
animationsEnabled = false;
shouldBeVisible = false;
shouldHaveFocus = false;
closeTimer.stop();
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = false;
dialogClosed();
Qt.callLater(() => animationsEnabled = true);
}
function toggle() { function toggle() {
shouldBeVisible ? close() : open(); shouldBeVisible ? close() : open();
} }
@@ -180,6 +193,7 @@ Item {
visible: root.showBackground && SettingsData.modalDarkenBackground visible: root.showBackground && SettingsData.modalDarkenBackground
Behavior on opacity { Behavior on opacity {
enabled: root.animationsEnabled
NumberAnimation { NumberAnimation {
duration: root.animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
@@ -272,6 +286,7 @@ Item {
} }
Behavior on animX { Behavior on animX {
enabled: root.animationsEnabled
NumberAnimation { NumberAnimation {
duration: root.animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
@@ -280,6 +295,7 @@ Item {
} }
Behavior on animY { Behavior on animY {
enabled: root.animationsEnabled
NumberAnimation { NumberAnimation {
duration: root.animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
@@ -288,6 +304,7 @@ Item {
} }
Behavior on scaleValue { Behavior on scaleValue {
enabled: root.animationsEnabled
NumberAnimation { NumberAnimation {
duration: root.animationDuration duration: root.animationDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline
@@ -312,6 +329,7 @@ Item {
y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5 y: Theme.snap(modalContainer.animY, root.dpr) + (parent.height - height) * (1 - modalContainer.scaleValue) * 0.5
Behavior on opacity { Behavior on opacity {
enabled: root.animationsEnabled
NumberAnimation { NumberAnimation {
duration: animationDuration duration: animationDuration
easing.type: Easing.BezierSpline easing.type: Easing.BezierSpline

View File

@@ -45,8 +45,7 @@ DankModal {
function hideInstant() { function hideInstant() {
onColorSelectedCallback = null; onColorSelectedCallback = null;
shouldBeVisible = false; instantClose();
visible = false;
} }
onColorSelected: color => { onColorSelected: color => {
@@ -81,26 +80,57 @@ DankModal {
selectedColor = currentColor; selectedColor = currentColor;
} }
function pickColorFromScreen() { function applyPickedColor(colorStr) {
hideInstant(); if (colorStr.length < 7 || !colorStr.startsWith('#'))
Proc.runCommand("hyprpicker", ["hyprpicker", "--format=hex"], (output, errorCode) => { return;
if (errorCode !== 0) { const pickedColor = Qt.color(colorStr);
console.warn("hyprpicker exited with code:", errorCode); root.selectedColor = pickedColor;
root.currentColor = pickedColor;
root.updateFromColor(pickedColor);
copyColorToClipboard(colorStr);
root.show();
}
function runNiriPicker() {
Proc.runCommand("niri-pick-color", ["niri", "msg", "pick-color"], (output, exitCode) => {
if (exitCode !== 0) {
console.warn("niri msg pick-color exited with code:", exitCode);
root.show(); root.show();
return; return;
} }
const colorStr = output.trim(); const hexMatch = output.match(/Hex:\s*(#[0-9A-Fa-f]{6})/);
if (colorStr.length >= 7 && colorStr.startsWith('#')) { if (hexMatch) {
const pickedColor = Qt.color(colorStr); applyPickedColor(hexMatch[1]);
root.selectedColor = pickedColor; } else {
root.currentColor = pickedColor; console.warn("Failed to parse niri pick-color output:", output);
root.updateFromColor(pickedColor);
copyColorToClipboard(colorStr);
root.show(); root.show();
} }
}); });
} }
function pickColorFromScreen() {
hideInstant();
Proc.runCommand("check-hyprpicker", ["which", "hyprpicker"], (output, exitCode) => {
if (exitCode === 0) {
Proc.runCommand("hyprpicker", ["hyprpicker", "--format=hex"], (hpOutput, hpCode) => {
if (hpCode !== 0) {
console.warn("hyprpicker exited with code:", hpCode);
root.show();
return;
}
applyPickedColor(hpOutput.trim());
});
return;
}
if (CompositorService.isNiri) {
runNiriPicker();
return;
}
console.warn("No color picker available");
root.show();
});
}
modalWidth: 680 modalWidth: 680
modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 680 modalHeight: contentLoader.item ? contentLoader.item.implicitHeight + Theme.spacingM * 2 : 680
backgroundColor: Theme.surfaceContainer backgroundColor: Theme.surfaceContainer

View File

@@ -22,7 +22,6 @@ FloatingWindow {
return; return;
} }
visible = true; visible = true;
UserInfoService.getUptime();
} }
function hide() { function hide() {

View File

@@ -8,16 +8,18 @@ Rectangle {
property bool editMode: false property bool editMode: false
signal powerButtonClicked() signal powerButtonClicked
signal lockRequested() signal lockRequested
signal editModeToggled() signal editModeToggled
signal settingsButtonClicked() signal settingsButtonClicked
Component.onCompleted: DgopService.addRef("system")
Component.onDestruction: DgopService.removeRef("system")
implicitHeight: 70 implicitHeight: 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
Theme.outline.b, 0.08)
border.width: 0 border.width: 0
Row { Row {
@@ -34,12 +36,12 @@ Rectangle {
height: 60 height: 60
imageSource: { imageSource: {
if (PortalService.profileImage === "") if (PortalService.profileImage === "")
return "" return "";
if (PortalService.profileImage.startsWith("/")) if (PortalService.profileImage.startsWith("/"))
return "file://" + PortalService.profileImage return "file://" + PortalService.profileImage;
return PortalService.profileImage return PortalService.profileImage;
} }
fallbackIcon: "person" fallbackIcon: "person"
} }
@@ -49,14 +51,13 @@ Rectangle {
spacing: 2 spacing: 2
Typography { Typography {
text: UserInfoService.fullName text: UserInfoService.fullName || UserInfoService.username || "User"
|| UserInfoService.username || "User"
style: Typography.Style.Subtitle style: Typography.Style.Subtitle
color: Theme.surfaceText color: Theme.surfaceText
} }
Typography { Typography {
text: (UserInfoService.uptime || "Unknown") text: DgopService.uptime || "Unknown"
style: Typography.Style.Caption style: Typography.Style.Caption
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
@@ -77,7 +78,7 @@ Rectangle {
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: {
root.lockRequested() root.lockRequested();
} }
} }
@@ -97,8 +98,8 @@ Rectangle {
iconColor: Theme.surfaceText iconColor: Theme.surfaceText
backgroundColor: "transparent" backgroundColor: "transparent"
onClicked: { onClicked: {
root.settingsButtonClicked() root.settingsButtonClicked();
settingsModal.show() settingsModal.show();
} }
} }
@@ -111,4 +112,4 @@ Rectangle {
onClicked: root.editModeToggled() onClicked: root.editModeToggled()
} }
} }
} }

View File

@@ -84,11 +84,8 @@ DankPopout {
if (shouldBeVisible) { if (shouldBeVisible) {
collapseAll(); collapseAll();
Qt.callLater(() => { Qt.callLater(() => {
if (NetworkService.activeService) { if (NetworkService.activeService)
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled; NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled;
}
if (UserInfoService)
UserInfoService.getUptime();
}); });
} else { } else {
Qt.callLater(() => { Qt.callLater(() => {

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Effects
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -7,6 +6,9 @@ import qs.Widgets
Card { Card {
id: root id: root
Component.onCompleted: DgopService.addRef("system")
Component.onDestruction: DgopService.removeRef("system")
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: Theme.spacingM anchors.leftMargin: Theme.spacingM
@@ -21,12 +23,12 @@ Card {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
imageSource: { imageSource: {
if (PortalService.profileImage === "") if (PortalService.profileImage === "")
return "" return "";
if (PortalService.profileImage.startsWith("/")) if (PortalService.profileImage.startsWith("/"))
return "file://" + PortalService.profileImage return "file://" + PortalService.profileImage;
return PortalService.profileImage return PortalService.profileImage;
} }
fallbackIcon: "person" fallbackIcon: "person"
} }
@@ -56,12 +58,16 @@ Card {
StyledText { StyledText {
text: { text: {
if (CompositorService.isNiri) return "on niri" if (CompositorService.isNiri)
if (CompositorService.isHyprland) return "on Hyprland" return "on niri";
if (CompositorService.isHyprland)
return "on Hyprland";
// technically they might not be on mangowc, but its what we support in the docs // technically they might not be on mangowc, but its what we support in the docs
if (CompositorService.isDwl) return "on MangoWC" if (CompositorService.isDwl)
if (CompositorService.isSway) return "on Sway" return "on MangoWC";
return "" if (CompositorService.isSway)
return "on Sway";
return "";
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
@@ -82,29 +88,12 @@ Card {
} }
StyledText { StyledText {
id: uptimeText text: DgopService.shortUptime || "up"
property real availableWidth: parent.parent.parent.parent.width - avatarContainer.width - Theme.spacingM * 3 - 16 - Theme.spacingS
property real longTextWidth: {
const fontSize = Math.round(Theme.fontSizeSmall || 12)
const testMetrics = Qt.createQmlObject('import QtQuick; TextMetrics { font.pixelSize: ' + fontSize + ' }', uptimeText)
testMetrics.text = UserInfoService.uptime || "up 1 hour, 23 minutes"
const result = testMetrics.width
testMetrics.destroy()
return result
}
// Just using truncated is always true initially idk
property bool shouldUseShort: longTextWidth > availableWidth
text: shouldUseShort ? UserInfoService.shortUptime : UserInfoService.uptime || "up 1h 23m"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: availableWidth
wrapMode: Text.NoWrap
} }
} }
} }
} }
} }

View File

@@ -38,7 +38,7 @@ Item {
} }
WeatherService.addRef(); WeatherService.addRef();
UserInfoService.refreshUserInfo(); UserInfoService.getUserInfo();
if (CompositorService.isHyprland) { if (CompositorService.isHyprland) {
updateHyprlandLayout(); updateHyprlandLayout();

View File

@@ -1,5 +1,4 @@
import QtQuick import QtQuick
import QtQuick.Controls
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -69,7 +68,7 @@ DankFlickable {
} }
StyledText { StyledText {
text: `${UserInfoService.uptime} Boot: ${DgopService.bootTime}` text: `${DgopService.uptime} Boot: ${DgopService.bootTime}`
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
@@ -83,9 +82,7 @@ DankFlickable {
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
} }
Rectangle { Rectangle {
@@ -134,7 +131,6 @@ DankFlickable {
color: Theme.primary color: Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
@@ -181,9 +177,7 @@ DankFlickable {
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
} }
Rectangle { Rectangle {
@@ -275,7 +269,6 @@ DankFlickable {
color: Theme.secondary color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
StyledText { StyledText {
@@ -365,22 +358,16 @@ DankFlickable {
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
Behavior on color { Behavior on color {
ColorAnimation { ColorAnimation {
duration: Theme.shortDuration duration: Theme.shortDuration
} }
} }
} }
} }
} }
} }
Rectangle { Rectangle {
@@ -418,7 +405,6 @@ DankFlickable {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
} }
Column { Column {
@@ -495,7 +481,6 @@ DankFlickable {
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
Repeater { Repeater {
@@ -596,19 +581,11 @@ DankFlickable {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
} }
} }
} }
} }
} }
} }
} }
} }
}
}

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtCore import QtCore
@@ -15,7 +14,6 @@ Singleton {
readonly property PwNode sink: Pipewire.defaultAudioSink readonly property PwNode sink: Pipewire.defaultAudioSink
readonly property PwNode source: Pipewire.defaultAudioSource readonly property PwNode source: Pipewire.defaultAudioSource
property bool suppressOSD: true
property bool soundsAvailable: false property bool soundsAvailable: false
property bool gsettingsAvailable: false property bool gsettingsAvailable: false
property var availableSoundThemes: [] property var availableSoundThemes: []
@@ -33,12 +31,14 @@ Singleton {
signal micMuteChanged signal micMuteChanged
Timer { Connections {
id: startupTimer target: root.sink?.audio ?? null
interval: 500
repeat: false function onVolumeChanged() {
running: true if (SessionData.suppressOSD)
onTriggered: root.suppressOSD = false return;
root.playVolumeChangeSoundIfEnabled();
}
} }
function detectSoundsAvailability() { function detectSoundsAvailability() {
@@ -47,35 +47,33 @@ Singleton {
import QtQuick import QtQuick
import QtMultimedia import QtMultimedia
Item {} Item {}
`, root, "AudioService.TestComponent") `, root, "AudioService.TestComponent");
if (testObj) { if (testObj) {
testObj.destroy() testObj.destroy();
} }
soundsAvailable = true soundsAvailable = true;
return true return true;
} catch (e) { } catch (e) {
soundsAvailable = false soundsAvailable = false;
return false return false;
} }
} }
function checkGsettings() { function checkGsettings() {
Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => { Proc.runCommand("checkGsettings", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null"], (output, exitCode) => {
gsettingsAvailable = (exitCode === 0) gsettingsAvailable = (exitCode === 0);
if (gsettingsAvailable) { if (gsettingsAvailable) {
scanSoundThemes() scanSoundThemes();
getCurrentSoundTheme() getCurrentSoundTheme();
} }
}, 0) }, 0);
} }
function scanSoundThemes() { function scanSoundThemes() {
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS");
const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== "" const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))) : ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))];
? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation)))
: ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))]
const basePaths = searchPaths.map(p => p + "/sounds").join(" ") const basePaths = searchPaths.map(p => p + "/sounds").join(" ");
const script = ` const script = `
for base_dir in ${basePaths}; do for base_dir in ${basePaths}; do
[ -d "$base_dir" ] || continue [ -d "$base_dir" ] || continue
@@ -84,65 +82,63 @@ Singleton {
basename "$theme_dir" basename "$theme_dir"
done done
done | sort -u done | sort -u
` `;
Proc.runCommand("scanSoundThemes", ["sh", "-c", script], (output, exitCode) => { Proc.runCommand("scanSoundThemes", ["sh", "-c", script], (output, exitCode) => {
if (exitCode === 0 && output.trim()) { if (exitCode === 0 && output.trim()) {
const themes = output.trim().split('\n').filter(t => t && t.length > 0) const themes = output.trim().split('\n').filter(t => t && t.length > 0);
availableSoundThemes = themes availableSoundThemes = themes;
} else { } else {
availableSoundThemes = [] availableSoundThemes = [];
} }
}, 0) }, 0);
} }
function getCurrentSoundTheme() { function getCurrentSoundTheme() {
Proc.runCommand("getCurrentSoundTheme", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null | sed \"s/'//g\""], (output, exitCode) => { Proc.runCommand("getCurrentSoundTheme", ["sh", "-c", "gsettings get org.gnome.desktop.sound theme-name 2>/dev/null | sed \"s/'//g\""], (output, exitCode) => {
if (exitCode === 0 && output.trim()) { if (exitCode === 0 && output.trim()) {
currentSoundTheme = output.trim() currentSoundTheme = output.trim();
console.log("AudioService: Current system sound theme:", currentSoundTheme) console.log("AudioService: Current system sound theme:", currentSoundTheme);
if (SettingsData.useSystemSoundTheme) { if (SettingsData.useSystemSoundTheme) {
discoverSoundFiles(currentSoundTheme) discoverSoundFiles(currentSoundTheme);
} }
} else { } else {
currentSoundTheme = "" currentSoundTheme = "";
console.log("AudioService: No system sound theme found") console.log("AudioService: No system sound theme found");
} }
}, 0) }, 0);
} }
function setSoundTheme(themeName) { function setSoundTheme(themeName) {
if (!themeName || themeName === currentSoundTheme) { if (!themeName || themeName === currentSoundTheme) {
return return;
} }
Proc.runCommand("setSoundTheme", ["sh", "-c", `gsettings set org.gnome.desktop.sound theme-name '${themeName}'`], (output, exitCode) => { Proc.runCommand("setSoundTheme", ["sh", "-c", `gsettings set org.gnome.desktop.sound theme-name '${themeName}'`], (output, exitCode) => {
if (exitCode === 0) { if (exitCode === 0) {
currentSoundTheme = themeName currentSoundTheme = themeName;
if (SettingsData.useSystemSoundTheme) { if (SettingsData.useSystemSoundTheme) {
discoverSoundFiles(themeName) discoverSoundFiles(themeName);
} }
} }
}, 0) }, 0);
} }
function discoverSoundFiles(themeName) { function discoverSoundFiles(themeName) {
if (!themeName) { if (!themeName) {
soundFilePaths = {} soundFilePaths = {};
if (soundsAvailable) { if (soundsAvailable) {
destroySoundPlayers() destroySoundPlayers();
createSoundPlayers() createSoundPlayers();
} }
return return;
} }
const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS") const xdgDataDirs = Quickshell.env("XDG_DATA_DIRS");
const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== "" const searchPaths = xdgDataDirs && xdgDataDirs.trim() !== "" ? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))) : ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))];
? xdgDataDirs.split(":").concat(Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation)))
: ["/usr/share", "/usr/local/share", Paths.strip(StandardPaths.writableLocation(StandardPaths.GenericDataLocation))]
const extensions = ["oga", "ogg", "wav", "mp3", "flac"] const extensions = ["oga", "ogg", "wav", "mp3", "flac"];
const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName;
const script = ` const script = `
for event_key in audio-volume-change power-plug power-unplug message message-new-instant; do for event_key in audio-volume-change power-plug power-unplug message message-new-instant; do
@@ -179,26 +175,26 @@ Singleton {
[ $found -eq 1 ] && break [ $found -eq 1 ] && break
done done
done done
` `;
Proc.runCommand("discoverSoundFiles", ["sh", "-c", script], (output, exitCode) => { Proc.runCommand("discoverSoundFiles", ["sh", "-c", script], (output, exitCode) => {
const paths = {} const paths = {};
if (exitCode === 0 && output.trim()) { if (exitCode === 0 && output.trim()) {
const lines = output.trim().split('\n') const lines = output.trim().split('\n');
for (let line of lines) { for (let line of lines) {
const parts = line.split('=') const parts = line.split('=');
if (parts.length === 2) { if (parts.length === 2) {
paths[parts[0]] = "file://" + parts[1] paths[parts[0]] = "file://" + parts[1];
} }
} }
} }
soundFilePaths = paths soundFilePaths = paths;
if (soundsAvailable) { if (soundsAvailable) {
destroySoundPlayers() destroySoundPlayers();
createSoundPlayers() createSoundPlayers();
} }
}, 0) }, 0);
} }
function getSoundPath(soundEvent) { function getSoundPath(soundEvent) {
@@ -208,45 +204,45 @@ Singleton {
"power-unplug": "../assets/sounds/plasma/power-unplug.wav", "power-unplug": "../assets/sounds/plasma/power-unplug.wav",
"message": "../assets/sounds/freedesktop/message.wav", "message": "../assets/sounds/freedesktop/message.wav",
"message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav" "message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav"
} };
const specialConditions = { const specialConditions = {
"smooth": ["audio-volume-change"] "smooth": ["audio-volume-change"]
} };
const themeLower = currentSoundTheme.toLowerCase() const themeLower = currentSoundTheme.toLowerCase();
if (SettingsData.useSystemSoundTheme && specialConditions[themeLower]?.includes(soundEvent)) { if (SettingsData.useSystemSoundTheme && specialConditions[themeLower]?.includes(soundEvent)) {
const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav") const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav");
console.log("AudioService: Using bundled sound (special condition) for", soundEvent, ":", bundledPath) console.log("AudioService: Using bundled sound (special condition) for", soundEvent, ":", bundledPath);
return bundledPath return bundledPath;
} }
if (SettingsData.useSystemSoundTheme && soundFilePaths[soundEvent]) { if (SettingsData.useSystemSoundTheme && soundFilePaths[soundEvent]) {
console.log("AudioService: Using system sound for", soundEvent, ":", soundFilePaths[soundEvent]) console.log("AudioService: Using system sound for", soundEvent, ":", soundFilePaths[soundEvent]);
return soundFilePaths[soundEvent] return soundFilePaths[soundEvent];
} }
const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav") const bundledPath = Qt.resolvedUrl(soundMap[soundEvent] || "../assets/sounds/freedesktop/message.wav");
console.log("AudioService: Using bundled sound for", soundEvent, ":", bundledPath) console.log("AudioService: Using bundled sound for", soundEvent, ":", bundledPath);
return bundledPath return bundledPath;
} }
function reloadSounds() { function reloadSounds() {
console.log("AudioService: Reloading sounds, useSystemSoundTheme:", SettingsData.useSystemSoundTheme, "currentSoundTheme:", currentSoundTheme) console.log("AudioService: Reloading sounds, useSystemSoundTheme:", SettingsData.useSystemSoundTheme, "currentSoundTheme:", currentSoundTheme);
if (SettingsData.useSystemSoundTheme && currentSoundTheme) { if (SettingsData.useSystemSoundTheme && currentSoundTheme) {
discoverSoundFiles(currentSoundTheme) discoverSoundFiles(currentSoundTheme);
} else { } else {
soundFilePaths = {} soundFilePaths = {};
if (soundsAvailable) { if (soundsAvailable) {
destroySoundPlayers() destroySoundPlayers();
createSoundPlayers() createSoundPlayers();
} }
} }
} }
function setupMediaDevices() { function setupMediaDevices() {
if (!soundsAvailable || mediaDevices) { if (!soundsAvailable || mediaDevices) {
return return;
} }
try { try {
@@ -259,7 +255,7 @@ Singleton {
console.log("AudioService: MediaDevices initialized, default output:", defaultAudioOutput?.description) console.log("AudioService: MediaDevices initialized, default output:", defaultAudioOutput?.description)
} }
} }
`, root, "AudioService.MediaDevices") `, root, "AudioService.MediaDevices");
if (mediaDevices) { if (mediaDevices) {
mediaDevicesConnections = Qt.createQmlObject(` mediaDevicesConnections = Qt.createQmlObject(`
@@ -272,48 +268,48 @@ Singleton {
root.createSoundPlayers() root.createSoundPlayers()
} }
} }
`, root, "AudioService.MediaDevicesConnections") `, root, "AudioService.MediaDevicesConnections");
} }
} catch (e) { } catch (e) {
console.log("AudioService: MediaDevices not available, using default audio output") console.log("AudioService: MediaDevices not available, using default audio output");
mediaDevices = null mediaDevices = null;
} }
} }
function destroySoundPlayers() { function destroySoundPlayers() {
if (volumeChangeSound) { if (volumeChangeSound) {
volumeChangeSound.destroy() volumeChangeSound.destroy();
volumeChangeSound = null volumeChangeSound = null;
} }
if (powerPlugSound) { if (powerPlugSound) {
powerPlugSound.destroy() powerPlugSound.destroy();
powerPlugSound = null powerPlugSound = null;
} }
if (powerUnplugSound) { if (powerUnplugSound) {
powerUnplugSound.destroy() powerUnplugSound.destroy();
powerUnplugSound = null powerUnplugSound = null;
} }
if (normalNotificationSound) { if (normalNotificationSound) {
normalNotificationSound.destroy() normalNotificationSound.destroy();
normalNotificationSound = null normalNotificationSound = null;
} }
if (criticalNotificationSound) { if (criticalNotificationSound) {
criticalNotificationSound.destroy() criticalNotificationSound.destroy();
criticalNotificationSound = null criticalNotificationSound = null;
} }
} }
function createSoundPlayers() { function createSoundPlayers() {
if (!soundsAvailable) { if (!soundsAvailable) {
return return;
} }
setupMediaDevices() setupMediaDevices();
try { try {
const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : "" const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : "";
const volumeChangePath = getSoundPath("audio-volume-change") const volumeChangePath = getSoundPath("audio-volume-change");
volumeChangeSound = Qt.createQmlObject(` volumeChangeSound = Qt.createQmlObject(`
import QtQuick import QtQuick
import QtMultimedia import QtMultimedia
@@ -323,9 +319,9 @@ Singleton {
${deviceProperty}volume: 1.0 ${deviceProperty}volume: 1.0
} }
} }
`, root, "AudioService.VolumeChangeSound") `, root, "AudioService.VolumeChangeSound");
const powerPlugPath = getSoundPath("power-plug") const powerPlugPath = getSoundPath("power-plug");
powerPlugSound = Qt.createQmlObject(` powerPlugSound = Qt.createQmlObject(`
import QtQuick import QtQuick
import QtMultimedia import QtMultimedia
@@ -335,9 +331,9 @@ Singleton {
${deviceProperty}volume: 1.0 ${deviceProperty}volume: 1.0
} }
} }
`, root, "AudioService.PowerPlugSound") `, root, "AudioService.PowerPlugSound");
const powerUnplugPath = getSoundPath("power-unplug") const powerUnplugPath = getSoundPath("power-unplug");
powerUnplugSound = Qt.createQmlObject(` powerUnplugSound = Qt.createQmlObject(`
import QtQuick import QtQuick
import QtMultimedia import QtMultimedia
@@ -347,9 +343,9 @@ Singleton {
${deviceProperty}volume: 1.0 ${deviceProperty}volume: 1.0
} }
} }
`, root, "AudioService.PowerUnplugSound") `, root, "AudioService.PowerUnplugSound");
const messagePath = getSoundPath("message") const messagePath = getSoundPath("message");
normalNotificationSound = Qt.createQmlObject(` normalNotificationSound = Qt.createQmlObject(`
import QtQuick import QtQuick
import QtMultimedia import QtMultimedia
@@ -359,9 +355,9 @@ Singleton {
${deviceProperty}volume: 1.0 ${deviceProperty}volume: 1.0
} }
} }
`, root, "AudioService.NormalNotificationSound") `, root, "AudioService.NormalNotificationSound");
const messageNewInstantPath = getSoundPath("message-new-instant") const messageNewInstantPath = getSoundPath("message-new-instant");
criticalNotificationSound = Qt.createQmlObject(` criticalNotificationSound = Qt.createQmlObject(`
import QtQuick import QtQuick
import QtMultimedia import QtMultimedia
@@ -371,114 +367,114 @@ Singleton {
${deviceProperty}volume: 1.0 ${deviceProperty}volume: 1.0
} }
} }
`, root, "AudioService.CriticalNotificationSound") `, root, "AudioService.CriticalNotificationSound");
} catch (e) { } catch (e) {
console.warn("AudioService: Error creating sound players:", e) console.warn("AudioService: Error creating sound players:", e);
} }
} }
function playVolumeChangeSound() { function playVolumeChangeSound() {
if (soundsAvailable && volumeChangeSound) { if (soundsAvailable && volumeChangeSound) {
volumeChangeSound.play() volumeChangeSound.play();
} }
} }
function playPowerPlugSound() { function playPowerPlugSound() {
if (soundsAvailable && powerPlugSound) { if (soundsAvailable && powerPlugSound) {
powerPlugSound.play() powerPlugSound.play();
} }
} }
function playPowerUnplugSound() { function playPowerUnplugSound() {
if (soundsAvailable && powerUnplugSound) { if (soundsAvailable && powerUnplugSound) {
powerUnplugSound.play() powerUnplugSound.play();
} }
} }
function playNormalNotificationSound() { function playNormalNotificationSound() {
if (soundsAvailable && normalNotificationSound && !SessionData.doNotDisturb) { if (soundsAvailable && normalNotificationSound && !SessionData.doNotDisturb) {
normalNotificationSound.play() normalNotificationSound.play();
} }
} }
function playCriticalNotificationSound() { function playCriticalNotificationSound() {
if (soundsAvailable && criticalNotificationSound && !SessionData.doNotDisturb) { if (soundsAvailable && criticalNotificationSound && !SessionData.doNotDisturb) {
criticalNotificationSound.play() criticalNotificationSound.play();
} }
} }
function playVolumeChangeSoundIfEnabled() { function playVolumeChangeSoundIfEnabled() {
if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged) { if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged) {
playVolumeChangeSound() playVolumeChangeSound();
} }
} }
function displayName(node) { function displayName(node) {
if (!node) { if (!node) {
return "" return "";
} }
if (node.properties && node.properties["device.description"]) { if (node.properties && node.properties["device.description"]) {
return node.properties["device.description"] return node.properties["device.description"];
} }
if (node.description && node.description !== node.name) { if (node.description && node.description !== node.name) {
return node.description return node.description;
} }
if (node.nickname && node.nickname !== node.name) { if (node.nickname && node.nickname !== node.name) {
return node.nickname return node.nickname;
} }
if (node.name.includes("analog-stereo")) { if (node.name.includes("analog-stereo")) {
return "Built-in Speakers" return "Built-in Speakers";
} }
if (node.name.includes("bluez")) { if (node.name.includes("bluez")) {
return "Bluetooth Audio" return "Bluetooth Audio";
} }
if (node.name.includes("usb")) { if (node.name.includes("usb")) {
return "USB Audio" return "USB Audio";
} }
if (node.name.includes("hdmi")) { if (node.name.includes("hdmi")) {
return "HDMI Audio" return "HDMI Audio";
} }
return node.name return node.name;
} }
function subtitle(name) { function subtitle(name) {
if (!name) { if (!name) {
return "" return "";
} }
if (name.includes('usb-')) { if (name.includes('usb-')) {
if (name.includes('SteelSeries')) { if (name.includes('SteelSeries')) {
return "USB Gaming Headset" return "USB Gaming Headset";
} }
if (name.includes('Generic')) { if (name.includes('Generic')) {
return "USB Audio Device" return "USB Audio Device";
} }
return "USB Audio" return "USB Audio";
} }
if (name.includes('pci-')) { if (name.includes('pci-')) {
if (name.includes('01_00.1') || name.includes('01:00.1')) { if (name.includes('01_00.1') || name.includes('01:00.1')) {
return "NVIDIA GPU Audio" return "NVIDIA GPU Audio";
} }
return "PCI Audio" return "PCI Audio";
} }
if (name.includes('bluez')) { if (name.includes('bluez')) {
return "Bluetooth Audio" return "Bluetooth Audio";
} }
if (name.includes('analog')) { if (name.includes('analog')) {
return "Built-in Audio" return "Built-in Audio";
} }
if (name.includes('hdmi')) { if (name.includes('hdmi')) {
return "HDMI Audio" return "HDMI Audio";
} }
return "" return "";
} }
PwObjectTracker { PwObjectTracker {
@@ -487,136 +483,134 @@ Singleton {
function setVolume(percentage) { function setVolume(percentage) {
if (!root.sink?.audio) { if (!root.sink?.audio) {
return "No audio sink available" return "No audio sink available";
} }
const clampedVolume = Math.max(0, Math.min(100, percentage)) const clampedVolume = Math.max(0, Math.min(100, percentage));
root.sink.audio.volume = clampedVolume / 100 root.sink.audio.volume = clampedVolume / 100;
return `Volume set to ${clampedVolume}%` return `Volume set to ${clampedVolume}%`;
} }
function toggleMute() { function toggleMute() {
if (!root.sink?.audio) { if (!root.sink?.audio) {
return "No audio sink available" return "No audio sink available";
} }
root.sink.audio.muted = !root.sink.audio.muted root.sink.audio.muted = !root.sink.audio.muted;
return root.sink.audio.muted ? "Audio muted" : "Audio unmuted" return root.sink.audio.muted ? "Audio muted" : "Audio unmuted";
} }
function setMicVolume(percentage) { function setMicVolume(percentage) {
if (!root.source?.audio) { if (!root.source?.audio) {
return "No audio source available" return "No audio source available";
} }
const clampedVolume = Math.max(0, Math.min(100, percentage)) const clampedVolume = Math.max(0, Math.min(100, percentage));
root.source.audio.volume = clampedVolume / 100 root.source.audio.volume = clampedVolume / 100;
return `Microphone volume set to ${clampedVolume}%` return `Microphone volume set to ${clampedVolume}%`;
} }
function toggleMicMute() { function toggleMicMute() {
if (!root.source?.audio) { if (!root.source?.audio) {
return "No audio source available" return "No audio source available";
} }
root.source.audio.muted = !root.source.audio.muted root.source.audio.muted = !root.source.audio.muted;
return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted" return root.source.audio.muted ? "Microphone muted" : "Microphone unmuted";
} }
IpcHandler { IpcHandler {
target: "audio" target: "audio"
function setvolume(percentage: string): string { function setvolume(percentage: string): string {
return root.setVolume(parseInt(percentage)) return root.setVolume(parseInt(percentage));
} }
function increment(step: string): string { function increment(step: string): string {
if (!root.sink?.audio) { if (!root.sink?.audio) {
return "No audio sink available" return "No audio sink available";
} }
if (root.sink.audio.muted) { if (root.sink.audio.muted) {
root.sink.audio.muted = false root.sink.audio.muted = false;
} }
const currentVolume = Math.round(root.sink.audio.volume * 100) const currentVolume = Math.round(root.sink.audio.volume * 100);
const stepValue = parseInt(step || "5") const stepValue = parseInt(step || "5");
const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue)) const newVolume = Math.max(0, Math.min(100, currentVolume + stepValue));
root.sink.audio.volume = newVolume / 100 root.sink.audio.volume = newVolume / 100;
root.playVolumeChangeSoundIfEnabled() return `Volume increased to ${newVolume}%`;
return `Volume increased to ${newVolume}%`
} }
function decrement(step: string): string { function decrement(step: string): string {
if (!root.sink?.audio) { if (!root.sink?.audio) {
return "No audio sink available" return "No audio sink available";
} }
if (root.sink.audio.muted) { if (root.sink.audio.muted) {
root.sink.audio.muted = false root.sink.audio.muted = false;
} }
const currentVolume = Math.round(root.sink.audio.volume * 100) const currentVolume = Math.round(root.sink.audio.volume * 100);
const stepValue = parseInt(step || "5") const stepValue = parseInt(step || "5");
const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue)) const newVolume = Math.max(0, Math.min(100, currentVolume - stepValue));
root.sink.audio.volume = newVolume / 100 root.sink.audio.volume = newVolume / 100;
root.playVolumeChangeSoundIfEnabled() return `Volume decreased to ${newVolume}%`;
return `Volume decreased to ${newVolume}%`
} }
function mute(): string { function mute(): string {
return root.toggleMute() return root.toggleMute();
} }
function setmic(percentage: string): string { function setmic(percentage: string): string {
return root.setMicVolume(parseInt(percentage)) return root.setMicVolume(parseInt(percentage));
} }
function micmute(): string { function micmute(): string {
const result = root.toggleMicMute() const result = root.toggleMicMute();
root.micMuteChanged() root.micMuteChanged();
return result return result;
} }
function status(): string { function status(): string {
let result = "Audio Status:\n" let result = "Audio Status:\n";
if (root.sink?.audio) { if (root.sink?.audio) {
const volume = Math.round(root.sink.audio.volume * 100) const volume = Math.round(root.sink.audio.volume * 100);
const muteStatus = root.sink.audio.muted ? " (muted)" : "" const muteStatus = root.sink.audio.muted ? " (muted)" : "";
result += `Output: ${volume}%${muteStatus}\n` result += `Output: ${volume}%${muteStatus}\n`;
} else { } else {
result += "Output: No sink available\n" result += "Output: No sink available\n";
} }
if (root.source?.audio) { if (root.source?.audio) {
const micVolume = Math.round(root.source.audio.volume * 100) const micVolume = Math.round(root.source.audio.volume * 100);
const muteStatus = root.source.audio.muted ? " (muted)" : "" const muteStatus = root.source.audio.muted ? " (muted)" : "";
result += `Input: ${micVolume}%${muteStatus}` result += `Input: ${micVolume}%${muteStatus}`;
} else { } else {
result += "Input: No source available" result += "Input: No source available";
} }
return result return result;
} }
} }
Connections { Connections {
target: SettingsData target: SettingsData
function onUseSystemSoundThemeChanged() { function onUseSystemSoundThemeChanged() {
reloadSounds() reloadSounds();
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (!detectSoundsAvailability()) { if (!detectSoundsAvailability()) {
console.warn("AudioService: QtMultimedia not available - sound effects disabled") console.warn("AudioService: QtMultimedia not available - sound effects disabled");
} else { } else {
console.info("AudioService: Sound effects enabled") console.info("AudioService: Sound effects enabled");
checkGsettings() checkGsettings();
Qt.callLater(createSoundPlayers) Qt.callLater(createSoundPlayers);
} }
} }
} }

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
@@ -72,504 +71,547 @@ Singleton {
property string bootTime: "" property string bootTime: ""
property string motherboard: "" property string motherboard: ""
property string biosVersion: "" property string biosVersion: ""
property string uptime: ""
property string shortUptime: ""
property int historySize: 60 property int historySize: 60
property var cpuHistory: [] property var cpuHistory: []
property var memoryHistory: [] property var memoryHistory: []
property var networkHistory: ({ property var networkHistory: ({
"rx": [], "rx": [],
"tx": [] "tx": []
}) })
property var diskHistory: ({ property var diskHistory: ({
"read": [], "read": [],
"write": [] "write": []
}) })
function addRef(modules = null) { function addRef(modules = null) {
refCount++ refCount++;
let modulesChanged = false let modulesChanged = false;
if (modules) { if (modules) {
const modulesToAdd = Array.isArray(modules) ? modules : [modules] const modulesToAdd = Array.isArray(modules) ? modules : [modules];
for (const module of modulesToAdd) { for (const module of modulesToAdd) {
// Increment reference count for this module // Increment reference count for this module
const currentCount = moduleRefCounts[module] || 0 const currentCount = moduleRefCounts[module] || 0;
moduleRefCounts[module] = currentCount + 1 moduleRefCounts[module] = currentCount + 1;
console.log("Adding ref for module:", module, "count:", moduleRefCounts[module]) console.log("Adding ref for module:", module, "count:", moduleRefCounts[module]);
// Add to enabled modules if not already there // Add to enabled modules if not already there
if (enabledModules.indexOf(module) === -1) { if (enabledModules.indexOf(module) === -1) {
enabledModules.push(module) enabledModules.push(module);
modulesChanged = true modulesChanged = true;
} }
} }
} }
if (modulesChanged || refCount === 1) { if (modulesChanged || refCount === 1) {
enabledModules = enabledModules.slice() // Force property change enabledModules = enabledModules.slice(); // Force property change
moduleRefCounts = Object.assign({}, moduleRefCounts) // Force property change moduleRefCounts = Object.assign({}, moduleRefCounts); // Force property change
updateAllStats() updateAllStats();
} else if (gpuPciIds.length > 0 && refCount > 0) { } else if (gpuPciIds.length > 0 && refCount > 0) {
// If we have GPU PCI IDs and active modules, make sure to update // If we have GPU PCI IDs and active modules, make sure to update
// This handles the case where PCI IDs were loaded after modules were added // This handles the case where PCI IDs were loaded after modules were added
updateAllStats() updateAllStats();
} }
} }
function removeRef(modules = null) { function removeRef(modules = null) {
refCount = Math.max(0, refCount - 1) refCount = Math.max(0, refCount - 1);
let modulesChanged = false let modulesChanged = false;
if (modules) { if (modules) {
const modulesToRemove = Array.isArray(modules) ? modules : [modules] const modulesToRemove = Array.isArray(modules) ? modules : [modules];
for (const module of modulesToRemove) { for (const module of modulesToRemove) {
const currentCount = moduleRefCounts[module] || 0 const currentCount = moduleRefCounts[module] || 0;
if (currentCount > 1) { if (currentCount > 1) {
// Decrement reference count // Decrement reference count
moduleRefCounts[module] = currentCount - 1 moduleRefCounts[module] = currentCount - 1;
console.log("Removing ref for module:", module, "count:", moduleRefCounts[module]) console.log("Removing ref for module:", module, "count:", moduleRefCounts[module]);
} else if (currentCount === 1) { } else if (currentCount === 1) {
// Remove completely when count reaches 0 // Remove completely when count reaches 0
delete moduleRefCounts[module] delete moduleRefCounts[module];
const index = enabledModules.indexOf(module) const index = enabledModules.indexOf(module);
if (index > -1) { if (index > -1) {
enabledModules.splice(index, 1) enabledModules.splice(index, 1);
modulesChanged = true modulesChanged = true;
console.log("Disabling module:", module, "(no more refs)") console.log("Disabling module:", module, "(no more refs)");
} }
} }
} }
} }
if (modulesChanged) { if (modulesChanged) {
enabledModules = enabledModules.slice() // Force property change enabledModules = enabledModules.slice(); // Force property change
moduleRefCounts = Object.assign({}, moduleRefCounts) // Force property change moduleRefCounts = Object.assign({}, moduleRefCounts); // Force property change
// Clear cursor data when CPU or process modules are no longer active // Clear cursor data when CPU or process modules are no longer active
if (!enabledModules.includes("cpu")) { if (!enabledModules.includes("cpu")) {
cpuCursor = "" cpuCursor = "";
cpuSampleCount = 0 cpuSampleCount = 0;
} }
if (!enabledModules.includes("processes")) { if (!enabledModules.includes("processes")) {
procCursor = "" procCursor = "";
processSampleCount = 0 processSampleCount = 0;
} }
} }
} }
function setGpuPciIds(pciIds) { function setGpuPciIds(pciIds) {
gpuPciIds = Array.isArray(pciIds) ? pciIds : [] gpuPciIds = Array.isArray(pciIds) ? pciIds : [];
} }
function addGpuPciId(pciId) { function addGpuPciId(pciId) {
const currentCount = gpuPciIdRefCounts[pciId] || 0 const currentCount = gpuPciIdRefCounts[pciId] || 0;
gpuPciIdRefCounts[pciId] = currentCount + 1 gpuPciIdRefCounts[pciId] = currentCount + 1;
// Add to gpuPciIds array if not already there // Add to gpuPciIds array if not already there
if (!gpuPciIds.includes(pciId)) { if (!gpuPciIds.includes(pciId)) {
gpuPciIds = gpuPciIds.concat([pciId]) gpuPciIds = gpuPciIds.concat([pciId]);
} }
console.log("Adding GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]) console.log("Adding GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]);
// Force property change notification // Force property change notification
gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts) gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts);
} }
function removeGpuPciId(pciId) { function removeGpuPciId(pciId) {
const currentCount = gpuPciIdRefCounts[pciId] || 0 const currentCount = gpuPciIdRefCounts[pciId] || 0;
if (currentCount > 1) { if (currentCount > 1) {
// Decrement reference count // Decrement reference count
gpuPciIdRefCounts[pciId] = currentCount - 1 gpuPciIdRefCounts[pciId] = currentCount - 1;
console.log("Removing GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]) console.log("Removing GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]);
} else if (currentCount === 1) { } else if (currentCount === 1) {
// Remove completely when count reaches 0 // Remove completely when count reaches 0
delete gpuPciIdRefCounts[pciId] delete gpuPciIdRefCounts[pciId];
const index = gpuPciIds.indexOf(pciId) const index = gpuPciIds.indexOf(pciId);
if (index > -1) { if (index > -1) {
gpuPciIds = gpuPciIds.slice() gpuPciIds = gpuPciIds.slice();
gpuPciIds.splice(index, 1) gpuPciIds.splice(index, 1);
} }
// Clear temperature data for this GPU when no longer monitored // Clear temperature data for this GPU when no longer monitored
if (availableGpus && availableGpus.length > 0) { if (availableGpus && availableGpus.length > 0) {
const updatedGpus = availableGpus.slice() const updatedGpus = availableGpus.slice();
for (var i = 0; i < updatedGpus.length; i++) { for (var i = 0; i < updatedGpus.length; i++) {
if (updatedGpus[i].pciId === pciId) { if (updatedGpus[i].pciId === pciId) {
updatedGpus[i] = Object.assign({}, updatedGpus[i], { updatedGpus[i] = Object.assign({}, updatedGpus[i], {
"temperature": 0 "temperature": 0
}) });
} }
} }
availableGpus = updatedGpus availableGpus = updatedGpus;
} }
console.log("Removing GPU PCI ID completely:", pciId) console.log("Removing GPU PCI ID completely:", pciId);
} }
// Force property change notification // Force property change notification
gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts) gpuPciIdRefCounts = Object.assign({}, gpuPciIdRefCounts);
} }
function setProcessOptions(limit = 20, sort = "cpu", disableCpu = false) { function setProcessOptions(limit = 20, sort = "cpu", disableCpu = false) {
processLimit = limit processLimit = limit;
processSort = sort processSort = sort;
noCpu = disableCpu noCpu = disableCpu;
} }
function updateAllStats() { function updateAllStats() {
if (dgopAvailable && refCount > 0 && enabledModules.length > 0) { if (dgopAvailable && refCount > 0 && enabledModules.length > 0) {
isUpdating = true isUpdating = true;
dgopProcess.running = true dgopProcess.running = true;
} else { } else {
isUpdating = false isUpdating = false;
} }
} }
function initializeGpuMetadata() { function initializeGpuMetadata() {
if (!dgopAvailable) if (!dgopAvailable)
return return;
gpuInitProcess.running = true gpuInitProcess.running = true;
} }
function initializeSystemMetadata() { function initializeSystemMetadata() {
if (!dgopAvailable) if (!dgopAvailable)
return return;
systemInitProcess.running = true systemInitProcess.running = true;
} }
function buildDgopCommand() { function buildDgopCommand() {
const cmd = ["dgop", "meta", "--json"] const cmd = ["dgop", "meta", "--json"];
if (enabledModules.length === 0) { if (enabledModules.length === 0) {
// Don't run if no modules are needed // Don't run if no modules are needed
return [] return [];
} }
// Replace 'gpu' with 'gpu-temp' when we have PCI IDs to monitor // Replace 'gpu' with 'gpu-temp' when we have PCI IDs to monitor
const finalModules = [] const finalModules = [];
for (const module of enabledModules) { for (const module of enabledModules) {
if (module === "gpu" && gpuPciIds.length > 0) { if (module === "gpu" && gpuPciIds.length > 0) {
finalModules.push("gpu-temp") finalModules.push("gpu-temp");
} else if (module !== "gpu") { } else if (module !== "gpu") {
finalModules.push(module) finalModules.push(module);
} }
} }
// Add gpu-temp module automatically when we have PCI IDs to monitor // Add gpu-temp module automatically when we have PCI IDs to monitor
if (gpuPciIds.length > 0 && finalModules.indexOf("gpu-temp") === -1) { if (gpuPciIds.length > 0 && finalModules.indexOf("gpu-temp") === -1) {
finalModules.push("gpu-temp") finalModules.push("gpu-temp");
} }
if (enabledModules.indexOf("all") !== -1) { if (enabledModules.indexOf("all") !== -1) {
cmd.push("--modules", "all") cmd.push("--modules", "all");
} else if (finalModules.length > 0) { } else if (finalModules.length > 0) {
const moduleList = finalModules.join(",") const moduleList = finalModules.join(",");
cmd.push("--modules", moduleList) cmd.push("--modules", moduleList);
} else { } else {
return [] return [];
} }
// Add cursor data if available for accurate CPU percentages // Add cursor data if available for accurate CPU percentages
if ((enabledModules.includes("cpu") || enabledModules.includes("all")) && cpuCursor) { if ((enabledModules.includes("cpu") || enabledModules.includes("all")) && cpuCursor) {
cmd.push("--cpu-cursor", cpuCursor) cmd.push("--cpu-cursor", cpuCursor);
} }
if ((enabledModules.includes("processes") || enabledModules.includes("all")) && procCursor) { if ((enabledModules.includes("processes") || enabledModules.includes("all")) && procCursor) {
cmd.push("--proc-cursor", procCursor) cmd.push("--proc-cursor", procCursor);
} }
if (gpuPciIds.length > 0) { if (gpuPciIds.length > 0) {
cmd.push("--gpu-pci-ids", gpuPciIds.join(",")) cmd.push("--gpu-pci-ids", gpuPciIds.join(","));
} }
if (enabledModules.indexOf("processes") !== -1 || enabledModules.indexOf("all") !== -1) { if (enabledModules.indexOf("processes") !== -1 || enabledModules.indexOf("all") !== -1) {
cmd.push("--limit", "100") // Get more data for client sorting cmd.push("--limit", "100"); // Get more data for client sorting
cmd.push("--sort", "cpu") // Always get CPU sorted data cmd.push("--sort", "cpu"); // Always get CPU sorted data
if (noCpu) { if (noCpu) {
cmd.push("--no-cpu") cmd.push("--no-cpu");
} }
} }
return cmd return cmd;
} }
function parseData(data) { function parseData(data) {
if (data.cpu) { if (data.cpu) {
const cpu = data.cpu const cpu = data.cpu;
cpuSampleCount++ cpuSampleCount++;
cpuUsage = cpu.usage || 0 cpuUsage = cpu.usage || 0;
cpuFrequency = cpu.frequency || 0 cpuFrequency = cpu.frequency || 0;
cpuTemperature = cpu.temperature || 0 cpuTemperature = cpu.temperature || 0;
cpuCores = cpu.count || 1 cpuCores = cpu.count || 1;
cpuModel = cpu.model || "" cpuModel = cpu.model || "";
perCoreCpuUsage = cpu.coreUsage || [] perCoreCpuUsage = cpu.coreUsage || [];
addToHistory(cpuHistory, cpuUsage) addToHistory(cpuHistory, cpuUsage);
if (cpu.cursor) { if (cpu.cursor) {
cpuCursor = cpu.cursor cpuCursor = cpu.cursor;
} }
} }
if (data.memory) { if (data.memory) {
const mem = data.memory const mem = data.memory;
const totalKB = mem.total || 0 const totalKB = mem.total || 0;
const availableKB = mem.available || 0 const availableKB = mem.available || 0;
const freeKB = mem.free || 0 const freeKB = mem.free || 0;
totalMemoryMB = totalKB / 1024 totalMemoryMB = totalKB / 1024;
availableMemoryMB = availableKB / 1024 availableMemoryMB = availableKB / 1024;
freeMemoryMB = freeKB / 1024 freeMemoryMB = freeKB / 1024;
usedMemoryMB = totalMemoryMB - availableMemoryMB usedMemoryMB = totalMemoryMB - availableMemoryMB;
memoryUsage = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0 memoryUsage = totalKB > 0 ? ((totalKB - availableKB) / totalKB) * 100 : 0;
totalMemoryKB = totalKB totalMemoryKB = totalKB;
usedMemoryKB = totalKB - availableKB usedMemoryKB = totalKB - availableKB;
totalSwapKB = mem.swaptotal || 0 totalSwapKB = mem.swaptotal || 0;
usedSwapKB = (mem.swaptotal || 0) - (mem.swapfree || 0) usedSwapKB = (mem.swaptotal || 0) - (mem.swapfree || 0);
addToHistory(memoryHistory, memoryUsage) addToHistory(memoryHistory, memoryUsage);
} }
if (data.network && Array.isArray(data.network)) { if (data.network && Array.isArray(data.network)) {
networkInterfaces = data.network networkInterfaces = data.network;
let totalRx = 0 let totalRx = 0;
let totalTx = 0 let totalTx = 0;
for (const iface of data.network) { for (const iface of data.network) {
totalRx += iface.rx || 0 totalRx += iface.rx || 0;
totalTx += iface.tx || 0 totalTx += iface.tx || 0;
} }
if (lastNetworkStats) { if (lastNetworkStats) {
const timeDiff = updateInterval / 1000 const timeDiff = updateInterval / 1000;
const rxDiff = totalRx - lastNetworkStats.rx const rxDiff = totalRx - lastNetworkStats.rx;
const txDiff = totalTx - lastNetworkStats.tx const txDiff = totalTx - lastNetworkStats.tx;
networkRxRate = Math.max(0, rxDiff / timeDiff) networkRxRate = Math.max(0, rxDiff / timeDiff);
networkTxRate = Math.max(0, txDiff / timeDiff) networkTxRate = Math.max(0, txDiff / timeDiff);
addToHistory(networkHistory.rx, networkRxRate / 1024) addToHistory(networkHistory.rx, networkRxRate / 1024);
addToHistory(networkHistory.tx, networkTxRate / 1024) addToHistory(networkHistory.tx, networkTxRate / 1024);
} }
lastNetworkStats = { lastNetworkStats = {
"rx": totalRx, "rx": totalRx,
"tx": totalTx "tx": totalTx
} };
} }
if (data.disk && Array.isArray(data.disk)) { if (data.disk && Array.isArray(data.disk)) {
diskDevices = data.disk diskDevices = data.disk;
let totalRead = 0 let totalRead = 0;
let totalWrite = 0 let totalWrite = 0;
for (const disk of data.disk) { for (const disk of data.disk) {
totalRead += (disk.read || 0) * 512 totalRead += (disk.read || 0) * 512;
totalWrite += (disk.write || 0) * 512 totalWrite += (disk.write || 0) * 512;
} }
if (lastDiskStats) { if (lastDiskStats) {
const timeDiff = updateInterval / 1000 const timeDiff = updateInterval / 1000;
const readDiff = totalRead - lastDiskStats.read const readDiff = totalRead - lastDiskStats.read;
const writeDiff = totalWrite - lastDiskStats.write const writeDiff = totalWrite - lastDiskStats.write;
diskReadRate = Math.max(0, readDiff / timeDiff) diskReadRate = Math.max(0, readDiff / timeDiff);
diskWriteRate = Math.max(0, writeDiff / timeDiff) diskWriteRate = Math.max(0, writeDiff / timeDiff);
addToHistory(diskHistory.read, diskReadRate / (1024 * 1024)) addToHistory(diskHistory.read, diskReadRate / (1024 * 1024));
addToHistory(diskHistory.write, diskWriteRate / (1024 * 1024)) addToHistory(diskHistory.write, diskWriteRate / (1024 * 1024));
} }
lastDiskStats = { lastDiskStats = {
"read": totalRead, "read": totalRead,
"write": totalWrite "write": totalWrite
} };
} }
if (data.diskmounts) { if (data.diskmounts) {
diskMounts = data.diskmounts || [] diskMounts = data.diskmounts || [];
} }
if (data.processes && Array.isArray(data.processes)) { if (data.processes && Array.isArray(data.processes)) {
const newProcesses = [] const newProcesses = [];
processSampleCount++ processSampleCount++;
for (const proc of data.processes) { for (const proc of data.processes) {
const cpuUsage = processSampleCount >= 2 ? (proc.cpu || 0) : 0 const cpuUsage = processSampleCount >= 2 ? (proc.cpu || 0) : 0;
newProcesses.push({ newProcesses.push({
"pid": proc.pid || 0, "pid": proc.pid || 0,
"ppid": proc.ppid || 0, "ppid": proc.ppid || 0,
"cpu": cpuUsage, "cpu": cpuUsage,
"memoryPercent": proc.memoryPercent || proc.pssPercent || 0, "memoryPercent": proc.memoryPercent || proc.pssPercent || 0,
"memoryKB": proc.memoryKB || proc.pssKB || 0, "memoryKB": proc.memoryKB || proc.pssKB || 0,
"command": proc.command || "", "command": proc.command || "",
"fullCommand": proc.fullCommand || "", "fullCommand": proc.fullCommand || "",
"displayName": (proc.command && proc.command.length > 15) ? proc.command.substring(0, 15) + "..." : (proc.command || "") "displayName": (proc.command && proc.command.length > 15) ? proc.command.substring(0, 15) + "..." : (proc.command || "")
}) });
} }
allProcesses = newProcesses allProcesses = newProcesses;
applySorting() applySorting();
if (data.cursor) { if (data.cursor) {
procCursor = data.cursor procCursor = data.cursor;
} }
} }
const gpuData = (data.gpu && data.gpu.gpus) || data.gpus const gpuData = (data.gpu && data.gpu.gpus) || data.gpus;
if (gpuData && Array.isArray(gpuData)) { if (gpuData && Array.isArray(gpuData)) {
// Check if this is temperature update data (has PCI IDs being monitored) // Check if this is temperature update data (has PCI IDs being monitored)
if (gpuPciIds.length > 0 && availableGpus && availableGpus.length > 0) { if (gpuPciIds.length > 0 && availableGpus && availableGpus.length > 0) {
// This is temperature data - merge with existing GPU metadata // This is temperature data - merge with existing GPU metadata
const updatedGpus = availableGpus.slice() const updatedGpus = availableGpus.slice();
for (var i = 0; i < updatedGpus.length; i++) { for (var i = 0; i < updatedGpus.length; i++) {
const existingGpu = updatedGpus[i] const existingGpu = updatedGpus[i];
const tempGpu = gpuData.find(g => g.pciId === existingGpu.pciId) const tempGpu = gpuData.find(g => g.pciId === existingGpu.pciId);
// Only update temperature if this GPU's PCI ID is being monitored // Only update temperature if this GPU's PCI ID is being monitored
if (tempGpu && gpuPciIds.includes(existingGpu.pciId)) { if (tempGpu && gpuPciIds.includes(existingGpu.pciId)) {
updatedGpus[i] = Object.assign({}, existingGpu, { updatedGpus[i] = Object.assign({}, existingGpu, {
"temperature": tempGpu.temperature || 0 "temperature": tempGpu.temperature || 0
}) });
} }
} }
availableGpus = updatedGpus availableGpus = updatedGpus;
} else { } else {
// This is initial GPU metadata - set the full list // This is initial GPU metadata - set the full list
const gpuList = [] const gpuList = [];
for (const gpu of gpuData) { for (const gpu of gpuData) {
let displayName = gpu.displayName || gpu.name || "Unknown GPU" let displayName = gpu.displayName || gpu.name || "Unknown GPU";
let fullName = gpu.fullName || gpu.name || "Unknown GPU" let fullName = gpu.fullName || gpu.name || "Unknown GPU";
gpuList.push({ gpuList.push({
"driver": gpu.driver || "", "driver": gpu.driver || "",
"vendor": gpu.vendor || "", "vendor": gpu.vendor || "",
"displayName": displayName, "displayName": displayName,
"fullName": fullName, "fullName": fullName,
"pciId": gpu.pciId || "", "pciId": gpu.pciId || "",
"temperature": gpu.temperature || 0 "temperature": gpu.temperature || 0
}) });
} }
availableGpus = gpuList availableGpus = gpuList;
} }
} }
if (data.system) { if (data.system) {
const sys = data.system const sys = data.system;
loadAverage = sys.loadavg || "" loadAverage = sys.loadavg || "";
processCount = sys.processes || 0 processCount = sys.processes || 0;
threadCount = sys.threads || 0 threadCount = sys.threads || 0;
bootTime = sys.boottime || "" bootTime = sys.boottime || "";
updateUptime();
} }
const hwData = data.hardware || (data.hostname || data.kernel || data.distro || data.arch) ? data : null const hwData = data.hardware || (data.hostname || data.kernel || data.distro || data.arch) ? data : null;
if (hwData) { if (hwData) {
hostname = hwData.hostname || "" hostname = hwData.hostname || "";
kernelVersion = hwData.kernel || "" kernelVersion = hwData.kernel || "";
distribution = hwData.distro || "" distribution = hwData.distro || "";
architecture = hwData.arch || "" architecture = hwData.arch || "";
motherboard = (hwData.bios && hwData.bios.motherboard) || "" motherboard = (hwData.bios && hwData.bios.motherboard) || "";
biosVersion = (hwData.bios && hwData.bios.version) || "" biosVersion = (hwData.bios && hwData.bios.version) || "";
} }
isUpdating = false isUpdating = false;
} }
function addToHistory(array, value) { function addToHistory(array, value) {
array.push(value) array.push(value);
if (array.length > historySize) { if (array.length > historySize) {
array.shift() array.shift();
} }
} }
function getProcessIcon(command) { function getProcessIcon(command) {
const cmd = command.toLowerCase() const cmd = command.toLowerCase();
if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser") || cmd.includes("chromium")) { if (cmd.includes("firefox") || cmd.includes("chrome") || cmd.includes("browser") || cmd.includes("chromium")) {
return "web" return "web";
} }
if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) { if (cmd.includes("code") || cmd.includes("editor") || cmd.includes("vim")) {
return "code" return "code";
} }
if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) { if (cmd.includes("terminal") || cmd.includes("bash") || cmd.includes("zsh")) {
return "terminal" return "terminal";
} }
if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) { if (cmd.includes("music") || cmd.includes("audio") || cmd.includes("spotify")) {
return "music_note" return "music_note";
} }
if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) { if (cmd.includes("video") || cmd.includes("vlc") || cmd.includes("mpv")) {
return "play_circle" return "play_circle";
} }
if (cmd.includes("systemd") || cmd.includes("elogind") || cmd.includes("kernel") || cmd.includes("kthread") || cmd.includes("kworker")) { if (cmd.includes("systemd") || cmd.includes("elogind") || cmd.includes("kernel") || cmd.includes("kthread") || cmd.includes("kworker")) {
return "settings" return "settings";
} }
return "memory" return "memory";
} }
function formatCpuUsage(cpu) { function formatCpuUsage(cpu) {
return (cpu || 0).toFixed(1) + "%" return (cpu || 0).toFixed(1) + "%";
} }
function formatMemoryUsage(memoryKB) { function formatMemoryUsage(memoryKB) {
const mem = memoryKB || 0 const mem = memoryKB || 0;
if (mem < 1024) { if (mem < 1024) {
return mem.toFixed(0) + " KB" return mem.toFixed(0) + " KB";
} else if (mem < 1024 * 1024) { } else if (mem < 1024 * 1024) {
return (mem / 1024).toFixed(1) + " MB" return (mem / 1024).toFixed(1) + " MB";
} else { } else {
return (mem / (1024 * 1024)).toFixed(1) + " GB" return (mem / (1024 * 1024)).toFixed(1) + " GB";
} }
} }
function formatSystemMemory(memoryKB) { function formatSystemMemory(memoryKB) {
const mem = memoryKB || 0 const mem = memoryKB || 0;
if (mem === 0) { if (mem === 0) {
return "--" return "--";
} }
if (mem < 1024 * 1024) { if (mem < 1024 * 1024) {
return (mem / 1024).toFixed(0) + " MB" return (mem / 1024).toFixed(0) + " MB";
} else { } else {
return (mem / (1024 * 1024)).toFixed(1) + " GB" return (mem / (1024 * 1024)).toFixed(1) + " GB";
} }
} }
function killProcess(pid) { function killProcess(pid) {
if (pid > 0) { if (pid > 0) {
Quickshell.execDetached("kill", [pid.toString()]) Quickshell.execDetached("kill", [pid.toString()]);
} }
} }
function updateUptime() {
if (!bootTime) {
uptime = "";
shortUptime = "";
return;
}
const bootDate = new Date(bootTime.replace(" ", "T"));
if (isNaN(bootDate.getTime())) {
uptime = "";
shortUptime = "";
return;
}
const now = new Date();
const seconds = Math.floor((now - bootDate) / 1000);
const days = Math.floor(seconds / 86400);
const hours = Math.floor((seconds % 86400) / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const parts = [];
if (days > 0)
parts.push(`${days} day${days === 1 ? "" : "s"}`);
if (hours > 0)
parts.push(`${hours} hour${hours === 1 ? "" : "s"}`);
if (minutes > 0)
parts.push(`${minutes} minute${minutes === 1 ? "" : "s"}`);
uptime = parts.length > 0 ? `up ${parts.join(", ")}` : `up ${seconds} seconds`;
var shortStr = "up";
if (days > 0)
shortStr += ` ${days}d`;
if (hours > 0)
shortStr += ` ${hours}h`;
if (minutes > 0)
shortStr += ` ${minutes}m`;
shortUptime = shortStr;
}
function setSortBy(newSortBy) { function setSortBy(newSortBy) {
if (newSortBy !== currentSort) { if (newSortBy !== currentSort) {
currentSort = newSortBy currentSort = newSortBy;
applySorting() applySorting();
} }
} }
function applySorting() { function applySorting() {
if (!allProcesses || allProcesses.length === 0) { if (!allProcesses || allProcesses.length === 0) {
return return;
} }
const sorted = allProcesses.slice() const sorted = allProcesses.slice();
sorted.sort((a, b) => { sorted.sort((a, b) => {
let valueA, valueB let valueA, valueB;
switch (currentSort) { switch (currentSort) {
case "cpu": case "cpu":
valueA = a.cpu || 0 valueA = a.cpu || 0;
valueB = b.cpu || 0 valueB = b.cpu || 0;
return valueB - valueA return valueB - valueA;
case "memory": case "memory":
valueA = a.memoryKB || 0 valueA = a.memoryKB || 0;
valueB = b.memoryKB || 0 valueB = b.memoryKB || 0;
return valueB - valueA return valueB - valueA;
case "name": case "name":
valueA = (a.command || "").toLowerCase() valueA = (a.command || "").toLowerCase();
valueB = (b.command || "").toLowerCase() valueB = (b.command || "").toLowerCase();
return valueA.localeCompare(valueB) return valueA.localeCompare(valueB);
case "pid": case "pid":
valueA = a.pid || 0 valueA = a.pid || 0;
valueB = b.pid || 0 valueB = b.pid || 0;
return valueA - valueB return valueA - valueB;
default: default:
return 0 return 0;
} }
}) });
processes = sorted.slice(0, processLimit) processes = sorted.slice(0, processLimit);
} }
Timer { Timer {
@@ -585,26 +627,26 @@ Singleton {
id: dgopProcess id: dgopProcess
command: root.buildDgopCommand() command: root.buildDgopCommand()
running: false running: false
onCommandChanged: { onCommandChanged:
//console.log("DgopService command:", JSON.stringify(command)) //console.log("DgopService command:", JSON.stringify(command))
} {}
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Dgop process failed with exit code:", exitCode) console.warn("Dgop process failed with exit code:", exitCode);
isUpdating = false isUpdating = false;
} }
} }
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.trim()) { if (text.trim()) {
try { try {
const data = JSON.parse(text.trim()) const data = JSON.parse(text.trim());
parseData(data) parseData(data);
} catch (e) { } catch (e) {
console.warn("Failed to parse dgop JSON:", e) console.warn("Failed to parse dgop JSON:", e);
console.warn("Raw text was:", text.substring(0, 200)) console.warn("Raw text was:", text.substring(0, 200));
isUpdating = false isUpdating = false;
} }
} }
} }
@@ -617,17 +659,17 @@ Singleton {
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("GPU init process failed with exit code:", exitCode) console.warn("GPU init process failed with exit code:", exitCode);
} }
} }
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.trim()) { if (text.trim()) {
try { try {
const data = JSON.parse(text.trim()) const data = JSON.parse(text.trim());
parseData(data) parseData(data);
} catch (e) { } catch (e) {
console.warn("Failed to parse GPU init JSON:", e) console.warn("Failed to parse GPU init JSON:", e);
} }
} }
} }
@@ -640,17 +682,17 @@ Singleton {
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("System init process failed with exit code:", exitCode) console.warn("System init process failed with exit code:", exitCode);
} }
} }
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.trim()) { if (text.trim()) {
try { try {
const data = JSON.parse(text.trim()) const data = JSON.parse(text.trim());
parseData(data) parseData(data);
} catch (e) { } catch (e) {
console.warn("Failed to parse system init JSON:", e) console.warn("Failed to parse system init JSON:", e);
} }
} }
} }
@@ -662,20 +704,20 @@ Singleton {
command: ["which", "dgop"] command: ["which", "dgop"]
running: false running: false
onExited: exitCode => { onExited: exitCode => {
dgopAvailable = (exitCode === 0) dgopAvailable = (exitCode === 0);
if (dgopAvailable) { if (dgopAvailable) {
initializeGpuMetadata() initializeGpuMetadata();
initializeSystemMetadata() initializeSystemMetadata();
if (SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.length > 0) { if (SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.length > 0) {
for (const pciId of SessionData.enabledGpuPciIds) { for (const pciId of SessionData.enabledGpuPciIds) {
addGpuPciId(pciId) addGpuPciId(pciId);
} }
if (refCount > 0 && enabledModules.length > 0) { if (refCount > 0 && enabledModules.length > 0) {
updateAllStats() updateAllStats();
} }
} }
} else { } else {
console.warn("dgop is not installed or not in PATH") console.warn("dgop is not installed or not in PATH");
} }
} }
} }
@@ -686,33 +728,33 @@ Singleton {
running: false running: false
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Failed to read /etc/os-release") console.warn("Failed to read /etc/os-release");
} }
} }
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: { onStreamFinished: {
if (text.trim()) { if (text.trim()) {
try { try {
const lines = text.trim().split('\n') const lines = text.trim().split('\n');
let prettyName = "" let prettyName = "";
let name = "" let name = "";
for (const line of lines) { for (const line of lines) {
const trimmedLine = line.trim() const trimmedLine = line.trim();
if (trimmedLine.startsWith('PRETTY_NAME=')) { if (trimmedLine.startsWith('PRETTY_NAME=')) {
prettyName = trimmedLine.substring(12).replace(/^["']|["']$/g, '') prettyName = trimmedLine.substring(12).replace(/^["']|["']$/g, '');
} else if (trimmedLine.startsWith('NAME=')) { } else if (trimmedLine.startsWith('NAME=')) {
name = trimmedLine.substring(5).replace(/^["']|["']$/g, '') name = trimmedLine.substring(5).replace(/^["']|["']$/g, '');
} }
} }
// Prefer PRETTY_NAME, fallback to NAME // Prefer PRETTY_NAME, fallback to NAME
const distroName = prettyName || name || "Linux" const distroName = prettyName || name || "Linux";
distribution = distroName distribution = distroName;
console.info("Detected distribution:", distroName) console.info("Detected distribution:", distroName);
} catch (e) { } catch (e) {
console.warn("Failed to parse /etc/os-release:", e) console.warn("Failed to parse /etc/os-release:", e);
distribution = "Linux" distribution = "Linux";
} }
} }
} }
@@ -720,7 +762,7 @@ Singleton {
} }
Component.onCompleted: { Component.onCompleted: {
dgopCheckProcess.running = true dgopCheckProcess.running = true;
osReleaseProcess.running = true osReleaseProcess.running = true;
} }
} }

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
@@ -23,9 +22,9 @@ Singleton {
readonly property bool nativeInhibitorAvailable: { readonly property bool nativeInhibitorAvailable: {
try { try {
return typeof IdleInhibitor !== "undefined" return typeof IdleInhibitor !== "undefined";
} catch (e) { } catch (e) {
return false return false;
} }
} }
@@ -42,10 +41,10 @@ Singleton {
property string seat: "" property string seat: ""
property string display: "" property string display: ""
signal sessionLocked() signal sessionLocked
signal sessionUnlocked() signal sessionUnlocked
signal prepareForSleep() signal sessionResumed
signal loginctlStateChanged() signal loginctlStateChanged
property bool stateInitialized: false property bool stateInitialized: false
@@ -57,30 +56,29 @@ Singleton {
running: true running: true
repeat: false repeat: false
onTriggered: { onTriggered: {
detectElogindProcess.running = true detectElogindProcess.running = true;
detectHibernateProcess.running = true detectHibernateProcess.running = true;
detectPrimeRunProcess.running = true detectPrimeRunProcess.running = true;
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable) console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable);
if (!SettingsData.loginctlLockIntegration) { if (!SettingsData.loginctlLockIntegration) {
console.log("SessionService: loginctl lock integration disabled by user") console.log("SessionService: loginctl lock integration disabled by user");
return return;
} }
if (socketPath && socketPath.length > 0) { if (socketPath && socketPath.length > 0) {
checkDMSCapabilities() checkDMSCapabilities();
} else { } else {
console.log("SessionService: DMS_SOCKET not set") console.log("SessionService: DMS_SOCKET not set");
} }
} }
} }
Process { Process {
id: detectUwsmProcess id: detectUwsmProcess
running: false running: false
command: ["which", "uwsm"] command: ["which", "uwsm"]
onExited: function (exitCode) { onExited: function (exitCode) {
hasUwsm = (exitCode === 0) hasUwsm = (exitCode === 0);
} }
} }
@@ -90,8 +88,8 @@ Singleton {
command: ["sh", "-c", "ps -eo comm= | grep -E '^(elogind|elogind-daemon)$'"] command: ["sh", "-c", "ps -eo comm= | grep -E '^(elogind|elogind-daemon)$'"]
onExited: function (exitCode) { onExited: function (exitCode) {
console.log("SessionService: Elogind detection exited with code", exitCode) console.log("SessionService: Elogind detection exited with code", exitCode);
isElogind = (exitCode === 0) isElogind = (exitCode === 0);
} }
} }
@@ -101,7 +99,7 @@ Singleton {
command: ["grep", "-q", "disk", "/sys/power/state"] command: ["grep", "-q", "disk", "/sys/power/state"]
onExited: function (exitCode) { onExited: function (exitCode) {
hibernateSupported = (exitCode === 0) hibernateSupported = (exitCode === 0);
} }
} }
@@ -111,7 +109,7 @@ Singleton {
command: ["which", "prime-run"] command: ["which", "prime-run"]
onExited: function (exitCode) { onExited: function (exitCode) {
hasPrimeRun = (exitCode === 0) hasPrimeRun = (exitCode === 0);
} }
} }
@@ -124,164 +122,167 @@ Singleton {
splitMarker: "\n" splitMarker: "\n"
onRead: data => { onRead: data => {
if (data.trim().toLowerCase().includes("not running")) { if (data.trim().toLowerCase().includes("not running")) {
_logout() _logout();
} }
} }
} }
onExited: function (exitCode) { onExited: function (exitCode) {
if (exitCode === 0) { if (exitCode === 0) {
return return;
} }
_logout() _logout();
} }
} }
function escapeShellArg(arg) { function escapeShellArg(arg) {
return "'" + arg.replace(/'/g, "'\\''") + "'" return "'" + arg.replace(/'/g, "'\\''") + "'";
} }
function needsShellExecution(prefix) { function needsShellExecution(prefix) {
if (!prefix || prefix.length === 0) return false if (!prefix || prefix.length === 0)
return /[;&|<>()$`\\"']/.test(prefix) return false;
return /[;&|<>()$`\\"']/.test(prefix);
} }
function launchDesktopEntry(desktopEntry, usePrimeRun) { function launchDesktopEntry(desktopEntry, usePrimeRun) {
let cmd = desktopEntry.command let cmd = desktopEntry.command;
if (usePrimeRun && hasPrimeRun) { if (usePrimeRun && hasPrimeRun) {
cmd = ["prime-run"].concat(cmd) cmd = ["prime-run"].concat(cmd);
} }
const userPrefix = SettingsData.launchPrefix?.trim() || "" const userPrefix = SettingsData.launchPrefix?.trim() || "";
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "" const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
if (prefix.length > 0 && needsShellExecution(prefix)) { if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ") const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
const shellCmd = `${prefix} ${escapedCmd}` const shellCmd = `${prefix} ${escapedCmd}`;
Quickshell.execDetached({ Quickshell.execDetached({
command: ["sh", "-c", shellCmd], command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"), workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
}) });
} else { } else {
if (prefix.length > 0) { if (prefix.length > 0) {
const launchPrefix = prefix.split(" ") const launchPrefix = prefix.split(" ");
cmd = launchPrefix.concat(cmd) cmd = launchPrefix.concat(cmd);
} }
Quickshell.execDetached({ Quickshell.execDetached({
command: cmd, command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"), workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
}) });
} }
} }
function launchDesktopAction(desktopEntry, action, usePrimeRun) { function launchDesktopAction(desktopEntry, action, usePrimeRun) {
let cmd = action.command let cmd = action.command;
if (usePrimeRun && hasPrimeRun) { if (usePrimeRun && hasPrimeRun) {
cmd = ["prime-run"].concat(cmd) cmd = ["prime-run"].concat(cmd);
} }
const userPrefix = SettingsData.launchPrefix?.trim() || "" const userPrefix = SettingsData.launchPrefix?.trim() || "";
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "" const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
if (prefix.length > 0 && needsShellExecution(prefix)) { if (prefix.length > 0 && needsShellExecution(prefix)) {
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ") const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
const shellCmd = `${prefix} ${escapedCmd}` const shellCmd = `${prefix} ${escapedCmd}`;
Quickshell.execDetached({ Quickshell.execDetached({
command: ["sh", "-c", shellCmd], command: ["sh", "-c", shellCmd],
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"), workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
}) });
} else { } else {
if (prefix.length > 0) { if (prefix.length > 0) {
const launchPrefix = prefix.split(" ") const launchPrefix = prefix.split(" ");
cmd = launchPrefix.concat(cmd) cmd = launchPrefix.concat(cmd);
} }
Quickshell.execDetached({ Quickshell.execDetached({
command: cmd, command: cmd,
workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME"), workingDirectory: desktopEntry.workingDirectory || Quickshell.env("HOME")
}) });
} }
} }
// * Session management // * Session management
function logout() { function logout() {
if (hasUwsm) { if (hasUwsm) {
uwsmLogout.running = true uwsmLogout.running = true;
} }
_logout() _logout();
} }
function _logout() { function _logout() {
if (SettingsData.customPowerActionLogout.length === 0) { if (SettingsData.customPowerActionLogout.length === 0) {
if (CompositorService.isNiri) { if (CompositorService.isNiri) {
NiriService.quit() NiriService.quit();
return return;
} }
if (CompositorService.isDwl) { if (CompositorService.isDwl) {
DwlService.quit() DwlService.quit();
return return;
} }
if (CompositorService.isSway) { if (CompositorService.isSway) {
try { I3.dispatch("exit") } catch(_){} try {
return I3.dispatch("exit");
} catch (_) {}
return;
} }
Hyprland.dispatch("exit") Hyprland.dispatch("exit");
} else { } else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLogout]);
} }
} }
function suspend() { function suspend() {
if (SettingsData.customPowerActionSuspend.length === 0) { if (SettingsData.customPowerActionSuspend.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend"]) Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend"]);
} else { } else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionSuspend]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionSuspend]);
} }
} }
function hibernate() { function hibernate() {
if (SettingsData.customPowerActionHibernate.length === 0) { if (SettingsData.customPowerActionHibernate.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "hibernate"]) Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "hibernate"]);
} else { } else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionHibernate]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionHibernate]);
} }
} }
function suspendThenHibernate() { function suspendThenHibernate() {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend-then-hibernate"]) Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "suspend-then-hibernate"]);
} }
function suspendWithBehavior(behavior) { function suspendWithBehavior(behavior) {
if (behavior === SettingsData.SuspendBehavior.Hibernate) { if (behavior === SettingsData.SuspendBehavior.Hibernate) {
hibernate() hibernate();
} else if (behavior === SettingsData.SuspendBehavior.SuspendThenHibernate) { } else if (behavior === SettingsData.SuspendBehavior.SuspendThenHibernate) {
suspendThenHibernate() suspendThenHibernate();
} else { } else {
suspend() suspend();
} }
} }
function reboot() { function reboot() {
if (SettingsData.customPowerActionReboot.length === 0) { if (SettingsData.customPowerActionReboot.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "reboot"]) Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "reboot"]);
} else { } else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionReboot]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionReboot]);
} }
} }
function poweroff() { function poweroff() {
if (SettingsData.customPowerActionPowerOff.length === 0) { if (SettingsData.customPowerActionPowerOff.length === 0) {
Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "poweroff"]) Quickshell.execDetached([isElogind ? "loginctl" : "systemctl", "poweroff"]);
} else { } else {
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionPowerOff]) Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionPowerOff]);
} }
} }
@@ -290,42 +291,42 @@ Singleton {
function enableIdleInhibit() { function enableIdleInhibit() {
if (idleInhibited) { if (idleInhibited) {
return return;
} }
console.log("SessionService: Enabling idle inhibit (native:", nativeInhibitorAvailable, ")") console.log("SessionService: Enabling idle inhibit (native:", nativeInhibitorAvailable, ")");
idleInhibited = true idleInhibited = true;
inhibitorChanged() inhibitorChanged();
} }
function disableIdleInhibit() { function disableIdleInhibit() {
if (!idleInhibited) { if (!idleInhibited) {
return return;
} }
console.log("SessionService: Disabling idle inhibit (native:", nativeInhibitorAvailable, ")") console.log("SessionService: Disabling idle inhibit (native:", nativeInhibitorAvailable, ")");
idleInhibited = false idleInhibited = false;
inhibitorChanged() inhibitorChanged();
} }
function toggleIdleInhibit() { function toggleIdleInhibit() {
if (idleInhibited) { if (idleInhibited) {
disableIdleInhibit() disableIdleInhibit();
} else { } else {
enableIdleInhibit() enableIdleInhibit();
} }
} }
function setInhibitReason(reason) { function setInhibitReason(reason) {
inhibitReason = reason inhibitReason = reason;
if (idleInhibited && !nativeInhibitorAvailable) { if (idleInhibited && !nativeInhibitorAvailable) {
const wasActive = idleInhibited const wasActive = idleInhibited;
idleInhibited = false idleInhibited = false;
Qt.callLater(() => { Qt.callLater(() => {
if (wasActive) { if (wasActive) {
idleInhibited = true idleInhibited = true;
} }
}) });
} }
} }
@@ -334,24 +335,24 @@ Singleton {
command: { command: {
if (!idleInhibited || nativeInhibitorAvailable) { if (!idleInhibited || nativeInhibitorAvailable) {
return ["true"] return ["true"];
} }
console.log("SessionService: Starting systemd/elogind inhibit process") console.log("SessionService: Starting systemd/elogind inhibit process");
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"] return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"];
} }
running: idleInhibited && !nativeInhibitorAvailable running: idleInhibited && !nativeInhibitorAvailable
onRunningChanged: { onRunningChanged: {
console.log("SessionService: Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")") console.log("SessionService: Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")");
} }
onExited: function (exitCode) { onExited: function (exitCode) {
if (idleInhibited && exitCode !== 0 && !nativeInhibitorAvailable) { if (idleInhibited && exitCode !== 0 && !nativeInhibitorAvailable) {
console.warn("SessionService: Inhibitor process crashed with exit code:", exitCode) console.warn("SessionService: Inhibitor process crashed with exit code:", exitCode);
idleInhibited = false idleInhibited = false;
ToastService.showWarning("Idle inhibitor failed") ToastService.showWarning("Idle inhibitor failed");
} }
} }
} }
@@ -361,12 +362,12 @@ Singleton {
function onConnectionStateChanged() { function onConnectionStateChanged() {
if (DMSService.isConnected) { if (DMSService.isConnected) {
checkDMSCapabilities() checkDMSCapabilities();
} }
} }
function onCapabilitiesReceived() { function onCapabilitiesReceived() {
syncSleepInhibitor() syncSleepInhibitor();
} }
} }
@@ -375,7 +376,7 @@ Singleton {
enabled: DMSService.isConnected enabled: DMSService.isConnected
function onCapabilitiesChanged() { function onCapabilitiesChanged() {
checkDMSCapabilities() checkDMSCapabilities();
} }
} }
@@ -386,22 +387,22 @@ Singleton {
if (SettingsData.loginctlLockIntegration) { if (SettingsData.loginctlLockIntegration) {
if (socketPath && socketPath.length > 0 && loginctlAvailable) { if (socketPath && socketPath.length > 0 && loginctlAvailable) {
if (!stateInitialized) { if (!stateInitialized) {
stateInitialized = true stateInitialized = true;
getLoginctlState() getLoginctlState();
syncLockBeforeSuspend() syncLockBeforeSuspend();
} }
} }
} else { } else {
stateInitialized = false stateInitialized = false;
} }
syncSleepInhibitor() syncSleepInhibitor();
} }
function onLockBeforeSuspendChanged() { function onLockBeforeSuspendChanged() {
if (SettingsData.loginctlLockIntegration) { if (SettingsData.loginctlLockIntegration) {
syncLockBeforeSuspend() syncLockBeforeSuspend();
} }
syncSleepInhibitor() syncSleepInhibitor();
} }
} }
@@ -410,109 +411,114 @@ Singleton {
enabled: SettingsData.loginctlLockIntegration enabled: SettingsData.loginctlLockIntegration
function onLoginctlStateUpdate(data) { function onLoginctlStateUpdate(data) {
updateLoginctlState(data) updateLoginctlState(data);
} }
function onLoginctlEvent(event) { function onLoginctlEvent(event) {
handleLoginctlEvent(event) handleLoginctlEvent(event);
} }
} }
function checkDMSCapabilities() { function checkDMSCapabilities() {
if (!DMSService.isConnected) { if (!DMSService.isConnected) {
return return;
} }
if (DMSService.capabilities.length === 0) { if (DMSService.capabilities.length === 0) {
return return;
} }
if (DMSService.capabilities.includes("loginctl")) { if (DMSService.capabilities.includes("loginctl")) {
loginctlAvailable = true loginctlAvailable = true;
if (SettingsData.loginctlLockIntegration && !stateInitialized) { if (SettingsData.loginctlLockIntegration && !stateInitialized) {
stateInitialized = true stateInitialized = true;
getLoginctlState() getLoginctlState();
syncLockBeforeSuspend() syncLockBeforeSuspend();
} }
} else { } else {
loginctlAvailable = false loginctlAvailable = false;
console.log("SessionService: loginctl capability not available in DMS") console.log("SessionService: loginctl capability not available in DMS");
} }
} }
function getLoginctlState() { function getLoginctlState() {
if (!loginctlAvailable) return if (!loginctlAvailable)
return;
DMSService.sendRequest("loginctl.getState", null, response => { DMSService.sendRequest("loginctl.getState", null, response => {
if (response.result) { if (response.result) {
updateLoginctlState(response.result) updateLoginctlState(response.result);
} }
}) });
} }
function syncLockBeforeSuspend() { function syncLockBeforeSuspend() {
if (!loginctlAvailable) return if (!loginctlAvailable)
return;
DMSService.sendRequest("loginctl.setLockBeforeSuspend", { DMSService.sendRequest("loginctl.setLockBeforeSuspend", {
enabled: SettingsData.lockBeforeSuspend enabled: SettingsData.lockBeforeSuspend
}, response => { }, response => {
if (response.error) { if (response.error) {
console.warn("SessionService: Failed to sync lock before suspend:", response.error) console.warn("SessionService: Failed to sync lock before suspend:", response.error);
} else { } else {
console.log("SessionService: Synced lock before suspend:", SettingsData.lockBeforeSuspend) console.log("SessionService: Synced lock before suspend:", SettingsData.lockBeforeSuspend);
} }
}) });
} }
function syncSleepInhibitor() { function syncSleepInhibitor() {
if (!loginctlAvailable) return if (!loginctlAvailable)
return;
if (!DMSService.apiVersion || DMSService.apiVersion < 4) return if (!DMSService.apiVersion || DMSService.apiVersion < 4)
return;
DMSService.sendRequest("loginctl.setSleepInhibitorEnabled", { DMSService.sendRequest("loginctl.setSleepInhibitorEnabled", {
enabled: SettingsData.loginctlLockIntegration && SettingsData.lockBeforeSuspend enabled: SettingsData.loginctlLockIntegration && SettingsData.lockBeforeSuspend
}, response => { }, response => {
if (response.error) { if (response.error) {
console.warn("SessionService: Failed to sync sleep inhibitor:", response.error) console.warn("SessionService: Failed to sync sleep inhibitor:", response.error);
} else { } else {
console.log("SessionService: Synced sleep inhibitor:", SettingsData.loginctlLockIntegration) console.log("SessionService: Synced sleep inhibitor:", SettingsData.loginctlLockIntegration);
} }
}) });
} }
function updateLoginctlState(state) { function updateLoginctlState(state) {
const wasLocked = locked const wasLocked = locked;
const wasSleeping = preparingForSleep;
sessionId = state.sessionId || "" sessionId = state.sessionId || "";
sessionPath = state.sessionPath || "" sessionPath = state.sessionPath || "";
locked = state.locked || false locked = state.locked || false;
active = state.active || false active = state.active || false;
idleHint = state.idleHint || false idleHint = state.idleHint || false;
lockedHint = state.lockedHint || false lockedHint = state.lockedHint || false;
sessionType = state.sessionType || "" preparingForSleep = state.preparingForSleep || false;
userName = state.userName || "" sessionType = state.sessionType || "";
seat = state.seat || "" userName = state.userName || "";
display = state.display || "" seat = state.seat || "";
display = state.display || "";
if (locked && !wasLocked) { if (locked && !wasLocked) {
sessionLocked() sessionLocked();
} else if (!locked && wasLocked) { } else if (!locked && wasLocked) {
sessionUnlocked() sessionUnlocked();
} }
loginctlStateChanged() if (wasSleeping && !preparingForSleep) {
sessionResumed();
}
loginctlStateChanged();
} }
function handleLoginctlEvent(event) { function handleLoginctlEvent(event) {
if (event.event === "Lock") { if (event.event === "Lock") {
locked = true locked = true;
lockedHint = true lockedHint = true;
sessionLocked() sessionLocked();
} else if (event.event === "Unlock") { } else if (event.event === "Unlock") {
locked = false locked = false;
lockedHint = false lockedHint = false;
sessionUnlocked() sessionUnlocked();
} }
} }
} }

View File

@@ -1,5 +1,4 @@
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
import QtQuick import QtQuick
@@ -12,77 +11,25 @@ Singleton {
property string username: "" property string username: ""
property string fullName: "" property string fullName: ""
property string profilePicture: "" property string profilePicture: ""
property string uptime: ""
property string shortUptime: ""
property string hostname: "" property string hostname: ""
property bool profileAvailable: false property bool profileAvailable: false
function getUserInfo() { function getUserInfo() {
Proc.runCommand("userInfo", ["bash", "-c", "echo \"$USER|$(getent passwd $USER | cut -d: -f5 | cut -d, -f1)|$(hostname)\""], (output, exitCode) => { Proc.runCommand("userInfo", ["sh", "-c", "echo \"$USER|$(getent passwd $USER | cut -d: -f5 | cut -d, -f1)|$(hostname)\""], (output, exitCode) => {
if (exitCode !== 0) { if (exitCode !== 0) {
root.username = "User" root.username = "User";
root.fullName = "User" root.fullName = "User";
root.hostname = "System" root.hostname = "System";
return return;
} }
const parts = output.trim().split("|") const parts = output.trim().split("|");
if (parts.length >= 3) { if (parts.length >= 3) {
root.username = parts[0] || "" root.username = parts[0] || "";
root.fullName = parts[1] || parts[0] || "" root.fullName = parts[1] || parts[0] || "";
root.hostname = parts[2] || "" root.hostname = parts[2] || "";
} }
}, 0) }, 0);
} }
function getUptime() { Component.onCompleted: getUserInfo()
Proc.runCommand("uptime", ["cat", "/proc/uptime"], (output, exitCode) => {
if (exitCode !== 0) {
root.uptime = "Unknown"
return
}
const seconds = parseInt(output.split(" ")[0])
const days = Math.floor(seconds / 86400)
const hours = Math.floor((seconds % 86400) / 3600)
const minutes = Math.floor((seconds % 3600) / 60)
const parts = []
if (days > 0) {
parts.push(`${days} day${days === 1 ? "" : "s"}`)
}
if (hours > 0) {
parts.push(`${hours} hour${hours === 1 ? "" : "s"}`)
}
if (minutes > 0) {
parts.push(`${minutes} minute${minutes === 1 ? "" : "s"}`)
}
if (parts.length > 0) {
root.uptime = `up ${parts.join(", ")}`
} else {
root.uptime = `up ${seconds} seconds`
}
let shortUptime = "up"
if (days > 0) {
shortUptime += ` ${days}d`
}
if (hours > 0) {
shortUptime += ` ${hours}h`
}
if (minutes > 0) {
shortUptime += ` ${minutes}m`
}
root.shortUptime = shortUptime
}, 0)
}
function refreshUserInfo() {
getUserInfo()
getUptime()
}
Component.onCompleted: {
getUserInfo()
getUptime()
}
} }

View File

@@ -27,39 +27,41 @@ PanelWindow {
signal osdHidden signal osdHidden
function show() { function show() {
OSDManager.showOSD(root) if (SessionData.suppressOSD)
closeTimer.stop() return;
shouldBeVisible = true OSDManager.showOSD(root);
visible = true closeTimer.stop();
hideTimer.restart() shouldBeVisible = true;
osdShown() visible = true;
hideTimer.restart();
osdShown();
} }
function hide() { function hide() {
shouldBeVisible = false shouldBeVisible = false;
closeTimer.restart() closeTimer.restart();
} }
function resetHideTimer() { function resetHideTimer() {
if (shouldBeVisible) { if (shouldBeVisible) {
hideTimer.restart() hideTimer.restart();
} }
} }
function updateHoverState() { function updateHoverState() {
let isHovered = (enableMouseInteraction && mouseArea.containsMouse) || osdContainer.childHovered let isHovered = (enableMouseInteraction && mouseArea.containsMouse) || osdContainer.childHovered;
if (enableMouseInteraction) { if (enableMouseInteraction) {
if (isHovered) { if (isHovered) {
hideTimer.stop() hideTimer.stop();
} else if (shouldBeVisible) { } else if (shouldBeVisible) {
hideTimer.restart() hideTimer.restart();
} }
} }
} }
function setChildHovered(hovered) { function setChildHovered(hovered) {
osdContainer.childHovered = hovered osdContainer.childHovered = hovered;
updateHoverState() updateHoverState();
} }
screen: modelData screen: modelData
@@ -78,88 +80,92 @@ PanelWindow {
readonly property bool isVerticalLayout: SettingsData.osdPosition === SettingsData.Position.LeftCenter || SettingsData.osdPosition === SettingsData.Position.RightCenter readonly property bool isVerticalLayout: SettingsData.osdPosition === SettingsData.Position.LeftCenter || SettingsData.osdPosition === SettingsData.Position.RightCenter
readonly property real barThickness: { readonly property real barThickness: {
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default") const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
if (!defaultBar || !(defaultBar.visible ?? true)) return 0 if (!defaultBar || !(defaultBar.visible ?? true))
const innerPadding = defaultBar.innerPadding ?? 4 return 0;
const widgetThickness = Math.max(20, 26 + innerPadding * 0.6) const innerPadding = defaultBar.innerPadding ?? 4;
return Math.max(widgetThickness + innerPadding + 4, Theme.barHeight - 4 - (8 - innerPadding)) const widgetThickness = Math.max(20, 26 + innerPadding * 0.6);
return Math.max(widgetThickness + innerPadding + 4, Theme.barHeight - 4 - (8 - innerPadding));
} }
readonly property real barOffset: { readonly property real barOffset: {
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default") const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
if (!defaultBar || !(defaultBar.visible ?? true)) return 0 if (!defaultBar || !(defaultBar.visible ?? true))
const spacing = defaultBar.spacing ?? 4 return 0;
const bottomGap = defaultBar.bottomGap ?? 0 const spacing = defaultBar.spacing ?? 4;
return barThickness + spacing + bottomGap const bottomGap = defaultBar.bottomGap ?? 0;
return barThickness + spacing + bottomGap;
} }
readonly property real dockThickness: { readonly property real dockThickness: {
if (!SettingsData.showDock) return 0 if (!SettingsData.showDock)
return SettingsData.dockIconSize + SettingsData.dockSpacing * 2 + 10 return 0;
return SettingsData.dockIconSize + SettingsData.dockSpacing * 2 + 10;
} }
readonly property real dockOffset: { readonly property real dockOffset: {
if (!SettingsData.showDock || SettingsData.dockAutoHide) return 0 if (!SettingsData.showDock || SettingsData.dockAutoHide)
return dockThickness + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin return 0;
return dockThickness + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin;
} }
readonly property real alignedX: { readonly property real alignedX: {
const margin = Theme.spacingM const margin = Theme.spacingM;
const centerX = (screenWidth - alignedWidth) / 2 const centerX = (screenWidth - alignedWidth) / 2;
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default") const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
const barPos = defaultBar?.position ?? SettingsData.Position.Top const barPos = defaultBar?.position ?? SettingsData.Position.Top;
switch (SettingsData.osdPosition) { switch (SettingsData.osdPosition) {
case SettingsData.Position.Left: case SettingsData.Position.Left:
case SettingsData.Position.Bottom: case SettingsData.Position.Bottom:
const leftBarOffset = barPos === SettingsData.Position.Left ? barOffset : 0 const leftBarOffset = barPos === SettingsData.Position.Left ? barOffset : 0;
const leftDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0 const leftDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0;
return Theme.snap(margin + Math.max(leftBarOffset, leftDockOffset), dpr) return Theme.snap(margin + Math.max(leftBarOffset, leftDockOffset), dpr);
case SettingsData.Position.Top: case SettingsData.Position.Top:
case SettingsData.Position.Right: case SettingsData.Position.Right:
const rightBarOffset = barPos === SettingsData.Position.Right ? barOffset : 0 const rightBarOffset = barPos === SettingsData.Position.Right ? barOffset : 0;
const rightDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0 const rightDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0;
return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightBarOffset, rightDockOffset), dpr) return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightBarOffset, rightDockOffset), dpr);
case SettingsData.Position.LeftCenter: case SettingsData.Position.LeftCenter:
const leftCenterBarOffset = barPos === SettingsData.Position.Left ? barOffset : 0 const leftCenterBarOffset = barPos === SettingsData.Position.Left ? barOffset : 0;
const leftCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0 const leftCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Left ? dockOffset : 0;
return Theme.snap(margin + Math.max(leftCenterBarOffset, leftCenterDockOffset), dpr) return Theme.snap(margin + Math.max(leftCenterBarOffset, leftCenterDockOffset), dpr);
case SettingsData.Position.RightCenter: case SettingsData.Position.RightCenter:
const rightCenterBarOffset = barPos === SettingsData.Position.Right ? barOffset : 0 const rightCenterBarOffset = barPos === SettingsData.Position.Right ? barOffset : 0;
const rightCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0 const rightCenterDockOffset = SettingsData.dockPosition === SettingsData.Position.Right ? dockOffset : 0;
return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightCenterBarOffset, rightCenterDockOffset), dpr) return Theme.snap(screenWidth - alignedWidth - margin - Math.max(rightCenterBarOffset, rightCenterDockOffset), dpr);
case SettingsData.Position.TopCenter: case SettingsData.Position.TopCenter:
case SettingsData.Position.BottomCenter: case SettingsData.Position.BottomCenter:
default: default:
return Theme.snap(centerX, dpr) return Theme.snap(centerX, dpr);
} }
} }
readonly property real alignedY: { readonly property real alignedY: {
const margin = Theme.spacingM const margin = Theme.spacingM;
const centerY = (screenHeight - alignedHeight) / 2 const centerY = (screenHeight - alignedHeight) / 2;
const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default") const defaultBar = SettingsData.barConfigs[0] || SettingsData.getBarConfig("default");
const barPos = defaultBar?.position ?? SettingsData.Position.Top const barPos = defaultBar?.position ?? SettingsData.Position.Top;
switch (SettingsData.osdPosition) { switch (SettingsData.osdPosition) {
case SettingsData.Position.Top: case SettingsData.Position.Top:
case SettingsData.Position.Left: case SettingsData.Position.Left:
case SettingsData.Position.TopCenter: case SettingsData.Position.TopCenter:
const topBarOffset = barPos === SettingsData.Position.Top ? barOffset : 0 const topBarOffset = barPos === SettingsData.Position.Top ? barOffset : 0;
const topDockOffset = SettingsData.dockPosition === SettingsData.Position.Top ? dockOffset : 0 const topDockOffset = SettingsData.dockPosition === SettingsData.Position.Top ? dockOffset : 0;
return Theme.snap(margin + Math.max(topBarOffset, topDockOffset), dpr) return Theme.snap(margin + Math.max(topBarOffset, topDockOffset), dpr);
case SettingsData.Position.Right: case SettingsData.Position.Right:
case SettingsData.Position.Bottom: case SettingsData.Position.Bottom:
case SettingsData.Position.BottomCenter: case SettingsData.Position.BottomCenter:
const bottomBarOffset = barPos === SettingsData.Position.Bottom ? barOffset : 0 const bottomBarOffset = barPos === SettingsData.Position.Bottom ? barOffset : 0;
const bottomDockOffset = SettingsData.dockPosition === SettingsData.Position.Bottom ? dockOffset : 0 const bottomDockOffset = SettingsData.dockPosition === SettingsData.Position.Bottom ? dockOffset : 0;
return Theme.snap(screenHeight - alignedHeight - margin - Math.max(bottomBarOffset, bottomDockOffset), dpr) return Theme.snap(screenHeight - alignedHeight - margin - Math.max(bottomBarOffset, bottomDockOffset), dpr);
case SettingsData.Position.LeftCenter: case SettingsData.Position.LeftCenter:
case SettingsData.Position.RightCenter: case SettingsData.Position.RightCenter:
default: default:
return Theme.snap(centerY, dpr) return Theme.snap(centerY, dpr);
} }
} }
@@ -177,9 +183,9 @@ PanelWindow {
repeat: false repeat: false
onTriggered: { onTriggered: {
if (!enableMouseInteraction || !mouseArea.containsMouse) { if (!enableMouseInteraction || !mouseArea.containsMouse) {
hide() hide();
} else { } else {
hideTimer.restart() hideTimer.restart();
} }
} }
} }
@@ -189,8 +195,8 @@ PanelWindow {
interval: animationDuration + 50 interval: animationDuration + 50
onTriggered: { onTriggered: {
if (!shouldBeVisible) { if (!shouldBeVisible) {
visible = false visible = false;
osdHidden() osdHidden();
} }
} }
} }
@@ -239,8 +245,8 @@ PanelWindow {
shadowBlur: Math.max(0, Math.min(1, osdContainer.shadowBlurPx / bgShadowLayer.blurMax)) shadowBlur: Math.max(0, Math.min(1, osdContainer.shadowBlurPx / bgShadowLayer.blurMax))
shadowScale: 1 + (2 * osdContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height)) shadowScale: 1 + (2 * osdContainer.shadowSpreadPx) / Math.max(1, Math.min(bgShadowLayer.width, bgShadowLayer.height))
shadowColor: { shadowColor: {
const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest const baseColor = Theme.isLightMode ? Qt.rgba(0, 0, 0, 1) : Theme.surfaceContainerHighest;
return Theme.withAlpha(baseColor, osdContainer.effectiveShadowAlpha) return Theme.withAlpha(baseColor, osdContainer.effectiveShadowAlpha);
} }
} }