1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-30 00:12:50 -05:00

Compare commits

..

6 Commits

Author SHA1 Message Date
bbedward
1b7dcf56a8 bump VERSION 2026-01-14 08:00:15 -05:00
bbedward
502bb88e92 modals: fix wifi passowrd, polkit, and VPN import 2026-01-14 08:00:05 -05:00
bbedward
b76d0ce97d settings: fix child windows on newer quickshell-git 2026-01-13 16:58:08 -05:00
bbedward
fa66d330cf bump VERSION 2026-01-13 16:42:49 -05:00
Lucas
157eab2d07 settings: fix modal not opening on latest quickshell (#1357) 2026-01-13 16:42:38 -05:00
Lucas
f50ad2dc22 nix: escape version string (#1353) 2026-01-13 16:42:38 -05:00
82 changed files with 4317 additions and 5202 deletions

View File

@@ -4,14 +4,13 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
package: package:
description: "Package to update" description: "Package to update (dms, dms-git, or all)"
required: true required: false
type: choice default: "all"
options: tag_version:
- dms description: "Specific tag version for dms stable (e.g., v1.0.2). Leave empty to auto-detect latest release."
- dms-git required: false
- all default: ""
default: "dms"
rebuild_release: rebuild_release:
description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)" description: "Release number for rebuilds (e.g., 2, 3, 4 to increment spec Release)"
required: false required: false
@@ -57,9 +56,8 @@ jobs:
} }
# Helper function to check dms stable tag # Helper function to check dms stable tag
# Sets LATEST_TAG variable in parent scope if update needed
check_dms_stable() { check_dms_stable() {
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "") local LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "v\?\([^"]*\)".*/\1/' || echo "")
local OBS_SPEC=$(curl -s -u "$OBS_USERNAME:$OBS_PASSWORD" "https://api.opensuse.org/source/home:AvengeMedia:dms/dms/dms.spec" 2>/dev/null || echo "") local OBS_SPEC=$(curl -s -u "$OBS_USERNAME:$OBS_PASSWORD" "https://api.opensuse.org/source/home:AvengeMedia:dms/dms/dms.spec" 2>/dev/null || echo "")
local OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs || echo "") local OBS_VERSION=$(echo "$OBS_SPEC" | grep "^Version:" | awk '{print $2}' | xargs || echo "")
@@ -75,8 +73,8 @@ jobs:
# Main logic # Main logic
REBUILD="${{ github.event.inputs.rebuild_release }}" REBUILD="${{ github.event.inputs.rebuild_release }}"
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
# Tag selected or pushed - always update stable package # Tag push - always update stable package
echo "packages=dms" >> $GITHUB_OUTPUT echo "packages=dms" >> $GITHUB_OUTPUT
VERSION="${GITHUB_REF#refs/tags/}" VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
@@ -106,12 +104,7 @@ jobs:
# Check each package and build list of those needing updates # Check each package and build list of those needing updates
PACKAGES_TO_UPDATE=() PACKAGES_TO_UPDATE=()
check_dms_git && PACKAGES_TO_UPDATE+=("dms-git") check_dms_git && PACKAGES_TO_UPDATE+=("dms-git")
if check_dms_stable; then check_dms_stable && PACKAGES_TO_UPDATE+=("dms")
PACKAGES_TO_UPDATE+=("dms")
if [[ -n "$LATEST_TAG" ]]; then
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
fi
fi
if [[ ${#PACKAGES_TO_UPDATE[@]} -gt 0 ]]; then if [[ ${#PACKAGES_TO_UPDATE[@]} -gt 0 ]]; then
echo "packages=${PACKAGES_TO_UPDATE[*]}" >> $GITHUB_OUTPUT echo "packages=${PACKAGES_TO_UPDATE[*]}" >> $GITHUB_OUTPUT
@@ -136,9 +129,6 @@ jobs:
if check_dms_stable; then if check_dms_stable; then
echo "packages=$PKG" >> $GITHUB_OUTPUT echo "packages=$PKG" >> $GITHUB_OUTPUT
echo "has_updates=true" >> $GITHUB_OUTPUT echo "has_updates=true" >> $GITHUB_OUTPUT
if [[ -n "$LATEST_TAG" ]]; then
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
fi
else else
echo "packages=" >> $GITHUB_OUTPUT echo "packages=" >> $GITHUB_OUTPUT
echo "has_updates=false" >> $GITHUB_OUTPUT echo "has_updates=false" >> $GITHUB_OUTPUT
@@ -171,19 +161,12 @@ jobs:
- name: Determine packages to update - name: Determine packages to update
id: packages id: packages
run: | run: |
# Check if GITHUB_REF points to a tag (works for both push events and workflow_dispatch with tag selected) if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" =~ ^refs/tags/ ]]; then
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then # Tag push event - use the pushed tag
# Tag selected or pushed - use the tag from GITHUB_REF
echo "packages=dms" >> $GITHUB_OUTPUT echo "packages=dms" >> $GITHUB_OUTPUT
VERSION="${GITHUB_REF#refs/tags/}" VERSION="${GITHUB_REF#refs/tags/}"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Using tag from GITHUB_REF: $VERSION" echo "Triggered by tag: $VERSION"
# Check if check-updates already determined a version (from auto-detection)
elif [[ -n "${{ needs.check-updates.outputs.version }}" ]]; then
# Use version from check-updates job
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "version=${{ needs.check-updates.outputs.version }}" >> $GITHUB_OUTPUT
echo "Using version from check-updates: ${{ needs.check-updates.outputs.version }}"
elif [[ "${{ github.event_name }}" == "schedule" ]]; then elif [[ "${{ github.event_name }}" == "schedule" ]]; then
# Scheduled run - dms-git only # Scheduled run - dms-git only
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
@@ -193,28 +176,22 @@ jobs:
# Determine version for dms stable # Determine version for dms stable
if [[ "${{ github.event.inputs.package }}" == "dms" ]]; then if [[ "${{ github.event.inputs.package }}" == "dms" ]]; then
# Use github.ref if tag selected, otherwise auto-detect latest # For explicit dms selection, require tag_version
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
VERSION="${GITHUB_REF#refs/tags/}" VERSION="${{ github.event.inputs.tag_version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Using tag from GITHUB_REF: $VERSION" echo "Using specified tag: $VERSION"
else else
# Auto-detect latest release for dms echo "ERROR: tag_version is required when package=dms"
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "") echo "Please specify a tag version (e.g., v1.0.2) or use package=all for auto-detection"
if [[ -n "$LATEST_TAG" ]]; then exit 1
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "Auto-detected latest release: $LATEST_TAG"
else
echo "ERROR: Could not auto-detect latest release"
exit 1
fi
fi fi
elif [[ "${{ github.event.inputs.package }}" == "all" ]]; then elif [[ "${{ github.event.inputs.package }}" == "all" ]]; then
# Use github.ref if tag selected, otherwise auto-detect latest # For "all", auto-detect if tag_version not specified
if [[ "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ -n "${{ github.event.inputs.tag_version }}" ]]; then
VERSION="${GITHUB_REF#refs/tags/}" VERSION="${{ github.event.inputs.tag_version }}"
echo "version=$VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Using tag from GITHUB_REF: $VERSION" echo "Using specified tag: $VERSION"
else else
# Auto-detect latest release for "all" # Auto-detect latest release for "all"
LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "") LATEST_TAG=$(curl -s https://api.github.com/repos/AvengeMedia/DankMaterialShell/releases/latest | grep '"tag_name"' | sed 's/.*"tag_name": "\([^"]*\)".*/\1/' || echo "")
@@ -229,7 +206,7 @@ jobs:
fi fi
# Use filtered packages from check-updates when package="all" and no rebuild/tag specified # Use filtered packages from check-updates when package="all" and no rebuild/tag specified
if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ ! "${{ github.ref }}" =~ ^refs/tags/ ]]; then if [[ "${{ github.event.inputs.package }}" == "all" ]] && [[ -z "${{ github.event.inputs.rebuild_release }}" ]] && [[ -z "${{ github.event.inputs.tag_version }}" ]]; then
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})" echo "Manual trigger: all (filtered to: ${{ needs.check-updates.outputs.packages }})"
else else
@@ -238,9 +215,6 @@ jobs:
fi fi
else else
echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT echo "packages=${{ needs.check-updates.outputs.packages }}" >> $GITHUB_OUTPUT
if [[ -n "${{ needs.check-updates.outputs.version }}" ]]; then
echo "version=${{ needs.check-updates.outputs.version }}" >> $GITHUB_OUTPUT
fi
fi fi
- name: Update dms-git spec version - name: Update dms-git spec version

View File

@@ -87,8 +87,6 @@ var (
swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`) swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`)
riverVersionRegex = regexp.MustCompile(`river (\d+\.\d+)`) riverVersionRegex = regexp.MustCompile(`river (\d+\.\d+)`)
wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`) wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`)
labwcVersionRegex = regexp.MustCompile(`labwc (\d+\.\d+\.\d+)`)
mangowcVersionRegex = regexp.MustCompile(`mango (\d+\.\d+\.\d+)`)
) )
var doctorCmd = &cobra.Command{ var doctorCmd = &cobra.Command{
@@ -450,13 +448,11 @@ func checkWindowManagers() []checkResult {
versionRegex *regexp.Regexp versionRegex *regexp.Regexp
commands []string commands []string
}{ }{
{"Hyprland", "Hyprland", "--version", hyprlandVersionRegex, []string{"hyprland", "Hyprland"}}, {"Hyprland", "hyprctl", "version", hyprlandVersionRegex, []string{"hyprland", "Hyprland"}},
{"niri", "niri", "--version", niriVersionRegex, []string{"niri"}}, {"niri", "niri", "--version", niriVersionRegex, []string{"niri"}},
{"Sway", "sway", "--version", swayVersionRegex, []string{"sway"}}, {"Sway", "sway", "--version", swayVersionRegex, []string{"sway"}},
{"River", "river", "-version", riverVersionRegex, []string{"river"}}, {"River", "river", "-version", riverVersionRegex, []string{"river"}},
{"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}}, {"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}},
{"labwc", "labwc", "--version", labwcVersionRegex, []string{"labwc"}},
{"mangowc", "mango", "-v", mangowcVersionRegex, []string{"mango"}},
} }
var results []checkResult var results []checkResult
@@ -481,7 +477,7 @@ func checkWindowManagers() []checkResult {
results = append(results, checkResult{ results = append(results, checkResult{
catCompositor, c.name, statusOK, catCompositor, c.name, statusOK,
getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details, getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details,
doctorDocsURL + "#compositor-checks", doctorDocsURL + "#compositor",
}) })
} }
@@ -490,7 +486,7 @@ func checkWindowManagers() []checkResult {
catCompositor, "Compositor", statusError, catCompositor, "Compositor", statusError,
"No supported Wayland compositor found", "No supported Wayland compositor found",
"Install Hyprland, niri, Sway, River, or Wayfire", "Install Hyprland, niri, Sway, River, or Wayfire",
doctorDocsURL + "#compositor-checks", doctorDocsURL + "#compositor",
}) })
} }
@@ -502,8 +498,8 @@ func checkWindowManagers() []checkResult {
} }
func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string { func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string {
output, err := exec.Command(cmd, arg).CombinedOutput() output, err := exec.Command(cmd, arg).Output()
if err != nil && len(output) == 0 { if err != nil {
return "installed" return "installed"
} }
@@ -638,14 +634,19 @@ func checkI2CAvailability() checkResult {
return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"} return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control", doctorDocsURL + "#optional-features"}
} }
func detectNetworkBackend(stackResult *network.DetectResult) string { func detectNetworkBackend() string {
switch stackResult.Backend { result, err := network.DetectNetworkStack()
if err != nil {
return ""
}
switch result.Backend {
case network.BackendNetworkManager: case network.BackendNetworkManager:
return "NetworkManager" return "NetworkManager"
case network.BackendIwd: case network.BackendIwd:
return "iwd" return "iwd"
case network.BackendNetworkd: case network.BackendNetworkd:
if stackResult.HasIwd { if result.HasIwd {
return "iwd + systemd-networkd" return "iwd + systemd-networkd"
} }
return "systemd-networkd" return "systemd-networkd"
@@ -656,73 +657,75 @@ func detectNetworkBackend(stackResult *network.DetectResult) string {
} }
} }
func getOptionalDBusStatus(busName string) (status, string) {
if utils.IsDBusServiceAvailable(busName) {
return statusOK, "Available"
} else {
return statusWarn, "Not available"
}
}
func checkOptionalDependencies() []checkResult { func checkOptionalDependencies() []checkResult {
var results []checkResult var results []checkResult
optionalFeaturesURL := doctorDocsURL + "#optional-features" if utils.IsServiceActive("accounts-daemon", false) {
results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusOK, "Running", "User accounts", doctorDocsURL + "#optional-features"})
} else {
results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusWarn, "Not running", "User accounts", doctorDocsURL + "#optional-features"})
}
accountsStatus, accountsMsg := getOptionalDBusStatus("org.freedesktop.Accounts") if utils.IsServiceActive("power-profiles-daemon", false) {
results = append(results, checkResult{catOptionalFeatures, "accountsservice", accountsStatus, accountsMsg, "User accounts", optionalFeaturesURL}) results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusOK, "Running", "Power profile management", doctorDocsURL + "#optional-features"})
} else {
ppdStatus, ppdMsg := getOptionalDBusStatus("org.freedesktop.UPower.PowerProfiles") results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusInfo, "Not running", "Power profile management", doctorDocsURL + "#optional-features"})
results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", ppdStatus, ppdMsg, "Power profile management", optionalFeaturesURL}) }
logindStatus, logindMsg := getOptionalDBusStatus("org.freedesktop.login1")
results = append(results, checkResult{catOptionalFeatures, "logind", logindStatus, logindMsg, "Session management", optionalFeaturesURL})
results = append(results, checkI2CAvailability()) results = append(results, checkI2CAvailability())
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"} terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 { if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL}) results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", doctorDocsURL + "#optional-features"})
} else { } else {
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", optionalFeaturesURL}) results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", doctorDocsURL + "#optional-features"})
} }
networkResult, err := network.DetectNetworkStack()
networkStatus, networkMessage, networkDetails := statusOK, "Not available", "Network management"
if err == nil && networkResult.Backend != network.BackendNone {
networkMessage = detectNetworkBackend(networkResult)
if doctorVerbose {
networkDetails = networkResult.ChosenReason
}
} else {
networkStatus = statusInfo
}
results = append(results, checkResult{catOptionalFeatures, "Network", networkStatus, networkMessage, networkDetails, optionalFeaturesURL})
deps := []struct { deps := []struct {
name, cmd, desc string name, cmd, altCmd, desc string
important bool important bool
}{ }{
{"matugen", "matugen", "Dynamic theming", true}, {"matugen", "matugen", "", "Dynamic theming", true},
{"dgop", "dgop", "System monitoring", true}, {"dgop", "dgop", "", "System monitoring", true},
{"cava", "cava", "Audio visualizer", true}, {"cava", "cava", "", "Audio visualizer", true},
{"khal", "khal", "Calendar events", false}, {"khal", "khal", "", "Calendar events", false},
{"danksearch", "dsearch", "File search", false}, {"Network", "nmcli", "iwctl", "Network management", false},
{"fprintd", "fprintd-list", "Fingerprint auth", false}, {"danksearch", "dsearch", "", "File search", false},
{"loginctl", "loginctl", "", "Session management", false},
{"fprintd", "fprintd-list", "", "Fingerprint auth", false},
} }
for _, d := range deps { for _, d := range deps {
found := utils.CommandExists(d.cmd) found, foundCmd := utils.CommandExists(d.cmd), d.cmd
if !found && d.altCmd != "" && utils.CommandExists(d.altCmd) {
found, foundCmd = true, d.altCmd
}
switch { switch {
case found: case found:
results = append(results, checkResult{catOptionalFeatures, d.name, statusOK, "Installed", d.desc, optionalFeaturesURL}) message := "Installed"
details := d.desc
if d.name == "Network" {
result, err := network.DetectNetworkStack()
if err == nil && result.Backend != network.BackendNone {
message = detectNetworkBackend() + " (active)"
if doctorVerbose {
details = result.ChosenReason
}
} else {
switch foundCmd {
case "nmcli":
message = "NetworkManager (installed)"
case "iwctl":
message = "iwd (installed)"
}
}
}
results = append(results, checkResult{catOptionalFeatures, d.name, statusOK, message, details, doctorDocsURL + "#optional-features"})
case d.important: case d.important:
results = append(results, checkResult{catOptionalFeatures, d.name, statusWarn, "Missing", d.desc, optionalFeaturesURL}) results = append(results, checkResult{catOptionalFeatures, d.name, statusWarn, "Missing", d.desc, doctorDocsURL + "#optional-features"})
default: default:
results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc, optionalFeaturesURL}) results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc, doctorDocsURL + "#optional-features"})
} }
} }
@@ -890,10 +893,6 @@ func printResultLine(r checkResult, styles tui.Styles) {
if doctorVerbose && r.details != "" { if doctorVerbose && r.details != "" {
fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details)) fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details))
} }
if (r.status == statusError || r.status == statusWarn) && r.url != "" {
fmt.Printf(" %s\n", styles.Subtle.Render("→ "+r.url))
}
} }
func printSummary(results []checkResult, qsMissingFeatures bool) { func printSummary(results []checkResult, qsMissingFeatures bool) {

View File

@@ -199,6 +199,31 @@ func labToHex(L, a, b float64) string {
return fmt.Sprintf("#%02x%02x%02x", r, g, b2) return fmt.Sprintf("#%02x%02x%02x", r, g, b2)
} }
// Adjust brightness while keeping the same hue
func retoneToL(hex string, Ltarget float64) string {
rgb := HexToRGB(hex)
col := colorful.Color{R: rgb.R, G: rgb.G, B: rgb.B}
L, a, b := col.Lab()
L100 := L * 100.0
scale := 1.0
if L100 != 0 {
scale = Ltarget / L100
}
a2, b2 := a*scale, b*scale
// Don't let it get too saturated
maxChroma := 0.4
if math.Hypot(a2, b2) > maxChroma {
k := maxChroma / math.Hypot(a2, b2)
a2 *= k
b2 *= k
}
return labToHex(Ltarget, a2, b2)
}
func DeltaPhiStar(hexFg, hexBg string, negativePolarity bool) float64 { func DeltaPhiStar(hexFg, hexBg string, negativePolarity bool) float64 {
Lf := getLstar(hexFg) Lf := getLstar(hexFg)
Lb := getLstar(hexBg) Lb := getLstar(hexBg)
@@ -331,59 +356,6 @@ func EnsureContrastDPSLstar(hexColor, hexBg string, minLc float64, isLightMode b
return hexColor return hexColor
} }
// Bidirectional contrast - tries both lighter and darker, picks closest to original
func EnsureContrastDPSBidirectional(hexColor, hexBg string, minLc float64, isLightMode bool) string {
current := DeltaPhiStarContrast(hexColor, hexBg, isLightMode)
if current >= minLc {
return hexColor
}
fg := HexToRGB(hexColor)
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
origL, af, bf := cf.Lab()
var darkerResult, lighterResult string
darkerL, lighterL := origL, origL
darkerFound, lighterFound := false, false
step := 0.5
for i := range 120 {
if !darkerFound {
darkerL = math.Max(0, origL-float64(i)*step)
cand := labToHex(darkerL, af, bf)
if DeltaPhiStarContrast(cand, hexBg, isLightMode) >= minLc {
darkerResult = cand
darkerFound = true
}
}
if !lighterFound {
lighterL = math.Min(100, origL+float64(i)*step)
cand := labToHex(lighterL, af, bf)
if DeltaPhiStarContrast(cand, hexBg, isLightMode) >= minLc {
lighterResult = cand
lighterFound = true
}
}
if darkerFound && lighterFound {
break
}
}
if darkerFound && lighterFound {
if math.Abs(darkerL-origL) <= math.Abs(lighterL-origL) {
return darkerResult
}
return lighterResult
}
if darkerFound {
return darkerResult
}
if lighterFound {
return lighterResult
}
return hexColor
}
type PaletteOptions struct { type PaletteOptions struct {
IsLight bool IsLight bool
Background string Background string
@@ -397,29 +369,6 @@ func ensureContrastAuto(hexColor, hexBg string, target float64, opts PaletteOpti
return EnsureContrast(hexColor, hexBg, target, opts.IsLight) return EnsureContrast(hexColor, hexBg, target, opts.IsLight)
} }
func ensureContrastBidirectional(hexColor, hexBg string, target float64, opts PaletteOptions) string {
if opts.UseDPS {
return EnsureContrastDPSBidirectional(hexColor, hexBg, target, opts.IsLight)
}
return EnsureContrast(hexColor, hexBg, target, opts.IsLight)
}
func blendHue(base, target, factor float64) float64 {
diff := target - base
if diff > 0.5 {
diff -= 1.0
} else if diff < -0.5 {
diff += 1.0
}
result := base + diff*factor
if result < 0 {
result += 1.0
} else if result >= 1.0 {
result -= 1.0
}
return result
}
func DeriveContainer(primary string, isLight bool) string { func DeriveContainer(primary string, isLight bool) string {
rgb := HexToRGB(primary) rgb := HexToRGB(primary)
hsv := RGBToHSV(rgb) hsv := RGBToHSV(rgb)
@@ -440,9 +389,6 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
rgb := HexToRGB(baseColor) rgb := HexToRGB(baseColor)
hsv := RGBToHSV(rgb) hsv := RGBToHSV(rgb)
pr := HexToRGB(primaryColor)
ph := RGBToHSV(pr)
var palette Palette var palette Palette
var normalTextTarget, secondaryTarget float64 var normalTextTarget, secondaryTarget float64
@@ -464,136 +410,115 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
} }
palette.Color0 = NewColorInfo(bgColor) palette.Color0 = NewColorInfo(bgColor)
baseSat := math.Max(ph.S, 0.5) hueShift := (hsv.H - 0.6) * 0.12
baseVal := math.Max(ph.V, 0.5) satBoost := 1.15
redH := blendHue(0.0, ph.H, 0.12) redH := math.Mod(0.0+hueShift+1.0, 1.0)
greenH := blendHue(0.33, ph.H, 0.10) var redColor string
yellowH := blendHue(0.14, ph.H, 0.04) if opts.IsLight {
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.80*satBoost, 1.0), V: 0.55}))
palette.Color1 = NewColorInfo(ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
} else {
redColor = RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.65*satBoost, 1.0), V: 0.80}))
palette.Color1 = NewColorInfo(ensureContrastAuto(redColor, bgColor, normalTextTarget, opts))
}
accentTarget := secondaryTarget * 0.7 greenH := math.Mod(0.33+hueShift+1.0, 1.0)
var greenColor string
if opts.IsLight {
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(math.Max(hsv.S*0.9, 0.80)*satBoost, 1.0), V: 0.45}))
palette.Color2 = NewColorInfo(ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
} else {
greenColor = RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.42*satBoost, 1.0), V: 0.84}))
palette.Color2 = NewColorInfo(ensureContrastAuto(greenColor, bgColor, normalTextTarget, opts))
}
yellowH := math.Mod(0.15+hueShift+1.0, 1.0)
var yellowColor string
if opts.IsLight {
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.75*satBoost, 1.0), V: 0.50}))
palette.Color3 = NewColorInfo(ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
} else {
yellowColor = RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.38*satBoost, 1.0), V: 0.86}))
palette.Color3 = NewColorInfo(ensureContrastAuto(yellowColor, bgColor, normalTextTarget, opts))
}
var blueColor string
if opts.IsLight {
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.9, 0.7), V: hsv.V * 1.1}))
palette.Color4 = NewColorInfo(ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
} else {
blueColor = RGBToHex(HSVToRGB(HSV{H: hsv.H, S: math.Max(hsv.S*0.8, 0.6), V: math.Min(hsv.V*1.6, 1.0)}))
palette.Color4 = NewColorInfo(ensureContrastAuto(blueColor, bgColor, normalTextTarget, opts))
}
magH := hsv.H - 0.03
if magH < 0 {
magH += 1.0
}
var magColor string
hr := HexToRGB(primaryColor)
hh := RGBToHSV(hr)
if opts.IsLight {
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Max(hh.S*0.9, 0.7), V: hh.V * 0.85}))
palette.Color5 = NewColorInfo(ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
} else {
magColor = RGBToHex(HSVToRGB(HSV{H: hh.H, S: hh.S * 0.8, V: hh.V * 0.75}))
palette.Color5 = NewColorInfo(ensureContrastAuto(magColor, bgColor, normalTextTarget, opts))
}
cyanH := hsv.H + 0.08
if cyanH > 1.0 {
cyanH -= 1.0
}
palette.Color6 = NewColorInfo(ensureContrastAuto(primaryColor, bgColor, normalTextTarget, opts))
if opts.IsLight { if opts.IsLight {
redS := math.Min(baseSat*1.2, 1.0) palette.Color7 = NewColorInfo("#1a1a1a")
redV := baseVal * 0.95 palette.Color8 = NewColorInfo("#2e2e2e")
palette.Color1 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: redH, S: redS, V: redV})), bgColor, normalTextTarget, opts))
greenS := math.Min(baseSat*1.3, 1.0)
greenV := baseVal * 0.75
palette.Color2 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: greenH, S: greenS, V: greenV})), bgColor, normalTextTarget, opts))
yellowS := math.Min(baseSat*1.5, 1.0)
yellowV := math.Min(baseVal*1.2, 1.0)
palette.Color3 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: yellowH, S: yellowS, V: yellowV})), bgColor, accentTarget, opts))
blueS := math.Min(ph.S*1.05, 1.0)
blueV := math.Min(ph.V*1.05, 1.0)
palette.Color4 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: ph.H, S: blueS, V: blueV})), bgColor, normalTextTarget, opts))
// Color5 matches primary_container exactly (light container in light mode)
container5 := DeriveContainer(primaryColor, true)
palette.Color5 = NewColorInfo(container5)
palette.Color6 = NewColorInfo(primaryColor)
gray7S := baseSat * 0.08
gray7V := baseVal * 0.28
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
gray8S := baseSat * 0.05
gray8V := baseVal * 0.85
dimTarget := secondaryTarget * 0.5
palette.Color8 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, dimTarget, opts))
brightRedS := math.Min(baseSat*1.0, 1.0)
brightRedV := math.Min(baseVal*1.2, 1.0)
palette.Color9 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: redH, S: brightRedS, V: brightRedV})), bgColor, accentTarget, opts))
brightGreenS := math.Min(baseSat*1.1, 1.0)
brightGreenV := math.Min(baseVal*1.1, 1.0)
palette.Color10 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: greenH, S: brightGreenS, V: brightGreenV})), bgColor, accentTarget, opts))
brightYellowS := math.Min(baseSat*1.4, 1.0)
brightYellowV := math.Min(baseVal*1.3, 1.0)
palette.Color11 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: yellowH, S: brightYellowS, V: brightYellowV})), bgColor, accentTarget, opts))
brightBlueS := math.Min(ph.S*1.1, 1.0)
brightBlueV := math.Min(ph.V*1.15, 1.0)
palette.Color12 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: ph.H, S: brightBlueS, V: brightBlueV})), bgColor, accentTarget, opts))
lightContainer := DeriveContainer(primaryColor, true)
palette.Color13 = NewColorInfo(lightContainer)
brightCyanS := ph.S * 0.5
brightCyanV := math.Min(ph.V*1.3, 1.0)
palette.Color14 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: ph.H, S: brightCyanS, V: brightCyanV})))
white15S := baseSat * 0.04
white15V := math.Min(baseVal*1.5, 1.0)
palette.Color15 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: white15S, V: white15V})))
} else { } else {
redS := math.Min(baseSat*1.1, 1.0) palette.Color7 = NewColorInfo("#abb2bf")
redV := math.Min(baseVal*1.15, 1.0) palette.Color8 = NewColorInfo("#5c6370")
palette.Color1 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: redH, S: redS, V: redV})), bgColor, normalTextTarget, opts)) }
greenS := math.Min(baseSat*1.0, 1.0) if opts.IsLight {
greenV := math.Min(baseVal*1.0, 1.0) brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.70*satBoost, 1.0), V: 0.65}))
palette.Color2 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: greenH, S: greenS, V: greenV})), bgColor, normalTextTarget, opts)) palette.Color9 = NewColorInfo(ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
brightGreen := RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(math.Max(hsv.S*0.85, 0.75)*satBoost, 1.0), V: 0.55}))
palette.Color10 = NewColorInfo(ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
brightYellow := RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.68*satBoost, 1.0), V: 0.60}))
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
hr := HexToRGB(primaryColor)
hh := RGBToHSV(hr)
brightBlue := RGBToHex(HSVToRGB(HSV{H: hh.H, S: math.Min(hh.S*1.1, 1.0), V: math.Min(hh.V*1.2, 1.0)}))
palette.Color12 = NewColorInfo(ensureContrastAuto(brightBlue, bgColor, secondaryTarget, opts))
brightMag := RGBToHex(HSVToRGB(HSV{H: magH, S: math.Max(hsv.S*0.9, 0.75), V: math.Min(hsv.V*1.25, 1.0)}))
palette.Color13 = NewColorInfo(ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
brightCyan := RGBToHex(HSVToRGB(HSV{H: cyanH, S: math.Max(hsv.S*0.75, 0.65), V: math.Min(hsv.V*1.25, 1.0)}))
palette.Color14 = NewColorInfo(ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
} else {
brightRed := RGBToHex(HSVToRGB(HSV{H: redH, S: math.Min(0.50*satBoost, 1.0), V: 0.88}))
palette.Color9 = NewColorInfo(ensureContrastAuto(brightRed, bgColor, secondaryTarget, opts))
brightGreen := RGBToHex(HSVToRGB(HSV{H: greenH, S: math.Min(0.35*satBoost, 1.0), V: 0.88}))
palette.Color10 = NewColorInfo(ensureContrastAuto(brightGreen, bgColor, secondaryTarget, opts))
brightYellow := RGBToHex(HSVToRGB(HSV{H: yellowH, S: math.Min(0.30*satBoost, 1.0), V: 0.91}))
palette.Color11 = NewColorInfo(ensureContrastAuto(brightYellow, bgColor, secondaryTarget, opts))
brightBlue := retoneToL(primaryColor, 85.0)
palette.Color12 = NewColorInfo(brightBlue)
brightMag := RGBToHex(HSVToRGB(HSV{H: magH, S: math.Max(hsv.S*0.7, 0.6), V: math.Min(hsv.V*1.3, 0.9)}))
palette.Color13 = NewColorInfo(ensureContrastAuto(brightMag, bgColor, secondaryTarget, opts))
brightCyanH := hsv.H + 0.02
if brightCyanH > 1.0 {
brightCyanH -= 1.0
}
brightCyan := RGBToHex(HSVToRGB(HSV{H: brightCyanH, S: math.Max(hsv.S*0.6, 0.5), V: math.Min(hsv.V*1.2, 0.85)}))
palette.Color14 = NewColorInfo(ensureContrastAuto(brightCyan, bgColor, secondaryTarget, opts))
}
yellowS := math.Min(baseSat*1.1, 1.0) if opts.IsLight {
yellowV := math.Min(baseVal*1.25, 1.0) palette.Color15 = NewColorInfo("#1a1a1a")
palette.Color3 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: yellowH, S: yellowS, V: yellowV})), bgColor, normalTextTarget, opts)) } else {
palette.Color15 = NewColorInfo("#ffffff")
// Slightly more saturated variant of primary
blueS := math.Min(ph.S*1.2, 1.0)
blueV := ph.V * 0.95
palette.Color4 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: ph.H, S: blueS, V: blueV})), bgColor, normalTextTarget, opts))
// Color5 matches primary_container exactly (dark container in dark mode)
darkContainer := DeriveContainer(primaryColor, false)
palette.Color5 = NewColorInfo(darkContainer)
palette.Color6 = NewColorInfo(primaryColor)
gray7S := baseSat * 0.12
gray7V := math.Min(baseVal*1.05, 1.0)
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
gray8S := baseSat * 0.15
gray8V := baseVal * 0.65
palette.Color8 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, secondaryTarget, opts))
brightRedS := math.Min(baseSat*0.75, 1.0)
brightRedV := math.Min(baseVal*1.35, 1.0)
palette.Color9 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: redH, S: brightRedS, V: brightRedV})), bgColor, accentTarget, opts))
brightGreenS := math.Min(baseSat*0.7, 1.0)
brightGreenV := math.Min(baseVal*1.2, 1.0)
palette.Color10 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: greenH, S: brightGreenS, V: brightGreenV})), bgColor, accentTarget, opts))
brightYellowS := math.Min(baseSat*0.7, 1.0)
brightYellowV := math.Min(baseVal*1.5, 1.0)
palette.Color11 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: yellowH, S: brightYellowS, V: brightYellowV})), bgColor, accentTarget, opts))
// Create a gradient of primary variants: Color12 -> Color13 -> Color14 -> Color15 (near white)
// Color12: Start of the lighter gradient - slightly desaturated
brightBlueS := ph.S * 0.85
brightBlueV := math.Min(ph.V*1.1, 1.0)
palette.Color12 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: ph.H, S: brightBlueS, V: brightBlueV})), bgColor, accentTarget, opts))
// Medium-high saturation pastel primary
color13S := ph.S * 0.7
color13V := math.Min(ph.V*1.3, 1.0)
palette.Color13 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: ph.H, S: color13S, V: color13V})))
// Lower saturation, lighter variant
color14S := ph.S * 0.45
color14V := math.Min(ph.V*1.4, 1.0)
palette.Color14 = NewColorInfo(RGBToHex(HSVToRGB(HSV{H: ph.H, S: color14S, V: color14V})))
white15S := baseSat * 0.05
white15V := math.Min(baseVal*1.45, 1.0)
palette.Color15 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: white15S, V: white15V})), bgColor, normalTextTarget, opts))
} }
return palette return palette

