mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Compare commits
4 Commits
f2be6cfeb1
...
hotfix-1.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b76d0ce97d | ||
|
|
fa66d330cf | ||
|
|
157eab2d07 | ||
|
|
f50ad2dc22 |
78
.github/workflows/run-obs.yml
vendored
78
.github/workflows/run-obs.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
This file is more of a quick reference so I know what to account for before next releases.
|
This file is more of a quick reference so I know what to account for before next releases.
|
||||||
|
|
||||||
# 1.4.0
|
|
||||||
|
|
||||||
- Overhauled system monitor, graphs, styling
|
|
||||||
- dbus API for plugins, KDEConnect
|
|
||||||
- new dank16 algorithm
|
|
||||||
- launcher actions, customize env, args, name, icon
|
|
||||||
|
|
||||||
# 1.2.0
|
# 1.2.0
|
||||||
|
|
||||||
- Added clipboard and clipboard history integration
|
- Added clipboard and clipboard history integration
|
||||||
|
|||||||
1
Makefile
1
Makefile
@@ -43,6 +43,7 @@ install-shell:
|
|||||||
@mkdir -p $(SHELL_INSTALL_DIR)
|
@mkdir -p $(SHELL_INSTALL_DIR)
|
||||||
@cp -r $(SHELL_DIR)/* $(SHELL_INSTALL_DIR)/
|
@cp -r $(SHELL_DIR)/* $(SHELL_INSTALL_DIR)/
|
||||||
@rm -rf $(SHELL_INSTALL_DIR)/.git* $(SHELL_INSTALL_DIR)/.github
|
@rm -rf $(SHELL_INSTALL_DIR)/.git* $(SHELL_INSTALL_DIR)/.github
|
||||||
|
@$(MAKE) --no-print-directory -C $(CORE_DIR) print-version > $(SHELL_INSTALL_DIR)/VERSION
|
||||||
@echo "Shell files installed"
|
@echo "Shell files installed"
|
||||||
|
|
||||||
install-completions:
|
install-completions:
|
||||||
|
|||||||
@@ -511,8 +511,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
colorCmd,
|
colorCmd,
|
||||||
screenshotCmd,
|
screenshotCmd,
|
||||||
notifyActionCmd,
|
notifyActionCmd,
|
||||||
notifyCmd,
|
|
||||||
genericNotifyActionCmd,
|
|
||||||
matugenCmd,
|
matugenCmd,
|
||||||
clipboardCmd,
|
clipboardCmd,
|
||||||
doctorCmd,
|
doctorCmd,
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/notify"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
notifyAppName string
|
|
||||||
notifyIcon string
|
|
||||||
notifyFile string
|
|
||||||
notifyTimeout int
|
|
||||||
)
|
|
||||||
|
|
||||||
var notifyCmd = &cobra.Command{
|
|
||||||
Use: "notify <summary> [body]",
|
|
||||||
Short: "Send a desktop notification",
|
|
||||||
Long: `Send a desktop notification with optional actions.
|
|
||||||
|
|
||||||
If --file is provided, the notification will have "Open" and "Open Folder" actions.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
dms notify "Hello" "World"
|
|
||||||
dms notify "File received" "photo.jpg" --file ~/Downloads/photo.jpg --icon smartphone
|
|
||||||
dms notify "Download complete" --file ~/Downloads/file.zip --app "My App"`,
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
|
||||||
Run: runNotify,
|
|
||||||
}
|
|
||||||
|
|
||||||
var genericNotifyActionCmd = &cobra.Command{
|
|
||||||
Use: "notify-action-generic",
|
|
||||||
Hidden: true,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
notify.RunActionListener(args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
notifyCmd.Flags().StringVar(¬ifyAppName, "app", "DMS", "Application name")
|
|
||||||
notifyCmd.Flags().StringVar(¬ifyIcon, "icon", "", "Icon name or path")
|
|
||||||
notifyCmd.Flags().StringVar(¬ifyFile, "file", "", "File path (enables Open/Open Folder actions)")
|
|
||||||
notifyCmd.Flags().IntVar(¬ifyTimeout, "timeout", 5000, "Timeout in milliseconds")
|
|
||||||
}
|
|
||||||
|
|
||||||
func runNotify(cmd *cobra.Command, args []string) {
|
|
||||||
summary := args[0]
|
|
||||||
body := ""
|
|
||||||
if len(args) > 1 {
|
|
||||||
body = args[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
n := notify.Notification{
|
|
||||||
AppName: notifyAppName,
|
|
||||||
Icon: notifyIcon,
|
|
||||||
Summary: summary,
|
|
||||||
Body: body,
|
|
||||||
FilePath: notifyFile,
|
|
||||||
Timeout: int32(notifyTimeout),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := notify.Send(n); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
core/go.mod
20
core/go.mod
@@ -16,21 +16,21 @@ require (
|
|||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.etcd.io/bbolt v1.4.3
|
go.etcd.io/bbolt v1.4.3
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93
|
||||||
golang.org/x/image v0.35.0
|
golang.org/x/image v0.34.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.7.0 // indirect
|
github.com/clipperhouse/displaywidth v0.6.2 // indirect
|
||||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.2 // indirect
|
github.com/cloudflare/circl v1.6.2 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc // indirect
|
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
github.com/go-logfmt/logfmt v0.6.1 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||||
@@ -38,8 +38,8 @@ require (
|
|||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.46.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -47,12 +47,12 @@ require (
|
|||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
github.com/charmbracelet/colorprofile v0.4.1 // indirect
|
||||||
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
github.com/charmbracelet/harmonica v0.2.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.11.4 // indirect
|
github.com/charmbracelet/x/ansi v0.11.3 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6
|
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0
|
github.com/lucasb-eyer/go-colorful v1.3.0
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
@@ -66,7 +66,7 @@ require (
|
|||||||
github.com/spf13/afero v1.15.0
|
github.com/spf13/afero v1.15.0
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sys v0.40.0
|
golang.org/x/sys v0.39.0
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.32.0
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
55
core/go.sum
55
core/go.sum
@@ -16,6 +16,8 @@ github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u
|
|||||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
|
||||||
|
github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
|
||||||
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
|
||||||
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
|
||||||
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
|
||||||
@@ -24,22 +26,24 @@ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoF
|
|||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||||
|
github.com/charmbracelet/x/ansi v0.11.2 h1:XAG3FSjiVtFvgEgGrNBkCNNYrsucAt8c6bfxHyROLLs=
|
||||||
|
github.com/charmbracelet/x/ansi v0.11.2/go.mod h1:9tY2bzX5SiJCU0iWyskjBeI2BRQfvPqI+J760Mjf+Rg=
|
||||||
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
|
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
|
||||||
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
|
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
|
||||||
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
|
github.com/clipperhouse/displaywidth v0.6.0 h1:k32vueaksef9WIKCNcoqRNyKbyvkvkysNYnAWz2fN4s=
|
||||||
|
github.com/clipperhouse/displaywidth v0.6.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||||
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
|
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
|
||||||
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||||
github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
|
|
||||||
github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
|
||||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||||
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
|
||||||
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||||
|
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||||
|
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
|
||||||
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
@@ -48,6 +52,8 @@ github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
@@ -58,19 +64,22 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||||
|
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0 h1:eY5aB2GXiVdgTueBcqsBt53WuJTRZAuCdIS/86Pcq5c=
|
||||||
|
github.com/go-git/go-billy/v6 v6.0.0-20251126203821-7f9c95185ee0/go.mod h1:0NjwVNrwtVFZBReAp5OoGklGJIgJFEbVyHneAr4lc8k=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0=
|
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
|
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc h1:rhkjrnRkamkRC7woapp425E4CAH6RPcqsS9X8LA93IY=
|
github.com/go-git/go-git-fixtures/v5 v5.1.1 h1:OH8i1ojV9bWfr0ZfasfpgtUXQHQyVS8HXik/V1C099w=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
|
github.com/go-git/go-git-fixtures/v5 v5.1.1/go.mod h1:Altk43lx3b1ks+dVoAG2300o5WWUnktvfY3VI6bcaXU=
|
||||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs=
|
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs=
|
||||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI=
|
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805 h1:jxQ3BzYeErNRvlI/4+0mpwqMzvB4g97U+ksfgvrUEbY=
|
||||||
|
github.com/go-git/go-git/v6 v6.0.0-20251128074608-48f817f57805/go.mod h1:dIwT3uWK1ooHInyVnK2JS5VfQ3peVGYaw2QPqX7uFvs=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 h1:0lz2eJScP8v5YZQsrEw+ggWC5jNySjg4bIZo5BIh6iI=
|
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 h1:0lz2eJScP8v5YZQsrEw+ggWC5jNySjg4bIZo5BIh6iI=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19/go.mod h1:L+Evfcs7EdTqxwv854354cb6+++7TFL3hJn3Wy4g+3w=
|
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19/go.mod h1:L+Evfcs7EdTqxwv854354cb6+++7TFL3hJn3Wy4g+3w=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6 h1:Yo1MlE8LpvD0pr7mZ04b6hKZKQcPvLrQFgyY1jNMEyU=
|
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6/go.mod h1:enMzPHv+9hL4B7tH7OJGQKNzCkMzXovUoaiXfsLF7Xs=
|
|
||||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||||
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
|
||||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
|
||||||
|
github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
@@ -118,12 +127,16 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sblinch/kdl-go v0.0.0-20250930225324-bf4099d4614a h1:8ZZwZWIQKC0YVMyaCkbrdeI8faTjD1QBrRAAWc1TjMI=
|
||||||
|
github.com/sblinch/kdl-go v0.0.0-20250930225324-bf4099d4614a/go.mod h1:b3oNGuAKOQzhsCKmuLc/urEOPzgHj6fB8vl8bwTBh28=
|
||||||
github.com/sblinch/kdl-go v0.0.0-20251203232544-981d4ecc17c3 h1:msKaIZrrNpvofLPDzNBW3152PJBsnPZsoNNosOCS+C0=
|
github.com/sblinch/kdl-go v0.0.0-20251203232544-981d4ecc17c3 h1:msKaIZrrNpvofLPDzNBW3152PJBsnPZsoNNosOCS+C0=
|
||||||
github.com/sblinch/kdl-go v0.0.0-20251203232544-981d4ecc17c3/go.mod h1:b3oNGuAKOQzhsCKmuLc/urEOPzgHj6fB8vl8bwTBh28=
|
github.com/sblinch/kdl-go v0.0.0-20251203232544-981d4ecc17c3/go.mod h1:b3oNGuAKOQzhsCKmuLc/urEOPzgHj6fB8vl8bwTBh28=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
||||||
|
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
||||||
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
@@ -140,37 +153,33 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
|
|||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||||
|
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
||||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
|
||||||
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
||||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
||||||
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
|
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||||
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
|
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||||
|
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,170 +0,0 @@
|
|||||||
package notify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
notifyDest = "org.freedesktop.Notifications"
|
|
||||||
notifyPath = "/org/freedesktop/Notifications"
|
|
||||||
notifyInterface = "org.freedesktop.Notifications"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
AppName string
|
|
||||||
Icon string
|
|
||||||
Summary string
|
|
||||||
Body string
|
|
||||||
FilePath string
|
|
||||||
Timeout int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func Send(n Notification) error {
|
|
||||||
conn, err := dbus.SessionBus()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("dbus session failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if n.AppName == "" {
|
|
||||||
n.AppName = "DMS"
|
|
||||||
}
|
|
||||||
if n.Timeout == 0 {
|
|
||||||
n.Timeout = 5000
|
|
||||||
}
|
|
||||||
|
|
||||||
var actions []string
|
|
||||||
if n.FilePath != "" {
|
|
||||||
actions = []string{
|
|
||||||
"open", "Open",
|
|
||||||
"folder", "Open Folder",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hints := map[string]dbus.Variant{}
|
|
||||||
if n.FilePath != "" {
|
|
||||||
hints["image_path"] = dbus.MakeVariant(n.FilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := conn.Object(notifyDest, notifyPath)
|
|
||||||
call := obj.Call(
|
|
||||||
notifyInterface+".Notify",
|
|
||||||
0,
|
|
||||||
n.AppName,
|
|
||||||
uint32(0),
|
|
||||||
n.Icon,
|
|
||||||
n.Summary,
|
|
||||||
n.Body,
|
|
||||||
actions,
|
|
||||||
hints,
|
|
||||||
n.Timeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
if call.Err != nil {
|
|
||||||
return fmt.Errorf("notify call failed: %w", call.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var notificationID uint32
|
|
||||||
if err := call.Store(¬ificationID); err != nil {
|
|
||||||
return fmt.Errorf("failed to get notification id: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(actions) > 0 && n.FilePath != "" {
|
|
||||||
spawnActionListener(notificationID, n.FilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func spawnActionListener(notificationID uint32, filePath string) {
|
|
||||||
exe, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(exe, "notify-action-generic", fmt.Sprintf("%d", notificationID), filePath)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
||||||
Setsid: true,
|
|
||||||
}
|
|
||||||
cmd.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func RunActionListener(args []string) {
|
|
||||||
if len(args) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
notificationID, err := strconv.ParseUint(args[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
filePath := args[1]
|
|
||||||
|
|
||||||
conn, err := dbus.SessionBus()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conn.AddMatchSignal(
|
|
||||||
dbus.WithMatchObjectPath(notifyPath),
|
|
||||||
dbus.WithMatchInterface(notifyInterface),
|
|
||||||
); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
signals := make(chan *dbus.Signal, 10)
|
|
||||||
conn.Signal(signals)
|
|
||||||
|
|
||||||
for sig := range signals {
|
|
||||||
switch sig.Name {
|
|
||||||
case notifyInterface + ".ActionInvoked":
|
|
||||||
if len(sig.Body) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id, ok := sig.Body[0].(uint32)
|
|
||||||
if !ok || id != uint32(notificationID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
action, ok := sig.Body[1].(string)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
handleAction(action, filePath)
|
|
||||||
return
|
|
||||||
|
|
||||||
case notifyInterface + ".NotificationClosed":
|
|
||||||
if len(sig.Body) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
id, ok := sig.Body[0].(uint32)
|
|
||||||
if !ok || id != uint32(notificationID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleAction(action, filePath string) {
|
|
||||||
switch action {
|
|
||||||
case "open", "default":
|
|
||||||
openPath(filePath)
|
|
||||||
case "folder":
|
|
||||||
openPath(filepath.Dir(filePath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func openPath(path string) {
|
|
||||||
cmd := exec.Command("xdg-open", path)
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
||||||
Setsid: true,
|
|
||||||
}
|
|
||||||
cmd.Start()
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,15 +110,17 @@ func (m *Manager) updateAdapterState() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
powered, _ := poweredVar.Value().(bool)
|
||||||
|
|
||||||
discoveringVar, err := obj.GetProperty(adapter1Iface + ".Discovering")
|
discoveringVar, err := obj.GetProperty(adapter1Iface + ".Discovering")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
discovering, _ := discoveringVar.Value().(bool)
|
||||||
|
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
m.state.Powered = dbusutil.AsOr(poweredVar, false)
|
m.state.Powered = powered
|
||||||
m.state.Discovering = dbusutil.AsOr(discoveringVar, false)
|
m.state.Discovering = discovering
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -168,20 +169,65 @@ func (m *Manager) updateDevices() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) deviceFromProps(path string, props map[string]dbus.Variant) Device {
|
func (m *Manager) deviceFromProps(path string, props map[string]dbus.Variant) Device {
|
||||||
return Device{
|
dev := Device{Path: path}
|
||||||
Path: path,
|
|
||||||
Address: dbusutil.GetOr(props, "Address", ""),
|
if v, ok := props["Address"]; ok {
|
||||||
Name: dbusutil.GetOr(props, "Name", ""),
|
if addr, ok := v.Value().(string); ok {
|
||||||
Alias: dbusutil.GetOr(props, "Alias", ""),
|
dev.Address = addr
|
||||||
Paired: dbusutil.GetOr(props, "Paired", false),
|
}
|
||||||
Trusted: dbusutil.GetOr(props, "Trusted", false),
|
|
||||||
Blocked: dbusutil.GetOr(props, "Blocked", false),
|
|
||||||
Connected: dbusutil.GetOr(props, "Connected", false),
|
|
||||||
Class: dbusutil.GetOr(props, "Class", uint32(0)),
|
|
||||||
Icon: dbusutil.GetOr(props, "Icon", ""),
|
|
||||||
RSSI: dbusutil.GetOr(props, "RSSI", int16(0)),
|
|
||||||
LegacyPairing: dbusutil.GetOr(props, "LegacyPairing", false),
|
|
||||||
}
|
}
|
||||||
|
if v, ok := props["Name"]; ok {
|
||||||
|
if name, ok := v.Value().(string); ok {
|
||||||
|
dev.Name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Alias"]; ok {
|
||||||
|
if alias, ok := v.Value().(string); ok {
|
||||||
|
dev.Alias = alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Paired"]; ok {
|
||||||
|
if paired, ok := v.Value().(bool); ok {
|
||||||
|
dev.Paired = paired
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Trusted"]; ok {
|
||||||
|
if trusted, ok := v.Value().(bool); ok {
|
||||||
|
dev.Trusted = trusted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Blocked"]; ok {
|
||||||
|
if blocked, ok := v.Value().(bool); ok {
|
||||||
|
dev.Blocked = blocked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Connected"]; ok {
|
||||||
|
if connected, ok := v.Value().(bool); ok {
|
||||||
|
dev.Connected = connected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Class"]; ok {
|
||||||
|
if class, ok := v.Value().(uint32); ok {
|
||||||
|
dev.Class = class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Icon"]; ok {
|
||||||
|
if icon, ok := v.Value().(string); ok {
|
||||||
|
dev.Icon = icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["RSSI"]; ok {
|
||||||
|
if rssi, ok := v.Value().(int16); ok {
|
||||||
|
dev.RSSI = rssi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["LegacyPairing"]; ok {
|
||||||
|
if legacy, ok := v.Value().(bool); ok {
|
||||||
|
dev.LegacyPairing = legacy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dev
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) startAgent() error {
|
func (m *Manager) startAgent() error {
|
||||||
@@ -282,13 +328,17 @@ func (m *Manager) handleAdapterPropertiesChanged(changed map[string]dbus.Variant
|
|||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
dirty := false
|
dirty := false
|
||||||
|
|
||||||
if powered, ok := dbusutil.Get[bool](changed, "Powered"); ok {
|
if v, ok := changed["Powered"]; ok {
|
||||||
m.state.Powered = powered
|
if powered, ok := v.Value().(bool); ok {
|
||||||
dirty = true
|
m.state.Powered = powered
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if discovering, ok := dbusutil.Get[bool](changed, "Discovering"); ok {
|
if v, ok := changed["Discovering"]; ok {
|
||||||
m.state.Discovering = discovering
|
if discovering, ok := v.Value().(bool); ok {
|
||||||
dirty = true
|
m.state.Discovering = discovering
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
@@ -299,28 +349,31 @@ func (m *Manager) handleAdapterPropertiesChanged(changed map[string]dbus.Variant
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed map[string]dbus.Variant) {
|
func (m *Manager) handleDevicePropertiesChanged(path dbus.ObjectPath, changed map[string]dbus.Variant) {
|
||||||
paired, hasPaired := dbusutil.Get[bool](changed, "Paired")
|
pairedVar, hasPaired := changed["Paired"]
|
||||||
_, hasConnected := changed["Connected"]
|
_, hasConnected := changed["Connected"]
|
||||||
_, hasTrusted := changed["Trusted"]
|
_, hasTrusted := changed["Trusted"]
|
||||||
|
|
||||||
if hasPaired {
|
if hasPaired {
|
||||||
devicePath := string(path)
|
devicePath := string(path)
|
||||||
if paired {
|
if paired, ok := pairedVar.Value().(bool); ok {
|
||||||
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
|
if paired {
|
||||||
if wasPending {
|
_, wasPending := m.pendingPairings.LoadAndDelete(devicePath)
|
||||||
select {
|
|
||||||
case m.eventQueue <- func() {
|
if wasPending {
|
||||||
time.Sleep(300 * time.Millisecond)
|
select {
|
||||||
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
|
case m.eventQueue <- func() {
|
||||||
if err := m.ConnectDevice(devicePath); err != nil {
|
time.Sleep(300 * time.Millisecond)
|
||||||
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
|
log.Infof("[Bluetooth] Auto-connecting newly paired device: %s", devicePath)
|
||||||
|
if err := m.ConnectDevice(devicePath); err != nil {
|
||||||
|
log.Warnf("[Bluetooth] Auto-connect failed: %v", err)
|
||||||
|
}
|
||||||
|
}:
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
}:
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m.pendingPairings.Delete(devicePath)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
m.pendingPairings.Delete(devicePath)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,237 +0,0 @@
|
|||||||
package dbus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
type objectParams struct {
|
|
||||||
bus string
|
|
||||||
dest string
|
|
||||||
path string
|
|
||||||
iface string
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractObjectParams(p map[string]any, requirePath bool) (objectParams, error) {
|
|
||||||
bus, err := params.String(p, "bus")
|
|
||||||
if err != nil {
|
|
||||||
return objectParams{}, err
|
|
||||||
}
|
|
||||||
dest, err := params.String(p, "dest")
|
|
||||||
if err != nil {
|
|
||||||
return objectParams{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var path string
|
|
||||||
if requirePath {
|
|
||||||
path, err = params.String(p, "path")
|
|
||||||
if err != nil {
|
|
||||||
return objectParams{}, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
path = params.StringOpt(p, "path", "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
iface, err := params.String(p, "interface")
|
|
||||||
if err != nil {
|
|
||||||
return objectParams{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return objectParams{bus: bus, dest: dest, path: path, iface: iface}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleRequest(conn net.Conn, req models.Request, m *Manager, clientID string) {
|
|
||||||
switch req.Method {
|
|
||||||
case "dbus.call":
|
|
||||||
handleCall(conn, req, m)
|
|
||||||
case "dbus.getProperty":
|
|
||||||
handleGetProperty(conn, req, m)
|
|
||||||
case "dbus.setProperty":
|
|
||||||
handleSetProperty(conn, req, m)
|
|
||||||
case "dbus.getAllProperties":
|
|
||||||
handleGetAllProperties(conn, req, m)
|
|
||||||
case "dbus.introspect":
|
|
||||||
handleIntrospect(conn, req, m)
|
|
||||||
case "dbus.listNames":
|
|
||||||
handleListNames(conn, req, m)
|
|
||||||
case "dbus.subscribe":
|
|
||||||
handleSubscribe(conn, req, m, clientID)
|
|
||||||
case "dbus.unsubscribe":
|
|
||||||
handleUnsubscribe(conn, req, m)
|
|
||||||
default:
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleCall(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
op, err := extractObjectParams(req.Params, true)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
method, err := params.String(req.Params, "method")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var args []any
|
|
||||||
if argsRaw, ok := params.Any(req.Params, "args"); ok {
|
|
||||||
if argsSlice, ok := argsRaw.([]any); ok {
|
|
||||||
args = argsSlice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := m.Call(op.bus, op.dest, op.path, op.iface, method, args)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGetProperty(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
op, err := extractObjectParams(req.Params, true)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
property, err := params.String(req.Params, "property")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := m.GetProperty(op.bus, op.dest, op.path, op.iface, property)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSetProperty(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
op, err := extractObjectParams(req.Params, true)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
property, err := params.String(req.Params, "property")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
value, ok := params.Any(req.Params, "value")
|
|
||||||
if !ok {
|
|
||||||
models.RespondError(conn, req.ID, "missing 'value' parameter")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.SetProperty(op.bus, op.dest, op.path, op.iface, property, value); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, models.SuccessResult{Success: true})
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGetAllProperties(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
op, err := extractObjectParams(req.Params, true)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := m.GetAllProperties(op.bus, op.dest, op.path, op.iface)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleIntrospect(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
bus, err := params.String(req.Params, "bus")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dest, err := params.String(req.Params, "dest")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := params.StringOpt(req.Params, "path", "/")
|
|
||||||
|
|
||||||
result, err := m.Introspect(bus, dest, path)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleListNames(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
bus, err := params.String(req.Params, "bus")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := m.ListNames(bus)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleSubscribe(conn net.Conn, req models.Request, m *Manager, clientID string) {
|
|
||||||
bus, err := params.String(req.Params, "bus")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sender := params.StringOpt(req.Params, "sender", "")
|
|
||||||
path := params.StringOpt(req.Params, "path", "")
|
|
||||||
iface := params.StringOpt(req.Params, "interface", "")
|
|
||||||
member := params.StringOpt(req.Params, "member", "")
|
|
||||||
|
|
||||||
result, err := m.Subscribe(clientID, bus, sender, path, iface, member)
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleUnsubscribe(conn net.Conn, req models.Request, m *Manager) {
|
|
||||||
subID, err := params.String(req.Params, "subscriptionId")
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := m.Unsubscribe(subID); err != nil {
|
|
||||||
models.RespondError(conn, req.ID, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
models.Respond(conn, req.ID, models.SuccessResult{Success: true})
|
|
||||||
}
|
|
||||||
@@ -1,362 +0,0 @@
|
|||||||
package dbus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewManager() (*Manager, error) {
|
|
||||||
systemConn, err := dbus.ConnectSystemBus()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to connect to system bus: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionConn, err := dbus.ConnectSessionBus()
|
|
||||||
if err != nil {
|
|
||||||
systemConn.Close()
|
|
||||||
return nil, fmt.Errorf("failed to connect to session bus: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m := &Manager{
|
|
||||||
systemConn: systemConn,
|
|
||||||
sessionConn: sessionConn,
|
|
||||||
}
|
|
||||||
|
|
||||||
go m.processSystemSignals()
|
|
||||||
go m.processSessionSignals()
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) getConn(bus string) (*dbus.Conn, error) {
|
|
||||||
switch bus {
|
|
||||||
case "system":
|
|
||||||
if m.systemConn == nil {
|
|
||||||
return nil, fmt.Errorf("system bus not connected")
|
|
||||||
}
|
|
||||||
return m.systemConn, nil
|
|
||||||
case "session":
|
|
||||||
if m.sessionConn == nil {
|
|
||||||
return nil, fmt.Errorf("session bus not connected")
|
|
||||||
}
|
|
||||||
return m.sessionConn, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid bus: %s (must be 'system' or 'session')", bus)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Call(bus, dest, path, iface, method string, args []any) (*CallResult, error) {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := conn.Object(dest, dbus.ObjectPath(path))
|
|
||||||
fullMethod := iface + "." + method
|
|
||||||
|
|
||||||
call := obj.Call(fullMethod, 0, args...)
|
|
||||||
if call.Err != nil {
|
|
||||||
return nil, fmt.Errorf("dbus call failed: %w", call.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CallResult{Values: call.Body}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) GetProperty(bus, dest, path, iface, property string) (*PropertyResult, error) {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := conn.Object(dest, dbus.ObjectPath(path))
|
|
||||||
|
|
||||||
var variant dbus.Variant
|
|
||||||
err = obj.Call("org.freedesktop.DBus.Properties.Get", 0, iface, property).Store(&variant)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get property: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &PropertyResult{Value: dbusutil.Normalize(variant.Value())}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) SetProperty(bus, dest, path, iface, property string, value any) error {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := conn.Object(dest, dbus.ObjectPath(path))
|
|
||||||
|
|
||||||
call := obj.Call("org.freedesktop.DBus.Properties.Set", 0, iface, property, dbus.MakeVariant(value))
|
|
||||||
if call.Err != nil {
|
|
||||||
return fmt.Errorf("failed to set property: %w", call.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) GetAllProperties(bus, dest, path, iface string) (map[string]any, error) {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := conn.Object(dest, dbus.ObjectPath(path))
|
|
||||||
|
|
||||||
var props map[string]dbus.Variant
|
|
||||||
err = obj.Call("org.freedesktop.DBus.Properties.GetAll", 0, iface).Store(&props)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get properties: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make(map[string]any)
|
|
||||||
for k, v := range props {
|
|
||||||
result[k] = dbusutil.Normalize(v.Value())
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Introspect(bus, dest, path string) (*IntrospectResult, error) {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := conn.Object(dest, dbus.ObjectPath(path))
|
|
||||||
|
|
||||||
var xml string
|
|
||||||
err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&xml)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to introspect: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &IntrospectResult{XML: xml}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) ListNames(bus string) (*ListNamesResult, error) {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var names []string
|
|
||||||
err = conn.BusObject().Call("org.freedesktop.DBus.ListNames", 0).Store(&names)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to list names: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ListNamesResult{Names: names}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Subscribe(clientID, bus, sender, path, iface, member string) (*SubscribeResult, error) {
|
|
||||||
conn, err := m.getConn(bus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
subID := generateSubscriptionID()
|
|
||||||
|
|
||||||
parts := []string{"type='signal'"}
|
|
||||||
if sender != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("sender='%s'", sender))
|
|
||||||
}
|
|
||||||
if path != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("path='%s'", path))
|
|
||||||
}
|
|
||||||
if iface != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("interface='%s'", iface))
|
|
||||||
}
|
|
||||||
if member != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("member='%s'", member))
|
|
||||||
}
|
|
||||||
matchRule := strings.Join(parts, ",")
|
|
||||||
|
|
||||||
call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, matchRule)
|
|
||||||
if call.Err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to add match rule: %w", call.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sub := &signalSubscription{
|
|
||||||
Bus: bus,
|
|
||||||
Sender: sender,
|
|
||||||
Path: path,
|
|
||||||
Interface: iface,
|
|
||||||
Member: member,
|
|
||||||
ClientID: clientID,
|
|
||||||
}
|
|
||||||
m.subscriptions.Store(subID, sub)
|
|
||||||
|
|
||||||
log.Debugf("dbus: subscribed %s to %s", subID, matchRule)
|
|
||||||
|
|
||||||
return &SubscribeResult{SubscriptionID: subID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(subID string) error {
|
|
||||||
sub, ok := m.subscriptions.LoadAndDelete(subID)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("subscription not found: %s", subID)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := m.getConn(sub.Bus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := []string{"type='signal'"}
|
|
||||||
if sub.Sender != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("sender='%s'", sub.Sender))
|
|
||||||
}
|
|
||||||
if sub.Path != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("path='%s'", sub.Path))
|
|
||||||
}
|
|
||||||
if sub.Interface != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("interface='%s'", sub.Interface))
|
|
||||||
}
|
|
||||||
if sub.Member != "" {
|
|
||||||
parts = append(parts, fmt.Sprintf("member='%s'", sub.Member))
|
|
||||||
}
|
|
||||||
matchRule := strings.Join(parts, ",")
|
|
||||||
|
|
||||||
call := conn.BusObject().Call("org.freedesktop.DBus.RemoveMatch", 0, matchRule)
|
|
||||||
if call.Err != nil {
|
|
||||||
log.Warnf("dbus: failed to remove match rule: %v", call.Err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("dbus: unsubscribed %s", subID)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) UnsubscribeClient(clientID string) {
|
|
||||||
var toDelete []string
|
|
||||||
m.subscriptions.Range(func(subID string, sub *signalSubscription) bool {
|
|
||||||
if sub.ClientID == clientID {
|
|
||||||
toDelete = append(toDelete, subID)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, subID := range toDelete {
|
|
||||||
if err := m.Unsubscribe(subID); err != nil {
|
|
||||||
log.Warnf("dbus: failed to unsubscribe %s: %v", subID, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) SubscribeSignals(clientID string) chan SignalEvent {
|
|
||||||
ch := make(chan SignalEvent, 64)
|
|
||||||
existing, loaded := m.signalSubscribers.LoadOrStore(clientID, ch)
|
|
||||||
if loaded {
|
|
||||||
return existing
|
|
||||||
}
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) UnsubscribeSignals(clientID string) {
|
|
||||||
if ch, ok := m.signalSubscribers.LoadAndDelete(clientID); ok {
|
|
||||||
close(ch)
|
|
||||||
}
|
|
||||||
m.UnsubscribeClient(clientID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) processSystemSignals() {
|
|
||||||
if m.systemConn == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch := make(chan *dbus.Signal, 256)
|
|
||||||
m.systemConn.Signal(ch)
|
|
||||||
|
|
||||||
for sig := range ch {
|
|
||||||
m.dispatchSignal("system", sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) processSessionSignals() {
|
|
||||||
if m.sessionConn == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch := make(chan *dbus.Signal, 256)
|
|
||||||
m.sessionConn.Signal(ch)
|
|
||||||
|
|
||||||
for sig := range ch {
|
|
||||||
m.dispatchSignal("session", sig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) dispatchSignal(bus string, sig *dbus.Signal) {
|
|
||||||
path := string(sig.Path)
|
|
||||||
iface := ""
|
|
||||||
member := sig.Name
|
|
||||||
|
|
||||||
if idx := strings.LastIndex(sig.Name, "."); idx != -1 {
|
|
||||||
iface = sig.Name[:idx]
|
|
||||||
member = sig.Name[idx+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
m.subscriptions.Range(func(subID string, sub *signalSubscription) bool {
|
|
||||||
if sub.Bus != bus {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if sub.Path != "" && sub.Path != path && !strings.HasPrefix(path, sub.Path) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if sub.Interface != "" && sub.Interface != iface {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if sub.Member != "" && sub.Member != member {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
event := SignalEvent{
|
|
||||||
SubscriptionID: subID,
|
|
||||||
Sender: sig.Sender,
|
|
||||||
Path: path,
|
|
||||||
Interface: iface,
|
|
||||||
Member: member,
|
|
||||||
Body: dbusutil.NormalizeSlice(sig.Body),
|
|
||||||
}
|
|
||||||
|
|
||||||
ch, ok := m.signalSubscribers.Load(sub.ClientID)
|
|
||||||
if !ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case ch <- event:
|
|
||||||
default:
|
|
||||||
log.Warnf("dbus: channel full for %s, dropping signal", subID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
|
||||||
m.signalSubscribers.Range(func(clientID string, ch chan SignalEvent) bool {
|
|
||||||
close(ch)
|
|
||||||
m.signalSubscribers.Delete(clientID)
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if m.systemConn != nil {
|
|
||||||
m.systemConn.Close()
|
|
||||||
}
|
|
||||||
if m.sessionConn != nil {
|
|
||||||
m.sessionConn.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func generateSubscriptionID() string {
|
|
||||||
b := make([]byte, 8)
|
|
||||||
if _, err := rand.Read(b); err != nil {
|
|
||||||
log.Warnf("dbus: failed to generate random subscription ID: %v", err)
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(b)
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package dbus
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
systemConn *dbus.Conn
|
|
||||||
sessionConn *dbus.Conn
|
|
||||||
|
|
||||||
subscriptions syncmap.Map[string, *signalSubscription]
|
|
||||||
signalSubscribers syncmap.Map[string, chan SignalEvent]
|
|
||||||
}
|
|
||||||
|
|
||||||
type signalSubscription struct {
|
|
||||||
Bus string
|
|
||||||
Sender string
|
|
||||||
Path string
|
|
||||||
Interface string
|
|
||||||
Member string
|
|
||||||
ClientID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type SignalEvent struct {
|
|
||||||
SubscriptionID string `json:"subscriptionId"`
|
|
||||||
Sender string `json:"sender"`
|
|
||||||
Path string `json:"path"`
|
|
||||||
Interface string `json:"interface"`
|
|
||||||
Member string `json:"member"`
|
|
||||||
Body []any `json:"body"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CallResult struct {
|
|
||||||
Values []any `json:"values"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PropertyResult struct {
|
|
||||||
Value any `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type IntrospectResult struct {
|
|
||||||
XML string `json:"xml"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListNamesResult struct {
|
|
||||||
Names []string `json:"names"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscribeResult struct {
|
|
||||||
SubscriptionID string `json:"subscriptionId"`
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -111,17 +110,61 @@ func (m *Manager) updateAccountsState() error {
|
|||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
defer m.stateMutex.Unlock()
|
defer m.stateMutex.Unlock()
|
||||||
|
|
||||||
m.state.Accounts.IconFile = dbusutil.GetOr(props, "IconFile", "")
|
if v, ok := props["IconFile"]; ok {
|
||||||
m.state.Accounts.RealName = dbusutil.GetOr(props, "RealName", "")
|
if val, ok := v.Value().(string); ok {
|
||||||
m.state.Accounts.UserName = dbusutil.GetOr(props, "UserName", "")
|
m.state.Accounts.IconFile = val
|
||||||
m.state.Accounts.AccountType = dbusutil.GetOr(props, "AccountType", int32(0))
|
}
|
||||||
m.state.Accounts.HomeDirectory = dbusutil.GetOr(props, "HomeDirectory", "")
|
}
|
||||||
m.state.Accounts.Shell = dbusutil.GetOr(props, "Shell", "")
|
if v, ok := props["RealName"]; ok {
|
||||||
m.state.Accounts.Email = dbusutil.GetOr(props, "Email", "")
|
if val, ok := v.Value().(string); ok {
|
||||||
m.state.Accounts.Language = dbusutil.GetOr(props, "Language", "")
|
m.state.Accounts.RealName = val
|
||||||
m.state.Accounts.Location = dbusutil.GetOr(props, "Location", "")
|
}
|
||||||
m.state.Accounts.Locked = dbusutil.GetOr(props, "Locked", false)
|
}
|
||||||
m.state.Accounts.PasswordMode = dbusutil.GetOr(props, "PasswordMode", int32(0))
|
if v, ok := props["UserName"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Accounts.UserName = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["AccountType"]; ok {
|
||||||
|
if val, ok := v.Value().(int32); ok {
|
||||||
|
m.state.Accounts.AccountType = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["HomeDirectory"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Accounts.HomeDirectory = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Shell"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Accounts.Shell = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Email"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Accounts.Email = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Language"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Accounts.Language = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Location"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Accounts.Location = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Locked"]; ok {
|
||||||
|
if val, ok := v.Value().(bool); ok {
|
||||||
|
m.state.Accounts.Locked = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["PasswordMode"]; ok {
|
||||||
|
if val, ok := v.Value().(int32); ok {
|
||||||
|
m.state.Accounts.PasswordMode = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -137,7 +180,7 @@ func (m *Manager) updateSettingsState() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if colorScheme, ok := dbusutil.As[uint32](variant); ok {
|
if colorScheme, ok := variant.Value().(uint32); ok {
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
m.state.Settings.ColorScheme = colorScheme
|
m.state.Settings.ColorScheme = colorScheme
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -133,15 +132,37 @@ func (m *Manager) updateSessionState() error {
|
|||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
defer m.stateMutex.Unlock()
|
defer m.stateMutex.Unlock()
|
||||||
|
|
||||||
m.state.Active = dbusutil.GetOr(props, "Active", m.state.Active)
|
if v, ok := props["Active"]; ok {
|
||||||
m.state.IdleHint = dbusutil.GetOr(props, "IdleHint", m.state.IdleHint)
|
if val, ok := v.Value().(bool); ok {
|
||||||
m.state.IdleSinceHint = dbusutil.GetOr(props, "IdleSinceHint", m.state.IdleSinceHint)
|
m.state.Active = val
|
||||||
if lockedHint, ok := dbusutil.Get[bool](props, "LockedHint"); ok {
|
}
|
||||||
m.state.LockedHint = lockedHint
|
}
|
||||||
m.state.Locked = lockedHint
|
if v, ok := props["IdleHint"]; ok {
|
||||||
|
if val, ok := v.Value().(bool); ok {
|
||||||
|
m.state.IdleHint = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["IdleSinceHint"]; ok {
|
||||||
|
if val, ok := v.Value().(uint64); ok {
|
||||||
|
m.state.IdleSinceHint = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["LockedHint"]; ok {
|
||||||
|
if val, ok := v.Value().(bool); ok {
|
||||||
|
m.state.LockedHint = val
|
||||||
|
m.state.Locked = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Type"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.SessionType = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Class"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.SessionClass = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m.state.SessionType = dbusutil.GetOr(props, "Type", m.state.SessionType)
|
|
||||||
m.state.SessionClass = dbusutil.GetOr(props, "Class", m.state.SessionClass)
|
|
||||||
if v, ok := props["User"]; ok {
|
if v, ok := props["User"]; ok {
|
||||||
if userArr, ok := v.Value().([]any); ok && len(userArr) >= 1 {
|
if userArr, ok := v.Value().([]any); ok && len(userArr) >= 1 {
|
||||||
if uid, ok := userArr[0].(uint32); ok {
|
if uid, ok := userArr[0].(uint32); ok {
|
||||||
@@ -149,12 +170,36 @@ func (m *Manager) updateSessionState() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.state.UserName = dbusutil.GetOr(props, "Name", m.state.UserName)
|
if v, ok := props["Name"]; ok {
|
||||||
m.state.RemoteHost = dbusutil.GetOr(props, "RemoteHost", m.state.RemoteHost)
|
if val, ok := v.Value().(string); ok {
|
||||||
m.state.Service = dbusutil.GetOr(props, "Service", m.state.Service)
|
m.state.UserName = val
|
||||||
m.state.TTY = dbusutil.GetOr(props, "TTY", m.state.TTY)
|
}
|
||||||
m.state.Display = dbusutil.GetOr(props, "Display", m.state.Display)
|
}
|
||||||
m.state.Remote = dbusutil.GetOr(props, "Remote", m.state.Remote)
|
if v, ok := props["RemoteHost"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.RemoteHost = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Service"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Service = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["TTY"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.TTY = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Display"]; ok {
|
||||||
|
if val, ok := v.Value().(string); ok {
|
||||||
|
m.state.Display = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if v, ok := props["Remote"]; ok {
|
||||||
|
if val, ok := v.Value().(bool); ok {
|
||||||
|
m.state.Remote = val
|
||||||
|
}
|
||||||
|
}
|
||||||
if v, ok := props["Seat"]; ok {
|
if v, ok := props["Seat"]; ok {
|
||||||
if seatArr, ok := v.Value().([]any); ok && len(seatArr) >= 1 {
|
if seatArr, ok := v.Value().([]any); ok && len(seatArr) >= 1 {
|
||||||
if seatID, ok := seatArr[0].(string); ok {
|
if seatID, ok := seatArr[0].(string); ok {
|
||||||
@@ -162,7 +207,11 @@ func (m *Manager) updateSessionState() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.state.VTNr = dbusutil.GetOr(props, "VTNr", m.state.VTNr)
|
if v, ok := props["VTNr"]; ok {
|
||||||
|
if val, ok := v.Value().(uint32); ok {
|
||||||
|
m.state.VTNr = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package loginctl
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil"
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,28 +117,31 @@ func (m *Manager) handlePropertiesChanged(sig *dbus.Signal) {
|
|||||||
for key, variant := range changes {
|
for key, variant := range changes {
|
||||||
switch key {
|
switch key {
|
||||||
case "Active":
|
case "Active":
|
||||||
if val, ok := dbusutil.As[bool](variant); ok {
|
if val, ok := variant.Value().(bool); ok {
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
m.state.Active = val
|
m.state.Active = val
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
needsUpdate = true
|
needsUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case "IdleHint":
|
case "IdleHint":
|
||||||
if val, ok := dbusutil.As[bool](variant); ok {
|
if val, ok := variant.Value().(bool); ok {
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
m.state.IdleHint = val
|
m.state.IdleHint = val
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
needsUpdate = true
|
needsUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case "IdleSinceHint":
|
case "IdleSinceHint":
|
||||||
if val, ok := dbusutil.As[uint64](variant); ok {
|
if val, ok := variant.Value().(uint64); ok {
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
m.state.IdleSinceHint = val
|
m.state.IdleSinceHint = val
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
needsUpdate = true
|
needsUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
case "LockedHint":
|
case "LockedHint":
|
||||||
if val, ok := dbusutil.As[bool](variant); ok {
|
if val, ok := variant.Value().(bool); ok {
|
||||||
m.stateMutex.Lock()
|
m.stateMutex.Lock()
|
||||||
m.state.LockedHint = val
|
m.state.LockedHint = val
|
||||||
m.state.Locked = val
|
m.state.Locked = val
|
||||||
|
|||||||
@@ -150,13 +150,21 @@ func (m *Manager) setConnectionPriority(connType string, autoconnectPriority int
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := exec.Command("nmcli", "con", "mod", connName,
|
if err := exec.Command("nmcli", "con", "mod", connName,
|
||||||
"connection.autoconnect-priority", fmt.Sprintf("%d", autoconnectPriority),
|
"connection.autoconnect-priority", fmt.Sprintf("%d", autoconnectPriority)).Run(); err != nil {
|
||||||
"ipv4.route-metric", fmt.Sprintf("%d", routeMetric),
|
log.Warnf("Failed to set autoconnect-priority for %v: %v", connName, err)
|
||||||
"ipv6.route-metric", fmt.Sprintf("%d", routeMetric)).Run(); err != nil {
|
|
||||||
log.Warnf("Failed to set priority for %s: %v", connName, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := exec.Command("nmcli", "con", "mod", connName,
|
||||||
|
"ipv4.route-metric", fmt.Sprintf("%d", routeMetric)).Run(); err != nil {
|
||||||
|
log.Warnf("Failed to set ipv4.route-metric for %v: %v", connName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := exec.Command("nmcli", "con", "mod", connName,
|
||||||
|
"ipv6.route-metric", fmt.Sprintf("%d", routeMetric)).Run(); err != nil {
|
||||||
|
log.Warnf("Failed to set ipv6.route-metric for %v: %v", connName, err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("Updated %v: autoconnect-priority=%d, route-metric=%d", connName, autoconnectPriority, routeMetric)
|
log.Infof("Updated %v: autoconnect-priority=%d, route-metric=%d", connName, autoconnectPriority, routeMetric)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
||||||
@@ -155,15 +154,6 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "dbus.") {
|
|
||||||
if dbusManager == nil {
|
|
||||||
models.RespondError(conn, req.ID, "dbus manager not initialized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
serverDbus.HandleRequest(conn, req, dbusManager, dbusClientID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "clipboard.") {
|
if strings.HasPrefix(req.Method, "clipboard.") {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "clipboard.getConfig":
|
case "clipboard.getConfig":
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/clipboard"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/cups"
|
||||||
serverDbus "github.com/AvengeMedia/DankMaterialShell/core/internal/server/dbus"
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/dwl"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/evdev"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/extworkspace"
|
||||||
@@ -66,11 +65,8 @@ var brightnessManager *brightness.Manager
|
|||||||
var wlrOutputManager *wlroutput.Manager
|
var wlrOutputManager *wlroutput.Manager
|
||||||
var evdevManager *evdev.Manager
|
var evdevManager *evdev.Manager
|
||||||
var clipboardManager *clipboard.Manager
|
var clipboardManager *clipboard.Manager
|
||||||
var dbusManager *serverDbus.Manager
|
|
||||||
var wlContext *wlcontext.SharedContext
|
var wlContext *wlcontext.SharedContext
|
||||||
|
|
||||||
const dbusClientID = "dms-dbus-client"
|
|
||||||
|
|
||||||
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
||||||
var cupsSubscribers syncmap.Map[string, bool]
|
var cupsSubscribers syncmap.Map[string, bool]
|
||||||
var cupsSubscriberCount atomic.Int32
|
var cupsSubscriberCount atomic.Int32
|
||||||
@@ -367,19 +363,6 @@ func InitializeClipboardManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeDbusManager() error {
|
|
||||||
manager, err := serverDbus.NewManager()
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to initialize dbus manager: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dbusManager = manager
|
|
||||||
|
|
||||||
log.Info("DBus manager initialized")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleConnection(conn net.Conn) {
|
func handleConnection(conn net.Conn) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
@@ -457,10 +440,6 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "clipboard")
|
caps = append(caps, "clipboard")
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbusManager != nil {
|
|
||||||
caps = append(caps, "dbus")
|
|
||||||
}
|
|
||||||
|
|
||||||
return Capabilities{Capabilities: caps}
|
return Capabilities{Capabilities: caps}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,10 +498,6 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "clipboard")
|
caps = append(caps, "clipboard")
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbusManager != nil {
|
|
||||||
caps = append(caps, "dbus")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ServerInfo{
|
return ServerInfo{
|
||||||
APIVersion: APIVersion,
|
APIVersion: APIVersion,
|
||||||
CLIVersion: CLIVersion,
|
CLIVersion: CLIVersion,
|
||||||
@@ -1158,31 +1133,6 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if shouldSubscribe("dbus") && dbusManager != nil {
|
|
||||||
wg.Add(1)
|
|
||||||
dbusChan := dbusManager.SubscribeSignals(dbusClientID)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
defer dbusManager.UnsubscribeSignals(dbusClientID)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event, ok := <-dbusChan:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case eventChan <- ServiceEvent{Service: "dbus", Data: event}:
|
|
||||||
case <-stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-stopChan:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
close(eventChan)
|
close(eventChan)
|
||||||
@@ -1248,9 +1198,6 @@ func cleanupManagers() {
|
|||||||
if clipboardManager != nil {
|
if clipboardManager != nil {
|
||||||
clipboardManager.Close()
|
clipboardManager.Close()
|
||||||
}
|
}
|
||||||
if dbusManager != nil {
|
|
||||||
dbusManager.Close()
|
|
||||||
}
|
|
||||||
if wlContext != nil {
|
if wlContext != nil {
|
||||||
wlContext.Close()
|
wlContext.Close()
|
||||||
}
|
}
|
||||||
@@ -1543,14 +1490,6 @@ func Start(printDocs bool) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
|
||||||
if err := InitializeDbusManager(); err != nil {
|
|
||||||
log.Warnf("DBus manager unavailable: %v", err)
|
|
||||||
} else {
|
|
||||||
notifyCapabilityChange()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
log.Info("")
|
log.Info("")
|
||||||
log.Infof("Ready! Capabilities: %v", getCapabilities().Capabilities)
|
log.Infof("Ready! Capabilities: %v", getCapabilities().Capabilities)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package dbusutil
|
|
||||||
|
|
||||||
import "github.com/godbus/dbus/v5"
|
|
||||||
|
|
||||||
func As[T any](v dbus.Variant) (T, bool) {
|
|
||||||
val, ok := v.Value().(T)
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func AsOr[T any](v dbus.Variant, def T) T {
|
|
||||||
if val, ok := v.Value().(T); ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
|
|
||||||
func Get[T any](m map[string]dbus.Variant, key string) (T, bool) {
|
|
||||||
v, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
var zero T
|
|
||||||
return zero, false
|
|
||||||
}
|
|
||||||
return As[T](v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetOr[T any](m map[string]dbus.Variant, key string, def T) T {
|
|
||||||
v, ok := m[key]
|
|
||||||
if !ok {
|
|
||||||
return def
|
|
||||||
}
|
|
||||||
return AsOr(v, def)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Normalize(v any) any {
|
|
||||||
switch val := v.(type) {
|
|
||||||
case dbus.Variant:
|
|
||||||
return Normalize(val.Value())
|
|
||||||
case dbus.ObjectPath:
|
|
||||||
return string(val)
|
|
||||||
case []dbus.ObjectPath:
|
|
||||||
result := make([]string, len(val))
|
|
||||||
for i, p := range val {
|
|
||||||
result[i] = string(p)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
case map[string]dbus.Variant:
|
|
||||||
result := make(map[string]any)
|
|
||||||
for k, vv := range val {
|
|
||||||
result[k] = Normalize(vv.Value())
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
case []any:
|
|
||||||
result := make([]any, len(val))
|
|
||||||
for i, item := range val {
|
|
||||||
result[i] = Normalize(item)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
default:
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NormalizeSlice(values []any) []any {
|
|
||||||
result := make([]any, len(values))
|
|
||||||
for i, v := range values {
|
|
||||||
result[i] = Normalize(v)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
package dbusutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAs(t *testing.T) {
|
|
||||||
t.Run("string", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant("hello")
|
|
||||||
val, ok := As[string](v)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, "hello", val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bool", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant(true)
|
|
||||||
val, ok := As[bool](v)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.True(t, val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("int32", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant(int32(42))
|
|
||||||
val, ok := As[int32](v)
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, int32(42), val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("wrong type", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant("hello")
|
|
||||||
_, ok := As[int](v)
|
|
||||||
assert.False(t, ok)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAsOr(t *testing.T) {
|
|
||||||
t.Run("exists", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant("hello")
|
|
||||||
val := AsOr(v, "default")
|
|
||||||
assert.Equal(t, "hello", val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("wrong type uses default", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant(123)
|
|
||||||
val := AsOr(v, "default")
|
|
||||||
assert.Equal(t, "default", val)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
|
||||||
m := map[string]dbus.Variant{
|
|
||||||
"name": dbus.MakeVariant("test"),
|
|
||||||
"enabled": dbus.MakeVariant(true),
|
|
||||||
"count": dbus.MakeVariant(int32(5)),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("exists", func(t *testing.T) {
|
|
||||||
val, ok := Get[string](m, "name")
|
|
||||||
assert.True(t, ok)
|
|
||||||
assert.Equal(t, "test", val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("missing key", func(t *testing.T) {
|
|
||||||
_, ok := Get[string](m, "missing")
|
|
||||||
assert.False(t, ok)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("wrong type", func(t *testing.T) {
|
|
||||||
_, ok := Get[int](m, "name")
|
|
||||||
assert.False(t, ok)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetOr(t *testing.T) {
|
|
||||||
m := map[string]dbus.Variant{
|
|
||||||
"name": dbus.MakeVariant("test"),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("exists", func(t *testing.T) {
|
|
||||||
val := GetOr(m, "name", "default")
|
|
||||||
assert.Equal(t, "test", val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("missing uses default", func(t *testing.T) {
|
|
||||||
val := GetOr(m, "missing", "default")
|
|
||||||
assert.Equal(t, "default", val)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("wrong type uses default", func(t *testing.T) {
|
|
||||||
val := GetOr(m, "name", 42)
|
|
||||||
assert.Equal(t, 42, val)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNormalize(t *testing.T) {
|
|
||||||
t.Run("variant unwrap", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant("hello")
|
|
||||||
result := Normalize(v)
|
|
||||||
assert.Equal(t, "hello", result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("nested variant", func(t *testing.T) {
|
|
||||||
v := dbus.MakeVariant(dbus.MakeVariant("nested"))
|
|
||||||
result := Normalize(v)
|
|
||||||
assert.Equal(t, "nested", result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("object path", func(t *testing.T) {
|
|
||||||
v := dbus.ObjectPath("/org/test")
|
|
||||||
result := Normalize(v)
|
|
||||||
assert.Equal(t, "/org/test", result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("object path slice", func(t *testing.T) {
|
|
||||||
v := []dbus.ObjectPath{"/org/a", "/org/b"}
|
|
||||||
result := Normalize(v)
|
|
||||||
assert.Equal(t, []string{"/org/a", "/org/b"}, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("variant map", func(t *testing.T) {
|
|
||||||
v := map[string]dbus.Variant{
|
|
||||||
"key": dbus.MakeVariant("value"),
|
|
||||||
}
|
|
||||||
result := Normalize(v)
|
|
||||||
expected := map[string]any{"key": "value"}
|
|
||||||
assert.Equal(t, expected, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("any slice", func(t *testing.T) {
|
|
||||||
v := []any{dbus.MakeVariant("a"), dbus.ObjectPath("/b")}
|
|
||||||
result := Normalize(v)
|
|
||||||
expected := []any{"a", "/b"}
|
|
||||||
assert.Equal(t, expected, result)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("passthrough primitives", func(t *testing.T) {
|
|
||||||
assert.Equal(t, "hello", Normalize("hello"))
|
|
||||||
assert.Equal(t, 42, Normalize(42))
|
|
||||||
assert.Equal(t, true, Normalize(true))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNormalizeSlice(t *testing.T) {
|
|
||||||
input := []any{
|
|
||||||
dbus.MakeVariant("a"),
|
|
||||||
dbus.ObjectPath("/b"),
|
|
||||||
"c",
|
|
||||||
}
|
|
||||||
result := NormalizeSlice(input)
|
|
||||||
expected := []any{"a", "/b", "c"}
|
|
||||||
assert.Equal(t, expected, result)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 != { }) {
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
inherit version;
|
inherit version;
|
||||||
pname = "dms-shell";
|
pname = "dms-shell";
|
||||||
src = ./core;
|
src = ./core;
|
||||||
vendorHash = "sha256-lXqOJ0yNlOcXuR3vcuVjFI02Hskmavcasb1Ntf3UlPM=";
|
vendorHash = "sha256-9CnZFtjXXWYELRiBX2UbZvWopnl9Y1ILuK+xP6YQZ9U=";
|
||||||
|
|
||||||
subPackages = [ "cmd/dms" ];
|
subPackages = [ "cmd/dms" ];
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
Saffron Bloom
|
Spicy Miso
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ Singleton {
|
|||||||
property bool _isReadOnly: false
|
property bool _isReadOnly: false
|
||||||
property bool _hasUnsavedChanges: false
|
property bool _hasUnsavedChanges: false
|
||||||
property var _loadedSessionSnapshot: null
|
property var _loadedSessionSnapshot: null
|
||||||
readonly property var _hooks: ({})
|
|
||||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
||||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||||
|
|
||||||
@@ -103,10 +102,6 @@ Singleton {
|
|||||||
property string weatherLocation: "New York, NY"
|
property string weatherLocation: "New York, NY"
|
||||||
property string weatherCoordinates: "40.7128,-74.0060"
|
property string weatherCoordinates: "40.7128,-74.0060"
|
||||||
|
|
||||||
property var hiddenApps: []
|
|
||||||
property var appOverrides: ({})
|
|
||||||
property bool searchAppActions: true
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -266,10 +261,6 @@ Singleton {
|
|||||||
_checkSessionWritable();
|
_checkSessionWritable();
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(key, value) {
|
|
||||||
Spec.set(root, key, value, saveSettings, _hooks);
|
|
||||||
}
|
|
||||||
|
|
||||||
function migrateFromUndefinedToV1(settings) {
|
function migrateFromUndefinedToV1(settings) {
|
||||||
console.info("SessionData: Migrating configuration from undefined to version 1");
|
console.info("SessionData: Migrating configuration from undefined to version 1");
|
||||||
if (typeof SettingsData !== "undefined") {
|
if (typeof SettingsData !== "undefined") {
|
||||||
@@ -915,61 +906,6 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideApp(appId) {
|
|
||||||
if (!appId)
|
|
||||||
return;
|
|
||||||
const current = [...hiddenApps];
|
|
||||||
if (current.indexOf(appId) === -1) {
|
|
||||||
current.push(appId);
|
|
||||||
hiddenApps = current;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showApp(appId) {
|
|
||||||
if (!appId)
|
|
||||||
return;
|
|
||||||
hiddenApps = hiddenApps.filter(id => id !== appId);
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAppHidden(appId) {
|
|
||||||
return appId && hiddenApps.indexOf(appId) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAppOverride(appId, overrides) {
|
|
||||||
if (!appId)
|
|
||||||
return;
|
|
||||||
const newOverrides = Object.assign({}, appOverrides);
|
|
||||||
if (!overrides || Object.keys(overrides).length === 0) {
|
|
||||||
delete newOverrides[appId];
|
|
||||||
} else {
|
|
||||||
newOverrides[appId] = overrides;
|
|
||||||
}
|
|
||||||
appOverrides = newOverrides;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAppOverride(appId) {
|
|
||||||
if (!appId)
|
|
||||||
return null;
|
|
||||||
return appOverrides[appId] || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearAppOverride(appId) {
|
|
||||||
if (!appId)
|
|
||||||
return;
|
|
||||||
const newOverrides = Object.assign({}, appOverrides);
|
|
||||||
delete newOverrides[appId];
|
|
||||||
appOverrides = newOverrides;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSearchAppActions(enabled) {
|
|
||||||
searchAppActions = enabled;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
function syncWallpaperForCurrentMode() {
|
function syncWallpaperForCurrentMode() {
|
||||||
if (!perModeWallpaper)
|
if (!perModeWallpaper)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -206,7 +205,6 @@ Singleton {
|
|||||||
property bool reverseScrolling: false
|
property bool reverseScrolling: false
|
||||||
property bool dwlShowAllTags: false
|
property bool dwlShowAllTags: false
|
||||||
property string workspaceColorMode: "default"
|
property string workspaceColorMode: "default"
|
||||||
property string workspaceOccupiedColorMode: "none"
|
|
||||||
property string workspaceUnfocusedColorMode: "default"
|
property string workspaceUnfocusedColorMode: "default"
|
||||||
property string workspaceUrgentColorMode: "default"
|
property string workspaceUrgentColorMode: "default"
|
||||||
property bool workspaceFocusedBorderEnabled: false
|
property bool workspaceFocusedBorderEnabled: false
|
||||||
@@ -367,7 +365,6 @@ Singleton {
|
|||||||
|
|
||||||
property bool showDock: false
|
property bool showDock: false
|
||||||
property bool dockAutoHide: false
|
property bool dockAutoHide: false
|
||||||
property bool dockSmartAutoHide: false
|
|
||||||
property bool dockGroupByApp: false
|
property bool dockGroupByApp: false
|
||||||
property bool dockOpenOnOverview: false
|
property bool dockOpenOnOverview: false
|
||||||
property int dockPosition: SettingsData.Position.Bottom
|
property int dockPosition: SettingsData.Position.Bottom
|
||||||
@@ -395,7 +392,6 @@ Singleton {
|
|||||||
property bool lockScreenShowDate: true
|
property bool lockScreenShowDate: true
|
||||||
property bool lockScreenShowProfileImage: true
|
property bool lockScreenShowProfileImage: true
|
||||||
property bool lockScreenShowPasswordField: true
|
property bool lockScreenShowPasswordField: true
|
||||||
property bool lockScreenPowerOffMonitorsOnLock: false
|
|
||||||
|
|
||||||
property bool enableFprint: false
|
property bool enableFprint: false
|
||||||
property int maxFprintTries: 15
|
property int maxFprintTries: 15
|
||||||
@@ -495,8 +491,7 @@ Singleton {
|
|||||||
"shadowIntensity": 0,
|
"shadowIntensity": 0,
|
||||||
"shadowOpacity": 60,
|
"shadowOpacity": 60,
|
||||||
"shadowColorMode": "text",
|
"shadowColorMode": "text",
|
||||||
"shadowCustomColor": "#000000",
|
"shadowCustomColor": "#000000"
|
||||||
"clickThrough": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,23 +55,9 @@ var SPEC = {
|
|||||||
enabledGpuPciIds: { def: [] },
|
enabledGpuPciIds: { def: [] },
|
||||||
|
|
||||||
wifiDeviceOverride: { def: "" },
|
wifiDeviceOverride: { def: "" },
|
||||||
weatherHourlyDetailed: { def: true },
|
weatherHourlyDetailed: { def: true }
|
||||||
|
|
||||||
hiddenApps: { def: [] },
|
|
||||||
appOverrides: { def: {} },
|
|
||||||
searchAppActions: { def: true }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
return Object.keys(SPEC).concat(["configVersion"]);
|
return Object.keys(SPEC).concat(["configVersion"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function set(root, key, value, saveFn, hooks) {
|
|
||||||
if (!(key in SPEC)) return;
|
|
||||||
root[key] = value;
|
|
||||||
var hookName = SPEC[key].onChange;
|
|
||||||
if (hookName && hooks && hooks[hookName]) {
|
|
||||||
hooks[hookName](root);
|
|
||||||
}
|
|
||||||
saveFn();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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 },
|
||||||
@@ -100,7 +99,6 @@ var SPEC = {
|
|||||||
reverseScrolling: { def: false },
|
reverseScrolling: { def: false },
|
||||||
dwlShowAllTags: { def: false },
|
dwlShowAllTags: { def: false },
|
||||||
workspaceColorMode: { def: "default" },
|
workspaceColorMode: { def: "default" },
|
||||||
workspaceOccupiedColorMode: { def: "none" },
|
|
||||||
workspaceUnfocusedColorMode: { def: "default" },
|
workspaceUnfocusedColorMode: { def: "default" },
|
||||||
workspaceUrgentColorMode: { def: "default" },
|
workspaceUrgentColorMode: { def: "default" },
|
||||||
workspaceFocusedBorderEnabled: { def: false },
|
workspaceFocusedBorderEnabled: { def: false },
|
||||||
@@ -232,7 +230,6 @@ var SPEC = {
|
|||||||
|
|
||||||
showDock: { def: false },
|
showDock: { def: false },
|
||||||
dockAutoHide: { def: false },
|
dockAutoHide: { def: false },
|
||||||
dockSmartAutoHide: { def: false },
|
|
||||||
dockGroupByApp: { def: false },
|
dockGroupByApp: { def: false },
|
||||||
dockOpenOnOverview: { def: false },
|
dockOpenOnOverview: { def: false },
|
||||||
dockPosition: { def: 1 },
|
dockPosition: { def: 1 },
|
||||||
@@ -260,7 +257,6 @@ var SPEC = {
|
|||||||
lockScreenShowDate: { def: true },
|
lockScreenShowDate: { def: true },
|
||||||
lockScreenShowProfileImage: { def: true },
|
lockScreenShowProfileImage: { def: true },
|
||||||
lockScreenShowPasswordField: { def: true },
|
lockScreenShowPasswordField: { def: true },
|
||||||
lockScreenPowerOffMonitorsOnLock: { def: false },
|
|
||||||
enableFprint: { def: false },
|
enableFprint: { def: false },
|
||||||
maxFprintTries: { def: 15 },
|
maxFprintTries: { def: 15 },
|
||||||
fprintdAvailable: { def: false, persist: false },
|
fprintdAvailable: { def: false, persist: false },
|
||||||
@@ -358,8 +354,7 @@ var SPEC = {
|
|||||||
shadowIntensity: 0,
|
shadowIntensity: 0,
|
||||||
shadowOpacity: 60,
|
shadowOpacity: 60,
|
||||||
shadowColorMode: "text",
|
shadowColorMode: "text",
|
||||||
shadowCustomColor: "#000000",
|
shadowCustomColor: "#000000"
|
||||||
clickThrough: false
|
|
||||||
}], onChange: "updateBarConfigs" },
|
}], onChange: "updateBarConfigs" },
|
||||||
|
|
||||||
desktopClockEnabled: { def: false },
|
desktopClockEnabled: { def: false },
|
||||||
|
|||||||
@@ -203,8 +203,6 @@ Item {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
dockRecreateDebounce.start();
|
dockRecreateDebounce.start();
|
||||||
// Force PolkitService singleton to initialize
|
|
||||||
PolkitService.polkitAvailable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -317,44 +315,19 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
WifiPasswordModal {
|
||||||
id: wifiPasswordModalLoader
|
id: wifiPasswordModal
|
||||||
active: false
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
PopoutService.wifiPasswordModalLoader = wifiPasswordModalLoader;
|
PopoutService.wifiPasswordModal = wifiPasswordModal;
|
||||||
}
|
|
||||||
|
|
||||||
WifiPasswordModal {
|
|
||||||
id: wifiPasswordModalItem
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
PopoutService.wifiPasswordModal = wifiPasswordModalItem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
PolkitAuthModal {
|
||||||
id: polkitAuthModalLoader
|
id: polkitAuthModal
|
||||||
active: false
|
|
||||||
|
|
||||||
PolkitAuthModal {
|
Component.onCompleted: {
|
||||||
id: polkitAuthModal
|
PopoutService.polkitAuthModal = polkitAuthModal;
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
PopoutService.polkitAuthModal = polkitAuthModal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: PolkitService.agent
|
|
||||||
enabled: PolkitService.polkitAvailable
|
|
||||||
|
|
||||||
function onAuthenticationRequestStarted() {
|
|
||||||
polkitAuthModalLoader.active = true;
|
|
||||||
if (polkitAuthModalLoader.item)
|
|
||||||
polkitAuthModalLoader.item.show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,21 +349,17 @@ Item {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const timeSinceLastPrompt = now - lastCredentialsTime;
|
const timeSinceLastPrompt = now - lastCredentialsTime;
|
||||||
|
|
||||||
wifiPasswordModalLoader.active = true;
|
if (wifiPasswordModal.visible && timeSinceLastPrompt < 1000) {
|
||||||
if (!wifiPasswordModalLoader.item)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (wifiPasswordModalLoader.item.visible && timeSinceLastPrompt < 1000) {
|
|
||||||
NetworkService.cancelCredentials(lastCredentialsToken);
|
NetworkService.cancelCredentials(lastCredentialsToken);
|
||||||
lastCredentialsToken = token;
|
lastCredentialsToken = token;
|
||||||
lastCredentialsTime = now;
|
lastCredentialsTime = now;
|
||||||
wifiPasswordModalLoader.item.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
|
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastCredentialsToken = token;
|
lastCredentialsToken = token;
|
||||||
lastCredentialsTime = now;
|
lastCredentialsTime = now;
|
||||||
wifiPasswordModalLoader.item.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
|
wifiPasswordModal.showFromPrompt(token, ssid, setting, fields, hints, reason, connType, connName, vpnService, fieldsInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -966,17 +963,6 @@ Item {
|
|||||||
return success ? `PLUGIN_DISABLE_SUCCESS: ${pluginId}` : `PLUGIN_DISABLE_FAILED: ${pluginId}`;
|
return success ? `PLUGIN_DISABLE_SUCCESS: ${pluginId}` : `PLUGIN_DISABLE_FAILED: ${pluginId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle(pluginId: string): string {
|
|
||||||
if (!pluginId)
|
|
||||||
return "ERROR: No plugin ID specified";
|
|
||||||
|
|
||||||
if (!PluginService.availablePlugins[pluginId])
|
|
||||||
return `PLUGIN_NOT_FOUND: ${pluginId}`;
|
|
||||||
|
|
||||||
const success = PluginService.togglePlugin(pluginId);
|
|
||||||
return success ? `PLUGIN_TOGGLE_SUCCESS: ${pluginId}` : `PLUGIN_TOGGLE_FAILED: ${pluginId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function list(): string {
|
function list(): string {
|
||||||
const plugins = PluginService.getAvailablePlugins();
|
const plugins = PluginService.getAvailablePlugins();
|
||||||
if (plugins.length === 0)
|
if (plugins.length === 0)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,63 +61,48 @@ 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";
|
|
||||||
}
|
|
||||||
|
|
||||||
function nextTab() {
|
|
||||||
currentTab = (currentTab + 1) % 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function previousTab() {
|
|
||||||
currentTab = (currentTab - 1 + 4) % 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
onCurrentTabChanged: {
|
|
||||||
if (visible && currentTab === 0 && searchField.visible)
|
|
||||||
searchField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
closingModal();
|
closingModal();
|
||||||
searchText = "";
|
|
||||||
expandedPid = "";
|
|
||||||
if (processesTabLoader.item)
|
|
||||||
processesTabLoader.item.reset();
|
|
||||||
DgopService.removeRef(["cpu", "memory", "network", "disk", "system"]);
|
|
||||||
} else {
|
} else {
|
||||||
DgopService.addRef(["cpu", "memory", "network", "disk", "system"]);
|
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
if (currentTab === 0 && searchField.visible)
|
if (contentFocusScope) {
|
||||||
searchField.forceActiveFocus();
|
|
||||||
else 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
|
||||||
parentFocusItem: contentFocusScope
|
|
||||||
onProcessKilled: {
|
|
||||||
if (processesTabLoader.item)
|
|
||||||
processesTabLoader.item.forceRefresh(3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
@@ -130,9 +115,6 @@ FloatingWindow {
|
|||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (processContextMenu.visible)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_1:
|
case Qt.Key_1:
|
||||||
currentTab = 0;
|
currentTab = 0;
|
||||||
@@ -146,43 +128,7 @@ 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_Tab:
|
|
||||||
nextTab();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Backtab:
|
|
||||||
previousTab();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
if (searchText.length > 0) {
|
|
||||||
searchText = "";
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentTab === 0 && processesTabLoader.item?.keyboardNavigationActive) {
|
|
||||||
processesTabLoader.item.reset();
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTab === 0 && processesTabLoader.item)
|
|
||||||
processesTabLoader.item.handleKey(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -215,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
|
||||||
@@ -225,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
|
||||||
@@ -287,278 +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
|
|
||||||
ignoreUpDownKeys: true
|
|
||||||
keyForwardTargets: [contentFocusScope]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pragma ComponentBehavior: Bound
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
@@ -20,10 +19,6 @@ Item {
|
|||||||
property string searchMode: "apps"
|
property string searchMode: "apps"
|
||||||
property bool usePopupContextMenu: false
|
property bool usePopupContextMenu: false
|
||||||
|
|
||||||
property bool editMode: false
|
|
||||||
property var editingApp: null
|
|
||||||
property string editAppId: ""
|
|
||||||
|
|
||||||
function resetScroll() {
|
function resetScroll() {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
resultsView.resetScroll();
|
resultsView.resetScroll();
|
||||||
@@ -48,49 +43,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openEditMode(app) {
|
|
||||||
if (!app)
|
|
||||||
return;
|
|
||||||
editingApp = app;
|
|
||||||
editAppId = app.id || app.execString || app.exec || "";
|
|
||||||
const existing = SessionData.getAppOverride(editAppId);
|
|
||||||
editNameField.text = existing?.name || "";
|
|
||||||
editIconField.text = existing?.icon || "";
|
|
||||||
editCommentField.text = existing?.comment || "";
|
|
||||||
editEnvVarsField.text = existing?.envVars || "";
|
|
||||||
editExtraFlagsField.text = existing?.extraFlags || "";
|
|
||||||
editMode = true;
|
|
||||||
Qt.callLater(() => editNameField.forceActiveFocus());
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeEditMode() {
|
|
||||||
editMode = false;
|
|
||||||
editingApp = null;
|
|
||||||
editAppId = "";
|
|
||||||
Qt.callLater(() => searchField.forceActiveFocus());
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAppOverride() {
|
|
||||||
const override = {};
|
|
||||||
if (editNameField.text.trim())
|
|
||||||
override.name = editNameField.text.trim();
|
|
||||||
if (editIconField.text.trim())
|
|
||||||
override.icon = editIconField.text.trim();
|
|
||||||
if (editCommentField.text.trim())
|
|
||||||
override.comment = editCommentField.text.trim();
|
|
||||||
if (editEnvVarsField.text.trim())
|
|
||||||
override.envVars = editEnvVarsField.text.trim();
|
|
||||||
if (editExtraFlagsField.text.trim())
|
|
||||||
override.extraFlags = editExtraFlagsField.text.trim();
|
|
||||||
SessionData.setAppOverride(editAppId, override);
|
|
||||||
closeEditMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetAppOverride() {
|
|
||||||
SessionData.clearAppOverride(editAppId);
|
|
||||||
closeEditMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
onSearchModeChanged: {
|
onSearchModeChanged: {
|
||||||
if (searchMode === "files") {
|
if (searchMode === "files") {
|
||||||
appLauncher.keyboardNavigationActive = false;
|
appLauncher.keyboardNavigationActive = false;
|
||||||
@@ -103,16 +55,10 @@ Item {
|
|||||||
focus: true
|
focus: true
|
||||||
clip: false
|
clip: false
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (editMode) {
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
|
||||||
closeEditMode();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Down) {
|
} else if (event.key === Qt.Key_Down) {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
@@ -209,6 +155,7 @@ Item {
|
|||||||
if (searchMode === "apps" && appLauncher.model.count > 0) {
|
if (searchMode === "apps" && appLauncher.model.count > 0) {
|
||||||
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
||||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||||
|
|
||||||
if (selectedApp && menu && resultsView) {
|
if (selectedApp && menu && resultsView) {
|
||||||
const itemPos = resultsView.getSelectedItemPosition();
|
const itemPos = resultsView.getSelectedItemPosition();
|
||||||
const contentPos = resultsView.mapToItem(spotlightKeyHandler, itemPos.x, itemPos.y);
|
const contentPos = resultsView.mapToItem(spotlightKeyHandler, itemPos.x, itemPos.y);
|
||||||
@@ -221,6 +168,7 @@ Item {
|
|||||||
|
|
||||||
AppLauncher {
|
AppLauncher {
|
||||||
id: appLauncher
|
id: appLauncher
|
||||||
|
|
||||||
viewMode: SettingsData.spotlightModalViewMode
|
viewMode: SettingsData.spotlightModalViewMode
|
||||||
gridColumns: SettingsData.appLauncherGridColumns
|
gridColumns: SettingsData.appLauncherGridColumns
|
||||||
onAppLaunched: () => {
|
onAppLaunched: () => {
|
||||||
@@ -237,6 +185,7 @@ Item {
|
|||||||
|
|
||||||
FileSearchController {
|
FileSearchController {
|
||||||
id: fileSearchController
|
id: fileSearchController
|
||||||
|
|
||||||
onFileOpened: () => {
|
onFileOpened: () => {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
@@ -248,6 +197,7 @@ Item {
|
|||||||
|
|
||||||
SpotlightContextMenuPopup {
|
SpotlightContextMenuPopup {
|
||||||
id: popupContextMenu
|
id: popupContextMenu
|
||||||
|
|
||||||
parent: spotlightKeyHandler
|
parent: spotlightKeyHandler
|
||||||
appLauncher: spotlightKeyHandler.appLauncher
|
appLauncher: spotlightKeyHandler.appLauncher
|
||||||
parentHandler: spotlightKeyHandler
|
parentHandler: spotlightKeyHandler
|
||||||
@@ -281,37 +231,20 @@ Item {
|
|||||||
target: parentModal
|
target: parentModal
|
||||||
function onSpotlightOpenChanged() {
|
function onSpotlightOpenChanged() {
|
||||||
if (parentModal && !parentModal.spotlightOpen) {
|
if (parentModal && !parentModal.spotlightOpen) {
|
||||||
if (layerContextMenuLoader.item)
|
if (layerContextMenuLoader.item) {
|
||||||
layerContextMenuLoader.item.hide();
|
layerContextMenuLoader.item.hide();
|
||||||
|
}
|
||||||
popupContextMenu.hide();
|
popupContextMenu.hide();
|
||||||
if (editMode)
|
|
||||||
closeEditMode();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enabled: parentModal !== null
|
enabled: parentModal !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: popupContextMenu
|
|
||||||
function onEditAppRequested(app) {
|
|
||||||
spotlightKeyHandler.openEditMode(app);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: layerContextMenuLoader.item
|
|
||||||
function onEditAppRequested(app) {
|
|
||||||
spotlightKeyHandler.openEditMode(app);
|
|
||||||
}
|
|
||||||
enabled: layerContextMenuLoader.item !== null
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
clip: false
|
clip: false
|
||||||
visible: !editMode
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: searchRow
|
id: searchRow
|
||||||
@@ -342,14 +275,18 @@ Item {
|
|||||||
ignoreTabKeys: true
|
ignoreTabKeys: true
|
||||||
keyForwardTargets: [spotlightKeyHandler]
|
keyForwardTargets: [spotlightKeyHandler]
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (searchMode === "apps")
|
if (searchMode === "apps") {
|
||||||
appLauncher.searchQuery = text;
|
appLauncher.searchQuery = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onTextEdited: {
|
||||||
|
updateSearchMode();
|
||||||
}
|
}
|
||||||
onTextEdited: updateSearchMode()
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
@@ -397,10 +334,13 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: listViewArea
|
id: listViewArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: appLauncher.setViewMode("list")
|
onClicked: () => {
|
||||||
|
appLauncher.setViewMode("list");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,10 +359,13 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: gridViewArea
|
id: gridViewArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: appLauncher.setViewMode("grid")
|
onClicked: () => {
|
||||||
|
appLauncher.setViewMode("grid");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -436,6 +379,7 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: filenameFilterButton
|
id: filenameFilterButton
|
||||||
|
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -450,10 +394,13 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: filenameFilterArea
|
id: filenameFilterArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: fileSearchController.searchField = "filename"
|
onClicked: () => {
|
||||||
|
fileSearchController.searchField = "filename";
|
||||||
|
}
|
||||||
onEntered: {
|
onEntered: {
|
||||||
filenameTooltipLoader.active = true;
|
filenameTooltipLoader.active = true;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
@@ -466,6 +413,7 @@ Item {
|
|||||||
onExited: {
|
onExited: {
|
||||||
if (filenameTooltipLoader.item)
|
if (filenameTooltipLoader.item)
|
||||||
filenameTooltipLoader.item.hide();
|
filenameTooltipLoader.item.hide();
|
||||||
|
|
||||||
filenameTooltipLoader.active = false;
|
filenameTooltipLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -473,6 +421,7 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: contentFilterButton
|
id: contentFilterButton
|
||||||
|
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -487,10 +436,13 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: contentFilterArea
|
id: contentFilterArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: fileSearchController.searchField = "body"
|
onClicked: () => {
|
||||||
|
fileSearchController.searchField = "body";
|
||||||
|
}
|
||||||
onEntered: {
|
onEntered: {
|
||||||
contentTooltipLoader.active = true;
|
contentTooltipLoader.active = true;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
@@ -503,6 +455,7 @@ Item {
|
|||||||
onExited: {
|
onExited: {
|
||||||
if (contentTooltipLoader.item)
|
if (contentTooltipLoader.item)
|
||||||
contentTooltipLoader.item.hide();
|
contentTooltipLoader.item.hide();
|
||||||
|
|
||||||
contentTooltipLoader.active = false;
|
contentTooltipLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -521,10 +474,13 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
appLauncher: spotlightKeyHandler.appLauncher
|
appLauncher: spotlightKeyHandler.appLauncher
|
||||||
visible: searchMode === "apps"
|
visible: searchMode === "apps"
|
||||||
|
|
||||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||||
|
|
||||||
if (menu?.show) {
|
if (menu?.show) {
|
||||||
const isPopup = menu.contentItem !== undefined;
|
const isPopup = menu.contentItem !== undefined;
|
||||||
|
|
||||||
if (isPopup) {
|
if (isPopup) {
|
||||||
const localPos = popupContextMenu.parent.mapFromItem(null, mouseX, mouseY);
|
const localPos = popupContextMenu.parent.mapFromItem(null, mouseX, mouseY);
|
||||||
menu.show(localPos.x, localPos.y, modelData, false);
|
menu.show(localPos.x, localPos.y, modelData, false);
|
||||||
@@ -544,320 +500,16 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
id: editView
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
visible: editMode
|
|
||||||
focus: editMode
|
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
closeEditMode();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
saveAppOverride();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_S:
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
saveAppOverride();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_R:
|
|
||||||
if ((event.modifiers & Qt.ControlModifier) && SessionData.getAppOverride(editAppId) !== null) {
|
|
||||||
resetAppOverride();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "arrow_back"
|
|
||||||
size: 20
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: backButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: closeEditMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
|
|
||||||
sourceSize.width: 40
|
|
||||||
sourceSize.height: 40
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Edit App")
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: editingApp?.name || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Theme.outlineMedium
|
|
||||||
}
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - y - buttonsRow.height - Theme.spacingM
|
|
||||||
contentHeight: editFieldsColumn.height
|
|
||||||
clip: true
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: editFieldsColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Name")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editNameField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: editingApp?.name || ""
|
|
||||||
keyNavigationTab: editIconField
|
|
||||||
keyNavigationBacktab: editExtraFlagsField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Icon")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editIconField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: editingApp?.icon || ""
|
|
||||||
keyNavigationTab: editCommentField
|
|
||||||
keyNavigationBacktab: editNameField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Description")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editCommentField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: editingApp?.comment || ""
|
|
||||||
keyNavigationTab: editEnvVarsField
|
|
||||||
keyNavigationBacktab: editIconField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Environment Variables")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "KEY=value KEY2=value2"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 1
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editEnvVarsField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: "VAR=value"
|
|
||||||
keyNavigationTab: editExtraFlagsField
|
|
||||||
keyNavigationBacktab: editCommentField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Extra Arguments")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editExtraFlagsField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: "--flag --option=value"
|
|
||||||
keyNavigationTab: editNameField
|
|
||||||
keyNavigationBacktab: editEnvVarsField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: buttonsRow
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: resetButton
|
|
||||||
width: 90
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
|
||||||
visible: SessionData.getAppOverride(editAppId) !== null
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Reset")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.error
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: resetButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: resetAppOverride()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: cancelButton
|
|
||||||
width: 90
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Cancel")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: closeEditMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
id: saveButton
|
|
||||||
width: 90
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Save")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.primaryText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: saveButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: saveAppOverride()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: filenameTooltipLoader
|
id: filenameTooltipLoader
|
||||||
|
|
||||||
active: false
|
active: false
|
||||||
sourceComponent: DankTooltip {}
|
sourceComponent: DankTooltip {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentTooltipLoader
|
id: contentTooltipLoader
|
||||||
|
|
||||||
active: false
|
active: false
|
||||||
sourceComponent: DankTooltip {}
|
sourceComponent: DankTooltip {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
|
|
||||||
@@ -18,8 +19,6 @@ PanelWindow {
|
|||||||
property real menuPositionX: 0
|
property real menuPositionX: 0
|
||||||
property real menuPositionY: 0
|
property real menuPositionY: 0
|
||||||
|
|
||||||
signal editAppRequested(var app)
|
|
||||||
|
|
||||||
readonly property real shadowBuffer: 5
|
readonly property real shadowBuffer: 5
|
||||||
|
|
||||||
screen: parentModal?.effectiveScreen
|
screen: parentModal?.effectiveScreen
|
||||||
@@ -107,7 +106,6 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onHideRequested: root.hide()
|
onHideRequested: root.hide()
|
||||||
onEditAppRequested: app => root.editAppRequested(app)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -68,27 +68,6 @@ Item {
|
|||||||
hideRequested();
|
hideRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool isRegularApp: desktopEntry && !currentApp?.isPlugin && !currentApp?.isCore && !currentApp?.isAction && !currentApp?.isBuiltInLauncher
|
|
||||||
|
|
||||||
signal editAppRequested(var app)
|
|
||||||
|
|
||||||
function hideCurrentApp() {
|
|
||||||
if (!desktopEntry)
|
|
||||||
return;
|
|
||||||
const appId = desktopEntry.id || desktopEntry.execString || "";
|
|
||||||
SessionData.hideApp(appId);
|
|
||||||
if (appLauncher)
|
|
||||||
appLauncher.updateFilteredModel();
|
|
||||||
hideRequested();
|
|
||||||
}
|
|
||||||
|
|
||||||
function editCurrentApp() {
|
|
||||||
if (!desktopEntry)
|
|
||||||
return;
|
|
||||||
editAppRequested(desktopEntry);
|
|
||||||
hideRequested();
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var menuItems: {
|
readonly property var menuItems: {
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
||||||
@@ -124,21 +103,6 @@ Item {
|
|||||||
action: togglePin
|
action: togglePin
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isRegularApp) {
|
|
||||||
items.push({
|
|
||||||
type: "item",
|
|
||||||
icon: "visibility_off",
|
|
||||||
text: I18n.tr("Hide App"),
|
|
||||||
action: hideCurrentApp
|
|
||||||
});
|
|
||||||
items.push({
|
|
||||||
type: "item",
|
|
||||||
icon: "edit",
|
|
||||||
text: I18n.tr("Edit App"),
|
|
||||||
action: editCurrentApp
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (desktopEntry && desktopEntry.actions) {
|
if (desktopEntry && desktopEntry.actions) {
|
||||||
items.push({
|
items.push({
|
||||||
type: "separator"
|
type: "separator"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
|
|
||||||
@@ -10,8 +11,6 @@ Popup {
|
|||||||
property var parentHandler: null
|
property var parentHandler: null
|
||||||
property var searchField: null
|
property var searchField: null
|
||||||
|
|
||||||
signal editAppRequested(var app)
|
|
||||||
|
|
||||||
function show(x, y, app, fromKeyboard) {
|
function show(x, y, app, fromKeyboard) {
|
||||||
fromKeyboard = fromKeyboard || false;
|
fromKeyboard = fromKeyboard || false;
|
||||||
menuContent.currentApp = app;
|
menuContent.currentApp = app;
|
||||||
@@ -54,7 +53,7 @@ Popup {
|
|||||||
if (parentHandler) {
|
if (parentHandler) {
|
||||||
parentHandler.enabled = true;
|
parentHandler.enabled = true;
|
||||||
}
|
}
|
||||||
if (searchField?.visible) {
|
if (searchField) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
searchField.forceActiveFocus();
|
searchField.forceActiveFocus();
|
||||||
});
|
});
|
||||||
@@ -85,6 +84,5 @@ Popup {
|
|||||||
id: menuContent
|
id: menuContent
|
||||||
appLauncher: root.appLauncher
|
appLauncher: root.appLauncher
|
||||||
onHideRequested: root.hide()
|
onHideRequested: root.hide()
|
||||||
onEditAppRequested: app => root.editAppRequested(app)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,20 +65,6 @@ DankModal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWithEditApp(app) {
|
|
||||||
openedFromOverview = false;
|
|
||||||
isClosing = false;
|
|
||||||
resetContent();
|
|
||||||
spotlightOpen = true;
|
|
||||||
open();
|
|
||||||
Qt.callLater(() => {
|
|
||||||
if (spotlightContent?.appLauncher)
|
|
||||||
spotlightContent.appLauncher.ensureInitialized();
|
|
||||||
if (spotlightContent?.openEditMode)
|
|
||||||
spotlightContent.openEditMode(app);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Widgets
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
import qs.Modules.AppDrawer
|
import qs.Modules.AppDrawer
|
||||||
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankPopout {
|
DankPopout {
|
||||||
@@ -11,63 +15,6 @@ DankPopout {
|
|||||||
|
|
||||||
property string searchMode: "apps"
|
property string searchMode: "apps"
|
||||||
property alias fileSearch: fileSearchController
|
property alias fileSearch: fileSearchController
|
||||||
property bool editMode: false
|
|
||||||
property var editingApp: null
|
|
||||||
property string editAppId: ""
|
|
||||||
|
|
||||||
function openEditMode(app) {
|
|
||||||
if (!app)
|
|
||||||
return;
|
|
||||||
editingApp = app;
|
|
||||||
editAppId = app.id || app.execString || app.exec || "";
|
|
||||||
const existing = SessionData.getAppOverride(editAppId);
|
|
||||||
if (contentLoader.item) {
|
|
||||||
contentLoader.item.searchField.focus = false;
|
|
||||||
contentLoader.item.editNameField.text = existing?.name || "";
|
|
||||||
contentLoader.item.editIconField.text = existing?.icon || "";
|
|
||||||
contentLoader.item.editCommentField.text = existing?.comment || "";
|
|
||||||
contentLoader.item.editEnvVarsField.text = existing?.envVars || "";
|
|
||||||
contentLoader.item.editExtraFlagsField.text = existing?.extraFlags || "";
|
|
||||||
}
|
|
||||||
editMode = true;
|
|
||||||
Qt.callLater(() => {
|
|
||||||
if (contentLoader.item?.editNameField)
|
|
||||||
contentLoader.item.editNameField.forceActiveFocus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeEditMode() {
|
|
||||||
editMode = false;
|
|
||||||
editingApp = null;
|
|
||||||
editAppId = "";
|
|
||||||
Qt.callLater(() => {
|
|
||||||
if (contentLoader.item?.searchField)
|
|
||||||
contentLoader.item.searchField.forceActiveFocus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveAppOverride() {
|
|
||||||
const override = {};
|
|
||||||
if (contentLoader.item) {
|
|
||||||
if (contentLoader.item.editNameField.text.trim())
|
|
||||||
override.name = contentLoader.item.editNameField.text.trim();
|
|
||||||
if (contentLoader.item.editIconField.text.trim())
|
|
||||||
override.icon = contentLoader.item.editIconField.text.trim();
|
|
||||||
if (contentLoader.item.editCommentField.text.trim())
|
|
||||||
override.comment = contentLoader.item.editCommentField.text.trim();
|
|
||||||
if (contentLoader.item.editEnvVarsField.text.trim())
|
|
||||||
override.envVars = contentLoader.item.editEnvVarsField.text.trim();
|
|
||||||
if (contentLoader.item.editExtraFlagsField.text.trim())
|
|
||||||
override.extraFlags = contentLoader.item.editExtraFlagsField.text.trim();
|
|
||||||
}
|
|
||||||
SessionData.setAppOverride(editAppId, override);
|
|
||||||
closeEditMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetAppOverride() {
|
|
||||||
SessionData.clearAppOverride(editAppId);
|
|
||||||
closeEditMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateSearchMode(text) {
|
function updateSearchMode(text) {
|
||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
@@ -95,25 +42,16 @@ DankPopout {
|
|||||||
popupHeight: 600
|
popupHeight: 600
|
||||||
triggerWidth: 40
|
triggerWidth: 40
|
||||||
positioning: ""
|
positioning: ""
|
||||||
contentHandlesKeys: editMode
|
|
||||||
|
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
if (contextMenu.visible) {
|
if (contextMenu.visible) {
|
||||||
contextMenu.close();
|
contextMenu.close();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (editMode) {
|
|
||||||
closeEditMode();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
searchMode = "apps";
|
searchMode = "apps";
|
||||||
editMode = false;
|
|
||||||
editingApp = null;
|
|
||||||
editAppId = "";
|
|
||||||
appLauncher.ensureInitialized();
|
appLauncher.ensureInitialized();
|
||||||
appLauncher.searchQuery = "";
|
appLauncher.searchQuery = "";
|
||||||
appLauncher.selectedIndex = 0;
|
appLauncher.selectedIndex = 0;
|
||||||
@@ -162,45 +100,8 @@ DankPopout {
|
|||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
property alias searchField: searchField
|
property alias searchField: searchField
|
||||||
property alias keyHandler: keyHandler
|
|
||||||
property alias editNameField: editNameField
|
|
||||||
property alias editIconField: editIconField
|
|
||||||
property alias editCommentField: editCommentField
|
|
||||||
property alias editEnvVarsField: editEnvVarsField
|
|
||||||
property alias editExtraFlagsField: editExtraFlagsField
|
|
||||||
|
|
||||||
focus: true
|
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
|
||||||
if (appDrawerPopout.editMode) {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
appDrawerPopout.closeEditMode();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
appDrawerPopout.saveAppOverride();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_S:
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
appDrawerPopout.saveAppOverride();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_R:
|
|
||||||
if ((event.modifiers & Qt.ControlModifier) && SessionData.getAppOverride(appDrawerPopout.editAppId) !== null) {
|
|
||||||
appDrawerPopout.resetAppOverride();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
smooth: true
|
smooth: true
|
||||||
@@ -239,7 +140,7 @@ DankPopout {
|
|||||||
id: keyHandler
|
id: keyHandler
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: !appDrawerPopout.editMode
|
focus: true
|
||||||
|
|
||||||
function selectNext() {
|
function selectNext() {
|
||||||
switch (appDrawerPopout.searchMode) {
|
switch (appDrawerPopout.searchMode) {
|
||||||
@@ -271,29 +172,6 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSelectedItemPosition() {
|
|
||||||
const index = appLauncher.selectedIndex;
|
|
||||||
if (appLauncher.viewMode === "list") {
|
|
||||||
const y = index * (appList.itemHeight + appList.itemSpacing) - appList.contentY;
|
|
||||||
return Qt.point(appList.width / 2, y + appList.itemHeight / 2 + appList.y);
|
|
||||||
}
|
|
||||||
const row = Math.floor(index / appGrid.actualColumns);
|
|
||||||
const col = index % appGrid.actualColumns;
|
|
||||||
const x = col * appGrid.cellWidth + appGrid.cellWidth / 2;
|
|
||||||
const y = row * appGrid.cellHeight - appGrid.contentY + appGrid.cellHeight / 2 + appGrid.y;
|
|
||||||
return Qt.point(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openContextMenuForSelected() {
|
|
||||||
if (appDrawerPopout.searchMode !== "apps" || appLauncher.model.count === 0)
|
|
||||||
return;
|
|
||||||
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
|
||||||
if (!selectedApp)
|
|
||||||
return;
|
|
||||||
const pos = getSelectedItemPosition();
|
|
||||||
contextMenu.show(pos.x, pos.y, selectedApp, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var keyMappings: {
|
readonly property var keyMappings: {
|
||||||
const mappings = {};
|
const mappings = {};
|
||||||
mappings[Qt.Key_Escape] = () => appDrawerPopout.close();
|
mappings[Qt.Key_Escape] = () => appDrawerPopout.close();
|
||||||
@@ -303,8 +181,6 @@ DankPopout {
|
|||||||
mappings[Qt.Key_Enter] = () => keyHandler.activateSelected();
|
mappings[Qt.Key_Enter] = () => keyHandler.activateSelected();
|
||||||
mappings[Qt.Key_Tab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : keyHandler.selectNext();
|
mappings[Qt.Key_Tab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : keyHandler.selectNext();
|
||||||
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
|
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
|
||||||
mappings[Qt.Key_Menu] = () => keyHandler.openContextMenuForSelected();
|
|
||||||
mappings[Qt.Key_F10] = () => keyHandler.openContextMenuForSelected();
|
|
||||||
|
|
||||||
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||||
mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||||
@@ -315,9 +191,6 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (appDrawerPopout.editMode)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (keyMappings[event.key]) {
|
if (keyMappings[event.key]) {
|
||||||
keyMappings[event.key]();
|
keyMappings[event.key]();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
@@ -325,8 +198,9 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasCtrl = event.modifiers & Qt.ControlModifier;
|
const hasCtrl = event.modifiers & Qt.ControlModifier;
|
||||||
if (!hasCtrl)
|
if (!hasCtrl) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_N:
|
case Qt.Key_N:
|
||||||
@@ -360,7 +234,6 @@ DankPopout {
|
|||||||
x: Theme.spacingS
|
x: Theme.spacingS
|
||||||
y: Theme.spacingS
|
y: Theme.spacingS
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: !appDrawerPopout.editMode
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -614,7 +487,7 @@ DankPopout {
|
|||||||
appLauncher.launchApp(modelData);
|
appLauncher.launchApp(modelData);
|
||||||
}
|
}
|
||||||
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
||||||
contextMenu.show(mouseX, mouseY, modelData, false);
|
contextMenu.show(mouseX, mouseY, modelData);
|
||||||
}
|
}
|
||||||
onKeyboardNavigationReset: {
|
onKeyboardNavigationReset: {
|
||||||
appLauncher.keyboardNavigationActive = false;
|
appLauncher.keyboardNavigationActive = false;
|
||||||
@@ -701,7 +574,7 @@ DankPopout {
|
|||||||
appLauncher.launchApp(modelData);
|
appLauncher.launchApp(modelData);
|
||||||
}
|
}
|
||||||
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
||||||
contextMenu.show(mouseX, mouseY, modelData, false);
|
contextMenu.show(mouseX, mouseY, modelData);
|
||||||
}
|
}
|
||||||
onKeyboardNavigationReset: {
|
onKeyboardNavigationReset: {
|
||||||
appLauncher.keyboardNavigationActive = false;
|
appLauncher.keyboardNavigationActive = false;
|
||||||
@@ -742,281 +615,6 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
|
||||||
id: editView
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
visible: appDrawerPopout.editMode
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "arrow_back"
|
|
||||||
size: 20
|
|
||||||
color: Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: backButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: appDrawerPopout.closeEditMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
|
||||||
width: 40
|
|
||||||
height: 40
|
|
||||||
source: appDrawerPopout.editingApp?.icon ? "image://icon/" + appDrawerPopout.editingApp.icon : "image://icon/application-x-executable"
|
|
||||||
sourceSize.width: 40
|
|
||||||
sourceSize.height: 40
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: 2
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Edit App")
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: appDrawerPopout.editingApp?.name || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Theme.outlineMedium
|
|
||||||
}
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
width: parent.width
|
|
||||||
height: parent.height - y - editButtonsRow.height - Theme.spacingM
|
|
||||||
contentHeight: editFieldsColumn.height
|
|
||||||
clip: true
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: editFieldsColumn
|
|
||||||
width: parent.width
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Name")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editNameField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
focus: true
|
|
||||||
placeholderText: appDrawerPopout.editingApp?.name || ""
|
|
||||||
keyNavigationTab: editIconField
|
|
||||||
keyNavigationBacktab: editExtraFlagsField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Icon")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editIconField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: appDrawerPopout.editingApp?.icon || ""
|
|
||||||
keyNavigationTab: editCommentField
|
|
||||||
keyNavigationBacktab: editNameField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Description")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editCommentField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: appDrawerPopout.editingApp?.comment || ""
|
|
||||||
keyNavigationTab: editEnvVarsField
|
|
||||||
keyNavigationBacktab: editIconField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Environment Variables")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: "KEY=value KEY2=value2"
|
|
||||||
font.pixelSize: Theme.fontSizeSmall - 1
|
|
||||||
color: Theme.surfaceVariantText
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editEnvVarsField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: "VAR=value"
|
|
||||||
keyNavigationTab: editExtraFlagsField
|
|
||||||
keyNavigationBacktab: editCommentField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
spacing: 4
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Extra Arguments")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: editExtraFlagsField
|
|
||||||
width: parent.width
|
|
||||||
height: 44
|
|
||||||
placeholderText: "--flag --option=value"
|
|
||||||
keyNavigationTab: editNameField
|
|
||||||
keyNavigationBacktab: editEnvVarsField
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: editButtonsRow
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 90
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
|
||||||
visible: SessionData.getAppOverride(appDrawerPopout.editAppId) !== null
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Reset")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.error
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: resetButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: appDrawerPopout.resetAppOverride()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 90
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Cancel")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: appDrawerPopout.closeEditMode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 90
|
|
||||||
height: 40
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Save")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.onPrimary
|
|
||||||
font.weight: Font.Medium
|
|
||||||
anchors.centerIn: parent
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: saveButtonArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: appDrawerPopout.saveAppOverride()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: contextMenu.visible
|
visible: contextMenu.visible
|
||||||
@@ -1026,21 +624,338 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SpotlightContextMenuPopup {
|
Popup {
|
||||||
id: contextMenu
|
id: contextMenu
|
||||||
|
|
||||||
parent: contentLoader.item
|
property var currentApp: null
|
||||||
appLauncher: appLauncher
|
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
||||||
parentHandler: contentLoader.item?.keyHandler ?? null
|
readonly property string appId: desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : ""
|
||||||
searchField: contentLoader.item?.searchField ?? null
|
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
|
||||||
visible: false
|
|
||||||
z: 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
function show(x, y, app) {
|
||||||
target: contextMenu
|
currentApp = app;
|
||||||
function onEditAppRequested(app) {
|
let finalX = x + 4;
|
||||||
appDrawerPopout.openEditMode(app);
|
let finalY = y + 4;
|
||||||
|
|
||||||
|
if (contextMenu.parent) {
|
||||||
|
const parentWidth = contextMenu.parent.width;
|
||||||
|
const parentHeight = contextMenu.parent.height;
|
||||||
|
const menuWidth = contextMenu.width;
|
||||||
|
const menuHeight = contextMenu.height;
|
||||||
|
|
||||||
|
if (finalX + menuWidth > parentWidth) {
|
||||||
|
finalX = Math.max(0, parentWidth - menuWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalY + menuHeight > parentHeight) {
|
||||||
|
finalY = Math.max(0, parentHeight - menuHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMenu.x = finalX;
|
||||||
|
contextMenu.y = finalY;
|
||||||
|
contextMenu.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
contextMenu.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
||||||
|
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
|
padding: 0
|
||||||
|
closePolicy: Popup.CloseOnPressOutside
|
||||||
|
modal: false
|
||||||
|
dim: false
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.topMargin: 4
|
||||||
|
anchors.leftMargin: 2
|
||||||
|
anchors.rightMargin: -2
|
||||||
|
anchors.bottomMargin: -4
|
||||||
|
radius: parent.radius
|
||||||
|
color: Qt.rgba(0, 0, 0, 0.15)
|
||||||
|
z: -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enter: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 0
|
||||||
|
to: 1
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exit: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "opacity"
|
||||||
|
from: 1
|
||||||
|
to: 0
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.emphasizedEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: menuColumn
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
spacing: 1
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: contextMenu.isPinned ? "keep_off" : "push_pin"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: contextMenu.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: pinMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (!contextMenu.desktopEntry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contextMenu.isPinned) {
|
||||||
|
SessionData.removePinnedApp(contextMenu.appId);
|
||||||
|
} else {
|
||||||
|
SessionData.addPinnedApp(contextMenu.appId);
|
||||||
|
}
|
||||||
|
contextMenu.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: 5
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: contextMenu.desktopEntry && contextMenu.desktopEntry.actions ? contextMenu.desktopEntry.actions : []
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: actionRow
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: Theme.iconSize - 2
|
||||||
|
height: Theme.iconSize - 2
|
||||||
|
visible: modelData.icon && modelData.icon !== ""
|
||||||
|
|
||||||
|
IconImage {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
||||||
|
smooth: true
|
||||||
|
asynchronous: true
|
||||||
|
visible: status === Image.Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: actionMouseArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (modelData && contextMenu.desktopEntry) {
|
||||||
|
SessionService.launchDesktopAction(contextMenu.desktopEntry, modelData);
|
||||||
|
if (contextMenu.currentApp) {
|
||||||
|
appLauncher.appLaunched(contextMenu.currentApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contextMenu.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: contextMenu.desktopEntry && contextMenu.desktopEntry.actions && contextMenu.desktopEntry.actions.length > 0
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: 5
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "launch"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Launch")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: launchMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (contextMenu.currentApp)
|
||||||
|
appLauncher.launchApp(contextMenu.currentApp);
|
||||||
|
|
||||||
|
contextMenu.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: SessionService.nvidiaCommand
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: 5
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: SessionService.nvidiaCommand
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: nvidiaMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: "memory"
|
||||||
|
size: Theme.iconSize - 2
|
||||||
|
color: Theme.surfaceText
|
||||||
|
opacity: 0.7
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Launch on dGPU")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Normal
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: nvidiaMouseArea
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (contextMenu.desktopEntry) {
|
||||||
|
SessionService.launchDesktopEntry(contextMenu.desktopEntry, true);
|
||||||
|
if (contextMenu.currentApp) {
|
||||||
|
appLauncher.appLaunched(contextMenu.currentApp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contextMenu.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,16 +72,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: SessionData
|
|
||||||
function onHiddenAppsChanged() {
|
|
||||||
updateFilteredModel();
|
|
||||||
}
|
|
||||||
function onAppOverridesChanged() {
|
|
||||||
updateFilteredModel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateFilteredModel() {
|
function updateFilteredModel() {
|
||||||
if (suppressUpdatesWhileLaunching) {
|
if (suppressUpdatesWhileLaunching) {
|
||||||
suppressUpdatesWhileLaunching = false;
|
suppressUpdatesWhileLaunching = false;
|
||||||
@@ -134,7 +124,7 @@ Item {
|
|||||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||||
});
|
});
|
||||||
const coreItems = AppSearchService.getCoreApps("");
|
const coreItems = AppSearchService.getCoreApps("");
|
||||||
apps = AppSearchService.getVisibleApplications().concat(emptyTriggerItems).concat(coreItems);
|
apps = AppSearchService.applications.concat(emptyTriggerItems).concat(coreItems);
|
||||||
} else {
|
} else {
|
||||||
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults);
|
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults);
|
||||||
const coreItems = AppSearchService.getCoreApps("").filter(app => app.categories.includes(selectedCategory));
|
const coreItems = AppSearchService.getCoreApps("").filter(app => app.categories.includes(selectedCategory));
|
||||||
@@ -215,7 +205,6 @@ Item {
|
|||||||
"isPlugin": isPluginItem,
|
"isPlugin": isPluginItem,
|
||||||
"isCore": app.isCore === true,
|
"isCore": app.isCore === true,
|
||||||
"isBuiltInLauncher": app.isBuiltInLauncher === true,
|
"isBuiltInLauncher": app.isBuiltInLauncher === true,
|
||||||
"isAction": app.isAction === true,
|
|
||||||
"appIndex": uniqueApps.length - 1,
|
"appIndex": uniqueApps.length - 1,
|
||||||
"pinned": app._pinned === true
|
"pinned": app._pinned === true
|
||||||
});
|
});
|
||||||
@@ -294,13 +283,6 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appData.isAction && actualApp.parentApp && actualApp.actionData) {
|
|
||||||
SessionService.launchDesktopAction(actualApp.parentApp, actualApp.actionData);
|
|
||||||
appLaunched(appData);
|
|
||||||
AppUsageHistoryData.addAppUsage(actualApp.parentApp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionService.launchDesktopEntry(actualApp);
|
SessionService.launchDesktopEntry(actualApp);
|
||||||
appLaunched(appData);
|
appLaunched(appData);
|
||||||
AppUsageHistoryData.addAppUsage(actualApp);
|
AppUsageHistoryData.addAppUsage(actualApp);
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -714,8 +687,8 @@ Rectangle {
|
|||||||
if (modelData.secured && !modelData.saved) {
|
if (modelData.secured && !modelData.saved) {
|
||||||
if (DMSService.apiVersion >= 7) {
|
if (DMSService.apiVersion >= 7) {
|
||||||
NetworkService.connectToWifi(modelData.ssid);
|
NetworkService.connectToWifi(modelData.ssid);
|
||||||
} else {
|
} else if (PopoutService.wifiPasswordModal) {
|
||||||
PopoutService.showWifiPasswordModal(modelData.ssid);
|
PopoutService.wifiPasswordModal.show(modelData.ssid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NetworkService.connectToWifi(modelData.ssid);
|
NetworkService.connectToWifi(modelData.ssid);
|
||||||
@@ -776,8 +749,8 @@ Rectangle {
|
|||||||
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
||||||
if (DMSService.apiVersion >= 7) {
|
if (DMSService.apiVersion >= 7) {
|
||||||
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
||||||
} else {
|
} else if (PopoutService.wifiPasswordModal) {
|
||||||
PopoutService.showWifiPasswordModal(networkContextMenu.currentSSID);
|
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
||||||
|
|||||||
@@ -296,9 +296,6 @@ Item {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
implicitWidth: isVertical ? widgetThickness : totalSize
|
|
||||||
implicitHeight: isVertical ? totalSize : widgetThickness
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: layoutTimer
|
id: layoutTimer
|
||||||
interval: 0
|
interval: 0
|
||||||
@@ -368,7 +365,6 @@ Item {
|
|||||||
onContentItemReady: contentItem => {
|
onContentItemReady: contentItem => {
|
||||||
contentItem.widthChanged.connect(() => layoutTimer.restart());
|
contentItem.widthChanged.connect(() => layoutTimer.restart());
|
||||||
contentItem.heightChanged.connect(() => layoutTimer.restart());
|
contentItem.heightChanged.connect(() => layoutTimer.restart());
|
||||||
layoutTimer.restart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: layoutTimer.restart()
|
onActiveChanged: layoutTimer.restart()
|
||||||
|
|||||||
@@ -20,13 +20,6 @@ Item {
|
|||||||
|
|
||||||
readonly property real innerPadding: barConfig?.innerPadding ?? 4
|
readonly property real innerPadding: barConfig?.innerPadding ?? 4
|
||||||
|
|
||||||
property alias hLeftSection: hLeftSection
|
|
||||||
property alias hCenterSection: hCenterSection
|
|
||||||
property alias hRightSection: hRightSection
|
|
||||||
property alias vLeftSection: vLeftSection
|
|
||||||
property alias vCenterSection: vCenterSection
|
|
||||||
property alias vRightSection: vRightSection
|
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.leftMargin: Math.max(Theme.spacingXS, innerPadding * 0.8)
|
anchors.leftMargin: Math.max(Theme.spacingXS, innerPadding * 0.8)
|
||||||
anchors.rightMargin: Math.max(Theme.spacingXS, innerPadding * 0.8)
|
anchors.rightMargin: Math.max(Theme.spacingXS, innerPadding * 0.8)
|
||||||
|
|||||||
@@ -500,78 +500,8 @@ PanelWindow {
|
|||||||
height: axis.isVertical ? parent.height : maskThickness
|
height: axis.isVertical ? parent.height : maskThickness
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool clickThroughEnabled: barConfig?.clickThrough ?? false
|
|
||||||
|
|
||||||
readonly property var _leftSection: topBarContent ? (barWindow.isVertical ? topBarContent.vLeftSection : topBarContent.hLeftSection) : null
|
|
||||||
readonly property var _centerSection: topBarContent ? (barWindow.isVertical ? topBarContent.vCenterSection : topBarContent.hCenterSection) : null
|
|
||||||
readonly property var _rightSection: topBarContent ? (barWindow.isVertical ? topBarContent.vRightSection : topBarContent.hRightSection) : null
|
|
||||||
|
|
||||||
function sectionRect(section, isCenter) {
|
|
||||||
if (!section)
|
|
||||||
return {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 0,
|
|
||||||
"h": 0
|
|
||||||
};
|
|
||||||
|
|
||||||
const pos = section.mapToItem(barWindow.contentItem, 0, 0);
|
|
||||||
const implW = section.implicitWidth || 0;
|
|
||||||
const implH = section.implicitHeight || 0;
|
|
||||||
|
|
||||||
const offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
|
||||||
const offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
|
||||||
|
|
||||||
const edgePad = 2;
|
|
||||||
return {
|
|
||||||
"x": pos.x + offsetX - edgePad,
|
|
||||||
"y": pos.y + offsetY - edgePad,
|
|
||||||
"w": implW + edgePad * 2,
|
|
||||||
"h": implH + edgePad * 2
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
item: clickThroughEnabled ? null : inputMask
|
item: inputMask
|
||||||
|
|
||||||
Region {
|
|
||||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._leftSection, false) : {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 0,
|
|
||||||
"h": 0
|
|
||||||
}
|
|
||||||
x: r.x
|
|
||||||
y: r.y
|
|
||||||
width: r.w
|
|
||||||
height: r.h
|
|
||||||
}
|
|
||||||
|
|
||||||
Region {
|
|
||||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._centerSection, true) : {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 0,
|
|
||||||
"h": 0
|
|
||||||
}
|
|
||||||
x: r.x
|
|
||||||
y: r.y
|
|
||||||
width: r.w
|
|
||||||
height: r.h
|
|
||||||
}
|
|
||||||
|
|
||||||
Region {
|
|
||||||
readonly property var r: barWindow.clickThroughEnabled ? barWindow.sectionRect(barWindow._rightSection, false) : {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"w": 0,
|
|
||||||
"h": 0
|
|
||||||
}
|
|
||||||
x: r.x
|
|
||||||
y: r.y
|
|
||||||
width: r.w
|
|
||||||
height: r.h
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 "?";
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -731,8 +729,7 @@ Item {
|
|||||||
Flow {
|
Flow {
|
||||||
id: workspaceRow
|
id: workspaceRow
|
||||||
|
|
||||||
x: isVertical ? visualBackground.x : (parent.width - implicitWidth) / 2
|
anchors.centerIn: parent
|
||||||
y: isVertical ? (parent.height - implicitHeight) / 2 : visualBackground.y
|
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
flow: isVertical ? Flow.TopToBottom : Flow.LeftToRight
|
||||||
|
|
||||||
@@ -755,17 +752,6 @@ Item {
|
|||||||
return !!(modelData && modelData.num === root.currentWorkspace);
|
return !!(modelData && modelData.num === root.currentWorkspace);
|
||||||
return modelData === root.currentWorkspace;
|
return modelData === root.currentWorkspace;
|
||||||
}
|
}
|
||||||
property bool isOccupied: {
|
|
||||||
if (CompositorService.isHyprland)
|
|
||||||
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
|
||||||
if (CompositorService.isDwl)
|
|
||||||
return modelData.clients > 0;
|
|
||||||
if (CompositorService.isNiri) {
|
|
||||||
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
|
||||||
return workspace ? (NiriService.windows?.some(win => win.workspace_id === workspace.id) ?? false) : false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
property bool isPlaceholder: {
|
property bool isPlaceholder: {
|
||||||
if (root.useExtWorkspace)
|
if (root.useExtWorkspace)
|
||||||
return !!(modelData && modelData.hidden);
|
return !!(modelData && modelData.hidden);
|
||||||
@@ -847,23 +833,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property color occupiedColor: {
|
|
||||||
switch (SettingsData.workspaceOccupiedColorMode) {
|
|
||||||
case "sec":
|
|
||||||
return Theme.secondary;
|
|
||||||
case "s":
|
|
||||||
return Theme.surface;
|
|
||||||
case "sc":
|
|
||||||
return Theme.surfaceContainer;
|
|
||||||
case "sch":
|
|
||||||
return Theme.surfaceContainerHigh;
|
|
||||||
case "schh":
|
|
||||||
return Theme.surfaceContainerHighest;
|
|
||||||
default:
|
|
||||||
return unfocusedColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property color urgentColor: {
|
readonly property color urgentColor: {
|
||||||
switch (SettingsData.workspaceUrgentColorMode) {
|
switch (SettingsData.workspaceUrgentColorMode) {
|
||||||
case "primary":
|
case "primary":
|
||||||
@@ -997,13 +966,12 @@ Item {
|
|||||||
dataUpdateTimer.restart();
|
dataUpdateTimer.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
width: root.isVertical ? root.widgetHeight : visualWidth
|
width: root.isVertical ? root.barThickness : visualWidth
|
||||||
height: root.isVertical ? visualHeight : root.widgetHeight
|
height: root.isVertical ? visualHeight : root.barThickness
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: focusedBorderRing
|
id: focusedBorderRing
|
||||||
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
|
anchors.centerIn: parent
|
||||||
y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
|
|
||||||
width: {
|
width: {
|
||||||
const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
|
const borderWidth = (SettingsData.workspaceFocusedBorderEnabled && isActive && !isPlaceholder) ? SettingsData.workspaceFocusedBorderThickness : 0;
|
||||||
return delegateRoot.visualWidth + borderWidth * 2;
|
return delegateRoot.visualWidth + borderWidth * 2;
|
||||||
@@ -1050,10 +1018,9 @@ Item {
|
|||||||
id: visualContent
|
id: visualContent
|
||||||
width: delegateRoot.visualWidth
|
width: delegateRoot.visualWidth
|
||||||
height: delegateRoot.visualHeight
|
height: delegateRoot.visualHeight
|
||||||
x: root.isVertical ? (root.widgetHeight - width) / 2 : (parent.width - width) / 2
|
anchors.centerIn: parent
|
||||||
y: root.isVertical ? (parent.height - height) / 2 : (root.widgetHeight - height) / 2
|
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : isOccupied ? occupiedColor : unfocusedColor
|
color: isActive ? activeColor : isUrgent ? urgentColor : isPlaceholder ? Theme.surfaceTextLight : isHovered ? Theme.withAlpha(unfocusedColor, 0.7) : unfocusedColor
|
||||||
|
|
||||||
border.width: isUrgent ? 2 : 0
|
border.width: isUrgent ? 2 : 0
|
||||||
border.color: isUrgent ? urgentColor : "transparent"
|
border.color: isUrgent ? urgentColor : "transparent"
|
||||||
@@ -1168,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 {
|
||||||
@@ -1184,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
|
||||||
@@ -1278,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 {
|
||||||
@@ -1294,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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Shapes
|
import QtQuick.Shapes
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Hyprland
|
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -29,7 +28,7 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property var modelData: item
|
property var modelData: item
|
||||||
property bool autoHide: SettingsData.dockAutoHide || SettingsData.dockSmartAutoHide
|
property bool autoHide: SettingsData.dockAutoHide
|
||||||
property real backgroundTransparency: SettingsData.dockTransparency
|
property real backgroundTransparency: SettingsData.dockTransparency
|
||||||
property bool groupByApp: SettingsData.dockGroupByApp
|
property bool groupByApp: SettingsData.dockGroupByApp
|
||||||
readonly property int borderThickness: SettingsData.dockBorderEnabled ? SettingsData.dockBorderThickness : 0
|
readonly property int borderThickness: SettingsData.dockBorderEnabled ? SettingsData.dockBorderThickness : 0
|
||||||
@@ -112,133 +111,6 @@ Variants {
|
|||||||
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
|
property bool contextMenuOpen: (dockVariants.contextMenu && dockVariants.contextMenu.visible && dockVariants.contextMenu.screen === modelData)
|
||||||
property bool revealSticky: false
|
property bool revealSticky: false
|
||||||
|
|
||||||
readonly property bool shouldHideForWindows: {
|
|
||||||
if (!SettingsData.dockSmartAutoHide)
|
|
||||||
return false;
|
|
||||||
if (!CompositorService.isNiri && !CompositorService.isHyprland)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
const screenName = dock.modelData?.name ?? "";
|
|
||||||
const dockThickness = effectiveBarHeight + SettingsData.dockSpacing + SettingsData.dockBottomGap + SettingsData.dockMargin;
|
|
||||||
const screenWidth = dock.screen?.width ?? 0;
|
|
||||||
const screenHeight = dock.screen?.height ?? 0;
|
|
||||||
|
|
||||||
if (CompositorService.isNiri) {
|
|
||||||
NiriService.windows;
|
|
||||||
|
|
||||||
let currentWorkspaceId = null;
|
|
||||||
for (let i = 0; i < NiriService.allWorkspaces.length; i++) {
|
|
||||||
const ws = NiriService.allWorkspaces[i];
|
|
||||||
if (ws.output === screenName && ws.is_active) {
|
|
||||||
currentWorkspaceId = ws.id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentWorkspaceId === null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (let i = 0; i < NiriService.windows.length; i++) {
|
|
||||||
const win = NiriService.windows[i];
|
|
||||||
if (win.workspace_id !== currentWorkspaceId)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Get window position and size from layout data
|
|
||||||
const tilePos = win.layout?.tile_pos_in_workspace_view;
|
|
||||||
const winSize = win.layout?.window_size || win.layout?.tile_size;
|
|
||||||
|
|
||||||
if (tilePos && winSize) {
|
|
||||||
const winX = tilePos[0];
|
|
||||||
const winY = tilePos[1];
|
|
||||||
const winW = winSize[0];
|
|
||||||
const winH = winSize[1];
|
|
||||||
|
|
||||||
switch (SettingsData.dockPosition) {
|
|
||||||
case SettingsData.Position.Top:
|
|
||||||
if (winY < dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Bottom:
|
|
||||||
if (winY + winH > screenHeight - dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Left:
|
|
||||||
if (winX < dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Right:
|
|
||||||
if (winX + winW > screenWidth - dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (!win.is_floating) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hyprland implementation
|
|
||||||
const filtered = CompositorService.filterCurrentWorkspace(CompositorService.sortedToplevels, screenName);
|
|
||||||
|
|
||||||
if (filtered.length === 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (let i = 0; i < filtered.length; i++) {
|
|
||||||
const toplevel = filtered[i];
|
|
||||||
|
|
||||||
let hyprToplevel = null;
|
|
||||||
if (Hyprland.toplevels) {
|
|
||||||
const hyprToplevels = Array.from(Hyprland.toplevels.values);
|
|
||||||
for (let j = 0; j < hyprToplevels.length; j++) {
|
|
||||||
if (hyprToplevels[j].wayland === toplevel) {
|
|
||||||
hyprToplevel = hyprToplevels[j];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hyprToplevel?.lastIpcObject)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const ipc = hyprToplevel.lastIpcObject;
|
|
||||||
const at = ipc.at;
|
|
||||||
const size = ipc.size;
|
|
||||||
if (!at || !size)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
const monX = hyprToplevel.monitor?.x ?? 0;
|
|
||||||
const monY = hyprToplevel.monitor?.y ?? 0;
|
|
||||||
|
|
||||||
const winX = at[0] - monX;
|
|
||||||
const winY = at[1] - monY;
|
|
||||||
const winW = size[0];
|
|
||||||
const winH = size[1];
|
|
||||||
|
|
||||||
switch (SettingsData.dockPosition) {
|
|
||||||
case SettingsData.Position.Top:
|
|
||||||
if (winY < dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Bottom:
|
|
||||||
if (winY + winH > screenHeight - dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Left:
|
|
||||||
if (winX < dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
case SettingsData.Position.Right:
|
|
||||||
if (winX + winW > screenWidth - dockThickness)
|
|
||||||
return true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: revealHold
|
id: revealHold
|
||||||
interval: 250
|
interval: 250
|
||||||
@@ -250,15 +122,6 @@ Variants {
|
|||||||
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
if (CompositorService.isNiri && NiriService.inOverview && SettingsData.dockOpenOnOverview) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Smart auto-hide: show dock when no windows overlap, hide when they do
|
|
||||||
if (SettingsData.dockSmartAutoHide) {
|
|
||||||
if (shouldHideForWindows)
|
|
||||||
return dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
|
||||||
return true; // No overlapping windows - show dock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular auto-hide: always hide unless hovering
|
|
||||||
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
return !autoHide || dockMouseArea.containsMouse || dockApps.requestDockShow || contextMenuOpen || revealSticky;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -12,34 +12,8 @@ Scope {
|
|||||||
|
|
||||||
property string sharedPasswordBuffer: ""
|
property string sharedPasswordBuffer: ""
|
||||||
property bool shouldLock: false
|
property bool shouldLock: false
|
||||||
|
|
||||||
onShouldLockChanged: {
|
|
||||||
if (shouldLock && lockPowerOffArmed) {
|
|
||||||
lockStateCheck.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: lockStateCheck
|
|
||||||
interval: 100
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (sessionLock.locked && lockPowerOffArmed) {
|
|
||||||
pendingLock = false;
|
|
||||||
IdleService.monitorsOff = true;
|
|
||||||
CompositorService.powerOffMonitors();
|
|
||||||
lockWakeAllowed = false;
|
|
||||||
lockWakeDebounce.restart();
|
|
||||||
lockPowerOffArmed = false;
|
|
||||||
dpmsReapplyTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool lockInitiatedLocally: false
|
property bool lockInitiatedLocally: false
|
||||||
property bool pendingLock: false
|
property bool pendingLock: false
|
||||||
property bool lockPowerOffArmed: false
|
|
||||||
property bool lockWakeAllowed: false
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
IdleService.lockComponent = this;
|
IdleService.lockComponent = this;
|
||||||
@@ -63,7 +37,6 @@ Scope {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
lockInitiatedLocally = true;
|
lockInitiatedLocally = true;
|
||||||
lockPowerOffArmed = SettingsData.lockScreenPowerOffMonitorsOnLock;
|
|
||||||
|
|
||||||
if (!SessionService.active && SessionService.loginctlAvailable) {
|
if (!SessionService.active && SessionService.loginctlAvailable) {
|
||||||
pendingLock = true;
|
pendingLock = true;
|
||||||
@@ -105,7 +78,6 @@ Scope {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lockInitiatedLocally = false;
|
lockInitiatedLocally = false;
|
||||||
lockPowerOffArmed = SettingsData.lockScreenPowerOffMonitorsOnLock;
|
|
||||||
shouldLock = true;
|
shouldLock = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,13 +96,11 @@ Scope {
|
|||||||
if (SessionService.active && pendingLock) {
|
if (SessionService.active && pendingLock) {
|
||||||
pendingLock = false;
|
pendingLock = false;
|
||||||
lockInitiatedLocally = true;
|
lockInitiatedLocally = true;
|
||||||
lockPowerOffArmed = SettingsData.lockScreenPowerOffMonitorsOnLock;
|
|
||||||
shouldLock = true;
|
shouldLock = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (SessionService.locked && !shouldLock && !pendingLock) {
|
if (SessionService.locked && !shouldLock && !pendingLock) {
|
||||||
lockInitiatedLocally = false;
|
lockInitiatedLocally = false;
|
||||||
lockPowerOffArmed = SettingsData.lockScreenPowerOffMonitorsOnLock;
|
|
||||||
shouldLock = true;
|
shouldLock = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,6 +119,13 @@ Scope {
|
|||||||
|
|
||||||
locked: shouldLock
|
locked: shouldLock
|
||||||
|
|
||||||
|
onLockedChanged: {
|
||||||
|
if (locked) {
|
||||||
|
pendingLock = false;
|
||||||
|
dpmsReapplyTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WlSessionLockSurface {
|
WlSessionLockSurface {
|
||||||
id: lockSurface
|
id: lockSurface
|
||||||
|
|
||||||
@@ -178,33 +155,7 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: sessionLock
|
|
||||||
|
|
||||||
function onLockedChanged() {
|
|
||||||
if (sessionLock.locked) {
|
|
||||||
pendingLock = false;
|
|
||||||
if (lockPowerOffArmed && SettingsData.lockScreenPowerOffMonitorsOnLock) {
|
|
||||||
IdleService.monitorsOff = true;
|
|
||||||
CompositorService.powerOffMonitors();
|
|
||||||
lockWakeAllowed = false;
|
|
||||||
lockWakeDebounce.restart();
|
|
||||||
}
|
|
||||||
lockPowerOffArmed = false;
|
|
||||||
dpmsReapplyTimer.start();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lockWakeAllowed = false;
|
|
||||||
if (IdleService.monitorsOff && SettingsData.lockScreenPowerOffMonitorsOnLock) {
|
|
||||||
IdleService.monitorsOff = false;
|
|
||||||
CompositorService.powerOnMonitors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LockScreenDemo {
|
LockScreenDemo {
|
||||||
|
|
||||||
id: demoWindow
|
id: demoWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,46 +200,4 @@ Scope {
|
|||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: IdleService.reapplyDpmsIfNeeded()
|
onTriggered: IdleService.reapplyDpmsIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: lockWakeDebounce
|
|
||||||
interval: 200
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (!sessionLock.locked)
|
|
||||||
return;
|
|
||||||
if (!SettingsData.lockScreenPowerOffMonitorsOnLock)
|
|
||||||
return;
|
|
||||||
if (!IdleService.monitorsOff) {
|
|
||||||
lockWakeAllowed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (lockWakeAllowed) {
|
|
||||||
IdleService.monitorsOff = false;
|
|
||||||
CompositorService.powerOnMonitors();
|
|
||||||
} else {
|
|
||||||
lockWakeAllowed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
enabled: sessionLock.locked
|
|
||||||
hoverEnabled: enabled
|
|
||||||
onPressed: lockWakeDebounce.restart()
|
|
||||||
onPositionChanged: lockWakeDebounce.restart()
|
|
||||||
onWheel: lockWakeDebounce.restart()
|
|
||||||
}
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: sessionLock.locked
|
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
if (!sessionLock.locked)
|
|
||||||
return;
|
|
||||||
lockWakeDebounce.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Common
|
import qs.Modals.Common
|
||||||
@@ -52,9 +51,7 @@ Item {
|
|||||||
if (tabIndex === NotepadStorageService.currentTabIndex && hasUnsavedChanges()) {
|
if (tabIndex === NotepadStorageService.currentTabIndex && hasUnsavedChanges()) {
|
||||||
root.pendingAction = "close_tab_" + tabIndex;
|
root.pendingAction = "close_tab_" + tabIndex;
|
||||||
root.confirmationDialogOpen = true;
|
root.confirmationDialogOpen = true;
|
||||||
confirmationDialogLoader.active = true;
|
confirmationDialog.open();
|
||||||
if (confirmationDialogLoader.item)
|
|
||||||
confirmationDialogLoader.item.open();
|
|
||||||
} else {
|
} else {
|
||||||
performCloseTab(tabIndex);
|
performCloseTab(tabIndex);
|
||||||
}
|
}
|
||||||
@@ -104,9 +101,7 @@ Item {
|
|||||||
root.pendingFileUrl = fileUrl;
|
root.pendingFileUrl = fileUrl;
|
||||||
root.pendingAction = "load_file";
|
root.pendingAction = "load_file";
|
||||||
root.confirmationDialogOpen = true;
|
root.confirmationDialogOpen = true;
|
||||||
confirmationDialogLoader.active = true;
|
confirmationDialog.open();
|
||||||
if (confirmationDialogLoader.item)
|
|
||||||
confirmationDialogLoader.item.open();
|
|
||||||
} else {
|
} else {
|
||||||
performLoadFromFile(fileUrl);
|
performLoadFromFile(fileUrl);
|
||||||
}
|
}
|
||||||
@@ -175,27 +170,29 @@ Item {
|
|||||||
saveToFile(fileUrl);
|
saveToFile(fileUrl);
|
||||||
} else {
|
} else {
|
||||||
root.fileDialogOpen = true;
|
root.fileDialogOpen = true;
|
||||||
saveBrowserLoader.active = true;
|
saveBrowser.open();
|
||||||
if (saveBrowserLoader.item)
|
|
||||||
saveBrowserLoader.item.open();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenRequested: {
|
onOpenRequested: {
|
||||||
textEditor.autoSaveToSession();
|
if (hasUnsavedChanges()) {
|
||||||
if (textEditor.text.length > 0) {
|
root.pendingAction = "open";
|
||||||
createNewTab();
|
root.confirmationDialogOpen = true;
|
||||||
|
confirmationDialog.open();
|
||||||
|
} else {
|
||||||
|
root.fileDialogOpen = true;
|
||||||
|
loadBrowser.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
root.fileDialogOpen = true;
|
|
||||||
loadBrowserLoader.active = true;
|
|
||||||
if (loadBrowserLoader.item)
|
|
||||||
loadBrowserLoader.item.open();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewRequested: {
|
onNewRequested: {
|
||||||
textEditor.autoSaveToSession();
|
if (hasUnsavedChanges()) {
|
||||||
createNewTab();
|
root.pendingAction = "new";
|
||||||
|
root.confirmationDialogOpen = true;
|
||||||
|
confirmationDialog.open();
|
||||||
|
} else {
|
||||||
|
createNewTab();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onEscapePressed: {
|
onEscapePressed: {
|
||||||
@@ -252,259 +249,238 @@ Item {
|
|||||||
onLoadFailed: error => {}
|
onLoadFailed: error => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
FileBrowserModal {
|
||||||
id: saveBrowserLoader
|
id: saveBrowser
|
||||||
active: false
|
|
||||||
|
|
||||||
FileBrowserSurfaceModal {
|
browserTitle: I18n.tr("Save Notepad File")
|
||||||
id: saveBrowser
|
browserIcon: "save"
|
||||||
|
browserType: "notepad_save"
|
||||||
|
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||||
|
allowStacking: true
|
||||||
|
saveMode: true
|
||||||
|
defaultFileName: {
|
||||||
|
if (currentTab && currentTab.title && currentTab.title !== "Untitled") {
|
||||||
|
return currentTab.title;
|
||||||
|
} else if (currentTab && !currentTab.isTemporary && currentTab.filePath) {
|
||||||
|
return currentTab.filePath.split('/').pop();
|
||||||
|
} else {
|
||||||
|
return "note.txt";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
browserTitle: I18n.tr("Save Notepad File")
|
onFileSelected: path => {
|
||||||
browserIcon: "save"
|
root.fileDialogOpen = false;
|
||||||
browserType: "notepad_save"
|
const cleanPath = path.toString().replace(/^file:\/\//, '');
|
||||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
const fileName = cleanPath.split('/').pop();
|
||||||
allowStacking: true
|
const fileUrl = "file://" + cleanPath;
|
||||||
saveMode: true
|
|
||||||
defaultFileName: {
|
root.currentFileName = fileName;
|
||||||
if (currentTab && currentTab.title && currentTab.title !== "Untitled") {
|
root.currentFileUrl = fileUrl;
|
||||||
return currentTab.title;
|
|
||||||
} else if (currentTab && !currentTab.isTemporary && currentTab.filePath) {
|
if (currentTab) {
|
||||||
return currentTab.filePath.split('/').pop();
|
NotepadStorageService.saveTabAs(NotepadStorageService.currentTabIndex, cleanPath);
|
||||||
} else {
|
|
||||||
return "note.txt";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFileSelected: path => {
|
saveToFile(fileUrl);
|
||||||
root.fileDialogOpen = false;
|
|
||||||
const cleanPath = path.toString().replace(/^file:\/\//, '');
|
|
||||||
const fileName = cleanPath.split('/').pop();
|
|
||||||
const fileUrl = "file://" + cleanPath;
|
|
||||||
|
|
||||||
root.currentFileName = fileName;
|
if (root.pendingAction === "new") {
|
||||||
root.currentFileUrl = fileUrl;
|
Qt.callLater(() => {
|
||||||
|
createNewTab();
|
||||||
if (currentTab) {
|
});
|
||||||
NotepadStorageService.saveTabAs(NotepadStorageService.currentTabIndex, cleanPath);
|
} else if (root.pendingAction === "open") {
|
||||||
}
|
Qt.callLater(() => {
|
||||||
|
root.fileDialogOpen = true;
|
||||||
saveToFile(fileUrl);
|
loadBrowser.open();
|
||||||
|
});
|
||||||
if (root.pendingAction === "new") {
|
} else if (root.pendingAction.startsWith("close_tab_")) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
createNewTab();
|
var tabIndex = parseInt(root.pendingAction.split("_")[2]);
|
||||||
});
|
performCloseTab(tabIndex);
|
||||||
} else if (root.pendingAction === "open") {
|
});
|
||||||
Qt.callLater(() => {
|
|
||||||
root.fileDialogOpen = true;
|
|
||||||
loadBrowserLoader.active = true;
|
|
||||||
if (loadBrowserLoader.item)
|
|
||||||
loadBrowserLoader.item.open();
|
|
||||||
});
|
|
||||||
} else if (root.pendingAction.startsWith("close_tab_")) {
|
|
||||||
Qt.callLater(() => {
|
|
||||||
var tabIndex = parseInt(root.pendingAction.split("_")[2]);
|
|
||||||
performCloseTab(tabIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
root.pendingAction = "";
|
|
||||||
|
|
||||||
close();
|
|
||||||
}
|
}
|
||||||
|
root.pendingAction = "";
|
||||||
|
|
||||||
onDialogClosed: {
|
close();
|
||||||
root.fileDialogOpen = false;
|
}
|
||||||
}
|
|
||||||
|
onDialogClosed: {
|
||||||
|
root.fileDialogOpen = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
FileBrowserModal {
|
||||||
id: loadBrowserLoader
|
id: loadBrowser
|
||||||
active: false
|
|
||||||
|
|
||||||
FileBrowserSurfaceModal {
|
browserTitle: I18n.tr("Open Notepad File")
|
||||||
id: loadBrowser
|
browserIcon: "folder_open"
|
||||||
|
browserType: "notepad_load"
|
||||||
|
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||||
|
allowStacking: true
|
||||||
|
|
||||||
browserTitle: I18n.tr("Open Notepad File")
|
onFileSelected: path => {
|
||||||
browserIcon: "folder_open"
|
root.fileDialogOpen = false;
|
||||||
browserType: "notepad_load"
|
const cleanPath = path.toString().replace(/^file:\/\//, '');
|
||||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
const fileName = cleanPath.split('/').pop();
|
||||||
allowStacking: true
|
const fileUrl = "file://" + cleanPath;
|
||||||
|
|
||||||
onFileSelected: path => {
|
root.currentFileName = fileName;
|
||||||
root.fileDialogOpen = false;
|
root.currentFileUrl = fileUrl;
|
||||||
const cleanPath = path.toString().replace(/^file:\/\//, '');
|
|
||||||
const fileName = cleanPath.split('/').pop();
|
|
||||||
const fileUrl = "file://" + cleanPath;
|
|
||||||
|
|
||||||
root.currentFileName = fileName;
|
loadFromFile(fileUrl);
|
||||||
root.currentFileUrl = fileUrl;
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
loadFromFile(fileUrl);
|
onDialogClosed: {
|
||||||
close();
|
root.fileDialogOpen = false;
|
||||||
}
|
|
||||||
|
|
||||||
onDialogClosed: {
|
|
||||||
root.fileDialogOpen = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyLoader {
|
DankModal {
|
||||||
id: confirmationDialogLoader
|
id: confirmationDialog
|
||||||
active: false
|
|
||||||
|
|
||||||
DankModal {
|
width: 400
|
||||||
id: confirmationDialog
|
height: 180
|
||||||
|
shouldBeVisible: false
|
||||||
|
allowStacking: true
|
||||||
|
|
||||||
width: 400
|
onBackgroundClicked: {
|
||||||
height: 180
|
close();
|
||||||
shouldBeVisible: false
|
root.confirmationDialogOpen = false;
|
||||||
allowStacking: true
|
}
|
||||||
|
|
||||||
onBackgroundClicked: {
|
content: Component {
|
||||||
close();
|
FocusScope {
|
||||||
root.confirmationDialogOpen = false;
|
anchors.fill: parent
|
||||||
}
|
focus: true
|
||||||
|
|
||||||
content: Component {
|
Keys.onEscapePressed: event => {
|
||||||
FocusScope {
|
confirmationDialog.close();
|
||||||
anchors.fill: parent
|
root.confirmationDialogOpen = false;
|
||||||
focus: true
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
Keys.onEscapePressed: event => {
|
Column {
|
||||||
confirmationDialog.close();
|
anchors.centerIn: parent
|
||||||
root.confirmationDialogOpen = false;
|
width: parent.width - Theme.spacingM * 2
|
||||||
event.accepted = true;
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - 40
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Unsaved Changes")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
onClicked: {
|
||||||
|
confirmationDialog.close();
|
||||||
|
root.confirmationDialogOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Item {
|
||||||
anchors.centerIn: parent
|
width: parent.width
|
||||||
width: parent.width - Theme.spacingM * 2
|
height: 40
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
width: parent.width
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
Column {
|
Rectangle {
|
||||||
width: parent.width - 40
|
width: Math.max(80, discardText.contentWidth + Theme.spacingM * 2)
|
||||||
spacing: Theme.spacingXS
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: discardArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||||
|
border.color: Theme.surfaceVariantAlpha
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: I18n.tr("Unsaved Changes")
|
id: discardText
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
anchors.centerIn: parent
|
||||||
|
text: I18n.tr("Don't Save")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
MouseArea {
|
||||||
text: root.pendingAction === "new" ? I18n.tr("You have unsaved changes. Save before creating a new file?") : root.pendingAction.startsWith("close_tab_") ? I18n.tr("You have unsaved changes. Save before closing this tab?") : root.pendingAction === "load_file" || root.pendingAction === "open" ? I18n.tr("You have unsaved changes. Save before opening a file?") : I18n.tr("You have unsaved changes. Save before continuing?")
|
id: discardArea
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
anchors.fill: parent
|
||||||
color: Theme.surfaceTextMedium
|
hoverEnabled: true
|
||||||
width: parent.width
|
cursorShape: Qt.PointingHandCursor
|
||||||
wrapMode: Text.Wrap
|
onClicked: {
|
||||||
}
|
confirmationDialog.close();
|
||||||
}
|
root.confirmationDialogOpen = false;
|
||||||
|
if (root.pendingAction === "new") {
|
||||||
DankActionButton {
|
createNewTab();
|
||||||
iconName: "close"
|
} else if (root.pendingAction === "open") {
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
onClicked: {
|
|
||||||
confirmationDialog.close();
|
|
||||||
root.confirmationDialogOpen = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 40
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(80, discardText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: discardArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
|
||||||
border.color: Theme.surfaceVariantAlpha
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: discardText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: I18n.tr("Don't Save")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: discardArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
confirmationDialog.close();
|
|
||||||
root.confirmationDialogOpen = false;
|
|
||||||
if (root.pendingAction === "new") {
|
|
||||||
createNewTab();
|
|
||||||
} else if (root.pendingAction === "open") {
|
|
||||||
root.fileDialogOpen = true;
|
|
||||||
loadBrowserLoader.active = true;
|
|
||||||
if (loadBrowserLoader.item)
|
|
||||||
loadBrowserLoader.item.open();
|
|
||||||
} else if (root.pendingAction === "load_file") {
|
|
||||||
performLoadFromFile(root.pendingFileUrl);
|
|
||||||
} else if (root.pendingAction.startsWith("close_tab_")) {
|
|
||||||
var tabIndex = parseInt(root.pendingAction.split("_")[2]);
|
|
||||||
performCloseTab(tabIndex);
|
|
||||||
}
|
|
||||||
root.pendingAction = "";
|
|
||||||
root.pendingFileUrl = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(70, saveAsText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: saveAsArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: saveAsText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: I18n.tr("Save")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.background
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: saveAsArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
confirmationDialog.close();
|
|
||||||
root.confirmationDialogOpen = false;
|
|
||||||
root.fileDialogOpen = true;
|
root.fileDialogOpen = true;
|
||||||
saveBrowserLoader.active = true;
|
loadBrowser.open();
|
||||||
if (saveBrowserLoader.item)
|
} else if (root.pendingAction === "load_file") {
|
||||||
saveBrowserLoader.item.open();
|
performLoadFromFile(root.pendingFileUrl);
|
||||||
|
} else if (root.pendingAction.startsWith("close_tab_")) {
|
||||||
|
var tabIndex = parseInt(root.pendingAction.split("_")[2]);
|
||||||
|
performCloseTab(tabIndex);
|
||||||
}
|
}
|
||||||
|
root.pendingAction = "";
|
||||||
|
root.pendingFileUrl = "";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Behavior on color {
|
Rectangle {
|
||||||
ColorAnimation {
|
width: Math.max(70, saveAsText.contentWidth + Theme.spacingM * 2)
|
||||||
duration: Theme.shortDuration
|
height: 36
|
||||||
easing.type: Theme.standardEasing
|
radius: Theme.cornerRadius
|
||||||
}
|
color: saveAsArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: saveAsText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: I18n.tr("Save")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.background
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: saveAsArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
confirmationDialog.close();
|
||||||
|
root.confirmationDialogOpen = false;
|
||||||
|
root.fileDialogOpen = true;
|
||||||
|
saveBrowser.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,15 +52,20 @@ Column {
|
|||||||
|
|
||||||
readonly property bool isActive: NotepadStorageService.currentTabIndex === index
|
readonly property bool isActive: NotepadStorageService.currentTabIndex === index
|
||||||
readonly property bool isHovered: tabMouseArea.containsMouse && !closeMouseArea.containsMouse
|
readonly property bool isHovered: tabMouseArea.containsMouse && !closeMouseArea.containsMouse
|
||||||
readonly property real tabWidth: 128
|
readonly property real calculatedWidth: {
|
||||||
|
const textWidth = tabText.paintedWidth || 100;
|
||||||
|
const closeButtonWidth = NotepadStorageService.tabs.length > 1 ? 20 : 0;
|
||||||
|
const spacing = Theme.spacingXS;
|
||||||
|
const padding = Theme.spacingM * 2;
|
||||||
|
return Math.max(120, Math.min(200, textWidth + closeButtonWidth + spacing + padding));
|
||||||
|
}
|
||||||
|
|
||||||
width: tabWidth
|
width: calculatedWidth
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: isActive ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryPressed, 0)
|
color: isActive ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryPressed, 0)
|
||||||
border.width: isActive ? 0 : 1
|
border.width: isActive ? 0 : 1
|
||||||
border.color: Theme.outlineMedium
|
border.color: Theme.outlineMedium
|
||||||
clip: true
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: tabMouseArea
|
id: tabMouseArea
|
||||||
@@ -74,14 +79,11 @@ Column {
|
|||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: tabContent
|
id: tabContent
|
||||||
anchors.fill: parent
|
anchors.centerIn: parent
|
||||||
anchors.leftMargin: Theme.spacingM
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: tabText
|
id: tabText
|
||||||
width: parent.width - (tabCloseButton.visible ? tabCloseButton.width + Theme.spacingXS : 0)
|
|
||||||
text: {
|
text: {
|
||||||
var prefix = "";
|
var prefix = "";
|
||||||
if (hasUnsavedChangesForTab(modelData)) {
|
if (hasUnsavedChangesForTab(modelData)) {
|
||||||
@@ -94,7 +96,6 @@ Column {
|
|||||||
font.weight: isActive ? Font.Medium : Font.Normal
|
font.weight: isActive ? Font.Medium : Font.Normal
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ Column {
|
|||||||
|
|
||||||
TextArea.flickable: TextArea {
|
TextArea.flickable: TextArea {
|
||||||
id: textArea
|
id: textArea
|
||||||
placeholderText: ""
|
placeholderText: I18n.tr("Start typing your notes here...")
|
||||||
placeholderTextColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
placeholderTextColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||||
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
||||||
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
||||||
@@ -404,22 +404,6 @@ Column {
|
|||||||
topPadding: Theme.spacingM
|
topPadding: Theme.spacingM
|
||||||
rightPadding: Theme.spacingM
|
rightPadding: Theme.spacingM
|
||||||
bottomPadding: Theme.spacingM
|
bottomPadding: Theme.spacingM
|
||||||
cursorDelegate: Rectangle {
|
|
||||||
width: 1.5
|
|
||||||
radius: 1
|
|
||||||
color: Theme.surfaceText
|
|
||||||
x: textArea.cursorRectangle.x
|
|
||||||
y: textArea.cursorRectangle.y
|
|
||||||
height: textArea.cursorRectangle.height
|
|
||||||
opacity: 1.0
|
|
||||||
|
|
||||||
SequentialAnimation on opacity {
|
|
||||||
running: textArea.activeFocus
|
|
||||||
loops: Animation.Infinite
|
|
||||||
PropertyAnimation { from: 1.0; to: 0.0; duration: 650; easing.type: Easing.InOutQuad }
|
|
||||||
PropertyAnimation { from: 0.0; to: 1.0; duration: 650; easing.type: Easing.InOutQuad }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
loadCurrentTabContent()
|
loadCurrentTabContent()
|
||||||
@@ -496,20 +480,6 @@ Column {
|
|||||||
color: "transparent"
|
color: "transparent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: placeholderOverlay
|
|
||||||
text: I18n.tr("Start typing your notes here...")
|
|
||||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
|
||||||
font.family: textArea.font.family
|
|
||||||
font.pixelSize: textArea.font.pixelSize
|
|
||||||
visible: textArea.text.length === 0
|
|
||||||
anchors.left: textArea.left
|
|
||||||
anchors.top: textArea.top
|
|
||||||
anchors.leftMargin: textArea.leftPadding
|
|
||||||
anchors.topMargin: textArea.topPadding
|
|
||||||
z: textArea.z + 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ Column {
|
|||||||
property string detailsText: ""
|
property string detailsText: ""
|
||||||
property bool showCloseButton: false
|
property bool showCloseButton: false
|
||||||
property var closePopout: null
|
property var closePopout: null
|
||||||
property alias headerActions: headerActionsLoader.sourceComponent
|
|
||||||
|
|
||||||
readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0
|
readonly property int headerHeight: popoutHeader.visible ? popoutHeader.height : 0
|
||||||
readonly property int detailsHeight: popoutDetails.visible ? popoutDetails.implicitHeight : 0
|
readonly property int detailsHeight: popoutDetails.visible ? popoutDetails.implicitHeight : 0
|
||||||
@@ -32,40 +31,31 @@ Column {
|
|||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Rectangle {
|
||||||
|
id: closeButton
|
||||||
|
width: 32
|
||||||
|
height: 32
|
||||||
|
radius: 16
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingXS
|
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
|
||||||
|
visible: root.showCloseButton
|
||||||
|
|
||||||
Loader {
|
DankIcon {
|
||||||
id: headerActionsLoader
|
anchors.centerIn: parent
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
name: "close"
|
||||||
|
size: Theme.iconSize - 4
|
||||||
|
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
MouseArea {
|
||||||
id: closeButton
|
id: closeArea
|
||||||
width: 32
|
anchors.fill: parent
|
||||||
height: 32
|
hoverEnabled: true
|
||||||
radius: 16
|
cursorShape: Qt.PointingHandCursor
|
||||||
color: closeArea.containsMouse ? Theme.errorHover : "transparent"
|
onPressed: {
|
||||||
visible: root.showCloseButton
|
if (root.closePopout) {
|
||||||
|
root.closePopout();
|
||||||
DankIcon {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
name: "close"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: closeArea.containsMouse ? Theme.error : Theme.surfaceText
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: closeArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onPressed: {
|
|
||||||
if (root.closePopout) {
|
|
||||||
root.closePopout();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
451
quickshell/Modules/ProcessList/PerformanceTab.qml
Normal file
451
quickshell/Modules/ProcessList/PerformanceTab.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,61 +8,8 @@ Popup {
|
|||||||
id: processContextMenu
|
id: processContextMenu
|
||||||
|
|
||||||
property var processData: null
|
property var processData: null
|
||||||
property int selectedIndex: -1
|
|
||||||
property bool keyboardNavigation: false
|
|
||||||
property var parentFocusItem: null
|
|
||||||
|
|
||||||
signal menuClosed
|
function show(x, y) {
|
||||||
signal processKilled
|
|
||||||
|
|
||||||
readonly property var menuItems: [
|
|
||||||
{
|
|
||||||
text: I18n.tr("Copy PID"),
|
|
||||||
icon: "tag",
|
|
||||||
action: copyPid,
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: I18n.tr("Copy Name"),
|
|
||||||
icon: "content_copy",
|
|
||||||
action: copyName,
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: I18n.tr("Copy Full Command"),
|
|
||||||
icon: "code",
|
|
||||||
action: copyFullCommand,
|
|
||||||
enabled: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "separator"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: I18n.tr("Kill Process"),
|
|
||||||
icon: "close",
|
|
||||||
action: killProcess,
|
|
||||||
enabled: true,
|
|
||||||
dangerous: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: I18n.tr("Force Kill (SIGKILL)"),
|
|
||||||
icon: "dangerous",
|
|
||||||
action: forceKillProcess,
|
|
||||||
enabled: processData && processData.pid > 1000,
|
|
||||||
dangerous: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
readonly property int visibleItemCount: {
|
|
||||||
let count = 0;
|
|
||||||
for (let i = 0; i < menuItems.length; i++) {
|
|
||||||
if (menuItems[i].type !== "separator")
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(x, y, fromKeyboard) {
|
|
||||||
let finalX = x;
|
let finalX = x;
|
||||||
let finalY = y;
|
let finalY = y;
|
||||||
|
|
||||||
@@ -72,111 +19,39 @@ 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;
|
||||||
processContextMenu.y = finalY;
|
processContextMenu.y = finalY;
|
||||||
keyboardNavigation = fromKeyboard || false;
|
|
||||||
selectedIndex = fromKeyboard ? 0 : -1;
|
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNext() {
|
width: 180
|
||||||
if (visibleItemCount === 0)
|
|
||||||
return;
|
|
||||||
let current = selectedIndex;
|
|
||||||
let next = current;
|
|
||||||
do {
|
|
||||||
next = (next + 1) % menuItems.length;
|
|
||||||
} while (menuItems[next].type === "separator" && next !== current)
|
|
||||||
selectedIndex = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectPrevious() {
|
|
||||||
if (visibleItemCount === 0)
|
|
||||||
return;
|
|
||||||
let current = selectedIndex;
|
|
||||||
let prev = current;
|
|
||||||
do {
|
|
||||||
prev = (prev - 1 + menuItems.length) % menuItems.length;
|
|
||||||
} while (menuItems[prev].type === "separator" && prev !== current)
|
|
||||||
selectedIndex = prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
function activateSelected() {
|
|
||||||
if (selectedIndex < 0 || selectedIndex >= menuItems.length)
|
|
||||||
return;
|
|
||||||
const item = menuItems[selectedIndex];
|
|
||||||
if (item.type === "separator" || !item.enabled)
|
|
||||||
return;
|
|
||||||
item.action();
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyPid() {
|
|
||||||
if (processData)
|
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", processData.pid.toString()]);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyName() {
|
|
||||||
if (processData) {
|
|
||||||
const name = processData.command || "";
|
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", name]);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyFullCommand() {
|
|
||||||
if (processData) {
|
|
||||||
const fullCmd = processData.fullCommand || processData.command || "";
|
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", fullCmd]);
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function killProcess() {
|
|
||||||
if (processData)
|
|
||||||
Quickshell.execDetached(["kill", processData.pid.toString()]);
|
|
||||||
processKilled();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
function forceKillProcess() {
|
|
||||||
if (processData)
|
|
||||||
Quickshell.execDetached(["kill", "-9", processData.pid.toString()]);
|
|
||||||
processKilled();
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
width: 200
|
|
||||||
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;
|
||||||
keyboardNavigation = false;
|
|
||||||
selectedIndex = -1;
|
|
||||||
menuClosed();
|
|
||||||
if (parentFocusItem)
|
|
||||||
Qt.callLater(() => parentFocusItem.forceActiveFocus());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
outsideClickTimer.start();
|
outsideClickTimer.start();
|
||||||
if (keyboardNavigation)
|
|
||||||
Qt.callLater(() => keyboardHandler.forceActiveFocus());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -184,147 +59,163 @@ 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)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
Item {
|
|
||||||
id: keyboardHandler
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: keyboardNavigation
|
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Down:
|
|
||||||
case Qt.Key_J:
|
|
||||||
keyboardNavigation = true;
|
|
||||||
selectNext();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Up:
|
|
||||||
case Qt.Key_K:
|
|
||||||
keyboardNavigation = true;
|
|
||||||
selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
case Qt.Key_Space:
|
|
||||||
activateSelected();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
case Qt.Key_Left:
|
|
||||||
case Qt.Key_H:
|
|
||||||
close();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: menuColumn
|
id: menuColumn
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
spacing: 1
|
spacing: 1
|
||||||
|
|
||||||
Repeater {
|
Rectangle {
|
||||||
model: menuItems
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: copyPidArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
Item {
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 28
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: copyNameArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: I18n.tr("Copy Process Name")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width - Theme.spacingS * 2
|
||||||
|
height: 5
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.centerIn: parent
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: modelData.type === "separator" ? 5 : 32
|
height: 1
|
||||||
visible: modelData.type !== "separator" || index > 0
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property int itemVisibleIndex: {
|
Rectangle {
|
||||||
let count = 0;
|
width: parent.width
|
||||||
for (let i = 0; i < index; i++) {
|
height: 28
|
||||||
if (menuItems[i].type !== "separator")
|
radius: Theme.cornerRadius
|
||||||
count++;
|
color: killArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||||
|
enabled: processContextMenu.processData
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
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()]);
|
||||||
}
|
}
|
||||||
return count;
|
|
||||||
|
processContextMenu.close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: modelData.type === "separator"
|
width: parent.width
|
||||||
width: parent.width - Theme.spacingS * 2
|
height: 28
|
||||||
height: 1
|
radius: Theme.cornerRadius
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
color: forceKillArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent"
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
|
opacity: enabled ? 1 : 0.5
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
StyledText {
|
||||||
id: menuItem
|
anchors.left: parent.left
|
||||||
visible: modelData.type !== "separator"
|
anchors.leftMargin: Theme.spacingS
|
||||||
width: parent.width
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
height: 32
|
text: I18n.tr("Force Kill Process")
|
||||||
radius: Theme.cornerRadius
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: {
|
color: parent.enabled ? (forceKillArea.containsMouse ? Theme.error : Theme.surfaceText) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||||
if (!modelData.enabled)
|
font.weight: Font.Normal
|
||||||
return "transparent";
|
}
|
||||||
const isSelected = keyboardNavigation && selectedIndex === index;
|
|
||||||
if (modelData.dangerous) {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2);
|
|
||||||
return menuItemArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent";
|
|
||||||
}
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
|
||||||
return menuItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
|
||||||
}
|
|
||||||
opacity: modelData.enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
Row {
|
MouseArea {
|
||||||
anchors.left: parent.left
|
id: forceKillArea
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
anchors.fill: parent
|
||||||
name: modelData.icon || ""
|
hoverEnabled: true
|
||||||
size: 16
|
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
color: {
|
enabled: parent.enabled
|
||||||
if (!modelData.enabled)
|
onClicked: {
|
||||||
return Theme.surfaceVariantText;
|
if (processContextMenu.processData) {
|
||||||
const isSelected = keyboardNavigation && selectedIndex === index;
|
Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]);
|
||||||
if (modelData.dangerous && (menuItemArea.containsMouse || isSelected))
|
|
||||||
return Theme.error;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.text || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Normal
|
|
||||||
color: {
|
|
||||||
if (!modelData.enabled)
|
|
||||||
return Theme.surfaceVariantText;
|
|
||||||
const isSelected = keyboardNavigation && selectedIndex === index;
|
|
||||||
if (modelData.dangerous && (menuItemArea.containsMouse || isSelected))
|
|
||||||
return Theme.error;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
processContextMenu.close();
|
||||||
id: menuItemArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: modelData.enabled
|
|
||||||
onEntered: {
|
|
||||||
keyboardNavigation = false;
|
|
||||||
selectedIndex = index;
|
|
||||||
}
|
|
||||||
onClicked: modelData.action()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
200
quickshell/Modules/ProcessList/ProcessListItem.qml
Normal file
200
quickshell/Modules/ProcessList/ProcessListItem.qml
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -12,39 +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 = "";
|
|
||||||
if (processesView)
|
|
||||||
processesView.reset();
|
|
||||||
}
|
}
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ref {
|
Ref {
|
||||||
@@ -64,259 +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;
|
||||||
processContextMenu.parentFocusItem = processListContent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (processContextMenu.visible)
|
if (event.key === Qt.Key_Escape) {
|
||||||
return;
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Escape:
|
|
||||||
if (processListPopout.searchText.length > 0) {
|
|
||||||
processListPopout.searchText = "";
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (processesView.keyboardNavigationActive) {
|
|
||||||
processesView.reset();
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processesView.handleKey(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
ignoreUpDownKeys: true
|
|
||||||
keyForwardTargets: [processListContent]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,177 +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 {
|
||||||
id: processesView
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
264
quickshell/Modules/ProcessList/ProcessListView.qml
Normal file
264
quickshell/Modules/ProcessList/ProcessListView.qml
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
quickshell/Modules/ProcessList/ProcessesTab.qml
Normal file
28
quickshell/Modules/ProcessList/ProcessesTab.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,757 +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 int selectedIndex: -1
|
|
||||||
property bool keyboardNavigationActive: false
|
|
||||||
property int forceRefreshCount: 0
|
|
||||||
|
|
||||||
readonly property bool pauseUpdates: (contextMenu?.visible ?? false) || expandedPid.length > 0
|
|
||||||
readonly property bool shouldUpdate: !pauseUpdates || forceRefreshCount > 0
|
|
||||||
property var cachedProcesses: []
|
|
||||||
|
|
||||||
signal openContextMenuRequested(int index, real x, real y, bool fromKeyboard)
|
|
||||||
|
|
||||||
onFilteredProcessesChanged: {
|
|
||||||
if (!shouldUpdate)
|
|
||||||
return;
|
|
||||||
cachedProcesses = filteredProcesses;
|
|
||||||
if (forceRefreshCount > 0)
|
|
||||||
forceRefreshCount--;
|
|
||||||
}
|
|
||||||
|
|
||||||
onShouldUpdateChanged: {
|
|
||||||
if (shouldUpdate)
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectNext() {
|
|
||||||
if (cachedProcesses.length === 0)
|
|
||||||
return;
|
|
||||||
keyboardNavigationActive = true;
|
|
||||||
selectedIndex = Math.min(selectedIndex + 1, cachedProcesses.length - 1);
|
|
||||||
ensureVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectPrevious() {
|
|
||||||
if (cachedProcesses.length === 0)
|
|
||||||
return;
|
|
||||||
keyboardNavigationActive = true;
|
|
||||||
if (selectedIndex <= 0) {
|
|
||||||
selectedIndex = -1;
|
|
||||||
keyboardNavigationActive = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedIndex = selectedIndex - 1;
|
|
||||||
ensureVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectFirst() {
|
|
||||||
if (cachedProcesses.length === 0)
|
|
||||||
return;
|
|
||||||
keyboardNavigationActive = true;
|
|
||||||
selectedIndex = 0;
|
|
||||||
ensureVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectLast() {
|
|
||||||
if (cachedProcesses.length === 0)
|
|
||||||
return;
|
|
||||||
keyboardNavigationActive = true;
|
|
||||||
selectedIndex = cachedProcesses.length - 1;
|
|
||||||
ensureVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleExpand() {
|
|
||||||
if (selectedIndex < 0 || selectedIndex >= cachedProcesses.length)
|
|
||||||
return;
|
|
||||||
const process = cachedProcesses[selectedIndex];
|
|
||||||
const pidStr = (process?.pid ?? -1).toString();
|
|
||||||
expandedPid = (expandedPid === pidStr) ? "" : pidStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openContextMenu() {
|
|
||||||
if (selectedIndex < 0 || selectedIndex >= cachedProcesses.length)
|
|
||||||
return;
|
|
||||||
const delegate = processListView.itemAtIndex(selectedIndex);
|
|
||||||
if (!delegate)
|
|
||||||
return;
|
|
||||||
const process = cachedProcesses[selectedIndex];
|
|
||||||
if (!process || !contextMenu)
|
|
||||||
return;
|
|
||||||
contextMenu.processData = process;
|
|
||||||
const itemPos = delegate.mapToItem(contextMenu.parent, delegate.width / 2, delegate.height / 2);
|
|
||||||
contextMenu.parentFocusItem = root;
|
|
||||||
contextMenu.show(itemPos.x, itemPos.y, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
selectedIndex = -1;
|
|
||||||
keyboardNavigationActive = false;
|
|
||||||
expandedPid = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function forceRefresh(count) {
|
|
||||||
forceRefreshCount = count || 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureVisible() {
|
|
||||||
if (selectedIndex < 0)
|
|
||||||
return;
|
|
||||||
processListView.positionViewAtIndex(selectedIndex, ListView.Contain);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleKey(event) {
|
|
||||||
switch (event.key) {
|
|
||||||
case Qt.Key_Down:
|
|
||||||
selectNext();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Up:
|
|
||||||
selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_J:
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
selectNext();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_K:
|
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
|
||||||
selectPrevious();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_Home:
|
|
||||||
selectFirst();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_End:
|
|
||||||
selectLast();
|
|
||||||
event.accepted = true;
|
|
||||||
return;
|
|
||||||
case Qt.Key_Space:
|
|
||||||
if (keyboardNavigationActive) {
|
|
||||||
toggleExpand();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_Return:
|
|
||||||
case Qt.Key_Enter:
|
|
||||||
if (keyboardNavigationActive) {
|
|
||||||
toggleExpand();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
case Qt.Key_Menu:
|
|
||||||
case Qt.Key_F10:
|
|
||||||
if (keyboardNavigationActive && selectedIndex >= 0) {
|
|
||||||
openContextMenu();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
required property int index
|
|
||||||
|
|
||||||
width: processListView.width
|
|
||||||
process: modelData
|
|
||||||
isExpanded: root.expandedPid === (modelData?.pid ?? -1).toString()
|
|
||||||
isSelected: root.keyboardNavigationActive && root.selectedIndex === index
|
|
||||||
contextMenu: root.contextMenu
|
|
||||||
onToggleExpand: {
|
|
||||||
const pidStr = (modelData?.pid ?? -1).toString();
|
|
||||||
root.expandedPid = (root.expandedPid === pidStr) ? "" : pidStr;
|
|
||||||
}
|
|
||||||
onClicked: {
|
|
||||||
root.keyboardNavigationActive = true;
|
|
||||||
root.selectedIndex = index;
|
|
||||||
}
|
|
||||||
onContextMenuRequested: (mouseX, mouseY) => {
|
|
||||||
if (root.contextMenu) {
|
|
||||||
root.contextMenu.processData = modelData;
|
|
||||||
root.contextMenu.parentFocusItem = root;
|
|
||||||
const globalPos = mapToItem(root.contextMenu.parent, mouseX, mouseY);
|
|
||||||
root.contextMenu.show(globalPos.x, globalPos.y, 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 bool isSelected: false
|
|
||||||
property var contextMenu: null
|
|
||||||
|
|
||||||
signal toggleExpand
|
|
||||||
signal clicked
|
|
||||||
signal contextMenuRequested(real mouseX, real mouseY)
|
|
||||||
|
|
||||||
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: {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
|
||||||
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent";
|
|
||||||
}
|
|
||||||
border.color: {
|
|
||||||
if (isSelected)
|
|
||||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
|
||||||
return 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) {
|
|
||||||
processItemRoot.contextMenuRequested(mouse.x, mouse.y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
processItemRoot.clicked();
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
435
quickshell/Modules/ProcessList/SystemOverview.qml
Normal file
435
quickshell/Modules/ProcessList/SystemOverview.qml
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
591
quickshell/Modules/ProcessList/SystemTab.qml
Normal file
591
quickshell/Modules/ProcessList/SystemTab.qml
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user