mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-06 05:28:29 -04:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 82a9824175 | |||
| 2baf048293 | |||
| cffc33e14a | |||
| 0c811e3417 | |||
| 6ad46cf2c2 | |||
| 34626070af | |||
| 80b27b9a6a | |||
| 49f968d26b | |||
| 99b0dc596d | |||
| 158c0c12d8 | |||
| e089948225 | |||
| 7f2ba56e06 | |||
| 6de5593216 | |||
| 365474b0d9 | |||
| dc8a47644a | |||
| 27483e68dc | |||
| 400a18a8ed | |||
| 32ddf614c3 | |||
| 19d919ed5c | |||
| 594a2cde19 | |||
| 11287459c3 | |||
| ef191babb7 | |||
| fe64a342f9 | |||
| e54be7d12d | |||
| 81c886784b | |||
| 6682bb120c | |||
| 5c02ec4789 | |||
| de1e1757c3 | |||
| 43d331d6cf | |||
| 01832856d4 | |||
| 0033e3f0e0 | |||
| 2d3706321a | |||
| 5bb884db57 | |||
| b42763ccbf | |||
| f2a6d62d65 | |||
| f66693df6b | |||
| a710d6d7cc | |||
| 64461c534f | |||
| 73da4879f6 | |||
| 8594a414ec | |||
| df396bfa43 | |||
| 33677150b1 |
+1
-1
@@ -69,7 +69,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260730184236-239485829b0b
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05 h1:Ij/yzOT8y2HL7V5Rkec1GxJV0rUvhKqfAzyGxVRTk1o=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260724133713-a4ef23371e05/go.mod h1:xt8RldAfti0QCWidwYIzsSSoJWsE61WgEhTu2H9UpD4=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260730184236-239485829b0b h1:UwX1H4BkzazL7ips9ljnHzBXGttYARcIi5Njj9aqIt4=
|
||||
github.com/AvengeMedia/dankgo v0.0.0-20260730184236-239485829b0b/go.mod h1:xt8RldAfti0QCWidwYIzsSSoJWsE61WgEhTu2H9UpD4=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||
|
||||
@@ -313,6 +313,7 @@ func EnsureContrastDPSLstar(hexColor, hexBg string, minLc float64, isLightMode b
|
||||
fg := HexToRGB(hexColor)
|
||||
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
|
||||
Lf, af, bf := cf.Lab()
|
||||
Lf *= 100.0
|
||||
|
||||
dir := 1.0
|
||||
if isLightMode {
|
||||
@@ -341,6 +342,7 @@ func EnsureContrastDPSBidirectional(hexColor, hexBg string, minLc float64, isLig
|
||||
fg := HexToRGB(hexColor)
|
||||
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
|
||||
origL, af, bf := cf.Lab()
|
||||
origL *= 100.0
|
||||
|
||||
var darkerResult, lighterResult string
|
||||
darkerL, lighterL := origL, origL
|
||||
@@ -420,6 +422,24 @@ func blendHue(base, target, factor float64) float64 {
|
||||
return result
|
||||
}
|
||||
|
||||
// color8 sits a fixed L* offset from the background so it keeps its dim role
|
||||
// regardless of primary brightness (conventional palettes put ANSI bright
|
||||
// black ~2-2.5:1 from the background, e.g. catppuccin-mocha #585b70 on #1e1e2e)
|
||||
func DeriveDim(bgHex string, hue, sat float64, isLight bool) string {
|
||||
offset := 22.0
|
||||
if isLight {
|
||||
offset = -offset
|
||||
}
|
||||
|
||||
bgL := getLstar(bgHex)
|
||||
targetL := math.Max(0, math.Min(100, bgL+offset))
|
||||
|
||||
tint := HSVToRGB(HSV{H: hue, S: sat, V: 0.5})
|
||||
c := colorful.Color{R: tint.R, G: tint.G, B: tint.B}
|
||||
_, af, bf := c.Lab()
|
||||
return labToHex(targetL, af, bf)
|
||||
}
|
||||
|
||||
func DeriveContainer(primary string, isLight bool) string {
|
||||
rgb := HexToRGB(primary)
|
||||
hsv := RGBToHSV(rgb)
|
||||
@@ -500,10 +520,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
|
||||
gray7V := baseVal * 0.28
|
||||
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
|
||||
|
||||
gray8S := baseSat * 0.05
|
||||
gray8V := baseVal * 0.85
|
||||
dimTarget := secondaryTarget * 0.5
|
||||
palette.Color8 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, dimTarget, opts))
|
||||
palette.Color8 = NewColorInfo(DeriveDim(bgColor, hsv.H, baseSat*0.05, true))
|
||||
|
||||
brightRedS := math.Min(baseSat*1.0, 1.0)
|
||||
brightRedV := math.Min(baseVal*1.2, 1.0)
|
||||
@@ -559,9 +576,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
|
||||
gray7V := math.Min(baseVal*1.05, 1.0)
|
||||
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
|
||||
|
||||
gray8S := baseSat * 0.15
|
||||
gray8V := baseVal * 0.65
|
||||
palette.Color8 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, secondaryTarget, opts))
|
||||
palette.Color8 = NewColorInfo(DeriveDim(bgColor, hsv.H, baseSat*0.15, false))
|
||||
|
||||
brightRedS := math.Min(baseSat*0.75, 1.0)
|
||||
brightRedV := math.Min(baseVal*1.35, 1.0)
|
||||
|
||||
@@ -679,3 +679,73 @@ func TestContrastAlgorithmComparison(t *testing.T) {
|
||||
|
||||
t.Logf("WCAG and DPS palettes differ in %d/16 colors", differentCount)
|
||||
}
|
||||
|
||||
func TestEnsureContrastDPSLightModeStaysLight(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
result string
|
||||
bg string
|
||||
minLstar float64
|
||||
}{
|
||||
{
|
||||
name: "bidirectional adjustment",
|
||||
result: EnsureContrastDPSBidirectional("#d0ccc6", "#f8f8f8", 17.5, true),
|
||||
bg: "#f8f8f8",
|
||||
minLstar: 20.0,
|
||||
},
|
||||
{
|
||||
name: "lstar adjustment",
|
||||
result: EnsureContrastDPSLstar("#c0c0c0", "#f8f8f8", 30.0, true),
|
||||
bg: "#f8f8f8",
|
||||
minLstar: 20.0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
lstar := getLstar(tt.result)
|
||||
if lstar < tt.minLstar {
|
||||
t.Errorf("result %s has L* %.2f on light bg %s, expected >= %.2f (collapsed to near-black)",
|
||||
tt.result, lstar, tt.bg, tt.minLstar)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePaletteColor8Dim(t *testing.T) {
|
||||
hues := []string{"#e91e63", "#f59e0b", "#22c55e", "#06b6d4", "#8b5cf6", "#ef4444"}
|
||||
|
||||
for _, base := range hues {
|
||||
t.Run(base, func(t *testing.T) {
|
||||
palette := GeneratePalette(base, PaletteOptions{IsLight: false, UseDPS: true})
|
||||
|
||||
bgRatio := ContrastRatio(palette.Color8.Hex, palette.Color0.Hex)
|
||||
if bgRatio < 1.5 || bgRatio > 3.0 {
|
||||
t.Errorf("Color8 %s vs bg %s ratio %.2f, expected 1.5-3.0 (bright black stays near bg)",
|
||||
palette.Color8.Hex, palette.Color0.Hex, bgRatio)
|
||||
}
|
||||
|
||||
sepRatio := ContrastRatio(palette.Color4.Hex, palette.Color8.Hex)
|
||||
if sepRatio < 2.0 {
|
||||
t.Errorf("Color4 %s vs Color8 %s ratio %.2f, expected >= 2.0 (blue must not collide with bright black)",
|
||||
palette.Color4.Hex, palette.Color8.Hex, sepRatio)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratePaletteLightColor8StaysLight(t *testing.T) {
|
||||
palette := GeneratePalette("#f59e0b", PaletteOptions{IsLight: true, UseDPS: true})
|
||||
|
||||
lstar := getLstar(palette.Color8.Hex)
|
||||
if lstar < 60.0 {
|
||||
t.Errorf("light mode Color8 %s has L* %.2f, expected >= 60 (dim grey, not near-black)",
|
||||
palette.Color8.Hex, lstar)
|
||||
}
|
||||
|
||||
bgRatio := ContrastRatio(palette.Color8.Hex, palette.Color0.Hex)
|
||||
if bgRatio > 3.0 {
|
||||
t.Errorf("light mode Color8 %s vs bg %s ratio %.2f, expected <= 3.0",
|
||||
palette.Color8.Hex, palette.Color0.Hex, bgRatio)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,11 @@ func NewGentooDistribution(config DistroConfig, logChan chan<- string) *GentooDi
|
||||
}
|
||||
}
|
||||
|
||||
func emergeInstallArgs(packages []string) []string {
|
||||
args := []string{"emerge", "--ask=n", "--quiet", "--autounmask-continue=y", "--autounmask-keep-keywords=y", "--autounmask-keep-masks=y"}
|
||||
return append(args, packages...)
|
||||
}
|
||||
|
||||
func (g *GentooDistribution) getArchKeyword() string {
|
||||
arch := runtime.GOARCH
|
||||
switch arch {
|
||||
@@ -319,6 +324,7 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
||||
}
|
||||
g.log("Portage tree synced successfully")
|
||||
|
||||
args := emergeInstallArgs(missingPkgs)
|
||||
g.log(fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")))
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhasePrerequisites,
|
||||
@@ -326,12 +332,10 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
||||
Step: fmt.Sprintf("Installing %d prerequisites...", len(missingPkgs)),
|
||||
IsComplete: false,
|
||||
NeedsSudo: true,
|
||||
CommandInfo: fmt.Sprintf("sudo emerge --ask=n %s", strings.Join(missingPkgs, " ")),
|
||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||
LogOutput: fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")),
|
||||
}
|
||||
|
||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
||||
args = append(args, missingPkgs...)
|
||||
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
@@ -521,8 +525,7 @@ func (g *GentooDistribution) installPortagePackages(ctx context.Context, package
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
||||
args = append(args, packageNames...)
|
||||
args := emergeInstallArgs(packageNames)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseSystemPackages,
|
||||
@@ -713,8 +716,7 @@ func (g *GentooDistribution) installGURUPackages(ctx context.Context, packages [
|
||||
guruPackages[i] = pkg + "::guru"
|
||||
}
|
||||
|
||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
||||
args = append(args, guruPackages...)
|
||||
args := emergeInstallArgs(guruPackages)
|
||||
|
||||
progressChan <- InstallProgressMsg{
|
||||
Phase: PhaseAURPackages,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -224,6 +225,44 @@ func (h *HyprlandProvider) validateAction(action string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var luaExprActionPattern = regexp.MustCompile(`^(function\s*\(|hl\.)`)
|
||||
|
||||
// isRawLuaActionText reports that action is a Lua expression to re-emit
|
||||
// verbatim rather than freeform dispatcher text to wrap for hyprctl. The
|
||||
// balance check keeps malformed input from corrupting the generated file.
|
||||
func isRawLuaActionText(action string) bool {
|
||||
if !luaExprActionPattern.MatchString(action) {
|
||||
return false
|
||||
}
|
||||
depth := 0
|
||||
var quote byte
|
||||
escaped := false
|
||||
for i := 0; i < len(action); i++ {
|
||||
c := action[i]
|
||||
switch {
|
||||
case escaped:
|
||||
escaped = false
|
||||
case quote != 0:
|
||||
switch c {
|
||||
case '\\':
|
||||
escaped = true
|
||||
case quote:
|
||||
quote = 0
|
||||
}
|
||||
case c == '"' || c == '\'':
|
||||
quote = c
|
||||
case c == '(':
|
||||
depth++
|
||||
case c == ')':
|
||||
depth--
|
||||
if depth < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return depth == 0 && quote == 0 && !escaped
|
||||
}
|
||||
|
||||
func (h *HyprlandProvider) SetBind(key, action, description string, options map[string]any) error {
|
||||
if err := h.ensureWritableConfig(); err != nil {
|
||||
return err
|
||||
@@ -254,11 +293,12 @@ func (h *HyprlandProvider) SetBind(key, action, description string, options map[
|
||||
canonicalKey := canonicalHyprlandOverrideKey(key)
|
||||
normalizedKey := hyprlandOverrideMapKey(canonicalKey)
|
||||
existingBinds[normalizedKey] = &hyprlandOverrideBind{
|
||||
Key: canonicalKey,
|
||||
Action: action,
|
||||
Description: description,
|
||||
Flags: flags,
|
||||
Options: options,
|
||||
Key: canonicalKey,
|
||||
Action: action,
|
||||
Description: description,
|
||||
Flags: flags,
|
||||
Options: options,
|
||||
RawLuaAction: isRawLuaActionText(action),
|
||||
}
|
||||
|
||||
return h.writeOverrideBinds(existingBinds)
|
||||
|
||||
@@ -463,6 +463,56 @@ func TestHyprlandSetBindLeavesConfOnlyInstallReadOnly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRawLuaActionText(t *testing.T) {
|
||||
cases := []struct {
|
||||
action string
|
||||
want bool
|
||||
}{
|
||||
{`function() hl.plugin.scrolloverview.overview("toggle") end`, true},
|
||||
{`hl.dsp.exec_cmd("foo")`, true},
|
||||
{`hl.dsp.no_op()`, true},
|
||||
{"workspace 3", false},
|
||||
{"exec zeditor", false},
|
||||
{`function() hl.foo( end`, false},
|
||||
{`function() hl.foo("a) end`, false},
|
||||
{`hl.foo()) hl.bar((`, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got := isRawLuaActionText(tc.action); got != tc.want {
|
||||
t.Errorf("isRawLuaActionText(%q) = %v, want %v", tc.action, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHyprlandSetBindPreservesRawLuaAction(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
dmsDir := filepath.Join(tmpDir, "dms")
|
||||
if err := os.MkdirAll(dmsDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dmsDir, "binds-user.lua"), []byte("-- DMS user keybind overrides\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
provider := NewHyprlandProvider(tmpDir)
|
||||
rawAction := `function() hl.plugin.scrolloverview.overview("toggle") end`
|
||||
if err := provider.SetBind("SUPER + G", rawAction, "Toggle overview", nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dmsDir, "binds-user.lua"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := string(data)
|
||||
if !strings.Contains(got, `hl.bind("SUPER + G", `+rawAction) {
|
||||
t.Fatalf("expected raw Lua action to be written verbatim, got:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "hyprctl dispatch function") {
|
||||
t.Fatalf("expected raw Lua action to not be wrapped in hyprctl dispatch, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHyprlandSetBindUpdatesSpacedLuaOverrideWithoutDuplicates(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
dmsDir := filepath.Join(tmpDir, "dms")
|
||||
|
||||
@@ -471,12 +471,9 @@ output_path = '%s'
|
||||
case TemplateKindTerminal:
|
||||
appendTerminalConfig(opts, cfgFile, tmpDir, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
||||
case TemplateKindVSCode:
|
||||
appendVSCodeConfig(cfgFile, "vscode", filepath.Join(homeDir, ".vscode/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "codium", filepath.Join(homeDir, ".vscode-oss/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "codeoss", filepath.Join(homeDir, ".config/Code - OSS/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/extensions"), opts.ShellDir)
|
||||
appendVSCodeConfig(cfgFile, "vscode-insiders", filepath.Join(homeDir, ".vscode-insiders/extensions"), opts.ShellDir)
|
||||
for _, editor := range vscodeEditors {
|
||||
appendVSCodeConfig(cfgFile, editor.name, editor.extensionsDir(homeDir), opts.ShellDir)
|
||||
}
|
||||
case TemplateKindEmacs:
|
||||
if utils.EmacsConfigDir() != "" {
|
||||
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigDirs, tmpl.ConfigFile)
|
||||
@@ -633,6 +630,23 @@ func appExists(checker utils.AppChecker, checkCmd []string, checkFlatpaks []stri
|
||||
return false
|
||||
}
|
||||
|
||||
type vscodeEditor struct {
|
||||
name string
|
||||
dataDir string
|
||||
}
|
||||
|
||||
var vscodeEditors = []vscodeEditor{
|
||||
{"vscode", ".vscode"},
|
||||
{"codium", ".vscode-oss"},
|
||||
{"cursor", ".cursor"},
|
||||
{"windsurf", ".windsurf"},
|
||||
{"vscode-insiders", ".vscode-insiders"},
|
||||
}
|
||||
|
||||
func (e vscodeEditor) extensionsDir(homeDir string) string {
|
||||
return filepath.Join(homeDir, e.dataDir, "extensions")
|
||||
}
|
||||
|
||||
func appendVSCodeConfig(cfgFile *os.File, name, extBaseDir, shellDir string) {
|
||||
pattern := filepath.Join(extBaseDir, "danklinux.dms-theme-*")
|
||||
matches, err := filepath.Glob(pattern)
|
||||
@@ -1168,16 +1182,8 @@ func CheckTemplates(checker utils.AppChecker) []TemplateCheck {
|
||||
}
|
||||
|
||||
func checkVSCodeExtension(homeDir string) bool {
|
||||
extDirs := []string{
|
||||
filepath.Join(homeDir, ".vscode/extensions"),
|
||||
filepath.Join(homeDir, ".vscode-oss/extensions"),
|
||||
filepath.Join(homeDir, ".config/Code - OSS/extensions"),
|
||||
filepath.Join(homeDir, ".cursor/extensions"),
|
||||
filepath.Join(homeDir, ".windsurf/extensions"),
|
||||
}
|
||||
|
||||
for _, extDir := range extDirs {
|
||||
pattern := filepath.Join(extDir, "danklinux.dms-theme-*")
|
||||
for _, editor := range vscodeEditors {
|
||||
pattern := filepath.Join(editor.extensionsDir(homeDir), "danklinux.dms-theme-*")
|
||||
if matches, err := filepath.Glob(pattern); err == nil && len(matches) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@ func TestLockScreenPasswordFieldBypassesTextInputIME(t *testing.T) {
|
||||
if !strings.Contains(content, "Keys.onPressed") || !strings.Contains(content, "event.text") {
|
||||
t.Fatalf("passwordField should handle physical key text manually instead of relying on a text input control")
|
||||
}
|
||||
|
||||
// Wayland IMEs commit unconsumed printable keys as text-input text rather
|
||||
// than forwarding raw keys, so the lock screen needs an IME commit sink
|
||||
// alongside raw key handling.
|
||||
if !strings.Contains(content, "id: imeCommitSink") {
|
||||
t.Fatalf("passwordField must keep the imeCommitSink TextInput so IME-routed keyboards can type (#2950)")
|
||||
}
|
||||
if !strings.Contains(content, "Qt.ImhSensitiveData") {
|
||||
t.Fatalf("imeCommitSink must advertise hidden-text hints so IMEs treat it as a password field")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockScreenPamSupportsManagedAndSystemPolicies(t *testing.T) {
|
||||
|
||||
@@ -48,7 +48,7 @@ func NewManager() (*Manager, error) {
|
||||
return nil, fmt.Errorf("failed to find keyboards: %w", err)
|
||||
}
|
||||
|
||||
initialCapsLock := readInitialCapsLockState(devices[0])
|
||||
initialCapsLock, _ := capsLockFromDevices(devices)
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
@@ -85,14 +85,21 @@ func NewManager() (*Manager, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func readInitialCapsLockState(device EvdevDevice) bool {
|
||||
ledStates, err := device.State(evLedType)
|
||||
if err != nil {
|
||||
log.Debugf("Could not read LED state: %v", err)
|
||||
return false
|
||||
func capsLockFromDevices(devices []EvdevDevice) (bool, bool) {
|
||||
for _, device := range devices {
|
||||
if device == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ledStates, err := device.State(evLedType)
|
||||
if err != nil || len(ledStates) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
return ledStates[ledCapslockKey], true
|
||||
}
|
||||
|
||||
return ledStates[ledCapslockKey]
|
||||
return false, false
|
||||
}
|
||||
|
||||
func findKeyboards() ([]EvdevDevice, error) {
|
||||
@@ -297,25 +304,22 @@ func (m *Manager) readAndUpdateCapsLockState(deviceIndex int) {
|
||||
m.devicesMutex.RUnlock()
|
||||
return
|
||||
}
|
||||
device := m.devices[deviceIndex]
|
||||
ordered := make([]EvdevDevice, 0, len(m.devices))
|
||||
ordered = append(ordered, m.devices[deviceIndex])
|
||||
for i, device := range m.devices {
|
||||
if i == deviceIndex {
|
||||
continue
|
||||
}
|
||||
ordered = append(ordered, device)
|
||||
}
|
||||
m.devicesMutex.RUnlock()
|
||||
|
||||
ledStates, err := device.State(evLedType)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to read LED state: %v", err)
|
||||
capsLockState, ok := capsLockFromDevices(ordered)
|
||||
if !ok {
|
||||
log.Debug("No LED-capable device available for caps lock state")
|
||||
return
|
||||
}
|
||||
|
||||
if len(ledStates) == 0 {
|
||||
log.Debug("No LED state available (empty map)")
|
||||
|
||||
// This means the device either:
|
||||
// - doesn't support LED reporting at all, or
|
||||
// - the kernel returned an empty state
|
||||
return
|
||||
}
|
||||
|
||||
capsLockState := ledStates[ledCapslockKey]
|
||||
m.updateCapsLockStateDirect(capsLockState)
|
||||
}
|
||||
|
||||
|
||||
@@ -306,7 +306,7 @@ func TestNotifySubscribers(t *testing.T) {
|
||||
m.Close()
|
||||
}
|
||||
|
||||
func TestReadInitialCapsLockState(t *testing.T) {
|
||||
func TestCapsLockFromDevices(t *testing.T) {
|
||||
t.Run("caps lock is on", func(t *testing.T) {
|
||||
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||
ledState := evdev.StateMap{
|
||||
@@ -314,7 +314,8 @@ func TestReadInitialCapsLockState(t *testing.T) {
|
||||
}
|
||||
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||
|
||||
result := readInitialCapsLockState(mockDevice)
|
||||
result, ok := capsLockFromDevices([]EvdevDevice{mockDevice})
|
||||
assert.True(t, ok)
|
||||
assert.True(t, result)
|
||||
})
|
||||
|
||||
@@ -325,7 +326,8 @@ func TestReadInitialCapsLockState(t *testing.T) {
|
||||
}
|
||||
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||
|
||||
result := readInitialCapsLockState(mockDevice)
|
||||
result, ok := capsLockFromDevices([]EvdevDevice{mockDevice})
|
||||
assert.True(t, ok)
|
||||
assert.False(t, result)
|
||||
})
|
||||
|
||||
@@ -333,9 +335,25 @@ func TestReadInitialCapsLockState(t *testing.T) {
|
||||
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(nil, errors.New("read error")).Once()
|
||||
|
||||
result := readInitialCapsLockState(mockDevice)
|
||||
result, ok := capsLockFromDevices([]EvdevDevice{mockDevice})
|
||||
assert.False(t, ok)
|
||||
assert.False(t, result)
|
||||
})
|
||||
|
||||
t.Run("falls back past device without LED state", func(t *testing.T) {
|
||||
noLedDevice := mocks.NewMockEvdevDevice(t)
|
||||
noLedDevice.EXPECT().State(evdev.EvType(evLedType)).Return(evdev.StateMap{}, nil).Once()
|
||||
|
||||
ledDevice := mocks.NewMockEvdevDevice(t)
|
||||
ledState := evdev.StateMap{
|
||||
ledCapslockKey: true,
|
||||
}
|
||||
ledDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||
|
||||
result, ok := capsLockFromDevices([]EvdevDevice{noLedDevice, nil, ledDevice})
|
||||
assert.True(t, ok)
|
||||
assert.True(t, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestHasInputGroupAccess(t *testing.T) {
|
||||
|
||||
@@ -302,17 +302,17 @@ func (a *SecretAgent) GetSecrets(
|
||||
}
|
||||
a.backend.cachedVPNCredsMu.Unlock()
|
||||
|
||||
a.backend.cachedGPSamlMu.Lock()
|
||||
cachedGPSaml := a.backend.cachedGPSamlCookie
|
||||
if cachedGPSaml != nil && cachedGPSaml.ConnectionUUID == connUuid {
|
||||
a.backend.cachedGPSamlCookie = nil
|
||||
a.backend.cachedGPSamlMu.Unlock()
|
||||
a.backend.cachedOpenConnectMu.Lock()
|
||||
cachedOpenConnect := a.backend.cachedOpenConnectAuth
|
||||
if cachedOpenConnect != nil && cachedOpenConnect.ConnectionUUID == connUuid {
|
||||
a.backend.cachedOpenConnectAuth = nil
|
||||
a.backend.cachedOpenConnectMu.Unlock()
|
||||
|
||||
log.Infof("[SecretAgent] Using cached GlobalProtect SAML cookie for %s", connUuid)
|
||||
log.Infof("[SecretAgent] Using cached OpenConnect authentication for %s", connUuid)
|
||||
|
||||
return buildGPSamlSecretsResponse(settingName, cachedGPSaml.Cookie, cachedGPSaml.Host, cachedGPSaml.Fingerprint), nil
|
||||
return buildOpenConnectSecretsResponse(settingName, cachedOpenConnect.Cookie, cachedOpenConnect.Host, cachedOpenConnect.Fingerprint), nil
|
||||
}
|
||||
a.backend.cachedGPSamlMu.Unlock()
|
||||
a.backend.cachedOpenConnectMu.Unlock()
|
||||
|
||||
if len(fields) == 1 && fields[0] == "gp-saml" {
|
||||
gateway := ""
|
||||
@@ -347,17 +347,17 @@ func (a *SecretAgent) GetSecrets(
|
||||
|
||||
log.Infof("[SecretAgent] GlobalProtect SAML authentication successful, returning cookie to NetworkManager")
|
||||
|
||||
a.backend.cachedGPSamlMu.Lock()
|
||||
a.backend.cachedGPSamlCookie = &cachedGPSamlCookie{
|
||||
a.backend.cachedOpenConnectMu.Lock()
|
||||
a.backend.cachedOpenConnectAuth = &cachedOpenConnectAuth{
|
||||
ConnectionUUID: connUuid,
|
||||
Cookie: authResult.Cookie,
|
||||
Host: authResult.Host,
|
||||
User: authResult.User,
|
||||
Fingerprint: authResult.Fingerprint,
|
||||
}
|
||||
a.backend.cachedGPSamlMu.Unlock()
|
||||
a.backend.cachedOpenConnectMu.Unlock()
|
||||
|
||||
return buildGPSamlSecretsResponse(settingName, authResult.Cookie, authResult.Host, authResult.Fingerprint), nil
|
||||
return buildOpenConnectSecretsResponse(settingName, authResult.Cookie, authResult.Host, authResult.Fingerprint), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -987,7 +987,7 @@ func buildWiFiSecretsResponse(settingName string, secrets map[string]string) nmS
|
||||
return out
|
||||
}
|
||||
|
||||
func buildGPSamlSecretsResponse(settingName, cookie, host, fingerprint string) nmSettingMap {
|
||||
func buildOpenConnectSecretsResponse(settingName, cookie, host, fingerprint string) nmSettingMap {
|
||||
out := nmSettingMap{}
|
||||
vpnSec := nmVariantMap{}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestNeedsExternalBrowserAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGPSamlSecretsResponse(t *testing.T) {
|
||||
func TestBuildOpenConnectSecretsResponse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
settingName string
|
||||
@@ -155,7 +155,7 @@ func TestBuildGPSamlSecretsResponse(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := buildGPSamlSecretsResponse(tt.settingName, tt.cookie, tt.host, tt.fingerprint)
|
||||
result := buildOpenConnectSecretsResponse(tt.settingName, tt.cookie, tt.host, tt.fingerprint)
|
||||
|
||||
assert.NotNil(t, result)
|
||||
assert.Contains(t, result, tt.settingName)
|
||||
|
||||
@@ -80,16 +80,16 @@ type NetworkManagerBackend struct {
|
||||
|
||||
hotspotPendingDevice string
|
||||
|
||||
pendingVPNSave *pendingVPNCredentials
|
||||
pendingVPNSaveMu sync.Mutex
|
||||
cachedVPNCreds *cachedVPNCredentials
|
||||
cachedVPNCredsMu sync.Mutex
|
||||
cachedPKCS11PIN *cachedPKCS11PIN
|
||||
cachedPKCS11Mu sync.Mutex
|
||||
cachedGPSamlCookie *cachedGPSamlCookie
|
||||
cachedGPSamlMu sync.Mutex
|
||||
cachedWiFiSecret *cachedWiFiSecret
|
||||
cachedWiFiSecretMu sync.Mutex
|
||||
pendingVPNSave *pendingVPNCredentials
|
||||
pendingVPNSaveMu sync.Mutex
|
||||
cachedVPNCreds *cachedVPNCredentials
|
||||
cachedVPNCredsMu sync.Mutex
|
||||
cachedPKCS11PIN *cachedPKCS11PIN
|
||||
cachedPKCS11Mu sync.Mutex
|
||||
cachedOpenConnectAuth *cachedOpenConnectAuth
|
||||
cachedOpenConnectMu sync.Mutex
|
||||
cachedWiFiSecret *cachedWiFiSecret
|
||||
cachedWiFiSecretMu sync.Mutex
|
||||
|
||||
onStateChange func()
|
||||
}
|
||||
@@ -100,8 +100,9 @@ type pendingVPNCredentials struct {
|
||||
Password string
|
||||
// Secrets holds all VPN secret fields keyed by name (e.g. "cert-pass");
|
||||
// falls back to Password under the "password" key when empty.
|
||||
Secrets map[string]string
|
||||
SavePassword bool
|
||||
Secrets map[string]string
|
||||
PersistentSecrets map[string]string
|
||||
SavePassword bool
|
||||
}
|
||||
|
||||
type cachedVPNCredentials struct {
|
||||
@@ -124,7 +125,7 @@ type cachedWiFiSecret struct {
|
||||
Secrets map[string]string
|
||||
}
|
||||
|
||||
type cachedGPSamlCookie struct {
|
||||
type cachedOpenConnectAuth struct {
|
||||
ConnectionUUID string
|
||||
Cookie string
|
||||
Host string
|
||||
|
||||
@@ -3,6 +3,7 @@ package network
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@@ -10,16 +11,29 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
type gpSamlAuthResult struct {
|
||||
type openConnectAuthResult struct {
|
||||
Cookie string
|
||||
Host string
|
||||
User string
|
||||
Fingerprint string
|
||||
}
|
||||
|
||||
type openConnectAuthError struct {
|
||||
cause error
|
||||
serverCert string
|
||||
}
|
||||
|
||||
func (e *openConnectAuthError) Error() string {
|
||||
return fmt.Sprintf("openconnect --authenticate failed: %v", e.cause)
|
||||
}
|
||||
|
||||
func (e *openConnectAuthError) Unwrap() error {
|
||||
return e.cause
|
||||
}
|
||||
|
||||
// runGlobalProtectSAMLAuth handles GlobalProtect SAML/SSO authentication using gp-saml-gui.
|
||||
// Only supports protocol=gp. Other protocols need their own implementations.
|
||||
func (b *NetworkManagerBackend) runGlobalProtectSAMLAuth(ctx context.Context, gateway, protocol string) (*gpSamlAuthResult, error) {
|
||||
func (b *NetworkManagerBackend) runGlobalProtectSAMLAuth(ctx context.Context, gateway, protocol string) (*openConnectAuthResult, error) {
|
||||
if gateway == "" {
|
||||
return nil, fmt.Errorf("GP SAML auth: gateway is empty")
|
||||
}
|
||||
@@ -63,7 +77,7 @@ func (b *NetworkManagerBackend) runGlobalProtectSAMLAuth(ctx context.Context, ga
|
||||
}
|
||||
}()
|
||||
|
||||
result := &gpSamlAuthResult{Host: gateway}
|
||||
result := &openConnectAuthResult{Host: gateway}
|
||||
var allOutput []string
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
@@ -117,13 +131,8 @@ func (b *NetworkManagerBackend) runGlobalProtectSAMLAuth(ctx context.Context, ga
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertGPPreloginCookie(ctx context.Context, gateway, preloginCookie, user string) (*gpSamlAuthResult, error) {
|
||||
ocPath, err := exec.LookPath("openconnect")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("openconnect not found: %w", err)
|
||||
}
|
||||
|
||||
args := []string{
|
||||
func convertGPPreloginCookie(ctx context.Context, gateway, preloginCookie, user string) (*openConnectAuthResult, error) {
|
||||
return runOpenConnectAuthenticate(ctx, []string{
|
||||
"--protocol=gp",
|
||||
"--usergroup=gateway:prelogin-cookie",
|
||||
"--user=" + user,
|
||||
@@ -131,18 +140,83 @@ func convertGPPreloginCookie(ctx context.Context, gateway, preloginCookie, user
|
||||
"--allow-insecure-crypto",
|
||||
"--authenticate",
|
||||
gateway,
|
||||
}, preloginCookie)
|
||||
}
|
||||
|
||||
func runOpenConnectPasswordAuth(
|
||||
ctx context.Context,
|
||||
data map[string]string,
|
||||
username, password, serverCert string,
|
||||
) (*openConnectAuthResult, error) {
|
||||
if data["protocol"] != "fortinet" {
|
||||
return nil, fmt.Errorf("only Fortinet password authentication is supported")
|
||||
}
|
||||
gateway := data["gateway"]
|
||||
if gateway == "" {
|
||||
return nil, fmt.Errorf("OpenConnect gateway is empty")
|
||||
}
|
||||
if username == "" || password == "" {
|
||||
return nil, fmt.Errorf("OpenConnect username and password are required")
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--protocol=fortinet",
|
||||
"--user=" + username,
|
||||
"--passwd-on-stdin",
|
||||
"--non-inter",
|
||||
}
|
||||
if usergroup := data["usergroup"]; usergroup != "" {
|
||||
args = append(args, "--usergroup="+usergroup)
|
||||
}
|
||||
if serverCert != "" {
|
||||
args = append(args, "--servercert="+serverCert)
|
||||
}
|
||||
args = append(args, "--authenticate", gateway)
|
||||
|
||||
result, err := runOpenConnectAuthenticate(ctx, args, password)
|
||||
if err == nil {
|
||||
result.Host = gateway
|
||||
if result.Fingerprint == "" {
|
||||
result.Fingerprint = serverCert
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func runOpenConnectAuthenticate(ctx context.Context, args []string, secret string) (*openConnectAuthResult, error) {
|
||||
ocPath, err := exec.LookPath("openconnect")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("openconnect not found: %w", err)
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, ocPath, args...)
|
||||
cmd.Stdin = strings.NewReader(preloginCookie)
|
||||
cmd.Stdin = strings.NewReader(secret + "\n")
|
||||
|
||||
output, err := cmd.CombinedOutput()
|
||||
result := parseOpenConnectAuthenticateOutput(string(output))
|
||||
serverCert := suggestedOpenConnectServerCert(string(output))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("openconnect --authenticate failed: %w\noutput: %s", err, string(output))
|
||||
if ctx.Err() != nil {
|
||||
return nil, fmt.Errorf("openconnect authentication timed out or was cancelled: %w", ctx.Err())
|
||||
}
|
||||
return nil, &openConnectAuthError{cause: err, serverCert: serverCert}
|
||||
}
|
||||
if result.Cookie == "" {
|
||||
return nil, &openConnectAuthError{
|
||||
cause: errors.New("no COOKIE in command output"),
|
||||
serverCert: serverCert,
|
||||
}
|
||||
}
|
||||
|
||||
result := &gpSamlAuthResult{}
|
||||
for _, line := range strings.Split(string(output), "\n") {
|
||||
log.Infof("[OpenConnect] Authentication successful: cookie_len=%d, host=%s, has_fingerprint=%v",
|
||||
len(result.Cookie), result.Host, result.Fingerprint != "")
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseOpenConnectAuthenticateOutput(output string) *openConnectAuthResult {
|
||||
result := &openConnectAuthResult{}
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
switch {
|
||||
case strings.HasPrefix(line, "COOKIE="):
|
||||
@@ -158,15 +232,7 @@ func convertGPPreloginCookie(ctx context.Context, gateway, preloginCookie, user
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.Cookie == "" {
|
||||
return nil, fmt.Errorf("no COOKIE in openconnect --authenticate output: %s", string(output))
|
||||
}
|
||||
|
||||
log.Infof("[GP-SAML] openconnect --authenticate: cookie_len=%d, host=%s, fingerprint=%s",
|
||||
len(result.Cookie), result.Host, result.Fingerprint)
|
||||
|
||||
return result, nil
|
||||
return result
|
||||
}
|
||||
|
||||
func unshellQuote(s string) string {
|
||||
@@ -179,7 +245,7 @@ func unshellQuote(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func parseGPSamlFromCommandLine(line string, result *gpSamlAuthResult) {
|
||||
func parseGPSamlFromCommandLine(line string, result *openConnectAuthResult) {
|
||||
if !strings.Contains(line, "openconnect") {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -71,7 +75,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
initialResult *gpSamlAuthResult
|
||||
initialResult *openConnectAuthResult
|
||||
expectedCookie string
|
||||
expectedUser string
|
||||
expectedFP string
|
||||
@@ -79,7 +83,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "full openconnect command",
|
||||
line: "openconnect --protocol=gp --cookie=AUTH123 --servercert=pin-sha256:ABC --user=john",
|
||||
initialResult: &gpSamlAuthResult{},
|
||||
initialResult: &openConnectAuthResult{},
|
||||
expectedCookie: "AUTH123",
|
||||
expectedUser: "john",
|
||||
expectedFP: "pin-sha256:ABC",
|
||||
@@ -87,7 +91,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "with equals signs in cookie",
|
||||
line: "openconnect --cookie=authcookie=xyz123&portal=GATE --user=jane",
|
||||
initialResult: &gpSamlAuthResult{},
|
||||
initialResult: &openConnectAuthResult{},
|
||||
expectedCookie: "authcookie=xyz123&portal=GATE",
|
||||
expectedUser: "jane",
|
||||
expectedFP: "",
|
||||
@@ -95,7 +99,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "non-openconnect line",
|
||||
line: "some other output",
|
||||
initialResult: &gpSamlAuthResult{},
|
||||
initialResult: &openConnectAuthResult{},
|
||||
expectedCookie: "",
|
||||
expectedUser: "",
|
||||
expectedFP: "",
|
||||
@@ -103,7 +107,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "preserves existing values",
|
||||
line: "openconnect --user=newuser",
|
||||
initialResult: &gpSamlAuthResult{Cookie: "existing", Fingerprint: "existing-fp"},
|
||||
initialResult: &openConnectAuthResult{Cookie: "existing", Fingerprint: "existing-fp"},
|
||||
expectedCookie: "existing",
|
||||
expectedUser: "newuser",
|
||||
expectedFP: "existing-fp",
|
||||
@@ -111,7 +115,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "only updates empty fields",
|
||||
line: "openconnect --cookie=NEW --user=NEW",
|
||||
initialResult: &gpSamlAuthResult{Cookie: "OLD"},
|
||||
initialResult: &openConnectAuthResult{Cookie: "OLD"},
|
||||
expectedCookie: "OLD",
|
||||
expectedUser: "NEW",
|
||||
expectedFP: "",
|
||||
@@ -119,7 +123,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "real gp-saml-gui output",
|
||||
line: "openconnect --protocol=gp --user=john.doe@example.com --os=linux-64 --usergroup=gateway:prelogin-cookie --passwd-on-stdin",
|
||||
initialResult: &gpSamlAuthResult{},
|
||||
initialResult: &openConnectAuthResult{},
|
||||
expectedCookie: "",
|
||||
expectedUser: "john.doe@example.com",
|
||||
expectedFP: "",
|
||||
@@ -127,7 +131,7 @@ func TestParseGPSamlFromCommandLine(t *testing.T) {
|
||||
{
|
||||
name: "with server cert flag",
|
||||
line: "openconnect --servercert=pin-sha256:xp3scfzy3rOgQEXnfPiYKrUk7D66a8b8O+gEXaMPleE= vpn.example.com",
|
||||
initialResult: &gpSamlAuthResult{},
|
||||
initialResult: &openConnectAuthResult{},
|
||||
expectedCookie: "",
|
||||
expectedUser: "",
|
||||
expectedFP: "pin-sha256:xp3scfzy3rOgQEXnfPiYKrUk7D66a8b8O+gEXaMPleE=",
|
||||
@@ -158,7 +162,7 @@ func TestParseGPSamlFromCommandLine_MultipleLines(t *testing.T) {
|
||||
"",
|
||||
}
|
||||
|
||||
result := &gpSamlAuthResult{}
|
||||
result := &openConnectAuthResult{}
|
||||
for _, line := range lines {
|
||||
parseGPSamlFromCommandLine(line, result)
|
||||
}
|
||||
@@ -167,3 +171,19 @@ func TestParseGPSamlFromCommandLine_MultipleLines(t *testing.T) {
|
||||
assert.Empty(t, result.Cookie, "cookie should not be parsed from command line")
|
||||
assert.Empty(t, result.Fingerprint)
|
||||
}
|
||||
|
||||
func TestRunOpenConnectAuthenticateSanitizesFailure(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
openConnectPath := filepath.Join(binDir, "openconnect")
|
||||
script := "#!/bin/sh\nprintf '%s\\n' 'Cookie: should-not-leak' 'Add --servercert pin-sha256:TEST-FINGERPRINT' >&2\nexit 1\n"
|
||||
assert.NoError(t, os.WriteFile(openConnectPath, []byte(script), 0o755))
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
_, err := runOpenConnectAuthenticate(context.Background(), []string{"--authenticate", "vpn.example.test"}, "password")
|
||||
assert.Error(t, err)
|
||||
assert.NotContains(t, err.Error(), "should-not-leak")
|
||||
|
||||
var authErr *openConnectAuthError
|
||||
assert.True(t, errors.As(err, &authErr))
|
||||
assert.Equal(t, "pin-sha256:TEST-FINGERPRINT", authErr.serverCert)
|
||||
}
|
||||
|
||||
@@ -326,6 +326,7 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
}
|
||||
|
||||
authAction := detectVPNAuthAction(vpnServiceType, vpnData)
|
||||
var openConnectAuth *openConnectAuthResult
|
||||
|
||||
switch authAction {
|
||||
case "openvpn_username":
|
||||
@@ -335,6 +336,19 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
if err := b.handleOpenVPNUsernameAuth(targetConn, connName, targetUUID, vpnServiceType); err != nil {
|
||||
return err
|
||||
}
|
||||
case "openconnect_password":
|
||||
if err := b.ensureOpenConnectAgentFlags(targetConn, vpnData); err != nil {
|
||||
return fmt.Errorf("failed to prepare OpenConnect connection: %w", err)
|
||||
}
|
||||
|
||||
authCtx, authCancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
openConnectAuth, err = b.handleOpenConnectPasswordAuth(
|
||||
authCtx, targetConn, connName, targetUUID, vpnServiceType, vpnData,
|
||||
)
|
||||
authCancel()
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenConnect authentication failed: %w", err)
|
||||
}
|
||||
case "gp_saml":
|
||||
gateway := vpnData["gateway"]
|
||||
protocol := vpnData["protocol"]
|
||||
@@ -345,7 +359,7 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
log.Infof("[ConnectVPN] GlobalProtect SAML/SSO authentication required for %s (gateway=%s)", connName, gateway)
|
||||
|
||||
samlCtx, samlCancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
authResult, err := b.runGlobalProtectSAMLAuth(samlCtx, gateway, protocol)
|
||||
openConnectAuth, err = b.runGlobalProtectSAMLAuth(samlCtx, gateway, protocol)
|
||||
samlCancel()
|
||||
if err != nil {
|
||||
errMsg := err.Error()
|
||||
@@ -363,16 +377,6 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
}
|
||||
}
|
||||
|
||||
b.cachedGPSamlMu.Lock()
|
||||
b.cachedGPSamlCookie = &cachedGPSamlCookie{
|
||||
ConnectionUUID: targetUUID,
|
||||
Cookie: authResult.Cookie,
|
||||
Host: authResult.Host,
|
||||
User: authResult.User,
|
||||
Fingerprint: authResult.Fingerprint,
|
||||
}
|
||||
b.cachedGPSamlMu.Unlock()
|
||||
|
||||
if err := targetConn.ClearSecrets(); err != nil {
|
||||
log.Warnf("[ConnectVPN] ClearSecrets failed (non-fatal): %v", err)
|
||||
} else {
|
||||
@@ -382,6 +386,19 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
log.Infof("[ConnectVPN] GlobalProtect SAML cookie cached for %s, proceeding with activation", connName)
|
||||
}
|
||||
|
||||
if openConnectAuth != nil {
|
||||
b.cachedOpenConnectMu.Lock()
|
||||
b.cachedOpenConnectAuth = &cachedOpenConnectAuth{
|
||||
ConnectionUUID: targetUUID,
|
||||
Cookie: openConnectAuth.Cookie,
|
||||
Host: openConnectAuth.Host,
|
||||
User: openConnectAuth.User,
|
||||
Fingerprint: openConnectAuth.Fingerprint,
|
||||
}
|
||||
b.cachedOpenConnectMu.Unlock()
|
||||
log.Infof("[ConnectVPN] OpenConnect authentication cached for %s, proceeding with activation", connName)
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = true
|
||||
b.state.ConnectingVPNUUID = targetUUID
|
||||
@@ -394,6 +411,13 @@ func (b *NetworkManagerBackend) ConnectVPN(uuidOrName string, singleActive bool)
|
||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
||||
_, err = nm.ActivateConnection(targetConn, nil, nil)
|
||||
if err != nil {
|
||||
b.cachedOpenConnectMu.Lock()
|
||||
b.cachedOpenConnectAuth = nil
|
||||
b.cachedOpenConnectMu.Unlock()
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
b.pendingVPNSave = nil
|
||||
b.pendingVPNSaveMu.Unlock()
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnectingVPN = false
|
||||
b.state.ConnectingVPNUUID = ""
|
||||
@@ -425,6 +449,9 @@ func detectVPNAuthAction(serviceType string, data map[string]string) string {
|
||||
log.Infof("[VPN] External browser auth detected for protocol '%s' but only GlobalProtect (gp) is currently supported", protocol)
|
||||
}
|
||||
}
|
||||
if protocol == "fortinet" && data["authtype"] == "password" {
|
||||
return "openconnect_password"
|
||||
}
|
||||
case strings.Contains(serviceType, "openvpn"):
|
||||
connType := data["connection-type"]
|
||||
username := data["username"]
|
||||
@@ -435,6 +462,200 @@ func detectVPNAuthAction(serviceType string, data map[string]string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func setOpenConnectAgentFlags(data map[string]string) bool {
|
||||
changed := false
|
||||
for _, field := range []string{"cookie", "gateway", "gwcert"} {
|
||||
key := field + "-flags"
|
||||
if data[key] != "2" {
|
||||
data[key] = "2"
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ensureOpenConnectAgentFlags(conn gonetworkmanager.Connection, data map[string]string) error {
|
||||
if !setOpenConnectAgentFlags(data) {
|
||||
return nil
|
||||
}
|
||||
if b.dbusConn == nil {
|
||||
return fmt.Errorf("NetworkManager D-Bus connection is unavailable")
|
||||
}
|
||||
|
||||
connObj := b.dbusConn.Object("org.freedesktop.NetworkManager", conn.GetPath())
|
||||
var existingSettings map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0).Store(&existingSettings); err != nil {
|
||||
return fmt.Errorf("failed to get connection settings: %w", err)
|
||||
}
|
||||
|
||||
vpn, ok := existingSettings["vpn"]
|
||||
if !ok {
|
||||
return fmt.Errorf("VPN settings are missing")
|
||||
}
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
|
||||
var stored map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSecrets", 0, "vpn").Store(&stored); err != nil {
|
||||
return fmt.Errorf("failed to preserve VPN secrets: %w", err)
|
||||
}
|
||||
if storedVPN, ok := stored["vpn"]; ok {
|
||||
if secrets, ok := storedVPN["secrets"]; ok {
|
||||
vpn["secrets"] = secrets
|
||||
}
|
||||
}
|
||||
|
||||
settings := map[string]map[string]dbus.Variant{"vpn": vpn}
|
||||
if connection, ok := existingSettings["connection"]; ok {
|
||||
settings["connection"] = connection
|
||||
}
|
||||
|
||||
var result map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.Update2", 0,
|
||||
settings, uint32(0x1), map[string]dbus.Variant{}).Store(&result); err != nil {
|
||||
return fmt.Errorf("failed to set NetworkManager secret-agent flags: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleOpenConnectPasswordAuth(
|
||||
ctx context.Context,
|
||||
targetConn gonetworkmanager.Connection,
|
||||
connName, targetUUID, vpnServiceType string,
|
||||
data map[string]string,
|
||||
) (*openConnectAuthResult, error) {
|
||||
username := data["username"]
|
||||
secrets := map[string]string{}
|
||||
if stored, err := targetConn.GetSecrets("vpn"); err == nil {
|
||||
if vpn, ok := stored["vpn"]; ok {
|
||||
if saved, ok := vpn["secrets"].(map[string]string); ok {
|
||||
secrets = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
password := secrets["password"]
|
||||
serverCert := secrets["certificate:"+data["gateway"]]
|
||||
if serverCert == "" {
|
||||
serverCert = secrets["gwcert"]
|
||||
}
|
||||
|
||||
var reply PromptReply
|
||||
if username == "" || password == "" {
|
||||
if b.promptBroker == nil {
|
||||
return nil, fmt.Errorf("password authentication requires an interactive prompt")
|
||||
}
|
||||
|
||||
fields := []string{}
|
||||
fieldsInfo := []FieldInfo{}
|
||||
if username == "" {
|
||||
fields = append(fields, "username")
|
||||
fieldsInfo = append(fieldsInfo, FieldInfo{Name: "username", Label: "Username", IsSecret: false})
|
||||
}
|
||||
if password == "" {
|
||||
fields = append(fields, "password")
|
||||
fieldsInfo = append(fieldsInfo, FieldInfo{Name: "password", Label: "Password", IsSecret: true})
|
||||
}
|
||||
|
||||
token, err := b.promptBroker.Ask(ctx, PromptRequest{
|
||||
Name: connName,
|
||||
ConnType: "vpn",
|
||||
VpnService: vpnServiceType,
|
||||
SettingName: "vpn",
|
||||
Fields: fields,
|
||||
FieldsInfo: fieldsInfo,
|
||||
Reason: "required",
|
||||
ConnectionId: connName,
|
||||
ConnectionUuid: targetUUID,
|
||||
ConnectionPath: string(targetConn.GetPath()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request credentials: %w", err)
|
||||
}
|
||||
|
||||
reply, err = b.promptBroker.Wait(ctx, token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("credentials prompt failed: %w", err)
|
||||
}
|
||||
if username == "" {
|
||||
username = reply.Secrets["username"]
|
||||
}
|
||||
if password == "" {
|
||||
password = reply.Secrets["password"]
|
||||
}
|
||||
}
|
||||
|
||||
auth, err := runOpenConnectPasswordAuth(ctx, data, username, password, serverCert)
|
||||
persistentSecrets := map[string]string{}
|
||||
var authErr *openConnectAuthError
|
||||
if err != nil && errors.As(err, &authErr) && authErr.serverCert != "" && authErr.serverCert != serverCert {
|
||||
if b.promptBroker == nil {
|
||||
return nil, fmt.Errorf("VPN server certificate is untrusted: %s", authErr.serverCert)
|
||||
}
|
||||
|
||||
reason := "server-certificate"
|
||||
if serverCert != "" {
|
||||
reason = "server-certificate-changed"
|
||||
}
|
||||
|
||||
token, promptErr := b.promptBroker.Ask(ctx, PromptRequest{
|
||||
Name: connName,
|
||||
ConnType: "vpn",
|
||||
VpnService: vpnServiceType,
|
||||
SettingName: "vpn",
|
||||
Hints: []string{authErr.serverCert},
|
||||
Reason: reason,
|
||||
ConnectionId: connName,
|
||||
ConnectionUuid: targetUUID,
|
||||
ConnectionPath: string(targetConn.GetPath()),
|
||||
})
|
||||
if promptErr != nil {
|
||||
return nil, fmt.Errorf("failed to request certificate confirmation: %w", promptErr)
|
||||
}
|
||||
if _, promptErr = b.promptBroker.Wait(ctx, token); promptErr != nil {
|
||||
return nil, fmt.Errorf("certificate confirmation failed: %w", promptErr)
|
||||
}
|
||||
|
||||
auth, err = runOpenConnectPasswordAuth(ctx, data, username, password, authErr.serverCert)
|
||||
if err == nil {
|
||||
persistentSecrets["certificate:"+data["gateway"]] = authErr.serverCert
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(reply.Secrets) > 0 || len(persistentSecrets) > 0 {
|
||||
creds := &pendingVPNCredentials{
|
||||
ConnectionPath: string(targetConn.GetPath()),
|
||||
PersistentSecrets: persistentSecrets,
|
||||
}
|
||||
if _, ok := reply.Secrets["username"]; ok {
|
||||
creds.Username = username
|
||||
}
|
||||
if reply.Save {
|
||||
creds.Username = username
|
||||
creds.Password = password
|
||||
creds.Secrets = map[string]string{"password": password}
|
||||
creds.SavePassword = true
|
||||
}
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
b.pendingVPNSave = creds
|
||||
b.pendingVPNSaveMu.Unlock()
|
||||
}
|
||||
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
func suggestedOpenConnectServerCert(output string) string {
|
||||
for _, field := range strings.Fields(output) {
|
||||
field = strings.Trim(field, "'\".,")
|
||||
if strings.HasPrefix(field, "pin-sha256:") {
|
||||
return field
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) handleOpenVPNUsernameAuth(targetConn gonetworkmanager.Connection, connName, targetUUID, vpnServiceType string) error {
|
||||
log.Infof("[ConnectVPN] OpenVPN requires username in vpn.data - prompting before activation")
|
||||
|
||||
@@ -758,13 +979,13 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
b.state.VPNErrorUuid = ""
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
// Clear cached PKCS11 PIN and SAML cookie on success
|
||||
// Clear cached one-shot authentication values on success.
|
||||
b.cachedPKCS11Mu.Lock()
|
||||
b.cachedPKCS11PIN = nil
|
||||
b.cachedPKCS11Mu.Unlock()
|
||||
b.cachedGPSamlMu.Lock()
|
||||
b.cachedGPSamlCookie = nil
|
||||
b.cachedGPSamlMu.Unlock()
|
||||
b.cachedOpenConnectMu.Lock()
|
||||
b.cachedOpenConnectAuth = nil
|
||||
b.cachedOpenConnectMu.Unlock()
|
||||
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
pending := b.pendingVPNSave
|
||||
@@ -787,13 +1008,16 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
b.state.VPNErrorUuid = connectingVPNUUID
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
// Clear cached PKCS11 PIN and SAML cookie on failure
|
||||
// Clear cached one-shot authentication values on failure.
|
||||
b.cachedPKCS11Mu.Lock()
|
||||
b.cachedPKCS11PIN = nil
|
||||
b.cachedPKCS11Mu.Unlock()
|
||||
b.cachedGPSamlMu.Lock()
|
||||
b.cachedGPSamlCookie = nil
|
||||
b.cachedGPSamlMu.Unlock()
|
||||
b.cachedOpenConnectMu.Lock()
|
||||
b.cachedOpenConnectAuth = nil
|
||||
b.cachedOpenConnectMu.Unlock()
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
b.pendingVPNSave = nil
|
||||
b.pendingVPNSaveMu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -811,13 +1035,16 @@ func (b *NetworkManagerBackend) updateVPNConnectionState() {
|
||||
b.state.VPNErrorUuid = connectingVPNUUID
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
// Clear cached PKCS11 PIN and SAML cookie
|
||||
// Clear cached one-shot authentication values.
|
||||
b.cachedPKCS11Mu.Lock()
|
||||
b.cachedPKCS11PIN = nil
|
||||
b.cachedPKCS11Mu.Unlock()
|
||||
b.cachedGPSamlMu.Lock()
|
||||
b.cachedGPSamlCookie = nil
|
||||
b.cachedGPSamlMu.Unlock()
|
||||
b.cachedOpenConnectMu.Lock()
|
||||
b.cachedOpenConnectAuth = nil
|
||||
b.cachedOpenConnectMu.Unlock()
|
||||
b.pendingVPNSaveMu.Lock()
|
||||
b.pendingVPNSave = nil
|
||||
b.pendingVPNSaveMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -863,17 +1090,40 @@ func (b *NetworkManagerBackend) saveVPNCredentials(creds *pendingVPNCredentials)
|
||||
log.Infof("[saveVPNCredentials] Saving username")
|
||||
}
|
||||
|
||||
// Save secrets if requested
|
||||
if creds.SavePassword {
|
||||
secs := creds.Secrets
|
||||
if len(secs) == 0 {
|
||||
secs = map[string]string{"password": creds.Password}
|
||||
secs := map[string]string{}
|
||||
if len(creds.PersistentSecrets) > 0 {
|
||||
var stored map[string]map[string]dbus.Variant
|
||||
if err := connObj.Call("org.freedesktop.NetworkManager.Settings.Connection.GetSecrets", 0, "vpn").Store(&stored); err != nil {
|
||||
log.Warnf("[saveVPNCredentials] GetSecrets failed: %v", err)
|
||||
return
|
||||
}
|
||||
for field := range secs {
|
||||
if storedVPN, ok := stored["vpn"]; ok {
|
||||
if storedSecrets, ok := storedVPN["secrets"]; ok {
|
||||
saved, _ := storedSecrets.Value().(map[string]string)
|
||||
for field, value := range saved {
|
||||
secs[field] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
for field, value := range creds.PersistentSecrets {
|
||||
secs[field] = value
|
||||
data[field+"-flags"] = "0"
|
||||
}
|
||||
}
|
||||
|
||||
if creds.SavePassword {
|
||||
toSave := creds.Secrets
|
||||
if len(toSave) == 0 {
|
||||
toSave = map[string]string{"password": creds.Password}
|
||||
}
|
||||
for field, value := range toSave {
|
||||
secs[field] = value
|
||||
data[field+"-flags"] = "0"
|
||||
}
|
||||
}
|
||||
if len(secs) > 0 {
|
||||
vpn["secrets"] = dbus.MakeVariant(secs)
|
||||
log.Infof("[saveVPNCredentials] Saving %d secret field(s) with flags=0", len(secs))
|
||||
log.Infof("[saveVPNCredentials] Saving %d secret field(s)", len(secs))
|
||||
}
|
||||
|
||||
vpn["data"] = dbus.MakeVariant(data)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
mock_gonetworkmanager "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/github.com/Wifx/gonetworkmanager/v2"
|
||||
"github.com/Wifx/gonetworkmanager/v2"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -136,3 +140,137 @@ func TestNetworkManagerBackend_UpdateVPNConnectionState_EmptyUUID(t *testing.T)
|
||||
backend.updateVPNConnectionState()
|
||||
})
|
||||
}
|
||||
|
||||
func TestDetectVPNAuthAction_FortinetPasswordOnly(t *testing.T) {
|
||||
service := "org.freedesktop.NetworkManager.openconnect"
|
||||
|
||||
assert.Equal(t, "openconnect_password", detectVPNAuthAction(service, map[string]string{
|
||||
"protocol": "fortinet",
|
||||
"authtype": "password",
|
||||
}))
|
||||
assert.Empty(t, detectVPNAuthAction(service, map[string]string{
|
||||
"protocol": "anyconnect",
|
||||
"authtype": "password",
|
||||
}))
|
||||
assert.Empty(t, detectVPNAuthAction(service, map[string]string{
|
||||
"protocol": "fortinet",
|
||||
"authtype": "saml",
|
||||
}))
|
||||
}
|
||||
|
||||
func TestEnsureOpenConnectAgentFlags(t *testing.T) {
|
||||
data := map[string]string{"protocol": "fortinet"}
|
||||
assert.True(t, setOpenConnectAgentFlags(data))
|
||||
assert.Equal(t, "2", data["cookie-flags"])
|
||||
assert.Equal(t, "2", data["gateway-flags"])
|
||||
assert.Equal(t, "2", data["gwcert-flags"])
|
||||
assert.False(t, setOpenConnectAgentFlags(data))
|
||||
}
|
||||
|
||||
func TestOpenConnectCertificateConfirmation(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
openConnectPath := filepath.Join(binDir, "openconnect")
|
||||
script := `#!/bin/sh
|
||||
case "$*" in
|
||||
*--servercert=pin-sha256:TEST-FINGERPRINT*)
|
||||
printf '%s\n' "COOKIE='SVPNCOOKIE=test'" "HOST='vpn.example.test'" "FINGERPRINT='pin-sha256:TEST-FINGERPRINT'"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' 'Add --servercert pin-sha256:TEST-FINGERPRINT' >&2
|
||||
exit 1
|
||||
`
|
||||
assert.NoError(t, os.WriteFile(openConnectPath, []byte(script), 0o755))
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
conn := mock_gonetworkmanager.NewMockConnection(t)
|
||||
connPath := dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings/999")
|
||||
conn.EXPECT().GetSecrets("vpn").Return(gonetworkmanager.ConnectionSettings{
|
||||
"vpn": {"secrets": map[string]string{"password": "test-password"}},
|
||||
}, nil)
|
||||
conn.EXPECT().GetPath().Return(connPath).Twice()
|
||||
|
||||
broker := &fakePromptBroker{
|
||||
asked: make(chan PromptRequest, 1),
|
||||
reply: PromptReply{},
|
||||
}
|
||||
backend := &NetworkManagerBackend{promptBroker: broker}
|
||||
data := map[string]string{
|
||||
"gateway": "vpn.example.test:443",
|
||||
"protocol": "fortinet",
|
||||
"authtype": "password",
|
||||
"username": "test-user",
|
||||
}
|
||||
|
||||
result, err := backend.handleOpenConnectPasswordAuth(
|
||||
context.Background(), conn, "Test VPN", "test-uuid",
|
||||
"org.freedesktop.NetworkManager.openconnect", data,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "SVPNCOOKIE=test", result.Cookie)
|
||||
assert.Equal(t, "vpn.example.test:443", result.Host)
|
||||
|
||||
prompt := <-broker.asked
|
||||
assert.Equal(t, "server-certificate", prompt.Reason)
|
||||
assert.Equal(t, []string{"pin-sha256:TEST-FINGERPRINT"}, prompt.Hints)
|
||||
|
||||
assert.Equal(t, map[string]string{
|
||||
"certificate:vpn.example.test:443": "pin-sha256:TEST-FINGERPRINT",
|
||||
}, backend.pendingVPNSave.PersistentSecrets)
|
||||
}
|
||||
|
||||
func TestOpenConnectCertificateRotationReprompts(t *testing.T) {
|
||||
binDir := t.TempDir()
|
||||
openConnectPath := filepath.Join(binDir, "openconnect")
|
||||
script := `#!/bin/sh
|
||||
case "$*" in
|
||||
*--servercert=pin-sha256:NEW-FINGERPRINT*)
|
||||
printf '%s\n' "COOKIE='SVPNCOOKIE=test'" "HOST='vpn.example.test'" "FINGERPRINT='pin-sha256:NEW-FINGERPRINT'"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
printf '%s\n' 'Add --servercert pin-sha256:NEW-FINGERPRINT' >&2
|
||||
exit 1
|
||||
`
|
||||
assert.NoError(t, os.WriteFile(openConnectPath, []byte(script), 0o755))
|
||||
t.Setenv("PATH", binDir)
|
||||
|
||||
conn := mock_gonetworkmanager.NewMockConnection(t)
|
||||
connPath := dbus.ObjectPath("/org/freedesktop/NetworkManager/Settings/999")
|
||||
conn.EXPECT().GetSecrets("vpn").Return(gonetworkmanager.ConnectionSettings{
|
||||
"vpn": {"secrets": map[string]string{
|
||||
"password": "test-password",
|
||||
"certificate:vpn.example.test:443": "pin-sha256:OLD-FINGERPRINT",
|
||||
}},
|
||||
}, nil)
|
||||
conn.EXPECT().GetPath().Return(connPath).Twice()
|
||||
|
||||
broker := &fakePromptBroker{
|
||||
asked: make(chan PromptRequest, 1),
|
||||
reply: PromptReply{},
|
||||
}
|
||||
backend := &NetworkManagerBackend{promptBroker: broker}
|
||||
data := map[string]string{
|
||||
"gateway": "vpn.example.test:443",
|
||||
"protocol": "fortinet",
|
||||
"authtype": "password",
|
||||
"username": "test-user",
|
||||
}
|
||||
|
||||
result, err := backend.handleOpenConnectPasswordAuth(
|
||||
context.Background(), conn, "Test VPN", "test-uuid",
|
||||
"org.freedesktop.NetworkManager.openconnect", data,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "SVPNCOOKIE=test", result.Cookie)
|
||||
|
||||
prompt := <-broker.asked
|
||||
assert.Equal(t, "server-certificate-changed", prompt.Reason)
|
||||
assert.Equal(t, []string{"pin-sha256:NEW-FINGERPRINT"}, prompt.Hints)
|
||||
|
||||
assert.Equal(t, map[string]string{
|
||||
"certificate:vpn.example.test:443": "pin-sha256:NEW-FINGERPRINT",
|
||||
}, backend.pendingVPNSave.PersistentSecrets)
|
||||
assert.False(t, backend.pendingVPNSave.SavePassword)
|
||||
assert.Empty(t, backend.pendingVPNSave.Secrets)
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) {
|
||||
handleGetNetworkQRCode(conn, req, manager)
|
||||
case "network.qrcode-content":
|
||||
handleGetNetworkQRCodeContent(conn, req, manager)
|
||||
case "network.generate-qrcode":
|
||||
handleGenerateQRCode(conn, req)
|
||||
case "network.delete-qrcode":
|
||||
handleDeleteQRCode(conn, req, manager)
|
||||
case "network.ethernet.info":
|
||||
@@ -365,6 +367,22 @@ func handleGetNetworkQRCodeContent(conn *models.Conn, req models.Request, manage
|
||||
models.Respond(conn, req.ID, content)
|
||||
}
|
||||
|
||||
func handleGenerateQRCode(conn *models.Conn, req models.Request) {
|
||||
text, err := params.String(req.Params, "text")
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
paths, err := generateTextQRCode(text)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
models.Respond(conn, req.ID, paths)
|
||||
}
|
||||
|
||||
func handleDeleteQRCode(conn *models.Conn, req models.Request, _ *Manager) {
|
||||
path, err := params.String(req.Params, "path")
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/yeqown/go-qrcode/v2"
|
||||
"github.com/yeqown/go-qrcode/writer/standard"
|
||||
)
|
||||
|
||||
const textQRCodeTmpPrefix = "/tmp/dank-text-qrcode-"
|
||||
|
||||
func generateTextQRCode(text string) ([2]string, error) {
|
||||
qrc, err := qrcode.New(text)
|
||||
if err != nil {
|
||||
return [2]string{}, fmt.Errorf("failed to create QR code for text: %w", err)
|
||||
}
|
||||
|
||||
pathThemed, pathNormal := textQRCodePaths(text)
|
||||
|
||||
if err := saveQRCodePNG(qrc, pathThemed, standard.WithBgTransparent(), standard.WithFgColorRGBHex("#ffffff")); err != nil {
|
||||
return [2]string{}, err
|
||||
}
|
||||
if err := saveQRCodePNG(qrc, pathNormal); err != nil {
|
||||
return [2]string{}, err
|
||||
}
|
||||
|
||||
return [2]string{pathThemed, pathNormal}, nil
|
||||
}
|
||||
|
||||
// Write to a temp file and rename into place so the shell's Image never
|
||||
// observes a partially written PNG.
|
||||
func saveQRCodePNG(qrc *qrcode.QRCode, path string, opts ...standard.ImageOption) error {
|
||||
tmpPath := path + ".tmp"
|
||||
opts = append(opts, standard.WithBuiltinImageEncoder(standard.PNG_FORMAT))
|
||||
|
||||
w, err := standard.New(tmpPath, opts...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create QR code writer: %w", err)
|
||||
}
|
||||
if err := qrc.Save(w); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return fmt.Errorf("failed to save QR code: %w", err)
|
||||
}
|
||||
if err := os.Rename(tmpPath, path); err != nil {
|
||||
os.Remove(tmpPath)
|
||||
return fmt.Errorf("failed to move QR code into place: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Paths are unique per generation, not per text: the library's mask selection
|
||||
// is non-deterministic, so regenerating the same text produces different
|
||||
// bytes, and reusing a path lets the shell's URL-keyed pixmap cache serve a
|
||||
// stale pattern over the new file.
|
||||
func textQRCodePaths(text string) (themed, normal string) {
|
||||
hash := fmt.Sprintf("%x", sha256.Sum256([]byte(text)))[:8]
|
||||
nonce := time.Now().UnixNano()
|
||||
themed = fmt.Sprintf("%s%s-%d-themed.png", textQRCodeTmpPrefix, hash, nonce)
|
||||
normal = fmt.Sprintf("%s%s-%d-normal.png", textQRCodeTmpPrefix, hash, nonce)
|
||||
return
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func qrCodePaths(ssid string) (themed, normal string) {
|
||||
|
||||
func isValidQRCodePath(path string) bool {
|
||||
clean := filepath.Clean(path)
|
||||
return strings.HasPrefix(clean, qrCodeTmpPrefix) && strings.HasSuffix(clean, ".png")
|
||||
return (strings.HasPrefix(clean, qrCodeTmpPrefix) || strings.HasPrefix(clean, textQRCodeTmpPrefix)) && strings.HasSuffix(clean, ".png")
|
||||
}
|
||||
|
||||
var safePathChar = regexp.MustCompile(`[^a-zA-Z0-9_-]`)
|
||||
|
||||
@@ -759,6 +759,9 @@ func (m *Manager) schedulerLoop() {
|
||||
|
||||
now := time.Now()
|
||||
m.recalcSchedule(now)
|
||||
// publish independent of output readiness so night status never
|
||||
// presents a stale schedule while applies are blocked (#2967)
|
||||
m.updateStateFromSchedule()
|
||||
|
||||
waitDur := 24 * time.Hour
|
||||
if enabled {
|
||||
@@ -1104,13 +1107,15 @@ func (m *Manager) SetTemperature(low, high int) error {
|
||||
m.configMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
m.config.LowTemp = low
|
||||
m.config.HighTemp = high
|
||||
err := m.config.Validate()
|
||||
m.configMutex.Unlock()
|
||||
if err != nil {
|
||||
updated := m.config
|
||||
updated.LowTemp = low
|
||||
updated.HighTemp = high
|
||||
if err := updated.Validate(); err != nil {
|
||||
m.configMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
m.config = updated
|
||||
m.configMutex.Unlock()
|
||||
m.triggerUpdate()
|
||||
return nil
|
||||
}
|
||||
@@ -1122,14 +1127,16 @@ func (m *Manager) SetLocation(lat, lon float64) error {
|
||||
m.configMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
m.config.Latitude = &lat
|
||||
m.config.Longitude = &lon
|
||||
m.config.UseIPLocation = false
|
||||
err := m.config.Validate()
|
||||
m.configMutex.Unlock()
|
||||
if err != nil {
|
||||
updated := m.config
|
||||
updated.Latitude = &lat
|
||||
updated.Longitude = &lon
|
||||
updated.UseIPLocation = false
|
||||
if err := updated.Validate(); err != nil {
|
||||
m.configMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
m.config = updated
|
||||
m.configMutex.Unlock()
|
||||
m.triggerUpdate()
|
||||
return nil
|
||||
}
|
||||
@@ -1164,13 +1171,15 @@ func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
||||
m.configMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
m.config.ManualSunrise = &sunrise
|
||||
m.config.ManualSunset = &sunset
|
||||
err := m.config.Validate()
|
||||
m.configMutex.Unlock()
|
||||
if err != nil {
|
||||
updated := m.config
|
||||
updated.ManualSunrise = &sunrise
|
||||
updated.ManualSunset = &sunset
|
||||
if err := updated.Validate(); err != nil {
|
||||
m.configMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
m.config = updated
|
||||
m.configMutex.Unlock()
|
||||
m.triggerUpdate()
|
||||
return nil
|
||||
}
|
||||
@@ -1193,12 +1202,14 @@ func (m *Manager) SetGamma(gamma float64) error {
|
||||
m.configMutex.Unlock()
|
||||
return nil
|
||||
}
|
||||
m.config.Gamma = gamma
|
||||
err := m.config.Validate()
|
||||
m.configMutex.Unlock()
|
||||
if err != nil {
|
||||
updated := m.config
|
||||
updated.Gamma = gamma
|
||||
if err := updated.Validate(); err != nil {
|
||||
m.configMutex.Unlock()
|
||||
return err
|
||||
}
|
||||
m.config = updated
|
||||
m.configMutex.Unlock()
|
||||
m.triggerUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_gamma_control"
|
||||
)
|
||||
|
||||
func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
|
||||
@@ -412,3 +413,75 @@ func TestNewManager_InvalidConfig(t *testing.T) {
|
||||
_, err := NewManager(mockDisplay, config)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSetters_RejectedValuesLeaveConfigUntouched(t *testing.T) {
|
||||
newManager := func() *Manager {
|
||||
return &Manager{
|
||||
config: DefaultConfig(),
|
||||
updateTrigger: make(chan struct{}, 1),
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("SetTemperature", func(t *testing.T) {
|
||||
m := newManager()
|
||||
before := m.config
|
||||
|
||||
err := m.SetTemperature(3200, 2500)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, before, m.config)
|
||||
assert.Empty(t, m.updateTrigger)
|
||||
})
|
||||
|
||||
t.Run("SetLocation", func(t *testing.T) {
|
||||
m := newManager()
|
||||
before := m.config
|
||||
|
||||
err := m.SetLocation(120.0, 10.0)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, before, m.config)
|
||||
assert.Empty(t, m.updateTrigger)
|
||||
})
|
||||
|
||||
t.Run("SetGamma", func(t *testing.T) {
|
||||
m := newManager()
|
||||
before := m.config
|
||||
|
||||
err := m.SetGamma(-1.0)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, before, m.config)
|
||||
assert.Empty(t, m.updateTrigger)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetters_ValidValuesCommitAndTrigger(t *testing.T) {
|
||||
m := &Manager{
|
||||
config: DefaultConfig(),
|
||||
updateTrigger: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
err := m.SetTemperature(3000, 6000)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 3000, m.config.LowTemp)
|
||||
assert.Equal(t, 6000, m.config.HighTemp)
|
||||
assert.Len(t, m.updateTrigger, 1)
|
||||
}
|
||||
|
||||
func TestApplyGamma_SkipsUnchangedTempAndGamma(t *testing.T) {
|
||||
m := &Manager{config: DefaultConfig()}
|
||||
m.controlsInitialized = true
|
||||
|
||||
out := &outputState{
|
||||
id: 1,
|
||||
rampSize: 256,
|
||||
gammaControl: &wlr_gamma_control.ZwlrGammaControlV1{},
|
||||
lastTemp: 5000,
|
||||
lastGamma: m.config.Gamma,
|
||||
}
|
||||
m.outputs.Store(out.id, out)
|
||||
|
||||
m.applyGamma(5000)
|
||||
|
||||
assert.False(t, out.failed, "unchanged temp must not reach the compositor write path")
|
||||
assert.Equal(t, 5000, out.lastTemp)
|
||||
assert.Equal(t, uint32(256), out.rampSize)
|
||||
}
|
||||
|
||||
+1
-1
Submodule dank-qml-common updated: 7cc4564e59...3b06bd9372
Generated
+3
-3
@@ -3,11 +3,11 @@
|
||||
"dank-qml-common": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1785121997,
|
||||
"narHash": "sha256-/MslqFCpjxws8DZqipEKCCTBa0m/SCV3+v1NRT6EuvQ=",
|
||||
"lastModified": 1785445867,
|
||||
"narHash": "sha256-l9IRsLIVZ7bV+KMuTdCga89tGt4lpdMXhQR7f/2qoe4=",
|
||||
"owner": "AvengeMedia",
|
||||
"repo": "dank-qml-common",
|
||||
"rev": "7cc4564e5903a2955fe7da76969f20252cacf9bf",
|
||||
"rev": "3b06bd9372e18bc8086cc3958d4f677d57fbfdd8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
inherit version;
|
||||
pname = "dms-shell";
|
||||
src = ./core;
|
||||
vendorHash = "sha256-ZvaOPC92ZFRPqSyLJa2TA9OUKQ3QnWCIMxrnYLGnC58=";
|
||||
vendorHash = "sha256-pjaRyB6E2TZvVd5a4xcdGSRVr9Dg9wEG/5e+HdtZJCg=";
|
||||
|
||||
subPackages = [ "cmd/dms" ];
|
||||
|
||||
|
||||
@@ -271,3 +271,61 @@ function getConflictingBinds(keyCombo, currentAction, allBinds, modKey) {
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
function qtKeyFromName(name) {
|
||||
var n = (name || "").toUpperCase();
|
||||
if (n.length === 1 && n >= "A" && n <= "Z")
|
||||
return Qt.Key_A + (n.charCodeAt(0) - 65);
|
||||
if (n.length === 1 && n >= "0" && n <= "9")
|
||||
return Qt.Key_0 + (n.charCodeAt(0) - 48);
|
||||
if (n.length >= 2 && n[0] === "F") {
|
||||
var f = parseInt(n.slice(1), 10);
|
||||
if (f >= 1 && f <= 12)
|
||||
return Qt.Key_F1 + (f - 1);
|
||||
}
|
||||
var named = {
|
||||
"SPACE": Qt.Key_Space,
|
||||
"TAB": Qt.Key_Tab,
|
||||
"RETURN": Qt.Key_Return,
|
||||
"ENTER": Qt.Key_Enter,
|
||||
"BACKSPACE": Qt.Key_Backspace,
|
||||
"DELETE": Qt.Key_Delete,
|
||||
"HOME": Qt.Key_Home,
|
||||
"END": Qt.Key_End,
|
||||
"UP": Qt.Key_Up,
|
||||
"DOWN": Qt.Key_Down,
|
||||
"LEFT": Qt.Key_Left,
|
||||
"RIGHT": Qt.Key_Right
|
||||
};
|
||||
return named[n] || 0;
|
||||
}
|
||||
|
||||
function isModifierKey(qk) {
|
||||
return qk === Qt.Key_Control || qk === Qt.Key_Shift || qk === Qt.Key_Alt || qk === Qt.Key_Meta
|
||||
|| qk === Qt.Key_NumLock || qk === Qt.Key_CapsLock || qk === Qt.Key_ScrollLock;
|
||||
}
|
||||
|
||||
function eventMatchesCombo(event, combo) {
|
||||
if (!combo)
|
||||
return false;
|
||||
var parts = combo.split("+");
|
||||
var keyName = parts[parts.length - 1].trim().toUpperCase();
|
||||
var wantsShift = false;
|
||||
var hasCtrl = false;
|
||||
for (var i = 0; i < parts.length - 1; i++) {
|
||||
var mod = parts[i].trim().toLowerCase();
|
||||
if (mod === "shift")
|
||||
wantsShift = true;
|
||||
else if (mod === "ctrl" || mod === "control")
|
||||
hasCtrl = true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
if (hasCtrl && !(event.modifiers & Qt.ControlModifier))
|
||||
return false;
|
||||
if (event.modifiers & (Qt.AltModifier | Qt.MetaModifier))
|
||||
return false;
|
||||
if (((event.modifiers & Qt.ShiftModifier) !== 0) !== wantsShift)
|
||||
return false;
|
||||
return event.key === qtKeyFromName(keyName);
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ Singleton {
|
||||
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
|
||||
|
||||
property bool wallpaperCyclingEnabled: false
|
||||
property bool wallpaperCyclingRandom: false
|
||||
property string wallpaperCyclingMode: "interval"
|
||||
property int wallpaperCyclingInterval: 300
|
||||
property string wallpaperCyclingTime: "06:00"
|
||||
@@ -646,6 +647,11 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setWallpaperCyclingRandom(random) {
|
||||
wallpaperCyclingRandom = random;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setWallpaperCyclingMode(mode) {
|
||||
wallpaperCyclingMode = mode;
|
||||
saveSettings();
|
||||
@@ -692,6 +698,37 @@ Singleton {
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setMonitorCyclingRandom(screenName, random) {
|
||||
var screen = null;
|
||||
var screens = Quickshell.screens;
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
if (screens[i].name === screenName) {
|
||||
screen = screens[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!screen) {
|
||||
log.warn("Screen not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
|
||||
|
||||
var newSettings = {};
|
||||
for (var key in monitorCyclingSettings) {
|
||||
var isThisScreen = key === screen.name || (screen.model && key === screen.model);
|
||||
if (!isThisScreen) {
|
||||
newSettings[key] = monitorCyclingSettings[key];
|
||||
}
|
||||
}
|
||||
|
||||
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||
newSettings[identifier].random = random;
|
||||
monitorCyclingSettings = newSettings;
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
function setMonitorCyclingMode(screenName, mode) {
|
||||
var screen = null;
|
||||
var screens = Quickshell.screens;
|
||||
@@ -1322,6 +1359,7 @@ Singleton {
|
||||
function getMonitorCyclingSettings(screenName) {
|
||||
var defaults = {
|
||||
"enabled": false,
|
||||
"random": false,
|
||||
"mode": "interval",
|
||||
"interval": 300,
|
||||
"time": "06:00"
|
||||
|
||||
@@ -659,9 +659,6 @@ Singleton {
|
||||
readonly property string iconTheme: resolveIconTheme()
|
||||
property var availableIconThemes: ["System Default"]
|
||||
property string systemDefaultIconTheme: ""
|
||||
property bool qt5ctAvailable: false
|
||||
property bool qt6ctAvailable: false
|
||||
property bool gtkAvailable: false
|
||||
|
||||
property var cursorSettings: ({
|
||||
"theme": "System Default",
|
||||
@@ -798,8 +795,6 @@ Singleton {
|
||||
property int fadeToDpmsGracePeriod: 5
|
||||
property string launchPrefix: ""
|
||||
|
||||
property bool gtkThemingEnabled: false
|
||||
property bool qtThemingEnabled: false
|
||||
property bool syncModeWithPortal: true
|
||||
property bool terminalsAlwaysDark: false
|
||||
|
||||
@@ -922,6 +917,8 @@ Singleton {
|
||||
property bool lockPamInlineU2f: false
|
||||
property bool lockPamExternallyManaged: false
|
||||
property string lockU2fPamPath: ""
|
||||
property string lockScreenSecurityKeyShortcut: "Ctrl+Q"
|
||||
property bool lockScreenSecurityKeyShortcutEnabled: false
|
||||
property bool greeterPamExternallyManaged: false
|
||||
property string lockScreenInactiveColor: "#000000"
|
||||
property int lockScreenNotificationMode: 0
|
||||
@@ -1683,7 +1680,6 @@ Singleton {
|
||||
_hasLoaded = true;
|
||||
applyStoredTheme();
|
||||
updateCompositorCursor();
|
||||
Processes.detectQtTools();
|
||||
Qt.callLater(checkIconThemeDrift);
|
||||
|
||||
_checkSettingsWritable();
|
||||
@@ -2556,11 +2552,19 @@ Singleton {
|
||||
return edges;
|
||||
}
|
||||
|
||||
function frameEdgeInsetForSide(screen, side) {
|
||||
if (!frameEnabled || !screen)
|
||||
readonly property real frameBarContentGap: frameBarInsetPadding < 0 ? frameThickness : frameBarInsetPadding
|
||||
readonly property real frameBarContentGapExtra: Math.max(0, frameBarContentGap - frameThickness)
|
||||
|
||||
function frameEdgeReservation(screen, edge) {
|
||||
if (!screen)
|
||||
return 0;
|
||||
const edges = getActiveBarEdgesForScreen(screen);
|
||||
return edges.includes(side) ? frameBarSize : frameThickness;
|
||||
return getActiveBarEdgesForScreen(screen).includes(edge) ? frameBarSize : frameThickness;
|
||||
}
|
||||
|
||||
function frameEdgeInsetForSide(screen, side) {
|
||||
if (!frameEnabled)
|
||||
return 0;
|
||||
return frameEdgeReservation(screen, side);
|
||||
}
|
||||
|
||||
function setMatugenScheme(scheme) {
|
||||
|
||||
@@ -96,8 +96,6 @@ Singleton {
|
||||
}
|
||||
|
||||
property bool matugenAvailable: false
|
||||
property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false
|
||||
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
|
||||
property var workerRunning: false
|
||||
property var pendingThemeRequest: null
|
||||
|
||||
@@ -352,6 +350,8 @@ Singleton {
|
||||
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
|
||||
readonly property color floatingSurface: foregroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
||||
readonly property color floatingSurfaceHigh: foregroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
||||
readonly property color floatingWindowSurface: readableSurface
|
||||
readonly property color notepadWindowSurface: withAlpha(surfaceContainer, notepadTransparency)
|
||||
readonly property color nestedSurface: floatingSurfaceHigh
|
||||
readonly property color notificationFloatingSurface: notificationForegroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
||||
readonly property color notificationFloatingSurfaceHigh: notificationForegroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
||||
|
||||
@@ -1,31 +1,69 @@
|
||||
pragma Singleton
|
||||
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import QtQuick
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var activeTrayMenus: ({})
|
||||
property var _pendingMenuRequest: null
|
||||
|
||||
signal openTrayMenuRequested
|
||||
|
||||
function requestOpenMenu(itemId, screenName) {
|
||||
_pendingMenuRequest = {
|
||||
"itemId": itemId,
|
||||
"screenName": screenName
|
||||
};
|
||||
openTrayMenuRequested();
|
||||
}
|
||||
|
||||
// Every SystemTrayBar instance receives the signal; the claim ensures
|
||||
// exactly one opens the menu, preferring the requested screen
|
||||
function claimMenuRequest(instanceScreenName) {
|
||||
if (!_pendingMenuRequest)
|
||||
return null;
|
||||
if (_pendingMenuRequest.screenName && _pendingMenuRequest.screenName !== instanceScreenName)
|
||||
return null;
|
||||
const request = _pendingMenuRequest;
|
||||
_pendingMenuRequest = null;
|
||||
return request;
|
||||
}
|
||||
|
||||
function findTrayItem(itemId: string): var {
|
||||
if (!itemId)
|
||||
return null;
|
||||
|
||||
return SystemTray.items.values.find(item => {
|
||||
const id = item?.id || "";
|
||||
const title = item?.tooltipTitle || "";
|
||||
const fullKey = title ? `${id}::${title}` : id;
|
||||
return fullKey === itemId || id === itemId;
|
||||
});
|
||||
}
|
||||
|
||||
function registerMenu(screenName, menu) {
|
||||
if (!screenName || !menu) return
|
||||
const newMenus = Object.assign({}, activeTrayMenus)
|
||||
newMenus[screenName] = menu
|
||||
activeTrayMenus = newMenus
|
||||
if (!screenName || !menu)
|
||||
return;
|
||||
const newMenus = Object.assign({}, activeTrayMenus);
|
||||
newMenus[screenName] = menu;
|
||||
activeTrayMenus = newMenus;
|
||||
}
|
||||
|
||||
function unregisterMenu(screenName) {
|
||||
if (!screenName) return
|
||||
const newMenus = Object.assign({}, activeTrayMenus)
|
||||
delete newMenus[screenName]
|
||||
activeTrayMenus = newMenus
|
||||
if (!screenName)
|
||||
return;
|
||||
const newMenus = Object.assign({}, activeTrayMenus);
|
||||
delete newMenus[screenName];
|
||||
activeTrayMenus = newMenus;
|
||||
}
|
||||
|
||||
function closeAllMenus() {
|
||||
function closeHoverMenus() {
|
||||
for (const screenName in activeTrayMenus) {
|
||||
const menu = activeTrayMenus[screenName]
|
||||
if (!menu) continue
|
||||
if (!menu || menu.openedByHover !== true) continue
|
||||
if (typeof menu.close === "function") {
|
||||
menu.close()
|
||||
} else if (menu.showMenu !== undefined) {
|
||||
@@ -33,4 +71,17 @@ Singleton {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllMenus() {
|
||||
for (const screenName in activeTrayMenus) {
|
||||
const menu = activeTrayMenus[screenName];
|
||||
if (!menu)
|
||||
continue;
|
||||
if (typeof menu.close === "function") {
|
||||
menu.close();
|
||||
} else if (menu.showMenu !== undefined) {
|
||||
menu.showMenu = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -487,41 +487,10 @@ Singleton {
|
||||
return pamFprintDetected ? "probe_failed" : "missing_pam_support";
|
||||
}
|
||||
|
||||
// --- Qt tools detection ---
|
||||
|
||||
function detectQtTools() {
|
||||
qtToolsDetectionProcess.running = true;
|
||||
}
|
||||
|
||||
function checkPluginSettings() {
|
||||
pluginSettingsCheckProcess.running = true;
|
||||
}
|
||||
|
||||
property var qtToolsDetectionProcess: Process {
|
||||
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
||||
running: false
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (!settingsRoot)
|
||||
return;
|
||||
if (text && text.trim()) {
|
||||
const lines = text.trim().split("\n");
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith("qt5ct:")) {
|
||||
settingsRoot.qt5ctAvailable = line.split(":")[1] === "true";
|
||||
} else if (line.startsWith("qt6ct:")) {
|
||||
settingsRoot.qt6ctAvailable = line.split(":")[1] === "true";
|
||||
} else if (line.startsWith("gtk:")) {
|
||||
settingsRoot.gtkAvailable = line.split(":")[1] === "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: authApplyDebounce
|
||||
interval: 300
|
||||
|
||||
@@ -19,6 +19,7 @@ var SPEC = {
|
||||
includedTransitions: { def: ["fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"] },
|
||||
|
||||
wallpaperCyclingEnabled: { def: false },
|
||||
wallpaperCyclingRandom: { def: false },
|
||||
wallpaperCyclingMode: { def: "interval" },
|
||||
wallpaperCyclingInterval: { def: 300 },
|
||||
wallpaperCyclingTime: { def: "06:00" },
|
||||
|
||||
@@ -298,9 +298,6 @@ var SPEC = {
|
||||
lastAppliedIconTheme: { def: "" },
|
||||
availableIconThemes: { def: ["System Default"], persist: false },
|
||||
systemDefaultIconTheme: { def: "", persist: false },
|
||||
qt5ctAvailable: { def: false, persist: false },
|
||||
qt6ctAvailable: { def: false, persist: false },
|
||||
gtkAvailable: { def: false, persist: false },
|
||||
|
||||
cursorSettings: { def: { theme: "System Default", size: 24, niri: { hideWhenTyping: false, hideAfterInactiveMs: 0 }, hyprland: { hideOnKeyPress: false, hideOnTouch: false, inactiveTimeout: 0 }, dwl: { cursorHideTimeout: 0 }, mango: { cursorHideTimeout: 0 } }, onChange: "updateCompositorCursor" },
|
||||
availableCursorThemes: { def: ["System Default"], persist: false },
|
||||
@@ -374,8 +371,6 @@ var SPEC = {
|
||||
fadeToDpmsGracePeriod: { def: 5 },
|
||||
launchPrefix: { def: "" },
|
||||
|
||||
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
||||
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
||||
syncModeWithPortal: { def: true },
|
||||
terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
|
||||
|
||||
@@ -473,6 +468,8 @@ var SPEC = {
|
||||
enableU2f: { def: false, onChange: "scheduleAuthApply" },
|
||||
u2fMode: { def: "or" },
|
||||
lockPamPath: { def: "" },
|
||||
lockScreenSecurityKeyShortcut: { def: "Ctrl+Q" },
|
||||
lockScreenSecurityKeyShortcutEnabled: { def: false },
|
||||
lockPamInlineFprint: { def: false },
|
||||
lockPamInlineU2f: { def: false },
|
||||
lockPamExternallyManaged: { def: false },
|
||||
|
||||
@@ -445,6 +445,23 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: qrGeneratorModalLoader
|
||||
active: false
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.qrGeneratorModalLoader = qrGeneratorModalLoader;
|
||||
}
|
||||
|
||||
QRGeneratorModal {
|
||||
id: qrGeneratorModalItem
|
||||
|
||||
Component.onCompleted: {
|
||||
PopoutService.qrGeneratorModal = qrGeneratorModalItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: polkitAuthModalLoader
|
||||
active: false
|
||||
@@ -998,6 +1015,9 @@ Item {
|
||||
case "reboot":
|
||||
SessionService.reboot();
|
||||
break;
|
||||
case "softreboot":
|
||||
SessionService.softReboot();
|
||||
break;
|
||||
case "poweroff":
|
||||
SessionService.poweroff();
|
||||
break;
|
||||
|
||||
+31
-33
@@ -25,32 +25,30 @@ Item {
|
||||
required property var windowRuleModalLoader
|
||||
|
||||
function getPreferredBar(refPropertyName) {
|
||||
if (!root.dankBarRepeater || root.dankBarRepeater.count === 0)
|
||||
return null;
|
||||
|
||||
const focusedScreenName = BarWidgetService.getFocusedScreenName();
|
||||
|
||||
const loaders = Array.from({
|
||||
length: root.dankBarRepeater.count
|
||||
}, (_, i) => root.dankBarRepeater.itemAt(i));
|
||||
const bars = [];
|
||||
if (root.dankBarRepeater) {
|
||||
for (let i = 0; i < root.dankBarRepeater.count; i++)
|
||||
bars.push(...(root.dankBarRepeater.itemAt(i)?.item?.barVariants?.instances || []));
|
||||
}
|
||||
const frameBars = BarWidgetService.frameHostedBars;
|
||||
for (const screenName in frameBars)
|
||||
bars.push(frameBars[screenName]);
|
||||
|
||||
let currentBar = null;
|
||||
for (const bar of bars) {
|
||||
if (!bar)
|
||||
continue;
|
||||
|
||||
for (const loader of loaders) {
|
||||
const instances = loader?.item?.barVariants?.instances || [];
|
||||
for (const bar of instances) {
|
||||
if (!bar)
|
||||
continue;
|
||||
const onFocusedScreen = focusedScreenName && bar.modelData?.name === focusedScreenName;
|
||||
const hasRef = !refPropertyName || !!bar[refPropertyName];
|
||||
|
||||
const onFocusedScreen = focusedScreenName && bar.modelData?.name === focusedScreenName;
|
||||
const hasRef = !refPropertyName || !!bar[refPropertyName];
|
||||
if (hasRef) {
|
||||
currentBar = bar;
|
||||
|
||||
if (hasRef) {
|
||||
currentBar = bar;
|
||||
|
||||
if (onFocusedScreen)
|
||||
break;
|
||||
}
|
||||
if (onFocusedScreen)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2025,18 +2023,6 @@ Item {
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function findTrayItem(itemId: string): var {
|
||||
if (!itemId)
|
||||
return null;
|
||||
|
||||
return SystemTray.items.values.find(item => {
|
||||
const id = item?.id || "";
|
||||
const title = item?.tooltipTitle || "";
|
||||
const fullKey = title ? `${id}::${title}` : id;
|
||||
return fullKey === itemId || id === itemId;
|
||||
});
|
||||
}
|
||||
|
||||
function list(): string {
|
||||
const items = SystemTray.items.values;
|
||||
if (items.length === 0)
|
||||
@@ -2052,7 +2038,7 @@ Item {
|
||||
}
|
||||
|
||||
function activate(itemId: string): string {
|
||||
const item = findTrayItem(itemId);
|
||||
const item = TrayMenuManager.findTrayItem(itemId);
|
||||
if (!item)
|
||||
return `ERROR: Tray item not found: ${itemId}`;
|
||||
|
||||
@@ -2060,8 +2046,20 @@ Item {
|
||||
return `SUCCESS: Activated ${itemId}`;
|
||||
}
|
||||
|
||||
function menu(itemId: string): string {
|
||||
const item = TrayMenuManager.findTrayItem(itemId);
|
||||
if (!item)
|
||||
return `ERROR: Tray item not found: ${itemId}`;
|
||||
|
||||
if (!item.hasMenu)
|
||||
return `ERROR: Tray item has no menu: ${itemId}`;
|
||||
|
||||
TrayMenuManager.requestOpenMenu(itemId, BarWidgetService.getFocusedScreenName());
|
||||
return `SUCCESS: Requested menu ${itemId}`;
|
||||
}
|
||||
|
||||
function status(itemId: string): string {
|
||||
const item = findTrayItem(itemId);
|
||||
const item = TrayMenuManager.findTrayItem(itemId);
|
||||
if (!item)
|
||||
return `ERROR: Tray item not found: ${itemId}`;
|
||||
|
||||
|
||||
@@ -819,16 +819,24 @@ Item {
|
||||
}
|
||||
|
||||
if (isCategoryFiltered) {
|
||||
var rawApps = AppSearchService.getAppsInCategory(appCategory);
|
||||
for (var i = 0; i < rawApps.length; i++) {
|
||||
allItems.push(getOrTransformApp(rawApps[i]));
|
||||
}
|
||||
// Also include core apps (DMS Settings etc.) that match this category
|
||||
var allCoreApps = AppSearchService.getCoreApps("");
|
||||
for (var i = 0; i < allCoreApps.length; i++) {
|
||||
var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]);
|
||||
if (coreAppCats.indexOf(appCategory) !== -1)
|
||||
allItems.push(transformCoreApp(allCoreApps[i]));
|
||||
var categoryPluginId = AppSearchService.getPluginIdForCategory(appCategory);
|
||||
if (categoryPluginId) {
|
||||
var pluginCategoryItems = getPluginItems(categoryPluginId, "");
|
||||
for (var i = 0; i < pluginCategoryItems.length; i++) {
|
||||
allItems.push(pluginCategoryItems[i]);
|
||||
}
|
||||
} else {
|
||||
var rawApps = AppSearchService.getAppsInCategory(appCategory);
|
||||
for (var i = 0; i < rawApps.length; i++) {
|
||||
allItems.push(getOrTransformApp(rawApps[i]));
|
||||
}
|
||||
// Also include core apps (DMS Settings etc.) that match this category
|
||||
var allCoreApps = AppSearchService.getCoreApps("");
|
||||
for (var i = 0; i < allCoreApps.length; i++) {
|
||||
var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]);
|
||||
if (coreAppCats.indexOf(appCategory) !== -1)
|
||||
allItems.push(transformCoreApp(allCoreApps[i]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var apps = searchApps(searchQuery);
|
||||
@@ -839,7 +847,7 @@ Item {
|
||||
|
||||
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
|
||||
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
|
||||
var newSections = Scorer.groupBySection(scoredItems, sectionDefinitions, sortAlpha, searchQuery ? 50 : 500);
|
||||
var newSections = Scorer.groupBySection(scoredItems, buildDynamicSectionDefs(allItems), sortAlpha, searchQuery ? 50 : 500);
|
||||
|
||||
for (var sid in collapsedSections) {
|
||||
for (var i = 0; i < newSections.length; i++) {
|
||||
|
||||
@@ -64,6 +64,10 @@ DankModal {
|
||||
NotificationService.dismissAllPopups();
|
||||
}
|
||||
|
||||
function dismissLastNotification() {
|
||||
NotificationService.dismissLastNotification();
|
||||
}
|
||||
|
||||
modalWidth: Math.min(500, screenWidth - 48)
|
||||
modalHeight: Math.min(700, screenHeight * 0.85)
|
||||
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
@@ -190,6 +194,11 @@ DankModal {
|
||||
return "NOTIFICATION_MODAL_DISMISS_ALL_POPUPS_SUCCESS";
|
||||
}
|
||||
|
||||
function dismiss(): string {
|
||||
notificationModal.dismissLastNotification();
|
||||
return "NOTIFICATION_DISMISS_SUCCESS";
|
||||
}
|
||||
|
||||
target: "notifications"
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,17 @@ FocusScope {
|
||||
property bool awaitingFprintForPassword: false
|
||||
property var windowControls: null
|
||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||
readonly property bool _blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
||||
readonly property real _fieldAlpha: {
|
||||
if (Theme.transparentBlurLayers)
|
||||
return 0.28;
|
||||
if (Theme.blurForegroundLayers)
|
||||
return Math.max(Theme.popupTransparency, 0.62);
|
||||
return Theme.popupTransparency;
|
||||
}
|
||||
readonly property color _fieldColor: Theme.withAlpha(Theme.surfaceContainerHigh, _fieldAlpha)
|
||||
readonly property color _fieldBorderColor: Theme.withAlpha(Theme.outline, _blurActive ? 0.16 : Theme.layerOutlineOpacity)
|
||||
readonly property color _fieldFocusedBorderColor: Theme.withAlpha(Theme.primary, _blurActive ? 0.72 : 1.0)
|
||||
|
||||
property string polkitEtcPamText: ""
|
||||
property string polkitLibPamText: ""
|
||||
@@ -202,7 +213,7 @@ FocusScope {
|
||||
StyledText {
|
||||
text: root.currentFlow?.message ?? ""
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
@@ -272,9 +283,10 @@ FocusScope {
|
||||
|
||||
width: parent.width
|
||||
height: root.inputFieldHeight
|
||||
backgroundColor: Theme.surfaceHover
|
||||
normalBorderColor: Theme.outlineStrong
|
||||
focusedBorderColor: Theme.primary
|
||||
cornerRadius: Theme.cornerRadius
|
||||
backgroundColor: root._fieldColor
|
||||
normalBorderColor: root._fieldBorderColor
|
||||
focusedBorderColor: root._fieldFocusedBorderColor
|
||||
borderWidth: 1
|
||||
focusedBorderWidth: 2
|
||||
leftIconName: root.polkitPamHasFprint ? "fingerprint" : ""
|
||||
@@ -352,7 +364,7 @@ FocusScope {
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Authenticate")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
color: Theme.primaryText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ FloatingWindow {
|
||||
title: I18n.tr("Authentication")
|
||||
minimumSize: Qt.size(460, 220)
|
||||
maximumSize: Qt.size(460, 220)
|
||||
color: Theme.surfaceContainer
|
||||
color: Theme.floatingWindowSurface
|
||||
visible: false
|
||||
|
||||
onClosed: hide()
|
||||
@@ -53,6 +53,25 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: root
|
||||
blurX: 0
|
||||
blurY: 0
|
||||
blurWidth: root.visible ? root.width : 0
|
||||
blurHeight: root.visible ? root.height : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: BlurService.borderColor
|
||||
border.width: BlurService.borderWidth
|
||||
antialiasing: true
|
||||
z: 100
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -175,6 +175,8 @@ DankModal {
|
||||
visibleActions = allActions.filter(action => {
|
||||
if (action === "hibernate" && !SessionService.hibernateSupported)
|
||||
return false;
|
||||
if (action === "softreboot" && !SessionService.softRebootSupported)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -223,6 +225,12 @@ DankModal {
|
||||
"label": I18n.tr("Reboot"),
|
||||
"key": "R"
|
||||
};
|
||||
case "softreboot":
|
||||
return {
|
||||
"icon": "autorenew",
|
||||
"label": I18n.tr("Soft Reboot"),
|
||||
"key": "B"
|
||||
};
|
||||
case "logout":
|
||||
return {
|
||||
"icon": "logout",
|
||||
@@ -370,7 +378,7 @@ DankModal {
|
||||
|
||||
function handleListNavigation(event, isPressed) {
|
||||
if (!isPressed) {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_B || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
||||
cancelHold();
|
||||
event.accepted = true;
|
||||
}
|
||||
@@ -429,6 +437,12 @@ DankModal {
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_B:
|
||||
if (visibleActions.includes("softreboot")) {
|
||||
startHold("softreboot", visibleActions.indexOf("softreboot"));
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_X:
|
||||
if (visibleActions.includes("logout")) {
|
||||
startHold("logout", visibleActions.indexOf("logout"));
|
||||
@@ -464,7 +478,7 @@ DankModal {
|
||||
|
||||
function handleGridNavigation(event, isPressed) {
|
||||
if (!isPressed) {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_B || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
||||
cancelHold();
|
||||
event.accepted = true;
|
||||
}
|
||||
@@ -539,6 +553,12 @@ DankModal {
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_B:
|
||||
if (visibleActions.includes("softreboot")) {
|
||||
startHold("softreboot", visibleActions.indexOf("softreboot"));
|
||||
event.accepted = true;
|
||||
}
|
||||
break;
|
||||
case Qt.Key_X:
|
||||
if (visibleActions.includes("logout")) {
|
||||
startHold("logout", visibleActions.indexOf("logout"));
|
||||
@@ -597,7 +617,7 @@ DankModal {
|
||||
|
||||
readonly property var actionData: root.getActionData(modelData)
|
||||
readonly property bool isSelected: root.selectedIndex === index
|
||||
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
|
||||
readonly property bool showWarning: modelData === "reboot" || modelData === "softreboot" || modelData === "poweroff"
|
||||
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
|
||||
|
||||
width: (root.modalWidth - Theme.spacingL * 2 - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
|
||||
@@ -627,7 +647,7 @@ DankModal {
|
||||
color: {
|
||||
if (gridButtonRect.modelData === "poweroff")
|
||||
return Theme.errorSelected;
|
||||
if (gridButtonRect.modelData === "reboot")
|
||||
if (gridButtonRect.modelData === "reboot" || gridButtonRect.modelData === "softreboot")
|
||||
return Theme.withAlpha(Theme.warning, 0.3);
|
||||
return Theme.primarySelected;
|
||||
}
|
||||
@@ -722,7 +742,7 @@ DankModal {
|
||||
|
||||
readonly property var actionData: root.getActionData(modelData)
|
||||
readonly property bool isSelected: root.selectedIndex === index
|
||||
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
|
||||
readonly property bool showWarning: modelData === "reboot" || modelData === "softreboot" || modelData === "poweroff"
|
||||
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
|
||||
|
||||
width: parent.width
|
||||
@@ -752,7 +772,7 @@ DankModal {
|
||||
color: {
|
||||
if (listButtonRect.modelData === "poweroff")
|
||||
return Theme.errorSelected;
|
||||
if (listButtonRect.modelData === "reboot")
|
||||
if (listButtonRect.modelData === "reboot" || listButtonRect.modelData === "softreboot")
|
||||
return Theme.withAlpha(Theme.warning, 0.3);
|
||||
return Theme.primarySelected;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
visible: false
|
||||
layerNamespace: "dms:qr-generator"
|
||||
|
||||
property bool disablePopupTransparency: true
|
||||
property bool generating: false
|
||||
property string themedQrCodePath: ""
|
||||
property string normalQrCodePath: ""
|
||||
property string initialText: ""
|
||||
property string _pendingPayload: ""
|
||||
property string _generatingPayload: ""
|
||||
property string _displayedPayload: ""
|
||||
modalWidth: 420
|
||||
modalHeight: 440
|
||||
onBackgroundClicked: hide()
|
||||
onOpened: {
|
||||
Qt.callLater(() => {
|
||||
modalFocusScope.forceActiveFocus();
|
||||
const item = contentLoader.item;
|
||||
if (!item)
|
||||
return;
|
||||
item.saveBrowserLoader = saveBrowserLoader;
|
||||
if (item.textInput) {
|
||||
item.textInput.text = initialText;
|
||||
item.textInput.forceActiveFocus();
|
||||
}
|
||||
if (initialText.length > 0) {
|
||||
_pendingPayload = initialText;
|
||||
generateQR(initialText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function show(text) {
|
||||
generating = false;
|
||||
initialText = text || "";
|
||||
_pendingPayload = "";
|
||||
_generatingPayload = "";
|
||||
_displayedPayload = "";
|
||||
themedQrCodePath = "";
|
||||
normalQrCodePath = "";
|
||||
open();
|
||||
}
|
||||
|
||||
function hide() {
|
||||
deleteQrCodeFiles(themedQrCodePath, normalQrCodePath);
|
||||
themedQrCodePath = "";
|
||||
normalQrCodePath = "";
|
||||
close();
|
||||
}
|
||||
|
||||
function deleteQrCodeFiles(themed, normal) {
|
||||
if (themed.length > 0)
|
||||
DMSService.sendRequest("network.delete-qrcode", {
|
||||
path: themed
|
||||
});
|
||||
if (normal.length > 0)
|
||||
DMSService.sendRequest("network.delete-qrcode", {
|
||||
path: normal
|
||||
});
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: genTimer
|
||||
interval: 200
|
||||
repeat: false
|
||||
onTriggered: root.generateQR(root._pendingPayload)
|
||||
}
|
||||
|
||||
function generateQR(text) {
|
||||
const trimmed = (text || "").trim();
|
||||
if (trimmed.length === 0 || trimmed === _displayedPayload || generating)
|
||||
return;
|
||||
|
||||
_generatingPayload = trimmed;
|
||||
generating = true;
|
||||
|
||||
DMSService.sendRequest("network.generate-qrcode", {
|
||||
text: trimmed
|
||||
}, response => {
|
||||
root.generating = false;
|
||||
if (response.error) {
|
||||
ToastService.showError(I18n.tr("Failed to generate QR code: %1").arg(JSON.stringify(response.error)));
|
||||
return;
|
||||
}
|
||||
if (!response.result)
|
||||
return;
|
||||
if (root._generatingPayload !== root._pendingPayload.trim()) {
|
||||
root.deleteQrCodeFiles(response.result[0], response.result[1]);
|
||||
genTimer.restart();
|
||||
return;
|
||||
}
|
||||
|
||||
const oldThemed = root.themedQrCodePath;
|
||||
const oldNormal = root.normalQrCodePath;
|
||||
root._displayedPayload = root._generatingPayload;
|
||||
root.themedQrCodePath = response.result[0];
|
||||
root.normalQrCodePath = response.result[1];
|
||||
root.deleteQrCodeFiles(oldThemed, oldNormal);
|
||||
});
|
||||
}
|
||||
|
||||
function onTextChanged(text) {
|
||||
_pendingPayload = text;
|
||||
const trimmed = text.trim();
|
||||
if (trimmed.length === 0 || trimmed === _displayedPayload) {
|
||||
genTimer.stop();
|
||||
return;
|
||||
}
|
||||
genTimer.restart();
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: saveBrowserLoader
|
||||
active: false
|
||||
|
||||
FileBrowserSurfaceModal {
|
||||
id: saveBrowser
|
||||
|
||||
browserTitle: I18n.tr("Save QR Code")
|
||||
browserIcon: "qr_code"
|
||||
browserType: "default"
|
||||
fileExtensions: ["*.png"]
|
||||
allowStacking: true
|
||||
saveMode: true
|
||||
defaultFileName: "qrcode.png"
|
||||
onFileSelected: path => {
|
||||
const cleanPath = decodeURI(path.toString().replace(/^file:\/\//, ''));
|
||||
copyQrCodeProcess.exec(["cp", "-f", root.normalQrCodePath, cleanPath]);
|
||||
}
|
||||
|
||||
Process {
|
||||
id: copyQrCodeProcess
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
saveBrowser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
id: contentItem
|
||||
|
||||
property alias textInput: textInput
|
||||
property var saveBrowserLoader: null
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingL
|
||||
|
||||
RowLayout {
|
||||
id: modalTitle
|
||||
width: parent.width
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("QR Generator")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.hide()
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
|
||||
DankTextField {
|
||||
id: textInput
|
||||
width: parent.width
|
||||
placeholderText: I18n.tr("Enter text to encode")
|
||||
showClearButton: true
|
||||
focus: true
|
||||
onTextEdited: root.onTextChanged(text)
|
||||
Keys.onEscapePressed: event => {
|
||||
event.accepted = true;
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: qrContainer
|
||||
height: Math.min(parent.height - parent.spacing - modalTitle.height - textInput.height - parent.spacing * 4, 260)
|
||||
width: height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
opacity: 1
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 80
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: qrCodeImg
|
||||
anchors.fill: parent
|
||||
source: root.themedQrCodePath
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: false
|
||||
visible: false
|
||||
|
||||
onSourceChanged: qrContainer.opacity = 0
|
||||
onStatusChanged: {
|
||||
if (status === Image.Ready)
|
||||
qrContainer.opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
MultiEffect {
|
||||
source: qrCodeImg
|
||||
anchors.fill: qrCodeImg
|
||||
colorization: 1.0
|
||||
colorizationColor: Theme.primary
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
width: parent.width
|
||||
visible: root.themedQrCodePath.length > 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Save")
|
||||
iconName: "save"
|
||||
backgroundColor: Theme.surfaceContainer
|
||||
textColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
contentItem.saveBrowserLoader.active = true;
|
||||
if (contentItem.saveBrowserLoader.item) {
|
||||
contentItem.saveBrowserLoader.item.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
text: I18n.tr("Copy")
|
||||
iconName: "content_copy"
|
||||
backgroundColor: Theme.primary
|
||||
textColor: Theme.onPrimary
|
||||
onClicked: {
|
||||
if (root.normalQrCodePath.length > 0)
|
||||
DMSService.sendRequest("clipboard.copyFile", {
|
||||
filePath: root.normalQrCodePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import qs.Widgets
|
||||
FloatingWindow {
|
||||
id: settingsModal
|
||||
|
||||
property bool disablePopupTransparency: true
|
||||
property var profileBrowser: profileBrowserLoader.item
|
||||
property var wallpaperBrowser: wallpaperBrowserLoader.item
|
||||
|
||||
@@ -95,7 +96,7 @@ FloatingWindow {
|
||||
minimumSize: Qt.size(500, 400)
|
||||
implicitWidth: 900
|
||||
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
||||
color: Theme.surfaceContainer
|
||||
color: Theme.floatingWindowSurface
|
||||
visible: false
|
||||
|
||||
onClosed: hide()
|
||||
@@ -120,6 +121,25 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: settingsModal
|
||||
blurX: 0
|
||||
blurY: 0
|
||||
blurWidth: settingsModal.visible ? settingsModal.width : 0
|
||||
blurHeight: settingsModal.visible ? settingsModal.height : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: BlurService.borderColor
|
||||
border.width: BlurService.borderWidth
|
||||
antialiasing: true
|
||||
z: 100
|
||||
}
|
||||
|
||||
Loader {
|
||||
active: settingsModal.visible
|
||||
sourceComponent: Component {
|
||||
@@ -180,8 +200,6 @@ FloatingWindow {
|
||||
FocusScope {
|
||||
id: contentFocusScope
|
||||
|
||||
property bool disablePopupTransparency: true
|
||||
|
||||
LayoutMirroring.enabled: I18n.isRtl
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
@@ -203,12 +221,6 @@ FloatingWindow {
|
||||
onDoubleClicked: windowControls.tryToggleMaximize()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.surfaceContainer
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingL
|
||||
|
||||
@@ -641,7 +641,7 @@ Rectangle {
|
||||
implicitWidth: __calculatedWidth
|
||||
width: __calculatedWidth
|
||||
height: parent.height
|
||||
color: Theme.surfaceContainer
|
||||
color: "transparent"
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -31,6 +31,7 @@ DankModal {
|
||||
property string promptToken: ""
|
||||
property string promptReason: ""
|
||||
property var promptFields: []
|
||||
property var promptHints: []
|
||||
property string promptSetting: ""
|
||||
|
||||
property bool isVpnPrompt: false
|
||||
@@ -40,17 +41,21 @@ DankModal {
|
||||
property var fieldsInfo: []
|
||||
property var secretValues: ({})
|
||||
|
||||
readonly property bool isCertificateChangedPrompt: promptReason === "server-certificate-changed"
|
||||
readonly property bool isCertificatePrompt: promptReason === "server-certificate" || isCertificateChangedPrompt
|
||||
readonly property string serverCertificateFingerprint: promptHints.length > 0 ? promptHints[0] : ""
|
||||
readonly property bool showUsernameField: requiresEnterprise && !isVpnPrompt && fieldsInfo.length === 0
|
||||
readonly property bool showPasswordField: fieldsInfo.length === 0
|
||||
readonly property bool showPasswordField: fieldsInfo.length === 0 && !isCertificatePrompt
|
||||
readonly property bool showAnonField: requiresEnterprise && !isVpnPrompt
|
||||
readonly property bool showDomainField: requiresEnterprise && !isVpnPrompt
|
||||
readonly property bool showSavePasswordCheckbox: (isVpnPrompt || fieldsInfo.length > 0) && promptReason !== "pkcs11"
|
||||
readonly property bool showSavePasswordCheckbox: (isVpnPrompt || fieldsInfo.length > 0) && promptReason !== "pkcs11" && !isCertificatePrompt
|
||||
|
||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||
readonly property int inputFieldWithSpacing: inputFieldHeight + Theme.spacingM
|
||||
readonly property int checkboxRowHeight: Theme.fontSizeMedium + Theme.spacingS
|
||||
readonly property int headerHeight: Theme.fontSizeLarge + Theme.fontSizeMedium + Theme.spacingM * 2
|
||||
readonly property int buttonRowHeight: 36 + Theme.spacingM
|
||||
readonly property int certificateWarningHeight: certificateWarningColumn.implicitHeight + Theme.spacingM * 2
|
||||
|
||||
property int calculatedHeight: {
|
||||
let h = headerHeight + buttonRowHeight + Theme.spacingL * 2;
|
||||
@@ -67,10 +72,16 @@ DankModal {
|
||||
h += inputFieldWithSpacing;
|
||||
if (showSavePasswordCheckbox)
|
||||
h += checkboxRowHeight;
|
||||
if (isCertificatePrompt)
|
||||
h += certificateWarningHeight + Theme.spacingM;
|
||||
return h;
|
||||
}
|
||||
|
||||
function focusFirstField() {
|
||||
if (isCertificatePrompt) {
|
||||
connectButton.forceActiveFocus();
|
||||
return;
|
||||
}
|
||||
if (fieldsInfo.length > 0) {
|
||||
if (dynamicFieldsRepeater.count > 0) {
|
||||
const firstItem = dynamicFieldsRepeater.itemAt(0);
|
||||
@@ -101,6 +112,7 @@ DankModal {
|
||||
promptToken = "";
|
||||
promptReason = "";
|
||||
promptFields = [];
|
||||
promptHints = [];
|
||||
promptSetting = "";
|
||||
isVpnPrompt = false;
|
||||
connectionName = "";
|
||||
@@ -127,6 +139,7 @@ DankModal {
|
||||
promptToken = "";
|
||||
promptReason = "";
|
||||
promptFields = [];
|
||||
promptHints = [];
|
||||
promptSetting = "";
|
||||
isVpnPrompt = false;
|
||||
connectionName = "";
|
||||
@@ -145,6 +158,7 @@ DankModal {
|
||||
promptToken = token;
|
||||
promptReason = reason;
|
||||
promptFields = fields || [];
|
||||
promptHints = hints || [];
|
||||
promptSetting = setting || "802-11-wireless-security";
|
||||
connectionType = connType || "802-11-wireless";
|
||||
connectionName = connName || ssid || "";
|
||||
@@ -324,6 +338,8 @@ DankModal {
|
||||
text: {
|
||||
if (promptReason === "pkcs11")
|
||||
return I18n.tr("Smartcard Authentication");
|
||||
if (isCertificatePrompt)
|
||||
return I18n.tr("Untrusted VPN certificate", "Title for VPN server certificate trust confirmation");
|
||||
if (isVpnPrompt)
|
||||
return I18n.tr("Connect to VPN");
|
||||
if (isHiddenNetwork)
|
||||
@@ -343,6 +359,8 @@ DankModal {
|
||||
text: {
|
||||
if (promptReason === "pkcs11")
|
||||
return I18n.tr("Enter PIN for ") + wifiPasswordSSID;
|
||||
if (isCertificatePrompt)
|
||||
return wifiPasswordSSID;
|
||||
if (fieldsInfo.length > 0)
|
||||
return I18n.tr("Enter credentials for ") + wifiPasswordSSID;
|
||||
if (isVpnPrompt)
|
||||
@@ -383,6 +401,45 @@ DankModal {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: certificateWarningBox
|
||||
|
||||
readonly property color warningTone: isCertificateChangedPrompt ? Theme.error : Theme.warning
|
||||
|
||||
width: parent.width
|
||||
height: certificateWarningHeight
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(warningTone, 0.12)
|
||||
border.color: Theme.withAlpha(warningTone, 0.5)
|
||||
border.width: 1
|
||||
visible: isCertificatePrompt
|
||||
|
||||
Column {
|
||||
id: certificateWarningColumn
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: isCertificateChangedPrompt ? I18n.tr("The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.", "Warning shown when a trusted VPN server certificate no longer matches") : I18n.tr("Only continue if you recognize this server certificate fingerprint.", "Warning shown before trusting an unverified VPN server certificate")
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: serverCertificateFingerprint
|
||||
wrapMode: Text.WrapAnywhere
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: certificateWarningBox.warningTone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: inputFieldHeight
|
||||
@@ -690,10 +747,15 @@ DankModal {
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: connectButton
|
||||
|
||||
width: Math.max(80, connectText.contentWidth + Theme.spacingM * 2)
|
||||
height: 36
|
||||
radius: Theme.cornerRadius
|
||||
color: connectArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||
border.color: activeFocus ? Theme.surfaceText : "transparent"
|
||||
border.width: activeFocus ? 2 : 0
|
||||
activeFocusOnTab: true
|
||||
enabled: {
|
||||
if (fieldsInfo.length > 0) {
|
||||
for (var i = 0; i < fieldsInfo.length; i++) {
|
||||
@@ -705,6 +767,8 @@ DankModal {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (isCertificatePrompt)
|
||||
return serverCertificateFingerprint.length > 0;
|
||||
if (isVpnPrompt)
|
||||
return passwordInput.text.length > 0;
|
||||
if (isHiddenNetwork)
|
||||
@@ -716,7 +780,7 @@ DankModal {
|
||||
StyledText {
|
||||
id: connectText
|
||||
anchors.centerIn: parent
|
||||
text: I18n.tr("Connect")
|
||||
text: isCertificatePrompt ? I18n.tr("Trust", "Button that approves a VPN server certificate fingerprint") : I18n.tr("Connect")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.background
|
||||
font.weight: Font.Medium
|
||||
@@ -731,6 +795,22 @@ DankModal {
|
||||
onClicked: submitCredentialsAndClose()
|
||||
}
|
||||
|
||||
Keys.onReturnPressed: event => {
|
||||
if (enabled)
|
||||
submitCredentialsAndClose();
|
||||
event.accepted = true;
|
||||
}
|
||||
Keys.onEnterPressed: event => {
|
||||
if (enabled)
|
||||
submitCredentialsAndClose();
|
||||
event.accepted = true;
|
||||
}
|
||||
Keys.onSpacePressed: event => {
|
||||
if (enabled)
|
||||
submitCredentialsAndClose();
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
Behavior on color {
|
||||
ColorAnimation {
|
||||
duration: Theme.shortDuration
|
||||
|
||||
@@ -469,6 +469,8 @@ PluginComponent {
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: peerMouseArea
|
||||
|
||||
z: -1
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
|
||||
@@ -41,7 +41,11 @@ Rectangle {
|
||||
property bool hasWifiAvailable: (NetworkService.wifiDevices?.length ?? 0) > 0
|
||||
property bool hasBothConnectionTypes: hasEthernetAvailable && hasWifiAvailable
|
||||
property int maxPinnedNetworks: 3
|
||||
readonly property int hotspotContentHeight: currentPreferenceIndex === 1 && NetworkService.hotspotAvailable ? 56 + Theme.spacingS : 0
|
||||
// Hosting on the only wifi adapter with no ethernet uplink just drops connectivity,
|
||||
// so the hotspot row only shows where sharing can actually work (or is already on).
|
||||
readonly property bool hotspotRelevant: NetworkService.hotspotEnabled || NetworkService.hotspotActivating || NetworkService.hotspotBusy || NetworkService.ethernetConnected || (NetworkService.wifiDevices?.length ?? 0) > 1
|
||||
readonly property bool showHotspotRow: currentPreferenceIndex === 1 && NetworkService.hotspotAvailable && hotspotRelevant
|
||||
readonly property int hotspotContentHeight: showHotspotRow ? 56 + Theme.spacingS : 0
|
||||
|
||||
property var hotspotStartConfirm: ConfirmModal {}
|
||||
|
||||
@@ -184,7 +188,7 @@ Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacingM
|
||||
anchors.topMargin: Theme.spacingM
|
||||
visible: currentPreferenceIndex === 1 && NetworkService.hotspotAvailable
|
||||
visible: root.showHotspotRow
|
||||
height: visible ? 56 : 0
|
||||
|
||||
Rectangle {
|
||||
|
||||
@@ -201,7 +201,16 @@ Item {
|
||||
direction: root.effectiveShadowDirection
|
||||
fallbackOffset: 4
|
||||
targetRadius: root.rt
|
||||
targetColor: barWindow._bgColor
|
||||
// wing-side body corners are square where the gothic fillets attach;
|
||||
// rounding the shadow there leaves a shadow-filled notch under the
|
||||
// translucent bar fill (#2975)
|
||||
topLeftRadius: root.gothEnabled && (root.isBottom || root.isRight) ? 0 : root.rt
|
||||
topRightRadius: root.gothEnabled && (root.isBottom || root.isLeft) ? 0 : root.rt
|
||||
bottomLeftRadius: root.gothEnabled && (root.isTop || root.isRight) ? 0 : root.rt
|
||||
bottomRightRadius: root.gothEnabled && (root.isTop || root.isLeft) ? 0 : root.rt
|
||||
// barShape below is the sole painter of the bar; a fill here doubles
|
||||
// the alpha of translucent bars while the wings stay single-painted
|
||||
targetColor: "transparent"
|
||||
|
||||
shadowBlurPx: root.shadowBlurPx
|
||||
shadowOffsetX: root.shadowOffsetX
|
||||
|
||||
@@ -38,11 +38,10 @@ Item {
|
||||
readonly property real _barInsetPaddingRaw: SettingsData.barInsetPaddingSyncAll ? SettingsData.barInsetPaddingShared : (barConfig?.barInsetPadding ?? -1)
|
||||
readonly property real _barInsetPaddingAuto: _barIsVertical ? Theme.spacingXS : _edgeBaseMargin
|
||||
readonly property real _barInsetPadding: _barInsetPaddingRaw < 0 ? _barInsetPaddingAuto : _barInsetPaddingRaw
|
||||
// Connected-frame Bar Inset Padding: absolute free-end gap the hosted bar spans full-width into
|
||||
// (auto < 0 = frameThickness so widgets align with the interior cutout, 0 = edge-to-edge). The extra
|
||||
// beyond frameThickness is what an adjacent bar end adds on top of its corner alignment.
|
||||
readonly property real _frameInsetResolved: SettingsData.frameBarInsetPadding < 0 ? SettingsData.frameThickness : SettingsData.frameBarInsetPadding
|
||||
readonly property real _frameInsetExtra: Math.max(0, _frameInsetResolved - SettingsData.frameThickness)
|
||||
// Hosted bars span their edge fully; frameBarContentGap is the free-end gap measured from the
|
||||
// screen edge (auto = frameThickness, aligning widgets with the interior cutout).
|
||||
readonly property real _frameInsetResolved: SettingsData.frameBarContentGap
|
||||
readonly property real _frameInsetExtra: SettingsData.frameBarContentGapExtra
|
||||
|
||||
// Horizontal bars span the full width and own the corners; the perpendicular vertical bar
|
||||
// tucks in below/above. Where they meet, inset the corner widget so it centres in the
|
||||
|
||||
@@ -609,7 +609,7 @@ Item {
|
||||
_closeHoverNotepad();
|
||||
activeHoverTrigger = "";
|
||||
PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen);
|
||||
TrayMenuManager.closeAllMenus();
|
||||
TrayMenuManager.closeHoverMenus();
|
||||
}
|
||||
|
||||
function _beginSupersededCloseForActive() {
|
||||
|
||||
@@ -83,6 +83,22 @@ BasePill {
|
||||
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: TrayMenuManager
|
||||
|
||||
function onOpenTrayMenuRequested() {
|
||||
const request = TrayMenuManager.claimMenuRequest(root.parentScreen?.name);
|
||||
if (!request)
|
||||
return;
|
||||
|
||||
const item = TrayMenuManager.findTrayItem(request.itemId);
|
||||
if (!item || !item.hasMenu)
|
||||
return;
|
||||
|
||||
root.showForTrayItem(item, root, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||
}
|
||||
}
|
||||
|
||||
function openInlineTrayContextMenu(trayItem, areaItem, mouse, anchorItem) {
|
||||
if (!trayItem) {
|
||||
return;
|
||||
@@ -1484,6 +1500,7 @@ BasePill {
|
||||
property bool isVertical: false
|
||||
property var axis: null
|
||||
property bool showMenu: false
|
||||
property bool openedByHover: false
|
||||
property var menuHandle: null
|
||||
|
||||
ListModel {
|
||||
@@ -1493,7 +1510,8 @@ BasePill {
|
||||
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
|
||||
}
|
||||
|
||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
|
||||
openedByHover = byHover === true;
|
||||
trayItem = item;
|
||||
anchorItem = anchor;
|
||||
parentScreen = screen;
|
||||
@@ -2084,7 +2102,7 @@ BasePill {
|
||||
}
|
||||
}
|
||||
|
||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
|
||||
if (!screen)
|
||||
return;
|
||||
if (currentTrayMenu) {
|
||||
@@ -2099,7 +2117,7 @@ BasePill {
|
||||
currentTrayMenu = trayMenuComponent.createObject(null);
|
||||
if (!currentTrayMenu)
|
||||
return;
|
||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj, byHover === true);
|
||||
}
|
||||
|
||||
function _trayLayoutRoot() {
|
||||
@@ -2147,7 +2165,7 @@ BasePill {
|
||||
if (!hit?.trayItem?.hasMenu)
|
||||
return false;
|
||||
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
||||
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis);
|
||||
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: activePlayer?.trackAlbum || ""
|
||||
text: MprisController.stableAlbum
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextSecondary
|
||||
width: parent.width
|
||||
|
||||
@@ -21,11 +21,10 @@ QtObject {
|
||||
|
||||
readonly property bool frameExclusionActive: CompositorService.frameWindowVisibleForScreen(screen)
|
||||
readonly property bool usesConnectedFrameChrome: CompositorService.usesConnectedFrameChromeForScreen(screen)
|
||||
readonly property bool connectedBarActiveOnEdge: usesConnectedFrameChrome && !!screen && SettingsData.getActiveBarEdgesForScreen(screen).includes(edge)
|
||||
|
||||
readonly property real connectedJoinInset: {
|
||||
if (usesConnectedFrameChrome)
|
||||
return connectedBarActiveOnEdge ? SettingsData.frameBarSize : SettingsData.frameThickness;
|
||||
return SettingsData.frameEdgeReservation(screen, edge);
|
||||
if (frameExclusionActive)
|
||||
return SettingsData.frameEdgeInsetForSide(screen, edge);
|
||||
return 0;
|
||||
|
||||
@@ -34,7 +34,7 @@ Scope {
|
||||
}
|
||||
|
||||
function exclusionSizeForEdge(edge) {
|
||||
return root.barEdges.includes(edge) ? SettingsData.frameBarSize : SettingsData.frameThickness;
|
||||
return SettingsData.frameEdgeReservation(root.screen, edge);
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -262,10 +262,22 @@ PanelWindow {
|
||||
return false;
|
||||
}
|
||||
|
||||
readonly property int cutoutTopInset: win._regionInt(barEdges.includes("top") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
readonly property int cutoutBottomInset: win._regionInt(barEdges.includes("bottom") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
readonly property int cutoutLeftInset: win._regionInt(barEdges.includes("left") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
readonly property int cutoutRightInset: win._regionInt(barEdges.includes("right") ? SettingsData.frameBarSize : SettingsData.frameThickness)
|
||||
readonly property int cutoutTopInset: {
|
||||
SettingsData.barConfigs;
|
||||
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "top"));
|
||||
}
|
||||
readonly property int cutoutBottomInset: {
|
||||
SettingsData.barConfigs;
|
||||
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "bottom"));
|
||||
}
|
||||
readonly property int cutoutLeftInset: {
|
||||
SettingsData.barConfigs;
|
||||
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "left"));
|
||||
}
|
||||
readonly property int cutoutRightInset: {
|
||||
SettingsData.barConfigs;
|
||||
return win._regionInt(SettingsData.frameEdgeReservation(win.targetScreen, "right"));
|
||||
}
|
||||
readonly property int cutoutWidth: Math.max(0, win._windowRegionWidth - win.cutoutLeftInset - win.cutoutRightInset)
|
||||
readonly property int cutoutHeight: Math.max(0, win._windowRegionHeight - win.cutoutTopInset - win.cutoutBottomInset)
|
||||
readonly property int cutoutRadius: {
|
||||
|
||||
@@ -66,9 +66,17 @@ Scope {
|
||||
IdleService.lockPowerOffRequested = false;
|
||||
}
|
||||
|
||||
// Avoid startup lock when using dms-greeter (#2952)
|
||||
function freshGreeterLogin() {
|
||||
const authTime = Number(Quickshell.env("DMS_GREETER_AUTH_TIME") || 0);
|
||||
if (!authTime)
|
||||
return false;
|
||||
return (Date.now() / 1000 - authTime) < 120;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
IdleService.lockComponent = this;
|
||||
if (SettingsData.lockAtStartup)
|
||||
if (SettingsData.lockAtStartup && !freshGreeterLogin())
|
||||
lock();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.DankCommon.Session
|
||||
import "../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
|
||||
import "../../Common/KeyUtils.js" as KeyUtils
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -98,6 +99,18 @@ Item {
|
||||
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
|
||||
}
|
||||
|
||||
function triggerSecurityKeyUnlock() {
|
||||
if (!canStartSecurityKeyUnlock())
|
||||
return;
|
||||
passwordField.clear();
|
||||
pam.u2f.startForAlternativeAuth();
|
||||
}
|
||||
|
||||
function securityKeyShortcutMatches(event) {
|
||||
return SettingsData.lockScreenSecurityKeyShortcutEnabled
|
||||
&& KeyUtils.eventMatchesCombo(event, SettingsData.lockScreenSecurityKeyShortcut);
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
WeatherService.addRef();
|
||||
UserInfoService.getUserInfo();
|
||||
@@ -932,7 +945,9 @@ Item {
|
||||
pam.passwd.start();
|
||||
}
|
||||
}
|
||||
Keys.onPressed: event => {
|
||||
Keys.onPressed: event => handleKey(event)
|
||||
|
||||
function handleKey(event) {
|
||||
if (demoMode) {
|
||||
return;
|
||||
}
|
||||
@@ -960,6 +975,12 @@ Item {
|
||||
}
|
||||
|
||||
if ((event.modifiers & Qt.ControlModifier) && !(event.modifiers & (Qt.AltModifier | Qt.MetaModifier))) {
|
||||
if (securityKeyShortcutMatches(event) && canStartSecurityKeyUnlock()) {
|
||||
triggerSecurityKeyUnlock();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case Qt.Key_A:
|
||||
cursorPosition = 0;
|
||||
@@ -1042,6 +1063,36 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
// Wayland IMEs commit unconsumed printable keys as text-input text
|
||||
// (ibus ibuswaylandim.c) instead of forwarding raw keys, so an active
|
||||
// text input must exist to receive them; the hidden-text hints put
|
||||
// fcitx5 into plain keyboard passthrough (CapabilityFlag::Password).
|
||||
// Raw keys stay in handleKey (#2950).
|
||||
TextInput {
|
||||
id: imeCommitSink
|
||||
|
||||
focus: true
|
||||
width: 1
|
||||
height: 1
|
||||
opacity: 0
|
||||
echoMode: TextInput.Password
|
||||
inputMethodHints: Qt.ImhHiddenText | Qt.ImhSensitiveData | Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
||||
Keys.onPressed: event => {
|
||||
passwordField.handleKey(event);
|
||||
if (!event.accepted && (event.modifiers & (Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier)))
|
||||
event.accepted = true;
|
||||
}
|
||||
onTextChanged: {
|
||||
if (text.length === 0)
|
||||
return;
|
||||
const committed = text;
|
||||
text = "";
|
||||
if (demoMode || root.unlocking || pam.passwd.active)
|
||||
return;
|
||||
passwordField.insertText(committed);
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!demoMode) {
|
||||
forceActiveFocus();
|
||||
@@ -1206,10 +1257,8 @@ Item {
|
||||
buttonSize: 32
|
||||
visible: root.canStartSecurityKeyUnlock()
|
||||
enabled: visible
|
||||
onClicked: {
|
||||
passwordField.clear();
|
||||
pam.u2f.startForAlternativeAuth();
|
||||
}
|
||||
tooltipText: SettingsData.lockScreenSecurityKeyShortcutEnabled ? I18n.tr("Security key (%1)", "lock screen security key button tooltip with shortcut").arg(SettingsData.lockScreenSecurityKeyShortcut) : I18n.tr("Security key", "lock screen security key button tooltip")
|
||||
onClicked: root.triggerSecurityKeyUnlock()
|
||||
}
|
||||
DankActionButton {
|
||||
id: virtualKeyboardButton
|
||||
@@ -1297,6 +1346,7 @@ Item {
|
||||
width: parent.width
|
||||
height: parent.height / 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -1
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.9)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import qs.Modules.Notepad
|
||||
FloatingWindow {
|
||||
id: win
|
||||
|
||||
property bool disablePopupTransparency: true
|
||||
property alias shouldBeVisible: win.visible
|
||||
property alias notepad: notepad
|
||||
|
||||
@@ -27,7 +28,7 @@ FloatingWindow {
|
||||
minimumSize: Qt.size(360, 320)
|
||||
implicitWidth: 640
|
||||
implicitHeight: 760
|
||||
color: Theme.surfaceContainer
|
||||
color: Theme.notepadWindowSurface
|
||||
visible: false
|
||||
|
||||
onVisibleChanged: {
|
||||
@@ -38,9 +39,27 @@ FloatingWindow {
|
||||
}
|
||||
}
|
||||
|
||||
// A compositor close (e.g. niri close-window)
|
||||
onClosed: win.visible = false
|
||||
|
||||
WindowBlur {
|
||||
targetWindow: win
|
||||
blurX: 0
|
||||
blurY: 0
|
||||
blurWidth: win.visible ? win.width : 0
|
||||
blurHeight: win.visible ? win.height : 0
|
||||
blurRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: "transparent"
|
||||
border.color: BlurService.borderColor
|
||||
border.width: BlurService.borderWidth
|
||||
antialiasing: true
|
||||
z: 100
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -58,12 +77,6 @@ FloatingWindow {
|
||||
onDoubleClicked: windowControls.tryToggleMaximize()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Theme.surfaceContainerHigh
|
||||
opacity: 0.5
|
||||
}
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
|
||||
@@ -360,8 +360,7 @@ QtObject {
|
||||
function _frameEdgeInset(side) {
|
||||
if (!manager.modelData)
|
||||
return 0;
|
||||
const edges = SettingsData.getActiveBarEdgesForScreen(manager.modelData);
|
||||
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
|
||||
const raw = SettingsData.frameEdgeReservation(manager.modelData, side);
|
||||
const dpr = CompositorService.getScreenScale(manager.modelData);
|
||||
return Math.max(0, Math.round(Theme.px(raw, dpr)));
|
||||
}
|
||||
|
||||
@@ -156,9 +156,10 @@ DankOSD {
|
||||
if (MprisController.isFirefoxYoutubeHoverPreview(player))
|
||||
return;
|
||||
|
||||
const newTitle = player.trackTitle || "";
|
||||
const newArtist = player.trackArtist || "";
|
||||
const newAlbum = player.trackAlbum || "";
|
||||
const metaPlayer = MprisController.bestMetadataPlayer(player);
|
||||
const newTitle = MprisController.displayTrackTitle(metaPlayer);
|
||||
const newArtist = metaPlayer.trackArtist || "";
|
||||
const newAlbum = metaPlayer.trackAlbum || "";
|
||||
const trackChanged = newTitle !== root._displayTitle || newArtist !== root._displayArtist || newAlbum !== root._displayAlbum;
|
||||
|
||||
root._displayTitle = newTitle;
|
||||
@@ -263,37 +264,102 @@ DankOSD {
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
radius: Theme.iconSize / 2
|
||||
color: "transparent"
|
||||
Row {
|
||||
id: transportControls
|
||||
|
||||
x: parent.gap
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root._displayIcon
|
||||
size: Theme.iconSize
|
||||
color: playPauseButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
Rectangle {
|
||||
width: Theme.iconSize - 4
|
||||
height: Theme.iconSize - 4
|
||||
radius: (Theme.iconSize - 4) / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: prevButton.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||
opacity: (root.player?.canGoPrevious ?? false) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_previous"
|
||||
size: Theme.iconSize - 10
|
||||
color: prevButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: prevButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: root.player?.canGoPrevious ?? false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
MprisController.previousOrRewind();
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: playPauseButton
|
||||
Rectangle {
|
||||
width: Theme.iconSize
|
||||
height: Theme.iconSize
|
||||
radius: Theme.iconSize / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: "transparent"
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
togglePlaying();
|
||||
root.hide();
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: root._displayIcon
|
||||
size: Theme.iconSize
|
||||
color: playPauseButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: playPauseButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
togglePlaying();
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: Theme.iconSize - 4
|
||||
height: Theme.iconSize - 4
|
||||
radius: (Theme.iconSize - 4) / 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: nextButton.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||
opacity: (root.player?.canGoNext ?? false) ? 1 : 0.3
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "skip_next"
|
||||
size: Theme.iconSize - 10
|
||||
color: nextButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: nextButton
|
||||
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
enabled: root.player?.canGoNext ?? false
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
MprisController.next();
|
||||
root.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
x: parent.gap * 2 + Theme.iconSize
|
||||
width: parent.width - Theme.iconSize - parent.gap * 3
|
||||
x: parent.gap * 2 + transportControls.width
|
||||
width: parent.width - transportControls.width - parent.gap * 3
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXXS
|
||||
|
||||
|
||||
@@ -29,6 +29,28 @@ Item {
|
||||
readonly property bool clickThrough: instanceData?.config?.clickThrough ?? false
|
||||
readonly property bool syncPositionAcrossScreens: instanceData?.config?.syncPositionAcrossScreens ?? false
|
||||
|
||||
// Unmapping with the widget still in the last buffer leaves a stale image in
|
||||
// Hyprland's blur cache behind transparent tiled windows (#2955), so hide the
|
||||
// content, present a transparent frame, then unmap.
|
||||
readonly property bool contentShowing: widgetEnabled && activeComponent !== null && (!showOnOverviewOnly || overviewActive)
|
||||
property bool surfaceLingering: false
|
||||
|
||||
onContentShowingChanged: {
|
||||
if (contentShowing) {
|
||||
lingerTimer.stop();
|
||||
surfaceLingering = false;
|
||||
return;
|
||||
}
|
||||
surfaceLingering = true;
|
||||
lingerTimer.restart();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: lingerTimer
|
||||
interval: 64
|
||||
onTriggered: root.surfaceLingering = false
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
enabled: !root.isBuiltin
|
||||
@@ -273,13 +295,7 @@ Item {
|
||||
PanelWindow {
|
||||
id: widgetWindow
|
||||
screen: root.screen
|
||||
visible: {
|
||||
if (!root.widgetEnabled || root.activeComponent === null)
|
||||
return false;
|
||||
if (root.showOnOverviewOnly)
|
||||
return root.overviewActive;
|
||||
return true;
|
||||
}
|
||||
visible: root.contentShowing || root.surfaceLingering
|
||||
color: "transparent"
|
||||
|
||||
Region {
|
||||
@@ -358,6 +374,7 @@ Item {
|
||||
id: contentLoader
|
||||
anchors.fill: parent
|
||||
active: root.widgetEnabled && root.activeComponent !== null
|
||||
visible: root.contentShowing
|
||||
sourceComponent: root.activeComponent
|
||||
opacity: 0
|
||||
|
||||
|
||||
@@ -1111,15 +1111,50 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function extractNiriOutputBlocks(content) {
|
||||
const blocks = [];
|
||||
const headerRegex = /output\s+"([^"]+)"\s*\{/g;
|
||||
let match;
|
||||
while ((match = headerRegex.exec(content)) !== null) {
|
||||
const start = headerRegex.lastIndex;
|
||||
let depth = 1;
|
||||
let i = start;
|
||||
while (i < content.length && depth > 0) {
|
||||
const ch = content[i];
|
||||
if (ch === '{')
|
||||
depth++;
|
||||
else if (ch === '}')
|
||||
depth--;
|
||||
i++;
|
||||
}
|
||||
blocks.push({
|
||||
"name": match[1],
|
||||
"body": content.slice(start, i - 1)
|
||||
});
|
||||
headerRegex.lastIndex = i;
|
||||
}
|
||||
return blocks;
|
||||
}
|
||||
|
||||
function stripNestedBlocks(body) {
|
||||
let stripped = body;
|
||||
let prev;
|
||||
do {
|
||||
prev = stripped;
|
||||
stripped = stripped.replace(/[\w-]+\s*\{[^{}]*\}/g, "");
|
||||
} while (stripped !== prev)
|
||||
return stripped;
|
||||
}
|
||||
|
||||
function parseNiriOutputs(content) {
|
||||
const result = {};
|
||||
const outputRegex = /output\s+"([^"]+)"\s*\{([^}]*)\}/g;
|
||||
let match;
|
||||
while ((match = outputRegex.exec(content)) !== null) {
|
||||
const name = match[1];
|
||||
const body = match[2];
|
||||
for (const block of extractNiriOutputBlocks(content)) {
|
||||
const name = block.name;
|
||||
const body = block.body;
|
||||
|
||||
const disabled = /^\s*off\s*$/m.test(body);
|
||||
// off marks the output disabled only at the top level of its block;
|
||||
// nested sections like hot-corners { off } must not count (#2966)
|
||||
const disabled = /^\s*off\s*$/m.test(stripNestedBlocks(body));
|
||||
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
||||
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
||||
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
||||
|
||||
@@ -13,7 +13,7 @@ Item {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
// Bar Inset Padding: resolve the "auto" sentinel (stored < 0) to the frame thickness for the slider display.
|
||||
readonly property int frameInsetPaddingDisplay: SettingsData.frameBarInsetPadding < 0 ? Math.round(SettingsData.frameThickness) : SettingsData.frameBarInsetPadding
|
||||
readonly property int frameInsetPaddingDisplay: Math.round(SettingsData.frameBarContentGap)
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -869,7 +869,7 @@ Item {
|
||||
|
||||
readonly property var tooltipTexts: ({
|
||||
"dms": I18n.tr("DMS shell actions (launcher, clipboard, etc.)"),
|
||||
"compositor": I18n.tr("Niri compositor actions (focus, move, etc.)"),
|
||||
"compositor": I18n.tr("Compositor actions (focus, move, etc.)", "keybind action type tooltip"),
|
||||
"spawn": I18n.tr("Run a program (e.g., firefox, kitty)"),
|
||||
"shell": I18n.tr("Run a shell command (e.g., notify-send)")
|
||||
})
|
||||
|
||||
@@ -822,7 +822,7 @@ Item {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search", "dms_clipboard_search", "dms_colorpicker"]
|
||||
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search", "dms_clipboard_search", "dms_colorpicker", "dms_qr_generator"]
|
||||
|
||||
delegate: Rectangle {
|
||||
id: pluginDelegate
|
||||
|
||||
@@ -5,6 +5,7 @@ import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.Settings.Widgets
|
||||
import "../../Common/KeyUtils.js" as KeyUtils
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -518,6 +519,117 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
settingKey: "lockScreenSecurityKeyShortcutEnabled"
|
||||
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "shortcut", "keybind", "authentication"]
|
||||
text: I18n.tr("Security key shortcut", "lock screen security key shortcut toggle")
|
||||
description: I18n.tr("Keyboard shortcut to start security key unlock", "lock screen security key shortcut setting")
|
||||
checked: SettingsData.lockScreenSecurityKeyShortcutEnabled
|
||||
visible: SettingsData.enableU2f && SettingsData.u2fMode === "or" && !root.lockU2fControlledByPrimary
|
||||
onToggled: checked => SettingsData.set("lockScreenSecurityKeyShortcutEnabled", checked)
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
x: Theme.spacingM
|
||||
spacing: Theme.spacingM
|
||||
visible: SettingsData.lockScreenSecurityKeyShortcutEnabled && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !root.lockU2fControlledByPrimary
|
||||
|
||||
Column {
|
||||
width: parent.width - securityKeyCapture.width - parent.spacing
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Key combination", "lock screen security key shortcut key combination setting")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: securityKeyCapture.captureError !== "" ? securityKeyCapture.captureError : I18n.tr("Press Ctrl+key to set. Esc cancels.", "lock screen security key shortcut key combination capture hint")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: securityKeyCapture.captureError !== "" ? Theme.warning : Theme.surfaceVariantText
|
||||
wrapMode: Text.WordWrap
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: securityKeyCapture
|
||||
width: 200
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
focus: capturing
|
||||
text: capturing ? I18n.tr("Press key...", "lock screen security key shortcut key combination capture prompt") : SettingsData.lockScreenSecurityKeyShortcut
|
||||
backgroundColor: capturing ? Theme.primaryContainer : Theme.surfaceContainer
|
||||
textColor: Theme.surfaceText
|
||||
|
||||
property bool capturing: false
|
||||
property string captureError: ""
|
||||
readonly property var reservedKeys: ["A", "E", "B", "F", "U", "K", "W", "H", "D"]
|
||||
|
||||
function startCapture() {
|
||||
captureError = "";
|
||||
capturing = true;
|
||||
securityKeyCapture.forceActiveFocus();
|
||||
}
|
||||
|
||||
function stopCapture() {
|
||||
capturing = false;
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
if (capturing)
|
||||
stopCapture();
|
||||
else
|
||||
startCapture();
|
||||
}
|
||||
|
||||
Keys.onPressed: event => {
|
||||
if (!securityKeyCapture.capturing)
|
||||
return;
|
||||
|
||||
if (KeyUtils.isModifierKey(event.key))
|
||||
return;
|
||||
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
securityKeyCapture.stopCapture();
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const mods = KeyUtils.modsFromEvent(event.modifiers);
|
||||
const hasCtrl = mods.includes("Ctrl");
|
||||
const hasAlt = mods.includes("Alt") || mods.includes("Super");
|
||||
if (!hasCtrl || hasAlt)
|
||||
return;
|
||||
|
||||
const hasShift = mods.includes("Shift");
|
||||
const key = KeyUtils.xkbKeyFromQtKey(event.key, !!(event.modifiers & Qt.KeypadModifier), hasShift);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
if (!KeyUtils.qtKeyFromName(key))
|
||||
return;
|
||||
|
||||
if (!hasShift && securityKeyCapture.reservedKeys.indexOf(key.toUpperCase()) !== -1) {
|
||||
securityKeyCapture.captureError = I18n.tr("Ctrl+%1 is used for password editing", "lock screen security key shortcut reserved key warning").arg(key.toUpperCase());
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsData.set("lockScreenSecurityKeyShortcut", KeyUtils.formatToken(mods, key));
|
||||
securityKeyCapture.captureError = "";
|
||||
securityKeyCapture.stopCapture();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
settingKey: "lockU2fPamPath"
|
||||
tags: ["lock", "screen", "pam", "u2f", "security", "key", "source", "service"]
|
||||
|
||||
@@ -410,8 +410,8 @@ Item {
|
||||
settingKey: "powerMenuDefaultAction"
|
||||
tags: ["power", "menu", "default", "action", "reboot", "logout", "shutdown"]
|
||||
text: I18n.tr("Default selected action")
|
||||
options: [I18n.tr("Reboot"), I18n.tr("Log Out"), I18n.tr("Power Off"), I18n.tr("Lock"), I18n.tr("Suspend"), I18n.tr("Restart DMS"), I18n.tr("Hibernate")]
|
||||
property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate"]
|
||||
options: [I18n.tr("Reboot"), I18n.tr("Log Out"), I18n.tr("Power Off"), I18n.tr("Lock"), I18n.tr("Suspend"), I18n.tr("Restart DMS"), I18n.tr("Hibernate"), I18n.tr("Soft Reboot")]
|
||||
property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate", "softreboot"]
|
||||
|
||||
Component.onCompleted: {
|
||||
const currentAction = SettingsData.powerMenuDefaultAction || "logout";
|
||||
@@ -475,6 +475,12 @@ Item {
|
||||
label: I18n.tr("Show Hibernate"),
|
||||
desc: I18n.tr("Only visible if hibernate is supported by your system"),
|
||||
hibernate: true
|
||||
},
|
||||
{
|
||||
key: "softreboot",
|
||||
label: I18n.tr("Show Soft Reboot"),
|
||||
desc: I18n.tr("Restart userspace without rebooting the kernel, requires systemd"),
|
||||
softreboot: true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -484,7 +490,13 @@ Item {
|
||||
tags: ["power", "menu", "action", "show", modelData.key]
|
||||
text: modelData.label
|
||||
description: modelData.desc || ""
|
||||
visible: !modelData.hibernate || SessionService.hibernateSupported
|
||||
visible: {
|
||||
if (modelData.hibernate)
|
||||
return SessionService.hibernateSupported;
|
||||
if (modelData.softreboot)
|
||||
return SessionService.softRebootSupported;
|
||||
return true;
|
||||
}
|
||||
checked: SettingsData.powerMenuActions.includes(modelData.key)
|
||||
onToggled: checked => {
|
||||
let actions = [...SettingsData.powerMenuActions];
|
||||
|
||||
@@ -1728,7 +1728,7 @@ Item {
|
||||
tags: ["surface", "popup", "transparency", "opacity", "modal"]
|
||||
settingKey: "popupTransparency"
|
||||
text: I18n.tr("Surface Opacity")
|
||||
description: I18n.tr("Controls opacity of shell surfaces, popouts, and modals")
|
||||
description: I18n.tr("Controls opacity of shell surfaces, popouts, modals, and floating windows", "Surface Opacity setting description including floating DMS windows")
|
||||
visible: !themeColorsTab.connectedFrameModeActive
|
||||
value: Math.round(SettingsData.popupTransparency * 100)
|
||||
minimum: 0
|
||||
@@ -2780,7 +2780,7 @@ Item {
|
||||
tags: ["matugen", "vscode", "code", "template"]
|
||||
settingKey: "matugenTemplateVscode"
|
||||
text: "VS Code"
|
||||
description: getTemplateDescription("vscode", "")
|
||||
description: getTemplateDescription("vscode", I18n.tr("Requires the DMS Theme extension from the editor marketplace", "vscode matugen template description"))
|
||||
descriptionColor: getTemplateDescriptionColor("vscode")
|
||||
visible: SettingsData.runDmsMatugenTemplates
|
||||
checked: SettingsData.matugenTemplateVscode
|
||||
|
||||
@@ -977,6 +977,33 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
id: randomToggle
|
||||
tab: "wallpaper"
|
||||
tags: ["cycling", "automatic", "random", "shuffle"]
|
||||
settingKey: "wallpaperCyclingRandom"
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
text: I18n.tr("Random Order")
|
||||
description: I18n.tr("Select a random wallpaper instead of cycling in alphabetical order")
|
||||
checked: SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).random : SessionData.wallpaperCyclingRandom
|
||||
onToggled: toggled => {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
SessionData.setMonitorCyclingRandom(selectedMonitorName, toggled);
|
||||
} else {
|
||||
SessionData.setWallpaperCyclingRandom(toggled);
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onSelectedMonitorNameChanged() {
|
||||
randomToggle.checked = Qt.binding(() => {
|
||||
return SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).random : SessionData.wallpaperCyclingRandom;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDropdownRow {
|
||||
id: intervalDropdown
|
||||
property var intervalOptions: [I18n.tr("5 seconds", "wallpaper interval"), I18n.tr("10 seconds", "wallpaper interval"), I18n.tr("15 seconds", "wallpaper interval"), I18n.tr("20 seconds", "wallpaper interval"), I18n.tr("25 seconds", "wallpaper interval"), I18n.tr("30 seconds", "wallpaper interval"), I18n.tr("35 seconds", "wallpaper interval"), I18n.tr("40 seconds", "wallpaper interval"), I18n.tr("45 seconds", "wallpaper interval"), I18n.tr("50 seconds", "wallpaper interval"), I18n.tr("55 seconds", "wallpaper interval"), I18n.tr("1 minute", "wallpaper interval"), I18n.tr("5 minutes", "wallpaper interval"), I18n.tr("15 minutes", "wallpaper interval"), I18n.tr("30 minutes", "wallpaper interval"), I18n.tr("1 hour", "wallpaper interval"), I18n.tr("1 hour 30 minutes", "wallpaper interval"), I18n.tr("2 hours", "wallpaper interval"), I18n.tr("3 hours", "wallpaper interval"), I18n.tr("4 hours", "wallpaper interval"), I18n.tr("6 hours", "wallpaper interval"), I18n.tr("8 hours", "wallpaper interval"), I18n.tr("12 hours", "wallpaper interval")]
|
||||
|
||||
@@ -38,7 +38,9 @@ StyledRect {
|
||||
return h;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
readonly property bool collapsed: collapsible && !expanded
|
||||
readonly property bool hasHeader: root.title !== "" || root.iconName !== ""
|
||||
|
||||
@@ -35,7 +35,9 @@ StyledRect {
|
||||
width: parent?.width ?? 0
|
||||
height: Theme.spacingL * 2 + contentColumn.height
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!settingKey)
|
||||
|
||||
@@ -29,7 +29,9 @@ StyledRect {
|
||||
width: parent?.width ?? 0
|
||||
height: Theme.spacingL * 2 + mainColumn.height
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceContainerHigh
|
||||
color: Theme.nestedSurface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: Theme.layerOutlineWidth
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!settingKey)
|
||||
|
||||
@@ -210,6 +210,19 @@ Singleton {
|
||||
defaultTrigger: "",
|
||||
isLauncher: false
|
||||
},
|
||||
"dms_qr_generator": {
|
||||
id: "dms_qr_generator",
|
||||
name: I18n.tr("QR Generator"),
|
||||
icon: "svg+corner:" + dmsLogoPath + "|qr_code",
|
||||
cornerIcon: "qr_code",
|
||||
comment: "DMS",
|
||||
action: "ipc:qr-generator",
|
||||
categories: ["Utility"],
|
||||
defaultTrigger: "qrg",
|
||||
isLauncher: true,
|
||||
viewMode: "list",
|
||||
viewModeEnforced: true
|
||||
},
|
||||
"dms_settings_search": {
|
||||
id: "dms_settings_search",
|
||||
name: I18n.tr("Settings Search"),
|
||||
@@ -244,7 +257,7 @@ Singleton {
|
||||
if (!SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true))
|
||||
continue;
|
||||
const plugin = builtInPlugins[pluginId];
|
||||
if (plugin.isLauncher)
|
||||
if (plugin.isLauncher && !plugin.action)
|
||||
continue;
|
||||
apps.push({
|
||||
name: plugin.name,
|
||||
@@ -309,6 +322,20 @@ Singleton {
|
||||
}));
|
||||
}
|
||||
|
||||
if (pluginId === "dms_qr_generator") {
|
||||
const text = (query || "").toString().trim();
|
||||
return [
|
||||
{
|
||||
name: text.length > 0 ? text : I18n.tr("Enter text to encode"),
|
||||
icon: "material:qr_code",
|
||||
comment: I18n.tr("QR Generator"),
|
||||
action: "qr_generate:" + text,
|
||||
isBuiltInLauncher: true,
|
||||
builtInPluginId: pluginId
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if (pluginId !== "dms_settings_search")
|
||||
return [];
|
||||
|
||||
@@ -340,14 +367,20 @@ Singleton {
|
||||
return false;
|
||||
|
||||
const parts = item.action.split(":");
|
||||
if (parts[0] !== "settings_nav")
|
||||
return false;
|
||||
|
||||
const tabIndex = parseInt(parts[1]);
|
||||
const section = parts.slice(2).join(":");
|
||||
SettingsSearchService.navigateToSection(section);
|
||||
PopoutService.openSettingsWithTabIndex(tabIndex);
|
||||
return true;
|
||||
switch (parts[0]) {
|
||||
case "settings_nav":
|
||||
{
|
||||
const tabIndex = parseInt(parts[1]);
|
||||
const section = parts.slice(2).join(":");
|
||||
SettingsSearchService.navigateToSection(section);
|
||||
PopoutService.openSettingsWithTabIndex(tabIndex);
|
||||
return true;
|
||||
}
|
||||
case "qr_generate":
|
||||
PopoutService.showQRGeneratorModal(parts.slice(1).join(":"));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getCoreApps(query) {
|
||||
@@ -378,6 +411,9 @@ Singleton {
|
||||
case "color-picker":
|
||||
PopoutService.showColorPicker();
|
||||
return true;
|
||||
case "qr-generator":
|
||||
PopoutService.showQRGeneratorModal();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -749,16 +785,24 @@ Singleton {
|
||||
if (category === I18n.tr("All"))
|
||||
return visibleApps;
|
||||
|
||||
const pluginItems = getPluginItems(category, "");
|
||||
if (pluginItems.length > 0)
|
||||
return pluginItems;
|
||||
|
||||
return visibleApps.filter(app => {
|
||||
const appCategories = getCategoriesForApp(app);
|
||||
return appCategories.includes(category);
|
||||
});
|
||||
}
|
||||
|
||||
function getPluginIdForCategory(category) {
|
||||
if (typeof PluginService === "undefined")
|
||||
return null;
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins();
|
||||
for (const pluginId in launchers) {
|
||||
if ((launchers[pluginId].name || pluginId) === category)
|
||||
return pluginId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Plugin launcher support functions
|
||||
function getPluginCategories() {
|
||||
if (typeof PluginService === "undefined") {
|
||||
@@ -778,31 +822,19 @@ Singleton {
|
||||
}
|
||||
|
||||
function getPluginCategoryIcon(category) {
|
||||
if (typeof PluginService === "undefined")
|
||||
const pluginId = getPluginIdForCategory(category);
|
||||
if (!pluginId)
|
||||
return null;
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins();
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId];
|
||||
if ((plugin.name || pluginId) === category) {
|
||||
return plugin.icon || "extension";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return PluginService.getLauncherPlugins()[pluginId].icon || "extension";
|
||||
}
|
||||
|
||||
function getPluginItems(category, query) {
|
||||
if (typeof PluginService === "undefined")
|
||||
const pluginId = getPluginIdForCategory(category);
|
||||
if (!pluginId)
|
||||
return [];
|
||||
|
||||
const launchers = PluginService.getLauncherPlugins();
|
||||
for (const pluginId in launchers) {
|
||||
const plugin = launchers[pluginId];
|
||||
if ((plugin.name || pluginId) === category) {
|
||||
return getPluginItemsForPlugin(pluginId, query);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
return getPluginItemsForPlugin(pluginId, query);
|
||||
}
|
||||
|
||||
function getPluginItemsForPlugin(pluginId, query) {
|
||||
|
||||
@@ -23,6 +23,13 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null
|
||||
function onHasPerformanceProfileChanged() {
|
||||
root.applyPowerProfile();
|
||||
}
|
||||
}
|
||||
|
||||
function applyPowerProfile() {
|
||||
if (!batteryAvailable)
|
||||
return;
|
||||
@@ -32,7 +39,7 @@ Singleton {
|
||||
const targetProfile = parseInt(profileValue);
|
||||
if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
|
||||
return;
|
||||
PowerProfiles.profile = targetProfile;
|
||||
PowerProfileWatcher.applyProfile(targetProfile);
|
||||
}
|
||||
|
||||
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
||||
@@ -136,7 +143,7 @@ Singleton {
|
||||
|
||||
function sendAlert(title, message, isWarning, category, notificationType) {
|
||||
if (notificationType === 1) {
|
||||
Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "battery-caution" : "battery-charging", title, message]);
|
||||
Quickshell.execDetached(["notify-send", "-u", isWarning ? "critical" : "normal", "-a", "DMS", "-i", isWarning ? "material:battery_alert" : "material:battery_charging_full", title, message]);
|
||||
} else {
|
||||
if (isWarning) {
|
||||
ToastService.showWarning(title, message, "", category);
|
||||
@@ -150,7 +157,7 @@ Singleton {
|
||||
if (isCharging && batteryLevel >= SettingsData.batteryChargeLimit) {
|
||||
if (!_hasNotifiedChargeLimit && SettingsData.batteryNotifyChargeLimit) {
|
||||
_hasNotifiedChargeLimit = true;
|
||||
sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "battery-charge-limit", SettingsData.batteryChargeLimitNotificationType);
|
||||
sendAlert(I18n.tr("Charge Limit Reached"), I18n.tr("Battery has charged to your set limit of %1%").arg(SettingsData.batteryChargeLimit), false, "material:battery_profile", SettingsData.batteryChargeLimitNotificationType);
|
||||
}
|
||||
} else if (!isCharging || batteryLevel < SettingsData.batteryChargeLimit - 2) {
|
||||
_hasNotifiedChargeLimit = false;
|
||||
@@ -166,7 +173,7 @@ Singleton {
|
||||
if (isCriticalBattery) {
|
||||
if (!_hasNotifiedCriticalBattery && SettingsData.batteryNotifyCritical) {
|
||||
_hasNotifiedCriticalBattery = true;
|
||||
sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "battery-critical", SettingsData.batteryCriticalNotificationType);
|
||||
sendAlert(I18n.tr("Critical Battery"), I18n.tr("Battery is at %1% - Connect charger immediately!").arg(batteryLevel), true, "material:battery_alert", SettingsData.batteryCriticalNotificationType);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -179,7 +186,7 @@ Singleton {
|
||||
if (isLowBattery) {
|
||||
if (!_hasNotifiedLowBattery && SettingsData.batteryNotifyLow) {
|
||||
_hasNotifiedLowBattery = true;
|
||||
sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "battery-low", SettingsData.batteryLowNotificationType);
|
||||
sendAlert(I18n.tr("Low Battery"), I18n.tr("Battery is at %1% - Consider charging soon").arg(batteryLevel), true, "material:battery_0_bar", SettingsData.batteryLowNotificationType);
|
||||
}
|
||||
|
||||
if (SettingsData.batteryAutoPowerSaver && PowerProfileWatcher.available) {
|
||||
@@ -223,6 +230,27 @@ Singleton {
|
||||
|
||||
applyPowerProfile();
|
||||
|
||||
if (isPluggedIn) {
|
||||
const dismissLow = SettingsData.batteryLowNotificationType === 1 && SettingsData.notificationTimeoutNormal === 0;
|
||||
const dismissCritical = SettingsData.batteryCriticalNotificationType === 1 && SettingsData.notificationTimeoutCritical === 0;
|
||||
|
||||
if (dismissLow || dismissCritical) {
|
||||
const lowSummary = I18n.tr("Low Battery");
|
||||
const criticalSummary = I18n.tr("Critical Battery");
|
||||
|
||||
for (const w of NotificationService.visibleNotifications) {
|
||||
if (!w || !w.notification)
|
||||
continue;
|
||||
|
||||
const summary = w.notification.summary;
|
||||
|
||||
if ((dismissLow && summary === lowSummary) || (dismissCritical && summary === criticalSummary)) {
|
||||
NotificationService.dismissNotification(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousPluggedState = isPluggedIn;
|
||||
}
|
||||
|
||||
|
||||
@@ -793,6 +793,13 @@ Singleton {
|
||||
autoRefreshEnabled = false;
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 10000
|
||||
repeat: true
|
||||
running: root.autoScan && root.networkAvailable && root.wifiEnabled
|
||||
onTriggered: root.scanWifi()
|
||||
}
|
||||
|
||||
function fetchWiredNetworkInfo(uuid) {
|
||||
if (!networkAvailable)
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("DMSService")
|
||||
|
||||
property bool dmsAvailable: false
|
||||
readonly property bool dmsAvailable: isConnected
|
||||
property var capabilities: []
|
||||
property int apiVersion: 0
|
||||
property string cliVersion: ""
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
property var availableThemes: []
|
||||
property var installedThemes: []
|
||||
property bool isConnected: false
|
||||
property bool isConnecting: false
|
||||
readonly property bool isConnecting: requestSocket.connected && !requestSocket.linkUp
|
||||
property bool subscribeConnected: false
|
||||
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
@@ -72,9 +72,10 @@ Singleton {
|
||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "sysupdate"]
|
||||
|
||||
Component.onCompleted: {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
detectUpdateCommand();
|
||||
}
|
||||
if (!socketPath || socketPath.length === 0)
|
||||
return;
|
||||
detectUpdateCommand();
|
||||
requestSocket.connected = true;
|
||||
}
|
||||
|
||||
function detectUpdateCommand() {
|
||||
@@ -82,12 +83,6 @@ Singleton {
|
||||
checkAurHelper.running = true;
|
||||
}
|
||||
|
||||
function startSocketConnection() {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
testProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkAurHelper
|
||||
command: ["sh", "-c", "command -v paru || command -v yay"]
|
||||
@@ -105,7 +100,6 @@ Singleton {
|
||||
} else {
|
||||
updateCommand = "dms update";
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +108,6 @@ Singleton {
|
||||
if (exitCode !== 0) {
|
||||
updateCommand = "dms update";
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +128,6 @@ Singleton {
|
||||
updateCommand = "dms update";
|
||||
}
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,52 +135,27 @@ Singleton {
|
||||
if (exitCode !== 0) {
|
||||
updateCommand = "dms update";
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: testProcess
|
||||
command: ["test", "-S", root.socketPath]
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
root.dmsAvailable = true;
|
||||
connectSocket();
|
||||
} else {
|
||||
root.dmsAvailable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function connectSocket() {
|
||||
if (!dmsAvailable || isConnected || isConnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
isConnecting = true;
|
||||
requestSocket.connected = true;
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: requestSocket
|
||||
path: root.socketPath
|
||||
connected: false
|
||||
|
||||
onConnectionStateChanged: {
|
||||
if (connected) {
|
||||
if (linkUp) {
|
||||
root.isConnected = true;
|
||||
root.isConnecting = false;
|
||||
root.connectionStateChanged();
|
||||
subscribeSocket.connected = true;
|
||||
} else {
|
||||
root.isConnected = false;
|
||||
root.isConnecting = false;
|
||||
root.apiVersion = 0;
|
||||
root.capabilities = [];
|
||||
root.connectionStateChanged();
|
||||
return;
|
||||
}
|
||||
root.isConnected = false;
|
||||
root.apiVersion = 0;
|
||||
root.capabilities = [];
|
||||
root.failPendingRequests();
|
||||
root.connectionStateChanged();
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
@@ -219,10 +186,10 @@ Singleton {
|
||||
connected: false
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscribeConnected = connected;
|
||||
if (connected) {
|
||||
sendSubscribeRequest();
|
||||
}
|
||||
root.subscribeConnected = linkUp;
|
||||
if (!linkUp)
|
||||
return;
|
||||
sendSubscribeRequest();
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
@@ -432,10 +399,20 @@ Singleton {
|
||||
|
||||
function handleResponse(response) {
|
||||
const callback = pendingRequests[response.id];
|
||||
if (!callback)
|
||||
return;
|
||||
delete pendingRequests[response.id];
|
||||
callback(response);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
delete pendingRequests[response.id];
|
||||
callback(response);
|
||||
function failPendingRequests() {
|
||||
const pending = pendingRequests;
|
||||
pendingRequests = {};
|
||||
clipboardRequestIds = {};
|
||||
for (const id in pending) {
|
||||
pending[id]({
|
||||
"error": "not connected to DMS socket"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1721,6 +1721,8 @@ Singleton {
|
||||
return "Temperature must be between 2500K and 6000K";
|
||||
|
||||
const rounded = Math.round(temp / 500) * 500;
|
||||
if (rounded > SessionData.nightModeHighTemperature)
|
||||
return "Night temperature must not exceed the day temperature (" + SessionData.nightModeHighTemperature + "K)";
|
||||
SessionData.setNightModeTemperature(rounded);
|
||||
|
||||
if (root.nightModeEnabled) {
|
||||
@@ -1750,6 +1752,8 @@ Singleton {
|
||||
return "Temperature must be between 2500K and 6500K";
|
||||
|
||||
const rounded = Math.round(temp / 500) * 500;
|
||||
if (rounded < SessionData.nightModeTemperature)
|
||||
return "Day temperature must be at least the night temperature (" + SessionData.nightModeTemperature + "K)";
|
||||
SessionData.setNightModeHighTemperature(rounded);
|
||||
|
||||
if (root.nightModeEnabled && SessionData.nightModeAutoEnabled)
|
||||
|
||||
@@ -388,6 +388,8 @@ Singleton {
|
||||
const binds = bindsData[cat];
|
||||
for (var i = 0; i < binds.length; i++) {
|
||||
const bind = binds[i];
|
||||
if (currentProvider === "hyprland" && bind.action && bind.action.startsWith("exec "))
|
||||
bind.action = "spawn " + bind.action.slice(5);
|
||||
const targetCat = Actions.isDmsAction(bind.action) ? "DMS" : cat;
|
||||
if (!processed[targetCat])
|
||||
processed[targetCat] = [];
|
||||
|
||||
@@ -47,6 +47,7 @@ Singleton {
|
||||
// Chromium can report blank metadata between tracks
|
||||
property string stableTitle: ""
|
||||
property string stableArtist: ""
|
||||
property string stableAlbum: ""
|
||||
|
||||
Connections {
|
||||
target: root.activePlayer
|
||||
@@ -59,6 +60,9 @@ Singleton {
|
||||
root._syncStableMeta();
|
||||
root._checkIdle();
|
||||
}
|
||||
function onTrackAlbumChanged() {
|
||||
root._syncStableMeta();
|
||||
}
|
||||
function onLengthChanged() {
|
||||
if (root.activePlayer && root.activePlayer.lengthSupported && root.activePlayer.length > 1) {
|
||||
root.activePlayerStableLength = root.activePlayer.length;
|
||||
@@ -72,8 +76,10 @@ Singleton {
|
||||
|
||||
onActivePlayerChanged: {
|
||||
activePlayerStableLength = (activePlayer && activePlayer.lengthSupported && activePlayer.length > 1) ? activePlayer.length : 0;
|
||||
stableTitle = activePlayer?.trackTitle || "";
|
||||
stableArtist = activePlayer?.trackArtist || "";
|
||||
stableTitle = "";
|
||||
stableArtist = "";
|
||||
stableAlbum = "";
|
||||
_syncStableMeta();
|
||||
_checkIdle();
|
||||
}
|
||||
|
||||
@@ -82,14 +88,24 @@ Singleton {
|
||||
if (!p) {
|
||||
stableTitle = "";
|
||||
stableArtist = "";
|
||||
stableAlbum = "";
|
||||
return;
|
||||
}
|
||||
if (isFirefoxYoutubeHoverPreview(p))
|
||||
return;
|
||||
if (p.trackTitle)
|
||||
stableTitle = p.trackTitle;
|
||||
if (p.trackArtist)
|
||||
stableArtist = p.trackArtist;
|
||||
const metadataPlayer = bestMetadataPlayer(p);
|
||||
const nextTitle = displayTrackTitle(metadataPlayer);
|
||||
const trackChanged = nextTitle && stableTitle && nextTitle.toLowerCase() !== stableTitle.toLowerCase();
|
||||
if (trackChanged) {
|
||||
stableArtist = "";
|
||||
stableAlbum = "";
|
||||
}
|
||||
if (nextTitle)
|
||||
stableTitle = nextTitle;
|
||||
if (metadataPlayer.trackArtist)
|
||||
stableArtist = metadataPlayer.trackArtist;
|
||||
if (metadataPlayer.trackAlbum)
|
||||
stableAlbum = metadataPlayer.trackAlbum;
|
||||
}
|
||||
|
||||
// Chromium reports stopped media w/blank metadata, resolve by checking idle status
|
||||
@@ -101,6 +117,7 @@ Singleton {
|
||||
return;
|
||||
root.stableTitle = "";
|
||||
root.stableArtist = "";
|
||||
root.stableAlbum = "";
|
||||
root._resolveActivePlayer();
|
||||
}
|
||||
}
|
||||
@@ -122,20 +139,133 @@ Singleton {
|
||||
delegate: Connections {
|
||||
required property MprisPlayer modelData
|
||||
target: modelData
|
||||
ignoreUnknownSignals: true
|
||||
function onIsPlayingChanged() {
|
||||
root._resolveActivePlayer();
|
||||
root._syncStableMeta();
|
||||
}
|
||||
function onTrackTitleChanged() {
|
||||
if (modelData.isPlaying)
|
||||
root._resolveActivePlayer();
|
||||
root._syncStableMeta();
|
||||
}
|
||||
function onTrackArtistChanged() {
|
||||
if (modelData.isPlaying)
|
||||
root._resolveActivePlayer();
|
||||
root._syncStableMeta();
|
||||
}
|
||||
function onTrackAlbumChanged() {
|
||||
if (modelData.isPlaying)
|
||||
root._resolveActivePlayer();
|
||||
root._syncStableMeta();
|
||||
}
|
||||
function onMetadataChanged() {
|
||||
if (modelData.isPlaying)
|
||||
root._resolveActivePlayer();
|
||||
root._syncStableMeta();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isIdle(player: MprisPlayer): bool {
|
||||
return player && player.playbackState === MprisPlaybackState.Stopped && !player.trackTitle && !player.trackArtist;
|
||||
return player && player.playbackState === MprisPlaybackState.Stopped;
|
||||
}
|
||||
|
||||
// Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title
|
||||
readonly property var _appTitleSuffixes: ["youtube", "youtube music", "soundcloud", "spotify", "chrome", "chromium", "firefox", "brave", "vivaldi", "twitch"]
|
||||
|
||||
function _stripAppTitleSuffix(title: string): string {
|
||||
const idx = title.lastIndexOf(" | ");
|
||||
if (idx <= 0)
|
||||
return title;
|
||||
const suffix = title.substring(idx + 3).trim().toLowerCase();
|
||||
return _appTitleSuffixes.indexOf(suffix) !== -1 ? title.substring(0, idx).trim() : title;
|
||||
}
|
||||
|
||||
function normalizedTrackTitle(player: MprisPlayer): string {
|
||||
return _stripAppTitleSuffix((player?.trackTitle || "").trim()).toLowerCase();
|
||||
}
|
||||
|
||||
function displayTrackTitle(player: MprisPlayer): string {
|
||||
return (player?.trackTitle || "").trim();
|
||||
}
|
||||
|
||||
function normalizedTrackArtist(player: MprisPlayer): string {
|
||||
return (player?.trackArtist || "").trim().toLowerCase();
|
||||
}
|
||||
|
||||
// Artist missing on either side: fall back to URL, then album, before trusting a title-only match
|
||||
function isSameTrack(first: MprisPlayer, second: MprisPlayer): bool {
|
||||
const firstTitle = normalizedTrackTitle(first);
|
||||
if (!firstTitle || firstTitle !== normalizedTrackTitle(second))
|
||||
return false;
|
||||
const firstArtist = normalizedTrackArtist(first);
|
||||
const secondArtist = normalizedTrackArtist(second);
|
||||
if (firstArtist && secondArtist)
|
||||
return firstArtist === secondArtist;
|
||||
const firstUrl = (first?.metadata?.["xesam:url"] || "").toString();
|
||||
const secondUrl = (second?.metadata?.["xesam:url"] || "").toString();
|
||||
if (firstUrl && secondUrl)
|
||||
return firstUrl === secondUrl;
|
||||
const firstAlbum = (first?.trackAlbum || "").trim().toLowerCase();
|
||||
const secondAlbum = (second?.trackAlbum || "").trim().toLowerCase();
|
||||
if (firstAlbum && secondAlbum)
|
||||
return firstAlbum === secondAlbum;
|
||||
return true;
|
||||
}
|
||||
|
||||
function metadataQuality(player: MprisPlayer): int {
|
||||
if (!player)
|
||||
return -1;
|
||||
let quality = player.trackArtist ? 100 : 0;
|
||||
quality += player.trackTitle ? 40 : 0;
|
||||
quality += player.trackAlbum ? 20 : 0;
|
||||
quality += player.trackArtUrl || player.metadata?.["mpris:artUrl"] ? 10 : 0;
|
||||
quality += player.metadata?.["xesam:url"] ? 5 : 0;
|
||||
return quality;
|
||||
}
|
||||
|
||||
function equivalentPlayers(player: MprisPlayer): var {
|
||||
if (!player)
|
||||
return [];
|
||||
return availablePlayers.filter(candidate => {
|
||||
return candidate.playbackState !== MprisPlaybackState.Stopped && isSameTrack(player, candidate);
|
||||
});
|
||||
}
|
||||
|
||||
function bestMetadataPlayer(player: MprisPlayer): MprisPlayer {
|
||||
const equivalents = equivalentPlayers(player);
|
||||
if (equivalents.length === 0)
|
||||
return player;
|
||||
return equivalents.reduce((best, candidate) => {
|
||||
return metadataQuality(candidate) > metadataQuality(best) ? candidate : best;
|
||||
}, player);
|
||||
}
|
||||
|
||||
function _bestPlayingPlayer(): MprisPlayer {
|
||||
const playing = availablePlayers.filter(player => player.isPlaying);
|
||||
if (playing.length === 0)
|
||||
return null;
|
||||
|
||||
const controllable = playing.filter(player => player.canControl);
|
||||
if (activePlayer?.isPlaying) {
|
||||
if (activePlayer.canControl || controllable.length === 0)
|
||||
return activePlayer;
|
||||
// Playing but not controllable: only a same-track controllable peer may take over
|
||||
const mirror = controllable.find(player => isSameTrack(activePlayer, player));
|
||||
return mirror || activePlayer;
|
||||
}
|
||||
|
||||
if (activePlayer?.canControl && activePlayer.playbackState === MprisPlaybackState.Paused) {
|
||||
const onlyEquivalentMirrors = playing.every(player => isSameTrack(activePlayer, player));
|
||||
if (onlyEquivalentMirrors)
|
||||
return null;
|
||||
}
|
||||
return controllable[0] || playing[0];
|
||||
}
|
||||
|
||||
function _resolveActivePlayer(): void {
|
||||
// A playing player always wins; otherwise keep the selection stable w/idle
|
||||
const playing = availablePlayers.find(p => p.isPlaying);
|
||||
const playing = _bestPlayingPlayer();
|
||||
if (playing) {
|
||||
if (activePlayer !== playing) {
|
||||
activePlayer = playing;
|
||||
|
||||
@@ -889,6 +889,12 @@ Singleton {
|
||||
NotifWrapper {}
|
||||
}
|
||||
|
||||
function dismissLastNotification() {
|
||||
const w = visibleNotifications[visibleNotifications.length - 1];
|
||||
if (w)
|
||||
dismissNotification(w);
|
||||
}
|
||||
|
||||
function dismissAllPopups() {
|
||||
for (const w of visibleNotifications) {
|
||||
if (w) {
|
||||
|
||||
@@ -47,6 +47,8 @@ Singleton {
|
||||
property var wifiPasswordModalLoader: null
|
||||
property var wifiQRCodeModal: null
|
||||
property var wifiQRCodeModalLoader: null
|
||||
property var qrGeneratorModal: null
|
||||
property var qrGeneratorModalLoader: null
|
||||
property var polkitAuthModal: null
|
||||
property var polkitAuthModalLoader: null
|
||||
property var bluetoothPairingModal: null
|
||||
@@ -891,6 +893,13 @@ Singleton {
|
||||
wifiQRCodeModal.show(ssid);
|
||||
}
|
||||
|
||||
function showQRGeneratorModal(initialText) {
|
||||
if (qrGeneratorModalLoader)
|
||||
qrGeneratorModalLoader.active = true;
|
||||
if (qrGeneratorModal)
|
||||
qrGeneratorModal.show(initialText || "");
|
||||
}
|
||||
|
||||
function showHiddenNetworkModal() {
|
||||
if (wifiPasswordModalLoader)
|
||||
wifiPasswordModalLoader.active = true;
|
||||
|
||||
@@ -19,6 +19,7 @@ Singleton {
|
||||
property bool loginctlCommandAvailable: false
|
||||
property bool systemctlCommandAvailable: false
|
||||
property bool hibernateSupported: false
|
||||
readonly property bool softRebootSupported: systemctlCommandAvailable
|
||||
property bool inhibitorAvailable: true
|
||||
property bool idleInhibited: false
|
||||
property string inhibitReason: "Keep system awake"
|
||||
@@ -219,6 +220,64 @@ Singleton {
|
||||
return envObj;
|
||||
}
|
||||
|
||||
function splitShellArgs(str) {
|
||||
const args = [];
|
||||
let current = "";
|
||||
let hasToken = false;
|
||||
let quote = "";
|
||||
let escaped = false;
|
||||
for (const ch of str) {
|
||||
if (escaped) {
|
||||
current += ch;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
switch (quote) {
|
||||
case "'":
|
||||
if (ch === "'") {
|
||||
quote = "";
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
continue;
|
||||
case "\"":
|
||||
switch (ch) {
|
||||
case "\"":
|
||||
quote = "";
|
||||
continue;
|
||||
case "\\":
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
switch (ch) {
|
||||
case "\\":
|
||||
escaped = true;
|
||||
continue;
|
||||
case "'":
|
||||
case "\"":
|
||||
quote = ch;
|
||||
hasToken = true;
|
||||
continue;
|
||||
case " ":
|
||||
case "\t":
|
||||
case "\n":
|
||||
if (!hasToken && current.length === 0)
|
||||
continue;
|
||||
args.push(current);
|
||||
current = "";
|
||||
hasToken = false;
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
}
|
||||
if (current.length > 0 || hasToken)
|
||||
args.push(current);
|
||||
return args;
|
||||
}
|
||||
|
||||
function launchDesktopEntry(desktopEntry, useNvidia) {
|
||||
if (!desktopEntry || !desktopEntry.command)
|
||||
return;
|
||||
@@ -232,8 +291,7 @@ Singleton {
|
||||
cmd = [nvidiaCommand].concat(cmd);
|
||||
|
||||
if (override?.extraFlags) {
|
||||
const extraArgs = override.extraFlags.trim().split(/\s+/).filter(arg => arg.length > 0);
|
||||
cmd = cmd.concat(extraArgs);
|
||||
cmd = cmd.concat(splitShellArgs(override.extraFlags));
|
||||
}
|
||||
|
||||
const userPrefix = SettingsData.launchPrefix?.trim() || "";
|
||||
@@ -413,6 +471,10 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
function softReboot() {
|
||||
Quickshell.execDetached(["systemctl", "soft-reboot"]);
|
||||
}
|
||||
|
||||
function poweroff() {
|
||||
if (SettingsData.customPowerActionPowerOff.length === 0) {
|
||||
Quickshell.execDetached(powerManagerCommand("poweroff"));
|
||||
|
||||
@@ -26,7 +26,7 @@ Singleton {
|
||||
return hash.toString(16).padStart(8, '0');
|
||||
}
|
||||
|
||||
function getArtworkUrl(player) {
|
||||
function _directArtworkUrl(player) {
|
||||
if (!player) return "";
|
||||
|
||||
let artUrl = player.trackArtUrl || "";
|
||||
@@ -54,6 +54,17 @@ Singleton {
|
||||
return "";
|
||||
}
|
||||
|
||||
function getArtworkUrl(player) {
|
||||
const directUrl = _directArtworkUrl(player);
|
||||
if (directUrl !== "")
|
||||
return directUrl;
|
||||
|
||||
const equivalent = MprisController.equivalentPlayers(player).find(candidate => {
|
||||
return candidate !== player && _directArtworkUrl(candidate) !== "";
|
||||
});
|
||||
return _directArtworkUrl(equivalent);
|
||||
}
|
||||
|
||||
function _commit(u, artKey, srcUrl) {
|
||||
resolvedArtUrl = u;
|
||||
_committedArtKey = u !== "" ? artKey : "";
|
||||
@@ -171,11 +182,25 @@ Singleton {
|
||||
onActivePlayerChanged: _updateArtUrl()
|
||||
|
||||
Connections {
|
||||
target: root.activePlayer
|
||||
ignoreUnknownSignals: true
|
||||
function onTrackTitleChanged() { root._updateArtUrl(); }
|
||||
function onTrackArtUrlChanged() { root._updateArtUrl(); }
|
||||
function onMetadataChanged() { root._updateArtUrl(); }
|
||||
target: MprisController
|
||||
function onAvailablePlayersChanged() {
|
||||
root._updateArtUrl();
|
||||
}
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
model: MprisController.availablePlayers
|
||||
delegate: Connections {
|
||||
required property MprisPlayer modelData
|
||||
target: modelData
|
||||
ignoreUnknownSignals: true
|
||||
function onIsPlayingChanged() { root._updateArtUrl(); }
|
||||
function onTrackTitleChanged() { root._updateArtUrl(); }
|
||||
function onTrackArtistChanged() { root._updateArtUrl(); }
|
||||
function onTrackAlbumChanged() { root._updateArtUrl(); }
|
||||
function onTrackArtUrlChanged() { root._updateArtUrl(); }
|
||||
function onMetadataChanged() { root._updateArtUrl(); }
|
||||
}
|
||||
}
|
||||
|
||||
function _trackKey() {
|
||||
@@ -201,8 +226,8 @@ Singleton {
|
||||
}
|
||||
_pendingArtKey = key;
|
||||
const url = getArtworkUrl(activePlayer);
|
||||
// Ignore Chrome's same-track thumbnail size updates.
|
||||
if (key !== "" && key === _committedArtKey)
|
||||
// Ignore duplicate notifications, but let a richer peer replace same-track art
|
||||
if (key !== "" && key === _committedArtKey && url === _committedSrcUrl)
|
||||
return;
|
||||
if (key !== "" && url !== "" && url === _committedSrcUrl) {
|
||||
// Chrome can publish track metadata before its new artwork URL.
|
||||
|
||||
@@ -231,11 +231,28 @@ Singleton {
|
||||
if (currentIndex === -1)
|
||||
currentIndex = 0;
|
||||
|
||||
let targetIndex;
|
||||
if (goToPrevious) {
|
||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||
let isRandom = false;
|
||||
if (targetScreenName) {
|
||||
isRandom = !!SessionData.getMonitorCyclingSettings(targetScreenName).random;
|
||||
} else {
|
||||
targetIndex = (currentIndex + 1) % wallpaperList.length;
|
||||
isRandom = !!SessionData.wallpaperCyclingRandom;
|
||||
}
|
||||
|
||||
let targetIndex;
|
||||
if (isRandom) {
|
||||
if (wallpaperList.length > 1) {
|
||||
do {
|
||||
targetIndex = Math.floor(Math.random() * wallpaperList.length);
|
||||
} while (targetIndex === currentIndex);
|
||||
} else {
|
||||
targetIndex = 0;
|
||||
}
|
||||
} else {
|
||||
if (goToPrevious) {
|
||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||
} else {
|
||||
targetIndex = (currentIndex + 1) % wallpaperList.length;
|
||||
}
|
||||
}
|
||||
const targetWallpaper = wallpaperList[targetIndex];
|
||||
if (!targetWallpaper || targetWallpaper === currentPath)
|
||||
|
||||
@@ -584,11 +584,9 @@ Item {
|
||||
}
|
||||
|
||||
function _frameEdgeInset(side) {
|
||||
if (!root.frameOwnsConnectedChrome || !root.screen)
|
||||
if (!root.frameOwnsConnectedChrome)
|
||||
return 0;
|
||||
const edges = SettingsData.getActiveBarEdgesForScreen(root.screen);
|
||||
const raw = edges.includes(side) ? SettingsData.frameBarSize : SettingsData.frameThickness;
|
||||
return Math.max(0, raw);
|
||||
return Math.max(0, SettingsData.frameEdgeReservation(root.screen, side));
|
||||
}
|
||||
|
||||
function _edgeGapFor(side, popupGap) {
|
||||
|
||||
@@ -379,6 +379,9 @@ Item {
|
||||
|
||||
animationsEnabled = true;
|
||||
shouldBeVisible = true;
|
||||
// Content-sized popouts lay out when contentWindow maps, while the geometry
|
||||
// handlers are still gated off. Re-snapshot so the surface isn't left short.
|
||||
_setSettledSurfaceGeometry();
|
||||
if (screen) {
|
||||
PopoutManager.showPopout(popoutHandle);
|
||||
opened();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[ColorScheme]
|
||||
active_colors={{colors.on_surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_container.default.hex}}, {{colors.outline.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.outline_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_primary.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary.default.hex}}, {{colors.on_primary.default.hex}}, {{colors.secondary.default.hex}}, {{colors.secondary.default.hex}}, {{colors.surface_container_low.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}
|
||||
active_colors={{colors.on_surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_container.default.hex}}, {{colors.outline.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.outline_variant.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.on_surface.default.hex}}, {{colors.background.default.hex}}, {{colors.background.default.hex}}, {{colors.shadow.default.hex}}, {{colors.primary.default.hex}}, {{colors.on_primary.default.hex}}, {{colors.secondary.default.hex}}, {{colors.secondary.default.hex}}, {{colors.surface_container_low.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}
|
||||
disabled_colors={{colors.on_surface_variant.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface_container.default.hex}}, {{colors.outline.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.outline_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.shadow.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}
|
||||
inactive_colors={{colors.on_surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface_container.default.hex}}, {{colors.outline.default.hex}}, {{colors.surface_variant.default.hex}}, {{colors.outline_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.shadow.default.hex}}, {{colors.secondary.default.hex}}, {{colors.on_secondary.default.hex}}, {{colors.secondary.default.hex}}, {{colors.secondary.default.hex}}, {{colors.surface_container_low.default.hex}}, {{colors.surface.default.hex}}, {{colors.surface.default.hex}}, {{colors.on_surface_variant.default.hex}}, {{colors.on_surface_variant.default.hex}}
|
||||
|
||||
|
||||
+1585
-1206
File diff suppressed because it is too large
Load Diff
@@ -198,7 +198,7 @@
|
||||
"2 seconds": "ثانيتان"
|
||||
},
|
||||
"2.4 GHz": {
|
||||
"2.4 GHz": ""
|
||||
"2.4 GHz": "2.4 GHz"
|
||||
},
|
||||
"20 minutes": {
|
||||
"20 minutes": "20 دقيقة"
|
||||
@@ -264,7 +264,7 @@
|
||||
"45 seconds": "45 ثانية"
|
||||
},
|
||||
"5 GHz": {
|
||||
"5 GHz": ""
|
||||
"5 GHz": "5 GHz"
|
||||
},
|
||||
"5 min before": {
|
||||
"5 min before": "قبل 5 دقائق"
|
||||
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "حول"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "لون التمييز"
|
||||
},
|
||||
@@ -378,7 +381,7 @@
|
||||
"Activate the DMS greeter? A terminal will open for sudo authentication. Run Sync after activation to apply your settings.": "هل تريد تفعيل شاشة ترحيب DMS؟ سيتم فتح نافذة طرفية لمصادقة sudo. قم بتشغيل 'مزامنة' (Sync) بعد التفعيل لتطبيق إعداداتك."
|
||||
},
|
||||
"Activates immediately": {
|
||||
"Activates immediately": ""
|
||||
"Activates immediately": "يتفعل فوراً"
|
||||
},
|
||||
"Activation": {
|
||||
"Activation": "التنشيط"
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "المحولات"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "عرض الوسائط التكيفي"
|
||||
},
|
||||
@@ -423,7 +429,7 @@
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms greeter sync --profile to publish their login-screen theme.": "هل تريد إضافة \"%1\" إلى مجموعة %2؟ يجب عليهم تسجيل الخروج ثم الدخول مرة أخرى، ثم تشغيل dms greeter sync --profile لنشر سمة شاشة تسجيل الدخول الخاصة بهم."
|
||||
},
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms-greeter sync --profile to publish their login-screen theme.": {
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms-greeter sync --profile to publish their login-screen theme.": ""
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms-greeter sync --profile to publish their login-screen theme.": "هل تريد إضافة \"%1\" إلى مجموعة %2؟ يجب عليهم تسجيل الخروج وإعادة تسجيل الدخول، ثم تشغيل dms-greeter sync --profile لنشر سمة شاشة تسجيل الدخول الخاصة بهم."
|
||||
},
|
||||
"Add Bar": {
|
||||
"Add Bar": "إضافة شريط"
|
||||
@@ -477,7 +483,7 @@
|
||||
"Add the new user to the %1 group so they can run dms greeter sync --profile.": "أضف المستخدم الجديد إلى مجموعة %1 حتى يتمكن من تشغيل dms greeter sync --profile."
|
||||
},
|
||||
"Add the new user to the %1 group so they can run dms-greeter sync --profile.": {
|
||||
"Add the new user to the %1 group so they can run dms-greeter sync --profile.": ""
|
||||
"Add the new user to the %1 group so they can run dms-greeter sync --profile.": "أضف المستخدم الجديد إلى المجموعة %1 ليتمكن من تشغيل dms-greeter sync --profile."
|
||||
},
|
||||
"Add the new user to the %1 group so they can use sudo.": {
|
||||
"Add the new user to the %1 group so they can use sudo.": "أضف المستخدم الجديد إلى مجموعة %1 حتى يتمكن من استخدام sudo."
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": "إضافة إلى التشغيل التلقائي"
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "ضبط ارتفاع الشريط عبر الحشوة الداخلية"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "تعديل عدد الأعمدة في وضع العرض الشبكي."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "ضبط مستوى الصوت لكل خطوة تمرير"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "عرض دائم عند وجود شاشة واحدة متصلة فقط"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "استخدم هذا التطبيق دائمًا لـ %1"
|
||||
},
|
||||
@@ -660,7 +678,7 @@
|
||||
"Applications and commands to start automatically when you log in": "التطبيقات والأوامر التي تبدأ تلقائياً عند تسجيل الدخول"
|
||||
},
|
||||
"Applies on the next greeter sync": {
|
||||
"Applies on the next greeter sync": ""
|
||||
"Applies on the next greeter sync": "يُطبق عند مزامنة الgreeter التالية"
|
||||
},
|
||||
"Apply Changes": {
|
||||
"Apply Changes": "تطبيق التغييرات"
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": "تم التحقق بنجاح!"
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "جاري المصادقة..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "المصادقة"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "تتطلب تغييرات المصادقة صلاحيات sudo. جاري فتح المحطة الطرفية حتى تتمكن من استخدام كلمة المرور أو بصمة الإصبع."
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "خطأ في المصادقة - حاول مرة أخرى"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": "فشل التحقق من الهوية - المحاولة %1 من %2"
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": "فشل التحقق - قد يؤدي ذلك إلى قفل حسابك"
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": "فشل التحقق - يرجى المحاولة مرة أخرى"
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "منح الإذن"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "إخفاء شريط التطبيقات تلقائياً"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": "تسجيل الدخول التلقائي"
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": "تغيير تسجيل الدخول التلقائي يحتاج إلى مزامنة"
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "متاح في أوضاع العرض التفصيلي والتوقعات"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": "بانتظار التحقق ببصمة الإصبع"
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": "بانتظار التحقق ببصمة الإصبع أو مفتاح الأمان"
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": "بانتظار التحقق بمفتاح الأمان"
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "رجوع"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": "العودة إلى قائمة المستخدمين"
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "النظام الخلفي"
|
||||
},
|
||||
@@ -1035,7 +1023,7 @@
|
||||
"Balanced palette with focused accents (default).": "ألوان متوازنة بلمسات محددة (افتراضي)."
|
||||
},
|
||||
"Band": {
|
||||
"Band": ""
|
||||
"Band": "Band"
|
||||
},
|
||||
"Bar": {
|
||||
"Bar": "شريط المهام"
|
||||
@@ -1293,7 +1281,7 @@
|
||||
"Calendar Backend": "الخلفية التقويمية"
|
||||
},
|
||||
"Calls / Headset": {
|
||||
"Calls / Headset": ""
|
||||
"Calls / Headset": "المكالمات / سماعة الرأس"
|
||||
},
|
||||
"Camera": {
|
||||
"Camera": "الكاميرا"
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": "اختر مجلد خلفية الشاشة"
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "اختر مكان ظهور النوافذ المنبثقة للإشعارات على الشاشة"
|
||||
},
|
||||
@@ -1581,16 +1572,16 @@
|
||||
"Close Window": "إغلاق النافذة"
|
||||
},
|
||||
"Codec switched successfully": {
|
||||
"Codec switched successfully": ""
|
||||
"Codec switched successfully": "تم تبديل الترميز بنجاح"
|
||||
},
|
||||
"Codec switching is unavailable because WirePlumber was not found": {
|
||||
"Codec switching is unavailable because WirePlumber was not found": ""
|
||||
"Codec switching is unavailable because WirePlumber was not found": "تبديل الترميز غير متاح لأن WirePlumber لم يتم العثور عليه"
|
||||
},
|
||||
"Codec switching is unavailable because pactl was not found": {
|
||||
"Codec switching is unavailable because pactl was not found": "تبديل الترميز غير متاح لأنه لم يتم العثور على pactl"
|
||||
},
|
||||
"Codec switching is unavailable. WirePlumber wpexec was not found.": {
|
||||
"Codec switching is unavailable. WirePlumber wpexec was not found.": ""
|
||||
"Codec switching is unavailable. WirePlumber wpexec was not found.": "تبديل الترميز غير متاح. لم يتم العثور على WirePlumber wpexec."
|
||||
},
|
||||
"Color": {
|
||||
"Color": "اللون"
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "إعدادات مدير النوافذ"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "تنسيق التكوين"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "التباين"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": "مساهم"
|
||||
},
|
||||
@@ -1905,7 +1902,7 @@
|
||||
"Corners & Background": "الزوايا والخلفية"
|
||||
},
|
||||
"Couldn't load hotspot password": {
|
||||
"Couldn't load hotspot password": ""
|
||||
"Couldn't load hotspot password": "تعذر تحميل كلمة مرور نقطة الاتصال"
|
||||
},
|
||||
"Count Only": {
|
||||
"Count Only": "العد فقط"
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "تعطيل المخرج"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "غير نشط"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "الباب مفتوح"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": "اسحب أداة من مقبضها إلى هنا لإعادة ترتيبها أو إسقاطها في مجموعة أخرى"
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": "ديناميكي: منحنى زنبركي مع تجاوز - الدخول يتجاوز هدفه لفترة وجيزة ثم يستقر. معبر وحيوي."
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": "كشف حافة التمرير"
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "إفراغ المهملات (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "تمكين عمق لون 10 بت للحصول على نطاق ألوان أوسع ودعم HDR"
|
||||
},
|
||||
@@ -2706,7 +2721,7 @@
|
||||
"Enable WiFi": "تمكين الـ Wi-Fi"
|
||||
},
|
||||
"Enable WiFi before starting the hotspot.": {
|
||||
"Enable WiFi before starting the hotspot.": ""
|
||||
"Enable WiFi before starting the hotspot.": "قم بتمكين الواي فاي قبل بدء نقطة الاتصال."
|
||||
},
|
||||
"Enable a custom override below to set per-bar shadow intensity, opacity, and color.": {
|
||||
"Enable a custom override below to set per-bar shadow intensity, opacity, and color.": "قم بتفعيل تجاوز مخصص أدناه لضبط شدة الظل، والشفافية، واللون لكل شريط."
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "ادخال كلمة المرور ل "
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "أدخل مفتاح المرور هذا في "
|
||||
},
|
||||
@@ -2934,7 +2952,7 @@
|
||||
"Failed to check pin limit": "فشل التحقق من حد التثبيت"
|
||||
},
|
||||
"Failed to configure hotspot": {
|
||||
"Failed to configure hotspot": ""
|
||||
"Failed to configure hotspot": "فشل في إعداد نقطة الاتصال"
|
||||
},
|
||||
"Failed to connect VPN": {
|
||||
"Failed to connect VPN": "فشل الاتصال بشبكة VPN"
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "فشل جلب رمز QR للشبكة: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": "فشل في إنشاء تجاوز systemd"
|
||||
},
|
||||
@@ -3066,7 +3087,7 @@
|
||||
"Failed to run 'dms greeter status'. Ensure DMS is installed and dms is in PATH.": "فشل تشغيل 'dms greeter status'. تأكد من تثبيت DMS وأن dms موجود في مسار النظام (PATH)."
|
||||
},
|
||||
"Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.": {
|
||||
"Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.": ""
|
||||
"Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.": "فشل تشغيل 'dms-greeter status'. تأكد من تثبيت حزمة dms-greeter."
|
||||
},
|
||||
"Failed to save VPN credentials": {
|
||||
"Failed to save VPN credentials": "فشل حفظ بيانات اعتماد VPN"
|
||||
@@ -3123,13 +3144,13 @@
|
||||
"Failed to start connection to %1": "فشل بدء الاتصال بـ %1"
|
||||
},
|
||||
"Failed to start hotspot": {
|
||||
"Failed to start hotspot": ""
|
||||
"Failed to start hotspot": "فشل بدء نقطة الاتصال"
|
||||
},
|
||||
"Failed to stop hotspot": {
|
||||
"Failed to stop hotspot": ""
|
||||
"Failed to stop hotspot": "فشل إيقاف نقطة الاتصال"
|
||||
},
|
||||
"Failed to switch codec": {
|
||||
"Failed to switch codec": ""
|
||||
"Failed to switch codec": "تعذر تبديل الترميز"
|
||||
},
|
||||
"Failed to unpin entry": {
|
||||
"Failed to unpin entry": "فشل في إلغاء تثبيت الإدخال"
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "الأعلام"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": "Flatpak"
|
||||
},
|
||||
@@ -3621,7 +3648,7 @@
|
||||
"Greeter group members can sync their login-screen theme with dms greeter sync --profile after logging out and back in.": "يمكن لأعضاء مجموعة الترحيب مزامنة سمة شاشة تسجيل الدخول الخاصة بهم باستخدام dms greeter sync --profile بعد تسجيل الخروج والدخول مرة أخرى."
|
||||
},
|
||||
"Greeter group members can sync their login-screen theme with dms-greeter sync --profile after logging out and back in.": {
|
||||
"Greeter group members can sync their login-screen theme with dms-greeter sync --profile after logging out and back in.": ""
|
||||
"Greeter group members can sync their login-screen theme with dms-greeter sync --profile after logging out and back in.": "يمكن لأعضاء مجموعة Greeter مزامنة سمة شاشة تسجيل الدخول الخاصة بهم باستخدام الأمر dms-greeter sync --profile بعد تسجيل الخروج والدخول مرة أخرى."
|
||||
},
|
||||
"Greeter group:": {
|
||||
"Greeter group:": "مجموعة الترحيب:"
|
||||
@@ -3837,22 +3864,22 @@
|
||||
"Hotkey overlay title (optional)": "عنوان تراكب اختصار لوحة المفاتيح (اختياري)"
|
||||
},
|
||||
"Hotspot": {
|
||||
"Hotspot": ""
|
||||
"Hotspot": "نقطة اتصال"
|
||||
},
|
||||
"Hotspot activation failed.": {
|
||||
"Hotspot activation failed.": ""
|
||||
"Hotspot activation failed.": "فشل تنشيط نقطة الاتصال."
|
||||
},
|
||||
"Hotspot name": {
|
||||
"Hotspot name": ""
|
||||
"Hotspot name": "اسم نقطة الاتصال"
|
||||
},
|
||||
"Hotspot saved": {
|
||||
"Hotspot saved": ""
|
||||
"Hotspot saved": "تم حفظ نقطة الاتصال"
|
||||
},
|
||||
"Hotspot started": {
|
||||
"Hotspot started": ""
|
||||
"Hotspot started": "تم تشغيل نقطة الاتصال"
|
||||
},
|
||||
"Hotspot stopped": {
|
||||
"Hotspot stopped": ""
|
||||
"Hotspot stopped": "تم إيقاف نقطة الاتصال"
|
||||
},
|
||||
"Hour": {
|
||||
"Hour": "الساعة"
|
||||
@@ -3912,7 +3939,7 @@
|
||||
"IP address or hostname": "عنوان IP أو اسم المضيف"
|
||||
},
|
||||
"IP sharing setup failed. Check that dnsmasq is installed.": {
|
||||
"IP sharing setup failed. Check that dnsmasq is installed.": ""
|
||||
"IP sharing setup failed. Check that dnsmasq is installed.": "فشل إعداد مشاركة بروتوكول الإنترنت (IP). تأكد من تثبيت dnsmasq."
|
||||
},
|
||||
"ISO Date": {
|
||||
"ISO Date": "تاريخ ISO"
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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.": "إذا كان الحقل مخفياً، فسيظهر بمجرد الضغط على مفتاح."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "تجاهل تمامًا"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": "الاحتفاظ بتعديلاتي"
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": "الاحتفاظ في الشريط"
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "القسم الأيسر"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "خفيف"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "مقفل"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "جاري تسجيل الدخول..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "تسجيل الدخول"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "التحكم في مستوى صوت الميكروفون"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "القسم الأوسط"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "نقاط التحميل"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "نقرات الفأرة تمر عبر الشريط إلى النوافذ خلفه"
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "أصلي: مصير النظام الأساسي (FreeType)."
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": "تمرير لوحة اللمس الطبيعي"
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "بدون تقريب الزوايا"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "بدون ظل"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "لم يتم الكشف عنه"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": "غير مدرج؟"
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "غير مقترن"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "مفعل"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "مفعل للأبد"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "تعديل حرارة الألوان بناءً على قواعد الوقت أو الموقع فقط."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": "فقط عند استخدام البطارية"
|
||||
},
|
||||
@@ -5511,7 +5562,7 @@
|
||||
"Open in terminal": "فتح في الطرفية"
|
||||
},
|
||||
"Open network": {
|
||||
"Open network": ""
|
||||
"Open network": "شبكة مفتوحة"
|
||||
},
|
||||
"Open search bar to find text": {
|
||||
"Open search bar to find text": "افتح شريط البحث للعثور على نص"
|
||||
@@ -5541,7 +5592,7 @@
|
||||
"Opens the connected launcher in Connected Frame Mode.": "يفتح المشغل المتصل في وضع الإطار المتصل."
|
||||
},
|
||||
"Optional": {
|
||||
"Optional": ""
|
||||
"Optional": "اختياري"
|
||||
},
|
||||
"Optional description": {
|
||||
"Optional description": "وصف اختياري"
|
||||
@@ -5553,7 +5604,7 @@
|
||||
"Optional state-based conditions applied to the first match.": "شروط اختيارية تعتمد على الحالة مطبقة على التطابق الأول."
|
||||
},
|
||||
"Optional; leave blank for open hotspot": {
|
||||
"Optional; leave blank for open hotspot": ""
|
||||
"Optional; leave blank for open hotspot": "اختياري؛ اتركه فارغاً لنقطة اتصال مفتوحة"
|
||||
},
|
||||
"Options": {
|
||||
"Options": "الخيارات"
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "جاري الاقتران..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "غائم جزئياً"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "تم تحديث كلمة المرور"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "كلمة المرور..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "كلمات المرور غير متطابقة."
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "المؤشر"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "تم تعطيل تكامل Polkit. تتطلب إدارة المستخدمين Polkit لرفع الامتيازات."
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "الضغط"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "منع إيقاف تشغيل الشاشة"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "البروتوكول"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": "Qt"
|
||||
},
|
||||
@@ -6189,7 +6249,7 @@
|
||||
"Re-enter password": "أعد إدخال كلمة المرور"
|
||||
},
|
||||
"Re-enter the password before saving.": {
|
||||
"Re-enter the password before saving.": ""
|
||||
"Re-enter the password before saving.": "أعد إدخال كلمة المرور قبل الحفظ."
|
||||
},
|
||||
"Reach local network devices while using an exit node": {
|
||||
"Reach local network devices while using an exit node": "الوصول إلى أجهزة الشبكة المحلية أثناء استخدام عقدة الخروج"
|
||||
@@ -6201,7 +6261,7 @@
|
||||
"Read:": "قراءة:"
|
||||
},
|
||||
"Ready": {
|
||||
"Ready": ""
|
||||
"Ready": "مستعد"
|
||||
},
|
||||
"Reason": {
|
||||
"Reason": "السبب"
|
||||
@@ -6249,7 +6309,7 @@
|
||||
"Release": "تحرير"
|
||||
},
|
||||
"Release to confirm": {
|
||||
"Release to confirm": ""
|
||||
"Release to confirm": "أفلِت للتأكيد"
|
||||
},
|
||||
"Reload From Disk": {
|
||||
"Reload From Disk": "إعادة التحميل من القرص"
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": "يتطلب تذكر المستخدم الأخير والجلسة. قم بتمكين تلك الخيارات أولاً."
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "إعادة تعيين"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "عكس اتجاه التمرير"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "عكس اتجاه تبديل اسطح المكتب عند التمرير فوق الشريط"
|
||||
},
|
||||
@@ -6519,7 +6588,7 @@
|
||||
"Run paru/yay with AUR enabled when 'Update All' is clicked.": "تشغيل paru/yay مع تمكين AUR عند النقر على \"تحديث الكل\"."
|
||||
},
|
||||
"Running": {
|
||||
"Running": ""
|
||||
"Running": "مشغل"
|
||||
},
|
||||
"Running Apps": {
|
||||
"Running Apps": "التطبيقات المشغلة"
|
||||
@@ -6546,13 +6615,13 @@
|
||||
"SMS sent successfully": "تم إرسال الرسالة القصيرة بنجاح"
|
||||
},
|
||||
"SSID": {
|
||||
"SSID": ""
|
||||
"SSID": "SSID"
|
||||
},
|
||||
"Saturation": {
|
||||
"Saturation": "التشبع"
|
||||
},
|
||||
"Save & Start": {
|
||||
"Save & Start": ""
|
||||
"Save & Start": "حفظ وابدأ"
|
||||
},
|
||||
"Save Notepad File": {
|
||||
"Save Notepad File": "حفظ ملف الملاحظة"
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "تمرير GitHub"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "عجلة التمرير"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "شريطي"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "البحث في إجراءات التطبيقات"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "اختر خوارزمية لوحة الألوان المستخدمة لاستخراج الألوان من خلفية الشاشة"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "اختر مستخدماً..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "اختر مزودي اختصارات المفاتيح المراد تضمينهم"
|
||||
},
|
||||
@@ -6903,13 +6975,13 @@
|
||||
"Set the percentage at which the battery is considered low.": "حدد النسبة المئوية التي تعتبر عندها البطارية منخفضة."
|
||||
},
|
||||
"Set up a WiFi hotspot for sharing this connection.": {
|
||||
"Set up a WiFi hotspot for sharing this connection.": ""
|
||||
"Set up a WiFi hotspot for sharing this connection.": "قم بإعداد نقطة اتصال WiFi لمشاركة هذا الاتصال."
|
||||
},
|
||||
"Set up hotspot": {
|
||||
"Set up hotspot": ""
|
||||
"Set up hotspot": "إعداد نقطة اتصال"
|
||||
},
|
||||
"Set up hotspot in Settings": {
|
||||
"Set up hotspot in Settings": ""
|
||||
"Set up hotspot in Settings": "إعداد نقطة اتصال في الإعدادات"
|
||||
},
|
||||
"Setting": {
|
||||
"Setting": "إعداد"
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "إظهار"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "إظهار الطرف الثالث"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": "إظهار إشعار عندما تصل البطارية إلى حد الشحن."
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": "إظهار نافذة تحذير منبثقة عندما تكون البطارية منخفضة."
|
||||
},
|
||||
@@ -7413,7 +7491,7 @@
|
||||
"Start": "ابدأ"
|
||||
},
|
||||
"Start Hotspot?": {
|
||||
"Start Hotspot?": ""
|
||||
"Start Hotspot?": "بدء نقطة الاتصال؟"
|
||||
},
|
||||
"Start KDE Connect or Valent to use this plugin": {
|
||||
"Start KDE Connect or Valent to use this plugin": "ابدأ KDE Connect أو Valent لاستخدام هذه الملحق"
|
||||
@@ -7422,19 +7500,19 @@
|
||||
"Start typing your notes here...": "ابدأ بكتابة ملاحظاتك هنا..."
|
||||
},
|
||||
"Starting hotspot...": {
|
||||
"Starting hotspot...": ""
|
||||
"Starting hotspot...": "جاري بدء نقطة الاتصال..."
|
||||
},
|
||||
"Starting the hotspot will disconnect WiFi from \"%1\" — the radio can't do both at once. Sharing internet then requires another connection, such as Ethernet.": {
|
||||
"Starting the hotspot will disconnect WiFi from \"%1\" — the radio can't do both at once. Sharing internet then requires another connection, such as Ethernet.": ""
|
||||
"Starting the hotspot will disconnect WiFi from \"%1\" — the radio can't do both at once. Sharing internet then requires another connection, such as Ethernet.": "سيؤدي بدء نقطة الاتصال إلى قطع اتصال WiFi بـ \"%1\" — لا يمكن للراديو القيام بالأمرين معاً في وقت واحد. تتطلب مشاركة الإنترنت بعد ذلك اتصالاً آخر، مثل الإيثرنت (Ethernet)."
|
||||
},
|
||||
"Starting...": {
|
||||
"Starting...": ""
|
||||
"Starting...": "جاري البدء..."
|
||||
},
|
||||
"State": {
|
||||
"State": "الحالة"
|
||||
},
|
||||
"Stop": {
|
||||
"Stop": ""
|
||||
"Stop": "قف"
|
||||
},
|
||||
"Stop ignoring %1": {
|
||||
"Stop ignoring %1": "إلغاء تجاهل %1"
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "تعليق ثم إسبات"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "موقع Sway"
|
||||
},
|
||||
@@ -7560,7 +7641,7 @@
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms greeter sync --profile instead of a primary user sync.": "يقوم المزامنة بتطبيق السمة والإعدادات الخاصة بك على شاشة تسجيل الدخول. يجب على المستخدمين المشتركين تشغيل dms greeter sync --profile بدلاً من المزامنة للمستخدم الأساسي."
|
||||
},
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.": {
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.": ""
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.": "يطبق Sync السمة والإعدادات الخاصة بك على شاشة تسجيل الدخول. يجب على المستخدمين المشتركين تشغيل dms-greeter sync --profile بدلاً من المزامنة للمستخدم الأساسي."
|
||||
},
|
||||
"Sync completed successfully.": {
|
||||
"Sync completed successfully.": "تمت المزامنة بنجاح."
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Tailscale غير متاح"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "محطة طرفية"
|
||||
},
|
||||
@@ -7665,7 +7758,7 @@
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.": "فشل الاحتياط للطرفية. قم بتثبيت أحد محاكيات الطرفية المدعومة أو قم بتشغيل 'dms greeter sync' يدوياً."
|
||||
},
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter sync' manually.": {
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter sync' manually.": ""
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter sync' manually.": "فشل الاحتياط للطرفية. قم بتثبيت أحد محاكيات الطرفية المدعومة أو قم بتشغيل 'dms-greeter sync' يدوياً."
|
||||
},
|
||||
"Terminal fallback opened. Complete authentication there; it will close automatically when done.": {
|
||||
"Terminal fallback opened. Complete authentication there; it will close automatically when done.": "تم فتح الاحطياط للطرفية. أكمل المصادقة هناك؛ سيتم إغلاقه تلقائيًا عند الانتهاء."
|
||||
@@ -7713,7 +7806,7 @@
|
||||
"The DMS_SOCKET environment variable is not set or the socket is unavailable. Automated plugin management requires the DMS_SOCKET.": "متغير البيئة DMS_SOCKET غير معين أو أن المقبس غير متاح. تتطلب الإدارة التلقائية للملحقات وجود DMS_SOCKET."
|
||||
},
|
||||
"The WiFi adapter could not start access point mode.": {
|
||||
"The WiFi adapter could not start access point mode.": ""
|
||||
"The WiFi adapter could not start access point mode.": "تعذر على محول WiFi بدء وضع نقطة الوصول."
|
||||
},
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": {
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": "الإعدادات أدناه ستعدل إعدادات GTK و Qt الخاصة بك. إذا كنت ترغب في الحفاظ على تكويناتك الحالية، يرجى أخذ نسخة احتياطية منها (qt5ct.conf|qt6ct.conf و ~/.config/gtk-3.0|gtk-4.0)."
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": "تنطبق القاعدة على أي نافذة تطابق واحدة من هذه."
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "المظهر والألوان"
|
||||
},
|
||||
@@ -7788,7 +7884,7 @@
|
||||
"This will delete all unpinned entries. %1 pinned entries will be kept.": "سيؤدي هذا إلى حذف جميع الإدخالات غير المثبتة. سيتم الاحتفاظ بـ %1 من الإدخالات المثبتة."
|
||||
},
|
||||
"This will disconnect WiFi from \"%1\" — the radio can't host a hotspot and stay connected at the same time. Internet sharing will need another connection, such as Ethernet.": {
|
||||
"This will disconnect WiFi from \"%1\" — the radio can't host a hotspot and stay connected at the same time. Internet sharing will need another connection, such as Ethernet.": ""
|
||||
"This will disconnect WiFi from \"%1\" — the radio can't host a hotspot and stay connected at the same time. Internet sharing will need another connection, such as Ethernet.": "سيؤدي هذا إلى قطع اتصال WiFi من \"%1\" — لا يمكن للراديو استضافة نقطة اتصال والبقاء متصلاً في الوقت نفسه. ستحتاج مشاركة الإنترنت إلى اتصال آخر، مثل إيثرنت."
|
||||
},
|
||||
"This will permanently delete all clipboard history.": {
|
||||
"This will permanently delete all clipboard history.": "سيؤدي هذا إلى حذف كل سجل الحافظة نهائياً."
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "الكثير من المحاولات - تم القفل"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "عدد كبير جداً من المحاولات الفاشلة - قد يكون الحساب مقفلاً"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "الأدوات"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "المس مفتاح الأمان الخاص بك..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "تحويل"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "إيقاف التشغيل الآن"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "اكتب حرفين على الأقل"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "إلغاء حفظ"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "محدث"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "يجب أن يبدأ اسم المستخدم بحرف صغير أو شرطة سفلية ويحتوي فقط على أحرف صغيرة، أرقام، شرطات، أو شرطات سفلية."
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "اسم المستخدم..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "المستخدمون"
|
||||
},
|
||||
@@ -8481,13 +8583,13 @@
|
||||
"W": "W"
|
||||
},
|
||||
"WCAG %1 body": {
|
||||
"WCAG %1 body": ""
|
||||
"WCAG %1 body": "WCAG %1 الجسم"
|
||||
},
|
||||
"WPA/WPA2": {
|
||||
"WPA/WPA2": "WPA/WPA2"
|
||||
},
|
||||
"WPA2 password": {
|
||||
"WPA2 password": ""
|
||||
"WPA2 password": "كلمة مرور WPA2"
|
||||
},
|
||||
"Wallpaper": {
|
||||
"Wallpaper": "خلفية الشاشة"
|
||||
@@ -8577,10 +8679,10 @@
|
||||
"WiFi enabled": "الWi-Fi مفعل"
|
||||
},
|
||||
"WiFi is disabled": {
|
||||
"WiFi is disabled": ""
|
||||
"WiFi is disabled": "WiFi غير مفعل"
|
||||
},
|
||||
"WiFi is disabled. You can still edit and save hotspot settings, but starting the hotspot requires WiFi to be enabled.": {
|
||||
"WiFi is disabled. You can still edit and save hotspot settings, but starting the hotspot requires WiFi to be enabled.": ""
|
||||
"WiFi is disabled. You can still edit and save hotspot settings, but starting the hotspot requires WiFi to be enabled.": "WiFi غير مفعل. لا يزال بإمكانك تعديل وحفظ إعدادات نقطة الاتصال، ولكن بدء تشغيل نقطة الاتصال يتطلب تمكين WiFi."
|
||||
},
|
||||
"WiFi is off": {
|
||||
"WiFi is off": "WiFi غير مفعل"
|
||||
@@ -8640,7 +8742,7 @@
|
||||
"Width of window border and focus ring": "عرض حدود النافذة وحلقة التركيز"
|
||||
},
|
||||
"Will disconnect \"%1": {
|
||||
"Will disconnect \"%1\"": ""
|
||||
"Will disconnect \"%1\"": "سيتم قطع اتصال \"%1\""
|
||||
},
|
||||
"Wind": {
|
||||
"Wind": "الرياح"
|
||||
@@ -8751,10 +8853,10 @@
|
||||
"Your compositor does not support background blur (ext-background-effect-v1)": "مدير النوافذ الخاص بك لا يدعم ميزة تخبيش الخلفية (ext-background-effect-v1)"
|
||||
},
|
||||
"Your hotspot is running.": {
|
||||
"Your hotspot is running.": ""
|
||||
"Your hotspot is running.": "نقطة الاتصال الخاصة بك قيد التشغيل."
|
||||
},
|
||||
"Your hotspot profile is saved and ready to start.": {
|
||||
"Your hotspot profile is saved and ready to start.": ""
|
||||
"Your hotspot profile is saved and ready to start.": "تم حفظ ملف تعريف نقطة الاتصال الخاص بك وهو جاهز للبدء."
|
||||
},
|
||||
"Your system is up to date!": {
|
||||
"Your system is up to date!": "نظامك محدث!"
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "متصل"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "Pri"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "Akcenta Koloro"
|
||||
},
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "Adaptiloj"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "Adapta aŭdvidaĵa larĝo"
|
||||
},
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": "Aldoni al memlanĉo"
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "Alĝustigu la baran altecon per interna remburaĵo"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "Alĝustigi la nombron de kolumnoj en krada vida reĝimo."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "Alĝustigi la laŭtecon per rula dentaĵo"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "Ĉiam montri kiam estas nur unu konektita ekrano"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "Ĉiam uzu ĉi tiun programon por %1"
|
||||
},
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": ""
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "Aŭtentigante..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "Aŭtentigo"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "Aŭtentigaj ŝanĝoj bezonas sudo-on. Malfermas terminalon por ke vi povu uzi pasvorton aŭ fingrospuron."
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "Aŭtentiga eraro - provu denove"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": ""
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": ""
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": ""
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "Rajtigi"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "Aŭtomata kaŝo de doko"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": "Aŭtomata ensaluto"
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": ""
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "Havebla en Detala kaj Prognoza vidreĝimoj"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": ""
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": ""
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": ""
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "Reen"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": "Reen al listo de uzantoj"
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "Interna programo"
|
||||
},
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": ""
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "Elekti kie sciigaj ŝprucfenestroj aperas sur la ekrano"
|
||||
},
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "Komponilaj agordoj"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "Agorda formato"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "Kontrasto"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": "kontribuanto"
|
||||
},
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "Malŝalti eligon"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "Malŝaltita"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "Pordo malfermita"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": ""
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": "Dinamika: Printempa bezier kun superfluo — eniro nelonge superas sian celon kaj poste ekloĝas. "
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": ""
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "Malplenigi Rubujon (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "Ŝalti 10-bitan kolorprofundon por pli larĝa kolorgamo kaj HDR-subteno"
|
||||
},
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "Enigu pasvorton por "
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "Enigu ĉi tiun pasŝlosilon sur "
|
||||
},
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "Malsukcesis alporti retan QR-kodon: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": "Malsukcesis generi sisteman anstataŭigon"
|
||||
},
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "Flagoj"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": ""
|
||||
},
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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 la kampo estas kaŝita, ĝi aperos tuj kiam klavo estas premita."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "Tute malatenti"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": "Konservu Miajn Redaktojn"
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": "Konservu en Trinkejo"
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "Maldekstra sekcio"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "Hela"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "Ŝlosita"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "Salutante..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "Saluti"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "Regado de mikrofona laŭteco"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "Meza sekcio"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "Munt-punktoj"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "Musklakoj pasas tra la trinkejo al fenestroj malantaŭ ĝi"
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "Denaska: platforma bildilo (FreeType)."
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": "Natura Tuŝpad Scrolling"
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "Sen rondigo"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "Sen ombro"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "Ne detektita"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": "Ĉu ne en listo?"
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "Ne parigita"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "Ŝaltita"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "Sur senfine"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "Nur alĝustigi gamaon baze de tempo- aŭ lok-reguloj."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": ""
|
||||
},
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "Parigante..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "Parte nuba"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "Pasvorto ĝisdatigita"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "Pasvorto..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "Pasvortoj ne kongruas."
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "Kursoro"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "Polkit-integriĝo estas malŝaltita. "
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "Premo"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "Malhelpi ekranan templimon"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "Protokolo"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": "Qt"
|
||||
},
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": "Necesas memori la lastan uzanton kaj sesion. "
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "Rekomencigi"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "Inversigi rulan direkton"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "Inversigi direkton de laborspaca ŝanĝo kiam oni rulas super la breto"
|
||||
},
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "Ruli GitHub-on"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "Rul-radeto"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "Rulado"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "Serĉi aplikaĵajn agojn"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "Elekti la paletran algoritmon uzatan por fonbild-bazitaj koloroj"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "Elektu uzanton..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "Elektu kiujn provizantojn de klavoligiloj inkluzivi"
|
||||
},
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "Montri"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "Montri triajn partiojn"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": ""
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": ""
|
||||
},
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "Dormeti poste pasivigi"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "Retejo de Sway"
|
||||
},
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Vostoskalo ne havebla"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "Terminalo"
|
||||
},
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": "La regulo validas por iu ajn fenestro kongrua kun unu el ĉi tiuj."
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "Etoso kaj koloroj"
|
||||
},
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "Tro da provoj - ŝlosita"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "Tro da malsukcesaj provoj - la konto eble estas ŝlosita"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "Iloj"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "Tuŝu vian sekurecŝlosilon..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "Transformi"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "Malŝaltu nun"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "Tajpu almenaŭ 2 signojn"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "Malestimata"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "Aktualigita"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "Uzantnomo devas komenci per minuskla litero aŭ substreko kaj enhavi nur minusklajn literojn, ciferojn, streketojn aŭ substrekojn."
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "Uzantnomo..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "Uzantoj"
|
||||
},
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "aligita"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
|
||||
+1821
-1716
File diff suppressed because it is too large
Load Diff
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "درباره"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "رنگ تأکیدی"
|
||||
},
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "آداپتورها"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "پهنای رسانه سازگار"
|
||||
},
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": ""
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "ارتفاع نوار را از طریق فاصله درونی تنظیم کن"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "تعداد ستونها در حالت نمای جدولی را تنظیم کنید."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "تنظیم حجم صدا بهازای هر پله اسکرول"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "همیشه وقتی فقط یک نمایشگر متصل وجود دارد، نشان بده"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "همیشه از این برنامه برای %1 استفاده کن"
|
||||
},
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": ""
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "درحال احراز هویت..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "احراز هویت"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "تغییرات احراز هویت به sudo نیاز دارد. درحال باز کردن ترمینال تا بتوانید از گذرواژه یا اثرانگشت استفاده کنید."
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "خطای احراز هویت - دوباره تلاش کنید"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": ""
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": ""
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": ""
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "اجازه دادن"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "پنهان خودکار داک"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": ""
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": ""
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "در حالتهای نمایش با جزئیات و پیشبینی در دسترس است"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": ""
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": ""
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": ""
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "بازگشت"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": ""
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "بکاند"
|
||||
},
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": ""
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "انتخاب کنید پاپآپ اعلان کجای صفحه ظاهر شود"
|
||||
},
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "تنظیمات کامپازیتور"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "تنظیم قالب"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "کنتراست"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": ""
|
||||
},
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "غیرفعالکردن خروجی"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "غیرفعال"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "درب باز"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": ""
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": ""
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": ""
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "خالی کردن زبالهدان (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "فعالکردن عمق رنگ ۱۰ بیت برای طیف رنگ عریضتر و پشتیبانی HDR"
|
||||
},
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "ورود گذرواژه برای "
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "ورود این کلید عبور در "
|
||||
},
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "دریافت کد QR شبکه ناموفق بود: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": ""
|
||||
},
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "فلگها"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": ""
|
||||
},
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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.": "اگر فیلد پنهان باشد، به محض فشردن کلید پدیدار میشود."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "کلاً نادیده بگیر"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": ""
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": ""
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "بخش چپ"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "کمرنگ"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "قفل شده"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "درحال ورود..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "ورود"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "کنترل حجم صدای میکروفون"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "بخش میانی"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "نقاط اتصال"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "کلیک موس از نوار عبور کند تا به پنجره پشت آن برسد"
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "محلی: نماپرداز سکو (FreeType)."
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": ""
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "بدون گردی"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "بدون سایه"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "تشخیص داده نشد"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": ""
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "جفت نشده"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "روشن"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "روشن بصورت نامحدود"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "گاما را فقط بر اساس قواعد زمانی یا مکانی تنظیم کن."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": ""
|
||||
},
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "درحال جفت شدن..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "نیمه ابری"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "گذرواژه بروز شد"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "گذرواژه..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "گذرواژهها مطابقت ندارند."
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "اشارهگر"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "یکپارچهسازی polkit غیرفعال است. مدیریت کاربر برای بالابردن دسترسیها نیاز به polkit دارد."
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "فشار"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "وقفه جلوگیری از خاموششدن صفحه"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "پروتکل"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": ""
|
||||
},
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": ""
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "تنظیم مجدد"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "جهت اسکرول معکوس"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "جهت تغییر محیطکار را هنگام اسکرولکردن روی نوار معکوس کن"
|
||||
},
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "گیتهاب اسکرول"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "چرخ اسکرول"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "اسکرولینگ"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "جستجوی اقدام برنامهها"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "انتخاب الگوریتم پالت رنگی استفاده شده برای رنگهای بر اساس تصویر پسزمینه"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "انتخاب کاربر..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "انتخاب کنید که کدام ارائه دهنده نگاشتکلیدها include شود"
|
||||
},
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "نمایش"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "نمایش شخص ثالث"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": ""
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": ""
|
||||
},
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "تعلیق سپس هایبرنیت"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "وبسایت Sway"
|
||||
},
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Tailcale در دسترس نیست"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "ترمینال"
|
||||
},
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": ""
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "تم و رنگها"
|
||||
},
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "تلاشهای بیش از حد - حساب بسته شد"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "تعداد تلاشهای ناموفق زیاد است - حساب ممکن است قفل شده باشد"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "ابزارها"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "کلید امنیتی خود را لمس کنید..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "تبدیل"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "الان خاموش کن"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "حداقل ۲ کاراکتر تایپ کنید"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "لغو اعتماد"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "بروز"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "نام کاربری باید با حرف کوچک یا زیرخط شروع شود و تنها شامل حروف کوچک، اعداد، خط تیره یا زیرخط باشد."
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "نام کاربری..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "کاربران"
|
||||
},
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "پیوست شد"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "براندون"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "À propos"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "Couleur d’accentuation"
|
||||
},
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "Adaptateurs"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": ""
|
||||
},
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": ""
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": ""
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "Ajuster le nombre de colonnes en mode vue en grille."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "Ajuster le volume à la molette"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "Toujours afficher lorsqu’un seul écran est connecté"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": ""
|
||||
},
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": ""
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "Authentification..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "Authentification"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": ""
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "Erreur d'authentification - essayez à nouveau"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": ""
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": ""
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": ""
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "Autoriser"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "Masquer automatiquement le dock"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": ""
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": ""
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "Disponible dans les modes Vue détaillée et Prévisions"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": ""
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": ""
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": ""
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "Retour"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": ""
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "Backend"
|
||||
},
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": ""
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "Choisir l’emplacement d’affichage des notifications"
|
||||
},
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "Paramètres du compositeur"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "Format de configuration"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "Contraste"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": ""
|
||||
},
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "Désactiver la sortie"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "Désactivé"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "Porte ouverte"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": ""
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": ""
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": ""
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": ""
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "Activer la profondeur de couleur 10 bits pour une gamme de couleurs étendue et la prise en charge du HDR"
|
||||
},
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "Saisir le mot de passe pour "
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "Saisir cette clé sur "
|
||||
},
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": ""
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": ""
|
||||
},
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "Indicateurs"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": ""
|
||||
},
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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.": "Si le champ est masqué, il apparaîtra dès qu'une touche est pressée."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "Ignorer complètement"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": ""
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": ""
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "Partie gauche"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": ""
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "Verrouillé"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "Connexion..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": ""
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "Contrôle du volume du microphone"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "Section centrale"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "Points de montage"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": ""
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": ""
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": ""
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "Pas d'arrondi"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "Pas d'ombre"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "Non détecté"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": ""
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "Non appairé"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "Activé"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": ""
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "Ajuster le gamma uniquement en fonction de l’heure ou de l’emplacement."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": ""
|
||||
},
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "Appairage en cours..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "Partiellement nuageux"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": ""
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "Mot de passe..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": ""
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "Curseur"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": ""
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "Pression"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "Empêcher la mise en veille de l’écran"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "Protocole"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": ""
|
||||
},
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": ""
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "Réinitialiser"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "Inverser la direction du défilement"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "Inverser la direction du changement d’espace lors du défilement sur la barre"
|
||||
},
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": ""
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "Molette"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "Défilement"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "Chercher des actions d'appli"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "Sélectionner la palette utilisée pour les couleurs basées sur le fond d’écran"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": ""
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "Sélectionner les fournisseurs de raccourcis à inclure"
|
||||
},
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "Montrer"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "Montrer les tiers"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": ""
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": ""
|
||||
},
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "Suspendre puis mettre en veille prolongée"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "Site Sway"
|
||||
},
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": ""
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "Terminal"
|
||||
},
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": ""
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "Thème et couleurs"
|
||||
},
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": ""
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "Trop de tentatives échouées - le compte peut être verrouillé"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "Outils"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": ""
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "Transformer"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": ""
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "Tapez au moins 2 caractères"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "Révoquer"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "À jour"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": ""
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "Nom d'utilisateur..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": ""
|
||||
},
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "attaché"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -198,7 +198,7 @@
|
||||
"2 seconds": "2 secondi"
|
||||
},
|
||||
"2.4 GHz": {
|
||||
"2.4 GHz": ""
|
||||
"2.4 GHz": "2.4 GHz"
|
||||
},
|
||||
"20 minutes": {
|
||||
"20 minutes": "20 minuti"
|
||||
@@ -264,7 +264,7 @@
|
||||
"45 seconds": "45 secondi"
|
||||
},
|
||||
"5 GHz": {
|
||||
"5 GHz": ""
|
||||
"5 GHz": "5 GHz"
|
||||
},
|
||||
"5 min before": {
|
||||
"5 min before": "5 min prima"
|
||||
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "Informazioni"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "Colore di Accento"
|
||||
},
|
||||
@@ -378,7 +381,7 @@
|
||||
"Activate the DMS greeter? A terminal will open for sudo authentication. Run Sync after activation to apply your settings.": "Attivare il greeter DMS? Si aprirà un terminale per l'autenticazione sudo. Esegui Sincronizza dopo l'attivazione per applicare le tue impostazioni."
|
||||
},
|
||||
"Activates immediately": {
|
||||
"Activates immediately": ""
|
||||
"Activates immediately": "Si attiva immediatamente"
|
||||
},
|
||||
"Activation": {
|
||||
"Activation": "Attivazione"
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "Adattatori"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "Larghezza Adattiva del Widget Media"
|
||||
},
|
||||
@@ -423,7 +429,7 @@
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms greeter sync --profile to publish their login-screen theme.": "Aggiungere \"%1\" al gruppo %2? Devono disconnettersi e accedere di nuovo, quindi eseguire dms greeter sync --profile per pubblicare il tema della schermata di accesso."
|
||||
},
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms-greeter sync --profile to publish their login-screen theme.": {
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms-greeter sync --profile to publish their login-screen theme.": ""
|
||||
"Add \"%1\" to the %2 group? They must log out and back in, then run dms-greeter sync --profile to publish their login-screen theme.": "Aggiungi \"%1\" al gruppo %2? L'utente deve disconnettersi e accedere di nuovo, dopo deve eseguire dms-greeter sync --profile per pubblicare il tema della schermata di accesso."
|
||||
},
|
||||
"Add Bar": {
|
||||
"Add Bar": "Aggiungi Barra"
|
||||
@@ -477,7 +483,7 @@
|
||||
"Add the new user to the %1 group so they can run dms greeter sync --profile.": "Aggiungi il nuovo utente al gruppo %1 in modo che possa eseguire il comando dms greeter sync --profile."
|
||||
},
|
||||
"Add the new user to the %1 group so they can run dms-greeter sync --profile.": {
|
||||
"Add the new user to the %1 group so they can run dms-greeter sync --profile.": ""
|
||||
"Add the new user to the %1 group so they can run dms-greeter sync --profile.": "Aggiungi il nuovo utente al gruppo %1 in modo che possa eseguire dms-greeter sync --profile."
|
||||
},
|
||||
"Add the new user to the %1 group so they can use sudo.": {
|
||||
"Add the new user to the %1 group so they can use sudo.": "Aggiungi il nuovo utente al gruppo %1 in modo che possa utilizzare sudo."
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": "Aggiungi all'Avvio Automatico"
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "Regola l'altezza della barra tramite spaziatura interna"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "Regola il numero di colonne nella modalità di visualizzazione a griglia."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "Regola volume per scatto rotellina"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "Mostra sempre quando è presente un solo schermo connesso"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "Usa sempre quest'app per %1"
|
||||
},
|
||||
@@ -660,7 +678,7 @@
|
||||
"Applications and commands to start automatically when you log in": "Applicazioni e Comandi da Avviare Automaticamente all'Accesso"
|
||||
},
|
||||
"Applies on the next greeter sync": {
|
||||
"Applies on the next greeter sync": ""
|
||||
"Applies on the next greeter sync": "Verrà applicato alla prossima sincronizzazione del greeter"
|
||||
},
|
||||
"Apply Changes": {
|
||||
"Apply Changes": "Applica Modifiche"
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": "Autenticato!"
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "Autenticazione in corso..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "Autenticazione"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "Le modifiche di autenticazione richiedono sudo. Apertura del terminale per consentirti l'uso della password o dell'impronta digitale."
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "Errore di autenticazione - riprova"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": "Autenticazione non riuscita - tentativo %1 di %2"
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": "Autenticazione non riuscita - possibile blocco dell'account"
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": "Autenticazione non riuscita - riprova"
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "Autorizza"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "Nascondi Automaticamente il Dock"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": "Accesso Automatico"
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": "La modifica dell'accesso automatico richiede una sincronizzazione"
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "Disponibile nelle modalità Dettagliata e Previsioni"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": "In attesa dell'autenticazione tramite impronta digitale"
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": "In attesa dell'autenticazione tramite impronta digitale o chiave di sicurezza"
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": "In attesa dell'autenticazione tramite chiave di sicurezza"
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "Indietro"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": "Torna alla Lista Utenti"
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "Backend"
|
||||
},
|
||||
@@ -1035,7 +1023,7 @@
|
||||
"Balanced palette with focused accents (default).": "Tavolozza bilanciata con accenti focalizzati (predefinito)."
|
||||
},
|
||||
"Band": {
|
||||
"Band": ""
|
||||
"Band": "Banda"
|
||||
},
|
||||
"Bar": {
|
||||
"Bar": "Barra"
|
||||
@@ -1293,7 +1281,7 @@
|
||||
"Calendar Backend": "Backend Calendario"
|
||||
},
|
||||
"Calls / Headset": {
|
||||
"Calls / Headset": ""
|
||||
"Calls / Headset": "Chiamate / Auricolare"
|
||||
},
|
||||
"Camera": {
|
||||
"Camera": "Fotocamera"
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": "Scegli cartella sfondi"
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "Scegli dove i popup delle notifiche appaiono sullo schermo"
|
||||
},
|
||||
@@ -1581,16 +1572,16 @@
|
||||
"Close Window": "Chiudi Finestra"
|
||||
},
|
||||
"Codec switched successfully": {
|
||||
"Codec switched successfully": ""
|
||||
"Codec switched successfully": "Codec cambiato correttamente"
|
||||
},
|
||||
"Codec switching is unavailable because WirePlumber was not found": {
|
||||
"Codec switching is unavailable because WirePlumber was not found": ""
|
||||
"Codec switching is unavailable because WirePlumber was not found": "Il cambio codec non è disponibile perché WirePlumber non è stato trovato"
|
||||
},
|
||||
"Codec switching is unavailable because pactl was not found": {
|
||||
"Codec switching is unavailable because pactl was not found": "Il passaggio del codec non è disponibile perché pactl non è stato trovato"
|
||||
},
|
||||
"Codec switching is unavailable. WirePlumber wpexec was not found.": {
|
||||
"Codec switching is unavailable. WirePlumber wpexec was not found.": ""
|
||||
"Codec switching is unavailable. WirePlumber wpexec was not found.": "Il cambio codec non è disponibile. WirePlumber wpexec non è stato trovato."
|
||||
},
|
||||
"Color": {
|
||||
"Color": "Colore"
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "Impostazioni del Compositor"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "Formato di Configurazione"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "Contrasto"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": "Contributore"
|
||||
},
|
||||
@@ -1905,7 +1902,7 @@
|
||||
"Corners & Background": "Angoli e Sfondo"
|
||||
},
|
||||
"Couldn't load hotspot password": {
|
||||
"Couldn't load hotspot password": ""
|
||||
"Couldn't load hotspot password": "Impossibile caricare la password dell'hotspot"
|
||||
},
|
||||
"Count Only": {
|
||||
"Count Only": "Solo Conteggio"
|
||||
@@ -2049,7 +2046,7 @@
|
||||
"Custom Lock Command": "Comando Personalizzato per il Blocco"
|
||||
},
|
||||
"Custom Logout Command": {
|
||||
"Custom Logout Command": "Comando Personalizzato Terminare la Sessione"
|
||||
"Custom Logout Command": "Comando Personalizzato per Terminare la Sessione"
|
||||
},
|
||||
"Custom Name": {
|
||||
"Custom Name": "Nome Personalizzato"
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "Disabilita Output"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "Disattivato"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "Sportello Aperto"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": "Trascina un widget dalla sua maniglia qui per riordinarlo o rilascialo in un altro gruppo"
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": "Dinamico: bezier a molla con overshooting — l'entrata supera brevemente il bersaglio poi si stabilizza. Espressivo e vitale."
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": "Rivelazione al Passaggio sul Bordo"
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "Svuota Cestino (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "Abilita profondità colore a 10 bit per una gamma cromatica più ampia e supporto HDR"
|
||||
},
|
||||
@@ -2706,7 +2721,7 @@
|
||||
"Enable WiFi": "Abilita WiFi"
|
||||
},
|
||||
"Enable WiFi before starting the hotspot.": {
|
||||
"Enable WiFi before starting the hotspot.": ""
|
||||
"Enable WiFi before starting the hotspot.": "Abilita WiFi prima di avviare l'hotspot."
|
||||
},
|
||||
"Enable a custom override below to set per-bar shadow intensity, opacity, and color.": {
|
||||
"Enable a custom override below to set per-bar shadow intensity, opacity, and color.": "Abilita una sovrascrittura personalizzata qui sotto per impostare intensità, opacità e colore dell'ombra per ogni barra."
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "Inserisci password per "
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "Inserisci questa passkey su "
|
||||
},
|
||||
@@ -2934,7 +2952,7 @@
|
||||
"Failed to check pin limit": "Impossibile verificare il limite delle voci fissate"
|
||||
},
|
||||
"Failed to configure hotspot": {
|
||||
"Failed to configure hotspot": ""
|
||||
"Failed to configure hotspot": "Impossibile configurare l'hotspot"
|
||||
},
|
||||
"Failed to connect VPN": {
|
||||
"Failed to connect VPN": "Impossibile connettersi alla VPN"
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "Impossibile recuperare il codice QR di rete: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": "Impossibile generare override systemd"
|
||||
},
|
||||
@@ -3066,7 +3087,7 @@
|
||||
"Failed to run 'dms greeter status'. Ensure DMS is installed and dms is in PATH.": "Impossibile eseguire \"dms greeter status\". Assicurati che DMS sia installato e dms sia nel PATH."
|
||||
},
|
||||
"Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.": {
|
||||
"Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.": ""
|
||||
"Failed to run 'dms-greeter status'. Ensure the dms-greeter package is installed.": "Impossibile eseguire \"dms-greeter status\". Assicurati che il pacchetto dms-greeter sia installato."
|
||||
},
|
||||
"Failed to save VPN credentials": {
|
||||
"Failed to save VPN credentials": "Impossibile salvare le credenziali VPN"
|
||||
@@ -3123,13 +3144,13 @@
|
||||
"Failed to start connection to %1": "Impossibile avviare la connessione a %1"
|
||||
},
|
||||
"Failed to start hotspot": {
|
||||
"Failed to start hotspot": ""
|
||||
"Failed to start hotspot": "Impossibile avviare l'hotspot"
|
||||
},
|
||||
"Failed to stop hotspot": {
|
||||
"Failed to stop hotspot": ""
|
||||
"Failed to stop hotspot": "Impossibile fermare l'hotspot"
|
||||
},
|
||||
"Failed to switch codec": {
|
||||
"Failed to switch codec": ""
|
||||
"Failed to switch codec": "Impossibile cambiare codec"
|
||||
},
|
||||
"Failed to unpin entry": {
|
||||
"Failed to unpin entry": "Impossibile rimuovere la voce"
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "Flag"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": "Flatpak"
|
||||
},
|
||||
@@ -3558,7 +3585,7 @@
|
||||
"Generic": "Generico"
|
||||
},
|
||||
"Geometric Centering": {
|
||||
"Geometric Centering": "Centratura Geometrico"
|
||||
"Geometric Centering": "Centratura Geometrica"
|
||||
},
|
||||
"Get Started": {
|
||||
"Get Started": "Inizia"
|
||||
@@ -3621,7 +3648,7 @@
|
||||
"Greeter group members can sync their login-screen theme with dms greeter sync --profile after logging out and back in.": "I membri del gruppo Greeter possono sincronizzare il tema della schermata di accesso con il comando dms greeter sync --profile dopo aver effettuato il logout e aver effettuato nuovamente l'accesso."
|
||||
},
|
||||
"Greeter group members can sync their login-screen theme with dms-greeter sync --profile after logging out and back in.": {
|
||||
"Greeter group members can sync their login-screen theme with dms-greeter sync --profile after logging out and back in.": ""
|
||||
"Greeter group members can sync their login-screen theme with dms-greeter sync --profile after logging out and back in.": "I membri del gruppo greeter possono sincronizzare il tema della schermata di accesso con dms-greeter sync --profile dopo aver effettuato il logout e l'accesso."
|
||||
},
|
||||
"Greeter group:": {
|
||||
"Greeter group:": "Gruppo greeter:"
|
||||
@@ -3837,22 +3864,22 @@
|
||||
"Hotkey overlay title (optional)": "Titolo della sovrapposizione per scorciatoie (opzionale)"
|
||||
},
|
||||
"Hotspot": {
|
||||
"Hotspot": ""
|
||||
"Hotspot": "Hotspot"
|
||||
},
|
||||
"Hotspot activation failed.": {
|
||||
"Hotspot activation failed.": ""
|
||||
"Hotspot activation failed.": "Attivazione dell'hotspot non riuscita."
|
||||
},
|
||||
"Hotspot name": {
|
||||
"Hotspot name": ""
|
||||
"Hotspot name": "Nome hotspot"
|
||||
},
|
||||
"Hotspot saved": {
|
||||
"Hotspot saved": ""
|
||||
"Hotspot saved": "Hotspot salvato"
|
||||
},
|
||||
"Hotspot started": {
|
||||
"Hotspot started": ""
|
||||
"Hotspot started": "Hotspot avviato"
|
||||
},
|
||||
"Hotspot stopped": {
|
||||
"Hotspot stopped": ""
|
||||
"Hotspot stopped": "Hotspot fermato"
|
||||
},
|
||||
"Hour": {
|
||||
"Hour": "Ora"
|
||||
@@ -3912,7 +3939,7 @@
|
||||
"IP address or hostname": "Indirizzo IP o nome host"
|
||||
},
|
||||
"IP sharing setup failed. Check that dnsmasq is installed.": {
|
||||
"IP sharing setup failed. Check that dnsmasq is installed.": ""
|
||||
"IP sharing setup failed. Check that dnsmasq is installed.": "Configurazione della condivisione IP non riuscita. Controlla che dnsmasq sia installato."
|
||||
},
|
||||
"ISO Date": {
|
||||
"ISO Date": "Data ISO"
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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 il campo è nascosto, apparirà non appena viene premuto un tasto."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "Ignora Completamente"
|
||||
},
|
||||
@@ -4029,7 +4059,7 @@
|
||||
"Incorrect password - try again": "Password errata - riprova"
|
||||
},
|
||||
"Index Centering": {
|
||||
"Index Centering": "Centratura indice"
|
||||
"Index Centering": "Centratura Indice"
|
||||
},
|
||||
"Indicator Style": {
|
||||
"Indicator Style": "Stile Indicatore"
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": "Mantieni le Mie Modifiche"
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": "Mantieni nella Barra"
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "Sezione Sinistra"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "Chiaro"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "Bloccato"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "Accesso in corso..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "Accesso"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "Controllo volume microfono"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "Sezione Centrale"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "Punti di Montaggio"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "I clic del mouse attraversano la barra e raggiungono le finestre dietro di essa."
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "Native: renderer di piattaforma (FreeType)."
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": "Scorrimento Naturale del Touchpad"
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "Squadrato"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "Nessuna Ombra"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "Non rilevato"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": "Non elencato?"
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "Non associato"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "Attivo"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "Attivo a tempo indeterminato"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "Regola la gamma solo in base alle regole di tempo o di posizione."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": "Solo sulla Batteria"
|
||||
},
|
||||
@@ -5511,7 +5562,7 @@
|
||||
"Open in terminal": "Apri nel terminale"
|
||||
},
|
||||
"Open network": {
|
||||
"Open network": ""
|
||||
"Open network": "Rete aperta"
|
||||
},
|
||||
"Open search bar to find text": {
|
||||
"Open search bar to find text": "Apri barra di ricerca per cercare testo"
|
||||
@@ -5541,7 +5592,7 @@
|
||||
"Opens the connected launcher in Connected Frame Mode.": "Apre il launcher connesso in modalità Connected Frame."
|
||||
},
|
||||
"Optional": {
|
||||
"Optional": ""
|
||||
"Optional": "Opzionale"
|
||||
},
|
||||
"Optional description": {
|
||||
"Optional description": "Descrizione facoltativa"
|
||||
@@ -5553,7 +5604,7 @@
|
||||
"Optional state-based conditions applied to the first match.": "Condizioni opzionali basate sullo stato applicate alla prima corrispondenza."
|
||||
},
|
||||
"Optional; leave blank for open hotspot": {
|
||||
"Optional; leave blank for open hotspot": ""
|
||||
"Optional; leave blank for open hotspot": "Opzionale; lascia vuoto per un hotspot aperto"
|
||||
},
|
||||
"Options": {
|
||||
"Options": "Opzioni"
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "Associazione in corso..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "Parzialmente Nuvoloso"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "Password aggiornata"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "Password..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "Le password non corrispondono."
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "Puntatore"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "L'integrazione con Polkit è disabilitata. La gestione degli utenti richiede che Polkit elevi i privilegi."
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "Pressione"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "Disattiva sospensione schermo"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "Protocollo"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": "Qt"
|
||||
},
|
||||
@@ -6189,7 +6249,7 @@
|
||||
"Re-enter password": "Reinserisci la password"
|
||||
},
|
||||
"Re-enter the password before saving.": {
|
||||
"Re-enter the password before saving.": ""
|
||||
"Re-enter the password before saving.": "Reinserisci la password prima di salvare."
|
||||
},
|
||||
"Reach local network devices while using an exit node": {
|
||||
"Reach local network devices while using an exit node": "Raggiungi i dispositivi della rete locale durante l'uso di un nodo di uscita"
|
||||
@@ -6201,7 +6261,7 @@
|
||||
"Read:": "Lettura:"
|
||||
},
|
||||
"Ready": {
|
||||
"Ready": ""
|
||||
"Ready": "Pronto"
|
||||
},
|
||||
"Reason": {
|
||||
"Reason": "Motivo"
|
||||
@@ -6249,7 +6309,7 @@
|
||||
"Release": "Rilascio"
|
||||
},
|
||||
"Release to confirm": {
|
||||
"Release to confirm": ""
|
||||
"Release to confirm": "Rilascia per confermare"
|
||||
},
|
||||
"Reload From Disk": {
|
||||
"Reload From Disk": "Ricarica Dal Disco"
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": "Richiede di ricordare l'ultimo utente e sessione. Abilita prima quelle opzioni."
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "Reimposta"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "Inverti Direzione Scorrimento"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "Inverti direzione cambio spazio di lavoro quando si scorre sulla barra"
|
||||
},
|
||||
@@ -6519,7 +6588,7 @@
|
||||
"Run paru/yay with AUR enabled when 'Update All' is clicked.": "Esegui paru/yay con AUR abilitato quando si fa clic su \"Aggiorna tutto\"."
|
||||
},
|
||||
"Running": {
|
||||
"Running": ""
|
||||
"Running": "In esecuzione"
|
||||
},
|
||||
"Running Apps": {
|
||||
"Running Apps": "App in Esecuzione"
|
||||
@@ -6546,13 +6615,13 @@
|
||||
"SMS sent successfully": "SMS inviato correttamente"
|
||||
},
|
||||
"SSID": {
|
||||
"SSID": ""
|
||||
"SSID": "SSID"
|
||||
},
|
||||
"Saturation": {
|
||||
"Saturation": "Saturazione"
|
||||
},
|
||||
"Save & Start": {
|
||||
"Save & Start": ""
|
||||
"Save & Start": "Salva e Avvia"
|
||||
},
|
||||
"Save Notepad File": {
|
||||
"Save Notepad File": "Salva File Blocco Note"
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "GitHub di Scroll"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "Rotella di Scorrimento"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "Scorrimento"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "Cerca Azioni App"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "Seleziona l'algoritmo della tavolozza usato per i colori basati sullo sfondo"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "Seleziona l'utente..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "Seleziona quali fornitori di scorciatoie includere"
|
||||
},
|
||||
@@ -6903,13 +6975,13 @@
|
||||
"Set the percentage at which the battery is considered low.": "Imposta la percentuale a cui la batteria è considerata scarica."
|
||||
},
|
||||
"Set up a WiFi hotspot for sharing this connection.": {
|
||||
"Set up a WiFi hotspot for sharing this connection.": ""
|
||||
"Set up a WiFi hotspot for sharing this connection.": "Configura un hotspot WiFi per condividere questa connessione."
|
||||
},
|
||||
"Set up hotspot": {
|
||||
"Set up hotspot": ""
|
||||
"Set up hotspot": "Configura hotspot"
|
||||
},
|
||||
"Set up hotspot in Settings": {
|
||||
"Set up hotspot in Settings": ""
|
||||
"Set up hotspot in Settings": "Configura l'hotspot nelle Impostazioni"
|
||||
},
|
||||
"Setting": {
|
||||
"Setting": "Impostazione"
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "Mostra"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "Mostra Terze Parti"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": "Mostra una notifica quando la batteria raggiunge il limite di carica."
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": "Mostra un popup di avviso quando la batteria è in esaurimento."
|
||||
},
|
||||
@@ -7413,7 +7491,7 @@
|
||||
"Start": "Avvia"
|
||||
},
|
||||
"Start Hotspot?": {
|
||||
"Start Hotspot?": ""
|
||||
"Start Hotspot?": "Avvia l'hotspot?"
|
||||
},
|
||||
"Start KDE Connect or Valent to use this plugin": {
|
||||
"Start KDE Connect or Valent to use this plugin": "Avvia KDE Connect o Valent per usare questo plugin"
|
||||
@@ -7422,19 +7500,19 @@
|
||||
"Start typing your notes here...": "Inizia a scrivere i tuoi appunti qui..."
|
||||
},
|
||||
"Starting hotspot...": {
|
||||
"Starting hotspot...": ""
|
||||
"Starting hotspot...": "Avvio dell'hotspot..."
|
||||
},
|
||||
"Starting the hotspot will disconnect WiFi from \"%1\" — the radio can't do both at once. Sharing internet then requires another connection, such as Ethernet.": {
|
||||
"Starting the hotspot will disconnect WiFi from \"%1\" — the radio can't do both at once. Sharing internet then requires another connection, such as Ethernet.": ""
|
||||
"Starting the hotspot will disconnect WiFi from \"%1\" — the radio can't do both at once. Sharing internet then requires another connection, such as Ethernet.": "L'avvio dell'hotspot disconnetterà il WiFi da \"%1\" — la radio non può fare entrambe le cose contemporaneamente. Per condividere Internet sarà necessaria un'altra connessione, ad esempio Ethernet."
|
||||
},
|
||||
"Starting...": {
|
||||
"Starting...": ""
|
||||
"Starting...": "Avviando..."
|
||||
},
|
||||
"State": {
|
||||
"State": "Stato"
|
||||
},
|
||||
"Stop": {
|
||||
"Stop": ""
|
||||
"Stop": "Ferma"
|
||||
},
|
||||
"Stop ignoring %1": {
|
||||
"Stop ignoring %1": "Smetti di ignorare %1"
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "Sospendi e poi Iberna"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "Sito Web di Sway"
|
||||
},
|
||||
@@ -7560,7 +7641,7 @@
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms greeter sync --profile instead of a primary user sync.": "La sincronizzazione applica il tuo tema e le tue impostazioni alla schermata di accesso. Gli utenti condivisi dovrebbero eseguire dms greeter sync --profile invece della sincronizzazione dell'utente principale."
|
||||
},
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.": {
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.": ""
|
||||
"Sync applies your theme and settings to the login screen. Shared users should run dms-greeter sync --profile instead of a primary user sync.": "La sincronizzazione applica il tuo tema e le tue impostazioni alla schermata di accesso. Gli utenti condivisi dovrebbero eseguire dms-greeter sync --profile invece di una sincronizzazione dell'utente principale."
|
||||
},
|
||||
"Sync completed successfully.": {
|
||||
"Sync completed successfully.": "Sincronizzazione completata con successo."
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Tailscale non disponibile"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "Terminale"
|
||||
},
|
||||
@@ -7665,7 +7758,7 @@
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms greeter sync' manually.": "Impossibile utilizzare il terminale di ripiego. Installa uno degli emulatori di terminale supportati oppure esegui manualmente 'dms greeter sync'."
|
||||
},
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter sync' manually.": {
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter sync' manually.": ""
|
||||
"Terminal fallback failed. Install one of the supported terminal emulators or run 'dms-greeter sync' manually.": "Terminale di ripiego non riuscito. Installa uno degli emulatori di terminale supportati o esegui manualmente 'dms-greeter sync'."
|
||||
},
|
||||
"Terminal fallback opened. Complete authentication there; it will close automatically when done.": {
|
||||
"Terminal fallback opened. Complete authentication there; it will close automatically when done.": "Terminale di ripiego aperto. Completa lì l'autenticazione; si chiuderà automaticamente al termine."
|
||||
@@ -7713,7 +7806,7 @@
|
||||
"The DMS_SOCKET environment variable is not set or the socket is unavailable. Automated plugin management requires the DMS_SOCKET.": "La variabile d'ambiente DMS_SOCKET non è impostata o il socket non è disponibile. La gestione automatica dei plugin richiede il DMS_SOCKET."
|
||||
},
|
||||
"The WiFi adapter could not start access point mode.": {
|
||||
"The WiFi adapter could not start access point mode.": ""
|
||||
"The WiFi adapter could not start access point mode.": "L'adattatore WiFi non è riuscito ad avviare la modalità access point."
|
||||
},
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": {
|
||||
"The below settings will modify your GTK and Qt settings. If you wish to preserve your current configurations, please back them up (qt5ct.conf|qt6ct.conf and ~/.config/gtk-3.0|gtk-4.0).": "Le seguenti impostazioni modificheranno le tue impostazioni GTK e Qt. Se vuoi preservare la tua configurazione attuale, per favore fai il backup (qt5ct.conf|qt6ct.conf e ~/.config/gtk-3.0|gtk-4.0)."
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": "La regola si applica a qualsiasi finestra che corrisponda a una di queste."
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "Tema e Colori"
|
||||
},
|
||||
@@ -7749,7 +7845,7 @@
|
||||
"Themes": "Temi"
|
||||
},
|
||||
"These add entries to the XDG autostart directory (~/.config/autostart/*.desktop)": {
|
||||
"These add entries to the XDG autostart directory (~/.config/autostart/*.desktop)": "> Queste aggiungono voci alla directory di avvio automatico XDG (~/.config/autostart/*.desktop)"
|
||||
"These add entries to the XDG autostart directory (~/.config/autostart/*.desktop)": "Queste aggiungono voci alla directory di avvio automatico XDG (~/.config/autostart/*.desktop)"
|
||||
},
|
||||
"Thickness": {
|
||||
"Thickness": "Spessore"
|
||||
@@ -7788,7 +7884,7 @@
|
||||
"This will delete all unpinned entries. %1 pinned entries will be kept.": "Questo eliminerà tutte le voci non fissate. %1 voci fissate verranno mantenute."
|
||||
},
|
||||
"This will disconnect WiFi from \"%1\" — the radio can't host a hotspot and stay connected at the same time. Internet sharing will need another connection, such as Ethernet.": {
|
||||
"This will disconnect WiFi from \"%1\" — the radio can't host a hotspot and stay connected at the same time. Internet sharing will need another connection, such as Ethernet.": ""
|
||||
"This will disconnect WiFi from \"%1\" — the radio can't host a hotspot and stay connected at the same time. Internet sharing will need another connection, such as Ethernet.": "Questa operazione disconnetterà il WiFi da \"%1\" — la radio non può ospitare un hotspot e rimanere connessa contemporaneamente. La condivisione di Internet richiederà un'altra connessione, ad esempio Ethernet."
|
||||
},
|
||||
"This will permanently delete all clipboard history.": {
|
||||
"This will permanently delete all clipboard history.": "La cronologia degli appunti verrà cancellata definitivamente."
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "Troppi tentativi - accesso bloccato"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "Troppi tentativi falliti - l'account potrebbe essere bloccato"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "Strumenti"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "Tocca la tua chiave di sicurezza..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "Trasforma"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "Spegni ora"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "Digita almeno 2 caratteri"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "Non Fidarti"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "Aggiornato"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "Il nome utente deve iniziare con una lettera minuscola o un trattino basso e deve contenere solo lettere minuscole, cifre, trattini o trattini bassi."
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "Nome utente..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "Utenti"
|
||||
},
|
||||
@@ -8481,13 +8583,13 @@
|
||||
"W": "W"
|
||||
},
|
||||
"WCAG %1 body": {
|
||||
"WCAG %1 body": ""
|
||||
"WCAG %1 body": "WCAG %1 corpo"
|
||||
},
|
||||
"WPA/WPA2": {
|
||||
"WPA/WPA2": "WPA/WPA2"
|
||||
},
|
||||
"WPA2 password": {
|
||||
"WPA2 password": ""
|
||||
"WPA2 password": "Password WPA2"
|
||||
},
|
||||
"Wallpaper": {
|
||||
"Wallpaper": "Sfondo"
|
||||
@@ -8577,10 +8679,10 @@
|
||||
"WiFi enabled": "WiFi attivato"
|
||||
},
|
||||
"WiFi is disabled": {
|
||||
"WiFi is disabled": ""
|
||||
"WiFi is disabled": "WiFi disattivato"
|
||||
},
|
||||
"WiFi is disabled. You can still edit and save hotspot settings, but starting the hotspot requires WiFi to be enabled.": {
|
||||
"WiFi is disabled. You can still edit and save hotspot settings, but starting the hotspot requires WiFi to be enabled.": ""
|
||||
"WiFi is disabled. You can still edit and save hotspot settings, but starting the hotspot requires WiFi to be enabled.": "WiFi disattivato. Puoi comunque modificare e salvare le impostazioni dell'hotspot, ma l'avvio dell'hotspot richiede che il WiFi sia abilitato."
|
||||
},
|
||||
"WiFi is off": {
|
||||
"WiFi is off": "WiFi spento"
|
||||
@@ -8640,7 +8742,7 @@
|
||||
"Width of window border and focus ring": "Larghezza del bordo e dell'anello di focus"
|
||||
},
|
||||
"Will disconnect \"%1": {
|
||||
"Will disconnect \"%1\"": ""
|
||||
"Will disconnect \"%1\"": "Disconnetterà \"%1\""
|
||||
},
|
||||
"Wind": {
|
||||
"Wind": "Vento"
|
||||
@@ -8751,10 +8853,10 @@
|
||||
"Your compositor does not support background blur (ext-background-effect-v1)": "Il tuo compositor non supporta la sfocatura dello sfondo (ext-background-effect-v1)"
|
||||
},
|
||||
"Your hotspot is running.": {
|
||||
"Your hotspot is running.": ""
|
||||
"Your hotspot is running.": "Il tuo hotspot è in esecuzione."
|
||||
},
|
||||
"Your hotspot profile is saved and ready to start.": {
|
||||
"Your hotspot profile is saved and ready to start.": ""
|
||||
"Your hotspot profile is saved and ready to start.": "Il profilo del tuo hotspot è salvato e pronto per l'avvio."
|
||||
},
|
||||
"Your system is up to date!": {
|
||||
"Your system is up to date!": "Il tuo sistema è aggiornato!"
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "agganciato"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
@@ -8871,7 +8976,7 @@
|
||||
"mangowc GitHub": "GitHub di mangowc"
|
||||
},
|
||||
"matugen not available or disabled - cannot apply %1 colors": {
|
||||
"matugen not available or disabled - cannot apply %1 colors": "atugen non è disponibile o disabilitato - impossibile applicare i colori %1"
|
||||
"matugen not available or disabled - cannot apply %1 colors": "matugen non è disponibile o disabilitato - impossibile applicare i colori %1"
|
||||
},
|
||||
"matugen not found - install matugen package for dynamic theming": {
|
||||
"matugen not found - install matugen package for dynamic theming": "matugen non trovato - installa il pacchetto matugen per il theming dinamico"
|
||||
|
||||
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "バージョン情報"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "アクセントカラー"
|
||||
},
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "アダプター"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "メディア幅の自動調整"
|
||||
},
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": "自動起動に追加"
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "内側の余白でバーの高さを調整"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "グリッド表示モードでの列数を調整します。"
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "スクロール1段ごとの音量調整"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "接続されたディスプレイが1つだけのときは常に表示"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "%1 は常にこのアプリ使用する"
|
||||
},
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": ""
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "認証中..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "認証"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "認証の変更には sudo が必要です。パスワードまたは指紋を使用できるよう端末を開きます。"
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "認証エラー - もう一度試してください"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": ""
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": ""
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": ""
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "許可"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "ドックを自動的に隠す"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": "自動ログイン"
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": ""
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "詳細表示と予報表示で利用可能"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": ""
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": ""
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": ""
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "戻る"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": "ユーザー一覧に戻る"
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "バックエンド"
|
||||
},
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": ""
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "通知ポップアップが画面に表示される場所を選ぶ"
|
||||
},
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "コンポジター設定"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "設定形式"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "コントラスト"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": "貢献者"
|
||||
},
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "出力を無効化"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "無効"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "ドアオープン"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": ""
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": "ダイナミック:オーバーシュート付きスプリングベジェ曲線 ― 開始点が目標値を一時的に超えた後、落ち着きます。表現力豊かで生き生きとした曲線です。"
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": ""
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "ゴミ箱を空にする (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "より広い色域と HDR サポートのために 10 ビット色深度を有効にします"
|
||||
},
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "パスワードを入力"
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "ここでパスキーを入力してください "
|
||||
},
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "ネットワークQRコードの取得に失敗しました: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": "systemd override の生成に失敗しました"
|
||||
},
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "フラグ"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": ""
|
||||
},
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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.": "フィールドが非表示の場合、キーが押されるとすぐに表示されます。"
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "完全に無視"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": "自分の編集を保持"
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": "バーに残す"
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "左セクション"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "ライト"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "ロック済み"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "ログイン中..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "ログイン"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "マイク音量コントロール"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "中間区間"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "マウントポイント"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "マウスクリックをバーの背後のウィンドウへ通す"
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "ネイティブ: プラットフォームレンダラー (FreeType)。"
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": "タッチパッドのナチュラルスクロール"
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "角丸なし"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "影なし"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "未検出"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": "一覧にありませんか?"
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "未ペアリング"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "オン"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "無期限にオン"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "ガンマは、時間または場所のルールに基づいてのみ調整します。"
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": "バッテリー駆動時のみ"
|
||||
},
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "ペアリング中..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "晴れ時々曇り"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "パスワードを更新しました"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "パスワード..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "パスワードが一致しません。"
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "ポインター"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "Polkit 連携が無効です。ユーザー管理には Polkit による権限昇格が必要です。"
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "気圧"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "画面のタイムアウトを防止"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "プロトコル"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": "Qt"
|
||||
},
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": "最後のユーザーとセッションの記憶が必要です。先にそれらのオプションを有効にしてください。"
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "リセット"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "スクロール方向を反転"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "バー上でスクロールしたときのワークスペース切り替え方向を反転"
|
||||
},
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "GitHub をスクロール"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "スクロールホイール"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "スクロール"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "アプリのアクションを検索"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "壁紙ベースの色で、使用するパレットアルゴリズムを選ぶ"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "ユーザーを選択..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "含めるキーバインドプロバイダーを選択"
|
||||
},
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "表示"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "サードパーティを表示"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": "バッテリーが充電上限に達したら通知を表示します。"
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": "バッテリー残量が少ないとき警告ポップアップを表示します。"
|
||||
},
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "サスペンド後に休止"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "Sway ウェブサイト"
|
||||
},
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Tailscaleを利用できません"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "ターミナル"
|
||||
},
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": "このルールは、これらのいずれかに一致するウィンドウに適用されます。"
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "テーマおよびカラー"
|
||||
},
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "試行回数が多すぎます - ロックされました"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "失敗回数が多すぎます - アカウントがロックされている可能性があります"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "ツール"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "セキュリティキーに触れてください..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "変形"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "今すぐオフにする"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "少なくとも 2 文字入力してください"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "信頼を解除"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "最新です"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "ユーザー名は小文字またはアンダースコアで始め、小文字、数字、ハイフン、アンダースコアだけを含める必要があります。"
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "ユーザー名..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "ユーザー"
|
||||
},
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "接続済み"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "정보"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "강조 색상"
|
||||
},
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "어댑터"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "적응형 미디어 너비"
|
||||
},
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": "자동 시작에 추가"
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "내부 패딩을 통해 표시줄 높이 조정"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "그리드 뷰 모드에서 열 수를 조정합니다."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "스크롤 단계별 볼륨 조정"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "연결된 디스플레이가 하나만 있을 때 항상 표시"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "%1에 항상 이 앱 사용"
|
||||
},
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": ""
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "인증 중..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "인증"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "인증 변경 사항에는 sudo가 필요합니다. 비밀번호나 지문을 사용할 수 있도록 터미널을 엽니다."
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "인증 오류 - 다시 시도"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": ""
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": ""
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": ""
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "권한 부여"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "독 자동 숨기기"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": "자동 로그인"
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": "자동 로그인 변경 시 동기화가 필요합니다"
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "상세 및 일기예보 보기 모드에서 사용 가능"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": ""
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": ""
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": ""
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "뒤로"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": "사용자 목록으로 돌아가기"
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "백엔드"
|
||||
},
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": "배경화면 폴더 선택"
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "화면에서 알림 팝업이 나타날 위치를 선택하세요"
|
||||
},
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "컴포지터 설정"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "구성 형식"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "대비"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": "기여자"
|
||||
},
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "출력 비활성화"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "비활성화됨"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "문 열림"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": "위젯의 핸들을 잡고 드래그하여 순서를 변경하거나 다른 그룹에 놓으세요"
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": "다이내믹: 오버슛이 있는 스프링 베지에 — 항목이 대상 위치를 짧게 넘어갔다가 안착합니다. 생동감 넘치고 표현력이 풍부합니다."
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": "가장자리 호버 시 드러내기"
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "휴지통 비우기 (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "더 넓은 색 영역과 HDR 지원을 위해 10비트 색 심도 활성화"
|
||||
},
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "비밀번호 입력:"
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "이 패스키를 입력할 곳:"
|
||||
},
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "네트워크 QR 코드 가져오기 실패: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": "systemd 덮어쓰기 생성 실패"
|
||||
},
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "플래그"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": ""
|
||||
},
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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.": "필드가 숨겨져 있어도 키를 누르는 즉시 나타납니다."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "완전히 무시"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": "내 편집 내용 유지"
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": "바에 유지"
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "왼쪽 구역"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "얇게"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "잠김"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "로그인 중..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "로그인"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "마이크 볼륨 제어"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "가운데 구역"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "마운트 지점"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "마우스 클릭이 바를 통과하여 뒤에 있는 창으로 전달됨"
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "네이티브: 플랫폼 렌더러(FreeType)."
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": "터치패드 자연스러운 스크롤"
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "둥글게 처리 없음"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "그림자 없음"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "감지되지 않음"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": "목록에 없습니까?"
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "페어링되지 않음"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "켜짐"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "무기한 켜짐"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "시간 또는 위치 규칙에 따라서만 감마를 조정합니다."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": "배터리 사용 시만"
|
||||
},
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "페어링 중..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "대체로 흐림"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "비밀번호 업데이트됨"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "비밀번호..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "비밀번호가 일치하지 않습니다."
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "포인터"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "Polkit 통합이 비활성화되었습니다. 사용자 관리를 위해서는 권한을 높이려면 Polkit이 필요합니다."
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "기압"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "화면 시간 초과 방지"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "프로토콜"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": "Qt"
|
||||
},
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": "마지막 사용자 및 세션을 기억해야 합니다. 먼저 해당 옵션을 활성화하세요."
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "재설정"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "스크롤 방향 반전"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "바 위에서 스크롤할 때 작업 공간 전환 방향 반전"
|
||||
},
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "GitHub 스크롤"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "스크롤 휠"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "스크롤"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "앱 작업 검색"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "배경화면 기반 색상에 사용할 팔레트 알고리즘 선택"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "사용자 선택..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "포함할 단축키 제공자 선택"
|
||||
},
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "표시"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "타사 표시"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": "배터리가 충전 한도에 도달하면 알림을 표시합니다."
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": "배터리가 부족할 때 경고 팝업을 표시합니다."
|
||||
},
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "절전 후 최대 절전 모드"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "Sway 웹사이트"
|
||||
},
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Tailscale을 사용할 수 없음"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "터미널"
|
||||
},
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": "규칙은 이 중 하나와 일치하는 모든 창에 적용됩니다."
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "테마 및 색상"
|
||||
},
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "시도 횟수 너무 많음 - 잠김"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "실패한 시도 횟수 너무 많음 - 계정이 잠길 수 있음"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "도구"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "보안 키를 터치하세요..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "변환"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "지금 끄기"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "2자 이상 입력"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "신뢰 해제"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "최신 상태"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "사용자 이름은 소문자나 밑줄로 시작해야 하며 소문자, 숫자, 하이픈 또는 밑줄만 포함해야 합니다."
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "사용자 이름..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "사용자"
|
||||
},
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "연결됨"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
|
||||
@@ -335,6 +335,9 @@
|
||||
"About": {
|
||||
"About": "Over"
|
||||
},
|
||||
"Acceleration Profile": {
|
||||
"Acceleration Profile": ""
|
||||
},
|
||||
"Accent Color": {
|
||||
"Accent Color": "Accentkleur"
|
||||
},
|
||||
@@ -410,6 +413,9 @@
|
||||
"Adapters": {
|
||||
"Adapters": "Adapters"
|
||||
},
|
||||
"Adaptive": {
|
||||
"Adaptive": ""
|
||||
},
|
||||
"Adaptive Media Width": {
|
||||
"Adaptive Media Width": "Adaptieve mediabreedte"
|
||||
},
|
||||
@@ -485,12 +491,21 @@
|
||||
"Add to Autostart": {
|
||||
"Add to Autostart": "Toevoegen aan automatisch opstarten"
|
||||
},
|
||||
"Adjust pointer sensitivity speed": {
|
||||
"Adjust pointer sensitivity speed": ""
|
||||
},
|
||||
"Adjust scrolling sensitivity multiplier": {
|
||||
"Adjust scrolling sensitivity multiplier": ""
|
||||
},
|
||||
"Adjust the bar height via inner padding": {
|
||||
"Adjust the bar height via inner padding": "De balkhoogte aanpassen via binnenmarge (padding)"
|
||||
},
|
||||
"Adjust the number of columns in grid view mode.": {
|
||||
"Adjust the number of columns in grid view mode.": "Pas het aantal kolommen in rasterweergave aan."
|
||||
},
|
||||
"Adjust touchpad pointer speed": {
|
||||
"Adjust touchpad pointer speed": ""
|
||||
},
|
||||
"Adjust volume per scroll indent": {
|
||||
"Adjust volume per scroll indent": "Volume per scrollstap aanpassen"
|
||||
},
|
||||
@@ -575,6 +590,9 @@
|
||||
"Always show when there's only one connected display": {
|
||||
"Always show when there's only one connected display": "Altijd tonen wanneer er slechts één beeldscherm is aangesloten"
|
||||
},
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": {
|
||||
"Always use the durations above, even if an app requests a shorter or longer one": ""
|
||||
},
|
||||
"Always use this app for %1": {
|
||||
"Always use this app for %1": "Gebruik deze app altijd voor %1"
|
||||
},
|
||||
@@ -782,9 +800,6 @@
|
||||
"Authenticated!": {
|
||||
"Authenticated!": "Geauthenticeerd!"
|
||||
},
|
||||
"Authenticating...": {
|
||||
"Authenticating...": "Controleren..."
|
||||
},
|
||||
"Authentication": {
|
||||
"Authentication": "Authenticatie"
|
||||
},
|
||||
@@ -803,18 +818,6 @@
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": {
|
||||
"Authentication changes need sudo. Opening terminal so you can use password or fingerprint.": "Verificatiewijzigingen vereisen sudo. Terminal wordt geopend zodat u een wachtwoord of vingerafdruk kunt gebruiken."
|
||||
},
|
||||
"Authentication error - try again": {
|
||||
"Authentication error - try again": "Authenticatiefout - probeer het opnieuw"
|
||||
},
|
||||
"Authentication failed - attempt %1 of %2": {
|
||||
"Authentication failed - attempt %1 of %2": "Authenticatie mislukt - poging %1 van %2"
|
||||
},
|
||||
"Authentication failed - lockout can occur": {
|
||||
"Authentication failed - lockout can occur": "Authenticatie mislukt - uitsluiting kan optreden"
|
||||
},
|
||||
"Authentication failed - try again": {
|
||||
"Authentication failed - try again": "Authenticatie mislukt - probeer het opnieuw"
|
||||
},
|
||||
"Authorize": {
|
||||
"Authorize": "Autoriseren"
|
||||
},
|
||||
@@ -878,9 +881,6 @@
|
||||
"Auto-hide Dock": {
|
||||
"Auto-hide Dock": "Dock automatisch verbergen"
|
||||
},
|
||||
"Auto-login": {
|
||||
"Auto-login": "Automatisch inloggen"
|
||||
},
|
||||
"Auto-login change needs a sync": {
|
||||
"Auto-login change needs a sync": "Wijziging aan automatisch inloggen vereist synchronisatie"
|
||||
},
|
||||
@@ -980,24 +980,12 @@
|
||||
"Available in Detailed and Forecast view modes": {
|
||||
"Available in Detailed and Forecast view modes": "Beschikbaar in weergavemodi Gedetailleerd en Voorspelling"
|
||||
},
|
||||
"Awaiting fingerprint authentication": {
|
||||
"Awaiting fingerprint authentication": "Wachten op vingerafdrukauthenticatie"
|
||||
},
|
||||
"Awaiting fingerprint or security key authentication": {
|
||||
"Awaiting fingerprint or security key authentication": "Wachten op vingerafdruk- of beveiligingssleutelauthenticatie"
|
||||
},
|
||||
"Awaiting security key authentication": {
|
||||
"Awaiting security key authentication": "Wachten op beveiligingssleutelauthenticatie"
|
||||
},
|
||||
"BSSID": {
|
||||
"BSSID": "BSSID"
|
||||
},
|
||||
"Back": {
|
||||
"Back": "Terug"
|
||||
},
|
||||
"Back to user list": {
|
||||
"Back to user list": "Terug naar gebruikerslijst"
|
||||
},
|
||||
"Backend": {
|
||||
"Backend": "Backend"
|
||||
},
|
||||
@@ -1454,6 +1442,9 @@
|
||||
"Choose wallpaper folder": {
|
||||
"Choose wallpaper folder": "Achtergrondmap kiezen"
|
||||
},
|
||||
"Choose when to generate scrolling events": {
|
||||
"Choose when to generate scrolling events": ""
|
||||
},
|
||||
"Choose where notification popups appear on screen": {
|
||||
"Choose where notification popups appear on screen": "Kies waar meldingen op het scherm verschijnen"
|
||||
},
|
||||
@@ -1688,6 +1679,9 @@
|
||||
"Compositor Settings": {
|
||||
"Compositor Settings": "Compositor-instellingen"
|
||||
},
|
||||
"Compositor actions (focus, move, etc.)": {
|
||||
"Compositor actions (focus, move, etc.)": ""
|
||||
},
|
||||
"Config Format": {
|
||||
"Config Format": "Config-formaat"
|
||||
},
|
||||
@@ -1805,6 +1799,9 @@
|
||||
"Contrast": {
|
||||
"Contrast": "Contrast"
|
||||
},
|
||||
"Contrast by variant": {
|
||||
"Contrast by variant": ""
|
||||
},
|
||||
"Contributor": {
|
||||
"Contributor": "Bijdrager"
|
||||
},
|
||||
@@ -2390,6 +2387,15 @@
|
||||
"Disable Output": {
|
||||
"Disable Output": "Uitvoer uitschakelen"
|
||||
},
|
||||
"Disable While Typing": {
|
||||
"Disable While Typing": ""
|
||||
},
|
||||
"Disable on External Mouse": {
|
||||
"Disable on External Mouse": ""
|
||||
},
|
||||
"Disable touchpad when an external mouse is connected": {
|
||||
"Disable touchpad when an external mouse is connected": ""
|
||||
},
|
||||
"Disabled": {
|
||||
"Disabled": "Uitgeschakeld"
|
||||
},
|
||||
@@ -2549,6 +2555,9 @@
|
||||
"Door Open": {
|
||||
"Door Open": "Deur open"
|
||||
},
|
||||
"Drag Lock": {
|
||||
"Drag Lock": ""
|
||||
},
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": {
|
||||
"Drag a widget by its handle here to reorder it or drop it into another group": "Sleep een widget aan de handgreep hiernaartoe om de volgorde te wijzigen of zet hem in een andere groep"
|
||||
},
|
||||
@@ -2621,6 +2630,9 @@
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": {
|
||||
"Dynamic: Spring bezier with overshoot — entry briefly exceeds its target then settles. Expressive and alive.": "Dynamisch: Verende bézier met doorschieten — de binnenkomst gaat kort voorbij het doel en komt dan tot rust. Expressief en levendig."
|
||||
},
|
||||
"Edge": {
|
||||
"Edge": ""
|
||||
},
|
||||
"Edge Hover Reveal": {
|
||||
"Edge Hover Reveal": "Weergeven bij rand-hover"
|
||||
},
|
||||
@@ -2669,6 +2681,9 @@
|
||||
"Empty Trash (%1)": {
|
||||
"Empty Trash (%1)": "Prullenbak legen (%1)"
|
||||
},
|
||||
"Emulate middle click by pressing left and right buttons": {
|
||||
"Emulate middle click by pressing left and right buttons": ""
|
||||
},
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": {
|
||||
"Enable 10-bit color depth for wider color gamut and HDR support": "Schakel 10-bits kleurdiepte in voor een breder kleurengamma en HDR-ondersteuning"
|
||||
},
|
||||
@@ -2795,6 +2810,9 @@
|
||||
"Enter password for ": {
|
||||
"Enter password for ": "Voer wachtwoord in voor "
|
||||
},
|
||||
"Enter text to encode": {
|
||||
"Enter text to encode": ""
|
||||
},
|
||||
"Enter this passkey on ": {
|
||||
"Enter this passkey on ": "Voer deze toegangscode in op "
|
||||
},
|
||||
@@ -2993,6 +3011,9 @@
|
||||
"Failed to fetch network QR code: %1": {
|
||||
"Failed to fetch network QR code: %1": "Ophalen van netwerk-QR-code mislukt: %1"
|
||||
},
|
||||
"Failed to generate QR code: %1": {
|
||||
"Failed to generate QR code: %1": ""
|
||||
},
|
||||
"Failed to generate systemd override": {
|
||||
"Failed to generate systemd override": "Genereren van systemd-override mislukt"
|
||||
},
|
||||
@@ -3263,6 +3284,12 @@
|
||||
"Flags": {
|
||||
"Flags": "Vlaggen"
|
||||
},
|
||||
"Flat": {
|
||||
"Flat": ""
|
||||
},
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": {
|
||||
"Flat uses constant speed; Adaptive scales with movement speed": ""
|
||||
},
|
||||
"Flatpak": {
|
||||
"Flatpak": "Flatpak"
|
||||
},
|
||||
@@ -3962,6 +3989,9 @@
|
||||
"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.": "Als het veld verborgen is, verschijnt het zodra een toets wordt ingedrukt."
|
||||
},
|
||||
"Ignore App-Requested Timeout": {
|
||||
"Ignore App-Requested Timeout": ""
|
||||
},
|
||||
"Ignore Completely": {
|
||||
"Ignore Completely": "Volledig negeren"
|
||||
},
|
||||
@@ -4199,6 +4229,9 @@
|
||||
"Keep My Edits": {
|
||||
"Keep My Edits": "Mijn bewerkingen behouden"
|
||||
},
|
||||
"Keep dragging when finger is briefly lifted": {
|
||||
"Keep dragging when finger is briefly lifted": ""
|
||||
},
|
||||
"Keep in Bar": {
|
||||
"Keep in Bar": "In de balk houden"
|
||||
},
|
||||
@@ -4334,6 +4367,9 @@
|
||||
"Left Section": {
|
||||
"Left Section": "Linkersectie"
|
||||
},
|
||||
"Left-Handed Mode": {
|
||||
"Left-Handed Mode": ""
|
||||
},
|
||||
"Light": {
|
||||
"Light": "Licht"
|
||||
},
|
||||
@@ -4439,9 +4475,6 @@
|
||||
"Locked": {
|
||||
"Locked": "Vergrendeld"
|
||||
},
|
||||
"Logging in...": {
|
||||
"Logging in...": "Inloggen..."
|
||||
},
|
||||
"Login": {
|
||||
"Login": "Inloggen"
|
||||
},
|
||||
@@ -4748,6 +4781,9 @@
|
||||
"Microphone volume control": {
|
||||
"Microphone volume control": "Microfoonvolumeregeling"
|
||||
},
|
||||
"Middle Click Emulation": {
|
||||
"Middle Click Emulation": ""
|
||||
},
|
||||
"Middle Section": {
|
||||
"Middle Section": "Middensectie"
|
||||
},
|
||||
@@ -4823,6 +4859,12 @@
|
||||
"Mount Points": {
|
||||
"Mount Points": "Aankoppelpunten"
|
||||
},
|
||||
"Mouse & Touchpad": {
|
||||
"Mouse & Touchpad": ""
|
||||
},
|
||||
"Mouse Settings": {
|
||||
"Mouse Settings": ""
|
||||
},
|
||||
"Mouse clicks pass through the bar to windows behind it": {
|
||||
"Mouse clicks pass through the bar to windows behind it": "Muisklikken gaan door de balk heen naar vensters erachter"
|
||||
},
|
||||
@@ -4886,6 +4928,9 @@
|
||||
"Native: platform renderer (FreeType).": {
|
||||
"Native: platform renderer (FreeType).": "Native: platform-renderer (FreeType)."
|
||||
},
|
||||
"Natural Scrolling": {
|
||||
"Natural Scrolling": ""
|
||||
},
|
||||
"Natural Touchpad Scrolling": {
|
||||
"Natural Touchpad Scrolling": "Natuurlijk scrollen via touchpad"
|
||||
},
|
||||
@@ -5033,6 +5078,9 @@
|
||||
"No Rounding": {
|
||||
"No Rounding": "Geen afronding"
|
||||
},
|
||||
"No Scroll": {
|
||||
"No Scroll": ""
|
||||
},
|
||||
"No Shadow": {
|
||||
"No Shadow": "Geen schaduw"
|
||||
},
|
||||
@@ -5336,9 +5384,6 @@
|
||||
"Not detected": {
|
||||
"Not detected": "Niet gedetecteerd"
|
||||
},
|
||||
"Not listed?": {
|
||||
"Not listed?": "Niet in de lijst?"
|
||||
},
|
||||
"Not paired": {
|
||||
"Not paired": "Niet gekoppeld"
|
||||
},
|
||||
@@ -5432,6 +5477,9 @@
|
||||
"On": {
|
||||
"On": "Aan"
|
||||
},
|
||||
"On Button Down": {
|
||||
"On Button Down": ""
|
||||
},
|
||||
"On indefinitely": {
|
||||
"On indefinitely": "Voor onbepaalde tijd ingeschakeld"
|
||||
},
|
||||
@@ -5450,6 +5498,9 @@
|
||||
"Only adjust gamma based on time or location rules.": {
|
||||
"Only adjust gamma based on time or location rules.": "Gamma alleen aanpassen op basis van tijd- of locatieregels."
|
||||
},
|
||||
"Only continue if you recognize this server certificate fingerprint.": {
|
||||
"Only continue if you recognize this server certificate fingerprint.": ""
|
||||
},
|
||||
"Only on Battery": {
|
||||
"Only on Battery": "Alleen op accustroom"
|
||||
},
|
||||
@@ -5687,6 +5738,9 @@
|
||||
"Pairing...": {
|
||||
"Pairing...": "Koppelen..."
|
||||
},
|
||||
"Partial": {
|
||||
"Partial": ""
|
||||
},
|
||||
"Partly Cloudy": {
|
||||
"Partly Cloudy": "Half bewolkt"
|
||||
},
|
||||
@@ -5708,9 +5762,6 @@
|
||||
"Password updated": {
|
||||
"Password updated": "Wachtwoord bijgewerkt"
|
||||
},
|
||||
"Password...": {
|
||||
"Password...": "Wachtwoord..."
|
||||
},
|
||||
"Passwords do not match.": {
|
||||
"Passwords do not match.": "Wachtwoorden komen niet overeen."
|
||||
},
|
||||
@@ -5894,6 +5945,9 @@
|
||||
"Pointer": {
|
||||
"Pointer": "Aanwijzer"
|
||||
},
|
||||
"Pointer Speed": {
|
||||
"Pointer Speed": ""
|
||||
},
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": {
|
||||
"Polkit integration is disabled. User management requires Polkit to elevate privileges.": "Polkit-integratie is uitgeschakeld. Gebruikersbeheer vereist Polkit voor het verhogen van bevoegdheden."
|
||||
},
|
||||
@@ -6014,6 +6068,9 @@
|
||||
"Pressure": {
|
||||
"Pressure": "Luchtdruk"
|
||||
},
|
||||
"Prevent accidental cursor jumps while typing": {
|
||||
"Prevent accidental cursor jumps while typing": ""
|
||||
},
|
||||
"Prevent screen timeout": {
|
||||
"Prevent screen timeout": "Scherm-time-out voorkomen"
|
||||
},
|
||||
@@ -6131,6 +6188,9 @@
|
||||
"Protocol": {
|
||||
"Protocol": "Protocol"
|
||||
},
|
||||
"QR Generator": {
|
||||
"QR Generator": ""
|
||||
},
|
||||
"Qt": {
|
||||
"Qt": "Qt"
|
||||
},
|
||||
@@ -6383,6 +6443,9 @@
|
||||
"Requires remembering the last user and session. Enable those options first.": {
|
||||
"Requires remembering the last user and session. Enable those options first.": "Vereist het onthouden van de laatste gebruiker en sessie. Schakel die opties eerst in."
|
||||
},
|
||||
"Requires the DMS Theme extension from the editor marketplace": {
|
||||
"Requires the DMS Theme extension from the editor marketplace": ""
|
||||
},
|
||||
"Reset": {
|
||||
"Reset": "Herstellen"
|
||||
},
|
||||
@@ -6437,6 +6500,12 @@
|
||||
"Reverse Scrolling Direction": {
|
||||
"Reverse Scrolling Direction": "Scrollrichting omkeren"
|
||||
},
|
||||
"Reverse mouse wheel scrolling direction": {
|
||||
"Reverse mouse wheel scrolling direction": ""
|
||||
},
|
||||
"Reverse two-finger scrolling direction": {
|
||||
"Reverse two-finger scrolling direction": ""
|
||||
},
|
||||
"Reverse workspace switch direction when scrolling over the bar": {
|
||||
"Reverse workspace switch direction when scrolling over the bar": "Werkbladwisselrichting omkeren bij scrollen over de balk"
|
||||
},
|
||||
@@ -6644,6 +6713,9 @@
|
||||
"Scroll GitHub": {
|
||||
"Scroll GitHub": "Scroll GitHub"
|
||||
},
|
||||
"Scroll Method": {
|
||||
"Scroll Method": ""
|
||||
},
|
||||
"Scroll Wheel": {
|
||||
"Scroll Wheel": "Scrollwiel"
|
||||
},
|
||||
@@ -6659,6 +6731,9 @@
|
||||
"Scrolling": {
|
||||
"Scrolling": "Scrollen"
|
||||
},
|
||||
"Scrolling Speed": {
|
||||
"Scrolling Speed": ""
|
||||
},
|
||||
"Search App Actions": {
|
||||
"Search App Actions": "App-acties zoeken"
|
||||
},
|
||||
@@ -6821,9 +6896,6 @@
|
||||
"Select the palette algorithm used for wallpaper-based colors": {
|
||||
"Select the palette algorithm used for wallpaper-based colors": "Selecteer het paletalgoritme voor op achtergrond gebaseerde kleuren"
|
||||
},
|
||||
"Select user...": {
|
||||
"Select user...": "Gebruiker selecteren..."
|
||||
},
|
||||
"Select which keybind providers to include": {
|
||||
"Select which keybind providers to include": "Selecteer welke sneltoetsproviders moeten worden opgenomen"
|
||||
},
|
||||
@@ -6992,6 +7064,9 @@
|
||||
"Show": {
|
||||
"Show": "Tonen"
|
||||
},
|
||||
"Show \"config reloaded\" Toast": {
|
||||
"Show \"config reloaded\" Toast": ""
|
||||
},
|
||||
"Show 3rd Party": {
|
||||
"Show 3rd Party": "Derde partijen tonen"
|
||||
},
|
||||
@@ -7187,6 +7262,9 @@
|
||||
"Show a notification when battery reaches the charge limit.": {
|
||||
"Show a notification when battery reaches the charge limit.": "Toon een melding wanneer de accu de laadlimiet bereikt."
|
||||
},
|
||||
"Show a toast when the compositor config is reloaded": {
|
||||
"Show a toast when the compositor config is reloaded": ""
|
||||
},
|
||||
"Show a warning popup when battery is running low.": {
|
||||
"Show a warning popup when battery is running low.": "Toon een waarschuwingspop-up als de accu bijna leeg is."
|
||||
},
|
||||
@@ -7529,6 +7607,9 @@
|
||||
"Suspend then Hibernate": {
|
||||
"Suspend then Hibernate": "Onderbreken dan Sluimerstand"
|
||||
},
|
||||
"Swap primary and secondary mouse buttons": {
|
||||
"Swap primary and secondary mouse buttons": ""
|
||||
},
|
||||
"Sway Website": {
|
||||
"Sway Website": "Sway-website"
|
||||
},
|
||||
@@ -7652,6 +7733,18 @@
|
||||
"Tailscale not available": {
|
||||
"Tailscale not available": "Tailscale niet beschikbaar"
|
||||
},
|
||||
"Tap and Drag": {
|
||||
"Tap and Drag": ""
|
||||
},
|
||||
"Tap and drag on the touchpad to move items": {
|
||||
"Tap and drag on the touchpad to move items": ""
|
||||
},
|
||||
"Tap the touchpad surface to trigger left click clicks": {
|
||||
"Tap the touchpad surface to trigger left click clicks": ""
|
||||
},
|
||||
"Tap to Click": {
|
||||
"Tap to Click": ""
|
||||
},
|
||||
"Terminal": {
|
||||
"Terminal": "Terminal"
|
||||
},
|
||||
@@ -7727,6 +7820,9 @@
|
||||
"The rule applies to any window matching one of these.": {
|
||||
"The rule applies to any window matching one of these.": "De regel is van toepassing op elk venster dat overeenkomt met een van deze."
|
||||
},
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": {
|
||||
"The server certificate has changed since it was last trusted. Only continue if you recognize the new fingerprint.": ""
|
||||
},
|
||||
"Theme & Colors": {
|
||||
"Theme & Colors": "Thema & Kleuren"
|
||||
},
|
||||
@@ -7919,9 +8015,6 @@
|
||||
"Too many attempts - locked out": {
|
||||
"Too many attempts - locked out": "Te veel pogingen - toegang geblokkeerd"
|
||||
},
|
||||
"Too many failed attempts - account may be locked": {
|
||||
"Too many failed attempts - account may be locked": "Te veel mislukte pogingen - account is mogelijk geblokkeerd"
|
||||
},
|
||||
"Tools": {
|
||||
"Tools": "Hulpmiddelen"
|
||||
},
|
||||
@@ -7958,6 +8051,12 @@
|
||||
"Touch your security key...": {
|
||||
"Touch your security key...": "Raak uw beveiligingssleutel aan..."
|
||||
},
|
||||
"Touchpad Settings": {
|
||||
"Touchpad Settings": ""
|
||||
},
|
||||
"Touchpad Speed": {
|
||||
"Touchpad Speed": ""
|
||||
},
|
||||
"Transform": {
|
||||
"Transform": "Transformatie"
|
||||
},
|
||||
@@ -8018,6 +8117,9 @@
|
||||
"Turn off now": {
|
||||
"Turn off now": "Nu uitschakelen"
|
||||
},
|
||||
"Two Finger": {
|
||||
"Two Finger": ""
|
||||
},
|
||||
"Type at least 2 characters": {
|
||||
"Type at least 2 characters": "Typ minstens 2 tekens"
|
||||
},
|
||||
@@ -8159,6 +8261,9 @@
|
||||
"Untrust": {
|
||||
"Untrust": "Niet vertrouwen"
|
||||
},
|
||||
"Untrusted VPN certificate": {
|
||||
"Untrusted VPN certificate": ""
|
||||
},
|
||||
"Up to date": {
|
||||
"Up to date": "Bijgewerkt"
|
||||
},
|
||||
@@ -8342,9 +8447,6 @@
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": {
|
||||
"Username must start with a lowercase letter or underscore and contain only lowercase letters, digits, hyphens, or underscores.": "Gebruikersnaam moet beginnen met een kleine letter of underscore en mag alleen kleine letters, cijfers, koppeltekens of underscores bevatten."
|
||||
},
|
||||
"Username...": {
|
||||
"Username...": "Gebruikersnaam..."
|
||||
},
|
||||
"Users": {
|
||||
"Users": "Gebruikers"
|
||||
},
|
||||
@@ -8765,6 +8867,9 @@
|
||||
"attached": {
|
||||
"attached": "gekoppeld"
|
||||
},
|
||||
"below AA": {
|
||||
"below AA": ""
|
||||
},
|
||||
"brandon": {
|
||||
"brandon": "brandon"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user