View File

@@ -366,19 +366,10 @@ func TestGeneratePalette(t *testing.T) {
t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex) t.Errorf("Light mode background = %s, expected #f8f8f8", result.Color0.Hex)
} }
// Color15 is now derived from primary, so just verify it's a valid color if tt.opts.IsLight && result.Color15.Hex != "#1a1a1a" {
// and has appropriate luminance for the mode (now theme-tinted, not pure white/black) t.Errorf("Light mode foreground = %s, expected #1a1a1a", result.Color15.Hex)
color15Lum := Luminance(result.Color15.Hex) } else if !tt.opts.IsLight && result.Color15.Hex != "#ffffff" {
if tt.opts.IsLight { t.Errorf("Dark mode foreground = %s, expected #ffffff", result.Color15.Hex)
// Light mode: Color15 should still be relatively light
if color15Lum < 0.5 {
t.Errorf("Light mode Color15 = %s (lum %.2f) is too dark", result.Color15.Hex, color15Lum)
}
} else {
// Dark mode: Color15 should be light (but may have theme tint, so lower threshold)
if color15Lum < 0.5 {
t.Errorf("Dark mode Color15 = %s (lum %.2f) is too dark", result.Color15.Hex, color15Lum)
}
} }
}) })
} }
@@ -588,10 +579,6 @@ func TestGeneratePaletteWithDPS(t *testing.T) {
bgColor := result.Color0.Hex bgColor := result.Color0.Hex
for i := 1; i < 8; i++ { for i := 1; i < 8; i++ {
// Skip Color5 (container) and Color6 (exact primary) - intentionally not contrast-adjusted
if i == 5 || i == 6 {
continue
}
lc := DeltaPhiStarContrast(colors[i].Hex, bgColor, tt.opts.IsLight) lc := DeltaPhiStarContrast(colors[i].Hex, bgColor, tt.opts.IsLight)
minLc := 30.0 minLc := 30.0
if lc < minLc && lc > 0 { if lc < minLc && lc > 0 {

View File

@@ -108,6 +108,7 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
packages := map[string]PackageMapping{ packages := map[string]PackageMapping{
// Standard zypper packages // Standard zypper packages
"git": {Name: "git", Repository: RepoTypeSystem}, "git": {Name: "git", Repository: RepoTypeSystem},
"ghostty": {Name: "ghostty", Repository: RepoTypeSystem},
"kitty": {Name: "kitty", Repository: RepoTypeSystem}, "kitty": {Name: "kitty", Repository: RepoTypeSystem},
"alacritty": {Name: "alacritty", Repository: RepoTypeSystem}, "alacritty": {Name: "alacritty", Repository: RepoTypeSystem},
"xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem}, "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem},
@@ -116,7 +117,6 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag
// DMS packages from OBS // DMS packages from OBS
"dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]), "dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]),
"quickshell": o.getQuickshellMapping(variants["quickshell"]), "quickshell": o.getQuickshellMapping(variants["quickshell"]),
"ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
"matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
"dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"},
} }

View File

@@ -37,14 +37,6 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager) {
handleSetConfig(conn, req, m) handleSetConfig(conn, req, m)
case "clipboard.store": case "clipboard.store":
handleStore(conn, req, m) handleStore(conn, req, m)
case "clipboard.pinEntry":
handlePinEntry(conn, req, m)
case "clipboard.unpinEntry":
handleUnpinEntry(conn, req, m)
case "clipboard.getPinnedEntries":
handleGetPinnedEntries(conn, req, m)
case "clipboard.getPinnedCount":
handleGetPinnedCount(conn, req, m)
default: default:
models.RespondError(conn, req.ID, "unknown method: "+req.Method) models.RespondError(conn, req.ID, "unknown method: "+req.Method)
} }
@@ -213,9 +205,6 @@ func handleSetConfig(conn net.Conn, req models.Request, m *Manager) {
if v, ok := models.Get[bool](req, "disabled"); ok { if v, ok := models.Get[bool](req, "disabled"); ok {
cfg.Disabled = v cfg.Disabled = v
} }
if v, ok := models.Get[float64](req, "maxPinned"); ok {
cfg.MaxPinned = int(v)
}
if err := m.SetConfig(cfg); err != nil { if err := m.SetConfig(cfg); err != nil {
models.RespondError(conn, req.ID, err.Error()) models.RespondError(conn, req.ID, err.Error())
@@ -241,43 +230,3 @@ func handleStore(conn net.Conn, req models.Request, m *Manager) {
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "stored"}) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "stored"})
} }
func handlePinEntry(conn net.Conn, req models.Request, m *Manager) {
id, err := params.Int(req.Params, "id")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
if err := m.PinEntry(uint64(id)); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry pinned"})
}
func handleUnpinEntry(conn net.Conn, req models.Request, m *Manager) {
id, err := params.Int(req.Params, "id")
if err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
if err := m.UnpinEntry(uint64(id)); err != nil {
models.RespondError(conn, req.ID, err.Error())
return
}
models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry unpinned"})
}
func handleGetPinnedEntries(conn net.Conn, req models.Request, m *Manager) {
pinned := m.GetPinnedEntries()
models.Respond(conn, req.ID, pinned)
}
func handleGetPinnedCount(conn net.Conn, req models.Request, m *Manager) {
count := m.GetPinnedCount()
models.Respond(conn, req.ID, map[string]int{"count": count})
}

View File

@@ -389,11 +389,7 @@ func (m *Manager) trimLengthInTx(b *bolt.Bucket) error {
} }
c := b.Cursor() c := b.Cursor()
var count int var count int
for k, v := c.Last(); k != nil; k, v = c.Prev() { for k, _ := c.Last(); k != nil; k, _ = c.Prev() {
entry, err := decodeEntry(v)
if err == nil && entry.Pinned {
continue
}
if count < m.config.MaxHistory { if count < m.config.MaxHistory {
count++ count++
continue continue
@@ -423,11 +419,6 @@ func encodeEntry(e Entry) ([]byte, error) {
buf.WriteByte(0) buf.WriteByte(0)
} }
binary.Write(buf, binary.BigEndian, e.Hash) binary.Write(buf, binary.BigEndian, e.Hash)
if e.Pinned {
buf.WriteByte(1)
} else {
buf.WriteByte(0)
}
return buf.Bytes(), nil return buf.Bytes(), nil
} }
@@ -471,12 +462,6 @@ func decodeEntry(data []byte) (Entry, error) {
binary.Read(buf, binary.BigEndian, &e.Hash) binary.Read(buf, binary.BigEndian, &e.Hash)
} }
if buf.Len() >= 1 {
var pinnedByte byte
binary.Read(buf, binary.BigEndian, &pinnedByte)
e.Pinned = pinnedByte == 1
}
return e, nil return e, nil
} }
@@ -750,54 +735,19 @@ func (m *Manager) ClearHistory() {
return return
} }
// Delete only non-pinned entries
if err := m.db.Update(func(tx *bolt.Tx) error { if err := m.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("clipboard")) if err := tx.DeleteBucket([]byte("clipboard")); err != nil {
if b == nil { return err
return nil
} }
_, err := tx.CreateBucket([]byte("clipboard"))
var toDelete [][]byte return err
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
entry, err := decodeEntry(v)
if err != nil || !entry.Pinned {
toDelete = append(toDelete, k)
}
}
for _, k := range toDelete {
if err := b.Delete(k); err != nil {
return err
}
}
return nil
}); err != nil { }); err != nil {
log.Errorf("Failed to clear clipboard history: %v", err) log.Errorf("Failed to clear clipboard history: %v", err)
return return
} }
pinnedCount := 0 if err := m.compactDB(); err != nil {
if err := m.db.View(func(tx *bolt.Tx) error { log.Errorf("Failed to compact database: %v", err)
b := tx.Bucket([]byte("clipboard"))
if b != nil {
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
entry, _ := decodeEntry(v)
if entry.Pinned {
pinnedCount++
}
}
}
return nil
}); err != nil {
log.Errorf("Failed to count pinned entries: %v", err)
}
if pinnedCount == 0 {
if err := m.compactDB(); err != nil {
log.Errorf("Failed to compact database: %v", err)
}
} }
m.updateState() m.updateState()
@@ -1010,10 +960,6 @@ func (m *Manager) clearOldEntries(days int) error {
if err != nil { if err != nil {
continue continue
} }
// Skip pinned entries
if entry.Pinned {
continue
}
if entry.Timestamp.Before(cutoff) { if entry.Timestamp.Before(cutoff) {
toDelete = append(toDelete, k) toDelete = append(toDelete, k)
} }
@@ -1304,153 +1250,3 @@ func (m *Manager) StoreData(data []byte, mimeType string) error {
return nil return nil
} }
func (m *Manager) PinEntry(id uint64) error {
if m.db == nil {
return fmt.Errorf("database not available")
}
// Check pinned count
cfg := m.getConfig()
pinnedCount := 0
if err := m.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("clipboard"))
if b == nil {
return nil
}
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
entry, err := decodeEntry(v)
if err == nil && entry.Pinned {
pinnedCount++
}
}
return nil
}); err != nil {
log.Errorf("Failed to count pinned entries: %v", err)
}
if pinnedCount >= cfg.MaxPinned {
return fmt.Errorf("maximum pinned entries reached (%d)", cfg.MaxPinned)
}
err := m.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("clipboard"))
v := b.Get(itob(id))
if v == nil {
return fmt.Errorf("entry not found")
}
entry, err := decodeEntry(v)
if err != nil {
return err
}
entry.Pinned = true
encoded, err := encodeEntry(entry)
if err != nil {
return err
}
return b.Put(itob(id), encoded)
})
if err == nil {
m.updateState()
m.notifySubscribers()
}
return err
}
func (m *Manager) UnpinEntry(id uint64) error {
if m.db == nil {
return fmt.Errorf("database not available")
}
err := m.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("clipboard"))
v := b.Get(itob(id))
if v == nil {
return fmt.Errorf("entry not found")
}
entry, err := decodeEntry(v)
if err != nil {
return err
}
entry.Pinned = false
encoded, err := encodeEntry(entry)
if err != nil {
return err
}
return b.Put(itob(id), encoded)
})
if err == nil {
m.updateState()
m.notifySubscribers()
}
return err
}
func (m *Manager) GetPinnedEntries() []Entry {
if m.db == nil {
return nil
}
var pinned []Entry
if err := m.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("clipboard"))
if b == nil {
return nil
}
c := b.Cursor()
for k, v := c.Last(); k != nil; k, v = c.Prev() {
entry, err := decodeEntry(v)
if err != nil {
continue
}
if entry.Pinned {
entry.Data = nil
pinned = append(pinned, entry)
}
}
return nil
}); err != nil {
log.Errorf("Failed to get pinned entries: %v", err)
}
return pinned
}
func (m *Manager) GetPinnedCount() int {
if m.db == nil {
return 0
}
count := 0
if err := m.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte("clipboard"))
if b == nil {
return nil
}
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
entry, err := decodeEntry(v)
if err == nil && entry.Pinned {
count++
}
}
return nil
}); err != nil {
log.Errorf("Failed to count pinned entries: %v", err)
}
return count
}

View File

@@ -19,7 +19,6 @@ type Config struct {
AutoClearDays int `json:"autoClearDays"` AutoClearDays int `json:"autoClearDays"`
ClearAtStartup bool `json:"clearAtStartup"` ClearAtStartup bool `json:"clearAtStartup"`
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
MaxPinned int `json:"maxPinned"`
} }
func DefaultConfig() Config { func DefaultConfig() Config {
@@ -28,7 +27,6 @@ func DefaultConfig() Config {
MaxEntrySize: 5 * 1024 * 1024, MaxEntrySize: 5 * 1024 * 1024,
AutoClearDays: 0, AutoClearDays: 0,
ClearAtStartup: false, ClearAtStartup: false,
MaxPinned: 25,
} }
} }
@@ -102,7 +100,6 @@ type Entry struct {
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
IsImage bool `json:"isImage"` IsImage bool `json:"isImage"`
Hash uint64 `json:"hash,omitempty"` Hash uint64 `json:"hash,omitempty"`
Pinned bool `json:"pinned"`
} }
type State struct { type State struct {

View File

@@ -1,20 +0,0 @@
package utils
import (
"github.com/godbus/dbus/v5"
)
func IsDBusServiceAvailable(busName string) bool {
conn, err := dbus.ConnectSystemBus()
if err != nil {
return false
}
defer conn.Close()
obj := conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus")
var owned bool
if err := obj.Call("org.freedesktop.DBus.NameHasOwner", 0, busName).Store(&owned); err != nil {
return false
}
return owned
}

View File

@@ -2,6 +2,7 @@ package utils
import ( import (
"os/exec" "os/exec"
"strings"
) )
type AppChecker interface { type AppChecker interface {
@@ -42,3 +43,16 @@ func AnyCommandExists(cmds ...string) bool {
} }
return false return false
} }
func IsServiceActive(name string, userService bool) bool {
if !CommandExists("systemctl") {
return false
}
args := []string{"is-active", name}
if userService {
args = []string{"--user", "is-active", name}
}
output, _ := exec.Command("systemctl", args...).Output()
return strings.EqualFold(strings.TrimSpace(string(output)), "active")
}

View File

@@ -15,6 +15,7 @@ Depends: ${misc:Depends},
quickshell-git | quickshell, quickshell-git | quickshell,
accountsservice, accountsservice,
cava, cava,
cliphist,
danksearch, danksearch,
dgop, dgop,
matugen, matugen,
@@ -28,7 +29,8 @@ Depends: ${misc:Depends},
qml6-module-qtquick-layouts, qml6-module-qtquick-layouts,
qml6-module-qtquick-templates, qml6-module-qtquick-templates,
qml6-module-qtquick-window, qml6-module-qtquick-window,
qt6ct qt6ct,
wl-clipboard
Provides: dms Provides: dms
Conflicts: dms Conflicts: dms
Replaces: dms Replaces: dms

View File

@@ -14,6 +14,7 @@ Depends: ${misc:Depends},
quickshell | quickshell-git, quickshell | quickshell-git,
accountsservice, accountsservice,
cava, cava,
cliphist,
danksearch, danksearch,
dgop, dgop,
matugen, matugen,
@@ -27,7 +28,8 @@ Depends: ${misc:Depends},
qml6-module-qtquick-layouts, qml6-module-qtquick-layouts,
qml6-module-qtquick-templates, qml6-module-qtquick-templates,
qml6-module-qtquick-window, qml6-module-qtquick-window,
qt6ct qt6ct,
wl-clipboard
Conflicts: dms-git Conflicts: dms-git
Replaces: dms-git Replaces: dms-git
Description: DankMaterialShell - Modern Wayland Desktop Shell Description: DankMaterialShell - Modern Wayland Desktop Shell

View File

@@ -33,6 +33,7 @@ Recommends: cava
Recommends: danksearch Recommends: danksearch
Recommends: matugen Recommends: matugen
Recommends: quickshell-git Recommends: quickshell-git
Recommends: wl-clipboard
# Recommended system packages # Recommended system packages
Recommends: NetworkManager Recommends: NetworkManager

View File

@@ -24,8 +24,10 @@ Requires: dms-cli = %{version}-%{release}
Requires: dgop Requires: dgop
Recommends: cava Recommends: cava
Recommends: cliphist
Recommends: danksearch Recommends: danksearch
Recommends: matugen Recommends: matugen
Recommends: wl-clipboard
Recommends: NetworkManager Recommends: NetworkManager
Recommends: qt6-qtmultimedia Recommends: qt6-qtmultimedia
Suggests: qt6ct Suggests: qt6ct

View File

@@ -11,18 +11,12 @@ let
inherit (config.services.greetd.settings.default_session) user; inherit (config.services.greetd.settings.default_session) user;
compositorPackage =
let
configured = lib.attrByPath [ "programs" cfg.compositor.name "package" ] null config;
in
if configured != null then configured else builtins.getAttr cfg.compositor.name pkgs;
cacheDir = "/var/lib/dms-greeter"; cacheDir = "/var/lib/dms-greeter";
greeterScript = pkgs.writeShellScriptBin "dms-greeter" '' greeterScript = pkgs.writeShellScriptBin "dms-greeter" ''
export PATH=$PATH:${ export PATH=$PATH:${
lib.makeBinPath [ lib.makeBinPath [
cfg.quickshell.package cfg.quickshell.package
compositorPackage config.programs.${cfg.compositor.name}.package
] ]
} }
${ ${
@@ -70,7 +64,6 @@ in
"niri" "niri"
"hyprland" "hyprland"
"sway" "sway"
"labwc"
]; ];
description = "Compositor to run greeter in"; description = "Compositor to run greeter in";
}; };

View File

