mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
Compare commits
9 Commits
faddc46185
...
913bb2ff67
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
913bb2ff67 | ||
|
|
3bb2696263 | ||
|
|
166843ded4 | ||
|
|
02166a4ca5 | ||
|
|
f0f2e6ef72 | ||
|
|
8d8d5de5fd | ||
|
|
6d76f0b476 | ||
|
|
f3f720bb37 | ||
|
|
2bf85bc4dd |
9
.github/workflows/prek.yml
vendored
9
.github/workflows/prek.yml
vendored
@@ -11,5 +11,14 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install flatpak
|
||||
run: sudo apt update && sudo apt install -y flatpak
|
||||
|
||||
- name: Add flathub
|
||||
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
|
||||
- name: Add a flatpak that mutagen could support
|
||||
run: sudo flatpak install -y org.freedesktop.Platform/x86_64/24.08 app.zen_browser.zen
|
||||
|
||||
- name: run pre-commit hooks
|
||||
uses: j178/prek-action@v1
|
||||
|
||||
@@ -15,3 +15,4 @@ This file is more of a quick reference so I know what to account for before next
|
||||
- new IPC targets
|
||||
- Initial RTL support/i18n
|
||||
- Theme registry
|
||||
- Notification persistence & history
|
||||
|
||||
@@ -513,5 +513,6 @@ func getCommonCommands() []*cobra.Command {
|
||||
notifyActionCmd,
|
||||
matugenCmd,
|
||||
clipboardCmd,
|
||||
doctorCmd,
|
||||
}
|
||||
}
|
||||
|
||||
853
core/cmd/dms/commands_doctor.go
Normal file
853
core/cmd/dms/commands_doctor.go
Normal file
@@ -0,0 +1,853 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/brightness"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/network"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/tui"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type status string
|
||||
|
||||
const (
|
||||
statusOK status = "ok"
|
||||
statusWarn status = "warn"
|
||||
statusError status = "error"
|
||||
statusInfo status = "info"
|
||||
)
|
||||
|
||||
func (s status) IconStyle(styles tui.Styles) (string, lipgloss.Style) {
|
||||
switch s {
|
||||
case statusOK:
|
||||
return "●", styles.Success
|
||||
case statusWarn:
|
||||
return "●", styles.Warning
|
||||
case statusError:
|
||||
return "●", styles.Error
|
||||
default:
|
||||
return "○", styles.Subtle
|
||||
}
|
||||
}
|
||||
|
||||
type DoctorStatus struct {
|
||||
Errors []checkResult
|
||||
Warnings []checkResult
|
||||
OK []checkResult
|
||||
Info []checkResult
|
||||
}
|
||||
|
||||
func (ds *DoctorStatus) Add(r checkResult) {
|
||||
switch r.status {
|
||||
case statusError:
|
||||
ds.Errors = append(ds.Errors, r)
|
||||
case statusWarn:
|
||||
ds.Warnings = append(ds.Warnings, r)
|
||||
case statusOK:
|
||||
ds.OK = append(ds.OK, r)
|
||||
case statusInfo:
|
||||
ds.Info = append(ds.Info, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (ds *DoctorStatus) HasIssues() bool {
|
||||
return len(ds.Errors) > 0 || len(ds.Warnings) > 0
|
||||
}
|
||||
|
||||
func (ds *DoctorStatus) ErrorCount() int {
|
||||
return len(ds.Errors)
|
||||
}
|
||||
|
||||
func (ds *DoctorStatus) WarningCount() int {
|
||||
return len(ds.Warnings)
|
||||
}
|
||||
|
||||
func (ds *DoctorStatus) OKCount() int {
|
||||
return len(ds.OK)
|
||||
}
|
||||
|
||||
var (
|
||||
quickshellVersionRegex = regexp.MustCompile(`quickshell (\d+\.\d+\.\d+)`)
|
||||
hyprlandVersionRegex = regexp.MustCompile(`v?(\d+\.\d+\.\d+)`)
|
||||
niriVersionRegex = regexp.MustCompile(`niri (\d+\.\d+)`)
|
||||
swayVersionRegex = regexp.MustCompile(`sway version (\d+\.\d+)`)
|
||||
riverVersionRegex = regexp.MustCompile(`river (\d+\.\d+)`)
|
||||
wayfireVersionRegex = regexp.MustCompile(`wayfire (\d+\.\d+)`)
|
||||
)
|
||||
|
||||
var doctorCmd = &cobra.Command{
|
||||
Use: "doctor",
|
||||
Short: "Diagnose DMS installation and dependencies",
|
||||
Long: "Check system health, verify dependencies, and diagnose configuration issues for DMS",
|
||||
Run: runDoctor,
|
||||
}
|
||||
|
||||
var doctorVerbose bool
|
||||
|
||||
func init() {
|
||||
doctorCmd.Flags().BoolVarP(&doctorVerbose, "verbose", "v", false, "Show detailed output including paths and versions")
|
||||
}
|
||||
|
||||
type category int
|
||||
|
||||
const (
|
||||
catSystem category = iota
|
||||
catVersions
|
||||
catInstallation
|
||||
catCompositor
|
||||
catQuickshellFeatures
|
||||
catOptionalFeatures
|
||||
catConfigFiles
|
||||
catServices
|
||||
catEnvironment
|
||||
)
|
||||
|
||||
func (c category) String() string {
|
||||
switch c {
|
||||
case catSystem:
|
||||
return "System"
|
||||
case catVersions:
|
||||
return "Versions"
|
||||
case catInstallation:
|
||||
return "Installation"
|
||||
case catCompositor:
|
||||
return "Compositor"
|
||||
case catQuickshellFeatures:
|
||||
return "Quickshell Features"
|
||||
case catOptionalFeatures:
|
||||
return "Optional Features"
|
||||
case catConfigFiles:
|
||||
return "Config Files"
|
||||
case catServices:
|
||||
return "Services"
|
||||
case catEnvironment:
|
||||
return "Environment"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
checkNameMaxLength = 21
|
||||
)
|
||||
|
||||
type checkResult struct {
|
||||
category category
|
||||
name string
|
||||
status status
|
||||
message string
|
||||
details string
|
||||
}
|
||||
|
||||
func runDoctor(cmd *cobra.Command, args []string) {
|
||||
printDoctorHeader()
|
||||
|
||||
qsFeatures, qsMissingFeatures := checkQuickshellFeatures()
|
||||
|
||||
results := slices.Concat(
|
||||
checkSystemInfo(),
|
||||
checkVersions(qsMissingFeatures),
|
||||
checkDMSInstallation(),
|
||||
checkWindowManagers(),
|
||||
qsFeatures,
|
||||
checkOptionalDependencies(),
|
||||
checkConfigurationFiles(),
|
||||
checkSystemdServices(),
|
||||
checkEnvironmentVars(),
|
||||
)
|
||||
|
||||
printResults(results)
|
||||
printSummary(results, qsMissingFeatures)
|
||||
}
|
||||
|
||||
func printDoctorHeader() {
|
||||
theme := tui.TerminalTheme()
|
||||
styles := tui.NewStyles(theme)
|
||||
|
||||
fmt.Println(getThemedASCII())
|
||||
fmt.Println(styles.Title.Render("System Health Check"))
|
||||
fmt.Println(styles.Subtle.Render("──────────────────────────────────────"))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func checkSystemInfo() []checkResult {
|
||||
var results []checkResult
|
||||
|
||||
osInfo, err := distros.GetOSInfo()
|
||||
if err != nil {
|
||||
status, message, details := statusWarn, fmt.Sprintf("Unknown (%v)", err), ""
|
||||
|
||||
if strings.Contains(err.Error(), "Unsupported distribution") {
|
||||
osRelease := readOSRelease()
|
||||
if osRelease["ID"] == "nixos" {
|
||||
status = statusOK
|
||||
message = osRelease["PRETTY_NAME"]
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("NixOS %s", osRelease["VERSION_ID"])
|
||||
}
|
||||
details = "Supported for runtime (install via NixOS module or Flake)"
|
||||
} else if osRelease["PRETTY_NAME"] != "" {
|
||||
message = fmt.Sprintf("%s (not supported by dms setup)", osRelease["PRETTY_NAME"])
|
||||
details = "DMS may work but automatic installation is not available"
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, checkResult{catSystem, "Operating System", status, message, details})
|
||||
} else {
|
||||
status := statusOK
|
||||
message := osInfo.PrettyName
|
||||
if message == "" {
|
||||
message = fmt.Sprintf("%s %s", osInfo.Distribution.ID, osInfo.VersionID)
|
||||
}
|
||||
if distros.IsUnsupportedDistro(osInfo.Distribution.ID, osInfo.VersionID) {
|
||||
status = statusWarn
|
||||
message += " (version may not be fully supported)"
|
||||
}
|
||||
results = append(results, checkResult{
|
||||
catSystem, "Operating System", status, message,
|
||||
fmt.Sprintf("ID: %s, Version: %s, Arch: %s", osInfo.Distribution.ID, osInfo.VersionID, osInfo.Architecture),
|
||||
})
|
||||
}
|
||||
|
||||
arch := runtime.GOARCH
|
||||
archStatus := statusOK
|
||||
if arch != "amd64" && arch != "arm64" {
|
||||
archStatus = statusError
|
||||
}
|
||||
results = append(results, checkResult{catSystem, "Architecture", archStatus, arch, ""})
|
||||
|
||||
waylandDisplay := os.Getenv("WAYLAND_DISPLAY")
|
||||
xdgSessionType := os.Getenv("XDG_SESSION_TYPE")
|
||||
|
||||
switch {
|
||||
case waylandDisplay != "" || xdgSessionType == "wayland":
|
||||
results = append(results, checkResult{
|
||||
catSystem, "Display Server", statusOK, "Wayland",
|
||||
fmt.Sprintf("WAYLAND_DISPLAY=%s", waylandDisplay),
|
||||
})
|
||||
case xdgSessionType == "x11":
|
||||
results = append(results, checkResult{catSystem, "Display Server", statusError, "X11 (DMS requires Wayland)", ""})
|
||||
default:
|
||||
results = append(results, checkResult{
|
||||
catSystem, "Display Server", statusWarn, "Unknown (ensure you're running Wayland)",
|
||||
fmt.Sprintf("XDG_SESSION_TYPE=%s", xdgSessionType),
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func checkEnvironmentVars() []checkResult {
|
||||
var results []checkResult
|
||||
results = append(results, checkEnvVar("QT_QPA_PLATFORMTHEME")...)
|
||||
results = append(results, checkEnvVar("QS_ICON_THEME")...)
|
||||
return results
|
||||
}
|
||||
|
||||
func checkEnvVar(name string) []checkResult {
|
||||
value := os.Getenv(name)
|
||||
if value != "" {
|
||||
return []checkResult{{catEnvironment, name, statusInfo, value, ""}}
|
||||
} else if doctorVerbose {
|
||||
return []checkResult{{catEnvironment, name, statusInfo, "Not set", ""}}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readOSRelease() map[string]string {
|
||||
result := make(map[string]string)
|
||||
data, err := os.ReadFile("/etc/os-release")
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
for line := range strings.SplitSeq(string(data), "\n") {
|
||||
if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
|
||||
result[parts[0]] = strings.Trim(parts[1], "\"")
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func checkVersions(qsMissingFeatures bool) []checkResult {
|
||||
dmsCliPath, _ := os.Executable()
|
||||
dmsCliDetails := ""
|
||||
if doctorVerbose {
|
||||
dmsCliDetails = dmsCliPath
|
||||
}
|
||||
|
||||
results := []checkResult{
|
||||
{catVersions, "DMS CLI", statusOK, formatVersion(Version), dmsCliDetails},
|
||||
}
|
||||
|
||||
qsVersion, qsStatus, qsPath := getQuickshellVersionInfo(qsMissingFeatures)
|
||||
qsDetails := ""
|
||||
if doctorVerbose && qsPath != "" {
|
||||
qsDetails = qsPath
|
||||
}
|
||||
results = append(results, checkResult{catVersions, "Quickshell", qsStatus, qsVersion, qsDetails})
|
||||
|
||||
dmsVersion, dmsPath := getDMSShellVersion()
|
||||
if dmsVersion != "" {
|
||||
results = append(results, checkResult{catVersions, "DMS Shell", statusOK, dmsVersion, dmsPath})
|
||||
} else {
|
||||
results = append(results, checkResult{catVersions, "DMS Shell", statusError, "Not installed or not detected", "Run 'dms setup' to install"})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func getDMSShellVersion() (version, path string) {
|
||||
if err := findConfig(nil, nil); err == nil && configPath != "" {
|
||||
versionFile := filepath.Join(configPath, "VERSION")
|
||||
if data, err := os.ReadFile(versionFile); err == nil {
|
||||
return strings.TrimSpace(string(data)), configPath
|
||||
}
|
||||
return "installed", configPath
|
||||
}
|
||||
|
||||
if dmsPath, err := config.LocateDMSConfig(); err == nil {
|
||||
versionFile := filepath.Join(dmsPath, "VERSION")
|
||||
if data, err := os.ReadFile(versionFile); err == nil {
|
||||
return strings.TrimSpace(string(data)), dmsPath
|
||||
}
|
||||
return "installed", dmsPath
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func getQuickshellVersionInfo(missingFeatures bool) (string, status, string) {
|
||||
if !utils.CommandExists("qs") {
|
||||
return "Not installed", statusError, ""
|
||||
}
|
||||
|
||||
qsPath, _ := exec.LookPath("qs")
|
||||
|
||||
output, err := exec.Command("qs", "--version").Output()
|
||||
if err != nil {
|
||||
return "Installed (version check failed)", statusWarn, qsPath
|
||||
}
|
||||
|
||||
fullVersion := strings.TrimSpace(string(output))
|
||||
if matches := quickshellVersionRegex.FindStringSubmatch(fullVersion); len(matches) >= 2 {
|
||||
if version.CompareVersions(matches[1], "0.2.0") < 0 {
|
||||
return fmt.Sprintf("%s (needs >= 0.2.0)", fullVersion), statusError, qsPath
|
||||
}
|
||||
if missingFeatures {
|
||||
return fullVersion, statusWarn, qsPath
|
||||
}
|
||||
return fullVersion, statusOK, qsPath
|
||||
}
|
||||
|
||||
return fullVersion, statusWarn, qsPath
|
||||
}
|
||||
|
||||
func checkDMSInstallation() []checkResult {
|
||||
var results []checkResult
|
||||
|
||||
dmsPath := ""
|
||||
if err := findConfig(nil, nil); err == nil && configPath != "" {
|
||||
dmsPath = configPath
|
||||
} else if path, err := config.LocateDMSConfig(); err == nil {
|
||||
dmsPath = path
|
||||
}
|
||||
|
||||
if dmsPath == "" {
|
||||
return []checkResult{{catInstallation, "DMS Configuration", statusError, "Not found", "shell.qml not found in any config path"}}
|
||||
}
|
||||
|
||||
results = append(results, checkResult{catInstallation, "DMS Configuration", statusOK, "Found", dmsPath})
|
||||
|
||||
shellQml := filepath.Join(dmsPath, "shell.qml")
|
||||
if _, err := os.Stat(shellQml); err != nil {
|
||||
results = append(results, checkResult{catInstallation, "shell.qml", statusError, "Missing", shellQml})
|
||||
} else {
|
||||
results = append(results, checkResult{catInstallation, "shell.qml", statusOK, "Present", shellQml})
|
||||
}
|
||||
|
||||
if doctorVerbose {
|
||||
installType := "Unknown"
|
||||
switch {
|
||||
case strings.Contains(dmsPath, "/nix/store"):
|
||||
installType = "Nix store"
|
||||
case strings.Contains(dmsPath, ".local/share") || strings.Contains(dmsPath, "/usr/share"):
|
||||
installType = "System package"
|
||||
case strings.Contains(dmsPath, ".config"):
|
||||
installType = "User config"
|
||||
}
|
||||
results = append(results, checkResult{catInstallation, "Install Type", statusInfo, installType, dmsPath})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func checkWindowManagers() []checkResult {
|
||||
compositors := []struct {
|
||||
name, versionCmd, versionArg string
|
||||
versionRegex *regexp.Regexp
|
||||
commands []string
|
||||
}{
|
||||
{"Hyprland", "hyprctl", "version", hyprlandVersionRegex, []string{"hyprland", "Hyprland"}},
|
||||
{"niri", "niri", "--version", niriVersionRegex, []string{"niri"}},
|
||||
{"Sway", "sway", "--version", swayVersionRegex, []string{"sway"}},
|
||||
{"River", "river", "-version", riverVersionRegex, []string{"river"}},
|
||||
{"Wayfire", "wayfire", "--version", wayfireVersionRegex, []string{"wayfire"}},
|
||||
}
|
||||
|
||||
var results []checkResult
|
||||
foundAny := false
|
||||
|
||||
for _, c := range compositors {
|
||||
if slices.ContainsFunc(c.commands, utils.CommandExists) {
|
||||
foundAny = true
|
||||
var compositorPath string
|
||||
for _, cmd := range c.commands {
|
||||
if path, err := exec.LookPath(cmd); err == nil {
|
||||
compositorPath = path
|
||||
break
|
||||
}
|
||||
}
|
||||
details := ""
|
||||
if doctorVerbose && compositorPath != "" {
|
||||
details = compositorPath
|
||||
}
|
||||
results = append(results, checkResult{
|
||||
catCompositor, c.name, statusOK,
|
||||
getVersionFromCommand(c.versionCmd, c.versionArg, c.versionRegex), details,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !foundAny {
|
||||
results = append(results, checkResult{
|
||||
catCompositor, "Compositor", statusError,
|
||||
"No supported Wayland compositor found",
|
||||
"Install Hyprland, niri, Sway, River, or Wayfire",
|
||||
})
|
||||
}
|
||||
|
||||
if wm := detectRunningWM(); wm != "" {
|
||||
results = append(results, checkResult{catCompositor, "Active", statusInfo, wm, ""})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func getVersionFromCommand(cmd, arg string, regex *regexp.Regexp) string {
|
||||
output, err := exec.Command(cmd, arg).Output()
|
||||
if err != nil {
|
||||
return "installed"
|
||||
}
|
||||
|
||||
outStr := string(output)
|
||||
if matches := regex.FindStringSubmatch(outStr); len(matches) > 1 {
|
||||
ver := matches[1]
|
||||
if strings.Contains(outStr, "git") || strings.Contains(outStr, "dirty") {
|
||||
return ver + " (git)"
|
||||
}
|
||||
return ver
|
||||
}
|
||||
return strings.TrimSpace(outStr)
|
||||
}
|
||||
|
||||
func detectRunningWM() string {
|
||||
switch {
|
||||
case os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "":
|
||||
return "Hyprland"
|
||||
case os.Getenv("NIRI_SOCKET") != "":
|
||||
return "niri"
|
||||
case os.Getenv("XDG_CURRENT_DESKTOP") != "":
|
||||
return os.Getenv("XDG_CURRENT_DESKTOP")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func checkQuickshellFeatures() ([]checkResult, bool) {
|
||||
if !utils.CommandExists("qs") {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
tmpDir := os.TempDir()
|
||||
testScript := filepath.Join(tmpDir, "qs-feature-test.qml")
|
||||
defer os.Remove(testScript)
|
||||
|
||||
qmlContent := `
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
|
||||
property bool polkitAvailable: false
|
||||
property bool idleMonitorAvailable: false
|
||||
property bool idleInhibitorAvailable: false
|
||||
property bool shortcutInhibitorAvailable: false
|
||||
|
||||
Timer {
|
||||
interval: 50
|
||||
running: true
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
try {
|
||||
var polkitTest = Qt.createQmlObject(
|
||||
'import Quickshell.Services.Polkit; import QtQuick; Item {}',
|
||||
root
|
||||
)
|
||||
root.polkitAvailable = true
|
||||
polkitTest.destroy()
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
var testItem = Qt.createQmlObject(
|
||||
'import Quickshell.Wayland; import QtQuick; QtObject { ' +
|
||||
'readonly property bool hasIdleMonitor: typeof IdleMonitor !== "undefined"; ' +
|
||||
'readonly property bool hasIdleInhibitor: typeof IdleInhibitor !== "undefined"; ' +
|
||||
'readonly property bool hasShortcutInhibitor: typeof ShortcutInhibitor !== "undefined" ' +
|
||||
'}',
|
||||
root
|
||||
)
|
||||
root.idleMonitorAvailable = testItem.hasIdleMonitor
|
||||
root.idleInhibitorAvailable = testItem.hasIdleInhibitor
|
||||
root.shortcutInhibitorAvailable = testItem.hasShortcutInhibitor
|
||||
testItem.destroy()
|
||||
} catch (e) {}
|
||||
|
||||
console.warn(root.polkitAvailable ? "FEATURE:Polkit:OK" : "FEATURE:Polkit:UNAVAILABLE")
|
||||
console.warn(root.idleMonitorAvailable ? "FEATURE:IdleMonitor:OK" : "FEATURE:IdleMonitor:UNAVAILABLE")
|
||||
console.warn(root.idleInhibitorAvailable ? "FEATURE:IdleInhibitor:OK" : "FEATURE:IdleInhibitor:UNAVAILABLE")
|
||||
console.warn(root.shortcutInhibitorAvailable ? "FEATURE:ShortcutInhibitor:OK" : "FEATURE:ShortcutInhibitor:UNAVAILABLE")
|
||||
|
||||
Quickshell.execDetached(["kill", "-TERM", String(Quickshell.processId)])
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
if err := os.WriteFile(testScript, []byte(qmlContent), 0644); err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cmd := exec.Command("qs", "-p", testScript)
|
||||
cmd.Env = append(os.Environ(), "NO_COLOR=1")
|
||||
output, _ := cmd.CombinedOutput()
|
||||
outputStr := string(output)
|
||||
|
||||
features := []struct{ name, desc string }{
|
||||
{"Polkit", "Authentication prompts"},
|
||||
{"IdleMonitor", "Idle detection"},
|
||||
{"IdleInhibitor", "Prevent idle/sleep"},
|
||||
{"ShortcutInhibitor", "Allow shortcut management (niri)"},
|
||||
}
|
||||
|
||||
var results []checkResult
|
||||
missingFeatures := false
|
||||
|
||||
for _, f := range features {
|
||||
available := strings.Contains(outputStr, fmt.Sprintf("FEATURE:%s:OK", f.name))
|
||||
status, message := statusOK, "Available"
|
||||
if !available {
|
||||
status, message = statusInfo, "Not available"
|
||||
missingFeatures = true
|
||||
}
|
||||
results = append(results, checkResult{catQuickshellFeatures, f.name, status, message, f.desc})
|
||||
}
|
||||
|
||||
return results, missingFeatures
|
||||
}
|
||||
|
||||
func checkI2CAvailability() checkResult {
|
||||
ddc, err := brightness.NewDDCBackend()
|
||||
if err != nil {
|
||||
return checkResult{catOptionalFeatures, "I2C/DDC", statusInfo, "Not available", "External monitor brightness control"}
|
||||
}
|
||||
defer ddc.Close()
|
||||
|
||||
devices, err := ddc.GetDevices()
|
||||
if err != nil || len(devices) == 0 {
|
||||
return checkResult{catOptionalFeatures, "I2C/DDC", statusInfo, "No monitors detected", "External monitor brightness control"}
|
||||
}
|
||||
|
||||
return checkResult{catOptionalFeatures, "I2C/DDC", statusOK, fmt.Sprintf("%d monitor(s) detected", len(devices)), "External monitor brightness control"}
|
||||
}
|
||||
|
||||
func detectNetworkBackend() string {
|
||||
result, err := network.DetectNetworkStack()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch result.Backend {
|
||||
case network.BackendNetworkManager:
|
||||
return "NetworkManager"
|
||||
case network.BackendIwd:
|
||||
return "iwd"
|
||||
case network.BackendNetworkd:
|
||||
if result.HasIwd {
|
||||
return "iwd + systemd-networkd"
|
||||
}
|
||||
return "systemd-networkd"
|
||||
case network.BackendConnMan:
|
||||
return "ConnMan"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func checkOptionalDependencies() []checkResult {
|
||||
var results []checkResult
|
||||
|
||||
if utils.IsServiceActive("accounts-daemon", false) {
|
||||
results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusOK, "Running", "User accounts"})
|
||||
} else {
|
||||
results = append(results, checkResult{catOptionalFeatures, "accountsservice", statusWarn, "Not running", "User accounts"})
|
||||
}
|
||||
|
||||
if utils.IsServiceActive("power-profiles-daemon", false) {
|
||||
results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusOK, "Running", "Power profile management"})
|
||||
} else {
|
||||
results = append(results, checkResult{catOptionalFeatures, "power-profiles-daemon", statusInfo, "Not running", "Power profile management"})
|
||||
}
|
||||
|
||||
i2cStatus := checkI2CAvailability()
|
||||
results = append(results, i2cStatus)
|
||||
|
||||
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
|
||||
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], ""})
|
||||
} else {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty"})
|
||||
}
|
||||
|
||||
deps := []struct {
|
||||
name, cmd, altCmd, desc string
|
||||
important bool
|
||||
}{
|
||||
{"matugen", "matugen", "", "Dynamic theming", true},
|
||||
{"dgop", "dgop", "", "System monitoring", true},
|
||||
{"cava", "cava", "", "Audio waveform", false},
|
||||
{"khal", "khal", "", "Calendar events", false},
|
||||
{"Network", "nmcli", "iwctl", "Network management", false},
|
||||
{"danksearch", "dsearch", "", "File search", false},
|
||||
{"loginctl", "loginctl", "", "Session management", false},
|
||||
{"fprintd", "fprintd-list", "", "Fingerprint auth", false},
|
||||
}
|
||||
|
||||
for _, d := range deps {
|
||||
found, foundCmd := utils.CommandExists(d.cmd), d.cmd
|
||||
if !found && d.altCmd != "" {
|
||||
if utils.CommandExists(d.altCmd) {
|
||||
found, foundCmd = true, d.altCmd
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
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})
|
||||
} else if d.important {
|
||||
results = append(results, checkResult{catOptionalFeatures, d.name, statusWarn, "Missing", d.desc})
|
||||
} else {
|
||||
results = append(results, checkResult{catOptionalFeatures, d.name, statusInfo, "Not installed", d.desc})
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func checkConfigurationFiles() []checkResult {
|
||||
configDir, _ := os.UserConfigDir()
|
||||
cacheDir, _ := os.UserCacheDir()
|
||||
dmsDir := "DankMaterialShell"
|
||||
|
||||
configFiles := []struct{ name, path string }{
|
||||
{"settings.json", filepath.Join(configDir, dmsDir, "settings.json")},
|
||||
{"clsettings.json", filepath.Join(configDir, dmsDir, "clsettings.json")},
|
||||
{"plugin_settings.json", filepath.Join(configDir, dmsDir, "plugin_settings.json")},
|
||||
{"session.json", filepath.Join(utils.XDGStateHome(), dmsDir, "session.json")},
|
||||
{"dms-colors.json", filepath.Join(cacheDir, dmsDir, "dms-colors.json")},
|
||||
}
|
||||
|
||||
var results []checkResult
|
||||
for _, cf := range configFiles {
|
||||
info, err := os.Stat(cf.path)
|
||||
if err == nil {
|
||||
status := statusOK
|
||||
message := "Present"
|
||||
|
||||
if info.Mode().Perm()&0200 == 0 {
|
||||
status = statusWarn
|
||||
message += " (read-only)"
|
||||
}
|
||||
|
||||
results = append(results, checkResult{catConfigFiles, cf.name, status, message, cf.path})
|
||||
} else {
|
||||
results = append(results, checkResult{catConfigFiles, cf.name, statusInfo, "Not yet created", cf.path})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func checkSystemdServices() []checkResult {
|
||||
if !utils.CommandExists("systemctl") {
|
||||
return nil
|
||||
}
|
||||
|
||||
var results []checkResult
|
||||
|
||||
dmsState := getServiceState("dms", true)
|
||||
if !dmsState.exists {
|
||||
results = append(results, checkResult{catServices, "dms.service", statusInfo, "Not installed", "Optional user service"})
|
||||
} else {
|
||||
status, message := statusOK, dmsState.enabled
|
||||
if dmsState.active != "" {
|
||||
message = fmt.Sprintf("%s, %s", dmsState.enabled, dmsState.active)
|
||||
}
|
||||
if dmsState.enabled == "disabled" {
|
||||
status, message = statusWarn, "Disabled"
|
||||
}
|
||||
results = append(results, checkResult{catServices, "dms.service", status, message, ""})
|
||||
}
|
||||
|
||||
greetdState := getServiceState("greetd", false)
|
||||
if greetdState.exists {
|
||||
status := statusOK
|
||||
if greetdState.enabled == "disabled" {
|
||||
status = statusInfo
|
||||
}
|
||||
results = append(results, checkResult{catServices, "greetd", status, greetdState.enabled, ""})
|
||||
} else if doctorVerbose {
|
||||
results = append(results, checkResult{catServices, "greetd", statusInfo, "Not installed", "Optional greeter service"})
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
type serviceState struct {
|
||||
exists bool
|
||||
enabled string
|
||||
active string
|
||||
}
|
||||
|
||||
func getServiceState(name string, userService bool) serviceState {
|
||||
args := []string{"is-enabled", name}
|
||||
if userService {
|
||||
args = []string{"--user", "is-enabled", name}
|
||||
}
|
||||
|
||||
output, _ := exec.Command("systemctl", args...).Output()
|
||||
enabled := strings.TrimSpace(string(output))
|
||||
|
||||
if enabled == "" || enabled == "not-found" {
|
||||
return serviceState{}
|
||||
}
|
||||
|
||||
state := serviceState{exists: true, enabled: enabled}
|
||||
|
||||
if userService {
|
||||
output, _ = exec.Command("systemctl", "--user", "is-active", name).Output()
|
||||
if active := strings.TrimSpace(string(output)); active != "" && active != "unknown" {
|
||||
state.active = active
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func printResults(results []checkResult) {
|
||||
theme := tui.TerminalTheme()
|
||||
styles := tui.NewStyles(theme)
|
||||
|
||||
currentCategory := category(-1)
|
||||
for _, r := range results {
|
||||
if r.category != currentCategory {
|
||||
if currentCategory != -1 {
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Printf(" %s\n", styles.Bold.Render(r.category.String()))
|
||||
currentCategory = r.category
|
||||
}
|
||||
printResultLine(r, styles)
|
||||
}
|
||||
}
|
||||
|
||||
func printResultLine(r checkResult, styles tui.Styles) {
|
||||
icon, style := r.status.IconStyle(styles)
|
||||
|
||||
name := r.name
|
||||
nameLen := len(name)
|
||||
|
||||
if nameLen > checkNameMaxLength {
|
||||
name = name[:checkNameMaxLength-1] + "…"
|
||||
nameLen = checkNameMaxLength
|
||||
}
|
||||
dots := strings.Repeat("·", checkNameMaxLength-nameLen)
|
||||
|
||||
fmt.Printf(" %s %s %s %s\n", style.Render(icon), name, styles.Subtle.Render(dots), r.message)
|
||||
|
||||
if doctorVerbose && r.details != "" {
|
||||
fmt.Printf(" %s\n", styles.Subtle.Render("└─ "+r.details))
|
||||
}
|
||||
}
|
||||
|
||||
func printSummary(results []checkResult, qsMissingFeatures bool) {
|
||||
theme := tui.TerminalTheme()
|
||||
styles := tui.NewStyles(theme)
|
||||
|
||||
var ds DoctorStatus
|
||||
for _, r := range results {
|
||||
ds.Add(r)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf(" %s\n", styles.Subtle.Render("──────────────────────────────────────"))
|
||||
|
||||
if !ds.HasIssues() {
|
||||
fmt.Printf(" %s\n", styles.Success.Render("✓ All checks passed!"))
|
||||
} else {
|
||||
var parts []string
|
||||
|
||||
if ds.ErrorCount() > 0 {
|
||||
parts = append(parts, styles.Error.Render(fmt.Sprintf("%d error(s)", ds.ErrorCount())))
|
||||
}
|
||||
if ds.WarningCount() > 0 {
|
||||
parts = append(parts, styles.Warning.Render(fmt.Sprintf("%d warning(s)", ds.WarningCount())))
|
||||
}
|
||||
parts = append(parts, styles.Success.Render(fmt.Sprintf("%d ok", ds.OKCount())))
|
||||
fmt.Printf(" %s\n", strings.Join(parts, ", "))
|
||||
|
||||
if qsMissingFeatures {
|
||||
fmt.Println()
|
||||
fmt.Printf(" %s\n", styles.Subtle.Render("→ Consider using quickshell-git for full feature support"))
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
@@ -250,61 +250,61 @@ output_path = '%s'
|
||||
if !opts.ShouldSkipTemplate("gtk") {
|
||||
switch opts.Mode {
|
||||
case "light":
|
||||
appendConfig(opts, cfgFile, nil, "gtk3-light.toml")
|
||||
appendConfig(opts, cfgFile, nil, nil, "gtk3-light.toml")
|
||||
default:
|
||||
appendConfig(opts, cfgFile, nil, "gtk3-dark.toml")
|
||||
appendConfig(opts, cfgFile, nil, nil, "gtk3-dark.toml")
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("niri") {
|
||||
appendConfig(opts, cfgFile, []string{"niri"}, "niri.toml")
|
||||
appendConfig(opts, cfgFile, []string{"niri"}, nil, "niri.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("qt5ct") {
|
||||
appendConfig(opts, cfgFile, []string{"qt5ct"}, "qt5ct.toml")
|
||||
appendConfig(opts, cfgFile, []string{"qt5ct"}, nil, "qt5ct.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("qt6ct") {
|
||||
appendConfig(opts, cfgFile, []string{"qt6ct"}, "qt6ct.toml")
|
||||
appendConfig(opts, cfgFile, []string{"qt6ct"}, nil, "qt6ct.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("firefox") {
|
||||
appendConfig(opts, cfgFile, []string{"firefox"}, "firefox.toml")
|
||||
appendConfig(opts, cfgFile, []string{"firefox"}, nil, "firefox.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("pywalfox") {
|
||||
appendConfig(opts, cfgFile, []string{"pywalfox"}, "pywalfox.toml")
|
||||
appendConfig(opts, cfgFile, []string{"pywalfox"}, nil, "pywalfox.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("zenbrowser") {
|
||||
appendConfig(opts, cfgFile, []string{"zen", "zen-browser"}, "zenbrowser.toml")
|
||||
appendConfig(opts, cfgFile, []string{"zen", "zen-browser"}, []string{"app.zen_browser.zen"}, "zenbrowser.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("vesktop") {
|
||||
appendConfig(opts, cfgFile, []string{"vesktop"}, "vesktop.toml")
|
||||
appendConfig(opts, cfgFile, []string{"vesktop"}, []string{"dev.vencord.Vesktop"}, "vesktop.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("equibop") {
|
||||
appendConfig(opts, cfgFile, []string{"equibop"}, "equibop.toml")
|
||||
appendConfig(opts, cfgFile, []string{"equibop"}, nil, "equibop.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("ghostty") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"ghostty"}, "ghostty.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"ghostty"}, nil, "ghostty.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("kitty") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"kitty"}, "kitty.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"kitty"}, nil, "kitty.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("foot") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"foot"}, "foot.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"foot"}, nil, "foot.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("alacritty") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"alacritty"}, "alacritty.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"alacritty"}, nil, "alacritty.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("wezterm") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"wezterm"}, "wezterm.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"wezterm"}, nil, "wezterm.toml")
|
||||
}
|
||||
if !opts.ShouldSkipTemplate("nvim") {
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"nvim"}, "neovim.toml")
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, []string{"nvim"}, nil, "neovim.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("dgop") {
|
||||
appendConfig(opts, cfgFile, []string{"dgop"}, "dgop.toml")
|
||||
appendConfig(opts, cfgFile, []string{"dgop"}, nil, "dgop.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("kcolorscheme") {
|
||||
appendConfig(opts, cfgFile, nil, "kcolorscheme.toml")
|
||||
appendConfig(opts, cfgFile, nil, nil, "kcolorscheme.toml")
|
||||
}
|
||||
|
||||
if !opts.ShouldSkipTemplate("vscode") {
|
||||
@@ -342,12 +342,21 @@ output_path = '%s'
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendConfig(opts *Options, cfgFile *os.File, checkCmd []string, fileName string) {
|
||||
func appendConfig(
|
||||
opts *Options,
|
||||
cfgFile *os.File,
|
||||
checkCmd []string,
|
||||
checkFlatpaks []string,
|
||||
fileName string,
|
||||
) {
|
||||
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
return
|
||||
}
|
||||
if len(checkCmd) > 0 && !utils.AnyCommandExists(checkCmd...) {
|
||||
cmdExists := checkCmd == nil || utils.AnyCommandExists(checkCmd...)
|
||||
flatpakExists := checkFlatpaks == nil || utils.AnyFlatpakExists(checkFlatpaks...)
|
||||
|
||||
if !cmdExists && !flatpakExists {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(configPath)
|
||||
@@ -358,12 +367,15 @@ func appendConfig(opts *Options, cfgFile *os.File, checkCmd []string, fileName s
|
||||
cfgFile.WriteString("\n")
|
||||
}
|
||||
|
||||
func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir string, checkCmd []string, fileName string) {
|
||||
func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir string, checkCmd []string, checkFlatpaks []string, fileName string) {
|
||||
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
return
|
||||
}
|
||||
if len(checkCmd) > 0 && !utils.AnyCommandExists(checkCmd...) {
|
||||
cmdExists := checkCmd == nil || utils.AnyCommandExists(checkCmd...)
|
||||
flatpakExists := checkFlatpaks == nil || utils.AnyFlatpakExists(checkFlatpaks...)
|
||||
|
||||
if !cmdExists && !flatpakExists {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(configPath)
|
||||
|
||||
300
core/internal/matugen/matugen_test.go
Normal file
300
core/internal/matugen/matugen_test.go
Normal file
@@ -0,0 +1,300 @@
|
||||
package matugen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppendConfigBinaryExists(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "test config content"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, []string{"sh"}, nil, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) == 0 {
|
||||
t.Errorf("expected config to be written when binary exists")
|
||||
}
|
||||
if string(output) != testConfig+"\n" {
|
||||
t.Errorf("expected %q, got %q", testConfig+"\n", string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigBinaryDoesNotExist(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "test config content"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{}, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 0 {
|
||||
t.Errorf("expected no config when binary doesn't exist, got: %q", string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigFlatpakExists(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "zen config content"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, nil, []string{"app.zen_browser.zen"}, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) == 0 {
|
||||
t.Errorf("expected config to be written when flatpak exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigFlatpakDoesNotExist(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "test config content"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, []string{}, []string{"com.nonexistent.flatpak"}, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 0 {
|
||||
t.Errorf("expected no config when flatpak doesn't exist, got: %q", string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigBothExist(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "zen config content"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, []string{"sh"}, []string{"app.zen_browser.zen"}, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) == 0 {
|
||||
t.Errorf("expected config to be written when both binary and flatpak exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigNeitherExists(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "test config content"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 0 {
|
||||
t.Errorf("expected no config when neither exists, got: %q", string(output))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigNoChecks(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
testConfig := "always include"
|
||||
configPath := filepath.Join(configsDir, "test.toml")
|
||||
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
|
||||
t.Fatalf("failed to write config: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, nil, nil, "test.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) == 0 {
|
||||
t.Errorf("expected config to be written when no checks specified")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendConfigFileDoesNotExist(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
outFile := filepath.Join(tempDir, "output.toml")
|
||||
cfgFile, err := os.Create(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create output file: %v", err)
|
||||
}
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{ShellDir: shellDir}
|
||||
|
||||
appendConfig(opts, cfgFile, nil, nil, "nonexistent.toml")
|
||||
|
||||
cfgFile.Close()
|
||||
output, err := os.ReadFile(outFile)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read output: %v", err)
|
||||
}
|
||||
|
||||
if len(output) != 0 {
|
||||
t.Errorf("expected no config when file doesn't exist, got: %q", string(output))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package utils
|
||||
|
||||
import "os/exec"
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func CommandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
@@ -15,3 +18,16 @@ func AnyCommandExists(cmds ...string) bool {
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -56,6 +57,10 @@ func FlatpakSearchBySubstring(substring string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func AnyFlatpakExists(flatpaks ...string) bool {
|
||||
return slices.ContainsFunc(flatpaks, FlatpakExists)
|
||||
}
|
||||
|
||||
func FlatpakInstallationDir(name string) (string, error) {
|
||||
if !FlatpakInPath() {
|
||||
return "", errors.New("flatpak not found in PATH")
|
||||
|
||||
@@ -208,3 +208,42 @@ func TestFlatpakInstallationDirCommandFailure(t *testing.T) {
|
||||
t.Errorf("expected 'not installed' error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyFlatpakExistsSomeExist(t *testing.T) {
|
||||
if !FlatpakInPath() {
|
||||
t.Skip("flatpak not in PATH")
|
||||
}
|
||||
|
||||
result := AnyFlatpakExists("com.nonexistent.flatpak", "app.zen_browser.zen", "com.another.nonexistent")
|
||||
if !result {
|
||||
t.Errorf("expected true when at least one flatpak exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyFlatpakExistsNoneExist(t *testing.T) {
|
||||
if !FlatpakInPath() {
|
||||
t.Skip("flatpak not in PATH")
|
||||
}
|
||||
|
||||
result := AnyFlatpakExists("com.nonexistent.flatpak1", "com.nonexistent.flatpak2")
|
||||
if result {
|
||||
t.Errorf("expected false when no flatpaks exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyFlatpakExistsNoFlatpak(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("PATH", tempDir)
|
||||
|
||||
result := AnyFlatpakExists("any.package.name", "another.package")
|
||||
if result {
|
||||
t.Errorf("expected false when flatpak not in PATH, got true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyFlatpakExistsEmpty(t *testing.T) {
|
||||
result := AnyFlatpakExists()
|
||||
if result {
|
||||
t.Errorf("expected false when no flatpaks specified")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func XDGStateHome() string {
|
||||
if dir := os.Getenv("XDG_STATE_HOME"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(append([]string{home}, ".local", "state")...)
|
||||
}
|
||||
|
||||
func ExpandPath(path string) (string, error) {
|
||||
expanded := os.ExpandEnv(path)
|
||||
expanded = filepath.Clean(expanded)
|
||||
|
||||
@@ -278,6 +278,8 @@ Singleton {
|
||||
property bool loginctlLockIntegration: true
|
||||
property bool fadeToLockEnabled: false
|
||||
property int fadeToLockGracePeriod: 5
|
||||
property bool fadeToDpmsEnabled: false
|
||||
property int fadeToDpmsGracePeriod: 5
|
||||
property string launchPrefix: ""
|
||||
property var brightnessDevicePins: ({})
|
||||
property var wifiNetworkPins: ({})
|
||||
@@ -351,6 +353,12 @@ Singleton {
|
||||
property int notificationTimeoutNormal: 5000
|
||||
property int notificationTimeoutCritical: 0
|
||||
property int notificationPopupPosition: SettingsData.Position.Top
|
||||
property bool notificationHistoryEnabled: true
|
||||
property int notificationHistoryMaxCount: 50
|
||||
property int notificationHistoryMaxAgeDays: 7
|
||||
property bool notificationHistorySaveLow: true
|
||||
property bool notificationHistorySaveNormal: true
|
||||
property bool notificationHistorySaveCritical: true
|
||||
|
||||
property bool osdAlwaysShowValue: false
|
||||
property int osdPosition: SettingsData.Position.BottomCenter
|
||||
|
||||
@@ -168,6 +168,8 @@ var SPEC = {
|
||||
loginctlLockIntegration: { def: true },
|
||||
fadeToLockEnabled: { def: false },
|
||||
fadeToLockGracePeriod: { def: 5 },
|
||||
fadeToDpmsEnabled: { def: false },
|
||||
fadeToDpmsGracePeriod: { def: 5 },
|
||||
launchPrefix: { def: "" },
|
||||
brightnessDevicePins: { def: {} },
|
||||
wifiNetworkPins: { def: {} },
|
||||
@@ -240,6 +242,12 @@ var SPEC = {
|
||||
notificationTimeoutNormal: { def: 5000 },
|
||||
notificationTimeoutCritical: { def: 0 },
|
||||
notificationPopupPosition: { def: 0 },
|
||||
notificationHistoryEnabled: { def: true },
|
||||
notificationHistoryMaxCount: { def: 50 },
|
||||
notificationHistoryMaxAgeDays: { def: 7 },
|
||||
notificationHistorySaveLow: { def: true },
|
||||
notificationHistorySaveNormal: { def: true },
|
||||
notificationHistorySaveCritical: { def: true },
|
||||
|
||||
osdAlwaysShowValue: { def: false },
|
||||
osdPosition: { def: 5 },
|
||||
|
||||
@@ -104,6 +104,46 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
delegate: Loader {
|
||||
id: fadeDpmsWindowLoader
|
||||
required property var modelData
|
||||
active: SettingsData.fadeToDpmsEnabled
|
||||
asynchronous: false
|
||||
|
||||
sourceComponent: FadeToDpmsWindow {
|
||||
screen: fadeDpmsWindowLoader.modelData
|
||||
|
||||
onFadeCompleted: {
|
||||
IdleService.requestMonitorOff();
|
||||
}
|
||||
|
||||
onFadeCancelled: {
|
||||
console.log("Fade to DPMS cancelled by user on screen:", fadeDpmsWindowLoader.modelData.name);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: IdleService
|
||||
enabled: fadeDpmsWindowLoader.item !== null
|
||||
|
||||
function onFadeToDpmsRequested() {
|
||||
if (fadeDpmsWindowLoader.item) {
|
||||
fadeDpmsWindowLoader.item.startFade();
|
||||
}
|
||||
}
|
||||
|
||||
function onCancelFadeToDpms() {
|
||||
if (fadeDpmsWindowLoader.item) {
|
||||
fadeDpmsWindowLoader.item.cancelFade();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: dankBarRepeater
|
||||
model: ScriptModel {
|
||||
|
||||
@@ -18,6 +18,8 @@ DankModal {
|
||||
|
||||
property bool notificationModalOpen: false
|
||||
property var notificationListRef: null
|
||||
property var historyListRef: null
|
||||
property int currentTab: 0
|
||||
|
||||
function show() {
|
||||
notificationModalOpen = true;
|
||||
@@ -61,7 +63,7 @@ DankModal {
|
||||
NotificationService.clearAllNotifications();
|
||||
}
|
||||
|
||||
function dismissAllPopups () {
|
||||
function dismissAllPopups() {
|
||||
NotificationService.dismissAllPopups();
|
||||
}
|
||||
|
||||
@@ -80,7 +82,18 @@ DankModal {
|
||||
NotificationService.onOverlayClose();
|
||||
}
|
||||
}
|
||||
modalFocusScope.Keys.onPressed: event => modalKeyboardController.handleKey(event)
|
||||
modalFocusScope.Keys.onPressed: event => {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
hide();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
if (currentTab === 1 && historyListRef) {
|
||||
historyListRef.handleKey(event);
|
||||
return;
|
||||
}
|
||||
modalKeyboardController.handleKey(event);
|
||||
}
|
||||
|
||||
NotificationKeyboardController {
|
||||
id: modalKeyboardController
|
||||
@@ -145,21 +158,20 @@ DankModal {
|
||||
|
||||
NotificationHeader {
|
||||
id: notificationHeader
|
||||
|
||||
keyboardController: modalKeyboardController
|
||||
onCurrentTabChanged: notificationModal.currentTab = currentTab
|
||||
}
|
||||
|
||||
NotificationSettings {
|
||||
id: notificationSettings
|
||||
|
||||
expanded: notificationHeader.showSettings
|
||||
}
|
||||
|
||||
KeyboardNavigatedNotificationList {
|
||||
id: notificationList
|
||||
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
visible: notificationHeader.currentTab === 0
|
||||
keyboardController: modalKeyboardController
|
||||
Component.onCompleted: {
|
||||
notificationModal.notificationListRef = notificationList;
|
||||
@@ -169,6 +181,14 @@ DankModal {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HistoryNotificationList {
|
||||
id: historyList
|
||||
width: parent.width
|
||||
height: parent.height - y
|
||||
visible: notificationHeader.currentTab === 1
|
||||
Component.onCompleted: notificationModal.historyListRef = historyList
|
||||
}
|
||||
}
|
||||
|
||||
NotificationKeyboardHints {
|
||||
@@ -178,7 +198,7 @@ DankModal {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
showHints: modalKeyboardController.showKeyboardHints
|
||||
showHints: notificationHeader.currentTab === 0 ? modalKeyboardController.showKeyboardHints : historyList.showKeyboardHints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ Item {
|
||||
if (!toplevel.screens)
|
||||
return false;
|
||||
for (let i = 0; i < toplevel.screens.length; i++) {
|
||||
if (toplevel.screens[i].name === screenName)
|
||||
if (toplevel.screens[i]?.name === screenName)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
102
quickshell/Modules/Lock/FadeToDpmsWindow.qml
Normal file
102
quickshell/Modules/Lock/FadeToDpmsWindow.qml
Normal file
@@ -0,0 +1,102 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool active: false
|
||||
|
||||
signal fadeCompleted
|
||||
signal fadeCancelled
|
||||
|
||||
visible: active
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.namespace: "dms:fade-to-dpms"
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: -1
|
||||
WlrLayershell.keyboardFocus: active ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
||||
|
||||
anchors {
|
||||
left: true
|
||||
right: true
|
||||
top: true
|
||||
bottom: true
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: fadeOverlay
|
||||
anchors.fill: parent
|
||||
color: "black"
|
||||
opacity: 0
|
||||
|
||||
onOpacityChanged: {
|
||||
if (opacity >= 0.99 && root.active) {
|
||||
root.fadeCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: fadeSeq
|
||||
running: false
|
||||
|
||||
NumberAnimation {
|
||||
target: fadeOverlay
|
||||
property: "opacity"
|
||||
from: 0.0
|
||||
to: 1.0
|
||||
duration: SettingsData.fadeToDpmsGracePeriod * 1000
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
function startFade() {
|
||||
if (!SettingsData.fadeToDpmsEnabled)
|
||||
return;
|
||||
active = true;
|
||||
fadeOverlay.opacity = 0.0;
|
||||
fadeSeq.stop();
|
||||
fadeSeq.start();
|
||||
}
|
||||
|
||||
function cancelFade() {
|
||||
fadeSeq.stop();
|
||||
fadeOverlay.opacity = 0.0;
|
||||
active = false;
|
||||
fadeCancelled();
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: root.active
|
||||
onClicked: root.cancelFade()
|
||||
onPressed: root.cancelFade()
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
anchors.fill: parent
|
||||
focus: root.active
|
||||
|
||||
Keys.onPressed: event => {
|
||||
root.cancelFade();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (active) {
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (active) {
|
||||
forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,15 +23,12 @@ Scope {
|
||||
Quickshell.execDetached(["sh", "-c", SettingsData.customPowerActionLock]);
|
||||
return;
|
||||
}
|
||||
shouldLock = true;
|
||||
if (!processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
|
||||
DMSService.lockSession(response => {
|
||||
if (response.error) {
|
||||
console.warn("Lock: Failed to call loginctl.lock:", response.error);
|
||||
shouldLock = true;
|
||||
}
|
||||
if (response.error)
|
||||
console.warn("Lock: loginctl.lock failed:", response.error);
|
||||
});
|
||||
} else {
|
||||
shouldLock = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +78,11 @@ Scope {
|
||||
|
||||
locked: shouldLock
|
||||
|
||||
onLockedChanged: {
|
||||
if (locked)
|
||||
dpmsReapplyTimer.start();
|
||||
}
|
||||
|
||||
WlSessionLockSurface {
|
||||
id: lockSurface
|
||||
|
||||
@@ -120,15 +122,12 @@ Scope {
|
||||
target: "lock"
|
||||
|
||||
function lock() {
|
||||
if (!root.processingExternalEvent && SettingsData.loginctlLockIntegration && DMSService.isConnected) {
|
||||
root.shouldLock = true;
|
||||
if (SettingsData.loginctlLockIntegration && DMSService.isConnected) {
|
||||
DMSService.lockSession(response => {
|
||||
if (response.error) {
|
||||
console.warn("Lock: Failed to call loginctl.lock:", response.error);
|
||||
root.shouldLock = true;
|
||||
}
|
||||
if (response.error)
|
||||
console.warn("Lock: loginctl.lock failed:", response.error);
|
||||
});
|
||||
} else {
|
||||
root.shouldLock = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,4 +139,11 @@ Scope {
|
||||
return sessionLock.locked;
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: dpmsReapplyTimer
|
||||
interval: 100
|
||||
repeat: false
|
||||
onTriggered: IdleService.reapplyDpmsIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property var historyItem
|
||||
property bool isSelected: false
|
||||
property bool keyboardNavigationActive: false
|
||||
|
||||
width: parent ? parent.width : 400
|
||||
height: 116
|
||||
radius: Theme.cornerRadius
|
||||
clip: true
|
||||
|
||||
color: {
|
||||
if (isSelected && keyboardNavigationActive)
|
||||
return Theme.primaryPressed;
|
||||
return Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency);
|
||||
}
|
||||
border.color: {
|
||||
if (isSelected && keyboardNavigationActive)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.5);
|
||||
if (historyItem.urgency === 2)
|
||||
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||
return Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.05);
|
||||
}
|
||||
border.width: {
|
||||
if (isSelected && keyboardNavigationActive)
|
||||
return 1.5;
|
||||
if (historyItem.urgency === 2)
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
visible: historyItem.urgency === 2
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: Theme.primary
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.02
|
||||
color: Theme.primary
|
||||
}
|
||||
GradientStop {
|
||||
position: 0.021
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 12
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 56
|
||||
height: 92
|
||||
|
||||
DankCircularImage {
|
||||
id: iconContainer
|
||||
readonly property bool hasNotificationImage: historyItem.image && historyItem.image !== ""
|
||||
|
||||
width: 63
|
||||
height: 63
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
|
||||
imageSource: {
|
||||
if (hasNotificationImage)
|
||||
return historyItem.image;
|
||||
if (historyItem.appIcon) {
|
||||
const appIcon = historyItem.appIcon;
|
||||
if (appIcon.startsWith("file://") || appIcon.startsWith("http://") || appIcon.startsWith("https://"))
|
||||
return appIcon;
|
||||
return Quickshell.iconPath(appIcon, true);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
hasImage: hasNotificationImage
|
||||
fallbackIcon: ""
|
||||
fallbackText: {
|
||||
const appName = historyItem.appName || "?";
|
||||
return appName.charAt(0).toUpperCase();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -2
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: root.color
|
||||
border.width: 5
|
||||
visible: parent.hasImage
|
||||
antialiasing: true
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.left: iconContainer.right
|
||||
anchors.leftMargin: 12
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
color: "transparent"
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -2
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: {
|
||||
const timeStr = NotificationService.formatHistoryTime(historyItem.timestamp);
|
||||
const appName = historyItem.appName || "";
|
||||
return timeStr.length > 0 ? `${appName} • ${timeStr}` : appName;
|
||||
}
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: historyItem.summary || ""
|
||||
color: Theme.surfaceText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
id: descriptionText
|
||||
text: historyItem.htmlBody || historyItem.body || ""
|
||||
color: Theme.surfaceVariantText
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 2
|
||||
wrapMode: Text.WordWrap
|
||||
visible: text.length > 0
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: link => Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
anchors.top: parent.top
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 12
|
||||
anchors.rightMargin: 16
|
||||
iconName: "close"
|
||||
iconSize: 18
|
||||
buttonSize: 28
|
||||
onClicked: NotificationService.removeFromHistory(historyItem.id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string selectedFilterKey: "all"
|
||||
property var keyboardController: null
|
||||
property bool keyboardActive: false
|
||||
property int selectedIndex: -1
|
||||
property bool showKeyboardHints: false
|
||||
|
||||
function getStartOfDay(date) {
|
||||
const d = new Date(date);
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
}
|
||||
|
||||
function getFilterRange(key) {
|
||||
const now = new Date();
|
||||
const startOfToday = getStartOfDay(now);
|
||||
const startOfYesterday = new Date(startOfToday.getTime() - 86400000);
|
||||
|
||||
switch (key) {
|
||||
case "all":
|
||||
return {
|
||||
start: null,
|
||||
end: null
|
||||
};
|
||||
case "1h":
|
||||
return {
|
||||
start: new Date(now.getTime() - 3600000),
|
||||
end: null
|
||||
};
|
||||
case "today":
|
||||
return {
|
||||
start: startOfToday,
|
||||
end: null
|
||||
};
|
||||
case "yesterday":
|
||||
return {
|
||||
start: startOfYesterday,
|
||||
end: startOfToday
|
||||
};
|
||||
case "older":
|
||||
return {
|
||||
start: null,
|
||||
end: getOlderCutoff()
|
||||
};
|
||||
case "7d":
|
||||
return {
|
||||
start: new Date(now.getTime() - 7 * 86400000),
|
||||
end: null
|
||||
};
|
||||
case "30d":
|
||||
return {
|
||||
start: new Date(now.getTime() - 30 * 86400000),
|
||||
end: null
|
||||
};
|
||||
default:
|
||||
return {
|
||||
start: null,
|
||||
end: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function countForFilter(key) {
|
||||
const range = getFilterRange(key);
|
||||
if (!range.start && !range.end)
|
||||
return NotificationService.historyList.length;
|
||||
return NotificationService.historyList.filter(n => {
|
||||
const ts = n.timestamp;
|
||||
if (range.start && ts < range.start.getTime())
|
||||
return false;
|
||||
if (range.end && ts >= range.end.getTime())
|
||||
return false;
|
||||
return true;
|
||||
}).length;
|
||||
}
|
||||
|
||||
readonly property var allFilters: [
|
||||
{ label: I18n.tr("All", "notification history filter"), key: "all", maxDays: 0 },
|
||||
{ label: I18n.tr("Last hour", "notification history filter"), key: "1h", maxDays: 1 },
|
||||
{ label: I18n.tr("Today", "notification history filter"), key: "today", maxDays: 1 },
|
||||
{ label: I18n.tr("Yesterday", "notification history filter"), key: "yesterday", maxDays: 2 },
|
||||
{ label: I18n.tr("7 days", "notification history filter"), key: "7d", maxDays: 7 },
|
||||
{ label: I18n.tr("30 days", "notification history filter"), key: "30d", maxDays: 30 },
|
||||
{ label: I18n.tr("Older", "notification history filter for content older than other filters"), key: "older", maxDays: 0 }
|
||||
]
|
||||
|
||||
function filterRelevantForRetention(filter) {
|
||||
const retention = SettingsData.notificationHistoryMaxAgeDays;
|
||||
if (filter.key === "older") {
|
||||
if (retention === 0) return true;
|
||||
return retention > 2 && retention < 7 || retention > 30;
|
||||
}
|
||||
if (retention === 0) return true;
|
||||
if (filter.maxDays === 0) return true;
|
||||
return filter.maxDays <= retention;
|
||||
}
|
||||
|
||||
function getOlderCutoff() {
|
||||
const retention = SettingsData.notificationHistoryMaxAgeDays;
|
||||
const now = new Date();
|
||||
if (retention === 0 || retention > 30)
|
||||
return new Date(now.getTime() - 30 * 86400000);
|
||||
if (retention >= 7)
|
||||
return new Date(now.getTime() - 7 * 86400000);
|
||||
const startOfToday = getStartOfDay(now);
|
||||
return new Date(startOfToday.getTime() - 86400000);
|
||||
}
|
||||
|
||||
readonly property var visibleFilters: {
|
||||
const result = [];
|
||||
const retention = SettingsData.notificationHistoryMaxAgeDays;
|
||||
for (let i = 0; i < allFilters.length; i++) {
|
||||
const f = allFilters[i];
|
||||
if (!filterRelevantForRetention(f)) continue;
|
||||
const count = countForFilter(f.key);
|
||||
if (f.key === "all" || count > 0) {
|
||||
result.push({ label: f.label, key: f.key, count: count });
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
onVisibleFiltersChanged: {
|
||||
let found = false;
|
||||
for (let i = 0; i < visibleFilters.length; i++) {
|
||||
if (visibleFilters[i].key === selectedFilterKey) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
selectedFilterKey = "all";
|
||||
}
|
||||
|
||||
function getFilteredHistory() {
|
||||
const range = getFilterRange(selectedFilterKey);
|
||||
if (!range.start && !range.end)
|
||||
return NotificationService.historyList;
|
||||
return NotificationService.historyList.filter(n => {
|
||||
const ts = n.timestamp;
|
||||
if (range.start && ts < range.start.getTime())
|
||||
return false;
|
||||
if (range.end && ts >= range.end.getTime())
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function getChipIndex() {
|
||||
for (let i = 0; i < visibleFilters.length; i++) {
|
||||
if (visibleFilters[i].key === selectedFilterKey)
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function enableAutoScroll() {
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankFilterChips {
|
||||
id: filterChips
|
||||
width: parent.width
|
||||
currentIndex: root.getChipIndex()
|
||||
showCounts: true
|
||||
model: root.visibleFilters
|
||||
onSelectionChanged: index => {
|
||||
if (index >= 0 && index < root.visibleFilters.length) {
|
||||
root.selectedFilterKey = root.visibleFilters[index].key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankListView {
|
||||
id: historyListView
|
||||
width: parent.width
|
||||
height: parent.height - filterChips.height - Theme.spacingS
|
||||
clip: true
|
||||
spacing: Theme.spacingS
|
||||
|
||||
model: ScriptModel {
|
||||
id: historyModel
|
||||
values: root.getFilteredHistory()
|
||||
objectProp: "id"
|
||||
}
|
||||
|
||||
NotificationEmptyState {
|
||||
visible: historyListView.count === 0
|
||||
y: Theme.spacingL
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
delegate: HistoryNotificationCard {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: ListView.view.width
|
||||
historyItem: modelData
|
||||
isSelected: root.keyboardActive && root.selectedIndex === index
|
||||
keyboardNavigationActive: root.keyboardActive
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function selectNext() {
|
||||
if (historyModel.values.length === 0)
|
||||
return;
|
||||
keyboardActive = true;
|
||||
selectedIndex = Math.min(selectedIndex + 1, historyModel.values.length - 1);
|
||||
historyListView.positionViewAtIndex(selectedIndex, ListView.Contain);
|
||||
}
|
||||
|
||||
function selectPrevious() {
|
||||
if (historyModel.values.length === 0)
|
||||
return;
|
||||
if (selectedIndex <= 0) {
|
||||
keyboardActive = false;
|
||||
selectedIndex = -1;
|
||||
return;
|
||||
}
|
||||
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||
historyListView.positionViewAtIndex(selectedIndex, ListView.Contain);
|
||||
}
|
||||
|
||||
function clearSelected() {
|
||||
if (selectedIndex < 0 || selectedIndex >= historyModel.values.length)
|
||||
return;
|
||||
const item = historyModel.values[selectedIndex];
|
||||
NotificationService.removeFromHistory(item.id);
|
||||
if (historyModel.values.length === 0) {
|
||||
keyboardActive = false;
|
||||
selectedIndex = -1;
|
||||
} else {
|
||||
selectedIndex = Math.min(selectedIndex, historyModel.values.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
function handleKey(event) {
|
||||
if (event.key === Qt.Key_Down || event.key === 16777237) {
|
||||
if (!keyboardActive) {
|
||||
keyboardActive = true;
|
||||
selectedIndex = 0;
|
||||
} else {
|
||||
selectNext();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_Up || event.key === 16777235) {
|
||||
if (keyboardActive) {
|
||||
selectPrevious();
|
||||
}
|
||||
event.accepted = true;
|
||||
} else if (keyboardActive && (event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace)) {
|
||||
clearSelected();
|
||||
event.accepted = true;
|
||||
} else if ((event.key === Qt.Key_Delete || event.key === Qt.Key_Backspace) && (event.modifiers & Qt.ShiftModifier)) {
|
||||
NotificationService.clearHistory();
|
||||
keyboardActive = false;
|
||||
selectedIndex = -1;
|
||||
event.accepted = true;
|
||||
} else if (event.key === Qt.Key_F10) {
|
||||
showKeyboardHints = !showKeyboardHints;
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Notifications.Center
|
||||
|
||||
DankPopout {
|
||||
id: root
|
||||
@@ -112,8 +111,11 @@ DankPopout {
|
||||
baseHeight += Theme.spacingM * 2;
|
||||
|
||||
const settingsHeight = notificationSettings.expanded ? notificationSettings.contentHeight : 0;
|
||||
let listHeight = notificationList.listContentHeight;
|
||||
if (NotificationService.groupedNotifications.length === 0) {
|
||||
let listHeight = notificationHeader.currentTab === 0 ? notificationList.listContentHeight : Math.max(200, NotificationService.historyList.length * 80);
|
||||
if (notificationHeader.currentTab === 0 && NotificationService.groupedNotifications.length === 0) {
|
||||
listHeight = 200;
|
||||
}
|
||||
if (notificationHeader.currentTab === 1 && NotificationService.historyList.length === 0) {
|
||||
listHeight = 200;
|
||||
}
|
||||
|
||||
@@ -143,7 +145,13 @@ DankPopout {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
notificationHistoryVisible = false;
|
||||
event.accepted = true;
|
||||
} else if (externalKeyboardController) {
|
||||
return;
|
||||
}
|
||||
if (notificationHeader.currentTab === 1) {
|
||||
historyList.handleKey(event);
|
||||
return;
|
||||
}
|
||||
if (externalKeyboardController) {
|
||||
externalKeyboardController.handleKey(event);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +195,14 @@ DankPopout {
|
||||
KeyboardNavigatedNotificationList {
|
||||
id: notificationList
|
||||
objectName: "notificationList"
|
||||
visible: notificationHeader.currentTab === 0
|
||||
width: parent.width
|
||||
height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
|
||||
}
|
||||
|
||||
HistoryNotificationList {
|
||||
id: historyList
|
||||
visible: notificationHeader.currentTab === 1
|
||||
width: parent.width
|
||||
height: parent.height - notificationContent.cachedHeaderHeight - notificationSettings.height - contentColumnInner.spacing * 2
|
||||
}
|
||||
@@ -200,7 +215,7 @@ DankPopout {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingL
|
||||
showHints: (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false
|
||||
showHints: notificationHeader.currentTab === 0 ? (externalKeyboardController && externalKeyboardController.showKeyboardHints) || false : historyList.showKeyboardHints
|
||||
z: 200
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,108 +8,150 @@ Item {
|
||||
|
||||
property var keyboardController: null
|
||||
property bool showSettings: false
|
||||
property int currentTab: 0
|
||||
|
||||
onCurrentTabChanged: {
|
||||
if (currentTab === 1 && !SettingsData.notificationHistoryEnabled)
|
||||
currentTab = 0;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onNotificationHistoryEnabledChanged() {
|
||||
if (!SettingsData.notificationHistoryEnabled)
|
||||
root.currentTab = 0;
|
||||
}
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
height: 32
|
||||
height: headerColumn.implicitHeight
|
||||
|
||||
DankTooltipV2 {
|
||||
id: sharedTooltip
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
Column {
|
||||
id: headerColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Notifications")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: doNotDisturbButton
|
||||
|
||||
iconName: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||
iconColor: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
onEntered: {
|
||||
sharedTooltip.show(I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom");
|
||||
}
|
||||
onExited: {
|
||||
sharedTooltip.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankActionButton {
|
||||
id: helpButton
|
||||
iconName: "info"
|
||||
iconColor: (keyboardController && keyboardController.showKeyboardHints) ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
visible: keyboardController !== null
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (keyboardController) {
|
||||
keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: settingsButton
|
||||
iconName: "settings"
|
||||
iconColor: root.showSettings ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: 28
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.showSettings = !root.showSettings
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
|
||||
width: 120
|
||||
height: 28
|
||||
radius: Theme.cornerRadius
|
||||
visible: NotificationService.notifications.length > 0
|
||||
color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(titleRow.implicitHeight, actionsRow.implicitHeight)
|
||||
|
||||
Row {
|
||||
anchors.centerIn: parent
|
||||
id: titleRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "delete_sweep"
|
||||
size: Theme.iconSizeSmall
|
||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Clear")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
text: I18n.tr("Notifications")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: doNotDisturbButton
|
||||
iconName: SessionData.doNotDisturb ? "notifications_off" : "notifications"
|
||||
iconColor: SessionData.doNotDisturb ? Theme.error : Theme.surfaceText
|
||||
buttonSize: Theme.iconSize + Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: SessionData.setDoNotDisturb(!SessionData.doNotDisturb)
|
||||
onEntered: sharedTooltip.show(I18n.tr("Do Not Disturb"), doNotDisturbButton, 0, 0, "bottom")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
Row {
|
||||
id: actionsRow
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NotificationService.clearAllNotifications()
|
||||
DankActionButton {
|
||||
id: helpButton
|
||||
iconName: "info"
|
||||
iconColor: (keyboardController && keyboardController.showKeyboardHints) ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: Theme.iconSize + Theme.spacingS
|
||||
visible: keyboardController !== null
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
if (keyboardController)
|
||||
keyboardController.showKeyboardHints = !keyboardController.showKeyboardHints;
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: settingsButton
|
||||
iconName: "settings"
|
||||
iconColor: root.showSettings ? Theme.primary : Theme.surfaceText
|
||||
buttonSize: Theme.iconSize + Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.showSettings = !root.showSettings
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: clearAllButton
|
||||
width: clearButtonContent.implicitWidth + Theme.spacingM * 2
|
||||
height: Theme.iconSize + Theme.spacingS
|
||||
radius: Theme.cornerRadius
|
||||
visible: root.currentTab === 0 ? NotificationService.notifications.length > 0 : NotificationService.historyList.length > 0
|
||||
color: clearArea.containsMouse ? Theme.primaryHoverLight : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
|
||||
Row {
|
||||
id: clearButtonContent
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "delete_sweep"
|
||||
size: Theme.iconSizeSmall
|
||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Clear")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: clearArea.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clearArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.currentTab === 0) {
|
||||
NotificationService.clearAllNotifications();
|
||||
} else {
|
||||
NotificationService.clearHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: tabGroup
|
||||
width: parent.width
|
||||
currentIndex: root.currentTab
|
||||
buttonHeight: 32
|
||||
buttonPadding: Theme.spacingM
|
||||
checkEnabled: false
|
||||
textSize: Theme.fontSizeSmall
|
||||
visible: SettingsData.notificationHistoryEnabled
|
||||
model: [I18n.tr("Current", "notification center tab") + " (" + NotificationService.notifications.length + ")", I18n.tr("History", "notification center tab") + " (" + NotificationService.historyList.length + ")"]
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (selected)
|
||||
root.currentTab = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Rectangle {
|
||||
@@ -36,64 +34,77 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var timeoutOptions: [{
|
||||
readonly property var timeoutOptions: [
|
||||
{
|
||||
"text": "Never",
|
||||
"value": 0
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "1 second",
|
||||
"value": 1000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "3 seconds",
|
||||
"value": 3000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "5 seconds",
|
||||
"value": 5000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "8 seconds",
|
||||
"value": 8000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "10 seconds",
|
||||
"value": 10000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "15 seconds",
|
||||
"value": 15000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "30 seconds",
|
||||
"value": 30000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "1 minute",
|
||||
"value": 60000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "2 minutes",
|
||||
"value": 120000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "5 minutes",
|
||||
"value": 300000
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"text": "10 minutes",
|
||||
"value": 600000
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
function getTimeoutText(value) {
|
||||
if (value === undefined || value === null || isNaN(value)) {
|
||||
return "5 seconds"
|
||||
return "5 seconds";
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].value === value) {
|
||||
return timeoutOptions[i].text
|
||||
return timeoutOptions[i].text;
|
||||
}
|
||||
}
|
||||
if (value === 0) {
|
||||
return "Never"
|
||||
return "Never";
|
||||
}
|
||||
if (value < 1000) {
|
||||
return value + "ms"
|
||||
return value + "ms";
|
||||
}
|
||||
if (value < 60000) {
|
||||
return Math.round(value / 1000) + " seconds"
|
||||
return Math.round(value / 1000) + " seconds";
|
||||
}
|
||||
return Math.round(value / 60000) + " minutes"
|
||||
return Math.round(value / 60000) + " minutes";
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -113,9 +124,10 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
height: Math.max(dndRow.implicitHeight, dndToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: dndRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
@@ -136,6 +148,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: dndToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SessionData.doNotDisturb
|
||||
@@ -162,13 +175,13 @@ Rectangle {
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutLow", timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutLow", timeoutOptions[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -177,13 +190,13 @@ Rectangle {
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutNormal", timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutNormal", timeoutOptions[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankDropdown {
|
||||
@@ -192,13 +205,13 @@ Rectangle {
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutCritical", timeoutOptions[i].value)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
if (timeoutOptions[i].text === value) {
|
||||
SettingsData.set("notificationTimeoutCritical", timeoutOptions[i].value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -209,9 +222,10 @@ Rectangle {
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 36
|
||||
height: Math.max(overlayRow.implicitHeight, overlayToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: overlayRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
@@ -242,11 +256,127 @@ Rectangle {
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: overlayToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.notificationOverlayEnabled
|
||||
onToggled: toggled => SettingsData.set("notificationOverlayEnabled", toggled)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.1)
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("History Settings")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(lowRow.implicitHeight, lowToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: lowRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "low_priority"
|
||||
size: Theme.iconSizeSmall
|
||||
color: SettingsData.notificationHistorySaveLow ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Low Priority")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: lowToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.notificationHistorySaveLow
|
||||
onToggled: toggled => SettingsData.set("notificationHistorySaveLow", toggled)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(normalRow.implicitHeight, normalToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: normalRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "notifications"
|
||||
size: Theme.iconSizeSmall
|
||||
color: SettingsData.notificationHistorySaveNormal ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Normal Priority")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: normalToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.notificationHistorySaveNormal
|
||||
onToggled: toggled => SettingsData.set("notificationHistorySaveNormal", toggled)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: Math.max(criticalRow.implicitHeight, criticalToggle.implicitHeight) + Theme.spacingS
|
||||
|
||||
Row {
|
||||
id: criticalRow
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
DankIcon {
|
||||
name: "priority_high"
|
||||
size: Theme.iconSizeSmall
|
||||
color: SettingsData.notificationHistorySaveCritical ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Critical Priority")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: criticalToggle
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
checked: SettingsData.notificationHistorySaveCritical
|
||||
onToggled: toggled => SettingsData.set("notificationHistorySaveCritical", toggled)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,16 +587,30 @@ Item {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
id: wiredLoadIcon
|
||||
name: "sync"
|
||||
size: 16
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
RotationAnimation on rotation {
|
||||
SequentialAnimation {
|
||||
running: NetworkService.networkWiredInfoLoading
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
NumberAnimation {
|
||||
target: wiredLoadIcon
|
||||
property: "opacity"
|
||||
to: 0.3
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: wiredLoadIcon
|
||||
property: "opacity"
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
onRunningChanged: if (!running)
|
||||
wiredLoadIcon.opacity = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,17 +771,8 @@ Item {
|
||||
DankActionButton {
|
||||
iconName: "refresh"
|
||||
buttonSize: 32
|
||||
visible: NetworkService.wifiEnabled && !NetworkService.wifiToggling
|
||||
enabled: !NetworkService.isScanning
|
||||
visible: NetworkService.wifiEnabled && !NetworkService.wifiToggling && !NetworkService.isScanning
|
||||
onClicked: NetworkService.scanWifi()
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: NetworkService.isScanning
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
@@ -956,17 +961,31 @@ Item {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "refresh"
|
||||
id: scanningIcon
|
||||
name: "wifi_find"
|
||||
size: 32
|
||||
color: Theme.surfaceVariantText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
RotationAnimation on rotation {
|
||||
running: true
|
||||
SequentialAnimation {
|
||||
running: NetworkService.isScanning
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
NumberAnimation {
|
||||
target: scanningIcon
|
||||
property: "opacity"
|
||||
to: 0.3
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: scanningIcon
|
||||
property: "opacity"
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
onRunningChanged: if (!running)
|
||||
scanningIcon.opacity = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1241,16 +1260,30 @@ Item {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
id: wifiInfoLoadIcon
|
||||
name: "sync"
|
||||
size: 16
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
RotationAnimation on rotation {
|
||||
SequentialAnimation {
|
||||
running: NetworkService.networkInfoLoading
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 1000
|
||||
NumberAnimation {
|
||||
target: wifiInfoLoadIcon
|
||||
property: "opacity"
|
||||
to: 0.3
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: wifiInfoLoadIcon
|
||||
property: "opacity"
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
onRunningChanged: if (!running)
|
||||
wifiInfoLoadIcon.opacity = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1707,9 +1740,31 @@ Item {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
id: vpnLoadIcon
|
||||
name: "sync"
|
||||
size: 16
|
||||
color: Theme.surfaceVariantText
|
||||
|
||||
SequentialAnimation {
|
||||
running: VPNService.configLoading
|
||||
loops: Animation.Infinite
|
||||
NumberAnimation {
|
||||
target: vpnLoadIcon
|
||||
property: "opacity"
|
||||
to: 0.3
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: vpnLoadIcon
|
||||
property: "opacity"
|
||||
to: 1.0
|
||||
duration: 400
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
onRunningChanged: if (!running)
|
||||
vpnLoadIcon.opacity = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
|
||||
@@ -219,6 +219,105 @@ Item {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsCard {
|
||||
width: parent.width
|
||||
iconName: "history"
|
||||
title: I18n.tr("History Settings")
|
||||
settingKey: "notificationHistory"
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "notificationHistoryEnabled"
|
||||
tags: ["notification", "history", "enable", "disable", "save"]
|
||||
text: I18n.tr("Enable History", "notification history toggle label")
|
||||
description: I18n.tr("Save dismissed notifications to history", "notification history toggle description")
|
||||
checked: SettingsData.notificationHistoryEnabled
|
||||
onToggled: checked => SettingsData.set("notificationHistoryEnabled", checked)
|
||||
}
|
||||
|
||||
SettingsSliderRow {
|
||||
settingKey: "notificationHistoryMaxCount"
|
||||
tags: ["notification", "history", "max", "count", "limit"]
|
||||
text: I18n.tr("Maximum History")
|
||||
description: I18n.tr("Maximum number of notifications to keep", "notification history limit")
|
||||
value: SettingsData.notificationHistoryMaxCount
|
||||
minimum: 10
|
||||
maximum: 200
|
||||
step: 10
|
||||
unit: ""
|
||||
defaultValue: 50
|
||||
onSliderValueChanged: newValue => SettingsData.set("notificationHistoryMaxCount", newValue)
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "notificationHistoryMaxAgeDays"
|
||||
tags: ["notification", "history", "max", "age", "days", "retention"]
|
||||
text: I18n.tr("History Retention", "notification history retention settings label")
|
||||
description: I18n.tr("Auto-delete notifications older than this", "notification history setting")
|
||||
currentValue: {
|
||||
switch (SettingsData.notificationHistoryMaxAgeDays) {
|
||||
case 0:
|
||||
return I18n.tr("Forever", "notification history retention option");
|
||||
case 1:
|
||||
return I18n.tr("1 day", "notification history retention option");
|
||||
case 3:
|
||||
return I18n.tr("3 days", "notification history retention option");
|
||||
case 7:
|
||||
return I18n.tr("7 days", "notification history retention option");
|
||||
case 14:
|
||||
return I18n.tr("14 days", "notification history retention option");
|
||||
case 30:
|
||||
return I18n.tr("30 days", "notification history retention option");
|
||||
default:
|
||||
return SettingsData.notificationHistoryMaxAgeDays + " " + I18n.tr("days");
|
||||
}
|
||||
}
|
||||
options: [I18n.tr("Forever", "notification history retention option"), I18n.tr("1 day", "notification history retention option"), I18n.tr("3 days", "notification history retention option"), I18n.tr("7 days", "notification history retention option"), I18n.tr("14 days", "notification history retention option"), I18n.tr("30 days", "notification history retention option")]
|
||||
onValueChanged: value => {
|
||||
let days = 7;
|
||||
if (value === I18n.tr("Forever", "notification history retention option"))
|
||||
days = 0;
|
||||
else if (value === I18n.tr("1 day", "notification history retention option"))
|
||||
days = 1;
|
||||
else if (value === I18n.tr("3 days", "notification history retention option"))
|
||||
days = 3;
|
||||
else if (value === I18n.tr("7 days", "notification history retention option"))
|
||||
days = 7;
|
||||
else if (value === I18n.tr("14 days", "notification history retention option"))
|
||||
days = 14;
|
||||
else if (value === I18n.tr("30 days", "notification history retention option"))
|
||||
days = 30;
|
||||
SettingsData.set("notificationHistoryMaxAgeDays", days);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "notificationHistorySaveLow"
|
||||
tags: ["notification", "history", "save", "low", "priority"]
|
||||
text: I18n.tr("Low Priority")
|
||||
description: I18n.tr("Save low priority notifications to history", "notification history setting")
|
||||
checked: SettingsData.notificationHistorySaveLow
|
||||
onToggled: checked => SettingsData.set("notificationHistorySaveLow", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "notificationHistorySaveNormal"
|
||||
tags: ["notification", "history", "save", "normal", "priority"]
|
||||
text: I18n.tr("Normal Priority")
|
||||
description: I18n.tr("Save normal priority notifications to history", "notification history setting")
|
||||
checked: SettingsData.notificationHistorySaveNormal
|
||||
onToggled: checked => SettingsData.set("notificationHistorySaveNormal", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "notificationHistorySaveCritical"
|
||||
tags: ["notification", "history", "save", "critical", "priority"]
|
||||
text: I18n.tr("Critical Priority")
|
||||
description: I18n.tr("Save critical priority notifications to history", "notification history setting")
|
||||
checked: SettingsData.notificationHistorySaveCritical
|
||||
onToggled: checked => SettingsData.set("notificationHistorySaveCritical", checked)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,15 @@ Item {
|
||||
onToggled: checked => SettingsData.set("fadeToLockEnabled", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "fadeToDpmsEnabled"
|
||||
tags: ["fade", "dpms", "monitor", "screen", "idle", "grace period"]
|
||||
text: I18n.tr("Fade to monitor off")
|
||||
description: I18n.tr("Gradually fade the screen before turning off monitors with a configurable grace period")
|
||||
checked: SettingsData.fadeToDpmsEnabled
|
||||
onToggled: checked => SettingsData.set("fadeToDpmsEnabled", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockBeforeSuspend"
|
||||
tags: ["lock", "suspend", "sleep", "security"]
|
||||
@@ -89,7 +98,7 @@ Item {
|
||||
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
|
||||
property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
|
||||
|
||||
text: I18n.tr("Fade grace period")
|
||||
text: I18n.tr("Lock fade grace period")
|
||||
options: periodOptions
|
||||
visible: SettingsData.fadeToLockEnabled
|
||||
enabled: SettingsData.fadeToLockEnabled
|
||||
@@ -107,6 +116,32 @@ Item {
|
||||
SettingsData.set("fadeToLockGracePeriod", periodValues[index]);
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
id: fadeDpmsGracePeriodDropdown
|
||||
settingKey: "fadeToDpmsGracePeriod"
|
||||
tags: ["fade", "grace", "period", "timeout", "dpms", "monitor"]
|
||||
property var periodOptions: ["1 second", "2 seconds", "3 seconds", "4 seconds", "5 seconds", "10 seconds", "15 seconds", "20 seconds", "30 seconds"]
|
||||
property var periodValues: [1, 2, 3, 4, 5, 10, 15, 20, 30]
|
||||
|
||||
text: I18n.tr("Monitor fade grace period")
|
||||
options: periodOptions
|
||||
visible: SettingsData.fadeToDpmsEnabled
|
||||
enabled: SettingsData.fadeToDpmsEnabled
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentPeriod = SettingsData.fadeToDpmsGracePeriod;
|
||||
const index = periodValues.indexOf(currentPeriod);
|
||||
currentValue = index >= 0 ? periodOptions[index] : "5 seconds";
|
||||
}
|
||||
|
||||
onValueChanged: value => {
|
||||
const index = periodOptions.indexOf(value);
|
||||
if (index < 0)
|
||||
return;
|
||||
SettingsData.set("fadeToDpmsGracePeriod", periodValues[index]);
|
||||
}
|
||||
}
|
||||
SettingsDropdownRow {
|
||||
id: powerProfileDropdown
|
||||
settingKey: "powerProfile"
|
||||
|
||||
@@ -53,6 +53,8 @@ Singleton {
|
||||
signal lockRequested
|
||||
signal fadeToLockRequested
|
||||
signal cancelFadeToLock
|
||||
signal fadeToDpmsRequested
|
||||
signal cancelFadeToDpms
|
||||
signal requestMonitorOff
|
||||
signal requestMonitorOn
|
||||
signal requestSuspend
|
||||
@@ -61,11 +63,17 @@ Singleton {
|
||||
property var lockMonitor: null
|
||||
property var suspendMonitor: null
|
||||
property var lockComponent: null
|
||||
property bool monitorsOff: false
|
||||
|
||||
function wake() {
|
||||
requestMonitorOn();
|
||||
}
|
||||
|
||||
function reapplyDpmsIfNeeded() {
|
||||
if (monitorsOff)
|
||||
CompositorService.powerOffMonitors();
|
||||
}
|
||||
|
||||
function createIdleMonitors() {
|
||||
if (!idleMonitorAvailable) {
|
||||
console.info("IdleService: IdleMonitor not available, skipping creation");
|
||||
@@ -90,8 +98,15 @@ Singleton {
|
||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout);
|
||||
monitorOffMonitor.isIdleChanged.connect(function () {
|
||||
if (monitorOffMonitor.isIdle) {
|
||||
root.requestMonitorOff();
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
root.fadeToDpmsRequested();
|
||||
} else {
|
||||
root.requestMonitorOff();
|
||||
}
|
||||
} else {
|
||||
if (SettingsData.fadeToDpmsEnabled) {
|
||||
root.cancelFadeToDpms();
|
||||
}
|
||||
root.requestMonitorOn();
|
||||
}
|
||||
});
|
||||
@@ -131,10 +146,12 @@ Singleton {
|
||||
Connections {
|
||||
target: root
|
||||
function onRequestMonitorOff() {
|
||||
monitorsOff = true;
|
||||
CompositorService.powerOffMonitors();
|
||||
}
|
||||
|
||||
function onRequestMonitorOn() {
|
||||
monitorsOff = false;
|
||||
CompositorService.powerOnMonitors();
|
||||
}
|
||||
|
||||
|
||||
@@ -145,6 +145,26 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: ensureOutputsProcess
|
||||
property string outputsPath: ""
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0)
|
||||
console.warn("NiriService: Failed to ensure outputs.kdl, exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: ensureBindsProcess
|
||||
property string bindsPath: ""
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode !== 0)
|
||||
console.warn("NiriService: Failed to ensure binds.kdl, exit code:", exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: eventStreamSocket
|
||||
path: root.socketPath
|
||||
@@ -1042,6 +1062,16 @@ Singleton {
|
||||
writeAlttabProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && cat > "${alttabPath}" << 'EOF'\n${alttabContent}\nEOF`];
|
||||
writeAlttabProcess.running = true;
|
||||
|
||||
const outputsPath = niriDmsDir + "/outputs.kdl";
|
||||
ensureOutputsProcess.outputsPath = outputsPath;
|
||||
ensureOutputsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && [ ! -f "${outputsPath}" ] && touch "${outputsPath}" || true`];
|
||||
ensureOutputsProcess.running = true;
|
||||
|
||||
const bindsPath = niriDmsDir + "/binds.kdl";
|
||||
ensureBindsProcess.bindsPath = bindsPath;
|
||||
ensureBindsProcess.command = ["sh", "-c", `mkdir -p "${niriDmsDir}" && [ ! -f "${bindsPath}" ] && touch "${bindsPath}" || true`];
|
||||
ensureBindsProcess.running = true;
|
||||
|
||||
configGenerationPending = false;
|
||||
}
|
||||
|
||||
@@ -1147,8 +1177,7 @@ Singleton {
|
||||
kdlContent += `output "${identifier}" {\n`;
|
||||
|
||||
if (niriSettings.disabled) {
|
||||
kdlContent += ` off\n}\n\n`;
|
||||
continue;
|
||||
kdlContent += ` off\n`;
|
||||
}
|
||||
|
||||
if (output.current_mode !== undefined && output.modes && output.modes[output.current_mode]) {
|
||||
@@ -1157,7 +1186,7 @@ Singleton {
|
||||
}
|
||||
|
||||
if (output.logical) {
|
||||
kdlContent += ` scale ${output.logical.scale ?? 1.0}\n`;
|
||||
kdlContent += ` scale ${output.logical.scale || 1.0}\n`;
|
||||
|
||||
if (output.logical.transform && output.logical.transform !== "Normal") {
|
||||
const transformMap = {
|
||||
|
||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import Quickshell.Services.Notifications
|
||||
import qs.Common
|
||||
import "../Common/markdown2html.js" as Markdown2Html
|
||||
@@ -14,6 +15,10 @@ Singleton {
|
||||
readonly property list<NotifWrapper> allWrappers: []
|
||||
readonly property list<NotifWrapper> popups: allWrappers.filter(n => n && n.popup)
|
||||
|
||||
property var historyList: []
|
||||
readonly property string historyFile: Paths.strip(Paths.cache) + "/notification_history.json"
|
||||
property bool historyLoaded: false
|
||||
|
||||
property list<NotifWrapper> notificationQueue: []
|
||||
property list<NotifWrapper> visibleNotifications: []
|
||||
property int maxVisibleNotifications: 3
|
||||
@@ -26,7 +31,7 @@ Singleton {
|
||||
property int maxIngressPerSecond: 20
|
||||
property double _lastIngressSec: 0
|
||||
property int _ingressCountThisSec: 0
|
||||
property int maxStoredNotifications: 50
|
||||
property int maxStoredNotifications: SettingsData.notificationHistoryMaxCount
|
||||
|
||||
property var _dismissQueue: []
|
||||
property int _dismissBatchSize: 8
|
||||
@@ -40,6 +45,165 @@ Singleton {
|
||||
|
||||
Component.onCompleted: {
|
||||
_recomputeGroups();
|
||||
Quickshell.execDetached(["mkdir", "-p", Paths.strip(Paths.cache)]);
|
||||
}
|
||||
|
||||
FileView {
|
||||
id: historyFileView
|
||||
path: root.historyFile
|
||||
printErrors: false
|
||||
onLoaded: root.loadHistory()
|
||||
onLoadFailed: error => {
|
||||
if (error === 2) {
|
||||
root.historyLoaded = true;
|
||||
historyFileView.writeAdapter();
|
||||
}
|
||||
}
|
||||
|
||||
JsonAdapter {
|
||||
id: historyAdapter
|
||||
property var notifications: []
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: historySaveTimer
|
||||
interval: 200
|
||||
onTriggered: root.performSaveHistory()
|
||||
}
|
||||
|
||||
function addToHistory(wrapper) {
|
||||
if (!wrapper)
|
||||
return;
|
||||
const urg = typeof wrapper.urgency === "number" ? wrapper.urgency : 1;
|
||||
const data = {
|
||||
id: wrapper.notification?.id?.toString() || Date.now().toString(),
|
||||
summary: wrapper.summary || "",
|
||||
body: wrapper.body || "",
|
||||
htmlBody: wrapper.htmlBody || wrapper.body || "",
|
||||
appName: wrapper.appName || "",
|
||||
appIcon: wrapper.appIcon || "",
|
||||
image: wrapper.cleanImage || "",
|
||||
urgency: urg,
|
||||
timestamp: wrapper.time.getTime(),
|
||||
desktopEntry: wrapper.desktopEntry || ""
|
||||
};
|
||||
let newList = [data, ...historyList];
|
||||
if (newList.length > SettingsData.notificationHistoryMaxCount) {
|
||||
newList = newList.slice(0, SettingsData.notificationHistoryMaxCount);
|
||||
}
|
||||
historyList = newList;
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
function saveHistory() {
|
||||
historySaveTimer.restart();
|
||||
}
|
||||
|
||||
function performSaveHistory() {
|
||||
try {
|
||||
historyAdapter.notifications = historyList;
|
||||
historyFileView.writeAdapter();
|
||||
} catch (e) {
|
||||
console.warn("NotificationService: save history failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
function loadHistory() {
|
||||
try {
|
||||
const maxAgeDays = SettingsData.notificationHistoryMaxAgeDays;
|
||||
const now = Date.now();
|
||||
const maxAgeMs = maxAgeDays > 0 ? maxAgeDays * 24 * 60 * 60 * 1000 : 0;
|
||||
const loaded = [];
|
||||
|
||||
for (const item of historyAdapter.notifications || []) {
|
||||
if (maxAgeMs > 0 && (now - item.timestamp) > maxAgeMs)
|
||||
continue;
|
||||
const urg = typeof item.urgency === "number" ? item.urgency : 1;
|
||||
const body = item.body || "";
|
||||
let htmlBody = item.htmlBody || "";
|
||||
if (!htmlBody && body) {
|
||||
htmlBody = (body.includes('<') && body.includes('>')) ? body : Markdown2Html.markdownToHtml(body);
|
||||
}
|
||||
loaded.push({
|
||||
id: item.id || "",
|
||||
summary: item.summary || "",
|
||||
body: body,
|
||||
htmlBody: htmlBody,
|
||||
appName: item.appName || "",
|
||||
appIcon: item.appIcon || "",
|
||||
image: item.image || "",
|
||||
urgency: urg,
|
||||
timestamp: item.timestamp || 0,
|
||||
desktopEntry: item.desktopEntry || ""
|
||||
});
|
||||
}
|
||||
historyList = loaded;
|
||||
historyLoaded = true;
|
||||
if (maxAgeMs > 0 && loaded.length !== (historyAdapter.notifications || []).length)
|
||||
saveHistory();
|
||||
} catch (e) {
|
||||
console.warn("NotificationService: load history failed:", e);
|
||||
historyLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromHistory(notificationId) {
|
||||
const idx = historyList.findIndex(n => n.id === notificationId);
|
||||
if (idx >= 0) {
|
||||
historyList = historyList.filter((_, i) => i !== idx);
|
||||
saveHistory();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
historyList = [];
|
||||
saveHistory();
|
||||
}
|
||||
|
||||
function getHistoryTimeRange(timestamp) {
|
||||
const now = new Date();
|
||||
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const itemDate = new Date(timestamp);
|
||||
const itemDay = new Date(itemDate.getFullYear(), itemDate.getMonth(), itemDate.getDate());
|
||||
const diffDays = Math.floor((today - itemDay) / (1000 * 60 * 60 * 24));
|
||||
if (diffDays === 0)
|
||||
return 0;
|
||||
if (diffDays === 1)
|
||||
return 1;
|
||||
return 2;
|
||||
}
|
||||
|
||||
function getHistoryCountForRange(range) {
|
||||
if (range === -1)
|
||||
return historyList.length;
|
||||
return historyList.filter(n => getHistoryTimeRange(n.timestamp) === range).length;
|
||||
}
|
||||
|
||||
function formatHistoryTime(timestamp) {
|
||||
root.timeUpdateTick;
|
||||
root.clockFormatChanged;
|
||||
const now = new Date();
|
||||
const date = new Date(timestamp);
|
||||
const diff = now.getTime() - timestamp;
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
if (hours < 1) {
|
||||
if (minutes < 1)
|
||||
return I18n.tr("now");
|
||||
return I18n.tr("%1m ago").arg(minutes);
|
||||
}
|
||||
const nowDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const itemDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
const daysDiff = Math.floor((nowDate - itemDate) / (1000 * 60 * 60 * 24));
|
||||
const timeStr = SettingsData.use24HourClock ? date.toLocaleTimeString(Qt.locale(), "HH:mm") : date.toLocaleTimeString(Qt.locale(), "h:mm AP");
|
||||
if (daysDiff === 0)
|
||||
return timeStr;
|
||||
if (daysDiff === 1)
|
||||
return I18n.tr("yesterday") + ", " + timeStr;
|
||||
return I18n.tr("%1 days ago").arg(daysDiff);
|
||||
}
|
||||
|
||||
function _nowSec() {
|
||||
@@ -84,6 +248,40 @@ Singleton {
|
||||
wrapper.isPersistent = isCritical || (timeoutMs === 0);
|
||||
}
|
||||
|
||||
function _shouldSaveToHistory(urgency) {
|
||||
if (!SettingsData.notificationHistoryEnabled)
|
||||
return false;
|
||||
switch (urgency) {
|
||||
case NotificationUrgency.Low:
|
||||
return SettingsData.notificationHistorySaveLow;
|
||||
case NotificationUrgency.Critical:
|
||||
return SettingsData.notificationHistorySaveCritical;
|
||||
default:
|
||||
return SettingsData.notificationHistorySaveNormal;
|
||||
}
|
||||
}
|
||||
|
||||
function pruneHistory() {
|
||||
const maxAgeDays = SettingsData.notificationHistoryMaxAgeDays;
|
||||
if (maxAgeDays <= 0)
|
||||
return;
|
||||
|
||||
const now = Date.now();
|
||||
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
|
||||
const pruned = historyList.filter(item => (now - item.timestamp) <= maxAgeMs);
|
||||
|
||||
if (pruned.length !== historyList.length) {
|
||||
historyList = pruned;
|
||||
saveHistory();
|
||||
}
|
||||
}
|
||||
|
||||
function deleteHistory() {
|
||||
historyList = [];
|
||||
historyAdapter.notifications = [];
|
||||
historyFileView.writeAdapter();
|
||||
}
|
||||
|
||||
function _trimStored() {
|
||||
if (notifications.length > maxStoredNotifications) {
|
||||
const overflow = notifications.length - maxStoredNotifications;
|
||||
@@ -121,6 +319,7 @@ Singleton {
|
||||
}
|
||||
visibleNotifications = [];
|
||||
_recomputeGroupsLater();
|
||||
pruneHistory();
|
||||
}
|
||||
|
||||
function onOverlayClose() {
|
||||
@@ -234,9 +433,11 @@ Singleton {
|
||||
|
||||
if (wrapper) {
|
||||
root.allWrappers.push(wrapper);
|
||||
if (!isTransient) {
|
||||
const shouldSave = !isTransient && _shouldSaveToHistory(notif.urgency);
|
||||
if (shouldSave) {
|
||||
root.notifications.push(wrapper);
|
||||
_trimStored();
|
||||
root.addToHistory(wrapper);
|
||||
}
|
||||
|
||||
Qt.callLater(() => {
|
||||
@@ -703,5 +904,13 @@ Singleton {
|
||||
function onUse24HourClockChanged() {
|
||||
root.clockFormatChanged = !root.clockFormatChanged;
|
||||
}
|
||||
function onNotificationHistoryMaxAgeDaysChanged() {
|
||||
root.pruneHistory();
|
||||
}
|
||||
function onNotificationHistoryEnabledChanged() {
|
||||
if (!SettingsData.notificationHistoryEnabled) {
|
||||
root.deleteHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ Item {
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
anchors.left: parent.left
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -79,7 +79,7 @@ Item {
|
||||
visible: description.length > 0
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
anchors.left: parent.left
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ Item {
|
||||
width: contentRow.width - (contentRow.children[0].visible ? contentRow.children[0].width + contentRow.spacing : 0)
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +232,8 @@ Item {
|
||||
}
|
||||
|
||||
contentItem: Rectangle {
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1)
|
||||
border.color: Theme.primary
|
||||
border.width: 2
|
||||
@@ -313,11 +316,14 @@ Item {
|
||||
|
||||
StyledText {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.emptyText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +363,9 @@ Item {
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
@@ -374,9 +382,10 @@ Item {
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: delegateRoot.isCurrentValue ? Theme.primary : Theme.surfaceText
|
||||
font.weight: delegateRoot.isCurrentValue ? Font.Medium : Font.Normal
|
||||
width: root.popupWidth > 0 ? undefined : (delegateRoot.width - parent.x - Theme.spacingS)
|
||||
width: root.popupWidth > 0 ? undefined : (delegateRoot.width - parent.x - Theme.spacingS * 2)
|
||||
elide: root.popupWidth > 0 ? Text.ElideNone : Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
101
quickshell/Widgets/DankFilterChips.qml
Normal file
101
quickshell/Widgets/DankFilterChips.qml
Normal file
@@ -0,0 +1,101 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Widgets
|
||||
|
||||
Flow {
|
||||
id: root
|
||||
|
||||
property var model: []
|
||||
property int currentIndex: 0
|
||||
property int chipHeight: 32
|
||||
property int chipPadding: Theme.spacingM
|
||||
property bool showCheck: true
|
||||
property bool showCounts: true
|
||||
|
||||
signal selectionChanged(int index)
|
||||
|
||||
spacing: Theme.spacingS
|
||||
width: parent ? parent.width : 400
|
||||
|
||||
Repeater {
|
||||
model: root.model
|
||||
|
||||
Rectangle {
|
||||
id: chip
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
property bool selected: index === root.currentIndex
|
||||
property bool hovered: mouseArea.containsMouse
|
||||
property bool pressed: mouseArea.pressed
|
||||
property string label: typeof modelData === "string" ? modelData : (modelData.label || "")
|
||||
property int count: typeof modelData === "object" ? (modelData.count || 0) : 0
|
||||
property bool showCount: root.showCounts && count > 0
|
||||
|
||||
width: contentRow.implicitWidth + root.chipPadding * 2
|
||||
height: root.chipHeight
|
||||
radius: height / 2
|
||||
|
||||
color: selected ? Theme.primary : Theme.surfaceVariant
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: parent.radius
|
||||
color: {
|
||||
if (pressed)
|
||||
return chip.selected ? Theme.primaryPressed : Theme.surfaceTextHover;
|
||||
if (hovered)
|
||||
return chip.selected ? Theme.primaryHover : Theme.surfaceTextHover;
|
||||
return "transparent";
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shorterDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: contentRow
|
||||
anchors.centerIn: parent
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: "check"
|
||||
size: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Theme.primaryText
|
||||
visible: root.showCheck && chip.selected
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: chip.label + (chip.showCount ? " (" + chip.count + ")" : "")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: chip.selected ? Font.Medium : Font.Normal
|
||||
color: chip.selected ? Theme.primaryText : Theme.surfaceVariantText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
root.currentIndex = chip.index;
|
||||
root.selectionChanged(chip.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ Item {
|
||||
font.weight: Font.Medium
|
||||
opacity: toggle.enabled ? 1 : 0.4
|
||||
width: parent.width
|
||||
anchors.left: parent.left
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -83,7 +83,7 @@ Item {
|
||||
wrapMode: Text.WordWrap
|
||||
width: Math.min(implicitWidth, toggle.width - 120)
|
||||
visible: toggle.description.length > 0
|
||||
anchors.left: parent.left
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"list.focusOutline": "{{colors.primary.dark.hex}}",
|
||||
"list.focusBackground": "{{colors.surface_container_high.dark.hex}}",
|
||||
"list.highlightForeground": "{{colors.primary.dark.hex}}",
|
||||
"list.focusHighlightForeground": "{{colors.on_primary.dark.hex}}",
|
||||
"list.errorForeground": "{{colors.error.dark.hex}}",
|
||||
"list.warningForeground": "{{colors.secondary.dark.hex}}",
|
||||
//
|
||||
@@ -162,9 +163,9 @@
|
||||
//
|
||||
// Scrollbar
|
||||
//
|
||||
"scrollbarSlider.background": "{{colors.outline.dark.hex}}40",
|
||||
"scrollbarSlider.hoverBackground": "{{colors.outline.dark.hex}}60",
|
||||
"scrollbarSlider.activeBackground": "{{colors.outline.dark.hex}}80",
|
||||
"scrollbarSlider.background": "{{colors.on_surface_variant.dark.hex}}50",
|
||||
"scrollbarSlider.hoverBackground": "{{colors.on_surface_variant.dark.hex}}80",
|
||||
"scrollbarSlider.activeBackground": "{{colors.on_surface_variant.dark.hex}}AA",
|
||||
//
|
||||
// Terminal (Dank16)
|
||||
//
|
||||
@@ -192,79 +193,107 @@
|
||||
//
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"comment"
|
||||
],
|
||||
"scope": ["comment"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color8.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword"
|
||||
],
|
||||
"scope": ["keyword"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color5.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string"
|
||||
],
|
||||
"scope": ["string"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color3.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant",
|
||||
"number"
|
||||
],
|
||||
"scope": ["constant", "constant.language", "constant.numeric"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable"
|
||||
],
|
||||
"scope": ["variable"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color15.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.function"
|
||||
],
|
||||
"scope": ["entity.name.function"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color2.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.class",
|
||||
"support.type"
|
||||
],
|
||||
"scope": ["entity.name.class", "support.type"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"invalid"
|
||||
],
|
||||
"scope": ["invalid"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.error.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"markup.heading"
|
||||
],
|
||||
"scope": ["markup.heading"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.primary.dark.hex}}",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.yaml", "punctuation.definition.block.sequence.item.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["source.yaml string.unquoted", "source.yaml string.quoted"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color3.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.language.boolean.yaml", "constant.language.null.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["punctuation.separator.key-value.mapping.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.toml", "support.type.property-name.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["string.quoted.single.basic.line.toml", "string.quoted.double.basic.line.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color3.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.language.boolean.toml", "constant.numeric.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.dark.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["meta.object-literal.key", "support.type.property-name.json"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.dark.hex}}"
|
||||
}
|
||||
}
|
||||
],
|
||||
//
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"list.focusOutline": "{{colors.primary.default.hex}}",
|
||||
"list.focusBackground": "{{colors.surface_container_high.default.hex}}",
|
||||
"list.highlightForeground": "{{colors.primary.default.hex}}",
|
||||
"list.focusHighlightForeground": "{{colors.on_primary.default.hex}}",
|
||||
"list.errorForeground": "{{colors.error.default.hex}}",
|
||||
"list.warningForeground": "{{colors.secondary.default.hex}}",
|
||||
"input.background": "{{colors.surface_container_low.default.hex}}",
|
||||
@@ -114,9 +115,9 @@
|
||||
"editorSuggestWidget.foreground": "{{colors.on_surface.default.hex}}",
|
||||
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.default.hex}}",
|
||||
"editorSuggestWidget.highlightForeground": "{{colors.primary.default.hex}}",
|
||||
"scrollbarSlider.background": "{{colors.outline.default.hex}}40",
|
||||
"scrollbarSlider.hoverBackground": "{{colors.outline.default.hex}}60",
|
||||
"scrollbarSlider.activeBackground": "{{colors.outline.default.hex}}80",
|
||||
"scrollbarSlider.background": "{{colors.on_surface_variant.default.hex}}50",
|
||||
"scrollbarSlider.hoverBackground": "{{colors.on_surface_variant.default.hex}}80",
|
||||
"scrollbarSlider.activeBackground": "{{colors.on_surface_variant.default.hex}}AA",
|
||||
"terminal.background": "{{colors.background.default.hex}}",
|
||||
"terminal.foreground": "{{colors.on_surface.default.hex}}",
|
||||
"terminal.ansiBlack": "{{dank16.color0.default.hex}}",
|
||||
@@ -138,79 +139,107 @@
|
||||
},
|
||||
"tokenColors": [
|
||||
{
|
||||
"scope": [
|
||||
"comment"
|
||||
],
|
||||
"scope": ["comment"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.outline.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"keyword"
|
||||
],
|
||||
"scope": ["keyword"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color5.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"string"
|
||||
],
|
||||
"scope": ["string"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color2.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"constant",
|
||||
"number"
|
||||
],
|
||||
"scope": ["constant", "constant.language", "constant.numeric"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"variable"
|
||||
],
|
||||
"scope": ["variable"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.function"
|
||||
],
|
||||
"scope": ["entity.name.function"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color4.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"entity.name.class",
|
||||
"support.type"
|
||||
],
|
||||
"scope": ["entity.name.class", "support.type"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.secondary.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"invalid"
|
||||
],
|
||||
"scope": ["invalid"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.error.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": [
|
||||
"markup.heading"
|
||||
],
|
||||
"scope": ["markup.heading"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.primary.default.hex}}",
|
||||
"fontStyle": "bold"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.yaml", "punctuation.definition.block.sequence.item.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["source.yaml string.unquoted", "source.yaml string.quoted"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color3.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.language.boolean.yaml", "constant.language.null.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["punctuation.separator.key-value.mapping.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.toml", "support.type.property-name.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["string.quoted.single.basic.line.toml", "string.quoted.double.basic.line.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color2.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.language.boolean.toml", "constant.numeric.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color12.default.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["meta.object-literal.key", "support.type.property-name.json"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.default.hex}}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"semanticTokenColors": {
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"list.focusBackground": "{{colors.surface_container_high.light.hex}}",
|
||||
"list.focusForeground": "{{colors.on_surface.light.hex}}",
|
||||
"list.highlightForeground": "{{colors.primary.light.hex}}",
|
||||
"list.focusHighlightForeground": "{{colors.on_primary.light.hex}}",
|
||||
"list.errorForeground": "{{colors.error.light.hex}}",
|
||||
"list.warningForeground": "{{colors.tertiary.light.hex}}",
|
||||
"statusBar.background": "{{colors.surface_container.light.hex}}",
|
||||
@@ -154,9 +155,9 @@
|
||||
"breadcrumb.foreground": "{{colors.outline.light.hex}}",
|
||||
"breadcrumb.focusForeground": "{{colors.on_surface.light.hex}}",
|
||||
"breadcrumb.activeSelectionForeground": "{{colors.primary.light.hex}}",
|
||||
"scrollbarSlider.background": "{{colors.outline.light.hex}}40",
|
||||
"scrollbarSlider.hoverBackground": "{{colors.outline.light.hex}}60",
|
||||
"scrollbarSlider.activeBackground": "{{colors.outline.light.hex}}80",
|
||||
"scrollbarSlider.background": "{{colors.on_surface_variant.light.hex}}50",
|
||||
"scrollbarSlider.hoverBackground": "{{colors.on_surface_variant.light.hex}}80",
|
||||
"scrollbarSlider.activeBackground": "{{colors.on_surface_variant.light.hex}}AA",
|
||||
"menubar.selectionBackground": "{{colors.primary_container.light.hex}}",
|
||||
"menubar.selectionForeground": "{{colors.on_primary_container.light.hex}}",
|
||||
"menu.background": "{{colors.surface_container.light.hex}}",
|
||||
@@ -239,10 +240,46 @@
|
||||
"meta.object-literal.key",
|
||||
"meta.property.object",
|
||||
"variable.other.property",
|
||||
"entity.name.tag.yaml"
|
||||
"support.type.property-name.json"
|
||||
],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color4.light.hex}}"
|
||||
"foreground": "{{colors.on_surface.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.yaml", "punctuation.definition.block.sequence.item.yaml", "punctuation.separator.key-value.mapping.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["source.yaml string.unquoted", "source.yaml string.quoted"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color3.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.language.boolean.yaml", "constant.language.null.yaml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color0.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["entity.name.tag.toml", "support.type.property-name.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{colors.on_surface.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["string.quoted.single.basic.line.toml", "string.quoted.double.basic.line.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color2.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": ["constant.language.boolean.toml", "constant.numeric.toml"],
|
||||
"settings": {
|
||||
"foreground": "{{dank16.color0.light.hex}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 متصل"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 نمایشگر"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 ابزارک"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(بدون نام)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "محو شدن برای قفل صفحه"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "فعالسازی پیکربندی ناموفق بود"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "قبل از قفل شدن صفحه نمایش را با یک دوره زمانی قابل تنظیم به تدریج محو کن"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "بازه زمانی نمودار"
|
||||
},
|
||||
@@ -1677,7 +1689,7 @@
|
||||
"Grid Columns": "ستونهای جدول"
|
||||
},
|
||||
"Group Workspace Apps": {
|
||||
"Group Workspace Apps": ""
|
||||
"Group Workspace Apps": "گروهبندی برنامههای workspace"
|
||||
},
|
||||
"Group by App": {
|
||||
"Group by App": "گروهبندی بر اساس برنامه"
|
||||
@@ -1686,7 +1698,7 @@
|
||||
"Group multiple windows of the same app together with a window count indicator": "گروهبندی چندین پنجره از برنامه یکسان با نشانگر تعداد پنجرهها"
|
||||
},
|
||||
"Group repeated application icons in unfocused workspaces": {
|
||||
"Group repeated application icons in unfocused workspaces": ""
|
||||
"Group repeated application icons in unfocused workspaces": "برنامههای تکرارشده در workspaceهای فوکوس نشده را گروهبندی کن"
|
||||
},
|
||||
"HDR (EDID)": {
|
||||
"HDR (EDID)": "HDR (EDID)"
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "قفلکردن قبل از تعلیق"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "خروج"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "پیکربندی مانیتور"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "مانیتوری که تصویر پسزمینه آن رنگهای تم پویا را تعیین میکند"
|
||||
},
|
||||
@@ -2946,7 +2964,7 @@
|
||||
"Scroll title if it doesn't fit in widget": "اگر عنوان در ابزارک جا نشد آن را حرکت بده"
|
||||
},
|
||||
"Scroll wheel behavior on media widget": {
|
||||
"Scroll wheel behavior on media widget": ""
|
||||
"Scroll wheel behavior on media widget": "رفتار چرخ اسکرول در ابزارک رسانه"
|
||||
},
|
||||
"Scrolling": {
|
||||
"Scrolling": "اسکرولینگ"
|
||||
@@ -3219,7 +3237,7 @@
|
||||
"Show workspace index numbers in the top bar workspace switcher": "نمایش شماره شاخص workspace در تغییردهنده workspace نوار بالا"
|
||||
},
|
||||
"Show workspace name on horizontal bars, and first letter on vertical bars": {
|
||||
"Show workspace name on horizontal bars, and first letter on vertical bars": ""
|
||||
"Show workspace name on horizontal bars, and first letter on vertical bars": "نام workspace را در نوارهای افقی و اولین حرف را در نوارهای عمودی نمایش بده"
|
||||
},
|
||||
"Shows all running applications with focus indication": {
|
||||
"Shows all running applications with focus indication": "همه برنامههای درحال اجرا را با نشانگر متمرکز نمایش میدهد"
|
||||
@@ -3252,10 +3270,10 @@
|
||||
"Sizing": "اندازهدهی"
|
||||
},
|
||||
"Smartcard Authentication": {
|
||||
"Smartcard Authentication": ""
|
||||
"Smartcard Authentication": "احراز هویت با کارت هوشمند"
|
||||
},
|
||||
"Smartcard PIN": {
|
||||
"Smartcard PIN": ""
|
||||
"Smartcard PIN": "PIN کارت هوشمند"
|
||||
},
|
||||
"Some plugins require a newer version of DMS:": {
|
||||
"Some plugins require a newer version of DMS:": "برخی افزونهها نیازمند نسخه جدیدتر DMS هستند:"
|
||||
@@ -3869,7 +3887,7 @@
|
||||
"Workspace Index Numbers": "شماره شاخص workspace"
|
||||
},
|
||||
"Workspace Names": {
|
||||
"Workspace Names": ""
|
||||
"Workspace Names": "نام workspaceها"
|
||||
},
|
||||
"Workspace Padding": {
|
||||
"Workspace Padding": "فاصله درونی workspace"
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "هیچ تصویر پسزمینهای انتخاب نشده"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "رسمی"
|
||||
},
|
||||
@@ -4052,7 +4114,7 @@
|
||||
"Select Profile Image": "انتخاب تصویر نمایه"
|
||||
},
|
||||
"read-only settings warning for NixOS home-manager users": {
|
||||
"Settings are read-only. Changes will not persist.": ""
|
||||
"Settings are read-only. Changes will not persist.": "تنظیمات تنها قابل خواندن هستند. تغییرات حفظ نخواهند شد."
|
||||
},
|
||||
"registry theme description": {
|
||||
"Color theme from DMS registry": "رنگ تم از مخزن DMS"
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "wtype در دسترس نیست - wtype را برای پشتیبانی از الصاق نصب کنید"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "نصب تنها از منابع معتبر"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 מחובר/ים"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 מסך/מסכים"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 ווידג׳טים"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(ללא שם)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "דהייה למסך הנעילה"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "הפעלת התצורה נכשלה"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "הפעל/י דהייה הדרגתית של המסך לפני הנעילה עם תקופת חסד הניתנת להגדרה"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": ""
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "נעל/י לפני השהיה"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "התנתק/י"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": ""
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "המסך שהרקע שלו קובע את צבעי ערכת הנושא הדינמית"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": ""
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "רשמי"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": ""
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• התקן/י רק ממקורות מהימנים"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 csatlakoztatva"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 kijelző"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 widget"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(Névtelen)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "Halványítás a zárolási képernyőre"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "A konfiguráció aktiválása sikertelen"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "Fokozatosan halványítsa el a képernyőt a zárolás előtt egy konfigurálható türelmi idővel"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "Grafikon időtartománya"
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "Zárolás felfüggesztés előtt"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "Kijelentkezés"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "Monitorkonfiguráció"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "Az a monitor, amelynek háttérképe meghatározza a dinamikus témaszíneket"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "Nincs háttérkép kiválasztva"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "hivatalos"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "A wtype nem elérhető - telepítsd a wtype csomagot a beillesztés támogatásához"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "Csak hivatalos forrásokból telepítsen"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 connesso"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 schermo(i)"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 widget"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(Senza nome)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "Dissolvenza verso la schermata di blocco"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "Impossibile attivare configurazione"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "Dissolvi gradualmente lo schermo prima del blocco, con un periodo di tolleranza configurabile"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "Intervallo Temporale del Grafico"
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "Blocca prima di sospendere"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "Termina Sessione"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "Configurazione Monitor"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "Monitor il cui sfondo determina i colori del tema dinamico"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "Nessuno sfondo selezionato"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "ufficiale"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "wtype non disponibile – installa wtype per il supporto all’incolla"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• Installa solo da sorgenti fidate"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": ""
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 表示"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 ウィジェット"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(名前なし)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": ""
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "設定が適用できませんでした"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": ""
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": ""
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "一時停止前にロック"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "ログアウト"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": ""
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "ダイナミックテーマの色を駆動する壁紙をモニター"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": ""
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "公式"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": ""
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• 信頼できるソースからのみインストールする"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 połączono"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 wyświetlaczy"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 widżetów"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(Bez nazwy)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "Wygaszanie do ekranu blokady"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "Nie udało się aktywować konfiguracji"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "Stopniowe wygaszanie ekranu przed zablokowaniem z konfigurowalnym czasem opóźnienia"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "Zakres czasowy wykresu"
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "Zablokuj przed wstrzymaniem"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "Wyloguj"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "Konfiguracja monitora"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "Monitor, którego tapeta steruje dynamicznymi kolorami motywu"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "Nie wybrano tapety"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "oficjalny"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "wtype niedostępny - zainstaluj wtype dla wsparcia wklejania"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• Instaluj tylko z zaufanych źródeł"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 conectado(s)"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 monitor(es)"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 widgets"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(Sem nome)"
|
||||
},
|
||||
@@ -36,7 +42,7 @@
|
||||
"0 = square corners": "0 = cantos quadrados"
|
||||
},
|
||||
"1 day": {
|
||||
"1 day": ""
|
||||
"1 day": "1 dia"
|
||||
},
|
||||
"1 event": {
|
||||
"1 event": "1 evento"
|
||||
@@ -57,7 +63,7 @@
|
||||
"10-bit Color": ""
|
||||
},
|
||||
"14 days": {
|
||||
"14 days": ""
|
||||
"14 days": "14 dias"
|
||||
},
|
||||
"15 seconds": {
|
||||
"15 seconds": "15 segundos"
|
||||
@@ -78,13 +84,13 @@
|
||||
"270°": ""
|
||||
},
|
||||
"3 days": {
|
||||
"3 days": ""
|
||||
"3 days": "3 dias"
|
||||
},
|
||||
"3 seconds": {
|
||||
"3 seconds": "3 segundos"
|
||||
},
|
||||
"30 days": {
|
||||
"30 days": ""
|
||||
"30 days": "30 dias"
|
||||
},
|
||||
"30 seconds": {
|
||||
"30 seconds": "30 segundos"
|
||||
@@ -93,19 +99,19 @@
|
||||
"3rd party": "Terceiros"
|
||||
},
|
||||
"5 minutes": {
|
||||
"5 minutes": ""
|
||||
"5 minutes": "5 minutos"
|
||||
},
|
||||
"5 seconds": {
|
||||
"5 seconds": ""
|
||||
"5 seconds": "5 segundos"
|
||||
},
|
||||
"7 days": {
|
||||
"7 days": ""
|
||||
"7 days": "7 dias"
|
||||
},
|
||||
"8 seconds": {
|
||||
"8 seconds": ""
|
||||
"8 seconds": "8 segundos"
|
||||
},
|
||||
"90 days": {
|
||||
"90 days": ""
|
||||
"90 days": "90 dias"
|
||||
},
|
||||
"90°": {
|
||||
"90°": ""
|
||||
@@ -195,7 +201,7 @@
|
||||
"Adjust the number of columns in grid view mode.": "Ajusta o número de colunas no modo de visualização em grade"
|
||||
},
|
||||
"Advanced": {
|
||||
"Advanced": ""
|
||||
"Advanced": "Avançado"
|
||||
},
|
||||
"Afternoon": {
|
||||
"Afternoon": "Tarde"
|
||||
@@ -216,7 +222,7 @@
|
||||
"Alt+←/Backspace: Back • F1/I: File Info • F10: Help • Esc: Close": "Alt+←/Backspace: Voltar • F1/I: Informações de Arquivo • F10: Ajuda • Esc: Fechar"
|
||||
},
|
||||
"Always Show Percentage": {
|
||||
"Always Show Percentage": ""
|
||||
"Always Show Percentage": "Sempre Mostrar Porcentagem"
|
||||
},
|
||||
"Always on icons": {
|
||||
"Always on icons": "Ícones sempre ligados"
|
||||
@@ -231,7 +237,7 @@
|
||||
"Always show when there's only one connected display": ""
|
||||
},
|
||||
"Amount": {
|
||||
"Amount": ""
|
||||
"Amount": "Quantidade"
|
||||
},
|
||||
"Analog": {
|
||||
"Analog": ""
|
||||
@@ -300,7 +306,7 @@
|
||||
"Audio Output Switch": ""
|
||||
},
|
||||
"Audio Visualizer": {
|
||||
"Audio Visualizer": ""
|
||||
"Audio Visualizer": "Visualizador de Áudio"
|
||||
},
|
||||
"Audio volume control": {
|
||||
"Audio volume control": ""
|
||||
@@ -348,7 +354,7 @@
|
||||
"Auto-Clear After": ""
|
||||
},
|
||||
"Auto-close Niri overview when launching apps.": {
|
||||
"Auto-close Niri overview when launching apps.": ""
|
||||
"Auto-close Niri overview when launching apps.": "Fechar automaticamente overview do niri ao lançar aplicativos."
|
||||
},
|
||||
"Auto-hide": {
|
||||
"Auto-hide": "Esconder Automaticamente"
|
||||
@@ -444,7 +450,7 @@
|
||||
"Battery level and power management": "Nível de bateria e manejamento de energia"
|
||||
},
|
||||
"Behavior": {
|
||||
"Behavior": ""
|
||||
"Behavior": "Comportamento"
|
||||
},
|
||||
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": {
|
||||
"Bind lock screen to dbus signals from loginctl. Disable if using an external lock screen": "Vincular o bloqueio de tela aos sinais do DBus do loginctl. Desative se estiver usando um bloqueio de tela externo"
|
||||
@@ -579,7 +585,7 @@
|
||||
"Capacity": "Capacidade"
|
||||
},
|
||||
"Caps Lock": {
|
||||
"Caps Lock": ""
|
||||
"Caps Lock": "Caps Lock"
|
||||
},
|
||||
"Caps Lock Indicator": {
|
||||
"Caps Lock Indicator": "Indicador de Caps Lock"
|
||||
@@ -660,10 +666,10 @@
|
||||
"Clear All Jobs": "Limpar Todos os Trabalhos"
|
||||
},
|
||||
"Clear all history when server starts": {
|
||||
"Clear all history when server starts": ""
|
||||
"Clear all history when server starts": "Limpar todo o histórico quando o servidor iniciar"
|
||||
},
|
||||
"Clear at Startup": {
|
||||
"Clear at Startup": ""
|
||||
"Clear at Startup": "Limpar ao Iniciar"
|
||||
},
|
||||
"Click 'Setup' to create dms/binds.kdl and add include to config.kdl.": {
|
||||
"Click 'Setup' to create dms/binds.kdl and add include to config.kdl.": ""
|
||||
@@ -681,7 +687,7 @@
|
||||
"Click to capture": "Clique para capturar"
|
||||
},
|
||||
"Clipboard": {
|
||||
"Clipboard": ""
|
||||
"Clipboard": "Área de transferência"
|
||||
},
|
||||
"Clipboard History": {
|
||||
"Clipboard History": "Histórico da Área de Transferência"
|
||||
@@ -690,10 +696,10 @@
|
||||
"Clipboard Manager": "Gerenciador da Área de Transferência"
|
||||
},
|
||||
"Clipboard service not available": {
|
||||
"Clipboard service not available": ""
|
||||
"Clipboard service not available": "Serviço de área de transferência não disponível"
|
||||
},
|
||||
"Clipboard works but nothing saved to disk": {
|
||||
"Clipboard works but nothing saved to disk": ""
|
||||
"Clipboard works but nothing saved to disk": "A área de transferência funciona mas nada é salvo no disco"
|
||||
},
|
||||
"Clock": {
|
||||
"Clock": "Relógio"
|
||||
@@ -708,7 +714,7 @@
|
||||
"Close Overview on Launch": "Fechar Overview ao Lançar Aplicativos"
|
||||
},
|
||||
"Color": {
|
||||
"Color": ""
|
||||
"Color": "Cor"
|
||||
},
|
||||
"Color Gamut": {
|
||||
"Color Gamut": ""
|
||||
@@ -717,7 +723,7 @@
|
||||
"Color Management": ""
|
||||
},
|
||||
"Color Mode": {
|
||||
"Color Mode": ""
|
||||
"Color Mode": "Modo de Cor"
|
||||
},
|
||||
"Color Override": {
|
||||
"Color Override": "Sobrescrever Cor"
|
||||
@@ -726,7 +732,7 @@
|
||||
"Color Picker": "Seletor de Cores"
|
||||
},
|
||||
"Color Temperature": {
|
||||
"Color Temperature": ""
|
||||
"Color Temperature": "Temperatura da Cor"
|
||||
},
|
||||
"Color displayed on monitors without the lock screen": {
|
||||
"Color displayed on monitors without the lock screen": ""
|
||||
@@ -840,7 +846,7 @@
|
||||
"Controls opacity of all popouts, modals, and their content layers": ""
|
||||
},
|
||||
"Cooldown": {
|
||||
"Cooldown": ""
|
||||
"Cooldown": "Tempo de espera"
|
||||
},
|
||||
"Copied to clipboard": {
|
||||
"Copied to clipboard": "Copiado para a área de transferência"
|
||||
@@ -855,7 +861,7 @@
|
||||
"Copy Process Name": "Copiar Nome do Processo"
|
||||
},
|
||||
"Corner Radius": {
|
||||
"Corner Radius": ""
|
||||
"Corner Radius": "Arredondamento"
|
||||
},
|
||||
"Corner Radius Override": {
|
||||
"Corner Radius Override": "Sobrescrever Arredondamento"
|
||||
@@ -882,13 +888,13 @@
|
||||
"Current Items": "Itens Atuais"
|
||||
},
|
||||
"Current Period": {
|
||||
"Current Period": ""
|
||||
"Current Period": "Período Atual"
|
||||
},
|
||||
"Current Status": {
|
||||
"Current Status": ""
|
||||
"Current Status": "Status Atual"
|
||||
},
|
||||
"Current Temp": {
|
||||
"Current Temp": ""
|
||||
"Current Temp": "Temperatura Atual"
|
||||
},
|
||||
"Current Weather": {
|
||||
"Current Weather": "Clima Atual"
|
||||
@@ -960,7 +966,7 @@
|
||||
"DMS out of date": "DMS desatualizado"
|
||||
},
|
||||
"DMS service is not connected. Clipboard settings are unavailable.": {
|
||||
"DMS service is not connected. Clipboard settings are unavailable.": ""
|
||||
"DMS service is not connected. Clipboard settings are unavailable.": "Serviço do DMS não está conectado. Configurações de área de transferência não estão disponíveis."
|
||||
},
|
||||
"DMS shell actions (launcher, clipboard, etc.)": {
|
||||
"DMS shell actions (launcher, clipboard, etc.)": "Ações do shell do DMS (lançador, área de transferência, etc.)"
|
||||
@@ -1086,16 +1092,16 @@
|
||||
"Disable Autoconnect": "Desativar conexão automática"
|
||||
},
|
||||
"Disable Clipboard Manager": {
|
||||
"Disable Clipboard Manager": ""
|
||||
"Disable Clipboard Manager": "Desativar Gerenciador de Área de Transferência"
|
||||
},
|
||||
"Disable History Persistence": {
|
||||
"Disable History Persistence": ""
|
||||
"Disable History Persistence": "Desativar Persistência de Histórico"
|
||||
},
|
||||
"Disable Output": {
|
||||
"Disable Output": ""
|
||||
},
|
||||
"Disable clipboard manager entirely (requires restart)": {
|
||||
"Disable clipboard manager entirely (requires restart)": ""
|
||||
"Disable clipboard manager entirely (requires restart)": "Desativa o gerenciador de área de transferência inteiramente (requer reinício)"
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "Desativado"
|
||||
@@ -1164,7 +1170,7 @@
|
||||
"Display seconds in the clock": ""
|
||||
},
|
||||
"Display the power system menu": {
|
||||
"Display the power system menu": ""
|
||||
"Display the power system menu": "Mostra o menu de energia do sistema"
|
||||
},
|
||||
"Display volume and brightness percentage values in OSD popups": {
|
||||
"Display volume and brightness percentage values in OSD popups": ""
|
||||
@@ -1284,16 +1290,16 @@
|
||||
"Enable loginctl lock integration": "Ativar integração de bloqueio do loginctl"
|
||||
},
|
||||
"Enable password field display on the lock screen window": {
|
||||
"Show Password Field": ""
|
||||
"Show Password Field": "Mostrar Campo de Senha"
|
||||
},
|
||||
"Enable power action icon on the lock screen window": {
|
||||
"Show Power Actions": ""
|
||||
"Show Power Actions": "Mostrar Ações de Energia"
|
||||
},
|
||||
"Enable profile image display on the lock screen window": {
|
||||
"Show Profile Image": ""
|
||||
"Show Profile Image": "Mostrar Imagem de Perfil"
|
||||
},
|
||||
"Enable system date display on the lock screen window": {
|
||||
"Show System Date": ""
|
||||
"Show System Date": "Mostrar Data do Sistema"
|
||||
},
|
||||
"Enable system status icons on the lock screen window": {
|
||||
"Show System Icons": ""
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "Esmaecer para a tela de bloqueio"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "Erro ao ativar configuração"
|
||||
},
|
||||
@@ -1395,7 +1404,7 @@
|
||||
"Failed to connect to %1": ""
|
||||
},
|
||||
"Failed to copy entry": {
|
||||
"Failed to copy entry": ""
|
||||
"Failed to copy entry": "Falha ao copiar entrada"
|
||||
},
|
||||
"Failed to create printer": {
|
||||
"Failed to create printer": "Falha ao criar impressora"
|
||||
@@ -1446,7 +1455,7 @@
|
||||
"Failed to load VPN config": "Falha ao carregar configuração da VPN"
|
||||
},
|
||||
"Failed to load clipboard configuration.": {
|
||||
"Failed to load clipboard configuration.": ""
|
||||
"Failed to load clipboard configuration.": "Falha ao carregar configuração de área de transferência."
|
||||
},
|
||||
"Failed to move job": {
|
||||
"Failed to move job": "Falha ao mover trabalho"
|
||||
@@ -1482,7 +1491,7 @@
|
||||
"Failed to resume printer": "Falha ao resumir impressora"
|
||||
},
|
||||
"Failed to save clipboard setting": {
|
||||
"Failed to save clipboard setting": ""
|
||||
"Failed to save clipboard setting": "Falha ao salvar configuração de área de transferência"
|
||||
},
|
||||
"Failed to save keybind": {
|
||||
"Failed to save keybind": "Falha ao salvar atalho"
|
||||
@@ -1581,7 +1590,7 @@
|
||||
"Focused Window": "Janela Focada"
|
||||
},
|
||||
"Follow focus": {
|
||||
"Follow focus": ""
|
||||
"Follow focus": "Seguir foco"
|
||||
},
|
||||
"Font Family": {
|
||||
"Font Family": "Família da Fonte"
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "Gradualmente esmaecer a tela antes de bloquear com um período de tolerância configurável"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": ""
|
||||
},
|
||||
@@ -1728,7 +1740,7 @@
|
||||
"High-fidelity palette that preserves source hues.": "Paleta de alta fidelidade que preserva tons da fonte."
|
||||
},
|
||||
"History Settings": {
|
||||
"History Settings": ""
|
||||
"History Settings": "Configurações do Histórico"
|
||||
},
|
||||
"Hold Duration": {
|
||||
"Hold Duration": "Duração do Pressionamento"
|
||||
@@ -1740,7 +1752,7 @@
|
||||
"Hold to Confirm Power Actions": "Manter Pressionado para Confirmar Ações de Energia"
|
||||
},
|
||||
"Hold to confirm (%1 ms)": {
|
||||
"Hold to confirm (%1 ms)": ""
|
||||
"Hold to confirm (%1 ms)": "Segure para confirmar (%1 ms)"
|
||||
},
|
||||
"Hold to confirm (%1s)": {
|
||||
"Hold to confirm (%1s)": "Manter pressionado para confirmar (%1s)"
|
||||
@@ -1791,13 +1803,13 @@
|
||||
"Idle monitoring not supported - requires newer Quickshell version": "Monitoramento de inatividade não disponível – requer versão mais recente do Quickshell"
|
||||
},
|
||||
"If the field is hidden, it will appear as soon as a key is pressed.": {
|
||||
"If the field is hidden, it will appear as soon as a key is pressed.": ""
|
||||
"If the field is hidden, it will appear as soon as a key is pressed.": "Se o campo está escondido, ele aparecerá no momento que uma tecla e pressionada."
|
||||
},
|
||||
"Image": {
|
||||
"Image": "Imagem"
|
||||
},
|
||||
"Image copied to clipboard": {
|
||||
"Image copied to clipboard": ""
|
||||
"Image copied to clipboard": "Imagem copiada para a área de trabalho"
|
||||
},
|
||||
"Import": {
|
||||
"Import": "Importar"
|
||||
@@ -1989,14 +2001,17 @@
|
||||
"Lock Screen Format": "Formato da Tela de Bloqueio"
|
||||
},
|
||||
"Lock Screen behaviour": {
|
||||
"Lock Screen behaviour": ""
|
||||
"Lock Screen behaviour": "Comportamento da Tela de Bloqueio"
|
||||
},
|
||||
"Lock Screen layout": {
|
||||
"Lock Screen layout": ""
|
||||
"Lock Screen layout": "Layout da Tela de Bloqueio"
|
||||
},
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "Bloquear antes de suspender"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "Sair"
|
||||
},
|
||||
@@ -2061,25 +2076,25 @@
|
||||
"Matugen Target Monitor": "Monitor-alvo do Matugen"
|
||||
},
|
||||
"Matugen Templates": {
|
||||
"Matugen Templates": ""
|
||||
"Matugen Templates": "Templates do Matugen"
|
||||
},
|
||||
"Max apps to show": {
|
||||
"Max apps to show": "Máximo de apps para mostrar"
|
||||
},
|
||||
"Maximize Detection": {
|
||||
"Maximize Detection": ""
|
||||
"Maximize Detection": "Detecção de Maximizado"
|
||||
},
|
||||
"Maximum Entry Size": {
|
||||
"Maximum Entry Size": ""
|
||||
"Maximum Entry Size": "Tamanho Máximo de Entrada"
|
||||
},
|
||||
"Maximum History": {
|
||||
"Maximum History": ""
|
||||
"Maximum History": "Histórico Máximo"
|
||||
},
|
||||
"Maximum number of clipboard entries to keep": {
|
||||
"Maximum number of clipboard entries to keep": ""
|
||||
"Maximum number of clipboard entries to keep": "Número máximo de entradas da área de transferência para guardar"
|
||||
},
|
||||
"Maximum size per clipboard entry": {
|
||||
"Maximum size per clipboard entry": ""
|
||||
"Maximum size per clipboard entry": "Tamanho máximo por entrada da área de transferência"
|
||||
},
|
||||
"Media": {
|
||||
"Media": "Mídia"
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": ""
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "Monitor o qual papel de parede controla cores de tema dinâmicas"
|
||||
},
|
||||
@@ -2229,7 +2247,7 @@
|
||||
"Network download and upload speed display": "Monitor de velocidades de download e upload da rede"
|
||||
},
|
||||
"Never": {
|
||||
"Never": ""
|
||||
"Never": "Nunca"
|
||||
},
|
||||
"New": {
|
||||
"New": "Novo"
|
||||
@@ -2247,7 +2265,7 @@
|
||||
"New York, NY": "Nova York, NY"
|
||||
},
|
||||
"Next Transition": {
|
||||
"Next Transition": ""
|
||||
"Next Transition": "Próxima Transição"
|
||||
},
|
||||
"Night": {
|
||||
"Night": "Noite"
|
||||
@@ -2259,7 +2277,7 @@
|
||||
"Night Temperature": "Temperatura Noturna"
|
||||
},
|
||||
"Niri Integration": {
|
||||
"Niri Integration": ""
|
||||
"Niri Integration": "Integração com niri"
|
||||
},
|
||||
"Niri Layout Overrides": {
|
||||
"Niri Layout Overrides": ""
|
||||
@@ -2280,7 +2298,7 @@
|
||||
"No Bluetooth adapter found": "Adaptador Bluetooth não encontrado"
|
||||
},
|
||||
"No GPU detected": {
|
||||
"No GPU detected": ""
|
||||
"No GPU detected": "Nenhuma GPU detectada"
|
||||
},
|
||||
"No Media": {
|
||||
"No Media": "Sem Mídia"
|
||||
@@ -2367,7 +2385,7 @@
|
||||
"Normal": ""
|
||||
},
|
||||
"Normal Font": {
|
||||
"Normal Font": ""
|
||||
"Normal Font": "Fonte Normal"
|
||||
},
|
||||
"Normal Priority": {
|
||||
"Normal Priority": "Prioridade Normal"
|
||||
@@ -2469,7 +2487,7 @@
|
||||
"Optional location": "Localização opcional"
|
||||
},
|
||||
"Options": {
|
||||
"Options": ""
|
||||
"Options": "Opções"
|
||||
},
|
||||
"Other": {
|
||||
"Other": "Outro"
|
||||
@@ -2493,7 +2511,7 @@
|
||||
"Override": "Sobrescrever"
|
||||
},
|
||||
"Override Corner Radius": {
|
||||
"Override Corner Radius": ""
|
||||
"Override Corner Radius": "Sobrescrever Arredondamento"
|
||||
},
|
||||
"Override Gaps": {
|
||||
"Override Gaps": ""
|
||||
@@ -2619,7 +2637,7 @@
|
||||
"Plugins": "Plugins"
|
||||
},
|
||||
"Pointer": {
|
||||
"Pointer": ""
|
||||
"Pointer": "Ponteiro"
|
||||
},
|
||||
"Popup Position": {
|
||||
"Popup Position": "Posição do Popup"
|
||||
@@ -2634,7 +2652,7 @@
|
||||
"Possible Override Conflicts": ""
|
||||
},
|
||||
"Power": {
|
||||
"Power": ""
|
||||
"Power": "Energia"
|
||||
},
|
||||
"Power & Security": {
|
||||
"Power & Security": "Energia & Segurança"
|
||||
@@ -2655,7 +2673,7 @@
|
||||
"Power Options": "Opções de Energia"
|
||||
},
|
||||
"Power Profile": {
|
||||
"Power Profile": ""
|
||||
"Power Profile": "Perfil de Energia"
|
||||
},
|
||||
"Power Profile Degradation": {
|
||||
"Power Profile Degradation": "Degradação do Perfil de Energia"
|
||||
@@ -2664,7 +2682,7 @@
|
||||
"Power profile management available": ""
|
||||
},
|
||||
"Power source": {
|
||||
"Power source": ""
|
||||
"Power source": "Fonte de energia"
|
||||
},
|
||||
"Precipitation Chance": {
|
||||
"Precipitation Chance": "Chance de Precipitação"
|
||||
@@ -2790,7 +2808,7 @@
|
||||
"Remove": "Remover"
|
||||
},
|
||||
"Remove gaps and border when windows are maximized": {
|
||||
"Remove gaps and border when windows are maximized": ""
|
||||
"Remove gaps and border when windows are maximized": "Remover espaçámentos e borda quando janelas estão maximizadas"
|
||||
},
|
||||
"Report": {
|
||||
"Report": "Relatório"
|
||||
@@ -2868,7 +2886,7 @@
|
||||
"Rounded corners for windows": ""
|
||||
},
|
||||
"Run DMS Templates": {
|
||||
"Run DMS Templates": ""
|
||||
"Run DMS Templates": "Rodar templates do DMS"
|
||||
},
|
||||
"Run User Templates": {
|
||||
"Run User Templates": "Executar Templates do Usuário"
|
||||
@@ -3273,7 +3291,7 @@
|
||||
"Sound Theme": "Tema do Som"
|
||||
},
|
||||
"Sounds": {
|
||||
"Sounds": ""
|
||||
"Sounds": "Sons"
|
||||
},
|
||||
"Space between windows": {
|
||||
"Space between windows": ""
|
||||
@@ -3369,7 +3387,7 @@
|
||||
"System Monitor Unavailable": "Monitor do Sistema Indisponível"
|
||||
},
|
||||
"System Sounds": {
|
||||
"System Sounds": ""
|
||||
"System Sounds": "Sons do Sistema"
|
||||
},
|
||||
"System Tray": {
|
||||
"System Tray": "Bandeja do Sistema"
|
||||
@@ -3468,7 +3486,7 @@
|
||||
"Time & Weather": "Hora & Clima"
|
||||
},
|
||||
"Time Format": {
|
||||
"Time Format": ""
|
||||
"Time Format": "Formato de Tempo"
|
||||
},
|
||||
"Time remaining: %1": {
|
||||
"Time remaining: %1": ""
|
||||
@@ -3549,7 +3567,7 @@
|
||||
"Transition Effect": "Efeito de Transição"
|
||||
},
|
||||
"Transparency": {
|
||||
"Transparency": ""
|
||||
"Transparency": "Transparência"
|
||||
},
|
||||
"Turn off monitors after": {
|
||||
"Turn off monitors after": "Desligar monitores depois de"
|
||||
@@ -3558,10 +3576,10 @@
|
||||
"Type": "Tipo"
|
||||
},
|
||||
"Typography": {
|
||||
"Typography": ""
|
||||
"Typography": "Tipografia"
|
||||
},
|
||||
"Typography & Motion": {
|
||||
"Typography & Motion": ""
|
||||
"Typography & Motion": "Tipografia & Movimento"
|
||||
},
|
||||
"Unavailable": {
|
||||
"Unavailable": "Indisponível"
|
||||
@@ -3741,7 +3759,7 @@
|
||||
"Visual effect used when wallpaper changes": "Efeito visual usado na mudança do papel de parede"
|
||||
},
|
||||
"Volume": {
|
||||
"Volume": ""
|
||||
"Volume": "Volume"
|
||||
},
|
||||
"Volume Changed": {
|
||||
"Volume Changed": "Volume Alterado"
|
||||
@@ -3854,7 +3872,7 @@
|
||||
"Wind Speed": "Velocidade do Vento"
|
||||
},
|
||||
"Window Corner Radius": {
|
||||
"Window Corner Radius": ""
|
||||
"Window Corner Radius": "Arredondamento de Janelas"
|
||||
},
|
||||
"Window Gaps": {
|
||||
"Window Gaps": ""
|
||||
@@ -3977,7 +3995,7 @@
|
||||
"events": "eventos"
|
||||
},
|
||||
"files": {
|
||||
"files": ""
|
||||
"files": "arquivos"
|
||||
},
|
||||
"generic theme description": {
|
||||
"Material Design inspired color themes": ""
|
||||
@@ -4016,7 +4034,7 @@
|
||||
"Matugen Missing": ""
|
||||
},
|
||||
"minutes": {
|
||||
"minutes": ""
|
||||
"minutes": "minutos"
|
||||
},
|
||||
"ms": {
|
||||
"ms": ""
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": ""
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "oficial"
|
||||
},
|
||||
@@ -4058,7 +4120,7 @@
|
||||
"Color theme from DMS registry": ""
|
||||
},
|
||||
"seconds": {
|
||||
"seconds": ""
|
||||
"seconds": "segundos"
|
||||
},
|
||||
"settings window title": {
|
||||
"Settings": "Configurações"
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": ""
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "Instale apenas de fontes confiáveis"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 bağlı"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 ekran"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 widget"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(İsimsiz)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "Kilit Ekranı Solması"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "Yapılandırma etkinleştirilemedi"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "Yapılandırılabilir bir bekleme süresi ile kilitlemeden önce ekranı kademeli olarak karartın"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "Grafik Zaman Aralığı"
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "Askıya almadan önce kilitle"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "Çıkış"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "Monitör Yapılandırması"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "Duvar kağıdı dinamik tema renklerini yönlendiren monitör"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "Duvar kağıdı seçilmedi"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "resmi"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "wtype mevcut değil - yapıştırma desteği için wtype'ı yükleyin"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• Yalnızca güvenilir kaynaklardan yükle"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "已连接 %1"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 显示"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 部件"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(未命名)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "淡出至锁定屏幕"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "无法应用配置"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "在锁定前通过可配置的宽限期逐渐淡出屏幕"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "图表时间范围"
|
||||
},
|
||||
@@ -1677,7 +1689,7 @@
|
||||
"Grid Columns": "网格列"
|
||||
},
|
||||
"Group Workspace Apps": {
|
||||
"Group Workspace Apps": ""
|
||||
"Group Workspace Apps": "分组工作区应用"
|
||||
},
|
||||
"Group by App": {
|
||||
"Group by App": "按应用分组"
|
||||
@@ -1686,7 +1698,7 @@
|
||||
"Group multiple windows of the same app together with a window count indicator": "将同一应用的多个窗口合并显示,并标注窗口数量"
|
||||
},
|
||||
"Group repeated application icons in unfocused workspaces": {
|
||||
"Group repeated application icons in unfocused workspaces": ""
|
||||
"Group repeated application icons in unfocused workspaces": "在不聚焦的工作区中将重复应用图标分组"
|
||||
},
|
||||
"HDR (EDID)": {
|
||||
"HDR (EDID)": "HDR(EDID)"
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "挂起前锁屏"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "注销"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "监视器配置"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "监视使用动态主题色的壁纸"
|
||||
},
|
||||
@@ -2946,7 +2964,7 @@
|
||||
"Scroll title if it doesn't fit in widget": "如果在部件中标题不适配,则可以滚动标题"
|
||||
},
|
||||
"Scroll wheel behavior on media widget": {
|
||||
"Scroll wheel behavior on media widget": ""
|
||||
"Scroll wheel behavior on media widget": "媒体部件上的滚轮行为"
|
||||
},
|
||||
"Scrolling": {
|
||||
"Scrolling": "滚动"
|
||||
@@ -3219,7 +3237,7 @@
|
||||
"Show workspace index numbers in the top bar workspace switcher": "在顶栏工作区切换器中显示工作区索引号"
|
||||
},
|
||||
"Show workspace name on horizontal bars, and first letter on vertical bars": {
|
||||
"Show workspace name on horizontal bars, and first letter on vertical bars": ""
|
||||
"Show workspace name on horizontal bars, and first letter on vertical bars": "在水平状态栏上显示工作区名称,而在垂直状态栏上显示首字母。"
|
||||
},
|
||||
"Shows all running applications with focus indication": {
|
||||
"Shows all running applications with focus indication": "显示所有正在运行应用程序,并标记焦点所在"
|
||||
@@ -3252,10 +3270,10 @@
|
||||
"Sizing": "大小调整"
|
||||
},
|
||||
"Smartcard Authentication": {
|
||||
"Smartcard Authentication": ""
|
||||
"Smartcard Authentication": "智能卡认证"
|
||||
},
|
||||
"Smartcard PIN": {
|
||||
"Smartcard PIN": ""
|
||||
"Smartcard PIN": "智能卡PIN"
|
||||
},
|
||||
"Some plugins require a newer version of DMS:": {
|
||||
"Some plugins require a newer version of DMS:": "有些插件需要更新版的 DMS:"
|
||||
@@ -3869,7 +3887,7 @@
|
||||
"Workspace Index Numbers": "工作区序号"
|
||||
},
|
||||
"Workspace Names": {
|
||||
"Workspace Names": ""
|
||||
"Workspace Names": "工作区名称"
|
||||
},
|
||||
"Workspace Padding": {
|
||||
"Workspace Padding": "工作区内边距"
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "未选择壁纸"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "官方"
|
||||
},
|
||||
@@ -4052,7 +4114,7 @@
|
||||
"Select Profile Image": "选择个人信息图像"
|
||||
},
|
||||
"read-only settings warning for NixOS home-manager users": {
|
||||
"Settings are read-only. Changes will not persist.": ""
|
||||
"Settings are read-only. Changes will not persist.": "设置处于只读状态。更改将不会保存。"
|
||||
},
|
||||
"registry theme description": {
|
||||
"Color theme from DMS registry": "DMS注册表的颜色主题"
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "wtype不可用,为支持粘贴,请安装wtype"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• 仅从可信来源安装"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
"%1 connected": {
|
||||
"%1 connected": "%1 已連接"
|
||||
},
|
||||
"%1 days ago": {
|
||||
"%1 days ago": ""
|
||||
},
|
||||
"%1 display(s)": {
|
||||
"%1 display(s)": "%1 個螢幕"
|
||||
},
|
||||
@@ -29,6 +32,9 @@
|
||||
"%1 widgets": {
|
||||
"%1 widgets": "%1 個部件"
|
||||
},
|
||||
"%1m ago": {
|
||||
"%1m ago": ""
|
||||
},
|
||||
"(Unnamed)": {
|
||||
"(Unnamed)": "(未命名)"
|
||||
},
|
||||
@@ -1373,6 +1379,9 @@
|
||||
"Fade to lock screen": {
|
||||
"Fade to lock screen": "淡出至鎖定螢幕"
|
||||
},
|
||||
"Fade to monitor off": {
|
||||
"Fade to monitor off": ""
|
||||
},
|
||||
"Failed to activate configuration": {
|
||||
"Failed to activate configuration": "無法啟動配置"
|
||||
},
|
||||
@@ -1664,6 +1673,9 @@
|
||||
"Gradually fade the screen before locking with a configurable grace period": {
|
||||
"Gradually fade the screen before locking with a configurable grace period": "在鎖定前逐漸淡出螢幕,並帶有可設定的緩衝期"
|
||||
},
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": {
|
||||
"Gradually fade the screen before turning off monitors with a configurable grace period": ""
|
||||
},
|
||||
"Graph Time Range": {
|
||||
"Graph Time Range": "圖表時間範圍"
|
||||
},
|
||||
@@ -1997,6 +2009,9 @@
|
||||
"Lock before suspend": {
|
||||
"Lock before suspend": "鎖定後暫停"
|
||||
},
|
||||
"Lock fade grace period": {
|
||||
"Lock fade grace period": ""
|
||||
},
|
||||
"Log Out": {
|
||||
"Log Out": "登出"
|
||||
},
|
||||
@@ -2171,6 +2186,9 @@
|
||||
"Monitor Configuration": {
|
||||
"Monitor Configuration": "顯示器配置"
|
||||
},
|
||||
"Monitor fade grace period": {
|
||||
"Monitor fade grace period": ""
|
||||
},
|
||||
"Monitor whose wallpaper drives dynamic theming colors": {
|
||||
"Monitor whose wallpaper drives dynamic theming colors": "系統介面顏色依據哪一個螢幕上的桌布來決定"
|
||||
},
|
||||
@@ -4030,6 +4048,50 @@
|
||||
"no wallpaper status": {
|
||||
"No wallpaper selected": "未選擇任何桌布"
|
||||
},
|
||||
"notification center tab": {
|
||||
"Current": "",
|
||||
"History": ""
|
||||
},
|
||||
"notification history filter": {
|
||||
"All": "",
|
||||
"Last hour": "",
|
||||
"Today": "",
|
||||
"Yesterday": ""
|
||||
},
|
||||
"notification history filter for content older than other filters": {
|
||||
"Older": ""
|
||||
},
|
||||
"notification history filter | notification history retention option": {
|
||||
"30 days": "",
|
||||
"7 days": ""
|
||||
},
|
||||
"notification history limit": {
|
||||
"Maximum number of notifications to keep": ""
|
||||
},
|
||||
"notification history retention option": {
|
||||
"1 day": "",
|
||||
"14 days": "",
|
||||
"3 days": "",
|
||||
"Forever": ""
|
||||
},
|
||||
"notification history retention settings label": {
|
||||
"History Retention": ""
|
||||
},
|
||||
"notification history setting": {
|
||||
"Auto-delete notifications older than this": "",
|
||||
"Save critical priority notifications to history": "",
|
||||
"Save low priority notifications to history": "",
|
||||
"Save normal priority notifications to history": ""
|
||||
},
|
||||
"notification history toggle description": {
|
||||
"Save dismissed notifications to history": ""
|
||||
},
|
||||
"notification history toggle label": {
|
||||
"Enable History": ""
|
||||
},
|
||||
"now": {
|
||||
"now": ""
|
||||
},
|
||||
"official": {
|
||||
"official": "官方"
|
||||
},
|
||||
@@ -4126,6 +4188,9 @@
|
||||
"wtype not available - install wtype for paste support": {
|
||||
"wtype not available - install wtype for paste support": "wtype 未可用 - 請安裝 wtype 以支援貼上功能"
|
||||
},
|
||||
"yesterday": {
|
||||
"yesterday": ""
|
||||
},
|
||||
"• Install only from trusted sources": {
|
||||
"• Install only from trusted sources": "• 僅從受信任的來源安裝"
|
||||
},
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "%1 days ago",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "%1 display(s)",
|
||||
"translation": "",
|
||||
@@ -69,6 +76,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "%1m ago",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "(Unnamed)",
|
||||
"translation": "",
|
||||
@@ -86,7 +100,7 @@
|
||||
{
|
||||
"term": "1 day",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history retention option",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -135,7 +149,7 @@
|
||||
{
|
||||
"term": "14 days",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history retention option",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -184,7 +198,7 @@
|
||||
{
|
||||
"term": "3 days",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history retention option",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -198,7 +212,7 @@
|
||||
{
|
||||
"term": "30 days",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history filter | notification history retention option",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -233,7 +247,7 @@
|
||||
{
|
||||
"term": "7 days",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history filter | notification history retention option",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -471,7 +485,7 @@
|
||||
{
|
||||
"term": "All",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history filter",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -825,6 +839,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Auto-delete notifications older than this",
|
||||
"translation": "",
|
||||
"context": "notification history setting",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Auto-hide",
|
||||
"translation": "",
|
||||
@@ -2085,6 +2106,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Current",
|
||||
"translation": "",
|
||||
"context": "notification center tab",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Current Items",
|
||||
"translation": "",
|
||||
@@ -2974,6 +3002,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Enable History",
|
||||
"translation": "",
|
||||
"context": "notification history toggle label",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Enable Overview Overlay",
|
||||
"translation": "",
|
||||
@@ -3185,14 +3220,14 @@
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Fade grace period",
|
||||
"term": "Fade to lock screen",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Fade to lock screen",
|
||||
"term": "Fade to monitor off",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
@@ -3751,6 +3786,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Forever",
|
||||
"translation": "",
|
||||
"context": "notification history retention option",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Forget",
|
||||
"translation": "",
|
||||
@@ -3884,6 +3926,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Gradually fade the screen before turning off monitors with a configurable grace period",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Graph Time Range",
|
||||
"translation": "",
|
||||
@@ -4052,6 +4101,20 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "History",
|
||||
"translation": "",
|
||||
"context": "notification center tab",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "History Retention",
|
||||
"translation": "",
|
||||
"context": "notification history retention settings label",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "History Settings",
|
||||
"translation": "",
|
||||
@@ -4521,6 +4584,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Last hour",
|
||||
"translation": "",
|
||||
"context": "notification history filter",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Last launched %1",
|
||||
"translation": "",
|
||||
@@ -4752,6 +4822,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Lock fade grace period",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Log Out",
|
||||
"translation": "",
|
||||
@@ -4962,6 +5039,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Maximum number of notifications to keep",
|
||||
"translation": "",
|
||||
"context": "notification history limit",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Maximum size per clipboard entry",
|
||||
"translation": "",
|
||||
@@ -5179,6 +5263,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Monitor fade grace period",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Monitor whose wallpaper drives dynamic theming colors",
|
||||
"translation": "",
|
||||
@@ -5809,6 +5900,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Older",
|
||||
"translation": "",
|
||||
"context": "notification history filter for content older than other filters",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "On-Screen Displays",
|
||||
"translation": "",
|
||||
@@ -6901,6 +6999,34 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Save critical priority notifications to history",
|
||||
"translation": "",
|
||||
"context": "notification history setting",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Save dismissed notifications to history",
|
||||
"translation": "",
|
||||
"context": "notification history toggle description",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Save low priority notifications to history",
|
||||
"translation": "",
|
||||
"context": "notification history setting",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Save normal priority notifications to history",
|
||||
"translation": "",
|
||||
"context": "notification history setting",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Save password",
|
||||
"translation": "",
|
||||
@@ -8388,7 +8514,7 @@
|
||||
{
|
||||
"term": "Today",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"context": "notification history filter",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
@@ -9323,6 +9449,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "Yesterday",
|
||||
"translation": "",
|
||||
"context": "notification history filter",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "You have unsaved changes. Save before closing this tab?",
|
||||
"translation": "",
|
||||
@@ -9470,6 +9603,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "now",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "official",
|
||||
"translation": "",
|
||||
@@ -9505,6 +9645,13 @@
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "yesterday",
|
||||
"translation": "",
|
||||
"context": "",
|
||||
"reference": "",
|
||||
"comment": ""
|
||||
},
|
||||
{
|
||||
"term": "• Install only from trusted sources",
|
||||
"translation": "",
|
||||
|
||||
Reference in New Issue
Block a user