1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-06 05:25:41 -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:
inputs:
package:
description: 'Package to upload (dms, dms-git, or all)'
description: 'Package to upload (dms, dms-git, dms-greeter, or all)'
required: false
default: 'dms-git'
rebuild_release:
@@ -83,6 +83,12 @@ jobs:
echo "Uploading dms-git to PPA..."
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
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
PPA_NAME="$PACKAGES"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
@@ -101,10 +107,13 @@ jobs:
if [[ "$PACKAGES" == "all" ]]; then
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 danklinux**: https://launchpad.net/~avengemedia/+archive/ubuntu/danklinux/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms" ]]; then
echo "- **PPA**: https://launchpad.net/~avengemedia/+archive/ubuntu/dms/+packages" >> $GITHUB_STEP_SUMMARY
elif [[ "$PACKAGES" == "dms-git" ]]; then
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
if [[ -n "${{ steps.packages.outputs.version }}" ]]; then

View File

@@ -182,8 +182,23 @@ case "$PACKAGE_NAME" in
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 uses pre-built binary from releases, like dgop
# danksearch uses pre-built binary from releases
GIT_REPO="AvengeMedia/danksearch"
;;
dgop)

View File

@@ -224,6 +224,13 @@ if [ "$KEEP_BUILDS" = "false" ]; then
REMOVED=$((REMOVED + 1))
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
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 doNotDisturb: 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 bool perMonitorWallpaper: false

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import QtQuick
import QtQuick.Effects
import qs.Common
import qs.Services
import qs.Widgets
@@ -7,6 +6,9 @@ import qs.Widgets
Card {
id: root
Component.onCompleted: DgopService.addRef("system")
Component.onDestruction: DgopService.removeRef("system")
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
@@ -21,12 +23,12 @@ Card {
anchors.verticalCenter: parent.verticalCenter
imageSource: {
if (PortalService.profileImage === "")
return ""
return "";
if (PortalService.profileImage.startsWith("/"))
return "file://" + PortalService.profileImage
return "file://" + PortalService.profileImage;
return PortalService.profileImage
return PortalService.profileImage;
}
fallbackIcon: "person"
}
@@ -56,12 +58,16 @@ Card {
StyledText {
text: {
if (CompositorService.isNiri) return "on niri"
if (CompositorService.isHyprland) return "on Hyprland"
if (CompositorService.isNiri)
return "on niri";
if (CompositorService.isHyprland)
return "on Hyprland";
// technically they might not be on mangowc, but its what we support in the docs
if (CompositorService.isDwl) return "on MangoWC"
if (CompositorService.isSway) return "on Sway"
return ""
if (CompositorService.isDwl)
return "on MangoWC";
if (CompositorService.isSway)
return "on Sway";
return "";
}
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
@@ -82,27 +88,10 @@ Card {
}
StyledText {
id: uptimeText
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"
text: DgopService.shortUptime || "up"
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
anchors.verticalCenter: parent.verticalCenter
elide: Text.ElideRight
width: availableWidth
wrapMode: Text.NoWrap
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
@@ -12,77 +11,25 @@ Singleton {
property string username: ""
property string fullName: ""
property string profilePicture: ""
property string uptime: ""
property string shortUptime: ""
property string hostname: ""
property bool profileAvailable: false
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) {
root.username = "User"
root.fullName = "User"
root.hostname = "System"
return
root.username = "User";
root.fullName = "User";
root.hostname = "System";
return;
}
const parts = output.trim().split("|")
const parts = output.trim().split("|");
if (parts.length >= 3) {
root.username = parts[0] || ""
root.fullName = parts[1] || parts[0] || ""
root.hostname = parts[2] || ""
root.username = parts[0] || "";
root.fullName = parts[1] || parts[0] || "";
root.hostname = parts[2] || "";
}
}, 0)
}, 0);
}
function getUptime() {
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()
}
Component.onCompleted: getUserInfo()
}

View File

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