@@ -73,13 +73,6 @@ in
default = hasPluginSettings; default = hasPluginSettings;
description = ''Whether to manage plugin settings. Automatically enabled if any plugins have settings configured.''; description = ''Whether to manage plugin settings. Automatically enabled if any plugins have settings configured.'';
}; };
systemd.target = lib.mkOption {
type = lib.types.str;
default = config.wayland.systemd.target;
defaultText = lib.literalExpression "config.wayland.systemd.target";
description = "Systemd target to bind to.";
};
}; };
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
@@ -91,8 +84,8 @@ in
systemd.user.services.dms = lib.mkIf cfg.systemd.enable { systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
Unit = { Unit = {
Description = "DankMaterialShell"; Description = "DankMaterialShell";
PartOf = [ cfg.systemd.target ]; PartOf = [ config.wayland.systemd.target ];
After = [ cfg.systemd.target ]; After = [ config.wayland.systemd.target ];
}; };
Service = { Service = {
@@ -100,7 +93,7 @@ in
Restart = "on-failure"; Restart = "on-failure";
}; };
Install.WantedBy = [ cfg.systemd.target ]; Install.WantedBy = [ config.wayland.systemd.target ];
}; };
xdg.stateFile."DankMaterialShell/session.json" = lib.mkIf (cfg.session != { }) { xdg.stateFile."DankMaterialShell/session.json" = lib.mkIf (cfg.session != { }) {

View File

@@ -20,19 +20,15 @@ in
imports = [ imports = [
(import ./options.nix args) (import ./options.nix args)
]; ];
options.programs.dank-material-shell.systemd.target = lib.mkOption {
type = lib.types.str;
description = "Systemd target to bind to.";
default = "graphical-session.target";
};
config = lib.mkIf cfg.enable { config = lib.mkIf cfg.enable {
systemd.user.services.dms = lib.mkIf cfg.systemd.enable { systemd.user.services.dms = lib.mkIf cfg.systemd.enable {
description = "DankMaterialShell"; description = "DankMaterialShell";
path = lib.mkForce [ ]; path = lib.mkForce [ ];
partOf = [ cfg.systemd.target ]; partOf = [ "graphical-session.target" ];
after = [ cfg.systemd.target ]; after = [ "graphical-session.target" ];
wantedBy = [ cfg.systemd.target ]; wantedBy = [ "graphical-session.target" ];
restartIfChanged = cfg.systemd.restartIfChanged; restartIfChanged = cfg.systemd.restartIfChanged;
serviceConfig = { serviceConfig = {

View File

@@ -20,9 +20,12 @@ Requires: accountsservice
Requires: dgop Requires: dgop
Recommends: cava Recommends: cava
Recommends: cliphist
Recommends: danksearch Recommends: danksearch
Recommends: matugen Recommends: matugen
Recommends: quickshell-git Recommends: quickshell-git
Recommends: wl-clipboard
Recommends: NetworkManager Recommends: NetworkManager
Recommends: qt6-qtmultimedia Recommends: qt6-qtmultimedia
Suggests: qt6ct Suggests: qt6ct

View File

@@ -23,10 +23,12 @@ Requires: dgop
# Core utilities (Highly recommended for DMS functionality) # Core utilities (Highly recommended for DMS functionality)
Recommends: cava Recommends: cava
Recommends: cliphist
Recommends: danksearch Recommends: danksearch
Recommends: matugen Recommends: matugen
Recommends: NetworkManager Recommends: NetworkManager
Recommends: qt6-qtmultimedia Recommends: qt6-qtmultimedia
Recommends: wl-clipboard
Suggests: qt6ct Suggests: qt6ct
%description %description

View File

@@ -15,6 +15,7 @@ Depends: ${misc:Depends},
quickshell-git | quickshell, quickshell-git | quickshell,
accountsservice, accountsservice,
cava, cava,
cliphist,
danksearch, danksearch,
dgop, dgop,
matugen, matugen,
@@ -28,7 +29,8 @@ Depends: ${misc:Depends},
qml6-module-qtquick-layouts, qml6-module-qtquick-layouts,
qml6-module-qtquick-templates, qml6-module-qtquick-templates,
qml6-module-qtquick-window, qml6-module-qtquick-window,
qt6ct qt6ct,
wl-clipboard
Provides: dms Provides: dms
Conflicts: dms Conflicts: dms
Replaces: dms Replaces: dms

View File

@@ -14,6 +14,7 @@ Depends: ${misc:Depends},
quickshell | quickshell-git, quickshell | quickshell-git,
accountsservice, accountsservice,
cava, cava,
cliphist,
danksearch, danksearch,
dgop, dgop,
matugen, matugen,
@@ -27,7 +28,8 @@ Depends: ${misc:Depends},
qml6-module-qtquick-layouts, qml6-module-qtquick-layouts,
qml6-module-qtquick-templates, qml6-module-qtquick-templates,
qml6-module-qtquick-window, qml6-module-qtquick-window,
qt6ct qt6ct,
wl-clipboard
Conflicts: dms-git Conflicts: dms-git
Replaces: dms-git Replaces: dms-git
Description: DankMaterialShell - Modern Wayland Desktop Shell Description: DankMaterialShell - Modern Wayland Desktop Shell

View File

@@ -1 +1 @@
Saffron Bloom Spicy Miso

View File

@@ -45,10 +45,6 @@ Singleton {
Quickshell.execDetached(["cp", strip(from), strip(to)]); Quickshell.execDetached(["cp", strip(from), strip(to)]);
} }
function isSteamApp(appId: string): bool {
return appId && /^steam_app_\d+$/.test(appId);
}
function moddedAppId(appId: string): string { function moddedAppId(appId: string): string {
const subs = SettingsData.appIdSubstitutions || []; const subs = SettingsData.appIdSubstitutions || [];
for (let i = 0; i < subs.length; i++) { for (let i = 0; i < subs.length; i++) {
@@ -64,9 +60,6 @@ Singleton {
} }
} }
} }
const steamMatch = appId.match(/^steam_app_(\d+)$/);
if (steamMatch)
return `steam_icon_${steamMatch[1]}`;
return appId; return appId;
} }

View File

@@ -82,19 +82,15 @@ Singleton {
popoutOpening(); popoutOpening();
} }
let movedFromOtherScreen = false; let justClosedSamePopout = false;
for (const otherScreenName in currentPopoutsByScreen) { for (const otherScreenName in currentPopoutsByScreen) {
if (otherScreenName === screenName) if (otherScreenName === screenName)
continue; continue;
const otherPopout = currentPopoutsByScreen[otherScreenName]; const otherPopout = currentPopoutsByScreen[otherScreenName];
if (!otherPopout) if (!otherPopout)
continue; continue;
if (otherPopout === popout) { if (otherPopout === popout) {
movedFromOtherScreen = true; justClosedSamePopout = true;
currentPopoutsByScreen[otherScreenName] = null;
currentPopoutTriggers[otherScreenName] = null;
continue;
} }
if (otherPopout.dashVisible !== undefined) { if (otherPopout.dashVisible !== undefined) {
@@ -116,7 +112,7 @@ Singleton {
} }
} }
if (currentPopout === popout && popout.shouldBeVisible && !movedFromOtherScreen) { if (currentPopout === popout && popout.shouldBeVisible) {
if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) { if (triggerId !== undefined && currentPopoutTriggers[screenName] === triggerId) {
if (popout.dashVisible !== undefined) { if (popout.dashVisible !== undefined) {
popout.dashVisible = false; popout.dashVisible = false;
@@ -143,7 +139,6 @@ Singleton {
popout.currentTabIndex = tabIndex; popout.currentTabIndex = tabIndex;
} }
currentPopoutTriggers[screenName] = triggerId; currentPopoutTriggers[screenName] = triggerId;
return;
} }
currentPopoutTriggers[screenName] = triggerId; currentPopoutTriggers[screenName] = triggerId;
@@ -158,8 +153,16 @@ Singleton {
ModalManager.closeAllModalsExcept(null); ModalManager.closeAllModalsExcept(null);
} }
if (movedFromOtherScreen) { if (justClosedSamePopout) {
popout.open(); Qt.callLater(() => {
if (popout.dashVisible !== undefined) {
popout.dashVisible = true;
} else if (popout.notificationHistoryVisible !== undefined) {
popout.notificationHistoryVisible = true;
} else {
popout.open();
}
});
} else { } else {
if (popout.dashVisible !== undefined) { if (popout.dashVisible !== undefined) {
popout.dashVisible = true; popout.dashVisible = true;

View File

@@ -145,7 +145,6 @@ Singleton {
property bool controlCenterShowMicPercent: true property bool controlCenterShowMicPercent: true
property bool controlCenterShowBatteryIcon: false property bool controlCenterShowBatteryIcon: false
property bool controlCenterShowPrinterIcon: false property bool controlCenterShowPrinterIcon: false
property bool controlCenterShowScreenSharingIcon: true
property bool showPrivacyButton: true property bool showPrivacyButton: true
property bool privacyShowMicIcon: false property bool privacyShowMicIcon: false
property bool privacyShowCameraIcon: false property bool privacyShowCameraIcon: false

View File

@@ -904,7 +904,7 @@ Singleton {
if (typeof SettingsData !== "undefined") { if (typeof SettingsData !== "undefined") {
const skipTemplates = []; const skipTemplates = [];
if (!SettingsData.runDmsMatugenTemplates) { if (!SettingsData.runDmsMatugenTemplates) {
skipTemplates.push("gtk", "nvim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode"); skipTemplates.push("gtk", "neovim", "niri", "qt5ct", "qt6ct", "firefox", "pywalfox", "zenbrowser", "vesktop", "equibop", "ghostty", "kitty", "foot", "alacritty", "wezterm", "dgop", "kcolorscheme", "vscode");
} else { } else {
if (!SettingsData.matugenTemplateGtk) if (!SettingsData.matugenTemplateGtk)
skipTemplates.push("gtk"); skipTemplates.push("gtk");

View File

@@ -28,8 +28,7 @@ Singleton {
showMicIcon: false, showMicIcon: false,
showMicPercent: true, showMicPercent: true,
showBatteryIcon: false, showBatteryIcon: false,
showPrinterIcon: false, showPrinterIcon: false
showScreenSharingIcon: true
}; };
leftModel.append(dummy); leftModel.append(dummy);
centerModel.append(dummy); centerModel.append(dummy);
@@ -85,8 +84,6 @@ Singleton {
item.showBatteryIcon = order[i].showBatteryIcon; item.showBatteryIcon = order[i].showBatteryIcon;
if (isObj && order[i].showPrinterIcon !== undefined) if (isObj && order[i].showPrinterIcon !== undefined)
item.showPrinterIcon = order[i].showPrinterIcon; item.showPrinterIcon = order[i].showPrinterIcon;
if (isObj && order[i].showScreenSharingIcon !== undefined)
item.showScreenSharingIcon = order[i].showScreenSharingIcon;
model.append(item); model.append(item);
} }

View File

@@ -70,7 +70,6 @@ var SPEC = {
controlCenterShowMicPercent: { def: false }, controlCenterShowMicPercent: { def: false },
controlCenterShowBatteryIcon: { def: false }, controlCenterShowBatteryIcon: { def: false },
controlCenterShowPrinterIcon: { def: false }, controlCenterShowPrinterIcon: { def: false },
controlCenterShowScreenSharingIcon: { def: true },
showPrivacyButton: { def: true }, showPrivacyButton: { def: true },
privacyShowMicIcon: { def: false }, privacyShowMicIcon: { def: false },

View File

@@ -132,11 +132,8 @@ Item {
case "media": case "media":
root.dankDashPopoutLoader.item.currentTabIndex = 1; root.dankDashPopoutLoader.item.currentTabIndex = 1;
break; break;
case "wallpaper":
root.dankDashPopoutLoader.item.currentTabIndex = 2;
break;
case "weather": case "weather":
root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 3 : 0; root.dankDashPopoutLoader.item.currentTabIndex = SettingsData.weatherEnabled ? 2 : 0;
break; break;
default: default:
root.dankDashPopoutLoader.item.currentTabIndex = 0; root.dankDashPopoutLoader.item.currentTabIndex = 0;

View File

@@ -25,10 +25,7 @@ Item {
width: parent.width width: parent.width
totalCount: modal.totalCount totalCount: modal.totalCount
showKeyboardHints: modal.showKeyboardHints showKeyboardHints: modal.showKeyboardHints
activeTab: modal.activeTab
pinnedCount: modal.pinnedCount
onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints onKeyboardHintsToggled: modal.showKeyboardHints = !modal.showKeyboardHints
onTabChanged: tabName => modal.activeTab = tabName
onClearAllClicked: { onClearAllClicked: {
clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () { clearConfirmDialog.show(I18n.tr("Clear All History?"), I18n.tr("This will permanently delete all clipboard history."), function () {
modal.clearAll(); modal.clearAll();
@@ -73,20 +70,18 @@ Item {
Rectangle { Rectangle {
width: parent.width width: parent.width
height: parent.height - y - keyboardHintsContainer.height - Theme.spacingL height: parent.height - ClipboardConstants.headerHeight - 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: "transparent" color: "transparent"
clip: true clip: true
// Recents Tab
DankListView { DankListView {
id: clipboardListView id: clipboardListView
anchors.fill: parent anchors.fill: parent
model: ScriptModel { model: ScriptModel {
values: clipboardContent.modal.unpinnedEntries values: clipboardContent.modal.clipboardEntries
objectProp: "id" objectProp: "id"
} }
visible: modal.activeTab === "recents"
currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0 currentIndex: clipboardContent.modal ? clipboardContent.modal.selectedIndex : 0
spacing: Theme.spacingXS spacing: Theme.spacingXS
@@ -119,11 +114,11 @@ Item {
} }
StyledText { StyledText {
text: I18n.tr("No recent clipboard entries found") text: I18n.tr("No clipboard entries found")
anchors.centerIn: parent anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
visible: clipboardContent.modal.unpinnedEntries.length === 0 visible: clipboardContent.modal.clipboardEntries.length === 0
} }
delegate: ClipboardEntry { delegate: ClipboardEntry {
@@ -140,60 +135,11 @@ Item {
listView: clipboardListView listView: clipboardListView
onCopyRequested: clipboardContent.modal.copyEntry(modelData) onCopyRequested: clipboardContent.modal.copyEntry(modelData)
onDeleteRequested: clipboardContent.modal.deleteEntry(modelData) onDeleteRequested: clipboardContent.modal.deleteEntry(modelData)
onPinRequested: clipboardContent.modal.pinEntry(modelData)
onUnpinRequested: clipboardContent.modal.unpinEntry(modelData)
}
}
// Saved Tab
DankListView {
id: savedListView
anchors.fill: parent
model: ScriptModel {
values: clipboardContent.modal.pinnedEntries
objectProp: "id"
}
visible: modal.activeTab === "saved"
spacing: Theme.spacingXS
interactive: true
flickDeceleration: 1500
maximumFlickVelocity: 2000
boundsBehavior: Flickable.DragAndOvershootBounds
boundsMovement: Flickable.FollowBoundsBehavior
pressDelay: 0
flickableDirection: Flickable.VerticalFlick
StyledText {
text: I18n.tr("No saved clipboard entries")
anchors.centerIn: parent
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
visible: clipboardContent.modal.pinnedEntries.length === 0
}
delegate: ClipboardEntry {
required property int index
required property var modelData
width: savedListView.width
height: ClipboardConstants.itemHeight
entry: modelData
entryIndex: index + 1
itemIndex: index
isSelected: false
modal: clipboardContent.modal
listView: savedListView
onCopyRequested: clipboardContent.modal.copyEntry(modelData)
onDeleteRequested: clipboardContent.modal.deletePinnedEntry(modelData)
onPinRequested: clipboardContent.modal.pinEntry(modelData)
onUnpinRequested: clipboardContent.modal.unpinEntry(modelData)
} }
} }
} }
Item { Item {
id: keyboardHintsContainer
width: parent.width width: parent.width
height: modal.showKeyboardHints ? ClipboardConstants.keyboardHintsHeight + Theme.spacingL : 0 height: modal.showKeyboardHints ? ClipboardConstants.keyboardHintsHeight + Theme.spacingL : 0

View File

@@ -14,8 +14,6 @@ Rectangle {
signal copyRequested signal copyRequested
signal deleteRequested signal deleteRequested
signal pinRequested
signal unpinRequested
readonly property string entryType: modal ? modal.getEntryType(entry) : "text" readonly property string entryType: modal ? modal.getEntryType(entry) : "text"
readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : "" readonly property string entryPreview: modal ? modal.getEntryPreview(entry) : ""
@@ -52,7 +50,7 @@ Rectangle {
Row { Row {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: parent.width - 110 width: parent.width - 68
spacing: Theme.spacingM spacing: Theme.spacingM
ClipboardThumbnail { ClipboardThumbnail {
@@ -102,32 +100,20 @@ Rectangle {
} }
} }
Row { DankActionButton {
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Theme.spacingM anchors.rightMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS iconName: "close"
iconSize: Theme.iconSize - 6
DankActionButton { iconColor: Theme.surfaceText
iconName: "push_pin" onClicked: deleteRequested()
iconSize: Theme.iconSize - 6
iconColor: entry.pinned ? Theme.primary : Theme.surfaceText
backgroundColor: entry.pinned ? Theme.primarySelected : "transparent"
onClicked: entry.pinned ? unpinRequested() : pinRequested()
}
DankActionButton {
iconName: "close"
iconSize: Theme.iconSize - 6
iconColor: Theme.surfaceText
onClicked: deleteRequested()
}
} }
MouseArea { MouseArea {
id: mouseArea id: mouseArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 80 anchors.rightMargin: 40
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: copyRequested() onClicked: copyRequested()

View File

@@ -8,13 +8,10 @@ Item {
property int totalCount: 0 property int totalCount: 0
property bool showKeyboardHints: false property bool showKeyboardHints: false
property string activeTab: "recents"
property int pinnedCount: 0
signal keyboardHintsToggled signal keyboardHintsToggled
signal clearAllClicked signal clearAllClicked
signal closeClicked signal closeClicked
signal tabChanged(string tabName)
height: ClipboardConstants.headerHeight height: ClipboardConstants.headerHeight
@@ -44,22 +41,6 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS spacing: Theme.spacingS
DankActionButton {
iconName: "history"
iconSize: Theme.iconSize - 4
iconColor: header.activeTab === "recents" ? Theme.primary : Theme.surfaceText
onClicked: tabChanged("recents")
}
DankActionButton {
iconName: "push_pin"
iconSize: Theme.iconSize - 4
iconColor: header.activeTab === "saved" ? Theme.primary : Theme.surfaceText
opacity: header.pinnedCount > 0 ? 1 : 0
enabled: header.pinnedCount > 0
onClicked: tabChanged("saved")
}
DankActionButton { DankActionButton {
iconName: "info" iconName: "info"
iconSize: Theme.iconSize - 4 iconSize: Theme.iconSize - 4

View File

@@ -19,8 +19,6 @@ DankModal {
property int totalCount: 0 property int totalCount: 0
property var clipboardEntries: [] property var clipboardEntries: []
property var pinnedEntries: []
property int pinnedCount: 0
property string searchText: "" property string searchText: ""
property int selectedIndex: 0 property int selectedIndex: 0
property bool keyboardNavigationActive: false property bool keyboardNavigationActive: false
@@ -76,37 +74,22 @@ DankModal {
function updateFilteredModel() { function updateFilteredModel() {
const query = searchText.trim(); const query = searchText.trim();
let filtered = [];
if (query.length === 0) { if (query.length === 0) {
filtered = internalEntries; clipboardEntries = internalEntries;
} else { } else {
const lowerQuery = query.toLowerCase(); const lowerQuery = query.toLowerCase();
filtered = internalEntries.filter(entry => clipboardEntries = internalEntries.filter(entry => entry.preview.toLowerCase().includes(lowerQuery));
entry.preview.toLowerCase().includes(lowerQuery)
);
} }
// Sort: pinned first, then by ID descending
filtered.sort((a, b) => {
if (a.pinned !== b.pinned) return b.pinned ? 1 : -1;
return b.id - a.id;
});
clipboardEntries = filtered;
unpinnedEntries = filtered.filter(e => !e.pinned);
totalCount = clipboardEntries.length; totalCount = clipboardEntries.length;
if (unpinnedEntries.length === 0) { if (clipboardEntries.length === 0) {
keyboardNavigationActive = false; keyboardNavigationActive = false;
selectedIndex = 0; selectedIndex = 0;
} else if (selectedIndex >= unpinnedEntries.length) { } else if (selectedIndex >= clipboardEntries.length) {
selectedIndex = unpinnedEntries.length - 1; selectedIndex = clipboardEntries.length - 1;
} }
} }
property var internalEntries: [] property var internalEntries: []
property var unpinnedEntries: []
property string activeTab: "recents"
function toggle() { function toggle() {
if (shouldBeVisible) { if (shouldBeVisible) {
@@ -152,10 +135,6 @@ DankModal {
return; return;
} }
internalEntries = response.result || []; internalEntries = response.result || [];
pinnedEntries = internalEntries.filter(e => e.pinned);
pinnedCount = pinnedEntries.length;
updateFilteredModel(); updateFilteredModel();
}); });
} }
@@ -192,85 +171,16 @@ DankModal {
}); });
} }
function deletePinnedEntry(entry) {
clearConfirmDialog.show(
I18n.tr("Delete Saved Item?"),
I18n.tr("This will permanently remove this saved clipboard item. This action cannot be undone."),
function () {
DMSService.sendRequest("clipboard.deleteEntry", {
"id": entry.id
}, function (response) {
if (response.error) {
console.warn("ClipboardHistoryModal: Failed to delete entry:", response.error);
return;
}
internalEntries = internalEntries.filter(e => e.id !== entry.id);
updateFilteredModel();
ToastService.showInfo(I18n.tr("Saved item deleted"));
});
},
function () {}
);
}
function pinEntry(entry) {
DMSService.sendRequest("clipboard.getPinnedCount", null, function (countResponse) {
if (countResponse.error) {
ToastService.showError(I18n.tr("Failed to check pin limit"));
return;
}
const maxPinned = 25; // TODO: Get from config
if (countResponse.result.count >= maxPinned) {
ToastService.showError(I18n.tr("Maximum pinned entries reached") + " (" + maxPinned + ")");
return;
}
DMSService.sendRequest("clipboard.pinEntry", { "id": entry.id }, function (response) {
if (response.error) {
ToastService.showError(I18n.tr("Failed to pin entry"));
return;
}
ToastService.showInfo(I18n.tr("Entry pinned"));
refreshClipboard();
});
});
}
function unpinEntry(entry) {
DMSService.sendRequest("clipboard.unpinEntry", { "id": entry.id }, function (response) {
if (response.error) {
ToastService.showError(I18n.tr("Failed to unpin entry"));
return;
}
ToastService.showInfo(I18n.tr("Entry unpinned"));
refreshClipboard();
});
}
function clearAll() { function clearAll() {
const hasPinned = pinnedCount > 0; DMSService.sendRequest("clipboard.clearHistory", null, function (response) {
const message = hasPinned if (response.error) {
? I18n.tr("This will delete all unpinned entries. %1 pinned entries will be kept.").arg(pinnedCount) console.warn("ClipboardHistoryModal: Failed to clear history:", response.error);
: I18n.tr("This will permanently delete all clipboard history."); return;
}
clearConfirmDialog.show( internalEntries = [];
I18n.tr("Clear History?"), clipboardEntries = [];
message, totalCount = 0;
function () { });
DMSService.sendRequest("clipboard.clearHistory", null, function (response) {
if (response.error) {
console.warn("ClipboardHistoryModal: Failed to clear history:", response.error);
return;
}
refreshClipboard();
if (hasPinned) {
ToastService.showInfo(I18n.tr("History cleared. %1 pinned entries kept.").arg(pinnedCount));
}
});
},
function () {}
);
} }
function getEntryPreview(entry) { function getEntryPreview(entry) {

View File

@@ -11,8 +11,7 @@ FloatingWindow {
id: processListModal id: processListModal
property int currentTab: 0 property int currentTab: 0
property string searchText: "" property var tabNames: ["Processes", "Performance", "System"]
property string expandedPid: ""
property bool shouldHaveFocus: visible property bool shouldHaveFocus: visible
property alias shouldBeVisible: processListModal.visible property alias shouldBeVisible: processListModal.visible
@@ -28,8 +27,9 @@ FloatingWindow {
function hide() { function hide() {
visible = false; visible = false;
if (processContextMenu.visible) if (processContextMenu.visible) {
processContextMenu.close(); processContextMenu.close();
}
} }
function toggle() { function toggle() {
@@ -61,39 +61,46 @@ FloatingWindow {
show(); show();
} }
function formatBytes(bytes) {
if (bytes < 1024)
return bytes.toFixed(0) + " B/s";
if (bytes < 1024 * 1024)
return (bytes / 1024).toFixed(1) + " KB/s";
if (bytes < 1024 * 1024 * 1024)
return (bytes / (1024 * 1024)).toFixed(1) + " MB/s";
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
}
objectName: "processListModal" objectName: "processListModal"
title: I18n.tr("System Monitor", "sysmon window title") title: I18n.tr("System Monitor", "sysmon window title")
minimumSize: Qt.size(750, 550) minimumSize: Qt.size(650, 400)
implicitWidth: 1000 implicitWidth: 900
implicitHeight: 720 implicitHeight: 680
color: Theme.surfaceContainer color: Theme.surfaceContainer
visible: false visible: false
onVisibleChanged: { onVisibleChanged: {
if (!visible) { if (!visible) {
closingModal(); closingModal();
searchText = "";
expandedPid = "";
DgopService.removeRef(["cpu", "memory", "network", "disk", "system"]);
} else { } else {
DgopService.addRef(["cpu", "memory", "network", "disk", "system"]);
Qt.callLater(() => { Qt.callLater(() => {
if (contentFocusScope) if (contentFocusScope) {
contentFocusScope.forceActiveFocus(); contentFocusScope.forceActiveFocus();
}
}); });
} }
} }
Component {
id: processesTabComponent
ProcessesTab {
contextMenu: processContextMenu
}
}
Component {
id: performanceTabComponent
PerformanceTab {}
}
Component {
id: systemTabComponent
SystemTab {}
}
ProcessContextMenu { ProcessContextMenu {
id: processContextMenu id: processContextMenu
} }
@@ -121,26 +128,6 @@ FloatingWindow {
currentTab = 2; currentTab = 2;
event.accepted = true; event.accepted = true;
return; return;
case Qt.Key_4:
currentTab = 3;
event.accepted = true;
return;
case Qt.Key_Escape:
if (searchText.length > 0) {
searchText = "";
event.accepted = true;
return;
}
hide();
event.accepted = true;
return;
case Qt.Key_F:
if (event.modifiers & Qt.ControlModifier) {
searchField.forceActiveFocus();
event.accepted = true;
return;
}
break;
} }
} }
@@ -174,7 +161,7 @@ FloatingWindow {
} }
StyledText { StyledText {
text: I18n.tr("The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature.", "dgop unavailable error message") text: I18n.tr("The 'dgop' tool is required for system monitoring.\nPlease install dgop to use this feature.")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -184,14 +171,14 @@ FloatingWindow {
} }
} }
ColumnLayout { Column {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
visible: DgopService.dgopAvailable visible: DgopService.dgopAvailable
Item { Item {
Layout.fillWidth: true width: parent.width
Layout.preferredHeight: 48 height: 48
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
@@ -246,276 +233,166 @@ FloatingWindow {
} }
} }
RowLayout { Item {
Layout.fillWidth: true width: parent.width
Layout.preferredHeight: 52 height: parent.height - 48
Layout.leftMargin: Theme.spacingL
Layout.rightMargin: Theme.spacingL
spacing: Theme.spacingL
Row { ColumnLayout {
spacing: 2
Repeater {
model: [
{
text: I18n.tr("Processes"),
icon: "list_alt"
},
{
text: I18n.tr("Performance"),
icon: "analytics"
},
{
text: I18n.tr("Disks"),
icon: "storage"
},
{
text: I18n.tr("System"),
icon: "computer"
}
]
Rectangle {
width: 120
height: 44
radius: Theme.cornerRadius
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
border.color: currentTab === index ? Theme.primary : "transparent"
border.width: currentTab === index ? 1 : 0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: modelData.icon
size: Theme.iconSize - 2
color: currentTab === index ? Theme.primary : Theme.surfaceText
opacity: currentTab === index ? 1 : 0.7
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: modelData.text
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Medium
color: currentTab === index ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: tabMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: currentTab = index
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
}
Item {
Layout.fillWidth: true
}
DankTextField {
id: searchField
Layout.preferredWidth: 250
Layout.preferredHeight: 40
placeholderText: I18n.tr("Search processes...", "process search placeholder")
leftIconName: "search"
showClearButton: true
text: searchText
visible: currentTab === 0
onTextChanged: searchText = text
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.margins: Theme.spacingL
Layout.topMargin: Theme.spacingM
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineLight
border.width: 1
clip: true
Loader {
id: processesTabLoader
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.leftMargin: Theme.spacingL
active: processListModal.visible && currentTab === 0 anchors.rightMargin: Theme.spacingL
visible: currentTab === 0 anchors.bottomMargin: Theme.spacingL
sourceComponent: ProcessesView { anchors.topMargin: 0
searchText: processListModal.searchText
expandedPid: processListModal.expandedPid
contextMenu: processContextMenu
onExpandedPidChanged: processListModal.expandedPid = expandedPid
}
}
Loader {
id: performanceTabLoader
anchors.fill: parent
anchors.margins: Theme.spacingS
active: processListModal.visible && currentTab === 1
visible: currentTab === 1
sourceComponent: PerformanceView {}
}
Loader {
id: disksTabLoader
anchors.fill: parent
anchors.margins: Theme.spacingS
active: processListModal.visible && currentTab === 2
visible: currentTab === 2
sourceComponent: DisksView {}
}
Loader {
id: systemTabLoader
anchors.fill: parent
anchors.margins: Theme.spacingS
active: processListModal.visible && currentTab === 3
visible: currentTab === 3
sourceComponent: SystemView {}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 32
Layout.leftMargin: Theme.spacingL
Layout.rightMargin: Theme.spacingL
Layout.bottomMargin: Theme.spacingM
color: "transparent"
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingL spacing: Theme.spacingL
Row { Rectangle {
spacing: Theme.spacingXS Layout.fillWidth: true
height: 52
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
radius: Theme.cornerRadius
border.color: Theme.outlineLight
border.width: 1
StyledText { Row {
text: I18n.tr("Processes:", "process count label in footer") anchors.fill: parent
font.pixelSize: Theme.fontSizeSmall anchors.margins: 4
color: Theme.surfaceVariantText spacing: 2
}
StyledText { Repeater {
text: DgopService.processCount.toString() model: tabNames
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold Rectangle {
color: Theme.surfaceText width: (parent.width - (tabNames.length - 1) * 2) / tabNames.length
height: 44
radius: Theme.cornerRadius
color: currentTab === index ? Theme.primaryPressed : (tabMouseArea.containsMouse ? Theme.primaryHoverLight : "transparent")
border.color: currentTab === index ? Theme.primary : "transparent"
border.width: currentTab === index ? 1 : 0
Row {
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: {
const tabIcons = ["list_alt", "analytics", "settings"];
return tabIcons[index] || "tab";
}
size: Theme.iconSize - 2
color: currentTab === index ? Theme.primary : Theme.surfaceText
opacity: currentTab === index ? 1 : 0.7
anchors.verticalCenter: parent.verticalCenter
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
StyledText {
text: modelData
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: currentTab === index ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -1
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
MouseArea {
id: tabMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: () => {
currentTab = index;
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
} }
} }
Row { Rectangle {
spacing: Theme.spacingXS Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineLight
border.width: 1
StyledText { Loader {
text: I18n.tr("Uptime:", "uptime label in footer") id: processesTab
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText anchors.fill: parent
anchors.margins: Theme.spacingS
active: processListModal.visible && currentTab === 0
visible: currentTab === 0
opacity: currentTab === 0 ? 1 : 0
sourceComponent: processesTabComponent
Behavior on opacity {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Theme.emphasizedEasing
}
}
} }
StyledText { Loader {
text: DgopService.shortUptime || "--" id: performanceTab
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
}
Row { anchors.fill: parent
anchors.right: parent.right anchors.margins: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter active: processListModal.visible && currentTab === 1
spacing: Theme.spacingL visible: currentTab === 1
opacity: currentTab === 1 ? 1 : 0
sourceComponent: performanceTabComponent
Row { Behavior on opacity {
spacing: Theme.spacingXS NumberAnimation {
duration: Theme.mediumDuration
DankIcon { easing.type: Theme.emphasizedEasing
name: "swap_horiz" }
size: 14 }
color: Theme.info
anchors.verticalCenter: parent.verticalCenter
} }
StyledText { Loader {
text: "↓" + formatBytes(DgopService.networkRxRate) + " ↑" + formatBytes(DgopService.networkTxRate) id: systemTab
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
}
}
Row { anchors.fill: parent
spacing: Theme.spacingXS anchors.margins: Theme.spacingS
active: processListModal.visible && currentTab === 2
visible: currentTab === 2
opacity: currentTab === 2 ? 1 : 0
sourceComponent: systemTabComponent
DankIcon { Behavior on opacity {
name: "storage" NumberAnimation {
size: 14 duration: Theme.mediumDuration
color: Theme.warning easing.type: Theme.emphasizedEasing
anchors.verticalCenter: parent.verticalCenter }
} }
StyledText {
text: "↓" + formatBytes(DgopService.diskReadRate) + " ↑" + formatBytes(DgopService.diskWriteRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
}
}
Row {
spacing: Theme.spacingXS
DankIcon {
name: "memory"
size: 14
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: DgopService.cpuUsage.toFixed(1) + "%"
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: DgopService.cpuUsage > 80 ? Theme.error : Theme.surfaceText
}
}
Row {
spacing: Theme.spacingXS
DankIcon {
name: "sd_card"
size: 14
color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: DgopService.formatSystemMemory(DgopService.usedMemoryKB) + " / " + DgopService.formatSystemMemory(DgopService.totalMemoryKB)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: DgopService.memoryUsage > 90 ? Theme.error : Theme.surfaceText
} }
} }
} }

View File

@@ -122,21 +122,6 @@ Rectangle {
contentHeight: audioColumn.height contentHeight: audioColumn.height
clip: true clip: true
property int maxPinnedInputs: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedInputs() {
const pins = SettingsData.audioInputDevicePins || {}
return normalizePinList(pins["preferredInput"])
}
Column { Column {
id: audioColumn id: audioColumn
width: parent.width width: parent.width
@@ -148,20 +133,16 @@ Rectangle {
const nodes = Pipewire.nodes.values.filter(node => { const nodes = Pipewire.nodes.values.filter(node => {
return node.audio && !node.isSink && !node.isStream; return node.audio && !node.isSink && !node.isStream;
}); });
const pinnedList = audioContent.getPinnedInputs(); const pins = SettingsData.audioInputDevicePins || {};
const pinnedName = pins["preferredInput"];
let sorted = [...nodes]; let sorted = [...nodes];
sorted.sort((a, b) => { sorted.sort((a, b) => {
// Pinned device first // Pinned device first
const aPinnedIndex = pinnedList.indexOf(a.name) if (a.name === pinnedName && b.name !== pinnedName)
const bPinnedIndex = pinnedList.indexOf(b.name) return -1;
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) { if (b.name === pinnedName && a.name !== pinnedName)
if (aPinnedIndex === -1) return 1;
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
// Then active device // Then active device
if (a === AudioService.source && b !== AudioService.source) if (a === AudioService.source && b !== AudioService.source)
return -1; return -1;
@@ -243,7 +224,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05); return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
} }
@@ -256,7 +237,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -264,12 +245,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin"); return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisDevicePinned = audioContent.getPinnedInputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -280,24 +261,16 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
let pinnedList = audioContent.normalizePinList(pins["preferredInput"]) const isCurrentlyPinned = pins["preferredInput"] === modelData.name;
const pinIndex = pinnedList.indexOf(modelData.name)
if (pinIndex !== -1) { if (isCurrentlyPinned) {
pinnedList.splice(pinIndex, 1) delete pins["preferredInput"];
} else { } else {
pinnedList.unshift(modelData.name) pins["preferredInput"] = modelData.name;
if (pinnedList.length > audioContent.maxPinnedInputs)
pinnedList = pinnedList.slice(0, audioContent.maxPinnedInputs)
} }
if (pinnedList.length > 0) SettingsData.set("audioInputDevicePins", pins);
pins["preferredInput"] = pinnedList
else
delete pins["preferredInput"]
SettingsData.set("audioInputDevicePins", pins)
} }
} }
} }

View File

@@ -132,21 +132,6 @@ Rectangle {
contentHeight: audioColumn.height contentHeight: audioColumn.height
clip: true clip: true
property int maxPinnedOutputs: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedOutputs() {
const pins = SettingsData.audioOutputDevicePins || {}
return normalizePinList(pins["preferredOutput"])
}
Column { Column {
id: audioColumn id: audioColumn
width: parent.width width: parent.width
@@ -158,20 +143,16 @@ Rectangle {
const nodes = Pipewire.nodes.values.filter(node => { const nodes = Pipewire.nodes.values.filter(node => {
return node.audio && node.isSink && !node.isStream; return node.audio && node.isSink && !node.isStream;
}); });
const pinnedList = audioContent.getPinnedOutputs(); const pins = SettingsData.audioOutputDevicePins || {};
const pinnedName = pins["preferredOutput"];
let sorted = [...nodes]; let sorted = [...nodes];
sorted.sort((a, b) => { sorted.sort((a, b) => {
// Pinned device first // Pinned device first
const aPinnedIndex = pinnedList.indexOf(a.name) if (a.name === pinnedName && b.name !== pinnedName)
const bPinnedIndex = pinnedList.indexOf(b.name) return -1;
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) { if (b.name === pinnedName && a.name !== pinnedName)
if (aPinnedIndex === -1) return 1;
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
// Then active device // Then active device
if (a === AudioService.sink && b !== AudioService.sink) if (a === AudioService.sink && b !== AudioService.sink)
return -1; return -1;
@@ -255,7 +236,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05); return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
} }
@@ -268,7 +249,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -276,12 +257,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin"); return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisDevicePinned = audioContent.getPinnedOutputs().includes(modelData.name); const isThisDevicePinned = (SettingsData.audioOutputDevicePins || {})["preferredOutput"] === modelData.name;
return isThisDevicePinned ? Theme.primary : Theme.surfaceText; return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -292,24 +273,16 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]) const isCurrentlyPinned = pins["preferredOutput"] === modelData.name;
const pinIndex = pinnedList.indexOf(modelData.name)
if (pinIndex !== -1) { if (isCurrentlyPinned) {
pinnedList.splice(pinIndex, 1) delete pins["preferredOutput"];
} else { } else {
pinnedList.unshift(modelData.name) pins["preferredOutput"] = modelData.name;
if (pinnedList.length > audioContent.maxPinnedOutputs)
pinnedList = pinnedList.slice(0, audioContent.maxPinnedOutputs)
} }
if (pinnedList.length > 0) SettingsData.set("audioOutputDevicePins", pins);
pins["preferredOutput"] = pinnedList
else
delete pins["preferredOutput"]
SettingsData.set("audioOutputDevicePins", pins)
} }
} }
} }

View File

@@ -150,21 +150,6 @@ Rectangle {
contentHeight: bluetoothColumn.height contentHeight: bluetoothColumn.height
clip: true clip: true
property int maxPinnedDevices: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedDevices() {
const pins = SettingsData.bluetoothDevicePins || {}
return normalizePinList(pins["preferredDevice"])
}
Column { Column {
id: bluetoothColumn id: bluetoothColumn
width: parent.width width: parent.width
@@ -177,18 +162,14 @@ Rectangle {
if (!BluetoothService.adapter || !BluetoothService.adapter.devices) if (!BluetoothService.adapter || !BluetoothService.adapter.devices)
return [] return []
const pinnedList = bluetoothContent.getPinnedDevices() const pins = SettingsData.bluetoothDevicePins || {}
const pinnedAddr = pins["preferredDevice"]
let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))] let devices = [...BluetoothService.adapter.devices.values.filter(dev => dev && (dev.paired || dev.trusted))]
devices.sort((a, b) => { devices.sort((a, b) => {
// Pinned device first // Pinned device first
const aPinnedIndex = pinnedList.indexOf(a.address) if (a.address === pinnedAddr && b.address !== pinnedAddr) return -1
const bPinnedIndex = pinnedList.indexOf(b.address) if (b.address === pinnedAddr && a.address !== pinnedAddr) return 1
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) {
if (aPinnedIndex === -1) return 1
if (bPinnedIndex === -1) return -1
return aPinnedIndex - bPinnedIndex
}
// Then connected devices // Then connected devices
if (a.connected && !b.connected) return -1 if (a.connected && !b.connected) return -1
if (!a.connected && b.connected) return 1 if (!a.connected && b.connected) return 1
@@ -321,7 +302,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address) const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05) return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
} }
@@ -334,7 +315,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address) const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address
return isThisDevicePinned ? Theme.primary : Theme.surfaceText return isThisDevicePinned ? Theme.primary : Theme.surfaceText
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -342,12 +323,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address) const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address
return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin") return isThisDevicePinned ? I18n.tr("Pinned") : I18n.tr("Pin")
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisDevicePinned = bluetoothContent.getPinnedDevices().includes(modelData.address) const isThisDevicePinned = (SettingsData.bluetoothDevicePins || {})["preferredDevice"] === modelData.address
return isThisDevicePinned ? Theme.primary : Theme.surfaceText return isThisDevicePinned ? Theme.primary : Theme.surfaceText
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -359,21 +340,13 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {}))
let pinnedList = bluetoothContent.normalizePinList(pins["preferredDevice"]) const isCurrentlyPinned = pins["preferredDevice"] === modelData.address
const pinIndex = pinnedList.indexOf(modelData.address)
if (pinIndex !== -1) { if (isCurrentlyPinned) {
pinnedList.splice(pinIndex, 1)
} else {
pinnedList.unshift(modelData.address)
if (pinnedList.length > bluetoothContent.maxPinnedDevices)
pinnedList = pinnedList.slice(0, bluetoothContent.maxPinnedDevices)
}
if (pinnedList.length > 0)
pins["preferredDevice"] = pinnedList
else
delete pins["preferredDevice"] delete pins["preferredDevice"]
} else {
pins["preferredDevice"] = modelData.address
}
SettingsData.set("bluetoothDevicePins", pins) SettingsData.set("bluetoothDevicePins", pins)
} }

View File

@@ -463,39 +463,20 @@ Rectangle {
contentHeight: wifiColumn.height contentHeight: wifiColumn.height
clip: true clip: true
property int maxPinnedNetworks: 3
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedNetworks() {
const pins = SettingsData.wifiNetworkPins || {}
return normalizePinList(pins["preferredWifi"])
}
property var frozenNetworks: [] property var frozenNetworks: []
property bool menuOpen: false property bool menuOpen: false
property var sortedNetworks: { property var sortedNetworks: {
const ssid = NetworkService.currentWifiSSID; const ssid = NetworkService.currentWifiSSID;
const networks = NetworkService.wifiNetworks; const networks = NetworkService.wifiNetworks;
const pinnedList = getPinnedNetworks() const pins = SettingsData.wifiNetworkPins || {};
const pinnedSSID = pins["preferredWifi"];
let sorted = [...networks]; let sorted = [...networks];
sorted.sort((a, b) => { sorted.sort((a, b) => {
const aPinnedIndex = pinnedList.indexOf(a.ssid) if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID)
const bPinnedIndex = pinnedList.indexOf(b.ssid) return -1;
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) { if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID)
if (aPinnedIndex === -1) return 1;
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
if (a.ssid === ssid) if (a.ssid === ssid)
return -1; return -1;
if (b.ssid === ssid) if (b.ssid === ssid)
@@ -644,7 +625,7 @@ Rectangle {
height: 28 height: 28
radius: height / 2 radius: height / 2
color: { color: {
const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid); const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05); return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
} }
@@ -657,7 +638,7 @@ Rectangle {
name: "push_pin" name: "push_pin"
size: 16 size: 16
color: { color: {
const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid); const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText; return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -665,12 +646,12 @@ Rectangle {
StyledText { StyledText {
text: { text: {
const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid); const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
return isThisNetworkPinned ? I18n.tr("Pinned") : I18n.tr("Pin"); return isThisNetworkPinned ? I18n.tr("Pinned") : I18n.tr("Pin");
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: {
const isThisNetworkPinned = wifiContent.getPinnedNetworks().includes(modelData.ssid); const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText; return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -681,24 +662,16 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {})) const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
let pinnedList = wifiContent.normalizePinList(pins["preferredWifi"]) const isCurrentlyPinned = pins["preferredWifi"] === modelData.ssid;
const pinIndex = pinnedList.indexOf(modelData.ssid)
if (pinIndex !== -1) { if (isCurrentlyPinned) {
pinnedList.splice(pinIndex, 1) delete pins["preferredWifi"];
} else { } else {
pinnedList.unshift(modelData.ssid) pins["preferredWifi"] = modelData.ssid;
if (pinnedList.length > wifiContent.maxPinnedNetworks)
pinnedList = pinnedList.slice(0, wifiContent.maxPinnedNetworks)
} }
if (pinnedList.length > 0) SettingsData.set("wifiNetworkPins", pins);
pins["preferredWifi"] = pinnedList
else
delete pins["preferredWifi"]
SettingsData.set("wifiNetworkPins", pins)
} }
} }
} }

View File

@@ -24,12 +24,10 @@ BasePill {
property bool showMicPercent: widgetData?.showMicPercent !== undefined ? widgetData.showMicPercent : SettingsData.controlCenterShowMicPercent property bool showMicPercent: widgetData?.showMicPercent !== undefined ? widgetData.showMicPercent : SettingsData.controlCenterShowMicPercent
property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon property bool showBatteryIcon: widgetData?.showBatteryIcon !== undefined ? widgetData.showBatteryIcon : SettingsData.controlCenterShowBatteryIcon
property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon property bool showPrinterIcon: widgetData?.showPrinterIcon !== undefined ? widgetData.showPrinterIcon : SettingsData.controlCenterShowPrinterIcon
property bool showScreenSharingIcon: widgetData?.showScreenSharingIcon !== undefined ? widgetData.showScreenSharingIcon : SettingsData.controlCenterShowScreenSharingIcon
property real touchpadThreshold: 100 property real touchpadThreshold: 100
property real micAccumulator: 0 property real micAccumulator: 0
property real volumeAccumulator: 0 property real volumeAccumulator: 0
property real brightnessAccumulator: 0 property real brightnessAccumulator: 0
readonly property real vIconSize: Theme.barIconSize(root.barThickness, -4)
Loader { Loader {
active: root.showPrinterIcon active: root.showPrinterIcon
@@ -215,25 +213,7 @@ BasePill {
} }
function hasNoVisibleIcons() { function hasNoVisibleIcons() {
if (root.showScreenSharingIcon && NiriService.hasCasts) return !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon && !root.showVpnIcon && !root.showBrightnessIcon && !root.showMicIcon && !root.showBatteryIcon && !root.showPrinterIcon;
return false;
if (root.showNetworkIcon && NetworkService.networkAvailable)
return false;
if (root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected)
return false;
if (root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled)
return false;
if (root.showAudioIcon)
return false;
if (root.showMicIcon)
return false;
if (root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice())
return false;
if (root.showBatteryIcon && BatteryService.batteryAvailable)
return false;
if (root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs())
return false;
return true;
} }
content: Component { content: Component {
@@ -244,74 +224,48 @@ BasePill {
Column { Column {
id: controlColumn id: controlColumn
visible: root.isVerticalOrientation visible: root.isVerticalOrientation
width: root.vIconSize anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
spacing: Theme.spacingXS spacing: Theme.spacingXS
Item { DankIcon {
width: root.vIconSize name: root.getNetworkIconName()
height: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
visible: root.showScreenSharingIcon && NiriService.hasCasts color: root.getNetworkIconColor()
anchors.horizontalCenter: parent.horizontalCenter
DankIcon {
name: "screen_record"
size: root.vIconSize
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
}
Item {
width: root.vIconSize
height: root.vIconSize
visible: root.showNetworkIcon && NetworkService.networkAvailable visible: root.showNetworkIcon && NetworkService.networkAvailable
DankIcon {
name: root.getNetworkIconName()
size: root.vIconSize
color: root.getNetworkIconColor()
anchors.centerIn: parent
}
} }
Item { DankIcon {
width: root.vIconSize name: "vpn_lock"
height: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
DankIcon {
name: "vpn_lock"
size: root.vIconSize
color: NetworkService.vpnConnected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
} }
Item { DankIcon {
width: root.vIconSize name: "bluetooth"
height: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
DankIcon {
name: "bluetooth"
size: root.vIconSize
color: BluetoothService.connected ? Theme.primary : Theme.surfaceText
anchors.centerIn: parent
}
} }
Item { Rectangle {
width: root.vIconSize width: audioIconV.implicitWidth + 4
height: root.vIconSize + (root.showAudioPercent ? audioPercentV.implicitHeight + 2 : 0) height: audioIconV.implicitHeight + (root.showAudioPercent ? audioPercentV.implicitHeight : 0) + 4
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showAudioIcon visible: root.showAudioIcon
DankIcon { DankIcon {
id: audioIconV id: audioIconV
name: root.getVolumeIconName() name: root.getVolumeIconName()
size: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor color: Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2
} }
StyledText { StyledText {
@@ -338,18 +292,21 @@ BasePill {
} }
} }
Item { Rectangle {
width: root.vIconSize width: micIconV.implicitWidth + 4
height: root.vIconSize + (root.showMicPercent ? micPercentV.implicitHeight + 2 : 0) height: micIconV.implicitHeight + (root.showAudioPercent ? micPercentV.implicitHeight : 0) + 4
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showMicIcon visible: root.showMicIcon
DankIcon { DankIcon {
id: micIconV id: micIconV
name: root.getMicIconName() name: root.getMicIconName()
size: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: root.getMicIconColor() color: root.getMicIconColor()
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2
} }
StyledText { StyledText {
@@ -376,18 +333,21 @@ BasePill {
} }
} }
Item { Rectangle {
width: root.vIconSize width: brightnessIconV.implicitWidth + 4
height: root.vIconSize + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight + 2 : 0) height: brightnessIconV.implicitHeight + (root.showBrightnessPercent ? brightnessPercentV.implicitHeight : 0) + 4
color: "transparent"
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice() visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
DankIcon { DankIcon {
id: brightnessIconV id: brightnessIconV
name: root.getBrightnessIconName() name: root.getBrightnessIconName()
size: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor color: Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: 2
} }
StyledText { StyledText {
@@ -411,43 +371,28 @@ BasePill {
} }
} }
Item { DankIcon {
width: root.vIconSize name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
height: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: root.getBatteryIconColor()
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBatteryIcon && BatteryService.batteryAvailable visible: root.showBatteryIcon && BatteryService.batteryAvailable
DankIcon {
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
size: root.vIconSize
color: root.getBatteryIconColor()
anchors.centerIn: parent
}
} }
Item { DankIcon {
width: root.vIconSize name: "print"
height: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: Theme.primary
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs() visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
DankIcon {
name: "print"
size: root.vIconSize
color: Theme.primary
anchors.centerIn: parent
}
} }
Item { DankIcon {
width: root.vIconSize name: "settings"
height: root.vIconSize size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter
visible: root.hasNoVisibleIcons() visible: root.hasNoVisibleIcons()
DankIcon {
name: "settings"
size: root.vIconSize
color: root.isActive ? Theme.primary : Theme.widgetIconColor
anchors.centerIn: parent
}
} }
} }
@@ -457,14 +402,6 @@ BasePill {
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankIcon {
name: "screen_record"
size: Theme.barIconSize(root.barThickness, -4)
color: NiriService.hasActiveCast ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: root.showScreenSharingIcon && NiriService.hasCasts
}
DankIcon { DankIcon {
id: networkIcon id: networkIcon
name: root.getNetworkIconName() name: root.getNetworkIconName()

View File

@@ -155,17 +155,9 @@ BasePill {
} }
} }
DankIcon {
anchors.centerIn: parent
size: 18
name: "sports_esports"
color: Theme.widgetTextColor
visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready && Paths.isSteamApp(activeWindow.appId)
}
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready && !Paths.isSteamApp(activeWindow.appId) visible: root.isVerticalOrientation && activeWindow && activeWindow.appId && appIcon.status !== Image.Ready
text: { text: {
if (!activeWindow || !activeWindow.appId) if (!activeWindow || !activeWindow.appId)
return "?"; return "?";

View File

@@ -393,19 +393,9 @@ Item {
} }
} }
DankIcon {
anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
size: Theme.barIconSize(root.barThickness)
name: "sports_esports"
color: Theme.widgetTextColor
visible: !iconImg.visible && Paths.isSteamApp(appId)
}
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: !iconImg.visible && !Paths.isSteamApp(appId) visible: !iconImg.visible
text: { text: {
root._desktopEntriesUpdateTrigger; root._desktopEntriesUpdateTrigger;
if (!appId) if (!appId)
@@ -638,19 +628,9 @@ Item {
} }
} }
DankIcon {
anchors.left: parent.left
anchors.leftMargin: (widgetData?.runningAppsCompactMode !== undefined ? widgetData.runningAppsCompactMode : SettingsData.runningAppsCompactMode) ? Math.round((parent.width - Theme.barIconSize(root.barThickness)) / 2) : Theme.spacingXS
anchors.verticalCenter: parent.verticalCenter
size: Theme.barIconSize(root.barThickness)
name: "sports_esports"
color: Theme.widgetTextColor
visible: !iconImg.visible && Paths.isSteamApp(appId)
}
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
visible: !iconImg.visible && !Paths.isSteamApp(appId) visible: !iconImg.visible
text: { text: {
root._desktopEntriesUpdateTrigger; root._desktopEntriesUpdateTrigger;
if (!appId) if (!appId)

View File

@@ -265,7 +265,6 @@ Item {
if (!byApp[key]) { if (!byApp[key]) {
const isQuickshell = keyBase === "org.quickshell"; const isQuickshell = keyBase === "org.quickshell";
const isSteamApp = Paths.isSteamApp(keyBase);
const moddedId = Paths.moddedAppId(keyBase); const moddedId = Paths.moddedAppId(keyBase);
const desktopEntry = DesktopEntries.heuristicLookup(moddedId); const desktopEntry = DesktopEntries.heuristicLookup(moddedId);
const icon = Paths.getAppIcon(keyBase, desktopEntry); const icon = Paths.getAppIcon(keyBase, desktopEntry);
@@ -273,7 +272,6 @@ Item {
"type": "icon", "type": "icon",
"icon": icon, "icon": icon,
"isQuickshell": isQuickshell, "isQuickshell": isQuickshell,
"isSteamApp": isSteamApp,
"active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)), "active": !!((w.activated || w.is_focused) || (CompositorService.isNiri && w.is_focused)),
"count": 1, "count": 1,
"windowId": w.address || w.id, "windowId": w.address || w.id,
@@ -1137,7 +1135,7 @@ Item {
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isQuickshell && !modelData.isSteamApp visible: !modelData.isQuickshell
} }
IconImage { IconImage {
@@ -1153,22 +1151,6 @@ Item {
} }
} }
IconImage {
anchors.fill: parent
source: modelData.icon
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp && modelData.icon
}
DankIcon {
anchors.centerIn: parent
size: root.appIconSize
name: "sports_esports"
color: Theme.widgetTextColor
opacity: modelData.active ? 1.0 : rowAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp && !modelData.icon
}
MouseArea { MouseArea {
id: rowAppMouseArea id: rowAppMouseArea
anchors.fill: parent anchors.fill: parent
@@ -1247,7 +1229,7 @@ Item {
anchors.fill: parent anchors.fill: parent
source: modelData.icon source: modelData.icon
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6 opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: !modelData.isQuickshell && !modelData.isSteamApp visible: !modelData.isQuickshell
} }
IconImage { IconImage {
@@ -1263,22 +1245,6 @@ Item {
} }
} }
IconImage {
anchors.fill: parent
source: modelData.icon
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp && modelData.icon
}
DankIcon {
anchors.centerIn: parent
size: root.appIconSize
name: "sports_esports"
color: Theme.widgetTextColor
opacity: modelData.active ? 1.0 : colAppMouseArea.containsMouse ? 0.8 : 0.6
visible: modelData.isSteamApp && !modelData.icon
}
MouseArea { MouseArea {
id: colAppMouseArea id: colAppMouseArea
anchors.fill: parent anchors.fill: parent

View File

@@ -327,12 +327,11 @@ Item {
clip: false clip: false
visible: !_noneAvailable && (!showNoPlayerNow) visible: !_noneAvailable && (!showNoPlayerNow)
ColumnLayout { ColumnLayout {
x: 72
y: 20
width: 484 width: 484
height: 370 height: 370
spacing: Theme.spacingXS spacing: Theme.spacingXS
anchors.top: parent.top
anchors.topMargin: 20
anchors.horizontalCenter: parent.horizontalCenter
Item { Item {
width: parent.width width: parent.width

View File

@@ -403,7 +403,7 @@ Item {
width: actualIconSize width: actualIconSize
height: actualIconSize height: actualIconSize
anchors.centerIn: parent anchors.centerIn: parent
visible: iconImg.status !== Image.Ready && appData && appData.appId && !Paths.isSteamApp(appData.appId) visible: iconImg.status !== Image.Ready
color: Theme.surfaceLight color: Theme.surfaceLight
radius: Theme.cornerRadius radius: Theme.cornerRadius
border.width: 1 border.width: 1
@@ -425,14 +425,6 @@ Item {
} }
} }
DankIcon {
anchors.centerIn: parent
size: actualIconSize
name: "sports_esports"
color: Theme.surfaceText
visible: iconImg.status !== Image.Ready && appData && appData.appId && Paths.isSteamApp(appData.appId)
}
Loader { Loader {
anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter anchors.horizontalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? undefined : parent.horizontalCenter
anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined anchors.verticalCenter: SettingsData.dockPosition === SettingsData.Position.Left || SettingsData.dockPosition === SettingsData.Position.Right ? parent.verticalCenter : undefined

View File

@@ -2,6 +2,8 @@
set -e set -e
export WLR_DRM_DEVICES=/dev/dri/card1
COMPOSITOR="" COMPOSITOR=""
COMPOSITOR_CONFIG="" COMPOSITOR_CONFIG=""
DMS_PATH="dms-greeter" DMS_PATH="dms-greeter"
@@ -14,7 +16,7 @@ dms-greeter - DankMaterialShell greeter launcher
Usage: dms-greeter --command COMPOSITOR [OPTIONS] Usage: dms-greeter --command COMPOSITOR [OPTIONS]
Required: Required:
--command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll, mangowc, or labwc) --command COMPOSITOR Compositor to use (niri, hyprland, sway, scroll or mangowc)
Options: Options:
-C, --config PATH Custom compositor config file -C, --config PATH Custom compositor config file
@@ -31,7 +33,6 @@ Examples:
dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms dms-greeter --command scroll -p /home/user/.config/quickshell/custom-dms
dms-greeter --command niri --cache-dir /tmp/dmsgreeter dms-greeter --command niri --cache-dir /tmp/dmsgreeter
dms-greeter --command mangowc dms-greeter --command mangowc
dms-greeter --command labwc
EOF EOF
} }
@@ -230,15 +231,6 @@ SCROLL_EOF
exec scroll -c "$COMPOSITOR_CONFIG" exec scroll -c "$COMPOSITOR_CONFIG"
;; ;;
labwc)
if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec labwc --config "$COMPOSITOR_CONFIG" --session "$QS_CMD"
else
exec labwc --session "$QS_CMD"
fi
;;
mangowc) mangowc)
if [[ -n "$COMPOSITOR_CONFIG" ]]; then if [[ -n "$COMPOSITOR_CONFIG" ]]; then
exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit" exec mango -c "$COMPOSITOR_CONFIG" -s "$QS_CMD && mmsg -d quit"
@@ -249,7 +241,7 @@ SCROLL_EOF
*) *)
echo "Error: Unsupported compositor: $COMPOSITOR" >&2 echo "Error: Unsupported compositor: $COMPOSITOR" >&2
echo "Supported compositors: niri, hyprland, sway, scroll, mangowc, labwc" >&2 echo "Supported compositors: niri, hyprland, sway, mangowc" >&2
exit 1 exit 1
;; ;;
esac esac

View File

@@ -1406,14 +1406,6 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio) visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
DankIcon {
name: "screen_record"
size: Theme.iconSize - 2
color: NiriService.hasActiveCast ? "white" : Qt.rgba(255, 255, 255, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: NiriService.hasCasts
}
DankIcon { DankIcon {
name: { name: {
if (NetworkService.wifiToggling) if (NetworkService.wifiToggling)

View File

@@ -262,7 +262,9 @@ DankOSD {
target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null target: AudioService.sink && AudioService.sink.audio ? AudioService.sink.audio : null
function onVolumeChanged() { function onVolumeChanged() {
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100)); if (!vertSlider.dragging) {
vertSlider.value = Math.min(100, Math.round(AudioService.sink.audio.volume * 100));
}
} }
} }
} }

View File

@@ -1,346 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
function formatSpeed(bytesPerSec) {
if (bytesPerSec < 1024)
return bytesPerSec.toFixed(0) + " B/s";
if (bytesPerSec < 1024 * 1024)
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
if (bytesPerSec < 1024 * 1024 * 1024)
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
}
Component.onCompleted: {
DgopService.addRef(["disk", "diskmounts"]);
}
Component.onDestruction: {
DgopService.removeRef(["disk", "diskmounts"]);
}
ColumnLayout {
anchors.fill: parent
spacing: Theme.spacingM
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 80
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
RowLayout {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXL
Column {
Layout.fillWidth: true
spacing: Theme.spacingXS
Row {
spacing: Theme.spacingS
DankIcon {
name: "storage"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Disk I/O", "disk io header in system monitor")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Row {
spacing: Theme.spacingL
Row {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Read:", "disk read label")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: root.formatSpeed(DgopService.diskReadRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.primary
}
}
Row {
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Write:", "disk write label")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: root.formatSpeed(DgopService.diskWriteRate)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.warning
}
}
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Row {
spacing: Theme.spacingS
DankIcon {
name: "folder"
size: Theme.iconSize - 2
color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Mount Points", "mount points header in system monitor")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Theme.outlineLight
}
DankListView {
id: mountListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 4
model: DgopService.diskMounts
delegate: Rectangle {
required property var modelData
required property int index
width: mountListView.width
height: 60
radius: Theme.cornerRadius
color: mountMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
readonly property real usedPct: {
const pctStr = modelData?.percent ?? "0%";
return parseFloat(pctStr.replace("%", "")) / 100;
}
MouseArea {
id: mountMouseArea
anchors.fill: parent
hoverEnabled: true
}
RowLayout {
anchors.fill: parent
anchors.margins: Theme.spacingS
spacing: Theme.spacingM
Column {
Layout.fillWidth: true
spacing: Theme.spacingXS
Row {
spacing: Theme.spacingS
DankIcon {
name: {
const mp = modelData?.mount ?? "";
if (mp === "/")
return "home";
if (mp === "/home")
return "person";
if (mp.includes("boot"))
return "memory";
if (mp.includes("media") || mp.includes("mnt"))
return "usb";
return "folder";
}
size: Theme.iconSize - 4
color: Theme.surfaceText
opacity: 0.8
}
StyledText {
text: modelData?.mount ?? ""
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: Theme.surfaceText
}
}
Row {
spacing: Theme.spacingS
StyledText {
text: modelData?.device ?? ""
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
}
StyledText {
text: modelData?.fstype ?? ""
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
}
Column {
Layout.preferredWidth: 200
spacing: Theme.spacingXS
Rectangle {
width: parent.width
height: 8
radius: 4
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
Rectangle {
width: parent.width * Math.min(1, parent.parent.parent.parent.usedPct)
height: parent.height
radius: 4
color: {
const pct = parent.parent.parent.parent.usedPct;
if (pct > 0.95)
return Theme.error;
if (pct > 0.85)
return Theme.warning;
return Theme.primary;
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
}
}
}
}
Row {
anchors.right: parent.right
spacing: Theme.spacingS
StyledText {
text: modelData?.used ?? ""
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
}
StyledText {
text: "/"
font.pixelSize: Theme.fontSizeSmall - 2
color: Theme.surfaceVariantText
}
StyledText {
text: modelData?.size ?? ""
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
}
StyledText {
Layout.preferredWidth: 50
text: modelData?.percent ?? ""
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: {
const pct = parent.parent.usedPct;
if (pct > 0.95)
return Theme.error;
if (pct > 0.85)
return Theme.warning;
return Theme.surfaceText;
}
horizontalAlignment: Text.AlignRight
}
}
}
Rectangle {
anchors.centerIn: parent
width: 300
height: 80
radius: Theme.cornerRadius
color: "transparent"
visible: DgopService.diskMounts.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
DankIcon {
name: "storage"
size: 32
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No mount points found", "empty state in disk mounts list")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,451 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Column {
function formatNetworkSpeed(bytesPerSec) {
if (bytesPerSec < 1024) {
return bytesPerSec.toFixed(0) + " B/s";
} else if (bytesPerSec < 1024 * 1024) {
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
} else if (bytesPerSec < 1024 * 1024 * 1024) {
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
} else {
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
}
}
function formatDiskSpeed(bytesPerSec) {
if (bytesPerSec < 1024 * 1024) {
return (bytesPerSec / 1024).toFixed(1) + " KB/s";
} else if (bytesPerSec < 1024 * 1024 * 1024) {
return (bytesPerSec / (1024 * 1024)).toFixed(1) + " MB/s";
} else {
return (bytesPerSec / (1024 * 1024 * 1024)).toFixed(1) + " GB/s";
}
}
anchors.fill: parent
spacing: Theme.spacingM
Component.onCompleted: {
DgopService.addRef(["cpu", "memory", "network", "disk"]);
}
Component.onDestruction: {
DgopService.removeRef(["cpu", "memory", "network", "disk"]);
}
Rectangle {
width: parent.width
height: 200
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
border.width: 1
Column {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
Row {
width: parent.width
height: 32
spacing: Theme.spacingM
StyledText {
text: "CPU"
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
width: 80
height: 24
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: `${DgopService.cpuUsage.toFixed(1)}%`
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.primary
anchors.centerIn: parent
}
}
Item {
width: parent.width - 280
height: 1
}
StyledText {
text: `${DgopService.cpuCores} cores`
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
}
DankFlickable {
clip: true
width: parent.width
height: parent.height - 40
contentHeight: coreUsageColumn.implicitHeight
Column {
id: coreUsageColumn
width: parent.width
spacing: 6
Repeater {
model: DgopService.perCoreCpuUsage
Row {
width: parent.width
height: 20
spacing: Theme.spacingS
StyledText {
text: `C${index}`
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
width: 24
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
width: parent.width - 80
height: 6
radius: 3
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: parent.width * Math.min(1, modelData / 100)
height: parent.height
radius: parent.radius
color: {
const usage = modelData;
if (usage > 80) {
return Theme.error;
}
if (usage > 60) {
return Theme.warning;
}
return Theme.primary;
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
}
}
}
}
StyledText {
text: modelData ? `${modelData.toFixed(0)}%` : "0%"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceText
width: 32
horizontalAlignment: Text.AlignRight
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}
}
}
Row {
width: parent.width
height: 80
spacing: Theme.spacingM
Rectangle {
width: (parent.width - Theme.spacingM) / 2
height: 80
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
border.width: 1
Row {
anchors.centerIn: parent
spacing: Theme.spacingM
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
StyledText {
text: I18n.tr("Memory")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: `${DgopService.formatSystemMemory(DgopService.usedMemoryKB)} / ${DgopService.formatSystemMemory(DgopService.totalMemoryKB)}`
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
width: 120
Rectangle {
width: parent.width
height: 16
radius: 8
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
Rectangle {
width: DgopService.totalMemoryKB > 0 ? parent.width * (DgopService.usedMemoryKB / DgopService.totalMemoryKB) : 0
height: parent.height
radius: parent.radius
color: {
const usage = DgopService.totalMemoryKB > 0 ? (DgopService.usedMemoryKB / DgopService.totalMemoryKB) : 0;
if (usage > 0.9) {
return Theme.error;
}
if (usage > 0.7) {
return Theme.warning;
}
return Theme.secondary;
}
Behavior on width {
NumberAnimation {
duration: Theme.mediumDuration
}
}
}
}
StyledText {
text: DgopService.totalMemoryKB > 0 ? `${((DgopService.usedMemoryKB / DgopService.totalMemoryKB) * 100).toFixed(1)}% used` : "No data"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
}
}
Rectangle {
width: (parent.width - Theme.spacingM) / 2
height: 80
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
border.width: 1
Row {
anchors.centerIn: parent
spacing: Theme.spacingM
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
StyledText {
text: I18n.tr("Swap")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: DgopService.totalSwapKB > 0 ? `${DgopService.formatSystemMemory(DgopService.usedSwapKB)} / ${DgopService.formatSystemMemory(DgopService.totalSwapKB)}` : "No swap"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: 4
width: 120
Rectangle {
width: parent.width
height: 16
radius: 8
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
Rectangle {
width: DgopService.totalSwapKB > 0 ? parent.width * (DgopService.usedSwapKB / DgopService.totalSwapKB) : 0
height: parent.height
radius: parent.radius
color: {
if (!DgopService.totalSwapKB) {
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3);
}
const usage = DgopService.usedSwapKB / DgopService.totalSwapKB;
if (usage > 0.9) {
return Theme.error;
}
if (usage > 0.7) {
return Theme.warning;
}
return Theme.info;
}
Behavior on width {
NumberAnimation {
duration: Theme.mediumDuration
}
}
}
}
StyledText {
text: DgopService.totalSwapKB > 0 ? `${((DgopService.usedSwapKB / DgopService.totalSwapKB) * 100).toFixed(1)}% used` : "N/A"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
}
}
}
Row {
width: parent.width
height: 80
spacing: Theme.spacingM
Rectangle {
width: (parent.width - Theme.spacingM) / 2
height: 80
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
border.width: 1
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Network")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
Row {
spacing: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
Row {
spacing: 4
StyledText {
text: "↓"
font.pixelSize: Theme.fontSizeSmall
color: Theme.info
}
StyledText {
text: DgopService.networkRxRate > 0 ? formatNetworkSpeed(DgopService.networkRxRate) : "0 B/s"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
Row {
spacing: 4
StyledText {
text: "↑"
font.pixelSize: Theme.fontSizeSmall
color: Theme.error
}
StyledText {
text: DgopService.networkTxRate > 0 ? formatNetworkSpeed(DgopService.networkTxRate) : "0 B/s"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
}
}
}
Rectangle {
width: (parent.width - Theme.spacingM) / 2
height: 80
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.06)
border.width: 1
Column {
anchors.centerIn: parent
spacing: Theme.spacingXS
StyledText {
text: I18n.tr("Disk")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
Row {
spacing: Theme.spacingS
anchors.horizontalCenter: parent.horizontalCenter
Row {
spacing: 4
StyledText {
text: "R"
font.pixelSize: Theme.fontSizeSmall
color: Theme.primary
}
StyledText {
text: formatDiskSpeed(DgopService.diskReadRate)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
Row {
spacing: 4
StyledText {
text: "W"
font.pixelSize: Theme.fontSizeSmall
color: Theme.warning
}
StyledText {
text: formatDiskSpeed(DgopService.diskWriteRate)
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Bold
color: Theme.surfaceText
}
}
}
}
}
}
}

View File

@@ -1,304 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
readonly property int historySize: 60
property var cpuHistory: []
property var memoryHistory: []
property var networkRxHistory: []
property var networkTxHistory: []
property var diskReadHistory: []
property var diskWriteHistory: []
function formatBytes(bytes) {
if (bytes < 1024)
return bytes.toFixed(0) + " B/s";
if (bytes < 1024 * 1024)
return (bytes / 1024).toFixed(1) + " KB/s";
if (bytes < 1024 * 1024 * 1024)
return (bytes / (1024 * 1024)).toFixed(1) + " MB/s";
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
}
function addToHistory(arr, val) {
const newArr = arr.slice();
newArr.push(val);
if (newArr.length > historySize)
newArr.shift();
return newArr;
}
function sampleData() {
cpuHistory = addToHistory(cpuHistory, DgopService.cpuUsage);
memoryHistory = addToHistory(memoryHistory, DgopService.memoryUsage);
networkRxHistory = addToHistory(networkRxHistory, DgopService.networkRxRate);
networkTxHistory = addToHistory(networkTxHistory, DgopService.networkTxRate);
diskReadHistory = addToHistory(diskReadHistory, DgopService.diskReadRate);
diskWriteHistory = addToHistory(diskWriteHistory, DgopService.diskWriteRate);
}
Component.onCompleted: {
DgopService.addRef(["cpu", "memory", "network", "disk", "diskmounts", "system"]);
}
Component.onDestruction: {
DgopService.removeRef(["cpu", "memory", "network", "disk", "diskmounts", "system"]);
}
SystemClock {
id: sampleClock
precision: SystemClock.Seconds
onDateChanged: {
if (date.getSeconds() % 1 === 0)
root.sampleData();
}
}
ColumnLayout {
anchors.fill: parent
spacing: Theme.spacingM
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: (root.height - Theme.spacingM * 2) / 2
spacing: Theme.spacingM
PerformanceCard {
Layout.fillWidth: true
Layout.fillHeight: true
title: "CPU"
icon: "memory"
value: DgopService.cpuUsage.toFixed(1) + "%"
subtitle: DgopService.cpuModel || (DgopService.cpuCores + " cores")
accentColor: Theme.primary
history: root.cpuHistory
maxValue: 100
showSecondary: false
extraInfo: DgopService.cpuTemperature > 0 ? (DgopService.cpuTemperature.toFixed(0) + "°C") : ""
extraInfoColor: DgopService.cpuTemperature > 80 ? Theme.error : (DgopService.cpuTemperature > 60 ? Theme.warning : Theme.surfaceVariantText)
}
PerformanceCard {
Layout.fillWidth: true
Layout.fillHeight: true
title: I18n.tr("Memory")
icon: "sd_card"
value: DgopService.memoryUsage.toFixed(1) + "%"
subtitle: DgopService.formatSystemMemory(DgopService.usedMemoryKB) + " / " + DgopService.formatSystemMemory(DgopService.totalMemoryKB)
accentColor: Theme.secondary
history: root.memoryHistory
maxValue: 100
showSecondary: false
extraInfo: DgopService.totalSwapKB > 0 ? ("Swap: " + DgopService.formatSystemMemory(DgopService.usedSwapKB)) : ""
extraInfoColor: Theme.surfaceVariantText
}
}
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: (root.height - Theme.spacingM * 2) / 2
spacing: Theme.spacingM
PerformanceCard {
Layout.fillWidth: true
Layout.fillHeight: true
title: I18n.tr("Network")
icon: "swap_horiz"
value: "↓ " + root.formatBytes(DgopService.networkRxRate)
subtitle: "↑ " + root.formatBytes(DgopService.networkTxRate)
accentColor: Theme.info
history: root.networkRxHistory
history2: root.networkTxHistory
maxValue: 0
showSecondary: true
extraInfo: ""
extraInfoColor: Theme.surfaceVariantText
}
PerformanceCard {
Layout.fillWidth: true
Layout.fillHeight: true
title: I18n.tr("Disk")
icon: "storage"
value: "R: " + root.formatBytes(DgopService.diskReadRate)
subtitle: "W: " + root.formatBytes(DgopService.diskWriteRate)
accentColor: Theme.warning
history: root.diskReadHistory
history2: root.diskWriteHistory
maxValue: 0
showSecondary: true
extraInfo: {
const rootMount = DgopService.diskMounts.find(m => m.mountpoint === "/");
if (rootMount) {
const usedPct = ((rootMount.used || 0) / Math.max(1, rootMount.total || 1) * 100).toFixed(0);
return "/ " + usedPct + "% used";
}
return "";
}
extraInfoColor: Theme.surfaceVariantText
}
}
}
component PerformanceCard: Rectangle {
id: card
property string title: ""
property string icon: ""
property string value: ""
property string subtitle: ""
property color accentColor: Theme.primary
property var history: []
property var history2: null
property real maxValue: 100
property bool showSecondary: false
property string extraInfo: ""
property color extraInfoColor: Theme.surfaceVariantText
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.color: Theme.outlineLight
border.width: 1
Canvas {
id: graphCanvas
anchors.fill: parent
anchors.margins: 4
renderStrategy: Canvas.Cooperative
property var hist: card.history
property var hist2: card.history2
onHistChanged: requestPaint()
onHist2Changed: requestPaint()
onWidthChanged: requestPaint()
onHeightChanged: requestPaint()
onPaint: {
const ctx = getContext("2d");
ctx.reset();
ctx.clearRect(0, 0, width, height);
if (!hist || hist.length < 2)
return;
let max = card.maxValue;
if (max <= 0) {
max = 1;
for (let k = 0; k < hist.length; k++)
max = Math.max(max, hist[k]);
if (hist2) {
for (let l = 0; l < hist2.length; l++)
max = Math.max(max, hist2[l]);
}
max *= 1.1;
}
const c = card.accentColor;
const grad = ctx.createLinearGradient(0, 0, 0, height);
grad.addColorStop(0, Qt.rgba(c.r, c.g, c.b, 0.25));
grad.addColorStop(1, Qt.rgba(c.r, c.g, c.b, 0.02));
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(0, height);
for (let i = 0; i < hist.length; i++) {
const x = (width / (root.historySize - 1)) * i;
const y = height - (hist[i] / max) * height * 0.8;
ctx.lineTo(x, y);
}
ctx.lineTo((width / (root.historySize - 1)) * (hist.length - 1), height);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.8);
ctx.lineWidth = 2;
ctx.beginPath();
for (let j = 0; j < hist.length; j++) {
const px = (width / (root.historySize - 1)) * j;
const py = height - (hist[j] / max) * height * 0.8;
j === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.stroke();
if (hist2 && hist2.length >= 2 && card.showSecondary) {
ctx.strokeStyle = Qt.rgba(c.r, c.g, c.b, 0.4);
ctx.lineWidth = 1.5;
ctx.setLineDash([4, 4]);
ctx.beginPath();
for (let m = 0; m < hist2.length; m++) {
const sx = (width / (root.historySize - 1)) * m;
const sy = height - (hist2[m] / max) * height * 0.8;
m === 0 ? ctx.moveTo(sx, sy) : ctx.lineTo(sx, sy);
}
ctx.stroke();
ctx.setLineDash([]);
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
RowLayout {
Layout.fillWidth: true
spacing: Theme.spacingS
DankIcon {
name: card.icon
size: Theme.iconSize
color: card.accentColor
}
StyledText {
text: card.title
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
}
Item {
Layout.fillWidth: true
}
StyledText {
text: card.extraInfo
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: card.extraInfoColor
visible: card.extraInfo.length > 0
}
}
Item {
Layout.fillHeight: true
}
StyledText {
text: card.value
font.pixelSize: Theme.fontSizeXLarge
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: card.subtitle
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}

View File

@@ -19,10 +19,13 @@ Popup {
const menuWidth = processContextMenu.width; const menuWidth = processContextMenu.width;
const menuHeight = processContextMenu.height; const menuHeight = processContextMenu.height;
if (finalX + menuWidth > parentWidth) if (finalX + menuWidth > parentWidth) {
finalX = Math.max(0, parentWidth - menuWidth); finalX = Math.max(0, parentWidth - menuWidth);
if (finalY + menuHeight > parentHeight) }
if (finalY + menuHeight > parentHeight) {
finalY = Math.max(0, parentHeight - menuHeight); finalY = Math.max(0, parentHeight - menuHeight);
}
} }
processContextMenu.x = finalX; processContextMenu.x = finalX;
@@ -30,19 +33,25 @@ Popup {
open(); open();
} }
width: 200 width: 180
height: menuColumn.implicitHeight + Theme.spacingS * 2 height: menuColumn.implicitHeight + Theme.spacingS * 2
padding: 0 padding: 0
modal: false modal: false
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
onClosed: {
onClosed: closePolicy = Popup.CloseOnEscape closePolicy = Popup.CloseOnEscape;
onOpened: outsideClickTimer.start() }
onOpened: {
outsideClickTimer.start();
}
Timer { Timer {
id: outsideClickTimer id: outsideClickTimer
interval: 100 interval: 100
onTriggered: processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside onTriggered: {
processContextMenu.closePolicy = Popup.CloseOnEscape | Popup.CloseOnPressOutside;
}
} }
background: Rectangle { background: Rectangle {
@@ -50,6 +59,8 @@ Popup {
} }
contentItem: Rectangle { contentItem: Rectangle {
id: menuContent
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
radius: Theme.cornerRadius radius: Theme.cornerRadius
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)
@@ -57,140 +68,157 @@ Popup {
Column { Column {
id: menuColumn id: menuColumn
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
spacing: 1 spacing: 1
MenuItem { Rectangle {
text: I18n.tr("Copy PID") width: parent.width
iconName: "tag" height: 28
onClicked: { radius: Theme.cornerRadius
if (processContextMenu.processData) color: copyPidArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
Quickshell.execDetached(["dms", "cl", "copy", processContextMenu.processData.pid.toString()]);
processContextMenu.close(); StyledText {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Copy PID")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
font.weight: Font.Normal
}
MouseArea {
id: copyPidArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (processContextMenu.processData) {
Quickshell.execDetached(["dms", "cl", "copy", processContextMenu.processData.pid.toString()]);
}
processContextMenu.close();
}
} }
} }
MenuItem { Rectangle {
text: I18n.tr("Copy Name") width: parent.width
iconName: "content_copy" height: 28
onClicked: { radius: Theme.cornerRadius
if (processContextMenu.processData) { color: copyNameArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
const name = processContextMenu.processData.command || "";
Quickshell.execDetached(["dms", "cl", "copy", name]);
}
processContextMenu.close();
}
}
MenuItem { StyledText {
text: I18n.tr("Copy Full Command") anchors.left: parent.left
iconName: "code" anchors.leftMargin: Theme.spacingS
onClicked: { anchors.verticalCenter: parent.verticalCenter
if (processContextMenu.processData) { text: I18n.tr("Copy Process Name")
const fullCmd = processContextMenu.processData.fullCommand || processContextMenu.processData.command || ""; font.pixelSize: Theme.fontSizeSmall
Quickshell.execDetached(["dms", "cl", "copy", fullCmd]); color: Theme.surfaceText
font.weight: Font.Normal
}
MouseArea {
id: copyNameArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (processContextMenu.processData) {
const processName = processContextMenu.processData.displayName || processContextMenu.processData.command;
Quickshell.execDetached(["dms", "cl", "copy", processName]);
}
processContextMenu.close();
} }
processContextMenu.close();
} }
} }
Rectangle { Rectangle {
width: parent.width - Theme.spacingS * 2 width: parent.width - Theme.spacingS * 2
height: 1 height: 5
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15) color: "transparent"
Rectangle {
anchors.centerIn: parent
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
} }
MenuItem { Rectangle {
text: I18n.tr("Kill Process") width: parent.width
iconName: "close" height: 28
dangerous: true radius: Theme.cornerRadius
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
enabled: processContextMenu.processData enabled: processContextMenu.processData
onClicked: { opacity: enabled ? 1 : 0.5
if (processContextMenu.processData)
Quickshell.execDetached(["kill", processContextMenu.processData.pid.toString()]); StyledText {
processContextMenu.close(); anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Kill Process")
font.pixelSize: Theme.fontSizeSmall
color: parent.enabled ? (killArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
font.weight: Font.Normal
}
MouseArea {
id: killArea
anchors.fill: parent
hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled
onClicked: {
if (processContextMenu.processData) {
Quickshell.execDetached(["kill", processContextMenu.processData.pid.toString()]);
}
processContextMenu.close();
}
} }
} }
MenuItem { Rectangle {
text: I18n.tr("Force Kill (SIGKILL)") width: parent.width
iconName: "dangerous" height: 28
dangerous: true radius: Theme.cornerRadius
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000 enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000
onClicked: { opacity: enabled ? 1 : 0.5
if (processContextMenu.processData)
Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]); StyledText {
processContextMenu.close(); anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
text: I18n.tr("Force Kill Process")
font.pixelSize: Theme.fontSizeSmall
color: parent.enabled ? (forceKillArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
font.weight: Font.Normal
}
MouseArea {
id: forceKillArea
anchors.fill: parent
hoverEnabled: true
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: parent.enabled
onClicked: {
if (processContextMenu.processData) {
Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]);
}
processContextMenu.close();
}
} }
} }
} }
} }
component MenuItem: Rectangle {
id: menuItem
property string text: ""
property string iconName: ""
property bool dangerous: false
property bool enabled: true
signal clicked
width: parent.width
height: 32
radius: Theme.cornerRadius
color: {
if (!enabled)
return "transparent";
if (dangerous)
return menuItemArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent";
return menuItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
}
opacity: enabled ? 1 : 0.5
Row {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingS
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: menuItem.iconName
size: 16
color: {
if (!menuItem.enabled)
return Theme.surfaceVariantText;
if (menuItem.dangerous && menuItemArea.containsMouse)
return Theme.error;
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: menuItem.text
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Normal
color: {
if (!menuItem.enabled)
return Theme.surfaceVariantText;
if (menuItem.dangerous && menuItemArea.containsMouse)
return Theme.error;
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: menuItemArea
anchors.fill: parent
hoverEnabled: true
cursorShape: menuItem.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
enabled: menuItem.enabled
onClicked: menuItem.clicked()
}
}
} }

View File

@@ -0,0 +1,200 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: processItem
property var process: null
property var contextMenu: null
width: parent ? parent.width : 0
height: 40
radius: Theme.cornerRadius
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Theme.withAlpha(Theme.primary, 0)
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.primary, 0)
border.width: 1
MouseArea {
id: processMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: (mouse) => {
if (mouse.button === Qt.RightButton) {
if (process && process.pid > 0 && contextMenu) {
contextMenu.processData = process;
const globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y);
const localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos;
contextMenu.show(localPos.x, localPos.y);
}
}
}
onPressAndHold: {
if (process && process.pid > 0 && contextMenu) {
contextMenu.processData = process;
const globalPos = processMouseArea.mapToGlobal(processMouseArea.width / 2, processMouseArea.height / 2);
contextMenu.show(globalPos.x, globalPos.y);
}
}
}
Item {
anchors.fill: parent
anchors.margins: 8
DankIcon {
id: processIcon
name: DgopService.getProcessIcon(process ? process.command : "")
size: Theme.iconSize - 4
color: {
if (process && process.cpu > 80) {
return Theme.error;
}
return Theme.surfaceText;
}
opacity: 0.8
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: process ? process.displayName : ""
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: Theme.surfaceText
width: 250
elide: Text.ElideRight
anchors.left: processIcon.right
anchors.leftMargin: 8
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: cpuBadge
width: 80
height: 20
radius: Theme.cornerRadius
color: {
if (process && process.cpu > 80) {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08);
}
anchors.right: parent.right
anchors.rightMargin: 194
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: DgopService.formatCpuUsage(process ? process.cpu : 0)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: {
if (process && process.cpu > 80) {
return Theme.error;
}
return Theme.surfaceText;
}
anchors.centerIn: parent
}
}
Rectangle {
id: memoryBadge
width: 80
height: 20
radius: Theme.cornerRadius
color: {
if (process && process.memoryKB > 1024 * 1024) {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08);
}
anchors.right: parent.right
anchors.rightMargin: 102
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: DgopService.formatMemoryUsage(process ? process.memoryKB : 0)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: {
if (process && process.memoryKB > 1024 * 1024) {
return Theme.error;
}
return Theme.surfaceText;
}
anchors.centerIn: parent
}
}
StyledText {
text: process ? process.pid.toString() : ""
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
opacity: 0.7
width: 50
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
anchors.rightMargin: 40
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
id: menuButton
width: 28
height: 28
radius: Theme.cornerRadius
color: menuButtonArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : Theme.withAlpha(Theme.surfaceText, 0)
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
DankIcon {
name: "more_vert"
size: Theme.iconSize - 2
color: Theme.surfaceText
opacity: 0.6
anchors.centerIn: parent
}
MouseArea {
id: menuButtonArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if (process && process.pid > 0 && contextMenu) {
contextMenu.processData = process;
const globalPos = menuButtonArea.mapToGlobal(menuButtonArea.width / 2, menuButtonArea.height);
const localPos = contextMenu.parent ? contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos;
contextMenu.show(localPos.x, localPos.y);
}
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
}

View File

@@ -12,37 +12,30 @@ DankPopout {
property var parentWidget: null property var parentWidget: null
property var triggerScreen: null property var triggerScreen: null
property string searchText: ""
property string expandedPid: ""
function hide() { function hide() {
close(); close();
if (processContextMenu.visible) if (processContextMenu.visible) {
processContextMenu.close(); processContextMenu.close();
}
} }
function show() { function show() {
open(); open();
} }
popupWidth: 650 popupWidth: 600
popupHeight: 550 popupHeight: 600
triggerWidth: 55 triggerWidth: 55
positioning: "" positioning: ""
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
onBackgroundClicked: { onBackgroundClicked: {
if (processContextMenu.visible) if (processContextMenu.visible) {
processContextMenu.close(); processContextMenu.close();
close();
}
onShouldBeVisibleChanged: {
if (!shouldBeVisible) {
searchText = "";
expandedPid = "";
} }
close();
} }
Ref { Ref {
@@ -62,247 +55,55 @@ DankPopout {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: "transparent" color: "transparent"
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
border.width: 0
clip: true clip: true
antialiasing: true
smooth: true
focus: true focus: true
Component.onCompleted: { Component.onCompleted: {
if (processListPopout.shouldBeVisible) if (processListPopout.shouldBeVisible) {
forceActiveFocus(); forceActiveFocus();
}
processContextMenu.parent = processListContent; processContextMenu.parent = processListContent;
} }
Keys.onPressed: event => { Keys.onPressed: event => {
switch (event.key) { if (event.key === Qt.Key_Escape) {
case Qt.Key_Escape:
if (processListPopout.searchText.length > 0) {
processListPopout.searchText = "";
event.accepted = true;
return;
}
processListPopout.close(); processListPopout.close();
event.accepted = true; event.accepted = true;
return;
case Qt.Key_F:
if (event.modifiers & Qt.ControlModifier) {
searchField.forceActiveFocus();
event.accepted = true;
return;
}
break;
} }
} }
Connections { Connections {
target: processListPopout
function onShouldBeVisibleChanged() { function onShouldBeVisibleChanged() {
if (processListPopout.shouldBeVisible) { if (processListPopout.shouldBeVisible) {
Qt.callLater(() => processListContent.forceActiveFocus()); Qt.callLater(() => {
processListContent.forceActiveFocus();
});
} }
} }
target: processListPopout
} }
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingL
spacing: Theme.spacingS spacing: Theme.spacingL
RowLayout { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
spacing: Theme.spacingM height: systemOverview.height + Theme.spacingM * 2
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.width: 0
Row { SystemOverview {
spacing: Theme.spacingS id: systemOverview
DankIcon { anchors.centerIn: parent
name: "analytics" width: parent.width - Theme.spacingM * 2
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Processes")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Item {
Layout.fillWidth: true
}
DankTextField {
id: searchField
Layout.preferredWidth: Theme.fontSizeMedium * 14
Layout.preferredHeight: Theme.fontSizeMedium * 2.5
placeholderText: I18n.tr("Search...")
leftIconName: "search"
showClearButton: true
text: processListPopout.searchText
onTextChanged: processListPopout.searchText = text
}
}
Item {
id: statsContainer
Layout.fillWidth: true
Layout.preferredHeight: Math.max(leftInfo.height, gaugesRow.height) + Theme.spacingS
function compactMem(kb) {
if (kb < 1024 * 1024) {
const mb = kb / 1024;
return mb >= 100 ? mb.toFixed(0) + " MB" : mb.toFixed(1) + " MB";
}
const gb = kb / (1024 * 1024);
return gb >= 10 ? gb.toFixed(0) + " GB" : gb.toFixed(1) + " GB";
}
readonly property real gaugeSize: Theme.fontSizeMedium * 6.5
readonly property var enabledGpusWithTemp: {
if (!SessionData.enabledGpuPciIds || SessionData.enabledGpuPciIds.length === 0)
return [];
const result = [];
for (const gpu of DgopService.availableGpus) {
if (SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1 && gpu.temperature > 0)
result.push(gpu);
}
return result;
}
readonly property bool hasGpu: enabledGpusWithTemp.length > 0
Row {
id: leftInfo
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingM
Rectangle {
width: Theme.fontSizeMedium * 3
height: width
radius: Theme.cornerRadius
color: Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.1)
SystemLogo {
anchors.centerIn: parent
width: parent.width * 0.7
height: width
colorOverride: Theme.primary
}
}
Column {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingXS / 2
StyledText {
text: DgopService.hostname || "localhost"
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
}
StyledText {
text: DgopService.distribution || "Linux"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
Row {
spacing: Theme.spacingS
Row {
spacing: Theme.spacingXS
DankIcon {
name: "schedule"
size: Theme.fontSizeSmall
color: Theme.surfaceVariantText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: DgopService.shortUptime || "--"
font.pixelSize: Theme.fontSizeSmall - 1
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall - 1
color: Theme.surfaceVariantText
}
StyledText {
text: DgopService.processCount + " " + I18n.tr("procs", "short for processes")
font.pixelSize: Theme.fontSizeSmall - 1
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
}
}
Row {
id: gaugesRow
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
CircleGauge {
width: statsContainer.gaugeSize
height: statsContainer.gaugeSize
value: DgopService.cpuUsage / 100
label: DgopService.cpuUsage.toFixed(0) + "%"
sublabel: "CPU"
detail: DgopService.cpuTemperature > 0 ? (DgopService.cpuTemperature.toFixed(0) + "°") : ""
accentColor: DgopService.cpuUsage > 80 ? Theme.error : (DgopService.cpuUsage > 50 ? Theme.warning : Theme.primary)
detailColor: DgopService.cpuTemperature > 85 ? Theme.error : (DgopService.cpuTemperature > 70 ? Theme.warning : Theme.surfaceVariantText)
}
CircleGauge {
width: statsContainer.gaugeSize
height: statsContainer.gaugeSize
value: DgopService.memoryUsage / 100
label: statsContainer.compactMem(DgopService.usedMemoryKB)
sublabel: I18n.tr("Memory")
detail: DgopService.totalSwapKB > 0 ? ("+" + statsContainer.compactMem(DgopService.usedSwapKB)) : ""
accentColor: DgopService.memoryUsage > 90 ? Theme.error : (DgopService.memoryUsage > 70 ? Theme.warning : Theme.secondary)
}
CircleGauge {
width: statsContainer.gaugeSize
height: statsContainer.gaugeSize
visible: statsContainer.hasGpu
readonly property var gpu: statsContainer.enabledGpusWithTemp[0] ?? null
readonly property color vendorColor: {
const vendor = (gpu?.vendor ?? "").toLowerCase();
if (vendor.includes("nvidia"))
return Theme.success;
if (vendor.includes("amd"))
return Theme.error;
if (vendor.includes("intel"))
return Theme.info;
return Theme.info;
}
value: Math.min(1, (gpu?.temperature ?? 0) / 100)
label: (gpu?.temperature ?? 0) > 0 ? ((gpu?.temperature ?? 0).toFixed(0) + "°C") : "--"
sublabel: "GPU"
accentColor: {
const temp = gpu?.temperature ?? 0;
if (temp > 85)
return Theme.error;
if (temp > 70)
return Theme.warning;
return vendorColor;
}
}
} }
} }
@@ -311,176 +112,16 @@ DankPopout {
Layout.fillHeight: true Layout.fillHeight: true
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency) color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
clip: true border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05)
border.width: 0
ProcessesView { ProcessListView {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingS anchors.margins: Theme.spacingS
searchText: processListPopout.searchText
expandedPid: processListPopout.expandedPid
contextMenu: processContextMenu contextMenu: processContextMenu
onExpandedPidChanged: processListPopout.expandedPid = expandedPid
} }
} }
} }
} }
} }
component CircleGauge: Item {
id: gaugeRoot
property real value: 0
property string label: ""
property string sublabel: ""
property string detail: ""
property color accentColor: Theme.primary
property color detailColor: Theme.surfaceVariantText
readonly property real thickness: Math.max(4, Math.min(width, height) / 15)
readonly property real glowExtra: thickness * 1.4
readonly property real arcPadding: thickness / 1.3
readonly property real innerDiameter: width - (arcPadding + thickness + glowExtra) * 2
readonly property real maxTextWidth: innerDiameter * 0.9
readonly property real baseLabelSize: Math.round(width * 0.18)
readonly property real labelSize: Math.round(Math.min(baseLabelSize, maxTextWidth / Math.max(1, label.length * 0.65)))
readonly property real sublabelSize: Math.round(Math.min(width * 0.13, maxTextWidth / Math.max(1, sublabel.length * 0.7)))
readonly property real detailSize: Math.round(Math.min(width * 0.12, maxTextWidth / Math.max(1, detail.length * 0.65)))
property real animValue: 0
onValueChanged: animValue = Math.min(1, Math.max(0, value))
Behavior on animValue {
NumberAnimation {
duration: Theme.mediumDuration
easing.type: Easing.OutCubic
}
}
Component.onCompleted: animValue = Math.min(1, Math.max(0, value))
Canvas {
id: glowCanvas
anchors.fill: parent
onPaint: {
const ctx = getContext("2d");
ctx.reset();
const cx = width / 2;
const cy = height / 2;
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
const startAngle = -Math.PI * 0.5;
const endAngle = Math.PI * 1.5;
ctx.lineCap = "round";
if (gaugeRoot.animValue > 0) {
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, prog);
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.2);
ctx.lineWidth = gaugeRoot.thickness + gaugeRoot.glowExtra;
ctx.stroke();
}
}
Connections {
target: gaugeRoot
function onAnimValueChanged() {
glowCanvas.requestPaint();
}
function onAccentColorChanged() {
glowCanvas.requestPaint();
}
function onWidthChanged() {
glowCanvas.requestPaint();
}
function onHeightChanged() {
glowCanvas.requestPaint();
}
}
Component.onCompleted: requestPaint()
}
Canvas {
id: arcCanvas
anchors.fill: parent
onPaint: {
const ctx = getContext("2d");
ctx.reset();
const cx = width / 2;
const cy = height / 2;
const radius = (Math.min(width, height) / 2) - gaugeRoot.arcPadding;
const startAngle = -Math.PI * 0.5;
const endAngle = Math.PI * 1.5;
ctx.lineCap = "round";
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, endAngle);
ctx.strokeStyle = Qt.rgba(gaugeRoot.accentColor.r, gaugeRoot.accentColor.g, gaugeRoot.accentColor.b, 0.1);
ctx.lineWidth = gaugeRoot.thickness;
ctx.stroke();
if (gaugeRoot.animValue > 0) {
const prog = startAngle + (endAngle - startAngle) * gaugeRoot.animValue;
ctx.beginPath();
ctx.arc(cx, cy, radius, startAngle, prog);
ctx.strokeStyle = gaugeRoot.accentColor;
ctx.lineWidth = gaugeRoot.thickness;
ctx.stroke();
}
}
Connections {
target: gaugeRoot
function onAnimValueChanged() {
arcCanvas.requestPaint();
}
function onAccentColorChanged() {
arcCanvas.requestPaint();
}
function onWidthChanged() {
arcCanvas.requestPaint();
}
function onHeightChanged() {
arcCanvas.requestPaint();
}
}
Component.onCompleted: requestPaint()
}
Column {
anchors.centerIn: parent
spacing: 1
StyledText {
text: gaugeRoot.label
font.pixelSize: gaugeRoot.labelSize
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: gaugeRoot.sublabel
font.pixelSize: gaugeRoot.sublabelSize
font.weight: Font.Medium
color: gaugeRoot.accentColor
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: gaugeRoot.detail
font.pixelSize: gaugeRoot.detailSize
font.family: SettingsData.monoFontFamily
color: gaugeRoot.detailColor
anchors.horizontalCenter: parent.horizontalCenter
visible: gaugeRoot.detail.length > 0
}
}
}
} }

View File

@@ -0,0 +1,264 @@
import QtQuick
import QtQuick.Controls
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Column {
id: root
property var contextMenu: null
Component.onCompleted: {
DgopService.addRef(["processes"]);
}
Component.onDestruction: {
DgopService.removeRef(["processes"]);
}
Item {
id: columnHeaders
width: parent.width
anchors.leftMargin: 8
height: 24
Rectangle {
width: 60
height: 20
color: {
if (DgopService.currentSort === "name") {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
}
return processHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : Theme.withAlpha(Theme.surfaceText, 0);
}
radius: Theme.cornerRadius
anchors.left: parent.left
anchors.leftMargin: 0
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: I18n.tr("Process")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: DgopService.currentSort === "name" ? Font.Bold : Font.Medium
color: Theme.surfaceText
opacity: DgopService.currentSort === "name" ? 1 : 0.7
anchors.centerIn: parent
}
MouseArea {
id: processHeaderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
DgopService.setSortBy("name");
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
Rectangle {
width: 80
height: 20
color: {
if (DgopService.currentSort === "cpu") {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
}
return cpuHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : Theme.withAlpha(Theme.surfaceText, 0);
}
radius: Theme.cornerRadius
anchors.right: parent.right
anchors.rightMargin: 200
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: "CPU"
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: DgopService.currentSort === "cpu" ? Font.Bold : Font.Medium
color: Theme.surfaceText
opacity: DgopService.currentSort === "cpu" ? 1 : 0.7
anchors.centerIn: parent
}
MouseArea {
id: cpuHeaderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
DgopService.setSortBy("cpu");
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
Rectangle {
width: 80
height: 20
color: {
if (DgopService.currentSort === "memory") {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
}
return memoryHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : Theme.withAlpha(Theme.surfaceText, 0);
}
radius: Theme.cornerRadius
anchors.right: parent.right
anchors.rightMargin: 112
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: "RAM"
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: DgopService.currentSort === "memory" ? Font.Bold : Font.Medium
color: Theme.surfaceText
opacity: DgopService.currentSort === "memory" ? 1 : 0.7
anchors.centerIn: parent
}
MouseArea {
id: memoryHeaderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
DgopService.setSortBy("memory");
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
Rectangle {
width: 50
height: 20
color: {
if (DgopService.currentSort === "pid") {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
}
return pidHeaderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : Theme.withAlpha(Theme.surfaceText, 0);
}
radius: Theme.cornerRadius
anchors.right: parent.right
anchors.rightMargin: 53
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: "PID"
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: DgopService.currentSort === "pid" ? Font.Bold : Font.Medium
color: Theme.surfaceText
opacity: DgopService.currentSort === "pid" ? 1 : 0.7
horizontalAlignment: Text.AlignHCenter
anchors.centerIn: parent
}
MouseArea {
id: pidHeaderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
DgopService.setSortBy("pid");
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
Rectangle {
width: 28
height: 28
radius: Theme.cornerRadius
color: sortOrderArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) : Theme.withAlpha(Theme.surfaceText, 0)
anchors.right: parent.right
anchors.rightMargin: 8
anchors.verticalCenter: parent.verticalCenter
StyledText {
text: DgopService.sortDescending ? "↓" : "↑"
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
anchors.centerIn: parent
}
MouseArea {
// TODO: Re-implement sort order toggle
id: sortOrderArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
DankListView {
id: processListView
property string keyRoleName: "pid"
width: parent.width
height: parent.height - columnHeaders.height
clip: true
spacing: 4
model: ScriptModel {
values: DgopService.processes
objectProp: "pid"
}
delegate: ProcessListItem {
process: modelData
contextMenu: root.contextMenu
}
}
}

View File

@@ -0,0 +1,28 @@
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Modules.ProcessList
import qs.Services
ColumnLayout {
id: processesTab
property var contextMenu: null
anchors.fill: parent
spacing: Theme.spacingM
SystemOverview {
Layout.fillWidth: true
}
ProcessListView {
Layout.fillWidth: true
Layout.fillHeight: true
contextMenu: processesTab.contextMenu
}
ProcessContextMenu {
id: localContextMenu
}
}

View File

@@ -1,607 +0,0 @@
import QtQuick
import QtQuick.Layouts
import Quickshell
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
property string searchText: ""
property string expandedPid: ""
property var contextMenu: null
property bool hoveringExpandedItem: false
readonly property bool pauseUpdates: hoveringExpandedItem || (contextMenu?.visible ?? false)
property var cachedProcesses: []
onFilteredProcessesChanged: {
if (!pauseUpdates)
cachedProcesses = filteredProcesses;
}
onPauseUpdatesChanged: {
if (!pauseUpdates)
cachedProcesses = filteredProcesses;
}
readonly property var filteredProcesses: {
if (!DgopService.allProcesses || DgopService.allProcesses.length === 0)
return [];
let procs = DgopService.allProcesses.slice();
if (searchText.length > 0) {
const search = searchText.toLowerCase();
procs = procs.filter(p => {
const cmd = (p.command || "").toLowerCase();
const fullCmd = (p.fullCommand || "").toLowerCase();
const pid = p.pid.toString();
return cmd.includes(search) || fullCmd.includes(search) || pid.includes(search);
});
}
const asc = DgopService.sortAscending;
procs.sort((a, b) => {
let valueA, valueB, result;
switch (DgopService.currentSort) {
case "cpu":
valueA = a.cpu || 0;
valueB = b.cpu || 0;
result = valueB - valueA;
break;
case "memory":
valueA = a.memoryKB || 0;
valueB = b.memoryKB || 0;
result = valueB - valueA;
break;
case "name":
valueA = (a.command || "").toLowerCase();
valueB = (b.command || "").toLowerCase();
result = valueA.localeCompare(valueB);
break;
case "pid":
valueA = a.pid || 0;
valueB = b.pid || 0;
result = valueA - valueB;
break;
default:
return 0;
}
return asc ? -result : result;
});
return procs;
}
Component.onCompleted: {
DgopService.addRef(["processes", "cpu", "memory", "system"]);
cachedProcesses = filteredProcesses;
}
Component.onDestruction: {
DgopService.removeRef(["processes", "cpu", "memory", "system"]);
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.fillWidth: true
Layout.preferredHeight: 36
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: 0
SortableHeader {
Layout.fillWidth: true
Layout.minimumWidth: 200
text: I18n.tr("Name")
sortKey: "name"
currentSort: DgopService.currentSort
sortAscending: DgopService.sortAscending
onClicked: DgopService.toggleSort("name")
alignment: Text.AlignLeft
}
SortableHeader {
Layout.preferredWidth: 100
text: "CPU"
sortKey: "cpu"
currentSort: DgopService.currentSort
sortAscending: DgopService.sortAscending
onClicked: DgopService.toggleSort("cpu")
}
SortableHeader {
Layout.preferredWidth: 100
text: I18n.tr("Memory")
sortKey: "memory"
currentSort: DgopService.currentSort
sortAscending: DgopService.sortAscending
onClicked: DgopService.toggleSort("memory")
}
SortableHeader {
Layout.preferredWidth: 80
text: "PID"
sortKey: "pid"
currentSort: DgopService.currentSort
sortAscending: DgopService.sortAscending
onClicked: DgopService.toggleSort("pid")
}
Item {
Layout.preferredWidth: 40
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Theme.outlineLight
}
DankListView {
id: processListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 2
model: ScriptModel {
values: root.cachedProcesses
objectProp: "pid"
}
delegate: ProcessItem {
required property var modelData
width: processListView.width
process: modelData
isExpanded: root.expandedPid === (modelData?.pid ?? -1).toString()
contextMenu: root.contextMenu
onToggleExpand: {
const pidStr = (modelData?.pid ?? -1).toString();
root.expandedPid = (root.expandedPid === pidStr) ? "" : pidStr;
}
onHoveringExpandedChanged: {
if (hoveringExpanded)
root.hoveringExpandedItem = true;
else
Qt.callLater(() => {
root.hoveringExpandedItem = false;
});
}
}
Rectangle {
anchors.centerIn: parent
width: 300
height: 100
radius: Theme.cornerRadius
color: "transparent"
visible: root.cachedProcesses.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
DankIcon {
name: root.searchText.length > 0 ? "search_off" : "hourglass_empty"
size: 32
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No matching processes", "empty state in process list")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
visible: root.searchText.length > 0
}
}
}
}
}
component SortableHeader: Item {
id: headerItem
property string text: ""
property string sortKey: ""
property string currentSort: ""
property bool sortAscending: false
property int alignment: Text.AlignHCenter
signal clicked
readonly property bool isActive: sortKey === currentSort
height: 36
Rectangle {
anchors.fill: parent
anchors.margins: 2
radius: Theme.cornerRadius
color: headerItem.isActive ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : (headerMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06) : "transparent")
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: 4
Item {
Layout.fillWidth: headerItem.alignment === Text.AlignLeft
visible: headerItem.alignment !== Text.AlignLeft
}
StyledText {
text: headerItem.text
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: headerItem.isActive ? Font.Bold : Font.Medium
color: headerItem.isActive ? Theme.primary : Theme.surfaceText
opacity: headerItem.isActive ? 1 : 0.8
}
DankIcon {
name: headerItem.sortAscending ? "arrow_upward" : "arrow_downward"
size: Theme.fontSizeSmall
color: Theme.primary
visible: headerItem.isActive
}
Item {
Layout.fillWidth: headerItem.alignment !== Text.AlignLeft
visible: headerItem.alignment === Text.AlignLeft
}
}
MouseArea {
id: headerMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: headerItem.clicked()
}
}
component ProcessItem: Rectangle {
id: processItemRoot
property var process: null
property bool isExpanded: false
property var contextMenu: null
readonly property bool hoveringExpanded: (isExpanded && processMouseArea.containsMouse) || copyMouseArea.containsMouse
signal toggleExpand
readonly property int processPid: process?.pid ?? 0
readonly property real processCpu: process?.cpu ?? 0
readonly property int processMemKB: process?.memoryKB ?? 0
readonly property string processCmd: process?.command ?? ""
readonly property string processFullCmd: process?.fullCommand ?? processCmd
height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44
radius: Theme.cornerRadius
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
border.width: 1
clip: true
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
MouseArea {
id: processMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: mouse => {
if (mouse.button === Qt.RightButton) {
if (processItemRoot.processPid > 0 && processItemRoot.contextMenu) {
processItemRoot.contextMenu.processData = processItemRoot.process;
const globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y);
const localPos = processItemRoot.contextMenu.parent ? processItemRoot.contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos;
processItemRoot.contextMenu.show(localPos.x, localPos.y);
}
return;
}
processItemRoot.toggleExpand();
}
}
Column {
anchors.fill: parent
spacing: 0
Item {
width: parent.width
height: 44
RowLayout {
anchors.fill: parent
anchors.leftMargin: Theme.spacingS
anchors.rightMargin: Theme.spacingS
spacing: 0
Item {
Layout.fillWidth: true
Layout.minimumWidth: 200
height: parent.height
Row {
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
DankIcon {
name: DgopService.getProcessIcon(processItemRoot.processCmd)
size: Theme.iconSize - 4
color: {
if (processItemRoot.processCpu > 80)
return Theme.error;
if (processItemRoot.processCpu > 50)
return Theme.warning;
return Theme.surfaceText;
}
opacity: 0.8
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: processItemRoot.processCmd
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: Theme.surfaceText
elide: Text.ElideRight
width: Math.min(implicitWidth, 280)
anchors.verticalCenter: parent.verticalCenter
}
}
}
Item {
Layout.preferredWidth: 100
height: parent.height
Rectangle {
anchors.centerIn: parent
width: 70
height: 24
radius: Theme.cornerRadius
color: {
if (processItemRoot.processCpu > 80)
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15);
if (processItemRoot.processCpu > 50)
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06);
}
StyledText {
anchors.centerIn: parent
text: DgopService.formatCpuUsage(processItemRoot.processCpu)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: {
if (processItemRoot.processCpu > 80)
return Theme.error;
if (processItemRoot.processCpu > 50)
return Theme.warning;
return Theme.surfaceText;
}
}
}
}
Item {
Layout.preferredWidth: 100
height: parent.height
Rectangle {
anchors.centerIn: parent
width: 70
height: 24
radius: Theme.cornerRadius
color: {
if (processItemRoot.processMemKB > 2 * 1024 * 1024)
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.15);
if (processItemRoot.processMemKB > 1024 * 1024)
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12);
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.06);
}
StyledText {
anchors.centerIn: parent
text: DgopService.formatMemoryUsage(processItemRoot.processMemKB)
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: {
if (processItemRoot.processMemKB > 2 * 1024 * 1024)
return Theme.error;
if (processItemRoot.processMemKB > 1024 * 1024)
return Theme.warning;
return Theme.surfaceText;
}
}
}
}
Item {
Layout.preferredWidth: 80
height: parent.height
StyledText {
anchors.centerIn: parent
text: processItemRoot.processPid > 0 ? processItemRoot.processPid.toString() : ""
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
}
}
Item {
Layout.preferredWidth: 40
height: parent.height
DankIcon {
anchors.centerIn: parent
name: processItemRoot.isExpanded ? "expand_less" : "expand_more"
size: Theme.iconSize - 4
color: Theme.surfaceVariantText
}
}
}
}
Rectangle {
id: expandedRect
width: parent.width - Theme.spacingM * 2
height: processItemRoot.isExpanded ? (expandedContent.implicitHeight + Theme.spacingS * 2) : 0
anchors.horizontalCenter: parent.horizontalCenter
radius: Theme.cornerRadius - 2
color: Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, 0.6)
clip: true
visible: processItemRoot.isExpanded
Behavior on height {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Column {
id: expandedContent
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingS
spacing: Theme.spacingXS
RowLayout {
width: parent.width
spacing: Theme.spacingS
StyledText {
id: cmdLabel
text: I18n.tr("Full Command:", "process detail label")
font.pixelSize: Theme.fontSizeSmall - 2
font.weight: Font.Bold
color: Theme.surfaceVariantText
Layout.alignment: Qt.AlignVCenter
}
StyledText {
id: cmdText
text: processItemRoot.processFullCmd
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
elide: Text.ElideMiddle
}
Rectangle {
id: copyBtn
Layout.preferredWidth: 24
Layout.preferredHeight: 24
Layout.alignment: Qt.AlignVCenter
radius: Theme.cornerRadius - 2
color: copyMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15) : "transparent"
DankIcon {
anchors.centerIn: parent
name: "content_copy"
size: 14
color: copyMouseArea.containsMouse ? Theme.primary : Theme.surfaceVariantText
}
MouseArea {
id: copyMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Quickshell.execDetached(["dms", "cl", "copy", processItemRoot.processFullCmd]);
}
}
}
}
Row {
spacing: Theme.spacingL
Row {
spacing: Theme.spacingXS
StyledText {
text: "PPID:"
font.pixelSize: Theme.fontSizeSmall - 2
font.weight: Font.Bold
color: Theme.surfaceVariantText
}
StyledText {
text: (processItemRoot.process?.ppid ?? 0) > 0 ? processItemRoot.process.ppid.toString() : "--"
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
}
}
Row {
spacing: Theme.spacingXS
StyledText {
text: "Mem:"
font.pixelSize: Theme.fontSizeSmall - 2
font.weight: Font.Bold
color: Theme.surfaceVariantText
}
StyledText {
text: (processItemRoot.process?.memoryPercent ?? 0).toFixed(1) + "%"
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,435 @@
import QtQuick
import QtQuick.Controls
import qs.Common
import qs.Services
import qs.Widgets
Row {
width: parent.width
spacing: Theme.spacingM
Component.onCompleted: {
DgopService.addRef(["cpu", "memory", "system"]);
}
Component.onDestruction: {
DgopService.removeRef(["cpu", "memory", "system"]);
}
Rectangle {
width: (parent.width - Theme.spacingM * 2) / 3
height: 80
radius: Theme.cornerRadius
color: {
if (DgopService.sortBy === "cpu") {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
} else if (cpuCardMouseArea.containsMouse) {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12);
} else {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08);
}
}
border.color: DgopService.sortBy === "cpu" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2)
border.width: DgopService.sortBy === "cpu" ? 2 : 1
MouseArea {
id: cpuCardMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
DgopService.setSortBy("cpu");
}
}
Column {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: I18n.tr("CPU")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: DgopService.sortBy === "cpu" ? Theme.primary : Theme.secondary
opacity: DgopService.sortBy === "cpu" ? 1 : 0.8
}
Row {
spacing: Theme.spacingS
StyledText {
text: {
if (DgopService.cpuUsage === undefined || DgopService.cpuUsage === null) {
return "--%";
}
return DgopService.cpuUsage.toFixed(1) + "%";
}
font.pixelSize: Theme.fontSizeLarge
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
width: 1
height: 20
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (DgopService.cpuTemperature === undefined || DgopService.cpuTemperature === null || DgopService.cpuTemperature <= 0) {
return "--°";
}
return Math.round(DgopService.cpuTemperature) + "°";
}
font.pixelSize: Theme.fontSizeMedium
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: {
if (DgopService.cpuTemperature > 80) {
return Theme.error;
}
if (DgopService.cpuTemperature > 60) {
return Theme.warning;
}
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: `${DgopService.cpuCores} cores`
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
opacity: 0.7
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
Rectangle {
width: (parent.width - Theme.spacingM * 2) / 3
height: 80
radius: Theme.cornerRadius
color: {
if (DgopService.sortBy === "memory") {
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16);
} else if (memoryCardMouseArea.containsMouse) {
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.12);
} else {
return Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.08);
}
}
border.color: DgopService.sortBy === "memory" ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.4) : Qt.rgba(Theme.secondary.r, Theme.secondary.g, Theme.secondary.b, 0.2)
border.width: DgopService.sortBy === "memory" ? 2 : 1
MouseArea {
id: memoryCardMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
DgopService.setSortBy("memory");
}
}
Column {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: I18n.tr("Memory")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: DgopService.sortBy === "memory" ? Theme.primary : Theme.secondary
opacity: DgopService.sortBy === "memory" ? 1 : 0.8
}
Row {
spacing: Theme.spacingS
StyledText {
text: DgopService.formatSystemMemory(DgopService.usedMemoryKB)
font.pixelSize: Theme.fontSizeLarge
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Rectangle {
width: 1
height: 20
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.3)
anchors.verticalCenter: parent.verticalCenter
visible: DgopService.totalSwapKB > 0
}
StyledText {
text: DgopService.totalSwapKB > 0 ? DgopService.formatSystemMemory(DgopService.usedSwapKB) : ""
font.pixelSize: Theme.fontSizeMedium
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
visible: DgopService.totalSwapKB > 0
}
}
StyledText {
text: {
if (DgopService.totalSwapKB > 0) {
return "of " + DgopService.formatSystemMemory(DgopService.totalMemoryKB) + " + swap";
}
return "of " + DgopService.formatSystemMemory(DgopService.totalMemoryKB);
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
opacity: 0.7
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
Behavior on border.color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
Rectangle {
width: (parent.width - Theme.spacingM * 2) / 3
height: 80
radius: Theme.cornerRadius
color: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1) {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.16);
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
}
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const vendor = gpu.vendor.toLowerCase();
if (vendor.includes("nvidia")) {
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1) {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.2);
} else {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12);
}
} else if (vendor.includes("amd")) {
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1) {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2);
} else {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12);
}
} else if (vendor.includes("intel")) {
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1) {
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.2);
} else {
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.12);
}
}
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1) {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.16);
} else {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
}
}
border.color: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const vendor = gpu.vendor.toLowerCase();
if (vendor.includes("nvidia")) {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3);
} else if (vendor.includes("amd")) {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
} else if (vendor.includes("intel")) {
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.3);
}
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
}
border.width: 1
MouseArea {
id: gpuCardMouseArea
anchors.fill: parent
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
cursorShape: DgopService.availableGpus.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: (mouse) => {
if (mouse.button === Qt.LeftButton) {
if (DgopService.availableGpus.length > 1) {
const nextIndex = (SessionData.selectedGpuIndex + 1) % DgopService.availableGpus.length;
SessionData.setSelectedGpuIndex(nextIndex);
}
} else if (mouse.button === Qt.RightButton) {
gpuContextMenu.popup();
}
}
}
Column {
anchors.left: parent.left
anchors.leftMargin: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
spacing: 2
StyledText {
text: I18n.tr("GPU")
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.secondary
opacity: 0.8
}
StyledText {
text: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return "No GPU";
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
// Check if temperature monitoring is enabled for this GPU
const tempEnabled = SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1;
const temp = gpu.temperature;
const hasTemp = tempEnabled && temp !== undefined && temp !== null && temp !== 0;
if (hasTemp) {
return Math.round(temp) + "°";
} else {
return gpu.vendor;
}
}
font.pixelSize: Theme.fontSizeLarge
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return Theme.surfaceText;
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const tempEnabled = SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1;
const temp = gpu.temperature || 0;
if (tempEnabled && temp > 80) {
return Theme.error;
}
if (tempEnabled && temp > 60) {
return Theme.warning;
}
return Theme.surfaceText;
}
}
StyledText {
text: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return "No GPUs detected";
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const tempEnabled = SessionData.enabledGpuPciIds && SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1;
const temp = gpu.temperature;
const hasTemp = tempEnabled && temp !== undefined && temp !== null && temp !== 0;
if (hasTemp) {
return gpu.vendor + " " + gpu.displayName;
} else {
return gpu.displayName;
}
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
opacity: 0.7
width: parent.parent.width - Theme.spacingM * 2
elide: Text.ElideRight
maximumLineCount: 1
}
}
Menu {
id: gpuContextMenu
MenuItem {
text: I18n.tr("Enable GPU Temperature")
checkable: true
checked: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return false;
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
if (!gpu.pciId) {
return false;
}
return SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.indexOf(gpu.pciId) !== -1 : false;
}
onTriggered: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return;
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
if (!gpu.pciId) {
return;
}
const enabledIds = SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.slice() : [];
const index = enabledIds.indexOf(gpu.pciId);
if (checked && index === -1) {
enabledIds.push(gpu.pciId);
DgopService.addGpuPciId(gpu.pciId);
} else if (!checked && index !== -1) {
enabledIds.splice(index, 1);
DgopService.removeGpuPciId(gpu.pciId);
}
SessionData.setEnabledGpuPciIds(enabledIds);
}
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}

View File

@@ -0,0 +1,591 @@
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
DankFlickable {
anchors.fill: parent
contentHeight: systemColumn.implicitHeight
clip: true
Component.onCompleted: {
DgopService.addRef(["system", "diskmounts"]);
}
Component.onDestruction: {
DgopService.removeRef(["system", "diskmounts"]);
}
Column {
id: systemColumn
width: parent.width
spacing: Theme.spacingM
Rectangle {
width: parent.width
height: systemInfoColumn.implicitHeight + 2 * Theme.spacingL
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
id: systemInfoColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingL
spacing: Theme.spacingL
Row {
width: parent.width
spacing: Theme.spacingL
SystemLogo {
width: 80
height: 80
}
Column {
width: parent.width - 80 - Theme.spacingL
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
StyledText {
text: DgopService.hostname
font.pixelSize: Theme.fontSizeXLarge
font.family: SettingsData.monoFontFamily
font.weight: Font.Light
color: Theme.surfaceText
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: `${DgopService.distribution} ${DgopService.architecture} ${DgopService.kernelVersion}`
font.pixelSize: Theme.fontSizeMedium
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
verticalAlignment: Text.AlignVCenter
}
StyledText {
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)
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: `Load: ${DgopService.loadAverage} ${DgopService.processCount} processes, ${DgopService.threadCount} threads`
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
verticalAlignment: Text.AlignVCenter
}
}
}
Rectangle {
width: parent.width
height: 1
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
}
Row {
width: parent.width
spacing: Theme.spacingXL
Rectangle {
width: (parent.width - Theme.spacingXL) / 2
height: hardwareColumn.implicitHeight + Theme.spacingL
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 1
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
Column {
id: hardwareColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingS
DankIcon {
name: "memory"
size: Theme.iconSizeSmall
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("System")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: DgopService.cpuModel
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: DgopService.motherboard
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: `BIOS ${DgopService.biosVersion}`
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: `${DgopService.formatSystemMemory(DgopService.totalMemoryKB)} RAM`
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
}
Rectangle {
width: (parent.width - Theme.spacingXL) / 2
height: gpuColumn.implicitHeight + Theme.spacingL
radius: Theme.cornerRadius
color: {
const baseColor = Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
const hoverColor = Qt.rgba(Theme.surfaceContainerHigh.r, Theme.surfaceContainerHigh.g, Theme.surfaceContainerHigh.b, Theme.popupTransparency * 1.5);
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1 ? hoverColor : baseColor;
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const vendor = gpu.fullName.split(' ')[0].toLowerCase();
let tintColor;
if (vendor.includes("nvidia")) {
tintColor = Theme.success;
} else if (vendor.includes("amd")) {
tintColor = Theme.error;
} else if (vendor.includes("intel")) {
tintColor = Theme.info;
} else {
return gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1 ? hoverColor : baseColor;
}
if (gpuCardMouseArea.containsMouse && DgopService.availableGpus.length > 1) {
return Qt.rgba((hoverColor.r + tintColor.r * 0.1) / 1.1, (hoverColor.g + tintColor.g * 0.1) / 1.1, (hoverColor.b + tintColor.b * 0.1) / 1.1, 0.6);
} else {
return Qt.rgba((baseColor.r + tintColor.r * 0.08) / 1.08, (baseColor.g + tintColor.g * 0.08) / 1.08, (baseColor.b + tintColor.b * 0.08) / 1.08, 0.4);
}
}
border.width: 1
border.color: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1);
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const vendor = gpu.fullName.split(' ')[0].toLowerCase();
if (vendor.includes("nvidia")) {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3);
} else if (vendor.includes("amd")) {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
} else if (vendor.includes("intel")) {
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.3);
}
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1);
}
MouseArea {
id: gpuCardMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: DgopService.availableGpus.length > 1 ? Qt.PointingHandCursor : Qt.ArrowCursor
onClicked: {
if (DgopService.availableGpus.length > 1) {
const nextIndex = (SessionData.selectedGpuIndex + 1) % DgopService.availableGpus.length;
SessionData.setSelectedGpuIndex(nextIndex);
}
}
}
Column {
id: gpuColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingM
spacing: Theme.spacingXS
Row {
width: parent.width
spacing: Theme.spacingS
DankIcon {
name: "auto_awesome_mosaic"
size: Theme.iconSizeSmall
color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: "GPU"
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter
}
}
StyledText {
text: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return "No GPUs detected";
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
return gpu.fullName;
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Medium
color: Theme.surfaceText
width: parent.width
elide: Text.ElideRight
maximumLineCount: 1
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return "Device: N/A";
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
return `Device: ${gpu.pciId}`;
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
textFormat: Text.RichText
}
StyledText {
text: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return "Driver: N/A";
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
return `Driver: ${gpu.driver}`;
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return "Temp: --°";
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const temp = gpu.temperature;
return `Temp: ${(temp === undefined || temp === null || temp === 0) ? '--°' : `${Math.round(temp)}°C`}`;
}
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: {
if (!DgopService.availableGpus || DgopService.availableGpus.length === 0) {
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
}
const gpu = DgopService.availableGpus[Math.min(SessionData.selectedGpuIndex, DgopService.availableGpus.length - 1)];
const temp = gpu.temperature || 0;
if (temp > 80) {
return Theme.error;
}
if (temp > 60) {
return Theme.warning;
}
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7);
}
width: parent.width
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
}
}
Rectangle {
width: parent.width
height: storageColumn.implicitHeight + 2 * Theme.spacingL
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
border.width: 0
Column {
id: storageColumn
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: Theme.spacingL
spacing: Theme.spacingS
Row {
width: parent.width
spacing: Theme.spacingS
DankIcon {
name: "storage"
size: Theme.iconSize
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Storage & Disks")
font.pixelSize: Theme.fontSizeLarge
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Column {
width: parent.width
spacing: 2
Row {
width: parent.width
height: 24
spacing: Theme.spacingS
StyledText {
text: I18n.tr("Device")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.25
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: I18n.tr("Mount")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.2
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: I18n.tr("Size")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: I18n.tr("Used")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: I18n.tr("Available")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: I18n.tr("Use%")
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
font.weight: Font.Bold
color: Theme.surfaceText
width: parent.width * 0.1
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
}
}
Repeater {
id: diskMountRepeater
model: DgopService.diskMounts
Rectangle {
width: parent.width
height: 24
radius: Theme.cornerRadius
color: diskMouseArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.04) : "transparent"
MouseArea {
id: diskMouseArea
anchors.fill: parent
hoverEnabled: true
}
Row {
anchors.fill: parent
spacing: Theme.spacingS
StyledText {
text: modelData.device
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.25
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.mount
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.2
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.size
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.used
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.avail
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
width: parent.width * 0.15
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
StyledText {
text: modelData.percent
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: {
const percent = parseInt(modelData.percent);
if (percent > 90) {
return Theme.error;
}
if (percent > 75) {
return Theme.warning;
}
return Theme.surfaceText;
}
width: parent.width * 0.1
elide: Text.ElideRight
anchors.verticalCenter: parent.verticalCenter
verticalAlignment: Text.AlignVCenter
}
}
}
}
}
}
}
}
}

View File

@@ -1,386 +0,0 @@
import QtQuick
import QtQuick.Layouts
import qs.Common
import qs.Services
import qs.Widgets
Item {
id: root
Component.onCompleted: {
DgopService.addRef(["system", "cpu"]);
}
Component.onDestruction: {
DgopService.removeRef(["system", "cpu"]);
}
ColumnLayout {
anchors.fill: parent
spacing: Theme.spacingM
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: systemInfoColumn.implicitHeight + Theme.spacingM * 2
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
ColumnLayout {
id: systemInfoColumn
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
spacing: Theme.spacingS
DankIcon {
name: "computer"
size: Theme.iconSize
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("System Information", "system info header in system monitor")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
GridLayout {
Layout.fillWidth: true
columns: 2
rowSpacing: Theme.spacingS
columnSpacing: Theme.spacingXL
InfoRow {
label: I18n.tr("Hostname", "system info label")
value: DgopService.hostname || "--"
}
InfoRow {
label: I18n.tr("Distribution", "system info label")
value: DgopService.distribution || "--"
}
InfoRow {
label: I18n.tr("Kernel", "system info label")
value: DgopService.kernelVersion || "--"
}
InfoRow {
label: I18n.tr("Architecture", "system info label")
value: DgopService.architecture || "--"
}
InfoRow {
label: I18n.tr("CPU")
value: DgopService.cpuModel || ("" + DgopService.cpuCores + " cores")
}
InfoRow {
label: I18n.tr("Uptime")
value: DgopService.uptime || "--"
}
InfoRow {
label: I18n.tr("Load Average", "system info label")
value: DgopService.loadAverage || "--"
}
InfoRow {
label: I18n.tr("Processes")
value: DgopService.processCount > 0 ? DgopService.processCount.toString() : "--"
}
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
radius: Theme.cornerRadius
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
ColumnLayout {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
Row {
spacing: Theme.spacingS
DankIcon {
name: "developer_board"
size: Theme.iconSize
color: Theme.secondary
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("GPU Monitoring", "gpu section header in system monitor")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Bold
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: Theme.outlineLight
}
DankListView {
id: gpuListView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
spacing: 8
model: DgopService.availableGpus
delegate: Rectangle {
required property var modelData
required property int index
width: gpuListView.width
height: 80
radius: Theme.cornerRadius
color: {
const vendor = (modelData?.vendor ?? "").toLowerCase();
if (vendor.includes("nvidia"))
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.08);
if (vendor.includes("amd"))
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.08);
if (vendor.includes("intel"))
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.08);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08);
}
border.color: {
const vendor = (modelData?.vendor ?? "").toLowerCase();
if (vendor.includes("nvidia"))
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.2);
if (vendor.includes("amd"))
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2);
if (vendor.includes("intel"))
return Qt.rgba(Theme.info.r, Theme.info.g, Theme.info.b, 0.2);
return Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.2);
}
border.width: 1
readonly property bool tempEnabled: {
const pciId = modelData?.pciId ?? "";
if (!pciId)
return false;
return SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.indexOf(pciId) !== -1 : false;
}
RowLayout {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingM
DankIcon {
name: "developer_board"
size: Theme.iconSize + 4
color: {
const vendor = (modelData?.vendor ?? "").toLowerCase();
if (vendor.includes("nvidia"))
return Theme.success;
if (vendor.includes("amd"))
return Theme.error;
if (vendor.includes("intel"))
return Theme.info;
return Theme.surfaceVariantText;
}
}
Column {
Layout.fillWidth: true
spacing: Theme.spacingXS
StyledText {
text: modelData?.displayName ?? I18n.tr("Unknown GPU", "fallback gpu name")
font.pixelSize: Theme.fontSizeMedium
font.weight: Font.Bold
color: Theme.surfaceText
}
Row {
spacing: Theme.spacingS
StyledText {
text: modelData?.vendor ?? ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: "•"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: (modelData?.driver ?? "").length > 0
}
StyledText {
text: modelData?.driver ?? ""
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
visible: (modelData?.driver ?? "").length > 0
}
}
StyledText {
text: modelData?.pciId ?? ""
font.pixelSize: Theme.fontSizeSmall - 2
font.family: SettingsData.monoFontFamily
color: Theme.surfaceVariantText
opacity: 0.7
}
}
Rectangle {
width: 70
height: 32
radius: Theme.cornerRadius
color: parent.parent.tempEnabled ? Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.3) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.15)
border.color: tempMouseArea.containsMouse ? Theme.outline : "transparent"
border.width: 1
Row {
id: tempRow
anchors.centerIn: parent
spacing: Theme.spacingXS
DankIcon {
name: "thermostat"
size: 16
color: {
if (!parent.parent.parent.parent.tempEnabled)
return Theme.surfaceVariantText;
const temp = modelData?.temperature ?? 0;
if (temp > 85)
return Theme.error;
if (temp > 70)
return Theme.warning;
return Theme.surfaceText;
}
opacity: parent.parent.parent.parent.tempEnabled ? 1 : 0.5
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: {
if (!parent.parent.parent.parent.tempEnabled)
return I18n.tr("Off");
const temp = modelData?.temperature ?? 0;
return temp > 0 ? (temp.toFixed(0) + "°C") : "--";
}
font.pixelSize: Theme.fontSizeSmall
font.family: parent.parent.parent.parent.tempEnabled ? SettingsData.monoFontFamily : ""
font.weight: parent.parent.parent.parent.tempEnabled ? Font.Bold : Font.Normal
color: {
if (!parent.parent.parent.parent.tempEnabled)
return Theme.surfaceVariantText;
const temp = modelData?.temperature ?? 0;
if (temp > 85)
return Theme.error;
if (temp > 70)
return Theme.warning;
return Theme.surfaceText;
}
anchors.verticalCenter: parent.verticalCenter
}
}
MouseArea {
id: tempMouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
const pciId = modelData?.pciId;
if (!pciId)
return;
const enabledIds = SessionData.enabledGpuPciIds ? SessionData.enabledGpuPciIds.slice() : [];
const idx = enabledIds.indexOf(pciId);
const wasEnabled = idx !== -1;
if (!wasEnabled) {
enabledIds.push(pciId);
DgopService.addGpuPciId(pciId);
} else {
enabledIds.splice(idx, 1);
DgopService.removeGpuPciId(pciId);
}
SessionData.setEnabledGpuPciIds(enabledIds);
}
}
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
}
}
}
}
}
Rectangle {
anchors.centerIn: parent
width: 300
height: 100
radius: Theme.cornerRadius
color: "transparent"
visible: DgopService.availableGpus.length === 0
Column {
anchors.centerIn: parent
spacing: Theme.spacingM
DankIcon {
name: "developer_board_off"
size: 32
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
StyledText {
text: I18n.tr("No GPUs detected", "empty state in gpu list")
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceVariantText
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
}
}
}
}
component InfoRow: RowLayout {
property string label: ""
property string value: ""
Layout.fillWidth: true
spacing: Theme.spacingS
StyledText {
text: label + ":"
font.pixelSize: Theme.fontSizeSmall
font.weight: Font.Medium
color: Theme.surfaceVariantText
Layout.preferredWidth: 100
}
StyledText {
text: value
font.pixelSize: Theme.fontSizeSmall
font.family: SettingsData.monoFontFamily
color: Theme.surfaceText
Layout.fillWidth: true
elide: Text.ElideRight
}
}
}

View File

@@ -177,11 +177,10 @@ Item {
StyledText { StyledText {
text: { text: {
if (!SystemUpdateService.shellVersion && !DMSService.cliVersion) if (!SystemUpdateService.shellVersion)
return "dms"; return "dms";
let version = SystemUpdateService.shellVersion || ""; let version = SystemUpdateService.shellVersion;
let cliVersion = DMSService.cliVersion || "";
// Debian/Ubuntu/OpenSUSE git format: 1.0.3+git2264.c5c5ce84 // Debian/Ubuntu/OpenSUSE git format: 1.0.3+git2264.c5c5ce84
let match = version.match(/^([\d.]+)\+git(\d+)\./); let match = version.match(/^([\d.]+)\+git(\d+)\./);
@@ -192,25 +191,7 @@ Item {
// Fedora COPR git format: 0.0.git.2267.d430cae9 // Fedora COPR git format: 0.0.git.2267.d430cae9
match = version.match(/^[\d.]+\.git\.(\d+)\./); match = version.match(/^[\d.]+\.git\.(\d+)\./);
if (match) { if (match) {
function extractBaseVersion(value) { return `dms (git) v1.0.3-${match[1]}`;
if (!value)
return "";
let baseMatch = value.match(/(\d+\.\d+\.\d+)/);
if (baseMatch)
return baseMatch[1];
baseMatch = value.match(/(\d+\.\d+)/);
if (baseMatch)
return baseMatch[1];
return "";
}
let baseVersion = extractBaseVersion(cliVersion);
if (!baseVersion)
baseVersion = extractBaseVersion(SystemUpdateService.semverVersion);
if (baseVersion) {
return `dms (git) v${baseVersion}-${match[1]}`;
}
return `dms (git) v${match[1]}`;
} }
// Stable release format: 1.0.3 // Stable release format: 1.0.3
@@ -219,18 +200,6 @@ Item {
return `dms v${match[1]}`; return `dms v${match[1]}`;
} }
if (!version && cliVersion) {
match = cliVersion.match(/^([\d.]+)\+git(\d+)\./);
if (match) {
return `dms (git) v${match[1]}-${match[2]}`;
}
match = cliVersion.match(/^([\d.]+)$/);
if (match) {
return `dms v${match[1]}`;
}
return `dms ${cliVersion}`;
}
return `dms ${version}`; return `dms ${version}`;
} }
font.pixelSize: Theme.fontSizeXLarge font.pixelSize: Theme.fontSizeXLarge

View File

@@ -125,33 +125,6 @@ Item {
} }
] ]
readonly property var maxPinnedOptions: [
{
text: "5",
value: 5
},
{
text: "10",
value: 10
},
{
text: "15",
value: 15
},
{
text: "25",
value: 25
},
{
text: "50",
value: 50
},
{
text: "100",
value: 100
}
]
function getMaxHistoryText(value) { function getMaxHistoryText(value) {
if (value <= 0) if (value <= 0)
return "∞"; return "∞";
@@ -179,14 +152,6 @@ Item {
return value + " " + I18n.tr("days"); return value + " " + I18n.tr("days");
} }
function getMaxPinnedText(value) {
for (let opt of maxPinnedOptions) {
if (opt.value === value)
return opt.text;
}
return value.toString();
}
function loadConfig() { function loadConfig() {
configLoaded = false; configLoaded = false;
configError = false; configError = false;
@@ -330,24 +295,6 @@ Item {
} }
} }
} }
SettingsDropdownRow {
tab: "clipboard"
tags: ["clipboard", "pinned", "max", "limit"]
settingKey: "maxPinned"
text: I18n.tr("Maximum Pinned Entries")
description: I18n.tr("Maximum number of entries that can be saved")
currentValue: root.getMaxPinnedText(root.config.maxPinned ?? 25)
options: root.maxPinnedOptions.map(opt => opt.text)
onValueChanged: value => {
for (let opt of root.maxPinnedOptions) {
if (opt.text === value) {
root.saveConfig("maxPinned", opt.value);
return;
}
}
}
}
} }
SettingsCard { SettingsCard {

View File

@@ -29,9 +29,6 @@ Item {
SettingsButtonGroupRow { SettingsButtonGroupRow {
text: I18n.tr("Position") text: I18n.tr("Position")
model: ["Top", "Bottom", "Left", "Right"] model: ["Top", "Bottom", "Left", "Right"]
buttonPadding: Theme.spacingS
minButtonWidth: 44
textSize: Theme.fontSizeSmall
currentIndex: { currentIndex: {
switch (SettingsData.dockPosition) { switch (SettingsData.dockPosition) {
case SettingsData.Position.Top: case SettingsData.Position.Top:
@@ -132,9 +129,6 @@ Item {
tags: ["dock", "indicator", "style", "circle", "line"] tags: ["dock", "indicator", "style", "circle", "line"]
text: I18n.tr("Indicator Style") text: I18n.tr("Indicator Style")
model: ["Circle", "Line"] model: ["Circle", "Line"]
buttonPadding: Theme.spacingS
minButtonWidth: 44
textSize: Theme.fontSizeSmall
currentIndex: SettingsData.dockIndicatorStyle === "circle" ? 0 : 1 currentIndex: SettingsData.dockIndicatorStyle === "circle" ? 0 : 1
onSelectionChanged: (index, selected) => { onSelectionChanged: (index, selected) => {
if (selected) { if (selected) {
@@ -231,9 +225,6 @@ Item {
description: I18n.tr("Choose the border accent color") description: I18n.tr("Choose the border accent color")
visible: SettingsData.dockBorderEnabled visible: SettingsData.dockBorderEnabled
model: ["Surface", "Secondary", "Primary"] model: ["Surface", "Secondary", "Primary"]
buttonPadding: Theme.spacingS
minButtonWidth: 44
textSize: Theme.fontSizeSmall
currentIndex: { currentIndex: {
switch (SettingsData.dockBorderColor) { switch (SettingsData.dockBorderColor) {
case "surfaceText": case "surfaceText":

View File

@@ -15,7 +15,6 @@ Item {
property string expandedVpnUuid: "" property string expandedVpnUuid: ""
property string expandedWifiSsid: "" property string expandedWifiSsid: ""
property string expandedEthDevice: "" property string expandedEthDevice: ""
property int maxPinnedWifiNetworks: 3
Component.onCompleted: { Component.onCompleted: {
NetworkService.addRef(); NetworkService.addRef();
@@ -31,40 +30,6 @@ Item {
vpnFileBrowserLoader.item.open(); vpnFileBrowserLoader.item.open();
} }
function normalizePinList(value) {
if (Array.isArray(value))
return value.filter(v => v)
if (typeof value === "string" && value.length > 0)
return [value]
return []
}
function getPinnedWifiNetworks() {
const pins = SettingsData.wifiNetworkPins || {}
return normalizePinList(pins["preferredWifi"])
}
function toggleWifiPin(ssid) {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}))
let pinnedList = normalizePinList(pins["preferredWifi"])
const pinIndex = pinnedList.indexOf(ssid)
if (pinIndex !== -1) {
pinnedList.splice(pinIndex, 1)
} else {
pinnedList.unshift(ssid)
if (pinnedList.length > maxPinnedWifiNetworks)
pinnedList = pinnedList.slice(0, maxPinnedWifiNetworks)
}
if (pinnedList.length > 0)
pins["preferredWifi"] = pinnedList
else
delete pins["preferredWifi"]
SettingsData.set("wifiNetworkPins", pins)
}
LazyLoader { LazyLoader {
id: vpnFileBrowserLoader id: vpnFileBrowserLoader
active: false active: false
@@ -1060,19 +1025,15 @@ Item {
model: { model: {
const ssid = NetworkService.currentWifiSSID; const ssid = NetworkService.currentWifiSSID;
const networks = NetworkService.wifiNetworks || []; const networks = NetworkService.wifiNetworks || [];
const pinnedList = networkTab.getPinnedWifiNetworks(); const pins = SettingsData.wifiNetworkPins || {};
const pinnedSSID = pins["preferredWifi"];
let sorted = [...networks]; let sorted = [...networks];
sorted.sort((a, b) => { sorted.sort((a, b) => {
const aPinnedIndex = pinnedList.indexOf(a.ssid) if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID)
const bPinnedIndex = pinnedList.indexOf(b.ssid) return -1;
if (aPinnedIndex !== -1 || bPinnedIndex !== -1) { if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID)
if (aPinnedIndex === -1) return 1;
return 1
if (bPinnedIndex === -1)
return -1
return aPinnedIndex - bPinnedIndex
}
if (a.ssid === ssid) if (a.ssid === ssid)
return -1; return -1;
if (b.ssid === ssid) if (b.ssid === ssid)
@@ -1088,7 +1049,7 @@ Item {
required property int index required property int index
readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID readonly property bool isConnected: modelData.ssid === NetworkService.currentWifiSSID
readonly property bool isPinned: networkTab.getPinnedWifiNetworks().includes(modelData.ssid) readonly property bool isPinned: (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
readonly property bool isExpanded: networkTab.expandedWifiSsid === modelData.ssid readonly property bool isExpanded: networkTab.expandedWifiSsid === modelData.ssid
width: parent.width width: parent.width
@@ -1263,7 +1224,13 @@ Item {
buttonSize: 28 buttonSize: 28
iconColor: isPinned ? Theme.primary : Theme.surfaceVariantText iconColor: isPinned ? Theme.primary : Theme.surfaceVariantText
onClicked: { onClicked: {
networkTab.toggleWifiPin(modelData.ssid) const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
if (isPinned) {
delete pins["preferredWifi"];
} else {
pins["preferredWifi"] = modelData.ssid;
}
SettingsData.set("wifiNetworkPins", pins);
} }
} }

View File

@@ -270,9 +270,7 @@ FloatingWindow {
root.updateFilteredPlugins(); root.updateFilteredPlugins();
return; return;
} }
thirdPartyConfirmLoader.active = true; thirdPartyConfirmModal.visible = true;
if (thirdPartyConfirmLoader.item)
thirdPartyConfirmLoader.item.show();
} }
} }
@@ -670,132 +668,119 @@ FloatingWindow {
} }
} }
LazyLoader { FloatingWindow {
id: thirdPartyConfirmLoader id: thirdPartyConfirmModal
active: false
FloatingWindow { objectName: "thirdPartyConfirm"
id: thirdPartyConfirmModal title: I18n.tr("Third-Party Plugin Warning")
implicitWidth: 500
implicitHeight: 350
color: Theme.surfaceContainer
visible: false
function show() { FocusScope {
visible = true; anchors.fill: parent
} focus: true
function hide() { Keys.onPressed: event => {
visible = false; if (event.key === Qt.Key_Escape) {
} thirdPartyConfirmModal.visible = false;
event.accepted = true;
objectName: "thirdPartyConfirm"
title: I18n.tr("Third-Party Plugin Warning")
implicitWidth: 500
implicitHeight: 350
color: Theme.surfaceContainer
visible: false
FocusScope {
anchors.fill: parent
focus: true
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
thirdPartyConfirmModal.hide();
event.accepted = true;
}
} }
}
Column { Column {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingL anchors.margins: Theme.spacingL
spacing: Theme.spacingL spacing: Theme.spacingL
Row { Row {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
DankIcon { DankIcon {
name: "warning" name: "warning"
size: Theme.iconSize size: Theme.iconSize
color: Theme.warning color: Theme.warning
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: I18n.tr("Third-Party Plugin Warning")
font.pixelSize: Theme.fontSizeLarge
font.weight: Font.Medium
color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter
}
Item {
width: parent.width - parent.spacing * 2 - Theme.iconSize - parent.children[1].implicitWidth - closeConfirmBtn.width
height: 1
}
DankActionButton {
id: closeConfirmBtn
iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
anchors.verticalCenter: parent.verticalCenter
onClicked: thirdPartyConfirmModal.hide()
}
} }
StyledText { StyledText {
width: parent.width text: I18n.tr("Third-Party Plugin Warning")
text: I18n.tr("Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\n\nThese plugins may pose security and privacy risks - install at your own risk.") font.pixelSize: Theme.fontSizeLarge
font.pixelSize: Theme.fontSizeMedium font.weight: Font.Medium
color: Theme.surfaceText color: Theme.surfaceText
wrapMode: Text.WordWrap anchors.verticalCenter: parent.verticalCenter
}
Column {
width: parent.width
spacing: Theme.spacingS
StyledText {
text: I18n.tr("• Plugins may contain bugs or security issues")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("• Review code before installation when possible")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("• Install only from trusted sources")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
} }
Item { Item {
width: parent.width width: parent.width - parent.spacing * 2 - Theme.iconSize - parent.children[1].implicitWidth - closeConfirmBtn.width
height: parent.height - parent.spacing * 3 - y height: 1
} }
Row { DankActionButton {
anchors.right: parent.right id: closeConfirmBtn
spacing: Theme.spacingM iconName: "close"
iconSize: Theme.iconSize - 2
iconColor: Theme.outline
anchors.verticalCenter: parent.verticalCenter
onClicked: thirdPartyConfirmModal.visible = false
}
}
DankButton { StyledText {
text: I18n.tr("Cancel") width: parent.width
iconName: "close" text: I18n.tr("Third-party plugins are created by the community and are not officially supported by DankMaterialShell.\n\nThese plugins may pose security and privacy risks - install at your own risk.")
onClicked: thirdPartyConfirmModal.hide() font.pixelSize: Theme.fontSizeMedium
} color: Theme.surfaceText
wrapMode: Text.WordWrap
}
DankButton { Column {
text: I18n.tr("I Understand") width: parent.width
iconName: "check" spacing: Theme.spacingS
onClicked: {
SessionData.setShowThirdPartyPlugins(true); StyledText {
root.updateFilteredPlugins(); text: I18n.tr("• Plugins may contain bugs or security issues")
thirdPartyConfirmModal.hide(); font.pixelSize: Theme.fontSizeSmall
} color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("• Review code before installation when possible")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
StyledText {
text: I18n.tr("• Install only from trusted sources")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
Item {
width: parent.width
height: parent.height - parent.spacing * 3 - y
}
Row {
anchors.right: parent.right
spacing: Theme.spacingM
DankButton {
text: I18n.tr("Cancel")
iconName: "close"
onClicked: thirdPartyConfirmModal.visible = false
}
DankButton {
text: I18n.tr("I Understand")
iconName: "check"
onClicked: {
SessionData.setShowThirdPartyPlugins(true);
root.updateFilteredPlugins();
thirdPartyConfirmModal.visible = false;
} }
} }
} }

View File

@@ -51,7 +51,6 @@ StyledRect {
Row { Row {
spacing: Theme.spacingM spacing: Theme.spacingM
width: parent.width
DankIcon { DankIcon {
id: headerIcon id: headerIcon
@@ -70,8 +69,6 @@ StyledRect {
color: Theme.surfaceText color: Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: root.title !== "" visible: root.title !== ""
width: parent.width - (headerIcon.visible ? headerIcon.width + parent.spacing : 0)
horizontalAlignment: Text.AlignLeft
} }
} }

View File

@@ -383,7 +383,6 @@ Item {
widgetObj.showMicPercent = SettingsData.controlCenterShowMicPercent; widgetObj.showMicPercent = SettingsData.controlCenterShowMicPercent;
widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon; widgetObj.showBatteryIcon = SettingsData.controlCenterShowBatteryIcon;
widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon; widgetObj.showPrinterIcon = SettingsData.controlCenterShowPrinterIcon;
widgetObj.showScreenSharingIcon = SettingsData.controlCenterShowScreenSharingIcon;
} }
if (widgetId === "diskUsage") if (widgetId === "diskUsage")
widgetObj.mountPath = "/"; widgetObj.mountPath = "/";
@@ -402,24 +401,6 @@ Item {
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function cloneWidgetData(widget) {
if (typeof widget === "string")
return {
"id": widget,
"enabled": true
};
var result = {
"id": widget.id,
"enabled": widget.enabled
};
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon"];
for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]];
}
return result;
}
function handleItemEnabledChanged(sectionId, itemId, enabled) { function handleItemEnabledChanged(sectionId, itemId, enabled) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
@@ -427,8 +408,42 @@ Item {
var widgetId = typeof widget === "string" ? widget : widget.id; var widgetId = typeof widget === "string" ? widget : widget.id;
if (widgetId !== itemId) if (widgetId !== itemId)
continue; continue;
var newWidget = cloneWidgetData(widget);
newWidget.enabled = enabled; if (typeof widget === "string") {
widgets[i] = {
"id": widget,
"enabled": enabled
};
break;
}
var newWidget = {
"id": widget.id,
"enabled": enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
else if (widget.id === "gpuTemp")
newWidget.selectedGpuIndex = 0;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
else if (widget.id === "gpuTemp")
newWidget.pciId = "";
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[i] = newWidget; widgets[i] = newWidget;
break; break;
} }
@@ -441,36 +456,128 @@ Item {
function handleSpacerSizeChanged(sectionId, widgetIndex, newSize) { function handleSpacerSizeChanged(sectionId, widgetIndex, newSize) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) if (widgetIndex < 0 || widgetIndex >= widgets.length) {
setWidgetsForSection(sectionId, widgets);
return; return;
}
var widget = widgets[widgetIndex]; var widget = widgets[widgetIndex];
var widgetId = typeof widget === "string" ? widget : widget.id; var widgetId = typeof widget === "string" ? widget : widget.id;
if (widgetId !== "spacer") if (widgetId !== "spacer") {
setWidgetsForSection(sectionId, widgets);
return; return;
var newWidget = cloneWidgetData(widget); }
newWidget.size = newSize;
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"size": newSize
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"size": newSize
};
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleGpuSelectionChanged(sectionId, widgetIndex, selectedGpuIndex) { function handleGpuSelectionChanged(sectionId, widgetIndex, selectedGpuIndex) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) if (widgetIndex < 0 || widgetIndex >= widgets.length) {
setWidgetsForSection(sectionId, widgets);
return; return;
}
var pciId = DgopService.availableGpus && DgopService.availableGpus.length > selectedGpuIndex ? DgopService.availableGpus[selectedGpuIndex].pciId : ""; var pciId = DgopService.availableGpus && DgopService.availableGpus.length > selectedGpuIndex ? DgopService.availableGpus[selectedGpuIndex].pciId : "";
var newWidget = cloneWidgetData(widgets[widgetIndex]); var widget = widgets[widgetIndex];
newWidget.selectedGpuIndex = selectedGpuIndex; if (typeof widget === "string") {
newWidget.pciId = pciId; widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"selectedGpuIndex": selectedGpuIndex,
"pciId": pciId
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"selectedGpuIndex": selectedGpuIndex,
"pciId": pciId
};
if (widget.size !== undefined)
newWidget.size = widget.size;
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleDiskMountSelectionChanged(sectionId, widgetIndex, mountPath) { function handleDiskMountSelectionChanged(sectionId, widgetIndex, mountPath) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) if (widgetIndex < 0 || widgetIndex >= widgets.length) {
setWidgetsForSection(sectionId, widgets);
return; return;
var newWidget = cloneWidgetData(widgets[widgetIndex]); }
newWidget.mountPath = mountPath;
var widget = widgets[widgetIndex];
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"mountPath": mountPath
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"mountPath": mountPath
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -479,8 +586,32 @@ Item {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
if (widgetIndex < 0 || widgetIndex >= widgets.length) if (widgetIndex < 0 || widgetIndex >= widgets.length)
return; return;
var newWidget = cloneWidgetData(widgets[widgetIndex]);
var widget = widgets[widgetIndex];
if (typeof widget === "string") {
widget = {
"id": widget,
"enabled": true
};
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled !== undefined ? widget.enabled : true,
"showNetworkIcon": widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon,
"showBluetoothIcon": widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon,
"showAudioIcon": widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon,
"showAudioPercent": widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent,
"showVpnIcon": widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon,
"showBrightnessIcon": widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon,
"showBrightnessPercent": widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent,
"showMicIcon": widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon,
"showMicPercent": widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent,
"showBatteryIcon": widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon,
"showPrinterIcon": widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon
};
newWidget[settingName] = value; newWidget[settingName] = value;
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -505,8 +636,46 @@ Item {
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
return; return;
} }
var newWidget = cloneWidgetData(widgets[widgetIndex]);
newWidget.minimumWidth = enabled; var widget = widgets[widgetIndex];
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"minimumWidth": enabled
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"minimumWidth": enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.mountPath !== undefined)
newWidget.mountPath = widget.mountPath;
if (widget.showSwap !== undefined)
newWidget.showSwap = widget.showSwap;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -517,41 +686,141 @@ Item {
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
return; return;
} }
var newWidget = cloneWidgetData(widgets[widgetIndex]);
newWidget.showSwap = enabled; var widget = widgets[widgetIndex];
if (typeof widget === "string") {
widgets[widgetIndex] = {
"id": widget,
"enabled": true,
"showSwap": enabled
};
setWidgetsForSection(sectionId, widgets);
return;
}
var newWidget = {
"id": widget.id,
"enabled": widget.enabled,
"showSwap": enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.mountPath !== undefined)
newWidget.mountPath = widget.mountPath;
if (widget.minimumWidth !== undefined)
newWidget.minimumWidth = widget.minimumWidth;
if (widget.mediaSize !== undefined)
newWidget.mediaSize = widget.mediaSize;
if (widget.clockCompactMode !== undefined)
newWidget.clockCompactMode = widget.clockCompactMode;
if (widget.focusedWindowCompactMode !== undefined)
newWidget.focusedWindowCompactMode = widget.focusedWindowCompactMode;
if (widget.runningAppsCompactMode !== undefined)
newWidget.runningAppsCompactMode = widget.runningAppsCompactMode;
if (widget.keyboardLayoutNameCompactMode !== undefined)
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[widgetIndex] = newWidget; widgets[widgetIndex] = newWidget;
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
function handleCompactModeChanged(sectionId, widgetId, value) { function handleCompactModeChanged(sectionId, widgetId, value) {
var widgets = getWidgetsForSection(sectionId).slice(); var widgets = getWidgetsForSection(sectionId).slice();
for (var i = 0; i < widgets.length; i++) { for (var i = 0; i < widgets.length; i++) {
var widget = widgets[i]; var widget = widgets[i];
var currentId = typeof widget === "string" ? widget : widget.id; var currentId = typeof widget === "string" ? widget : widget.id;
if (currentId !== widgetId) if (currentId !== widgetId)
continue; continue;
var newWidget = cloneWidgetData(widget); if (typeof widget === "string") {
widgets[i] = {
"id": widget,
"enabled": true
};
widget = widgets[i];
} else {
var newWidget = {
"id": widget.id,
"enabled": widget.enabled
};
if (widget.size !== undefined)
newWidget.size = widget.size;
if (widget.selectedGpuIndex !== undefined)
newWidget.selectedGpuIndex = widget.selectedGpuIndex;
if (widget.pciId !== undefined)
newWidget.pciId = widget.pciId;
if (widget.mountPath !== undefined)
newWidget.mountPath = widget.mountPath;
if (widget.minimumWidth !== undefined)
newWidget.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined)
newWidget.showSwap = widget.showSwap;
if (widget.mediaSize !== undefined)
newWidget.mediaSize = widget.mediaSize;
if (widget.clockCompactMode !== undefined)
newWidget.clockCompactMode = widget.clockCompactMode;
if (widget.focusedWindowCompactMode !== undefined)
newWidget.focusedWindowCompactMode = widget.focusedWindowCompactMode;
if (widget.runningAppsCompactMode !== undefined)
newWidget.runningAppsCompactMode = widget.runningAppsCompactMode;
if (widget.keyboardLayoutNameCompactMode !== undefined)
newWidget.keyboardLayoutNameCompactMode = widget.keyboardLayoutNameCompactMode;
if (widget.id === "controlCenterButton") {
newWidget.showNetworkIcon = widget.showNetworkIcon ?? SettingsData.controlCenterShowNetworkIcon;
newWidget.showBluetoothIcon = widget.showBluetoothIcon ?? SettingsData.controlCenterShowBluetoothIcon;
newWidget.showAudioIcon = widget.showAudioIcon ?? SettingsData.controlCenterShowAudioIcon;
newWidget.showAudioPercent = widget.showAudioPercent ?? SettingsData.controlCenterShowAudioPercent;
newWidget.showVpnIcon = widget.showVpnIcon ?? SettingsData.controlCenterShowVpnIcon;
newWidget.showBrightnessIcon = widget.showBrightnessIcon ?? SettingsData.controlCenterShowBrightnessIcon;
newWidget.showBrightnessPercent = widget.showBrightnessPercent ?? SettingsData.controlCenterShowBrightnessPercent;
newWidget.showMicIcon = widget.showMicIcon ?? SettingsData.controlCenterShowMicIcon;
newWidget.showMicPercent = widget.showMicPercent ?? SettingsData.controlCenterShowMicPercent;
newWidget.showBatteryIcon = widget.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
newWidget.showPrinterIcon = widget.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
}
widgets[i] = newWidget;
widget = newWidget;
}
switch (widgetId) { switch (widgetId) {
case "music": case "music":
newWidget.mediaSize = value; widget.mediaSize = value;
break; break;
case "clock": case "clock":
newWidget.clockCompactMode = value; widget.clockCompactMode = value;
break; break;
case "focusedWindow": case "focusedWindow":
newWidget.focusedWindowCompactMode = value; widget.focusedWindowCompactMode = value;
break; break;
case "runningApps": case "runningApps":
newWidget.runningAppsCompactMode = value; widget.runningAppsCompactMode = value;
break; break;
case "keyboard_layout_name": case "keyboard_layout_name":
newWidget.keyboardLayoutNameCompactMode = value; widget.keyboardLayoutNameCompactMode = value;
break; break;
} }
widgets[i] = newWidget;
break; break;
} }
setWidgetsForSection(sectionId, widgets); setWidgetsForSection(sectionId, widgets);
} }
@@ -598,8 +867,6 @@ Item {
item.showBatteryIcon = widget.showBatteryIcon; item.showBatteryIcon = widget.showBatteryIcon;
if (widget.showPrinterIcon !== undefined) if (widget.showPrinterIcon !== undefined)
item.showPrinterIcon = widget.showPrinterIcon; item.showPrinterIcon = widget.showPrinterIcon;
if (widget.showScreenSharingIcon !== undefined)
item.showScreenSharingIcon = widget.showScreenSharingIcon;
if (widget.minimumWidth !== undefined) if (widget.minimumWidth !== undefined)
item.minimumWidth = widget.minimumWidth; item.minimumWidth = widget.minimumWidth;
if (widget.showSwap !== undefined) if (widget.showSwap !== undefined)

View File

@@ -31,19 +31,6 @@ Column {
signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled) signal minimumWidthChanged(string sectionId, int widgetIndex, bool enabled)
signal showSwapChanged(string sectionId, int widgetIndex, bool enabled) signal showSwapChanged(string sectionId, int widgetIndex, bool enabled)
function cloneWidgetData(widget) {
var result = {
"id": widget.id,
"enabled": widget.enabled
};
var keys = ["size", "selectedGpuIndex", "pciId", "mountPath", "minimumWidth", "showSwap", "mediaSize", "clockCompactMode", "focusedWindowCompactMode", "runningAppsCompactMode", "keyboardLayoutNameCompactMode", "showNetworkIcon", "showBluetoothIcon", "showAudioIcon", "showAudioPercent", "showVpnIcon", "showBrightnessIcon", "showBrightnessPercent", "showMicIcon", "showMicPercent", "showBatteryIcon", "showPrinterIcon", "showScreenSharingIcon"];
for (var i = 0; i < keys.length; i++) {
if (widget[keys[i]] !== undefined)
result[keys[i]] = widget[keys[i]];
}
return result;
}
width: parent.width width: parent.width
height: implicitHeight height: implicitHeight
spacing: Theme.spacingM spacing: Theme.spacingM
@@ -740,7 +727,13 @@ Column {
var newItems = root.items.slice(); var newItems = root.items.slice();
var draggedItem = newItems.splice(index, 1)[0]; var draggedItem = newItems.splice(index, 1)[0];
newItems.splice(newIndex, 0, draggedItem); newItems.splice(newIndex, 0, draggedItem);
root.itemOrderChanged(newItems.map(item => root.cloneWidgetData(item))); root.itemOrderChanged(newItems.map(item => {
return ({
"id": item.id,
"enabled": item.enabled,
"size": item.size
});
}));
} }
} }
delegateItem.x = 0; delegateItem.x = 0;
@@ -882,11 +875,6 @@ Column {
icon: "print", icon: "print",
label: I18n.tr("Printer"), label: I18n.tr("Printer"),
setting: "showPrinterIcon" setting: "showPrinterIcon"
},
{
icon: "screen_record",
label: I18n.tr("Screen Sharing"),
setting: "showScreenSharingIcon"
} }
] ]
@@ -919,8 +907,6 @@ Column {
return wd?.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon; return wd?.showBatteryIcon ?? SettingsData.controlCenterShowBatteryIcon;
case "showPrinterIcon": case "showPrinterIcon":
return wd?.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon; return wd?.showPrinterIcon ?? SettingsData.controlCenterShowPrinterIcon;
case "showScreenSharingIcon":
return wd?.showScreenSharingIcon ?? SettingsData.controlCenterShowScreenSharingIcon;
default: default:
return false; return false;
} }

View File

@@ -63,15 +63,15 @@ Item {
onToggled: checked => SettingsData.set("showWorkspaceApps", checked) onToggled: checked => SettingsData.set("showWorkspaceApps", checked)
} }
Item { Row {
width: parent.width width: parent.width - Theme.spacingL
height: maxAppsColumn.height spacing: Theme.spacingL
visible: SettingsData.showWorkspaceApps visible: SettingsData.showWorkspaceApps
opacity: visible ? 1 : 0 opacity: visible ? 1 : 0
anchors.left: parent.left
anchors.leftMargin: Theme.spacingL
Column { Column {
id: maxAppsColumn
x: Theme.spacingL
width: 120 width: 120
spacing: Theme.spacingS spacing: Theme.spacingS
@@ -80,15 +80,14 @@ Item {
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
horizontalAlignment: Text.AlignLeft
} }
DankTextField { DankTextField {
width: 100 width: 100
height: 28 height: 28
placeholderText: "3" placeholderText: "#ffffff"
text: SettingsData.maxWorkspaceIcons text: SettingsData.maxWorkspaceIcons
maximumLength: 2 maximumLength: 7
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
topPadding: Theme.spacingXS topPadding: Theme.spacingXS
bottomPadding: Theme.spacingXS bottomPadding: Theme.spacingXS

View File

@@ -11,7 +11,7 @@ Singleton {
id: root id: root
readonly property string currentVersion: "1.2" readonly property string currentVersion: "1.2"
readonly property bool changelogEnabled: false readonly property bool changelogEnabled: true
readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell" readonly property string configDir: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/DankMaterialShell"
readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion readonly property string changelogMarkerPath: configDir + "/.changelog-" + currentVersion

View File

@@ -21,7 +21,6 @@ Singleton {
property int processLimit: 20 property int processLimit: 20
property string processSort: "cpu" property string processSort: "cpu"
property bool noCpu: false property bool noCpu: false
property int dgopProcessPid: 0
// Cursor data for accurate CPU calculations // Cursor data for accurate CPU calculations
property string cpuCursor: "" property string cpuCursor: ""
@@ -60,7 +59,6 @@ Singleton {
property var processes: [] property var processes: []
property var allProcesses: [] property var allProcesses: []
property string currentSort: "cpu" property string currentSort: "cpu"
property bool sortAscending: false
property var availableGpus: [] property var availableGpus: []
property string kernelVersion: "" property string kernelVersion: ""
@@ -95,8 +93,10 @@ Singleton {
if (modules) { if (modules) {
const modulesToAdd = Array.isArray(modules) ? modules : [modules]; const modulesToAdd = Array.isArray(modules) ? modules : [modules];
for (const module of modulesToAdd) { for (const module of modulesToAdd) {
// Increment reference count for this module
const currentCount = moduleRefCounts[module] || 0; const currentCount = moduleRefCounts[module] || 0;
moduleRefCounts[module] = currentCount + 1; moduleRefCounts[module] = currentCount + 1;
console.log("Adding ref for module:", module, "count:", moduleRefCounts[module]);
// Add to enabled modules if not already there // Add to enabled modules if not already there
if (enabledModules.indexOf(module) === -1) { if (enabledModules.indexOf(module) === -1) {
@@ -126,13 +126,17 @@ Singleton {
for (const module of modulesToRemove) { for (const module of modulesToRemove) {
const currentCount = moduleRefCounts[module] || 0; const currentCount = moduleRefCounts[module] || 0;
if (currentCount > 1) { if (currentCount > 1) {
// Decrement reference count
moduleRefCounts[module] = currentCount - 1; moduleRefCounts[module] = currentCount - 1;
console.log("Removing ref for module:", module, "count:", moduleRefCounts[module]);
} else if (currentCount === 1) { } else if (currentCount === 1) {
// Remove completely when count reaches 0
delete moduleRefCounts[module]; delete moduleRefCounts[module];
const index = enabledModules.indexOf(module); const index = enabledModules.indexOf(module);
if (index > -1) { if (index > -1) {
enabledModules.splice(index, 1); enabledModules.splice(index, 1);
modulesChanged = true; modulesChanged = true;
console.log("Disabling module:", module, "(no more refs)");
} }
} }
} }
@@ -167,13 +171,17 @@ Singleton {
gpuPciIds = gpuPciIds.concat([pciId]); gpuPciIds = gpuPciIds.concat([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) { function removeGpuPciId(pciId) {
const currentCount = gpuPciIdRefCounts[pciId] || 0; const currentCount = gpuPciIdRefCounts[pciId] || 0;
if (currentCount > 1) { if (currentCount > 1) {
// Decrement reference count
gpuPciIdRefCounts[pciId] = currentCount - 1; gpuPciIdRefCounts[pciId] = currentCount - 1;
console.log("Removing GPU PCI ID ref:", pciId, "count:", gpuPciIdRefCounts[pciId]);
} else if (currentCount === 1) { } else if (currentCount === 1) {
// Remove completely when count reaches 0 // Remove completely when count reaches 0
delete gpuPciIdRefCounts[pciId]; delete gpuPciIdRefCounts[pciId];
@@ -195,6 +203,8 @@ Singleton {
} }
availableGpus = updatedGpus; availableGpus = updatedGpus;
} }
console.log("Removing GPU PCI ID completely:", pciId);
} }
// Force property change notification // Force property change notification
@@ -379,12 +389,8 @@ Singleton {
if (data.processes && Array.isArray(data.processes)) { if (data.processes && Array.isArray(data.processes)) {
const newProcesses = []; const newProcesses = [];
processSampleCount++; processSampleCount++;
const ourPid = dgopProcessPid;
for (const proc of data.processes) { for (const proc of data.processes) {
if (ourPid > 0 && proc.pid === ourPid)
continue;
const cpuUsage = processSampleCount >= 2 ? (proc.cpu || 0) : 0; const cpuUsage = processSampleCount >= 2 ? (proc.cpu || 0) : 0;
newProcesses.push({ newProcesses.push({
@@ -571,55 +577,39 @@ Singleton {
function setSortBy(newSortBy) { function setSortBy(newSortBy) {
if (newSortBy !== currentSort) { if (newSortBy !== currentSort) {
currentSort = newSortBy; currentSort = newSortBy;
sortAscending = false;
applySorting(); applySorting();
} }
} }
function toggleSort(column) {
if (column === currentSort) {
sortAscending = !sortAscending;
} else {
currentSort = column;
sortAscending = false;
}
applySorting();
}
function applySorting() { function applySorting() {
if (!allProcesses || allProcesses.length === 0) if (!allProcesses || allProcesses.length === 0) {
return; return;
}
const asc = sortAscending;
const sorted = allProcesses.slice(); const sorted = allProcesses.slice();
sorted.sort((a, b) => { sorted.sort((a, b) => {
let valueA, valueB, result; let valueA, valueB;
switch (currentSort) { switch (currentSort) {
case "cpu": case "cpu":
valueA = a.cpu || 0; valueA = a.cpu || 0;
valueB = b.cpu || 0; valueB = b.cpu || 0;
result = valueB - valueA; return valueB - valueA;
break;
case "memory": case "memory":
valueA = a.memoryKB || 0; valueA = a.memoryKB || 0;
valueB = b.memoryKB || 0; valueB = b.memoryKB || 0;
result = valueB - valueA; return valueB - valueA;
break;
case "name": case "name":
valueA = (a.command || "").toLowerCase(); valueA = (a.command || "").toLowerCase();
valueB = (b.command || "").toLowerCase(); valueB = (b.command || "").toLowerCase();
result = valueA.localeCompare(valueB); return valueA.localeCompare(valueB);
break;
case "pid": case "pid":
valueA = a.pid || 0; valueA = a.pid || 0;
valueB = b.pid || 0; valueB = b.pid || 0;
result = valueA - valueB; return valueA - valueB;
break;
default: default:
return 0; return 0;
} }
return asc ? -result : result;
}); });
processes = sorted.slice(0, processLimit); processes = sorted.slice(0, processLimit);
@@ -638,7 +628,10 @@ Singleton {
id: dgopProcess id: dgopProcess
command: root.buildDgopCommand() command: root.buildDgopCommand()
running: false running: false
onStarted: dgopProcessPid = processId ?? 0 onCommandChanged:
//console.log("DgopService command:", JSON.stringify(command))
{}
onExited: exitCode => { onExited: exitCode => {
if (exitCode !== 0) { if (exitCode !== 0) {
console.warn("Dgop process failed with exit code:", exitCode); console.warn("Dgop process failed with exit code:", exitCode);

View File

@@ -27,10 +27,6 @@ Singleton {
property bool inOverview: false property bool inOverview: false
property var casts: []
property bool hasCasts: casts.length > 0
property bool hasActiveCast: casts.some(c => c.is_active)
property int currentKeyboardLayoutIndex: 0 property int currentKeyboardLayoutIndex: 0
property var keyboardLayoutNames: [] property var keyboardLayoutNames: []
@@ -360,15 +356,6 @@ Singleton {
case 'ScreenshotCaptured': case 'ScreenshotCaptured':
handleScreenshotCaptured(event.ScreenshotCaptured); handleScreenshotCaptured(event.ScreenshotCaptured);
break; break;
case 'CastsChanged':
handleCastsChanged(event.CastsChanged);
break;
case 'CastStartedOrChanged':
handleCastStartedOrChanged(event.CastStartedOrChanged);
break;
case 'CastStopped':
handleCastStopped(event.CastStopped);
break;
} }
} }
@@ -662,28 +649,6 @@ Singleton {
} }
} }
function handleCastsChanged(data) {
casts = data.casts || [];
}
function handleCastStartedOrChanged(data) {
if (!data.cast)
return;
const cast = data.cast;
const existingIndex = casts.findIndex(c => c.stream_id === cast.stream_id);
if (existingIndex >= 0) {
const updatedCasts = [...casts];
updatedCasts[existingIndex] = cast;
casts = updatedCasts;
} else {
casts = [...casts, cast];
}
}
function handleCastStopped(data) {
casts = casts.filter(c => c.stream_id !== data.stream_id);
}
function updateCurrentOutputWorkspaces() { function updateCurrentOutputWorkspaces() {
if (!currentOutput) { if (!currentOutput) {
currentOutputWorkspaces = allWorkspaces; currentOutputWorkspaces = allWorkspaces;

View File

@@ -287,14 +287,11 @@ Singleton {
return false; return false;
} }
if (isDaemon) { // MODIFICATION: Treat Launchers as persistent instances like Daemons
const newDaemons = Object.assign({}, pluginDaemonComponents); if (isDaemon || isLauncher) {
newDaemons[pluginId] = comp;
pluginDaemonComponents = newDaemons;
} else if (isLauncher) {
const instance = comp.createObject(root, { const instance = comp.createObject(root, {
"pluginId": pluginId, "pluginId": pluginId,
"pluginService": root "pluginService": root // Inject PluginService
}); });
if (!instance) { if (!instance) {
console.error("PluginService: failed to instantiate plugin:", pluginId, comp.errorString()); console.error("PluginService: failed to instantiate plugin:", pluginId, comp.errorString());
@@ -305,9 +302,15 @@ Singleton {
newInstances[pluginId] = instance; newInstances[pluginId] = instance;
pluginInstances = newInstances; pluginInstances = newInstances;
const newLaunchers = Object.assign({}, pluginLauncherComponents); if (isDaemon) {
newLaunchers[pluginId] = comp; const newDaemons = Object.assign({}, pluginDaemonComponents);
pluginLauncherComponents = newLaunchers; newDaemons[pluginId] = comp;
pluginDaemonComponents = newDaemons;
} else {
const newLaunchers = Object.assign({}, pluginLauncherComponents);
newLaunchers[pluginId] = comp;
pluginLauncherComponents = newLaunchers;
}
} else if (isDesktop) { } else if (isDesktop) {
const newDesktop = Object.assign({}, pluginDesktopComponents); const newDesktop = Object.assign({}, pluginDesktopComponents);
newDesktop[pluginId] = comp; newDesktop[pluginId] = comp;

View File

@@ -754,7 +754,7 @@ Singleton {
"humidity": Math.round(hourly.relative_humidity_2m?.[i] || 0), "humidity": Math.round(hourly.relative_humidity_2m?.[i] || 0),
"wind": Math.round(hourly.wind_speed_10m?.[i] || 0), "wind": Math.round(hourly.wind_speed_10m?.[i] || 0),
"pressure": Math.round(hourly.surface_pressure?.[i] || 0), "pressure": Math.round(hourly.surface_pressure?.[i] || 0),
"precipitationProbability": Math.round(hourly.precipitation_probability?.[i] || 0), "precipitationProbability": Math.round(hourly.precipitation_probability_max?.[0] || 0),
"visibility": Math.round(hourly.visibility?.[i] || 0), "visibility": Math.round(hourly.visibility?.[i] || 0),
"isDay": isDay "isDay": isDay
}); });

View File

@@ -1 +1 @@
v1.4-unstable v1.2.2

View File

@@ -202,7 +202,7 @@
{ {
"scope": ["keyword"], "scope": ["keyword"],
"settings": { "settings": {
"foreground": "{{dank16.color6.dark.hex}}" "foreground": "{{dank16.color5.dark.hex}}"
} }
}, },
{ {
@@ -320,7 +320,7 @@
"foreground": "{{dank16.color12.dark.hex}}" "foreground": "{{dank16.color12.dark.hex}}"
}, },
"keyword": { "keyword": {
"foreground": "{{dank16.color6.dark.hex}}" "foreground": "{{dank16.color5.dark.hex}}"
}, },
"comment": { "comment": {
"foreground": "{{dank16.color8.dark.hex}}" "foreground": "{{dank16.color8.dark.hex}}"

View File

@@ -148,7 +148,7 @@
{ {
"scope": ["keyword"], "scope": ["keyword"],
"settings": { "settings": {
"foreground": "{{dank16.color6.default.hex}}" "foreground": "{{dank16.color5.default.hex}}"
} }
}, },
{ {
@@ -272,7 +272,7 @@
"foreground": "{{dank16.color12.default.hex}}" "foreground": "{{dank16.color12.default.hex}}"
}, },
"keyword": { "keyword": {
"foreground": "{{dank16.color6.default.hex}}" "foreground": "{{dank16.color5.default.hex}}"
}, },
"comment": { "comment": {
"foreground": "{{colors.outline.default.hex}}" "foreground": "{{colors.outline.default.hex}}"

View File

@@ -190,7 +190,7 @@
"storage.type" "storage.type"
], ],
"settings": { "settings": {
"foreground": "{{dank16.color6.light.hex}}" "foreground": "{{dank16.color5.light.hex}}"
} }
}, },
{ {
@@ -444,7 +444,7 @@
"foreground": "{{colors.outline.light.hex}}" "foreground": "{{colors.outline.light.hex}}"
}, },
"keyword": { "keyword": {
"foreground": "{{dank16.color6.light.hex}}" "foreground": "{{dank16.color5.light.hex}}"
}, },
"operator": { "operator": {
"foreground": "{{colors.on_surface.light.hex}}" "foreground": "{{colors.on_surface.light.hex}}"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -240,7 +240,7 @@
"All displays": "Tutti gli schermi" "All displays": "Tutti gli schermi"
}, },
"Allow clicks to pass through the widget": { "Allow clicks to pass through the widget": {
"Allow clicks to pass through the widget": "Consenti il passaggio dei clic attraverso il widget" "Allow clicks to pass through the widget": ""
}, },
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": { "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": {
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/Backspace: Indietro • F1/I: File Info • F10: Aiuto • Esc: Chiudi" "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/Backspace: Indietro • F1/I: File Info • F10: Aiuto • Esc: Chiudi"
@@ -279,7 +279,7 @@
"Anonymous Identity (optional)": "Identità anonima (facoltativa)" "Anonymous Identity (optional)": "Identità anonima (facoltativa)"
}, },
"App ID Substitutions": { "App ID Substitutions": {
"App ID Substitutions": "Sostituzioni App ID" "App ID Substitutions": ""
}, },
"App Launcher": { "App Launcher": {
"App Launcher": "App Launcher" "App Launcher": "App Launcher"
@@ -729,7 +729,7 @@
"Click Import to add a .ovpn or .conf": "Clicca su Importa per aggiungere un file .ovpn o .conf" "Click Import to add a .ovpn or .conf": "Clicca su Importa per aggiungere un file .ovpn o .conf"
}, },
"Click Through": { "Click Through": {
"Click Through": "Clic passanti" "Click Through": ""
}, },
"Click any shortcut to edit. Changes save to %1": { "Click any shortcut to edit. Changes save to %1": {
"Click any shortcut to edit. Changes save to %1": "Clicca su qualsiasi scorciatoia per modificare. Le modifiche vengono salvate in %1" "Click any shortcut to edit. Changes save to %1": "Clicca su qualsiasi scorciatoia per modificare. Le modifiche vengono salvate in %1"
@@ -1797,7 +1797,7 @@
"Grid Columns": "Colonne Griglia" "Grid Columns": "Colonne Griglia"
}, },
"Group": { "Group": {
"Group": "Gruppo" "Group": ""
}, },
"Group Workspace Apps": { "Group Workspace Apps": {
"Group Workspace Apps": "Raggruppa App per Spazio di Lavoro" "Group Workspace Apps": "Raggruppa App per Spazio di Lavoro"
@@ -1809,13 +1809,13 @@
"Group multiple windows of the same app together with a window count indicator": "Raggruppa molteplici finestre della stessa app con un indicatore del numero di finestre" "Group multiple windows of the same app together with a window count indicator": "Raggruppa molteplici finestre della stessa app con un indicatore del numero di finestre"
}, },
"Group removed": { "Group removed": {
"Group removed": "Gruppo rimosso" "Group removed": ""
}, },
"Group repeated application icons in unfocused workspaces": { "Group repeated application icons in unfocused workspaces": {
"Group repeated application icons in unfocused workspaces": "Raggruppa le icone delle applicazioni duplicate negli spazi di lavoro non attivi" "Group repeated application icons in unfocused workspaces": "Raggruppa le icone delle applicazioni duplicate negli spazi di lavoro non attivi"
}, },
"Groups": { "Groups": {
"Groups": "Gruppi" "Groups": ""
}, },
"HDR (EDID)": { "HDR (EDID)": {
"HDR (EDID)": "HDR (EDID)" "HDR (EDID)": "HDR (EDID)"
@@ -2214,7 +2214,7 @@
"Manual Show/Hide": "Mostra/Nascondi Manuale" "Manual Show/Hide": "Mostra/Nascondi Manuale"
}, },
"Map window class names to icon names for proper icon display": { "Map window class names to icon names for proper icon display": {
"Map window class names to icon names for proper icon display": "Associa i nomi delle classi delle finestre ai nomi delle icone per una corretta visualizzazione" "Map window class names to icon names for proper icon display": ""
}, },
"Margin": { "Margin": {
"Margin": "Margini" "Margin": "Margini"
@@ -2439,7 +2439,7 @@
"New York, NY": "New York, NY" "New York, NY": "New York, NY"
}, },
"New group name...": { "New group name...": {
"New group name...": "Nome del nuovo gruppo..." "New group name...": ""
}, },
"Next Transition": { "Next Transition": {
"Next Transition": "Prossima Transizione" "Next Transition": "Prossima Transizione"
@@ -2673,7 +2673,7 @@
"Options": "Opzioni" "Options": "Opzioni"
}, },
"Organize widgets into collapsible groups": { "Organize widgets into collapsible groups": {
"Organize widgets into collapsible groups": "Organizza i widget in gruppi comprimibili" "Organize widgets into collapsible groups": ""
}, },
"Other": { "Other": {
"Other": "Altro" "Other": "Altro"
@@ -2748,7 +2748,7 @@
"Password": "Password" "Password": "Password"
}, },
"Pattern": { "Pattern": {
"Pattern": "Pattern" "Pattern": ""
}, },
"Pause": { "Pause": {
"Pause": "Pausa" "Pause": "Pausa"
@@ -3012,7 +3012,7 @@
"Repeat": "Ripetizione" "Repeat": "Ripetizione"
}, },
"Replacement": { "Replacement": {
"Replacement": "Sostituzione" "Replacement": ""
}, },
"Report": { "Report": {
"Report": "Riepilogo" "Report": "Riepilogo"
@@ -3648,7 +3648,7 @@
"Sync Mode with Portal": "Modalità Sync con Portale" "Sync Mode with Portal": "Modalità Sync con Portale"
}, },
"Sync Position Across Screens": { "Sync Position Across Screens": {
"Sync Position Across Screens": "Sincronizza la posizione tra gli schermi" "Sync Position Across Screens": ""
}, },
"Sync dark mode with settings portals for system-wide theme hints": { "Sync dark mode with settings portals for system-wide theme hints": {
"Sync dark mode with settings portals for system-wide theme hints": "Sincronizza tema scuro con impostazioni di sistema" "Sync dark mode with settings portals for system-wide theme hints": "Sincronizza tema scuro con impostazioni di sistema"
@@ -3876,7 +3876,7 @@
"Unfocused Color": "Colore Inattivo" "Unfocused Color": "Colore Inattivo"
}, },
"Ungrouped": { "Ungrouped": {
"Ungrouped": "Non Raggruppato" "Ungrouped": ""
}, },
"Uninstall Plugin": { "Uninstall Plugin": {
"Uninstall Plugin": "Disinstalla Plugin" "Uninstall Plugin": "Disinstalla Plugin"
@@ -3978,13 +3978,13 @@
"Use light theme instead of dark theme": "Usa tema chiaro invece del tema scuro" "Use light theme instead of dark theme": "Usa tema chiaro invece del tema scuro"
}, },
"Use smaller notification cards": { "Use smaller notification cards": {
"Use smaller notification cards": "Usa schede di notifica più piccole" "Use smaller notification cards": ""
}, },
"Use sound theme from system settings": { "Use sound theme from system settings": {
"Use sound theme from system settings": "Usa tema di suoni dalle impostazioni di sistema" "Use sound theme from system settings": "Usa tema di suoni dalle impostazioni di sistema"
}, },
"Use the same position and size on all displays": { "Use the same position and size on all displays": {
"Use the same position and size on all displays": "Usa la stessa posizione e dimensione su tutti gli schermi" "Use the same position and size on all displays": ""
}, },
"Use trigger prefix to activate": { "Use trigger prefix to activate": {
"Use trigger prefix to activate": "Usa il prefisso attivatore per attivare" "Use trigger prefix to activate": "Usa il prefisso attivatore per attivare"
@@ -4553,7 +4553,7 @@
"No wallpaper selected": "Nessuno sfondo selezionato" "No wallpaper selected": "Nessuno sfondo selezionato"
}, },
"notification center tab": { "notification center tab": {
"Current": "Attuali", "Current": "Attuale",
"History": "Cronologia" "History": "Cronologia"
}, },
"notification history filter": { "notification history filter": {

View File

@@ -240,7 +240,7 @@
"All displays": "所有显示器" "All displays": "所有显示器"
}, },
"Allow clicks to pass through the widget": { "Allow clicks to pass through the widget": {
"Allow clicks to pass through the widget": "允许鼠标穿透部件" "Allow clicks to pass through the widget": ""
}, },
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": { "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": {
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/退格: 返回 • F1/I: 文件信息 • F10: 帮助 • Esc: 关闭" "Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/退格: 返回 • F1/I: 文件信息 • F10: 帮助 • Esc: 关闭"
@@ -279,7 +279,7 @@
"Anonymous Identity (optional)": "匿名身份(可选)" "Anonymous Identity (optional)": "匿名身份(可选)"
}, },
"App ID Substitutions": { "App ID Substitutions": {
"App ID Substitutions": "应用ID替换" "App ID Substitutions": ""
}, },
"App Launcher": { "App Launcher": {
"App Launcher": "启动器" "App Launcher": "启动器"
@@ -729,7 +729,7 @@
"Click Import to add a .ovpn or .conf": "点击导入添加 .ovpn 或 .conf 文件" "Click Import to add a .ovpn or .conf": "点击导入添加 .ovpn 或 .conf 文件"
}, },
"Click Through": { "Click Through": {
"Click Through": "鼠标穿透" "Click Through": ""
}, },
"Click any shortcut to edit. Changes save to %1": { "Click any shortcut to edit. Changes save to %1": {
"Click any shortcut to edit. Changes save to %1": "点击任意快捷方式以编辑。更改将保存至%1。" "Click any shortcut to edit. Changes save to %1": "点击任意快捷方式以编辑。更改将保存至%1。"
@@ -1797,7 +1797,7 @@
"Grid Columns": "网格列" "Grid Columns": "网格列"
}, },
"Group": { "Group": {
"Group": "分组" "Group": ""
}, },
"Group Workspace Apps": { "Group Workspace Apps": {
"Group Workspace Apps": "分组工作区应用" "Group Workspace Apps": "分组工作区应用"
@@ -1809,13 +1809,13 @@
"Group multiple windows of the same app together with a window count indicator": "将同一应用的多个窗口合并显示,并标注窗口数量" "Group multiple windows of the same app together with a window count indicator": "将同一应用的多个窗口合并显示,并标注窗口数量"
}, },
"Group removed": { "Group removed": {
"Group removed": "分组已移除" "Group removed": ""
}, },
"Group repeated application icons in unfocused workspaces": { "Group repeated application icons in unfocused workspaces": {
"Group repeated application icons in unfocused workspaces": "在不聚焦的工作区中将重复应用图标分组" "Group repeated application icons in unfocused workspaces": "在不聚焦的工作区中将重复应用图标分组"
}, },
"Groups": { "Groups": {
"Groups": "分组" "Groups": ""
}, },
"HDR (EDID)": { "HDR (EDID)": {
"HDR (EDID)": "HDREDID" "HDR (EDID)": "HDREDID"
@@ -2214,7 +2214,7 @@
"Manual Show/Hide": "手动显示/隐藏" "Manual Show/Hide": "手动显示/隐藏"
}, },
"Map window class names to icon names for proper icon display": { "Map window class names to icon names for proper icon display": {
"Map window class names to icon names for proper icon display": "将窗口类名称映射到图标名称以实现正确的图标显示" "Map window class names to icon names for proper icon display": ""
}, },
"Margin": { "Margin": {
"Margin": "边距" "Margin": "边距"
@@ -2439,7 +2439,7 @@
"New York, NY": "纽约,美国纽约州" "New York, NY": "纽约,美国纽约州"
}, },
"New group name...": { "New group name...": {
"New group name...": "新分组名..." "New group name...": ""
}, },
"Next Transition": { "Next Transition": {
"Next Transition": "下一过渡" "Next Transition": "下一过渡"
@@ -2673,7 +2673,7 @@
"Options": "选项" "Options": "选项"
}, },
"Organize widgets into collapsible groups": { "Organize widgets into collapsible groups": {
"Organize widgets into collapsible groups": "将部件组织成可折叠分组" "Organize widgets into collapsible groups": ""
}, },
"Other": { "Other": {
"Other": "其他" "Other": "其他"
@@ -2748,7 +2748,7 @@
"Password": "密码" "Password": "密码"
}, },
"Pattern": { "Pattern": {
"Pattern": "模式" "Pattern": ""
}, },
"Pause": { "Pause": {
"Pause": "暂停" "Pause": "暂停"
@@ -3012,7 +3012,7 @@
"Repeat": "重复" "Repeat": "重复"
}, },
"Replacement": { "Replacement": {
"Replacement": "替换" "Replacement": ""
}, },
"Report": { "Report": {
"Report": "报告" "Report": "报告"
@@ -3648,7 +3648,7 @@
"Sync Mode with Portal": "同步系统深色模式" "Sync Mode with Portal": "同步系统深色模式"
}, },
"Sync Position Across Screens": { "Sync Position Across Screens": {
"Sync Position Across Screens": "在显示器间同步位置" "Sync Position Across Screens": ""
}, },
"Sync dark mode with settings portals for system-wide theme hints": { "Sync dark mode with settings portals for system-wide theme hints": {
"Sync dark mode with settings portals for system-wide theme hints": "随系统设置开启深色模式,以适配全局主题" "Sync dark mode with settings portals for system-wide theme hints": "随系统设置开启深色模式,以适配全局主题"
@@ -3876,7 +3876,7 @@
"Unfocused Color": "未聚焦颜色" "Unfocused Color": "未聚焦颜色"
}, },
"Ungrouped": { "Ungrouped": {
"Ungrouped": "已解除分组" "Ungrouped": ""
}, },
"Uninstall Plugin": { "Uninstall Plugin": {
"Uninstall Plugin": "卸载插件" "Uninstall Plugin": "卸载插件"
@@ -3978,13 +3978,13 @@
"Use light theme instead of dark theme": "使用浅色主题替代深色主题" "Use light theme instead of dark theme": "使用浅色主题替代深色主题"
}, },
"Use smaller notification cards": { "Use smaller notification cards": {
"Use smaller notification cards": "使用更小的通知卡" "Use smaller notification cards": ""
}, },
"Use sound theme from system settings": { "Use sound theme from system settings": {
"Use sound theme from system settings": "使用系统设置中的声音主题" "Use sound theme from system settings": "使用系统设置中的声音主题"
}, },
"Use the same position and size on all displays": { "Use the same position and size on all displays": {
"Use the same position and size on all displays": "在所有显示器上使用同样的位置与大小" "Use the same position and size on all displays": ""
}, },
"Use trigger prefix to activate": { "Use trigger prefix to activate": {
"Use trigger prefix to activate": "使用触发前缀以激活" "Use trigger prefix to activate": "使用触发前缀以激活"