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

Compare commits

...

9 Commits

Author SHA1 Message Date
bbedward
913bb2ff67 niri: ensure outputs.kdl and binds.kdl exist 2026-01-03 18:31:36 -05:00
Lucas
3bb2696263 Add doctor command (#1259)
* feat: doctor command

* doctor: use console.warn for quickshell feature logs

* doctor: show compositor, quickshell and cli path in verbose

* doctor: show useful env variables

* doctor: indicate if config files are readonly

* doctor: add power-profiles-daemon and i2c

* doctor: cleanup code

* doctor: fix icon theme env variable

* doctor: use builtin config/cache dir functions

* doctor: refactor to use DoctorStatus struct and 'enum'

* doctor: use network backend detector
2026-01-03 18:28:23 -05:00
bbedward
166843ded4 niri: preserve remaining settings when turning off output 2026-01-03 16:17:16 -05:00
Ryan Bateman
02166a4ca5 feat: matugen detects flatpak installations of zenbrowser and vesktop (#1251)
* feat: matugen detects flatpak installations of zenbrowser and vesktop

* fix: add flatpak deps on precommit runner

* fix: address short circuit conditions
2026-01-03 15:28:39 -05:00
bbedward
f0f2e6ef72 i18n: update terms 2026-01-03 15:20:34 -05:00
bbedward
8d8d5de5fd matugen: update vscode template
- yaml/toml highlighting colors
- fix scrollbar contrast
- fix command-search marker
2026-01-03 15:10:38 -05:00
bbedward
6d76f0b476 power: add fade to monitor off option
fixes #558
2026-01-03 15:00:12 -05:00
bbedward
f3f720bb37 settings: fix network refresh button animation behavior
fixes #1258
2026-01-03 14:37:27 -05:00
bbedward
2bf85bc4dd motifications: add support for configurable persistent history
fixes #929
2026-01-03 13:08:48 -05:00
47 changed files with 4494 additions and 776 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -513,5 +513,6 @@ func getCommonCommands() []*cobra.Command {
notifyActionCmd,
matugenCmd,
clipboardCmd,
doctorCmd,
}
}

View 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()
}

View File

@@ -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)

View 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))
}
}

View File

@@ -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")
}

View File

@@ -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")

View File

@@ -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")
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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 },

View File

@@ -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 {

View File

@@ -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
}
}
}

View File

@@ -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;

View 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();
}
}
}

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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;
}
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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)
}
}
}
}

View File

@@ -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 {

View File

@@ -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)
}
}
}
}
}

View File

@@ -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"

View File

@@ -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();
}

View File

@@ -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 = {

View File

@@ -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();
}
}
}
}

View File

@@ -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
}
}

View 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);
}
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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}}"
}
}
],
//

View File

@@ -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": {

View File

@@ -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

View File

@@ -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": "نصب تنها از منابع معتبر"
},

View File

@@ -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": "• התקן/י רק ממקורות מהימנים"
},

View File

@@ -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"
},

View File

@@ -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 allincolla"
},
"yesterday": {
"yesterday": ""
},
"• Install only from trusted sources": {
"• Install only from trusted sources": "• Installa solo da sorgenti fidate"
},

View File

@@ -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": "• 信頼できるソースからのみインストールする"
},

View File

@@ -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ł"
},

View File

@@ -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"
},

View File

@@ -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"
},

View File

@@ -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)": "HDREDID"
@@ -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": "• 仅从可信来源安装"
},

View File

@@ -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": "• 僅從受信任的來源安裝"
},

View File

@@ -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": "",