mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
Compare commits
4 Commits
679a59ad76
...
e9fa2c78ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9fa2c78ee | ||
|
|
59dae954cd | ||
|
|
5c4ce86da4 | ||
|
|
0cf2c40377 |
@@ -1497,6 +1497,20 @@ func checkGreeterStatus() error {
|
||||
}
|
||||
|
||||
fmt.Println("\nConfiguration Symlinks:")
|
||||
colorSyncInfo, colorSyncErr := greeter.ResolveGreeterColorSyncInfo(homeDir)
|
||||
if colorSyncErr != nil {
|
||||
fmt.Printf(" ✗ Failed to resolve expected greeter color source: %v\n", colorSyncErr)
|
||||
allGood = false
|
||||
colorSyncInfo = greeter.GreeterColorSyncInfo{
|
||||
SourcePath: filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json"),
|
||||
}
|
||||
}
|
||||
|
||||
colorThemeDesc := "Color theme"
|
||||
if colorSyncInfo.UsesDynamicWallpaperOverride {
|
||||
colorThemeDesc = "Color theme (greeter wallpaper override)"
|
||||
}
|
||||
|
||||
symlinks := []struct {
|
||||
source string
|
||||
target string
|
||||
@@ -1513,9 +1527,9 @@ func checkGreeterStatus() error {
|
||||
desc: "Session state",
|
||||
},
|
||||
{
|
||||
source: filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json"),
|
||||
source: colorSyncInfo.SourcePath,
|
||||
target: filepath.Join(cacheDir, "colors.json"),
|
||||
desc: "Color theme",
|
||||
desc: colorThemeDesc,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1557,6 +1571,10 @@ func checkGreeterStatus() error {
|
||||
fmt.Printf(" ✓ %s: synced correctly\n", link.desc)
|
||||
}
|
||||
|
||||
if colorSyncInfo.UsesDynamicWallpaperOverride {
|
||||
fmt.Printf(" ℹ Dynamic theme uses greeter override colors from %s\n", colorSyncInfo.SourcePath)
|
||||
}
|
||||
|
||||
fmt.Println("\nGreeter Wallpaper Override:")
|
||||
overridePath := filepath.Join(cacheDir, "greeter_wallpaper_override.jpg")
|
||||
if stat, err := os.Stat(overridePath); err == nil && !stat.IsDir() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
"github.com/sblinch/kdl-go"
|
||||
"github.com/sblinch/kdl-go/document"
|
||||
@@ -1074,6 +1076,7 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
||||
}{
|
||||
{filepath.Join(homeDir, ".config", "DankMaterialShell"), "DankMaterialShell config"},
|
||||
{filepath.Join(homeDir, ".local", "state", "DankMaterialShell"), "DankMaterialShell state"},
|
||||
{filepath.Join(homeDir, ".cache", "DankMaterialShell"), "DankMaterialShell cache"},
|
||||
{filepath.Join(homeDir, ".cache", "quickshell"), "quickshell cache"},
|
||||
{filepath.Join(homeDir, ".config", "quickshell"), "quickshell config"},
|
||||
{filepath.Join(homeDir, ".local", "share", "wayland-sessions"), "wayland sessions"},
|
||||
@@ -1108,6 +1111,217 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type GreeterColorSyncInfo struct {
|
||||
SourcePath string
|
||||
ThemeName string
|
||||
UsesDynamicWallpaperOverride bool
|
||||
}
|
||||
|
||||
type greeterThemeSyncSettings struct {
|
||||
CurrentThemeName string `json:"currentThemeName"`
|
||||
GreeterWallpaperPath string `json:"greeterWallpaperPath"`
|
||||
MatugenScheme string `json:"matugenScheme"`
|
||||
IconTheme string `json:"iconTheme"`
|
||||
}
|
||||
|
||||
type greeterThemeSyncSession struct {
|
||||
IsLightMode bool `json:"isLightMode"`
|
||||
}
|
||||
|
||||
type greeterThemeSyncState struct {
|
||||
ThemeName string
|
||||
GreeterWallpaperPath string
|
||||
ResolvedGreeterWallpaperPath string
|
||||
MatugenScheme string
|
||||
IconTheme string
|
||||
IsLightMode bool
|
||||
UsesDynamicWallpaperOverride bool
|
||||
}
|
||||
|
||||
func defaultGreeterColorsSource(homeDir string) string {
|
||||
return filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json")
|
||||
}
|
||||
|
||||
func greeterOverrideColorsStateDir(homeDir string) string {
|
||||
return filepath.Join(homeDir, ".cache", "DankMaterialShell", "greeter-colors")
|
||||
}
|
||||
|
||||
func greeterOverrideColorsSource(homeDir string) string {
|
||||
return filepath.Join(greeterOverrideColorsStateDir(homeDir), "dms-colors.json")
|
||||
}
|
||||
|
||||
func readOptionalJSONFile(path string, dst any) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(string(data)) == "" {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, dst)
|
||||
}
|
||||
|
||||
func readGreeterThemeSyncSettings(homeDir string) (greeterThemeSyncSettings, error) {
|
||||
settings := greeterThemeSyncSettings{
|
||||
CurrentThemeName: "purple",
|
||||
MatugenScheme: "scheme-tonal-spot",
|
||||
IconTheme: "System Default",
|
||||
}
|
||||
settingsPath := filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json")
|
||||
if err := readOptionalJSONFile(settingsPath, &settings); err != nil {
|
||||
return greeterThemeSyncSettings{}, fmt.Errorf("failed to parse settings at %s: %w", settingsPath, err)
|
||||
}
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
func readGreeterThemeSyncSession(homeDir string) (greeterThemeSyncSession, error) {
|
||||
session := greeterThemeSyncSession{}
|
||||
sessionPath := filepath.Join(homeDir, ".local", "state", "DankMaterialShell", "session.json")
|
||||
if err := readOptionalJSONFile(sessionPath, &session); err != nil {
|
||||
return greeterThemeSyncSession{}, fmt.Errorf("failed to parse session at %s: %w", sessionPath, err)
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func resolveGreeterThemeSyncState(homeDir string) (greeterThemeSyncState, error) {
|
||||
settings, err := readGreeterThemeSyncSettings(homeDir)
|
||||
if err != nil {
|
||||
return greeterThemeSyncState{}, err
|
||||
}
|
||||
session, err := readGreeterThemeSyncSession(homeDir)
|
||||
if err != nil {
|
||||
return greeterThemeSyncState{}, err
|
||||
}
|
||||
|
||||
resolvedWallpaperPath := ""
|
||||
if settings.GreeterWallpaperPath != "" {
|
||||
resolvedWallpaperPath = settings.GreeterWallpaperPath
|
||||
if !filepath.IsAbs(resolvedWallpaperPath) {
|
||||
resolvedWallpaperPath = filepath.Join(homeDir, resolvedWallpaperPath)
|
||||
}
|
||||
}
|
||||
|
||||
usesDynamicWallpaperOverride := strings.EqualFold(strings.TrimSpace(settings.CurrentThemeName), "dynamic") && resolvedWallpaperPath != ""
|
||||
|
||||
return greeterThemeSyncState{
|
||||
ThemeName: settings.CurrentThemeName,
|
||||
GreeterWallpaperPath: settings.GreeterWallpaperPath,
|
||||
ResolvedGreeterWallpaperPath: resolvedWallpaperPath,
|
||||
MatugenScheme: settings.MatugenScheme,
|
||||
IconTheme: settings.IconTheme,
|
||||
IsLightMode: session.IsLightMode,
|
||||
UsesDynamicWallpaperOverride: usesDynamicWallpaperOverride,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s greeterThemeSyncState) effectiveColorsSource(homeDir string) string {
|
||||
if s.UsesDynamicWallpaperOverride {
|
||||
return greeterOverrideColorsSource(homeDir)
|
||||
}
|
||||
return defaultGreeterColorsSource(homeDir)
|
||||
}
|
||||
|
||||
func ResolveGreeterColorSyncInfo(homeDir string) (GreeterColorSyncInfo, error) {
|
||||
state, err := resolveGreeterThemeSyncState(homeDir)
|
||||
if err != nil {
|
||||
return GreeterColorSyncInfo{}, err
|
||||
}
|
||||
return GreeterColorSyncInfo{
|
||||
SourcePath: state.effectiveColorsSource(homeDir),
|
||||
ThemeName: state.ThemeName,
|
||||
UsesDynamicWallpaperOverride: state.UsesDynamicWallpaperOverride,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ensureGreeterSyncSourceFile(path string) error {
|
||||
sourceDir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(sourceDir, 0o755); err != nil {
|
||||
return fmt.Errorf("failed to create source directory %s: %w", sourceDir, err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err := os.WriteFile(path, []byte("{}"), 0o644); err != nil {
|
||||
return fmt.Errorf("failed to create source file %s: %w", path, err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to inspect source file %s: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncGreeterDynamicOverrideColors(dmsPath, homeDir string, state greeterThemeSyncState, logFunc func(string)) error {
|
||||
if !state.UsesDynamicWallpaperOverride {
|
||||
return nil
|
||||
}
|
||||
|
||||
st, err := os.Stat(state.ResolvedGreeterWallpaperPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("configured greeter wallpaper not found at %s: %w", state.ResolvedGreeterWallpaperPath, err)
|
||||
}
|
||||
if st.IsDir() {
|
||||
return fmt.Errorf("configured greeter wallpaper path points to a directory: %s", state.ResolvedGreeterWallpaperPath)
|
||||
}
|
||||
|
||||
mode := matugen.ColorModeDark
|
||||
if state.IsLightMode {
|
||||
mode = matugen.ColorModeLight
|
||||
}
|
||||
|
||||
opts := matugen.Options{
|
||||
StateDir: greeterOverrideColorsStateDir(homeDir),
|
||||
ShellDir: dmsPath,
|
||||
ConfigDir: filepath.Join(homeDir, ".config"),
|
||||
Kind: "image",
|
||||
Value: state.ResolvedGreeterWallpaperPath,
|
||||
Mode: mode,
|
||||
IconTheme: state.IconTheme,
|
||||
MatugenType: state.MatugenScheme,
|
||||
RunUserTemplates: false,
|
||||
ColorsOnly: true,
|
||||
}
|
||||
|
||||
err = matugen.Run(opts)
|
||||
switch {
|
||||
case errors.Is(err, matugen.ErrNoChanges):
|
||||
logFunc("✓ Greeter dynamic override colors already up to date")
|
||||
return nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("failed to generate greeter dynamic colors from wallpaper override: %w", err)
|
||||
default:
|
||||
logFunc("✓ Generated greeter dynamic colors from wallpaper override")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func syncGreeterColorSource(homeDir, cacheDir string, state greeterThemeSyncState, logFunc func(string), sudoPassword string) error {
|
||||
source := state.effectiveColorsSource(homeDir)
|
||||
if !state.UsesDynamicWallpaperOverride {
|
||||
if err := ensureGreeterSyncSourceFile(source); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if _, err := os.Stat(source); err != nil {
|
||||
return fmt.Errorf("expected generated greeter colors at %s: %w", source, err)
|
||||
}
|
||||
|
||||
target := filepath.Join(cacheDir, "colors.json")
|
||||
_ = runSudoCmd(sudoPassword, "rm", "-f", target)
|
||||
if err := runSudoCmd(sudoPassword, "ln", "-sf", source, target); err != nil {
|
||||
return fmt.Errorf("failed to create symlink for wallpaper based theming (%s -> %s): %w", target, source, err)
|
||||
}
|
||||
|
||||
if state.UsesDynamicWallpaperOverride {
|
||||
logFunc("✓ Synced wallpaper based theming (greeter wallpaper override)")
|
||||
} else {
|
||||
logFunc("✓ Synced wallpaper based theming")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPassword string, forceAuth bool) error {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -1131,11 +1345,6 @@ func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPasswo
|
||||
target: filepath.Join(cacheDir, "session.json"),
|
||||
desc: "state (wallpaper configuration)",
|
||||
},
|
||||
{
|
||||
source: filepath.Join(homeDir, ".cache", "DankMaterialShell", "dms-colors.json"),
|
||||
target: filepath.Join(cacheDir, "colors.json"),
|
||||
desc: "wallpaper based theming",
|
||||
},
|
||||
}
|
||||
|
||||
for _, link := range symlinks {
|
||||
@@ -1161,7 +1370,20 @@ func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPasswo
|
||||
logFunc(fmt.Sprintf("✓ Synced %s", link.desc))
|
||||
}
|
||||
|
||||
if err := syncGreeterWallpaperOverride(homeDir, cacheDir, logFunc, sudoPassword); err != nil {
|
||||
state, err := resolveGreeterThemeSyncState(homeDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve greeter color source: %w", err)
|
||||
}
|
||||
|
||||
if err := syncGreeterDynamicOverrideColors(dmsPath, homeDir, state, logFunc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syncGreeterColorSource(homeDir, cacheDir, state, logFunc, sudoPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := syncGreeterWallpaperOverride(cacheDir, logFunc, sudoPassword, state); err != nil {
|
||||
return fmt.Errorf("greeter wallpaper override sync failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -1180,23 +1402,9 @@ func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPasswo
|
||||
return nil
|
||||
}
|
||||
|
||||
func syncGreeterWallpaperOverride(homeDir, cacheDir string, logFunc func(string), sudoPassword string) error {
|
||||
settingsPath := filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json")
|
||||
data, err := os.ReadFile(settingsPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("failed to read settings at %s: %w", settingsPath, err)
|
||||
}
|
||||
var settings struct {
|
||||
GreeterWallpaperPath string `json:"greeterWallpaperPath"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &settings); err != nil {
|
||||
return fmt.Errorf("failed to parse settings at %s: %w", settingsPath, err)
|
||||
}
|
||||
func syncGreeterWallpaperOverride(cacheDir string, logFunc func(string), sudoPassword string, state greeterThemeSyncState) error {
|
||||
destPath := filepath.Join(cacheDir, "greeter_wallpaper_override.jpg")
|
||||
if settings.GreeterWallpaperPath == "" {
|
||||
if state.ResolvedGreeterWallpaperPath == "" {
|
||||
if err := runSudoCmd(sudoPassword, "rm", "-f", destPath); err != nil {
|
||||
return fmt.Errorf("failed to clear override file %s: %w", destPath, err)
|
||||
}
|
||||
@@ -1206,10 +1414,7 @@ func syncGreeterWallpaperOverride(homeDir, cacheDir string, logFunc func(string)
|
||||
if err := runSudoCmd(sudoPassword, "rm", "-f", destPath); err != nil {
|
||||
return fmt.Errorf("failed to remove old override file %s: %w", destPath, err)
|
||||
}
|
||||
src := settings.GreeterWallpaperPath
|
||||
if !filepath.IsAbs(src) {
|
||||
src = filepath.Join(homeDir, src)
|
||||
}
|
||||
src := state.ResolvedGreeterWallpaperPath
|
||||
st, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("configured greeter wallpaper not found at %s: %w", src, err)
|
||||
|
||||
98
core/internal/greeter/installer_test.go
Normal file
98
core/internal/greeter/installer_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package greeter
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func writeTestJSON(t *testing.T, path string, content string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
t.Fatalf("failed to create parent dir for %s: %v", path, err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("failed to write %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGreeterThemeSyncState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
settingsJSON string
|
||||
sessionJSON string
|
||||
wantSourcePath string
|
||||
wantResolvedWallpaper string
|
||||
wantDynamicOverrideUsed bool
|
||||
}{
|
||||
{
|
||||
name: "dynamic theme with greeter wallpaper override uses generated greeter colors",
|
||||
settingsJSON: `{
|
||||
"currentThemeName": "dynamic",
|
||||
"greeterWallpaperPath": "Pictures/blue.jpg",
|
||||
"matugenScheme": "scheme-tonal-spot",
|
||||
"iconTheme": "Papirus"
|
||||
}`,
|
||||
sessionJSON: `{"isLightMode":true}`,
|
||||
wantSourcePath: filepath.Join(".cache", "DankMaterialShell", "greeter-colors", "dms-colors.json"),
|
||||
wantResolvedWallpaper: filepath.Join("Pictures", "blue.jpg"),
|
||||
wantDynamicOverrideUsed: true,
|
||||
},
|
||||
{
|
||||
name: "dynamic theme without override uses desktop colors",
|
||||
settingsJSON: `{
|
||||
"currentThemeName": "dynamic",
|
||||
"greeterWallpaperPath": ""
|
||||
}`,
|
||||
sessionJSON: `{"isLightMode":false}`,
|
||||
wantSourcePath: filepath.Join(".cache", "DankMaterialShell", "dms-colors.json"),
|
||||
wantResolvedWallpaper: "",
|
||||
wantDynamicOverrideUsed: false,
|
||||
},
|
||||
{
|
||||
name: "non-dynamic theme keeps desktop colors even with override wallpaper",
|
||||
settingsJSON: `{
|
||||
"currentThemeName": "purple",
|
||||
"greeterWallpaperPath": "/tmp/blue.jpg"
|
||||
}`,
|
||||
sessionJSON: `{"isLightMode":false}`,
|
||||
wantSourcePath: filepath.Join(".cache", "DankMaterialShell", "dms-colors.json"),
|
||||
wantResolvedWallpaper: "/tmp/blue.jpg",
|
||||
wantDynamicOverrideUsed: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
homeDir := t.TempDir()
|
||||
writeTestJSON(t, filepath.Join(homeDir, ".config", "DankMaterialShell", "settings.json"), tt.settingsJSON)
|
||||
writeTestJSON(t, filepath.Join(homeDir, ".local", "state", "DankMaterialShell", "session.json"), tt.sessionJSON)
|
||||
|
||||
state, err := resolveGreeterThemeSyncState(homeDir)
|
||||
if err != nil {
|
||||
t.Fatalf("resolveGreeterThemeSyncState returned error: %v", err)
|
||||
}
|
||||
|
||||
if got := state.effectiveColorsSource(homeDir); got != filepath.Join(homeDir, tt.wantSourcePath) {
|
||||
t.Fatalf("effectiveColorsSource = %q, want %q", got, filepath.Join(homeDir, tt.wantSourcePath))
|
||||
}
|
||||
|
||||
wantResolvedWallpaper := tt.wantResolvedWallpaper
|
||||
if wantResolvedWallpaper != "" && !filepath.IsAbs(wantResolvedWallpaper) {
|
||||
wantResolvedWallpaper = filepath.Join(homeDir, wantResolvedWallpaper)
|
||||
}
|
||||
if state.ResolvedGreeterWallpaperPath != wantResolvedWallpaper {
|
||||
t.Fatalf("ResolvedGreeterWallpaperPath = %q, want %q", state.ResolvedGreeterWallpaperPath, wantResolvedWallpaper)
|
||||
}
|
||||
|
||||
if state.UsesDynamicWallpaperOverride != tt.wantDynamicOverrideUsed {
|
||||
t.Fatalf("UsesDynamicWallpaperOverride = %v, want %v", state.UsesDynamicWallpaperOverride, tt.wantDynamicOverrideUsed)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,7 @@ type Options struct {
|
||||
IconTheme string
|
||||
MatugenType string
|
||||
RunUserTemplates bool
|
||||
ColorsOnly bool
|
||||
StockColors string
|
||||
SyncModeWithPortal bool
|
||||
TerminalsAlwaysDark bool
|
||||
@@ -274,6 +275,10 @@ func buildOnce(opts *Options) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if opts.ColorsOnly {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if isDMSGTKActive(opts.ConfigDir) {
|
||||
switch opts.Mode {
|
||||
case ColorModeLight:
|
||||
@@ -331,6 +336,10 @@ output_path = '%s'
|
||||
|
||||
`, opts.ShellDir, opts.ColorsOutput())
|
||||
|
||||
if opts.ColorsOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
for _, tmpl := range templateRegistry {
|
||||
if opts.ShouldSkipTemplate(tmpl.ID) {
|
||||
@@ -597,10 +606,10 @@ func detectMatugenVersionLocked() (matugenFlags, error) {
|
||||
matugenVersionOK = true
|
||||
|
||||
if matugenSupportsCOE {
|
||||
log.Infof("Matugen %s supports --continue-on-error", versionStr)
|
||||
log.Debugf("Matugen %s detected: continue-on-error support enabled", versionStr)
|
||||
}
|
||||
if matugenIsV4 {
|
||||
log.Infof("Matugen %s: using v4 flags", versionStr)
|
||||
log.Debugf("Matugen %s detected: using v4 compatibility flags", versionStr)
|
||||
}
|
||||
return matugenFlags{matugenSupportsCOE, matugenIsV4}, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package matugen
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
mocks_utils "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/utils"
|
||||
@@ -392,3 +393,51 @@ func TestSubstituteVars(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMergedConfigColorsOnly(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
shellDir := filepath.Join(tempDir, "shell")
|
||||
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||
if err := os.MkdirAll(configsDir, 0o755); err != nil {
|
||||
t.Fatalf("failed to create configs dir: %v", err)
|
||||
}
|
||||
|
||||
baseConfig := "[config]\ncustom_keywords = []\n"
|
||||
if err := os.WriteFile(filepath.Join(configsDir, "base.toml"), []byte(baseConfig), 0o644); err != nil {
|
||||
t.Fatalf("failed to write base config: %v", err)
|
||||
}
|
||||
|
||||
cfgFile, err := os.CreateTemp(tempDir, "merged-*.toml")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp config: %v", err)
|
||||
}
|
||||
defer os.Remove(cfgFile.Name())
|
||||
defer cfgFile.Close()
|
||||
|
||||
opts := &Options{
|
||||
ShellDir: shellDir,
|
||||
ConfigDir: filepath.Join(tempDir, "config"),
|
||||
StateDir: filepath.Join(tempDir, "state"),
|
||||
ColorsOnly: true,
|
||||
}
|
||||
|
||||
if err := buildMergedConfig(opts, cfgFile, filepath.Join(tempDir, "templates")); err != nil {
|
||||
t.Fatalf("buildMergedConfig failed: %v", err)
|
||||
}
|
||||
|
||||
if err := cfgFile.Close(); err != nil {
|
||||
t.Fatalf("failed to close merged config: %v", err)
|
||||
}
|
||||
|
||||
output, err := os.ReadFile(cfgFile.Name())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read merged config: %v", err)
|
||||
}
|
||||
|
||||
content := string(output)
|
||||
assert.Contains(t, content, "[templates.dank]")
|
||||
assert.Contains(t, content, "output_path = '"+filepath.Join(opts.StateDir, "dms-colors.json")+"'")
|
||||
assert.NotContains(t, content, "[templates.gtk]")
|
||||
assert.False(t, strings.Contains(content, "output_path = 'CONFIG_DIR/"), "colors-only config should not emit app template outputs")
|
||||
}
|
||||
|
||||
@@ -538,6 +538,8 @@ Color picker modal control.
|
||||
|
||||
**Functions:**
|
||||
- `open` - Show color picker modal
|
||||
- `openColor <color>` - Show color picker modal with a pre-selected color
|
||||
- Parameters: `color` - Color string (e.g. "#ff0000", "#3f51b5")
|
||||
- `close` - Hide color picker modal
|
||||
- `closeInstant` - Hide color picker modal without animation
|
||||
- `toggle` - Toggle color picker modal visibility
|
||||
|
||||
@@ -1278,7 +1278,10 @@ Singleton {
|
||||
}
|
||||
|
||||
if (themeData.variants.options && themeData.variants.options.length > 0) {
|
||||
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default;
|
||||
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||
const selectedVariantId = isGreeterMode
|
||||
? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : themeData.variants.default)
|
||||
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, themeData.variants.default) : themeData.variants.default);
|
||||
const variant = findVariant(themeData.variants.options, selectedVariantId);
|
||||
if (variant) {
|
||||
const variantColors = variant[colorMode] || variant.dark || variant.light || {};
|
||||
@@ -1650,8 +1653,13 @@ Singleton {
|
||||
const defaults = customThemeRawData.variants.defaults || {};
|
||||
const darkDefaults = defaults.dark || {};
|
||||
const lightDefaults = defaults.light || defaults.dark || {};
|
||||
const storedDark = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults;
|
||||
const storedLight = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults;
|
||||
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||
const storedDark = isGreeterMode
|
||||
? (GreetdSettings.registryThemeVariants[themeId]?.dark || darkDefaults)
|
||||
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, darkDefaults, "dark") : darkDefaults);
|
||||
const storedLight = isGreeterMode
|
||||
? (GreetdSettings.registryThemeVariants[themeId]?.light || lightDefaults)
|
||||
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeMultiVariant(themeId, lightDefaults, "light") : lightDefaults);
|
||||
const darkFlavorId = storedDark.flavor || darkDefaults.flavor || "";
|
||||
const lightFlavorId = storedLight.flavor || lightDefaults.flavor || "";
|
||||
const accentId = storedDark.accent || darkDefaults.accent || "";
|
||||
@@ -1669,7 +1677,10 @@ Singleton {
|
||||
lightTheme = mergeColors(lightTheme, accent[lightFlavor.id] || {});
|
||||
}
|
||||
} else if (customThemeRawData.variants.options) {
|
||||
const selectedVariantId = typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default;
|
||||
const isGreeterMode = typeof SessionData !== "undefined" && SessionData.isGreeterMode;
|
||||
const selectedVariantId = isGreeterMode
|
||||
? (typeof GreetdSettings.registryThemeVariants[themeId] === "string" ? GreetdSettings.registryThemeVariants[themeId] : customThemeRawData.variants.default)
|
||||
: (typeof SettingsData !== "undefined" ? SettingsData.getRegistryThemeVariant(themeId, customThemeRawData.variants.default) : customThemeRawData.variants.default);
|
||||
const variant = findVariant(customThemeRawData.variants.options, selectedVariantId);
|
||||
if (variant) {
|
||||
darkTheme = mergeColors(darkTheme, variant.dark || {});
|
||||
@@ -1997,6 +2008,7 @@ Singleton {
|
||||
const colorsPath = SessionData.isGreeterMode ? greetCfgDir + "/colors.json" : stateDir + "/dms-colors.json";
|
||||
return colorsPath;
|
||||
}
|
||||
blockLoading: false
|
||||
watchChanges: !SessionData.isGreeterMode
|
||||
|
||||
function parseAndLoadColors() {
|
||||
|
||||
@@ -26,7 +26,9 @@ Rectangle {
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: keyboardHints.enterToPaste ? I18n.tr("↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help", "Keyboard hints when enter-to-paste is enabled") : "↑/↓: Navigate • Enter/Ctrl+C: Copy • Del: Delete • F10: Help"
|
||||
text: keyboardHints.enterToPaste
|
||||
? I18n.tr("↑/↓: Navigate • Enter: Paste • Del: Delete • F10: Help", "Keyboard hints when enter-to-paste is enabled")
|
||||
: I18n.tr("↑/↓: Navigate • Enter/Ctrl+C: Copy • Del: Delete • F10: Help")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -147,6 +147,13 @@ DankModal {
|
||||
return "COLOR_PICKER_MODAL_OPEN_SUCCESS";
|
||||
}
|
||||
|
||||
function openColor(color: string): string {
|
||||
root.selectedColor = Qt.color(color);
|
||||
root.currentColor = Qt.color(color);
|
||||
root.updateFromColor(Qt.color(color));
|
||||
return open();
|
||||
}
|
||||
|
||||
function close(): string {
|
||||
root.hide();
|
||||
return "COLOR_PICKER_MODAL_CLOSE_SUCCESS";
|
||||
|
||||
@@ -304,7 +304,17 @@ DankModal {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("%1 active, %2 filtered").arg(MuxService.sessions.length).arg(muxModal.filteredSessions.length)
|
||||
text: {
|
||||
const total = MuxService.sessions.length;
|
||||
const filtered = muxModal.filteredSessions.length;
|
||||
const activePart = total === 1
|
||||
? I18n.tr("%1 active session").arg(total)
|
||||
: I18n.tr("%1 active sessions").arg(total);
|
||||
const filteredPart = filtered === 1
|
||||
? I18n.tr("%1 filtered").arg(filtered)
|
||||
: I18n.tr("%1 filtered").arg(filtered);
|
||||
return activePart + ", " + filteredPart;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
}
|
||||
@@ -468,7 +478,9 @@ DankModal {
|
||||
text: {
|
||||
var parts = []
|
||||
if (modelData.windows !== "N/A")
|
||||
parts.push(I18n.tr("%1 windows").arg(modelData.windows))
|
||||
parts.push(modelData.windows === 1
|
||||
? I18n.tr("%1 window").arg(modelData.windows)
|
||||
: I18n.tr("%1 windows").arg(modelData.windows))
|
||||
parts.push(modelData.attached ? I18n.tr("attached") : I18n.tr("detached"))
|
||||
return parts.join(" \u2022 ")
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `Details for "${networkSSID}"`
|
||||
text: I18n.tr("Details for \"%1\"").arg(networkSSID)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
@@ -102,7 +102,7 @@ DankModal {
|
||||
id: detailsText
|
||||
|
||||
width: parent.width
|
||||
text: NetworkService.networkInfoDetails && NetworkService.networkInfoDetails.replace(/\\n/g, '\n') || "No information available"
|
||||
text: NetworkService.networkInfoDetails && NetworkService.networkInfoDetails.replace(/\\n/g, '\n') || I18n.tr("No information available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
@@ -66,7 +66,7 @@ DankModal {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: `Details for "${networkID}"`
|
||||
text: I18n.tr("Details for \"%1\"").arg(networkSSID)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
@@ -102,7 +102,7 @@ DankModal {
|
||||
id: detailsText
|
||||
|
||||
width: parent.width
|
||||
text: NetworkService.networkWiredInfoDetails && NetworkService.networkWiredInfoDetails.replace(/\\n/g, '\n') || "No information available"
|
||||
text: NetworkService.networkWiredInfoDetails && NetworkService.networkWiredInfoDetails.replace(/\\n/g, '\n') || I18n.tr("No information available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
@@ -22,12 +22,12 @@ DankPopout {
|
||||
|
||||
function setProfile(profile) {
|
||||
if (typeof PowerProfiles === "undefined") {
|
||||
ToastService.showError("power-profiles-daemon not available");
|
||||
ToastService.showError(I18n.tr("power-profiles-daemon not available"));
|
||||
return;
|
||||
}
|
||||
PowerProfiles.profile = profile;
|
||||
if (PowerProfiles.profile !== profile) {
|
||||
ToastService.showError("Failed to set power profile");
|
||||
ToastService.showError(I18n.tr("Failed to set power profile"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,10 +173,10 @@ DankPopout {
|
||||
width: parent.width - Theme.iconSizeLarge - 32 - Theme.spacingM * 2
|
||||
readonly property string timeInfoText: {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return "Power profile management available";
|
||||
return I18n.tr("Power profile management available");
|
||||
const time = BatteryService.formatTimeRemaining();
|
||||
if (time !== "Unknown") {
|
||||
return BatteryService.isCharging ? `Time until full: ${time}` : `Time remaining: ${time}`;
|
||||
return BatteryService.isCharging ? I18n.tr("Time until full: %1").arg(time) : I18n.tr("Time remaining: %1").arg(time);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -188,7 +188,7 @@ DankPopout {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : "Power"
|
||||
text: BatteryService.batteryAvailable ? `${BatteryService.batteryLevel}%` : I18n.tr("Power")
|
||||
font.pixelSize: Theme.fontSizeXLarge
|
||||
color: {
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging) {
|
||||
@@ -338,7 +338,7 @@ DankPopout {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : "Unknown"
|
||||
text: BatteryService.batteryCapacity > 0 ? `${BatteryService.batteryCapacity.toFixed(1)} Wh` : I18n.tr("Unknown")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Bold
|
||||
@@ -393,7 +393,7 @@ DankPopout {
|
||||
width: parent.width - percentText.width - chargingIcon.width - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: modelData.model || `Battery ${index + 1}`
|
||||
text: modelData.model || I18n.tr("Battery %1").arg(index + 1)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
|
||||
@@ -320,7 +320,7 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
StyledText {
|
||||
text: activePlayer?.trackTitle || "Unknown Track"
|
||||
text: activePlayer?.trackTitle || I18n.tr("Unknown Track")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
font.weight: Font.Bold
|
||||
color: Theme.surfaceText
|
||||
@@ -332,7 +332,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: activePlayer?.trackArtist || "Unknown Artist"
|
||||
text: activePlayer?.trackTitle || I18n.tr("Unknown Artist")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.8)
|
||||
width: parent.width
|
||||
@@ -669,7 +669,7 @@ Item {
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||
}
|
||||
onEntered: sharedTooltip.show("Media Players", playerSelectorButton, 0, 0, isRightEdge ? "right" : "left")
|
||||
onEntered: sharedTooltip.show(I18n.tr("Media Players"), playerSelectorButton, 0, 0, isRightEdge ? "right" : "left")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
}
|
||||
@@ -788,7 +788,7 @@ Item {
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
|
||||
}
|
||||
onEntered: sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left")
|
||||
onEntered: sharedTooltip.show(I18n.tr("Output Device"), audioDevicesButton, 0, 0, isRightEdge ? "right" : "left")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ Card {
|
||||
topPadding: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
text: activePlayer?.trackTitle || "Unknown"
|
||||
text: activePlayer?.trackTitle || I18n.tr("Unknown")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
@@ -99,7 +99,7 @@ Card {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: activePlayer?.trackArtist || "Unknown Artist"
|
||||
text: activePlayer?.trackArtist || I18n.tr("Unknown Artist")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
width: parent.width
|
||||
|
||||
@@ -28,7 +28,7 @@ Card {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: WeatherService.weather.loading ? "Loading..." : "No Weather"
|
||||
text: WeatherService.weather.loading ? I18n.tr("Loading...") : I18n.tr("No Weather")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
@@ -538,7 +538,11 @@ Item {
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: wallpaperFolderModel.count > 0 ? `${wallpaperFolderModel.count} wallpapers • ${currentPage + 1} / ${totalPages}` : "No wallpapers"
|
||||
text: wallpaperFolderModel.count > 0
|
||||
? (wallpaperFolderModel.count === 1
|
||||
? I18n.tr("%1 wallpaper • %2 / %3").arg(wallpaperFolderModel.count).arg(currentPage + 1).arg(totalPages)
|
||||
: I18n.tr("%1 wallpapers • %2 / %3").arg(wallpaperFolderModel.count).arg(currentPage + 1).arg(totalPages))
|
||||
: I18n.tr("No wallpapers")
|
||||
font.pixelSize: 14
|
||||
color: Theme.surfaceText
|
||||
opacity: 0.7
|
||||
|
||||
@@ -230,19 +230,19 @@ Item {
|
||||
|
||||
function currentAuthMessage() {
|
||||
if (GreeterState.pamState === "error")
|
||||
return "Authentication error - try again";
|
||||
return I18n.tr("Authentication error - try again");
|
||||
if (GreeterState.pamState === "max")
|
||||
return "Too many failed attempts - account may be locked";
|
||||
return I18n.tr("Too many failed attempts - account may be locked");
|
||||
if (GreeterState.pamState === "fail") {
|
||||
if (passwordAttemptLimitHint > 0) {
|
||||
const attempt = Math.max(1, Math.min(passwordFailureCount, passwordAttemptLimitHint));
|
||||
const remaining = Math.max(passwordAttemptLimitHint - attempt, 0);
|
||||
if (remaining > 0) {
|
||||
return "Incorrect password - attempt " + attempt + " of " + passwordAttemptLimitHint + " (lockout may follow)";
|
||||
return I18n.tr("Incorrect password - attempt %1 of %2 (lockout may follow)").arg(attempt).arg(passwordAttemptLimitHint);
|
||||
}
|
||||
return "Incorrect password - next failures may trigger account lockout";
|
||||
return I18n.tr("Incorrect password - next failures may trigger account lockout");
|
||||
}
|
||||
return "Incorrect password";
|
||||
return I18n.tr("Incorrect password");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -1012,15 +1012,15 @@ Item {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (GreeterState.unlocking) {
|
||||
return "Logging in...";
|
||||
return I18n.tr("Logging in...");
|
||||
}
|
||||
if (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse) {
|
||||
return "Authenticating...";
|
||||
return I18n.tr("Authenticating...");
|
||||
}
|
||||
if (GreeterState.showPasswordInput) {
|
||||
return "Password...";
|
||||
return I18n.tr("Password...");
|
||||
}
|
||||
return "Username...";
|
||||
return I18n.tr("Username...");
|
||||
}
|
||||
color: (GreeterState.unlocking || (Greetd.state !== GreetdState.Inactive && !awaitingExternalAuth && !pendingPasswordResponse)) ? Theme.primary : Theme.outline
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
|
||||
@@ -271,10 +271,6 @@ hotkey-overlay {
|
||||
|
||||
environment {
|
||||
DMS_RUN_GREETER "1"
|
||||
HOME "$CACHE_DIR"
|
||||
XDG_STATE_HOME "$CACHE_DIR/.local/state"
|
||||
XDG_DATA_HOME "$CACHE_DIR/.local/share"
|
||||
XDG_CACHE_HOME "$CACHE_DIR/.cache"
|
||||
}
|
||||
|
||||
debug {
|
||||
@@ -328,16 +324,11 @@ NIRI_EOF
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
env = DMS_RUN_GREETER,1
|
||||
env = HOME,$CACHE_DIR
|
||||
env = XDG_STATE_HOME,$CACHE_DIR/.local/state
|
||||
env = XDG_DATA_HOME,$CACHE_DIR/.local/share
|
||||
env = XDG_CACHE_HOME,$CACHE_DIR/.cache
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
}
|
||||
|
||||
exec-once = systemctl --user import-environment HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME 2>/dev/null || true
|
||||
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
@@ -346,11 +337,6 @@ HYPRLAND_EOF
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
|
||||
env = HOME,$CACHE_DIR
|
||||
env = XDG_STATE_HOME,$CACHE_DIR/.local/state
|
||||
env = XDG_DATA_HOME,$CACHE_DIR/.local/share
|
||||
env = XDG_CACHE_HOME,$CACHE_DIR/.cache
|
||||
exec-once = systemctl --user import-environment HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME 2>/dev/null || true
|
||||
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
@@ -367,7 +353,6 @@ HYPRLAND_EOF
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << SWAY_EOF
|
||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
||||
exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
@@ -376,7 +361,6 @@ SWAY_EOF
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << SWAY_EOF
|
||||
|
||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
||||
exec "$QS_CMD; swaymsg exit"
|
||||
SWAY_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
@@ -389,7 +373,6 @@ SWAY_EOF
|
||||
if [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << SCROLL_EOF
|
||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
||||
exec "$QS_CMD; scrollmsg exit"
|
||||
SCROLL_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
@@ -398,7 +381,6 @@ SCROLL_EOF
|
||||
cat "$COMPOSITOR_CONFIG" > "$TEMP_CONFIG"
|
||||
cat >> "$TEMP_CONFIG" << SCROLL_EOF
|
||||
|
||||
exec --no-startup-id dbus-update-activation-environment --systemd HOME XDG_STATE_HOME XDG_DATA_HOME XDG_CACHE_HOME
|
||||
exec "$QS_CMD; scrollmsg exit"
|
||||
SCROLL_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
|
||||
@@ -832,13 +832,21 @@ Column {
|
||||
spacing: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
text: textArea.text.length > 0 ? I18n.tr("%1 characters").arg(textArea.text.length) : I18n.tr("Empty")
|
||||
text: {
|
||||
const len = textArea.text.length;
|
||||
if (len === 0) return I18n.tr("Empty");
|
||||
return len === 1
|
||||
? I18n.tr("%1 character").arg(len)
|
||||
: I18n.tr("%1 characters").arg(len);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Lines: %1").arg(textArea.lineCount)
|
||||
text: textArea.lineCount === 1
|
||||
? I18n.tr("Line: %1").arg(textArea.lineCount)
|
||||
: I18n.tr("Lines: %1").arg(textArea.lineCount)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: textArea.text.length > 0
|
||||
|
||||
@@ -713,7 +713,7 @@ Rectangle {
|
||||
StyledText {
|
||||
id: expandedActionText
|
||||
text: {
|
||||
const baseText = modelData.text || "Open";
|
||||
const baseText = modelData.text || I18n.tr("Open");
|
||||
if (keyboardNavigationActive && (isGroupSelected || selectedNotificationIndex >= 0))
|
||||
return `${baseText} (${index + 1})`;
|
||||
return baseText;
|
||||
@@ -849,7 +849,7 @@ Rectangle {
|
||||
StyledText {
|
||||
id: collapsedActionText
|
||||
text: {
|
||||
const baseText = modelData.text || "Open";
|
||||
const baseText = modelData.text || I18n.tr("Open");
|
||||
if (keyboardNavigationActive && isGroupSelected) {
|
||||
return `${baseText} (${index + 1})`;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ Rectangle {
|
||||
|
||||
function getTimeoutText(value) {
|
||||
if (value === undefined || value === null || isNaN(value)) {
|
||||
return "5 seconds";
|
||||
return I18n.tr("5 seconds");
|
||||
}
|
||||
|
||||
for (let i = 0; i < timeoutOptions.length; i++) {
|
||||
@@ -94,15 +94,15 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
if (value === 0) {
|
||||
return "Never";
|
||||
return I18n.tr("Never");
|
||||
}
|
||||
if (value < 1000) {
|
||||
return value + "ms";
|
||||
}
|
||||
if (value < 60000) {
|
||||
return Math.round(value / 1000) + " seconds";
|
||||
return Math.round(value / 1000) + " " + I18n.tr("seconds");
|
||||
}
|
||||
return Math.round(value / 60000) + " minutes";
|
||||
return Math.round(value / 60000) + " " + I18n.tr("minutes");
|
||||
}
|
||||
|
||||
Column {
|
||||
@@ -169,7 +169,7 @@ Rectangle {
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Low Priority")
|
||||
description: "Timeout for low priority notifications"
|
||||
description: I18n.tr("Timeout for low priority notifications")
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutLow)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
@@ -184,7 +184,7 @@ Rectangle {
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Normal Priority")
|
||||
description: "Timeout for normal priority notifications"
|
||||
description: I18n.tr("Timeout for normal priority notifications")
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutNormal)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
@@ -199,7 +199,7 @@ Rectangle {
|
||||
|
||||
DankDropdown {
|
||||
text: I18n.tr("Critical Priority")
|
||||
description: "Timeout for critical priority notifications"
|
||||
description: I18n.tr("Timeout for critical priority notifications")
|
||||
currentValue: getTimeoutText(SettingsData.notificationTimeoutCritical)
|
||||
options: timeoutOptions.map(opt => opt.text)
|
||||
onValueChanged: value => {
|
||||
@@ -225,6 +225,8 @@ Rectangle {
|
||||
Row {
|
||||
id: overlayRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: overlayToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
@@ -238,17 +240,22 @@ Rectangle {
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: overlayRow.width - Theme.iconSizeSmall - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Notification Overlay")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Display all priorities over fullscreen apps")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,6 +276,8 @@ Rectangle {
|
||||
Row {
|
||||
id: privacyRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: privacyToggle.left
|
||||
anchors.rightMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
|
||||
@@ -282,17 +291,22 @@ Rectangle {
|
||||
Column {
|
||||
spacing: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: privacyRow.width - Theme.iconSizeSmall - Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Privacy Mode")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
width: parent.width
|
||||
text: I18n.tr("Hide notification content until expanded")
|
||||
font.pixelSize: Theme.fontSizeSmall - 1
|
||||
color: Theme.surfaceVariantText
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,22 +68,22 @@ Item {
|
||||
|
||||
property string compositorTooltip: {
|
||||
if (isHyprland)
|
||||
return "Hyprland Website";
|
||||
return I18n.tr("Hyprland Website");
|
||||
if (isSway)
|
||||
return "Sway Website";
|
||||
return I18n.tr("Sway Website");
|
||||
if (isScroll)
|
||||
return "Scroll Github";
|
||||
return I18n.tr("Scroll GitHub");
|
||||
if (isMiracle)
|
||||
return "Miracle WM GitHub";
|
||||
return I18n.tr("Scroll GitHub");
|
||||
if (isDwl)
|
||||
return "mangowc GitHub";
|
||||
return I18n.tr("mangowc GitHub");
|
||||
if (isLabwc)
|
||||
return "LabWC Website";
|
||||
return "niri GitHub";
|
||||
return I18n.tr("LabWC Website");
|
||||
return I18n.tr("niri GitHub");
|
||||
}
|
||||
|
||||
property string dmsDiscordUrl: "https://discord.gg/ppWTpKmPgT"
|
||||
property string dmsDiscordTooltip: "niri/dms Discord"
|
||||
property string dmsDiscordTooltip: I18n.tr("niri/dms Discord")
|
||||
|
||||
property string compositorDiscordUrl: {
|
||||
if (isHyprland)
|
||||
@@ -95,17 +95,17 @@ Item {
|
||||
|
||||
property string compositorDiscordTooltip: {
|
||||
if (isHyprland)
|
||||
return "Hyprland Discord Server";
|
||||
return I18n.tr("Hyprland Discord Server");
|
||||
if (isDwl)
|
||||
return "mangowc Discord Server";
|
||||
return I18n.tr("mangowc Discord Server");
|
||||
return "";
|
||||
}
|
||||
|
||||
property string redditUrl: "https://reddit.com/r/niri"
|
||||
property string redditTooltip: "r/niri Subreddit"
|
||||
property string redditTooltip: I18n.tr("r/niri Subreddit")
|
||||
|
||||
property string ircUrl: "https://web.libera.chat/gamja/?channels=#labwc"
|
||||
property string ircTooltip: "LabWC IRC Channel"
|
||||
property string ircTooltip: I18n.tr("LabWC IRC Channel")
|
||||
|
||||
property bool showMatrix: isNiri && !isHyprland && !isSway && !isScroll && !isMiracle && !isDwl && !isLabwc
|
||||
property bool showCompositorDiscord: isHyprland || isDwl
|
||||
@@ -396,7 +396,7 @@ Item {
|
||||
visible: showMatrix
|
||||
|
||||
property bool hovered: false
|
||||
property string tooltipText: "niri Matrix Chat"
|
||||
property string tooltipText: I18n.tr("niri Matrix Chat")
|
||||
|
||||
Image {
|
||||
anchors.fill: parent
|
||||
@@ -582,9 +582,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr(`dms is a highly customizable, modern desktop shell with a <a href="https://m3.material.io/" style="text-decoration:none; color:${Theme.primary};">material 3 inspired</a> design.
|
||||
<br /><br/>It is built with <a href="https://quickshell.org" style="text-decoration:none; color:${Theme.primary};">Quickshell</a>, a QT6 framework for building desktop shells, and <a href="https://go.dev" style="text-decoration:none; color:${Theme.primary};">Go</a>, a statically typed, compiled programming language.
|
||||
`)
|
||||
text: I18n.tr('dms is a highly customizable, modern desktop shell with a <a href="https://m3.material.io/" style="text-decoration:none; color:%1;">material 3 inspired</a> design.<br /><br/>It is built with <a href="https://quickshell.org" style="text-decoration:none; color:%1;">Quickshell</a>, a QT6 framework for building desktop shells, and <a href="https://go.dev" style="text-decoration:none; color:%1;">Go</a>, a statically typed, compiled programming language.').arg(Theme.primary)
|
||||
textFormat: Text.RichText
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
linkColor: Theme.primary
|
||||
@@ -825,7 +823,7 @@ Item {
|
||||
|
||||
StyledText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: `<a href="https://github.com/AvengeMedia/DankMaterialShell/blob/master/LICENSE" style="text-decoration:none; color:${Theme.surfaceVariantText};">MIT License</a>`
|
||||
text: I18n.tr('<a href="https://github.com/AvengeMedia/DankMaterialShell/blob/master/LICENSE" style="text-decoration:none; color:%1;">MIT License</a>').arg(Theme.surfaceVariantText)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceVariantText
|
||||
textFormat: Text.RichText
|
||||
|
||||
@@ -181,11 +181,11 @@ Item {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
model: [
|
||||
{
|
||||
"text": "Time",
|
||||
"text": I18n.tr("Time"),
|
||||
"icon": "access_time"
|
||||
},
|
||||
{
|
||||
"text": "Location",
|
||||
"text": I18n.tr("Location"),
|
||||
"icon": "place"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -542,7 +542,14 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: KeybindsService.loading ? I18n.tr("Shortcuts") : I18n.tr("Shortcuts (%1)").arg(keybindsTab._filteredBinds.length)
|
||||
text: {
|
||||
if (KeybindsService.loading)
|
||||
return I18n.tr("Shortcuts");
|
||||
const count = keybindsTab._filteredBinds.length;
|
||||
return count === 1
|
||||
? I18n.tr("Shortcut (%1)").arg(count)
|
||||
: I18n.tr("Shortcuts (%1)").arg(count);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
|
||||
@@ -2854,7 +2854,7 @@ Item {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr(`Generate baseline GTK3/4 or QT5/QT6 (requires qt6ct-kde) configurations to follow DMS colors. Only needed once.<br /><br />It is recommended to configure <a href="https://github.com/AvengeMedia/DankMaterialShell/blob/master/README.md#Theming" style="text-decoration:none; color:${Theme.primary};">adw-gtk3</a> prior to applying GTK themes.`)
|
||||
text: I18n.tr('Generate baseline GTK3/4 or QT5/QT6 (requires qt6ct-kde) configurations to follow DMS colors. Only needed once.<br /><br />It is recommended to configure <a href="https://github.com/AvengeMedia/DankMaterialShell/blob/master/README.md#Theming" style="text-decoration:none; color:%1;">adw-gtk3</a> prior to applying GTK themes.').arg(Theme.primary)
|
||||
textFormat: Text.RichText
|
||||
linkColor: Theme.primary
|
||||
onLinkActivated: url => Qt.openUrlExternally(url)
|
||||
|
||||
@@ -138,7 +138,7 @@ Item {
|
||||
{
|
||||
"id": "gpuTemp",
|
||||
"text": I18n.tr("GPU Temperature"),
|
||||
"description": "",
|
||||
"description": I18n.tr("GPU temperature display"),
|
||||
"icon": "auto_awesome_mosaic",
|
||||
"warning": !DgopService.dgopAvailable ? I18n.tr("Requires 'dgop' tool") : I18n.tr("This widget prevents GPU power off states, which can significantly impact battery life on laptops. It is not recommended to use this on laptops with hybrid graphics."),
|
||||
"enabled": DgopService.dgopAvailable
|
||||
|
||||
@@ -67,12 +67,14 @@ DankPopout {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: {
|
||||
if (SystemUpdateService.isChecking)
|
||||
return "Checking...";
|
||||
return I18n.tr("Checking...");
|
||||
if (SystemUpdateService.hasError)
|
||||
return "Error";
|
||||
return I18n.tr("Error");
|
||||
if (SystemUpdateService.updateCount === 0)
|
||||
return "Up to date";
|
||||
return SystemUpdateService.updateCount + " updates";
|
||||
return I18n.tr("Up to date");
|
||||
return SystemUpdateService.updateCount === 1
|
||||
? I18n.tr("%1 update").arg(SystemUpdateService.updateCount)
|
||||
: I18n.tr("%1 updates").arg(SystemUpdateService.updateCount);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
@@ -136,18 +138,20 @@ DankPopout {
|
||||
width: parent.width
|
||||
text: {
|
||||
if (SystemUpdateService.hasError) {
|
||||
return "Failed to check for updates:\n" + SystemUpdateService.errorMessage;
|
||||
return I18n.tr("Failed to check for updates:\n%1").arg(SystemUpdateService.errorMessage);
|
||||
}
|
||||
if (!SystemUpdateService.helperAvailable) {
|
||||
return "No package manager found. Please install 'paru' or 'yay' on Arch-based systems to check for updates.";
|
||||
return I18n.tr("No package manager found. Please install 'paru' or 'yay' on Arch-based systems to check for updates.");
|
||||
}
|
||||
if (SystemUpdateService.isChecking) {
|
||||
return "Checking for updates...";
|
||||
return I18n.tr("Checking for updates...");
|
||||
}
|
||||
if (SystemUpdateService.updateCount === 0) {
|
||||
return "Your system is up to date!";
|
||||
return I18n.tr("Your system is up to date!");
|
||||
}
|
||||
return `Found ${SystemUpdateService.updateCount} packages to update:`;
|
||||
return SystemUpdateService.updateCount === 1
|
||||
? I18n.tr("Found %1 package to update:").arg(SystemUpdateService.updateCount)
|
||||
: I18n.tr("Found %1 packages to update:").arg(SystemUpdateService.updateCount);
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user