1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2025-12-05 21:15:38 -05:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Saurabh
de8f2e6a68 feat/matugen3 (#771)
* added matugen 3 terminal templates and logic

fixed version check and light terminal check

refactored json generation

fixed syntax

keep tmp debug

fixed file outputs

fixed syntax issues and implicit passing

added debug stderr output

* moved calls to matugen after template is built correctly

added --json hex

disabled debug message

cleaned up code into modular functions, re-added second full matugen call

fixed args

added shift

commented vs code section

debug changes

* arg format fixes

fixed json import flag

fixed string quotation

fix arg order

* cleaned up

fix cfg naming

* removed mt2.0 templates and refactored worker

removed/replaced matugen 2 templates

fix formatter diffs + consistent styling

* fixed last json output

* fixed syntax error

* vs code templates

* matugen: inject all stock/custom theme colors as overrides
- also some general architectural changes

* dank16: remove vscode enrich option

---------

Co-authored-by: bbedward
2025-11-26 16:34:53 -05:00
Álvaro
85704e3947 Improved applications naming in AudioOutputDetail (#821) 2025-11-26 16:28:26 -05:00
bbedward
4d661ff41d dankinstall: add artix 2025-11-26 16:18:11 -05:00
bbedward
d7b39634e6 hyprland: fix focus grab 2025-11-26 12:46:19 -05:00
bbedward
039c98b9e3 power: switch to hold-style confirmation
fixes #775
2025-11-26 11:19:18 -05:00
bbedward
172c4bf0a9 confirm: add keepPopoutsOpen 2025-11-26 10:34:59 -05:00
bbedward
1f2a1c5dec niri: keep overview focus when open 2025-11-26 09:38:15 -05:00
36 changed files with 1578 additions and 1052 deletions

View File

@@ -46,3 +46,9 @@ packages:
outpkg: mocks_evdev
interfaces:
EvdevDevice:
github.com/AvengeMedia/DankMaterialShell/core/internal/version:
config:
dir: "internal/mocks/version"
outpkg: mocks_version
interfaces:
VersionFetcher:

View File

@@ -2,7 +2,6 @@ package main
import (
"fmt"
"os"
"strings"
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
@@ -26,7 +25,6 @@ func init() {
dank16Cmd.Flags().Bool("alacritty", false, "Output in Alacritty terminal format")
dank16Cmd.Flags().Bool("ghostty", false, "Output in Ghostty terminal format")
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
dank16Cmd.Flags().String("vscode-enrich", "", "Enrich existing VSCode theme file with terminal colors")
dank16Cmd.Flags().String("background", "", "Custom background color")
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
}
@@ -44,7 +42,6 @@ func runDank16(cmd *cobra.Command, args []string) {
isAlacritty, _ := cmd.Flags().GetBool("alacritty")
isGhostty, _ := cmd.Flags().GetBool("ghostty")
isWezterm, _ := cmd.Flags().GetBool("wezterm")
vscodeEnrich, _ := cmd.Flags().GetString("vscode-enrich")
background, _ := cmd.Flags().GetString("background")
contrastAlgo, _ := cmd.Flags().GetString("contrast")
@@ -65,18 +62,7 @@ func runDank16(cmd *cobra.Command, args []string) {
colors := dank16.GeneratePalette(primaryColor, opts)
if vscodeEnrich != "" {
data, err := os.ReadFile(vscodeEnrich)
if err != nil {
log.Fatalf("Error reading file: %v", err)
}
enriched, err := dank16.EnrichVSCodeTheme(data, colors)
if err != nil {
log.Fatalf("Error enriching theme: %v", err)
}
fmt.Println(string(enriched))
} else if isJson {
if isJson {
fmt.Print(dank16.GenerateJSON(colors))
} else if isKitty {
fmt.Print(dank16.GenerateKittyTheme(colors))

View File

@@ -1,7 +1,6 @@
package dank16
import (
"encoding/json"
"math"
"testing"
)
@@ -373,79 +372,6 @@ func TestGeneratePalette(t *testing.T) {
}
}
func TestEnrichVSCodeTheme(t *testing.T) {
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
baseTheme := map[string]interface{}{
"name": "Test Theme",
"type": "dark",
"colors": map[string]interface{}{
"editor.background": "#000000",
},
}
themeJSON, err := json.Marshal(baseTheme)
if err != nil {
t.Fatalf("Failed to marshal base theme: %v", err)
}
result, err := EnrichVSCodeTheme(themeJSON, colors)
if err != nil {
t.Fatalf("EnrichVSCodeTheme failed: %v", err)
}
var enriched map[string]interface{}
if err := json.Unmarshal(result, &enriched); err != nil {
t.Fatalf("Failed to unmarshal result: %v", err)
}
colorsMap, ok := enriched["colors"].(map[string]interface{})
if !ok {
t.Fatal("colors is not a map")
}
terminalColors := []string{
"terminal.ansiBlack",
"terminal.ansiRed",
"terminal.ansiGreen",
"terminal.ansiYellow",
"terminal.ansiBlue",
"terminal.ansiMagenta",
"terminal.ansiCyan",
"terminal.ansiWhite",
"terminal.ansiBrightBlack",
"terminal.ansiBrightRed",
"terminal.ansiBrightGreen",
"terminal.ansiBrightYellow",
"terminal.ansiBrightBlue",
"terminal.ansiBrightMagenta",
"terminal.ansiBrightCyan",
"terminal.ansiBrightWhite",
}
for i, key := range terminalColors {
if val, ok := colorsMap[key]; !ok {
t.Errorf("Missing terminal color: %s", key)
} else if val != colors[i] {
t.Errorf("%s = %s, expected %s", key, val, colors[i])
}
}
if colorsMap["editor.background"] != "#000000" {
t.Error("Original theme colors should be preserved")
}
}
func TestEnrichVSCodeThemeInvalidJSON(t *testing.T) {
colors := GeneratePalette("#625690", PaletteOptions{IsLight: false})
invalidJSON := []byte("{invalid json")
_, err := EnrichVSCodeTheme(invalidJSON, colors)
if err == nil {
t.Error("Expected error for invalid JSON, got nil")
}
}
func TestRoundTripConversion(t *testing.T) {
testColors := []string{"#000000", "#ffffff", "#ff0000", "#00ff00", "#0000ff", "#625690", "#808080"}

View File

@@ -1,250 +0,0 @@
package dank16
import (
"encoding/json"
"fmt"
)
type VSCodeTheme struct {
Schema string `json:"$schema"`
Name string `json:"name"`
Type string `json:"type"`
Colors map[string]string `json:"colors"`
TokenColors []VSCodeTokenColor `json:"tokenColors"`
SemanticHighlighting bool `json:"semanticHighlighting"`
SemanticTokenColors map[string]VSCodeTokenSetting `json:"semanticTokenColors"`
}
type VSCodeTokenColor struct {
Scope interface{} `json:"scope"`
Settings VSCodeTokenSetting `json:"settings"`
}
type VSCodeTokenSetting struct {
Foreground string `json:"foreground,omitempty"`
FontStyle string `json:"fontStyle,omitempty"`
}
func updateTokenColor(tc interface{}, scopeToColor map[string]string) {
tcMap, ok := tc.(map[string]interface{})
if !ok {
return
}
scopes, ok := tcMap["scope"].([]interface{})
if !ok {
return
}
settings, ok := tcMap["settings"].(map[string]interface{})
if !ok {
return
}
isYaml := hasScopeContaining(scopes, "yaml")
for _, scope := range scopes {
scopeStr, ok := scope.(string)
if !ok {
continue
}
if scopeStr == "string" && isYaml {
continue
}
if applyColorToScope(settings, scope, scopeToColor) {
break
}
}
}
func applyColorToScope(settings map[string]interface{}, scope interface{}, scopeToColor map[string]string) bool {
scopeStr, ok := scope.(string)
if !ok {
return false
}
newColor, exists := scopeToColor[scopeStr]
if !exists {
return false
}
settings["foreground"] = newColor
return true
}
func hasScopeContaining(scopes []interface{}, substring string) bool {
for _, scope := range scopes {
scopeStr, ok := scope.(string)
if !ok {
continue
}
for i := 0; i <= len(scopeStr)-len(substring); i++ {
if scopeStr[i:i+len(substring)] == substring {
return true
}
}
}
return false
}
func EnrichVSCodeTheme(themeData []byte, colors []string) ([]byte, error) {
var theme map[string]interface{}
if err := json.Unmarshal(themeData, &theme); err != nil {
return nil, err
}
colorsMap, ok := theme["colors"].(map[string]interface{})
if !ok {
colorsMap = make(map[string]interface{})
theme["colors"] = colorsMap
}
bg := colors[0]
isLight := false
if len(bg) == 7 && bg[0] == '#' {
r, g, b := 0, 0, 0
fmt.Sscanf(bg[1:], "%02x%02x%02x", &r, &g, &b)
luminance := (0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)) / 255.0
isLight = luminance > 0.5
}
if isLight {
theme["type"] = "light"
} else {
theme["type"] = "dark"
}
colorsMap["terminal.ansiBlack"] = colors[0]
colorsMap["terminal.ansiRed"] = colors[1]
colorsMap["terminal.ansiGreen"] = colors[2]
colorsMap["terminal.ansiYellow"] = colors[3]
colorsMap["terminal.ansiBlue"] = colors[4]
colorsMap["terminal.ansiMagenta"] = colors[5]
colorsMap["terminal.ansiCyan"] = colors[6]
colorsMap["terminal.ansiWhite"] = colors[7]
colorsMap["terminal.ansiBrightBlack"] = colors[8]
colorsMap["terminal.ansiBrightRed"] = colors[9]
colorsMap["terminal.ansiBrightGreen"] = colors[10]
colorsMap["terminal.ansiBrightYellow"] = colors[11]
colorsMap["terminal.ansiBrightBlue"] = colors[12]
colorsMap["terminal.ansiBrightMagenta"] = colors[13]
colorsMap["terminal.ansiBrightCyan"] = colors[14]
colorsMap["terminal.ansiBrightWhite"] = colors[15]
tokenColors, ok := theme["tokenColors"].([]interface{})
if ok {
scopeToColor := map[string]string{
"comment": colors[8],
"punctuation.definition.comment": colors[8],
"keyword": colors[5],
"storage.type": colors[13],
"storage.modifier": colors[5],
"variable": colors[15],
"variable.parameter": colors[7],
"meta.object-literal.key": colors[4],
"meta.property.object": colors[4],
"variable.other.property": colors[4],
"constant.other.symbol": colors[12],
"constant.numeric": colors[12],
"constant.language": colors[12],
"constant.character": colors[3],
"entity.name.type": colors[12],
"support.type": colors[13],
"entity.name.class": colors[12],
"entity.name.function": colors[2],
"support.function": colors[2],
"support.class": colors[15],
"support.variable": colors[15],
"variable.language": colors[12],
"entity.name.tag.yaml": colors[12],
"string.unquoted.plain.out.yaml": colors[15],
"string.unquoted.yaml": colors[15],
"string": colors[3],
}
for i, tc := range tokenColors {
updateTokenColor(tc, scopeToColor)
tokenColors[i] = tc
}
yamlRules := []VSCodeTokenColor{
{
Scope: "entity.name.tag.yaml",
Settings: VSCodeTokenSetting{Foreground: colors[12]},
},
{
Scope: []string{"string.unquoted.plain.out.yaml", "string.unquoted.yaml"},
Settings: VSCodeTokenSetting{Foreground: colors[15]},
},
}
for _, rule := range yamlRules {
tokenColors = append(tokenColors, rule)
}
theme["tokenColors"] = tokenColors
}
if semanticTokenColors, ok := theme["semanticTokenColors"].(map[string]interface{}); ok {
updates := map[string]string{
"variable": colors[15],
"variable.readonly": colors[12],
"property": colors[4],
"function": colors[2],
"method": colors[2],
"type": colors[12],
"class": colors[12],
"typeParameter": colors[13],
"enumMember": colors[12],
"string": colors[3],
"number": colors[12],
"comment": colors[8],
"keyword": colors[5],
"operator": colors[15],
"parameter": colors[7],
"namespace": colors[15],
}
for key, color := range updates {
if existing, ok := semanticTokenColors[key].(map[string]interface{}); ok {
existing["foreground"] = color
} else {
semanticTokenColors[key] = map[string]interface{}{
"foreground": color,
}
}
}
} else {
semanticTokenColors := make(map[string]interface{})
updates := map[string]string{
"variable": colors[7],
"variable.readonly": colors[12],
"property": colors[4],
"function": colors[2],
"method": colors[2],
"type": colors[12],
"class": colors[12],
"typeParameter": colors[13],
"enumMember": colors[12],
"string": colors[3],
"number": colors[12],
"comment": colors[8],
"keyword": colors[5],
"operator": colors[15],
"parameter": colors[7],
"namespace": colors[15],
}
for key, color := range updates {
semanticTokenColors[key] = map[string]interface{}{
"foreground": color,
}
}
theme["semanticTokenColors"] = semanticTokenColors
}
return json.MarshalIndent(theme, "", " ")
}

View File

@@ -37,6 +37,9 @@ func init() {
Register("garuda", "#cba6f7", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan)
})
Register("artix", "#1793D1", FamilyArch, func(config DistroConfig, logChan chan<- string) Distribution {
return NewArchDistribution(config, logChan)
})
}
type ArchDistribution struct {

View File

@@ -0,0 +1,144 @@
// Code generated by mockery v2.53.5. DO NOT EDIT.
package mocks_version
import mock "github.com/stretchr/testify/mock"
// MockVersionFetcher is an autogenerated mock type for the VersionFetcher type
type MockVersionFetcher struct {
mock.Mock
}
type MockVersionFetcher_Expecter struct {
mock *mock.Mock
}
func (_m *MockVersionFetcher) EXPECT() *MockVersionFetcher_Expecter {
return &MockVersionFetcher_Expecter{mock: &_m.Mock}
}
// GetCurrentVersion provides a mock function with given fields: dmsPath
func (_m *MockVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
ret := _m.Called(dmsPath)
if len(ret) == 0 {
panic("no return value specified for GetCurrentVersion")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
return rf(dmsPath)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(dmsPath)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(dmsPath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockVersionFetcher_GetCurrentVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCurrentVersion'
type MockVersionFetcher_GetCurrentVersion_Call struct {
*mock.Call
}
// GetCurrentVersion is a helper method to define mock.On call
// - dmsPath string
func (_e *MockVersionFetcher_Expecter) GetCurrentVersion(dmsPath interface{}) *MockVersionFetcher_GetCurrentVersion_Call {
return &MockVersionFetcher_GetCurrentVersion_Call{Call: _e.mock.On("GetCurrentVersion", dmsPath)}
}
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetCurrentVersion_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockVersionFetcher_GetCurrentVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetCurrentVersion_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockVersionFetcher_GetCurrentVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetCurrentVersion_Call {
_c.Call.Return(run)
return _c
}
// GetLatestVersion provides a mock function with given fields: dmsPath
func (_m *MockVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
ret := _m.Called(dmsPath)
if len(ret) == 0 {
panic("no return value specified for GetLatestVersion")
}
var r0 string
var r1 error
if rf, ok := ret.Get(0).(func(string) (string, error)); ok {
return rf(dmsPath)
}
if rf, ok := ret.Get(0).(func(string) string); ok {
r0 = rf(dmsPath)
} else {
r0 = ret.Get(0).(string)
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(dmsPath)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MockVersionFetcher_GetLatestVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestVersion'
type MockVersionFetcher_GetLatestVersion_Call struct {
*mock.Call
}
// GetLatestVersion is a helper method to define mock.On call
// - dmsPath string
func (_e *MockVersionFetcher_Expecter) GetLatestVersion(dmsPath interface{}) *MockVersionFetcher_GetLatestVersion_Call {
return &MockVersionFetcher_GetLatestVersion_Call{Call: _e.mock.On("GetLatestVersion", dmsPath)}
}
func (_c *MockVersionFetcher_GetLatestVersion_Call) Run(run func(dmsPath string)) *MockVersionFetcher_GetLatestVersion_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(string))
})
return _c
}
func (_c *MockVersionFetcher_GetLatestVersion_Call) Return(_a0 string, _a1 error) *MockVersionFetcher_GetLatestVersion_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *MockVersionFetcher_GetLatestVersion_Call) RunAndReturn(run func(string) (string, error)) *MockVersionFetcher_GetLatestVersion_Call {
_c.Call.Return(run)
return _c
}
// NewMockVersionFetcher creates a new instance of MockVersionFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value.
func NewMockVersionFetcher(t interface {
mock.TestingT
Cleanup(func())
}) *MockVersionFetcher {
mock := &MockVersionFetcher{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@@ -18,28 +18,27 @@ type VersionInfo struct {
HasUpdate bool
}
func GetCurrentDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
// VersionFetcher is an interface for fetching version information
type VersionFetcher interface {
GetCurrentVersion(dmsPath string) (string, error)
GetLatestVersion(dmsPath string) (string, error)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return "", fmt.Errorf("DMS not installed")
}
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
if err := os.Chdir(dmsPath); err != nil {
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
}
// DefaultVersionFetcher is the default implementation that uses git/curl
type DefaultVersionFetcher struct{}
func (d *DefaultVersionFetcher) GetCurrentVersion(dmsPath string) (string, error) {
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
if err := os.Chdir(dmsPath); err != nil {
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
}
tagCmd := exec.Command("git", "describe", "--exact-match", "--tags", "HEAD")
if tagOutput, err := tagCmd.Output(); err == nil {
return strings.TrimSpace(string(tagOutput)), nil
@@ -65,21 +64,14 @@ func GetCurrentDMSVersion() (string, error) {
return "unknown", nil
}
func GetLatestDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
func (d *DefaultVersionFetcher) GetLatestVersion(dmsPath string) (string, error) {
if _, err := os.Stat(filepath.Join(dmsPath, ".git")); err == nil {
originalDir, err := os.Getwd()
if err != nil {
return "", err
}
defer os.Chdir(originalDir)
if err := os.Chdir(dmsPath); err != nil {
return "", fmt.Errorf("failed to change to DMS directory: %w", err)
}
@@ -154,13 +146,54 @@ func GetLatestDMSVersion() (string, error) {
return result.TagName, nil
}
// defaultFetcher is used by the public functions
var defaultFetcher VersionFetcher = &DefaultVersionFetcher{}
func GetCurrentDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return "", fmt.Errorf("DMS not installed")
}
return defaultFetcher.GetCurrentVersion(dmsPath)
}
func GetLatestDMSVersion() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
return defaultFetcher.GetLatestVersion(dmsPath)
}
func GetDMSVersionInfo() (*VersionInfo, error) {
current, err := GetCurrentDMSVersion()
return GetDMSVersionInfoWithFetcher(defaultFetcher)
}
func GetDMSVersionInfoWithFetcher(fetcher VersionFetcher) (*VersionInfo, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get home directory: %w", err)
}
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
return nil, fmt.Errorf("DMS not installed")
}
current, err := fetcher.GetCurrentVersion(dmsPath)
if err != nil {
return nil, err
}
latest, err := GetLatestDMSVersion()
latest, err := fetcher.GetLatestVersion(dmsPath)
if err != nil {
return nil, fmt.Errorf("failed to get latest version: %w", err)
}

View File

@@ -5,6 +5,8 @@ import (
"os/exec"
"path/filepath"
"testing"
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
)
func TestCompareVersions(t *testing.T) {
@@ -33,36 +35,107 @@ func TestCompareVersions(t *testing.T) {
}
func TestGetDMSVersionInfo_Structure(t *testing.T) {
homeDir, err := os.UserHomeDir()
if err != nil {
t.Skip("Cannot get home directory")
}
// Create a temp directory with a fake DMS installation
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0755)
dmsPath := filepath.Join(homeDir, ".config", "quickshell", "dms")
if _, err := os.Stat(dmsPath); os.IsNotExist(err) {
t.Skip("DMS not installed, skipping version info test")
}
// Create a .git directory to simulate git installation
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
info, err := GetDMSVersionInfo()
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
// Create mock fetcher
mockFetcher := mocks_version.NewMockVersionFetcher(t)
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("v0.1.0", nil)
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("v0.1.1", nil)
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
if err != nil {
t.Fatalf("GetDMSVersionInfo() failed: %v", err)
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
}
if info == nil {
t.Fatal("GetDMSVersionInfo() returned nil")
t.Fatal("GetDMSVersionInfoWithFetcher() returned nil")
}
if info.Current == "" {
t.Error("Current version is empty")
if info.Current != "v0.1.0" {
t.Errorf("Current version = %s, expected v0.1.0", info.Current)
}
if info.Latest == "" {
t.Error("Latest version is empty")
if info.Latest != "v0.1.1" {
t.Errorf("Latest version = %s, expected v0.1.1", info.Latest)
}
if !info.HasUpdate {
t.Error("HasUpdate should be true when current != latest")
}
if !info.IsTag {
t.Error("IsTag should be true for v0.1.0")
}
t.Logf("Current: %s, Latest: %s, HasUpdate: %v", info.Current, info.Latest, info.HasUpdate)
}
func TestGetDMSVersionInfo_BranchVersion(t *testing.T) {
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0755)
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
mockFetcher := mocks_version.NewMockVersionFetcher(t)
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("master@abc1234", nil)
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("master@def5678", nil)
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
if err != nil {
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
}
if !info.IsBranch {
t.Error("IsBranch should be true for branch@commit format")
}
if !info.IsGit {
t.Error("IsGit should be true for branch@commit format")
}
if !info.HasUpdate {
t.Error("HasUpdate should be true when commits differ")
}
}
func TestGetDMSVersionInfo_NoUpdate(t *testing.T) {
tempDir := t.TempDir()
dmsPath := filepath.Join(tempDir, ".config", "quickshell", "dms")
os.MkdirAll(dmsPath, 0755)
os.MkdirAll(filepath.Join(dmsPath, ".git"), 0755)
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)
os.Setenv("HOME", tempDir)
mockFetcher := mocks_version.NewMockVersionFetcher(t)
mockFetcher.EXPECT().GetCurrentVersion(dmsPath).Return("v0.1.0", nil)
mockFetcher.EXPECT().GetLatestVersion(dmsPath).Return("v0.1.0", nil)
info, err := GetDMSVersionInfoWithFetcher(mockFetcher)
if err != nil {
t.Fatalf("GetDMSVersionInfoWithFetcher() failed: %v", err)
}
if info.HasUpdate {
t.Error("HasUpdate should be false when current == latest")
}
}
func TestGetCurrentDMSVersion_NotInstalled(t *testing.T) {
originalHome := os.Getenv("HOME")
defer os.Setenv("HOME", originalHome)

View File

@@ -303,6 +303,7 @@ Singleton {
property bool osdPowerProfileEnabled: true
property bool powerActionConfirm: true
property int powerActionHoldDuration: 1
property var powerMenuActions: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"]
property string powerMenuDefaultAction: "logout"
property bool powerMenuGridLayout: false

View File

@@ -88,7 +88,9 @@ Singleton {
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
property var matugenColors: ({})
property var _pendingGenerateParams: null
property var customThemeData: null
Component.onCompleted: {
@@ -784,13 +786,26 @@ Singleton {
}
}
function setDesiredTheme(kind, value, isLight, iconTheme, matugenType) {
function setDesiredTheme(kind, value, isLight, iconTheme, matugenType, stockColors) {
if (!matugenAvailable) {
console.warn("Theme: matugen not available or disabled - cannot set system theme");
return;
}
console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", "type:", matugenType);
if (workerRunning) {
console.info("Theme: Worker already running, queueing request");
pendingThemeRequest = {
kind,
value,
isLight,
iconTheme,
matugenType,
stockColors
};
return;
}
console.info("Theme: Setting desired theme -", kind, "mode:", isLight ? "light" : "dark", stockColors ? "(stock colors)" : "(dynamic)");
if (typeof NiriService !== "undefined" && CompositorService.isNiri) {
NiriService.suppressNextToast();
@@ -805,15 +820,18 @@ Singleton {
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
};
if (stockColors) {
desired.stockColors = JSON.stringify(stockColors);
}
const json = JSON.stringify(desired);
const desiredPath = stateDir + "/matugen.desired.json";
Quickshell.execDetached(["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF`]);
workerRunning = true;
const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false";
const terminalsAlwaysDark = (typeof SettingsData !== "undefined" && SettingsData.terminalsAlwaysDark) ? "true" : "false";
console.log("Theme: Starting matugen worker");
systemThemeGenerator.command = [shellDir + "/scripts/matugen-worker.sh", stateDir, shellDir, configDir, syncModeWithPortal, terminalsAlwaysDark, "--run"];
workerRunning = true;
systemThemeGenerator.command = ["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF\nexec '${shellDir}/scripts/matugen-worker.sh' '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' '${terminalsAlwaysDark}' --run`];
systemThemeGenerator.running = true;
}
@@ -821,40 +839,122 @@ Singleton {
const isGreeterMode = (typeof SessionData !== "undefined" && SessionData.isGreeterMode);
if (!matugenAvailable || isGreeterMode)
return;
_pendingGenerateParams = true;
_themeGenerateDebounce.restart();
}
function _executeThemeGeneration() {
if (!_pendingGenerateParams)
return;
_pendingGenerateParams = null;
const isLight = (typeof SessionData !== "undefined" && SessionData.isLightMode);
const iconTheme = (typeof SettingsData !== "undefined" && SettingsData.iconTheme) ? SettingsData.iconTheme : "System Default";
if (currentTheme === dynamic) {
if (!rawWallpaperPath) {
if (!rawWallpaperPath)
return;
}
const selectedMatugenType = (typeof SettingsData !== "undefined" && SettingsData.matugenScheme) ? SettingsData.matugenScheme : "scheme-tonal-spot";
if (rawWallpaperPath.startsWith("#")) {
setDesiredTheme("hex", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
} else {
setDesiredTheme("image", rawWallpaperPath, isLight, iconTheme, selectedMatugenType);
}
} else {
let primaryColor;
let matugenType;
if (currentTheme === "custom") {
if (!customThemeData || !customThemeData.primary) {
console.warn("Custom theme data not available for system theme generation");
return;
}
primaryColor = customThemeData.primary;
matugenType = customThemeData.matugen_type;
} else {
primaryColor = currentThemeData.primary;
matugenType = currentThemeData.matugen_type;
}
if (!primaryColor) {
console.warn("No primary color available for theme:", currentTheme);
return;
}
setDesiredTheme("hex", primaryColor, isLight, iconTheme, matugenType);
const kind = rawWallpaperPath.startsWith("#") ? "hex" : "image";
setDesiredTheme(kind, rawWallpaperPath, isLight, iconTheme, selectedMatugenType, null);
return;
}
let darkTheme, lightTheme;
if (currentTheme === "custom") {
darkTheme = customThemeData;
lightTheme = customThemeData;
} else {
darkTheme = StockThemes.getThemeByName(currentTheme, false);
lightTheme = StockThemes.getThemeByName(currentTheme, true);
}
if (!darkTheme || !darkTheme.primary) {
console.warn("Theme data not available for:", currentTheme);
return;
}
const stockColors = buildMatugenColorsFromTheme(darkTheme, lightTheme);
const themeData = isLight ? lightTheme : darkTheme;
setDesiredTheme("hex", themeData.primary, isLight, iconTheme, themeData.matugen_type, stockColors);
}
function buildMatugenColorsFromTheme(darkTheme, lightTheme) {
const colors = {};
function addColor(matugenKey, darkVal, lightVal) {
if (!darkVal && !lightVal)
return;
colors[matugenKey] = {
"dark": {
"color": String(darkVal || lightVal)
},
"light": {
"color": String(lightVal || darkVal)
},
"default": {
"color": String(darkVal || lightVal)
}
};
}
function get(theme, key, fallback) {
return theme[key] || fallback;
}
addColor("primary", darkTheme.primary, lightTheme.primary);
addColor("on_primary", darkTheme.primaryText, lightTheme.primaryText);
addColor("primary_container", darkTheme.primaryContainer, lightTheme.primaryContainer);
addColor("on_primary_container", darkTheme.primaryContainerText || darkTheme.surfaceText, lightTheme.primaryContainerText || lightTheme.surfaceText);
addColor("secondary", darkTheme.secondary, lightTheme.secondary);
addColor("on_secondary", darkTheme.secondaryText || darkTheme.primaryText, lightTheme.secondaryText || lightTheme.primaryText);
addColor("secondary_container", darkTheme.secondaryContainer || darkTheme.surfaceContainerHigh, lightTheme.secondaryContainer || lightTheme.surfaceContainerHigh);
addColor("on_secondary_container", darkTheme.secondaryContainerText || darkTheme.surfaceText, lightTheme.secondaryContainerText || lightTheme.surfaceText);
addColor("tertiary", darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiary || lightTheme.secondary);
addColor("on_tertiary", darkTheme.tertiaryText || darkTheme.secondaryText || darkTheme.primaryText, lightTheme.tertiaryText || lightTheme.secondaryText || lightTheme.primaryText);
addColor("tertiary_container", darkTheme.tertiaryContainer || darkTheme.secondaryContainer || darkTheme.surfaceContainerHigh, lightTheme.tertiaryContainer || lightTheme.secondaryContainer || lightTheme.surfaceContainerHigh);
addColor("on_tertiary_container", darkTheme.tertiaryContainerText || darkTheme.surfaceText, lightTheme.tertiaryContainerText || lightTheme.surfaceText);
addColor("error", darkTheme.error || "#F2B8B5", lightTheme.error || "#B3261E");
addColor("on_error", darkTheme.errorText || "#601410", lightTheme.errorText || "#FFFFFF");
addColor("error_container", darkTheme.errorContainer || "#8C1D18", lightTheme.errorContainer || "#F9DEDC");
addColor("on_error_container", darkTheme.errorContainerText || "#F9DEDC", lightTheme.errorContainerText || "#410E0B");
addColor("surface", darkTheme.surface, lightTheme.surface);
addColor("on_surface", darkTheme.surfaceText, lightTheme.surfaceText);
addColor("surface_variant", darkTheme.surfaceVariant, lightTheme.surfaceVariant);
addColor("on_surface_variant", darkTheme.surfaceVariantText, lightTheme.surfaceVariantText);
addColor("surface_tint", darkTheme.surfaceTint, lightTheme.surfaceTint);
addColor("background", darkTheme.background, lightTheme.background);
addColor("on_background", darkTheme.backgroundText, lightTheme.backgroundText);
addColor("outline", darkTheme.outline, lightTheme.outline);
addColor("outline_variant", darkTheme.outlineVariant || darkTheme.surfaceVariant, lightTheme.outlineVariant || lightTheme.surfaceVariant);
addColor("surface_container", darkTheme.surfaceContainer, lightTheme.surfaceContainer);
addColor("surface_container_high", darkTheme.surfaceContainerHigh, lightTheme.surfaceContainerHigh);
addColor("surface_container_highest", darkTheme.surfaceContainerHighest || darkTheme.surfaceContainerHigh, lightTheme.surfaceContainerHighest || lightTheme.surfaceContainerHigh);
addColor("surface_container_low", darkTheme.surfaceContainerLow || darkTheme.surface, lightTheme.surfaceContainerLow || lightTheme.surface);
addColor("surface_container_lowest", darkTheme.surfaceContainerLowest || darkTheme.background, lightTheme.surfaceContainerLowest || lightTheme.background);
addColor("surface_bright", darkTheme.surfaceBright || darkTheme.surfaceContainerHighest || darkTheme.surfaceContainerHigh, lightTheme.surfaceBright || lightTheme.surface);
addColor("surface_dim", darkTheme.surfaceDim || darkTheme.background, lightTheme.surfaceDim || lightTheme.surfaceContainer);
addColor("inverse_surface", darkTheme.inverseSurface || lightTheme.surface, lightTheme.inverseSurface || darkTheme.surface);
addColor("inverse_on_surface", darkTheme.inverseOnSurface || lightTheme.surfaceText, lightTheme.inverseOnSurface || darkTheme.surfaceText);
addColor("inverse_primary", darkTheme.inversePrimary || lightTheme.primary, lightTheme.inversePrimary || darkTheme.primary);
addColor("scrim", darkTheme.scrim || "#000000", lightTheme.scrim || "#000000");
addColor("shadow", darkTheme.shadow || "#000000", lightTheme.shadow || "#000000");
addColor("source_color", darkTheme.primary, lightTheme.primary);
addColor("primary_fixed", darkTheme.primaryFixed || darkTheme.primaryContainer, lightTheme.primaryFixed || lightTheme.primaryContainer);
addColor("primary_fixed_dim", darkTheme.primaryFixedDim || darkTheme.primary, lightTheme.primaryFixedDim || lightTheme.primary);
addColor("on_primary_fixed", darkTheme.onPrimaryFixed || darkTheme.primaryText, lightTheme.onPrimaryFixed || lightTheme.primaryText);
addColor("on_primary_fixed_variant", darkTheme.onPrimaryFixedVariant || darkTheme.primaryText, lightTheme.onPrimaryFixedVariant || lightTheme.primaryText);
addColor("secondary_fixed", darkTheme.secondaryFixed || darkTheme.secondary, lightTheme.secondaryFixed || lightTheme.secondary);
addColor("secondary_fixed_dim", darkTheme.secondaryFixedDim || darkTheme.secondary, lightTheme.secondaryFixedDim || lightTheme.secondary);
addColor("on_secondary_fixed", darkTheme.onSecondaryFixed || darkTheme.primaryText, lightTheme.onSecondaryFixed || lightTheme.primaryText);
addColor("on_secondary_fixed_variant", darkTheme.onSecondaryFixedVariant || darkTheme.primaryText, lightTheme.onSecondaryFixedVariant || lightTheme.primaryText);
addColor("tertiary_fixed", darkTheme.tertiaryFixed || darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiaryFixed || lightTheme.tertiary || lightTheme.secondary);
addColor("tertiary_fixed_dim", darkTheme.tertiaryFixedDim || darkTheme.tertiary || darkTheme.secondary, lightTheme.tertiaryFixedDim || lightTheme.tertiary || lightTheme.secondary);
addColor("on_tertiary_fixed", darkTheme.onTertiaryFixed || darkTheme.primaryText, lightTheme.onTertiaryFixed || lightTheme.primaryText);
addColor("on_tertiary_fixed_variant", darkTheme.onTertiaryFixedVariant || darkTheme.primaryText, lightTheme.onTertiaryFixedVariant || lightTheme.primaryText);
return colors;
}
function applyGtkColors() {
@@ -1005,10 +1105,6 @@ Singleton {
if (exitCode === 0) {
console.info("Theme: Matugen worker completed successfully");
if (currentTheme === dynamic) {
console.log("Theme: Reloading dynamic colors file");
dynamicColorsFileView.reload();
}
} else if (exitCode === 2) {
console.log("Theme: Matugen worker completed with code 2 (no changes needed)");
} else {
@@ -1017,6 +1113,13 @@ Singleton {
}
console.warn("Theme: Matugen worker failed with exit code:", exitCode);
}
if (pendingThemeRequest) {
const req = pendingThemeRequest;
pendingThemeRequest = null;
console.info("Theme: Processing queued theme request");
setDesiredTheme(req.kind, req.value, req.isLight, req.iconTheme, req.matugenType, req.stockColors);
}
}
}
@@ -1129,6 +1232,13 @@ Singleton {
}
}
Timer {
id: _themeGenerateDebounce
interval: 100
repeat: false
onTriggered: root._executeThemeGeneration()
}
// These timers are for screen transitions, since sometimes QML still beats the niri call
Timer {
id: themeTransitionTimer

View File

@@ -205,6 +205,7 @@ var SPEC = {
osdPowerProfileEnabled: { def: false },
powerActionConfirm: { def: true },
powerActionHoldDuration: { def: 1 },
powerMenuActions: { def: ["reboot", "logout", "poweroff", "lock", "suspend", "restart"] },
powerMenuDefaultAction: { def: "logout" },
powerMenuGridLayout: { def: false },

View File

@@ -343,16 +343,6 @@ Item {
}
}
LazyLoader {
id: powerConfirmModalLoader
active: false
ConfirmModal {
id: powerConfirmModal
}
}
LazyLoader {
id: processListPopoutLoader
@@ -489,22 +479,6 @@ Item {
id: powerMenuModal
onPowerActionRequested: (action, title, message) => {
if (SettingsData.powerActionConfirm) {
powerConfirmModalLoader.active = true;
if (powerConfirmModalLoader.item) {
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary;
powerConfirmModalLoader.item.show(title, message, () => actionApply(action), function () {});
}
} else {
actionApply(action);
}
}
onLockRequested: {
lock.activate();
}
function actionApply(action) {
switch (action) {
case "logout":
SessionService.logout();
@@ -524,6 +498,10 @@ Item {
}
}
onLockRequested: {
lock.activate();
}
Component.onCompleted: {
PopoutService.powerMenuModal = powerMenuModal;
}

View File

@@ -11,7 +11,7 @@ DankModal {
layerNamespace: "dms:bluetooth-pairing"
HyprlandFocusGrab {
windows: [root]
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}

View File

@@ -14,7 +14,7 @@ DankModal {
layerNamespace: "dms:clipboard"
HyprlandFocusGrab {
windows: [clipboardHistoryModal]
windows: [clipboardHistoryModal.contentWindow]
active: CompositorService.isHyprland && clipboardHistoryModal.shouldHaveFocus
}

View File

@@ -7,6 +7,7 @@ DankModal {
id: root
layerNamespace: "dms:confirm-modal"
keepPopoutsOpen: true
property string confirmTitle: ""
property string confirmMessage: ""

View File

@@ -44,6 +44,8 @@ Item {
property bool keepContentLoaded: false
property bool keepPopoutsOpen: false
property var customKeyboardFocus: null
readonly property alias contentWindow: contentWindow
readonly property alias backgroundWindow: backgroundWindow
signal opened
signal dialogClosed
@@ -55,12 +57,15 @@ Item {
ModalManager.openModal(root);
closeTimer.stop();
shouldBeVisible = true;
contentWindow.visible = false;
if (useBackgroundWindow)
backgroundWindow.visible = true;
contentWindow.visible = true;
shouldHaveFocus = false;
Qt.callLater(() => {
shouldHaveFocus = Qt.binding(() => shouldBeVisible);
contentWindow.visible = true;
shouldHaveFocus = false;
Qt.callLater(() => {
shouldHaveFocus = Qt.binding(() => shouldBeVisible);
});
});
}

View File

@@ -12,7 +12,7 @@ DankModal {
layerNamespace: "dms:color-picker"
HyprlandFocusGrab {
windows: [root]
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}

View File

@@ -12,7 +12,7 @@ DankModal {
layerNamespace: "dms:notification-center-modal"
HyprlandFocusGrab {
windows: [notificationModal]
windows: [notificationModal.contentWindow]
active: CompositorService.isHyprland && notificationModal.shouldHaveFocus
}

View File

@@ -11,7 +11,7 @@ DankModal {
layerNamespace: "dms:polkit"
HyprlandFocusGrab {
windows: [root]
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}

View File

@@ -1,4 +1,5 @@
import QtQuick
import QtQuick.Effects
import Quickshell
import Quickshell.Hyprland
import qs.Common
@@ -10,9 +11,10 @@ DankModal {
id: root
layerNamespace: "dms:power-menu"
keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root]
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}
@@ -25,9 +27,94 @@ DankModal {
property int gridColumns: 3
property int gridRows: 2
property string holdAction: ""
property int holdActionIndex: -1
property real holdProgress: 0
property bool showHoldHint: false
readonly property bool needsConfirmation: SettingsData.powerActionConfirm
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000
signal powerActionRequested(string action, string title, string message)
signal lockRequested
function actionNeedsConfirm(action) {
return action !== "lock" && action !== "restart";
}
function startHold(action, actionIndex) {
if (!needsConfirmation || !actionNeedsConfirm(action)) {
executeAction(action);
return;
}
holdAction = action;
holdActionIndex = actionIndex;
holdProgress = 0;
showHoldHint = false;
holdTimer.start();
}
function cancelHold() {
if (holdAction === "")
return;
const wasHolding = holdProgress > 0;
holdTimer.stop();
if (wasHolding && holdProgress < 1) {
showHoldHint = true;
hintTimer.restart();
}
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
}
function completeHold() {
if (holdProgress < 1) {
cancelHold();
return;
}
const action = holdAction;
holdTimer.stop();
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
executeAction(action);
}
function executeAction(action) {
if (action === "lock") {
close();
lockRequested();
return;
}
if (action === "restart") {
close();
Quickshell.execDetached(["dms", "restart"]);
return;
}
close();
root.powerActionRequested(action, "", "");
}
Timer {
id: holdTimer
interval: 16
repeat: true
onTriggered: {
root.holdProgress = Math.min(1, root.holdProgress + (interval / root.holdDurationMs));
if (root.holdProgress >= 1) {
stop();
root.completeHold();
}
}
}
Timer {
id: hintTimer
interval: 2000
onTriggered: root.showHoldHint = false
}
function openCentered() {
parentBounds = Qt.rect(0, 0, 0, 0);
parentScreen = null;
@@ -39,9 +126,7 @@ DankModal {
parentBounds = bounds;
parentScreen = targetScreen;
backgroundOpacity = 0;
keepPopoutsOpen = true;
open();
keepPopoutsOpen = false;
}
function updateVisibleActions() {
@@ -142,44 +227,8 @@ DankModal {
}
}
function selectOption(action) {
if (action === "lock") {
close();
lockRequested();
return;
}
if (action === "restart") {
close();
Quickshell.execDetached(["dms", "restart"]);
return;
}
close();
const actions = {
"logout": {
"title": I18n.tr("Log Out"),
"message": I18n.tr("Are you sure you want to log out?")
},
"suspend": {
"title": I18n.tr("Suspend"),
"message": I18n.tr("Are you sure you want to suspend the system?")
},
"hibernate": {
"title": I18n.tr("Hibernate"),
"message": I18n.tr("Are you sure you want to hibernate the system?")
},
"reboot": {
"title": I18n.tr("Reboot"),
"message": I18n.tr("Are you sure you want to reboot the system?")
},
"poweroff": {
"title": I18n.tr("Power Off"),
"message": I18n.tr("Are you sure you want to power off the system?")
}
};
const selected = actions[action];
if (selected) {
root.powerActionRequested(action, selected.title, selected.message);
}
function selectOption(action, actionIndex) {
startHold(action, actionIndex !== undefined ? actionIndex : -1);
}
shouldBeVisible: false
@@ -208,8 +257,15 @@ DankModal {
}
return Qt.point(0, 0);
}
onBackgroundClicked: () => close()
onBackgroundClicked: () => {
cancelHold();
close();
}
onOpened: () => {
holdAction = "";
holdActionIndex = -1;
holdProgress = 0;
showHoldHint = false;
updateVisibleActions();
const defaultIndex = getDefaultActionIndex();
if (SettingsData.powerMenuGridLayout) {
@@ -221,16 +277,42 @@ DankModal {
}
Qt.callLater(() => modalFocusScope.forceActiveFocus());
}
onDialogClosed: () => {
cancelHold();
}
Component.onCompleted: updateVisibleActions()
modalFocusScope.Keys.onPressed: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (SettingsData.powerMenuGridLayout) {
handleGridNavigation(event);
handleGridNavigation(event, true);
} else {
handleListNavigation(event);
handleListNavigation(event, true);
}
}
modalFocusScope.Keys.onReleased: event => {
if (event.isAutoRepeat) {
event.accepted = true;
return;
}
if (SettingsData.powerMenuGridLayout) {
handleGridNavigation(event, false);
} else {
handleListNavigation(event, false);
}
}
function handleListNavigation(event) {
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))) {
cancelHold();
event.accepted = true;
}
return;
}
switch (event.key) {
case Qt.Key_Up:
case Qt.Key_Backtab:
@@ -244,7 +326,7 @@ DankModal {
break;
case Qt.Key_Return:
case Qt.Key_Enter:
selectOption(getActionAtIndex(selectedIndex));
startHold(getActionAtIndex(selectedIndex), selectedIndex);
event.accepted = true;
break;
case Qt.Key_N:
@@ -255,7 +337,8 @@ DankModal {
break;
case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) {
selectOption("poweroff");
const idx = visibleActions.indexOf("poweroff");
startHold("poweroff", idx);
event.accepted = true;
} else {
selectedIndex = (selectedIndex - 1 + visibleActions.length) % visibleActions.length;
@@ -275,33 +358,41 @@ DankModal {
}
break;
case Qt.Key_R:
selectOption("reboot");
startHold("reboot", visibleActions.indexOf("reboot"));
event.accepted = true;
break;
case Qt.Key_X:
selectOption("logout");
startHold("logout", visibleActions.indexOf("logout"));
event.accepted = true;
break;
case Qt.Key_L:
selectOption("lock");
startHold("lock", visibleActions.indexOf("lock"));
event.accepted = true;
break;
case Qt.Key_S:
selectOption("suspend");
startHold("suspend", visibleActions.indexOf("suspend"));
event.accepted = true;
break;
case Qt.Key_H:
selectOption("hibernate");
startHold("hibernate", visibleActions.indexOf("hibernate"));
event.accepted = true;
break;
case Qt.Key_D:
selectOption("restart");
startHold("restart", visibleActions.indexOf("restart"));
event.accepted = true;
break;
}
}
function handleGridNavigation(event) {
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))) {
cancelHold();
event.accepted = true;
}
return;
}
switch (event.key) {
case Qt.Key_Left:
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
@@ -327,7 +418,7 @@ DankModal {
break;
case Qt.Key_Return:
case Qt.Key_Enter:
selectOption(getActionAtIndex(selectedIndex));
startHold(getActionAtIndex(selectedIndex), selectedIndex);
event.accepted = true;
break;
case Qt.Key_N:
@@ -339,7 +430,8 @@ DankModal {
break;
case Qt.Key_P:
if (!(event.modifiers & Qt.ControlModifier)) {
selectOption("poweroff");
const idx = visibleActions.indexOf("poweroff");
startHold("poweroff", idx);
event.accepted = true;
} else {
selectedCol = (selectedCol - 1 + gridColumns) % gridColumns;
@@ -362,27 +454,27 @@ DankModal {
}
break;
case Qt.Key_R:
selectOption("reboot");
startHold("reboot", visibleActions.indexOf("reboot"));
event.accepted = true;
break;
case Qt.Key_X:
selectOption("logout");
startHold("logout", visibleActions.indexOf("logout"));
event.accepted = true;
break;
case Qt.Key_L:
selectOption("lock");
startHold("lock", visibleActions.indexOf("lock"));
event.accepted = true;
break;
case Qt.Key_S:
selectOption("suspend");
startHold("suspend", visibleActions.indexOf("suspend"));
event.accepted = true;
break;
case Qt.Key_H:
selectOption("hibernate");
startHold("hibernate", visibleActions.indexOf("hibernate"));
event.accepted = true;
break;
case Qt.Key_D:
selectOption("restart");
startHold("restart", visibleActions.indexOf("restart"));
event.accepted = true;
break;
}
@@ -391,12 +483,14 @@ DankModal {
content: Component {
Item {
anchors.fill: parent
implicitHeight: SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight + Theme.spacingL * 2 : buttonColumn.implicitHeight + Theme.spacingL * 2
implicitHeight: (SettingsData.powerMenuGridLayout ? buttonGrid.implicitHeight : buttonColumn.implicitHeight) + Theme.spacingL * 2 + (root.needsConfirmation ? hintRow.height + Theme.spacingM : 0)
Grid {
id: buttonGrid
visible: SettingsData.powerMenuGridLayout
anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Theme.spacingL
columns: root.gridColumns
columnSpacing: Theme.spacingS
rowSpacing: Theme.spacingS
@@ -405,12 +499,14 @@ DankModal {
model: root.visibleActions
Rectangle {
id: gridButtonRect
required property int index
required property string modelData
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 isHolding: root.holdActionIndex === index && root.holdProgress > 0
width: (root.modalWidth - Theme.spacingL * 2 - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
height: 100
@@ -425,16 +521,50 @@ DankModal {
border.color: isSelected ? Theme.primary : "transparent"
border.width: isSelected ? 2 : 0
Rectangle {
id: gridProgressMask
anchors.fill: parent
radius: parent.radius
visible: false
layer.enabled: true
}
Item {
anchors.fill: parent
visible: gridButtonRect.isHolding
layer.enabled: gridButtonRect.isHolding
layer.effect: MultiEffect {
maskEnabled: true
maskSource: gridProgressMask
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * root.holdProgress
color: {
if (gridButtonRect.modelData === "poweroff")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
if (gridButtonRect.modelData === "reboot")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
}
}
}
Column {
anchors.centerIn: parent
spacing: Theme.spacingS
DankIcon {
name: parent.parent.actionData.icon
name: gridButtonRect.actionData.icon
size: Theme.iconSize + 8
color: {
if (parent.parent.showWarning && mouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning;
if (gridButtonRect.showWarning && (mouseArea.containsMouse || gridButtonRect.isHolding)) {
return gridButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
@@ -442,11 +572,11 @@ DankModal {
}
StyledText {
text: parent.parent.actionData.label
text: gridButtonRect.actionData.label
font.pixelSize: Theme.fontSizeMedium
color: {
if (parent.parent.showWarning && mouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning;
if (gridButtonRect.showWarning && (mouseArea.containsMouse || gridButtonRect.isHolding)) {
return gridButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
@@ -462,7 +592,7 @@ DankModal {
anchors.horizontalCenter: parent.horizontalCenter
StyledText {
text: parent.parent.parent.actionData.key
text: gridButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall - 1
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium
@@ -476,11 +606,14 @@ DankModal {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
onPressed: {
root.selectedRow = Math.floor(index / root.gridColumns);
root.selectedCol = index % root.gridColumns;
root.selectOption(modelData);
root.selectedIndex = index;
root.startHold(modelData, index);
}
onReleased: root.cancelHold()
onCanceled: root.cancelHold()
}
}
}
@@ -492,9 +625,10 @@ DankModal {
anchors {
left: parent.left
right: parent.right
top: parent.top
leftMargin: Theme.spacingL
rightMargin: Theme.spacingL
verticalCenter: parent.verticalCenter
topMargin: Theme.spacingL
}
spacing: Theme.spacingS
@@ -502,12 +636,14 @@ DankModal {
model: root.visibleActions
Rectangle {
id: listButtonRect
required property int index
required property string modelData
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 isHolding: root.holdActionIndex === index && root.holdProgress > 0
width: parent.width
height: 56
@@ -522,6 +658,40 @@ DankModal {
border.color: isSelected ? Theme.primary : "transparent"
border.width: isSelected ? 2 : 0
Rectangle {
id: listProgressMask
anchors.fill: parent
radius: parent.radius
visible: false
layer.enabled: true
}
Item {
anchors.fill: parent
visible: listButtonRect.isHolding
layer.enabled: listButtonRect.isHolding
layer.effect: MultiEffect {
maskEnabled: true
maskSource: listProgressMask
maskSpreadAtMin: 1
maskThresholdMin: 0.5
}
Rectangle {
anchors.left: parent.left
anchors.top: parent.top
anchors.bottom: parent.bottom
width: parent.width * root.holdProgress
color: {
if (listButtonRect.modelData === "poweroff")
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3);
if (listButtonRect.modelData === "reboot")
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3);
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
}
}
}
Row {
anchors {
left: parent.left
@@ -533,11 +703,11 @@ DankModal {
spacing: Theme.spacingM
DankIcon {
name: parent.parent.actionData.icon
name: listButtonRect.actionData.icon
size: Theme.iconSize + 4
color: {
if (parent.parent.showWarning && listMouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning;
if (listButtonRect.showWarning && (listMouseArea.containsMouse || listButtonRect.isHolding)) {
return listButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
@@ -545,11 +715,11 @@ DankModal {
}
StyledText {
text: parent.parent.actionData.label
text: listButtonRect.actionData.label
font.pixelSize: Theme.fontSizeMedium
color: {
if (parent.parent.showWarning && listMouseArea.containsMouse) {
return parent.parent.modelData === "poweroff" ? Theme.error : Theme.warning;
if (listButtonRect.showWarning && (listMouseArea.containsMouse || listButtonRect.isHolding)) {
return listButtonRect.modelData === "poweroff" ? Theme.error : Theme.warning;
}
return Theme.surfaceText;
}
@@ -570,7 +740,7 @@ DankModal {
}
StyledText {
text: parent.parent.actionData.key
text: listButtonRect.actionData.key
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
font.weight: Font.Medium
@@ -583,14 +753,53 @@ DankModal {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
onPressed: {
root.selectedIndex = index;
root.selectOption(modelData);
root.startHold(modelData, index);
}
onReleased: root.cancelHold()
onCanceled: root.cancelHold()
}
}
}
}
Row {
id: hintRow
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Theme.spacingS
spacing: Theme.spacingXS
visible: root.needsConfirmation
opacity: root.showHoldHint ? 1 : 0.5
Behavior on opacity {
NumberAnimation {
duration: 150
}
}
DankIcon {
name: root.showHoldHint ? "warning" : "touch_app"
size: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
readonly property int remainingSeconds: Math.ceil(SettingsData.powerActionHoldDuration * (1 - root.holdProgress))
text: {
if (root.showHoldHint)
return I18n.tr("Hold longer to confirm");
if (root.holdProgress > 0)
return I18n.tr("Hold to confirm (%1s)").arg(remainingSeconds);
return I18n.tr("Hold to confirm (%1s)").arg(SettingsData.powerActionHoldDuration);
}
font.pixelSize: Theme.fontSizeSmall
color: root.showHoldHint ? Theme.warning : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.6)
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
}

View File

@@ -583,11 +583,54 @@ Item {
DankToggle {
width: parent.width
text: I18n.tr("Show Confirmation on Power Actions")
description: I18n.tr("Request confirmation on power off, restart, suspend, hibernate and logout actions")
text: I18n.tr("Hold to Confirm Power Actions")
description: I18n.tr("Require holding button/key to confirm power off, restart, suspend, hibernate and logout")
checked: SettingsData.powerActionConfirm
onToggled: checked => SettingsData.set("powerActionConfirm", checked)
}
Column {
width: parent.width
spacing: Theme.spacingS
visible: SettingsData.powerActionConfirm
Item {
width: parent.width - Theme.spacingM * 2
anchors.horizontalCenter: parent.horizontalCenter
height: holdDurationLabel.height
StyledText {
id: holdDurationLabel
text: I18n.tr("Hold Duration")
font.pixelSize: Appearance.fontSize.normal
font.weight: Font.Medium
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
}
StyledText {
text: SettingsData.powerActionHoldDuration + "s"
font.pixelSize: Appearance.fontSize.normal
font.weight: Font.Medium
color: Theme.primary
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
}
DankSlider {
width: parent.width - Theme.spacingM * 2
anchors.horizontalCenter: parent.horizontalCenter
minimum: 1
maximum: 10
unit: "s"
wheelEnabled: false
showValue: false
thumbOutlineColor: Theme.surfaceContainerHigh
value: SettingsData.powerActionHoldDuration
onSliderValueChanged: newValue => SettingsData.set("powerActionHoldDuration", newValue)
}
}
}
}

View File

@@ -11,7 +11,7 @@ DankModal {
layerNamespace: "dms:spotlight"
HyprlandFocusGrab {
windows: [spotlightModal]
windows: [spotlightModal.contentWindow]
active: CompositorService.isHyprland && spotlightModal.shouldHaveFocus
}

View File

@@ -12,7 +12,7 @@ DankModal {
keepPopoutsOpen: true
HyprlandFocusGrab {
windows: [root]
windows: [root.contentWindow]
active: CompositorService.isHyprland && root.shouldHaveFocus
}

View File

@@ -354,7 +354,12 @@ Rectangle {
}
StyledText {
text: AudioService.displayName(modelData)
text: {
const mediaName = modelData && modelData.properties ? (modelData.properties["media.name"] || "") : "";
const max = 30;
const truncated = mediaName.length > max ? mediaName.substring(0, max) + "..." : mediaName;
return AudioService.displayName(modelData) + (truncated ? ": " + truncated : "");
}
font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal

View File

@@ -12,10 +12,12 @@ Scope {
property bool searchActive: false
property string searchActiveScreen: ""
property bool isClosing: false
property bool releaseKeyboard: false
property bool overlayActive: (NiriService.inOverview && !(PopoutService.spotlightModal?.spotlightOpen ?? false)) || searchActive
function showSpotlight(screenName) {
isClosing = false;
releaseKeyboard = false;
searchActive = true;
searchActiveScreen = screenName;
}
@@ -26,10 +28,16 @@ Scope {
isClosing = true;
}
function hideAndReleaseKeyboard() {
releaseKeyboard = true;
hideSpotlight();
}
function completeHide() {
searchActive = false;
searchActiveScreen = "";
isClosing = false;
releaseKeyboard = false;
}
Connections {
@@ -97,7 +105,7 @@ Scope {
return WlrKeyboardFocus.None;
if (!isActiveScreen)
return WlrKeyboardFocus.None;
if (niriOverviewScope.isClosing)
if (niriOverviewScope.releaseKeyboard)
return WlrKeyboardFocus.None;
return WlrKeyboardFocus.Exclusive;
}
@@ -248,6 +256,20 @@ Scope {
Component.onCompleted: {
parentModal = fakeParentModal;
}
Connections {
target: spotlightContent.appLauncher
function onAppLaunched() {
niriOverviewScope.releaseKeyboard = true;
}
}
Connections {
target: spotlightContent.fileSearchController
function onFileOpened() {
niriOverviewScope.releaseKeyboard = true;
}
}
}
}
}

View File

@@ -1,11 +1,11 @@
[templates.dmscodiumdefault]
input_path = 'SHELL_DIR/matugen/templates/vscode-color-theme-default.json'
output_path = '~/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-default-base.json'
output_path = '~/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-default.json'
[templates.dmscodiumdark]
input_path = 'SHELL_DIR/matugen/templates/vscode-color-theme-dark.json'
output_path = '~/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-dark-base.json'
output_path = '~/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-dark.json'
[templates.dmscodiumlight]
input_path = 'SHELL_DIR/matugen/templates/vscode-color-theme-light.json'
output_path = '~/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-light-base.json'
output_path = '~/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-light.json'

View File

@@ -1,11 +1,11 @@
[templates.dmsvscodedefault]
input_path = 'SHELL_DIR/matugen/templates/vscode-color-theme-default.json'
output_path = '~/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-default-base.json'
output_path = '~/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-default.json'
[templates.dmsvscodedark]
input_path = 'SHELL_DIR/matugen/templates/vscode-color-theme-dark.json'
output_path = '~/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-dark-base.json'
output_path = '~/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-dark.json'
[templates.dmsvscodelight]
input_path = 'SHELL_DIR/matugen/templates/vscode-color-theme-light.json'
output_path = '~/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-light-base.json'
output_path = '~/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1/themes/dankshell-light.json'

View File

@@ -9,3 +9,23 @@ background = '{{colors.primary_container.default.hex}}'
[colors.cursor]
text = '{{colors.background.default.hex}}'
cursor = '{{colors.primary.default.hex}}'
[colors.normal]
black = '{{dank16.color0}}'
red = '{{dank16.color1}}'
green = '{{dank16.color2}}'
yellow = '{{dank16.color3}}'
blue = '{{dank16.color4}}'
magenta = '{{dank16.color5}}'
cyan = '{{dank16.color6}}'
white = '{{dank16.color7}}'
[colors.bright]
black = '{{dank16.color8}}'
red = '{{dank16.color9}}'
green = '{{dank16.color10}}'
yellow = '{{dank16.color11}}'
blue = '{{dank16.color12}}'
magenta = '{{dank16.color13}}'
cyan = '{{dank16.color14}}'
white = '{{dank16.color15}}'

View File

@@ -3,3 +3,20 @@ foreground={{colors.on_surface.default.hex_stripped}}
background={{colors.background.default.hex_stripped}}
selection-foreground={{colors.on_surface.default.hex_stripped}}
selection-background={{colors.primary_container.default.hex_stripped}}
regular0={{dank16.color0 | remove_prefix: '#'}}
regular1={{dank16.color1 | remove_prefix: '#'}}
regular2={{dank16.color2 | remove_prefix: '#'}}
regular3={{dank16.color3 | remove_prefix: '#'}}
regular4={{dank16.color4 | remove_prefix: '#'}}
regular5={{dank16.color5 | remove_prefix: '#'}}
regular6={{dank16.color6 | remove_prefix: '#'}}
regular7={{dank16.color7 | remove_prefix: '#'}}
bright0={{dank16.color8 | remove_prefix: '#'}}
bright1={{dank16.color9 | remove_prefix: '#'}}
bright2={{dank16.color10 | remove_prefix: '#'}}
bright3={{dank16.color11 | remove_prefix: '#'}}
bright4={{dank16.color12 | remove_prefix: '#'}}
bright5={{dank16.color13 | remove_prefix: '#'}}
bright6={{dank16.color14 | remove_prefix: '#'}}
bright7={{dank16.color15 | remove_prefix: '#'}}

View File

@@ -2,4 +2,21 @@ background = {{colors.background.default.hex}}
foreground = {{colors.on_surface.default.hex}}
cursor-color = {{colors.primary.default.hex}}
selection-background = {{colors.primary_container.default.hex}}
selection-foreground = {{colors.on_surface.default.hex}}
selection-foreground = {{colors.on_surface.default.hex}}
palette = 0={{dank16.color0}}
palette = 1={{dank16.color1}}
palette = 2={{dank16.color2}}
palette = 3={{dank16.color3}}
palette = 4={{dank16.color4}}
palette = 5={{dank16.color5}}
palette = 6={{dank16.color6}}
palette = 7={{dank16.color7}}
palette = 8={{dank16.color8}}
palette = 9={{dank16.color9}}
palette = 10={{dank16.color10}}
palette = 11={{dank16.color11}}
palette = 12={{dank16.color12}}
palette = 13={{dank16.color13}}
palette = 14={{dank16.color14}}
palette = 15={{dank16.color15}}

View File

@@ -5,4 +5,21 @@ foreground {{colors.on_surface.default.hex}}
background {{colors.background.default.hex}}
selection_foreground {{colors.on_secondary.default.hex}}
selection_background {{colors.secondary_fixed_dim.default.hex}}
url_color {{colors.primary.default.hex}}
url_color {{colors.primary.default.hex}}
color0 {{dank16.color0}}
color1 {{dank16.color1}}
color2 {{dank16.color2}}
color3 {{dank16.color3}}
color4 {{dank16.color4}}
color5 {{dank16.color5}}
color6 {{dank16.color6}}
color7 {{dank16.color7}}
color8 {{dank16.color8}}
color9 {{dank16.color9}}
color10 {{dank16.color10}}
color11 {{dank16.color11}}
color12 {{dank16.color12}}
color13 {{dank16.color13}}
color14 {{dank16.color14}}
color15 {{dank16.color15}}

View File

@@ -97,6 +97,22 @@
"terminal.background": "{{colors.background.dark.hex}}",
"terminal.foreground": "{{colors.on_surface.dark.hex}}",
"terminal.ansiBlack": "{{dank16.color0}}",
"terminal.ansiRed": "{{dank16.color1}}",
"terminal.ansiGreen": "{{dank16.color2}}",
"terminal.ansiYellow": "{{dank16.color3}}",
"terminal.ansiBlue": "{{dank16.color4}}",
"terminal.ansiMagenta": "{{dank16.color5}}",
"terminal.ansiCyan": "{{dank16.color6}}",
"terminal.ansiWhite": "{{dank16.color7}}",
"terminal.ansiBrightBlack": "{{dank16.color8}}",
"terminal.ansiBrightRed": "{{dank16.color9}}",
"terminal.ansiBrightGreen": "{{dank16.color10}}",
"terminal.ansiBrightYellow": "{{dank16.color11}}",
"terminal.ansiBrightBlue": "{{dank16.color12}}",
"terminal.ansiBrightMagenta": "{{dank16.color13}}",
"terminal.ansiBrightCyan": "{{dank16.color14}}",
"terminal.ansiBrightWhite": "{{dank16.color15}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.dark.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.dark.hex}}",
@@ -158,19 +174,6 @@
},
"tokenColors": [
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{colors.outline.dark.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["keyword", "storage.type", "storage.modifier"],
"settings": {
"foreground": "{{colors.primary.dark.hex}}"
}
},
{
"scope": ["variable", "meta.object-literal.key"],
"settings": {
@@ -263,53 +266,166 @@
"settings": {
"foreground": "{{colors.secondary.dark.hex}}"
}
},
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{dank16.color8}}"
}
},
{
"scope": "keyword",
"settings": {
"foreground": "{{dank16.color5}}"
}
},
{
"scope": "storage.type",
"settings": {
"foreground": "{{dank16.color13}}"
}
},
{
"scope": "storage.modifier",
"settings": {
"foreground": "{{dank16.color5}}"
}
},
{
"scope": "variable",
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "variable.parameter",
"settings": {
"foreground": "{{dank16.color7}}"
}
},
{
"scope": ["meta.object-literal.key", "meta.property.object", "variable.other.property"],
"settings": {
"foreground": "{{dank16.color4}}"
}
},
{
"scope": "constant.other.symbol",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": ["constant.numeric", "constant.language"],
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "constant.character",
"settings": {
"foreground": "{{dank16.color3}}"
}
},
{
"scope": ["entity.name.type", "entity.name.class"],
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "support.type",
"settings": {
"foreground": "{{dank16.color13}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{dank16.color2}}"
}
},
{
"scope": ["support.class", "support.variable"],
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "variable.language",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "entity.name.tag.yaml",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": ["string.unquoted.plain.out.yaml", "string.unquoted.yaml"],
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "string",
"settings": {
"foreground": "{{dank16.color3}}"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable": {
"foreground": "{{dank16.color15}}"
},
"variable.readonly": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color12}}"
},
"property": {
"foreground": "{{colors.on_surface.dark.hex}}"
"foreground": "{{dank16.color4}}"
},
"function": {
"foreground": "{{colors.primary.dark.hex}}"
"foreground": "{{dank16.color2}}"
},
"method": {
"foreground": "{{colors.primary.dark.hex}}"
"foreground": "{{dank16.color2}}"
},
"type": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color12}}"
},
"class": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color12}}"
},
"typeParameter": {
"foreground": "{{dank16.color13}}"
},
"enumMember": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color12}}"
},
"string": {
"foreground": "{{colors.secondary.dark.hex}}"
"foreground": "{{dank16.color3}}"
},
"number": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color12}}"
},
"comment": {
"foreground": "{{colors.outline.dark.hex}}",
"fontStyle": "italic"
"foreground": "{{dank16.color8}}"
},
"keyword": {
"foreground": "{{colors.primary.dark.hex}}"
"foreground": "{{dank16.color5}}"
},
"operator": {
"foreground": "{{colors.on_surface.dark.hex}}"
"foreground": "{{dank16.color15}}"
},
"parameter": {
"foreground": "{{colors.on_surface.dark.hex}}"
"foreground": "{{dank16.color7}}"
},
"namespace": {
"foreground": "{{colors.secondary.dark.hex}}"
"foreground": "{{dank16.color15}}"
}
}
}

View File

@@ -97,6 +97,22 @@
"terminal.background": "{{colors.background.default.hex}}",
"terminal.foreground": "{{colors.on_surface.default.hex}}",
"terminal.ansiBlack": "{{dank16.color0}}",
"terminal.ansiRed": "{{dank16.color1}}",
"terminal.ansiGreen": "{{dank16.color2}}",
"terminal.ansiYellow": "{{dank16.color3}}",
"terminal.ansiBlue": "{{dank16.color4}}",
"terminal.ansiMagenta": "{{dank16.color5}}",
"terminal.ansiCyan": "{{dank16.color6}}",
"terminal.ansiWhite": "{{dank16.color7}}",
"terminal.ansiBrightBlack": "{{dank16.color8}}",
"terminal.ansiBrightRed": "{{dank16.color9}}",
"terminal.ansiBrightGreen": "{{dank16.color10}}",
"terminal.ansiBrightYellow": "{{dank16.color11}}",
"terminal.ansiBrightBlue": "{{dank16.color12}}",
"terminal.ansiBrightMagenta": "{{dank16.color13}}",
"terminal.ansiBrightCyan": "{{dank16.color14}}",
"terminal.ansiBrightWhite": "{{dank16.color15}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.default.hex}}",
@@ -158,19 +174,6 @@
},
"tokenColors": [
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{colors.outline.default.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["keyword", "storage.type", "storage.modifier"],
"settings": {
"foreground": "{{colors.primary.default.hex}}"
}
},
{
"scope": ["variable", "meta.object-literal.key"],
"settings": {
@@ -263,53 +266,166 @@
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{dank16.color8}}"
}
},
{
"scope": "keyword",
"settings": {
"foreground": "{{dank16.color5}}"
}
},
{
"scope": "storage.type",
"settings": {
"foreground": "{{dank16.color13}}"
}
},
{
"scope": "storage.modifier",
"settings": {
"foreground": "{{dank16.color5}}"
}
},
{
"scope": "variable",
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "variable.parameter",
"settings": {
"foreground": "{{dank16.color7}}"
}
},
{
"scope": ["meta.object-literal.key", "meta.property.object", "variable.other.property"],
"settings": {
"foreground": "{{dank16.color4}}"
}
},
{
"scope": "constant.other.symbol",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": ["constant.numeric", "constant.language"],
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "constant.character",
"settings": {
"foreground": "{{dank16.color3}}"
}
},
{
"scope": ["entity.name.type", "entity.name.class"],
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "support.type",
"settings": {
"foreground": "{{dank16.color13}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{dank16.color2}}"
}
},
{
"scope": ["support.class", "support.variable"],
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "variable.language",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "entity.name.tag.yaml",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": ["string.unquoted.plain.out.yaml", "string.unquoted.yaml"],
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "string",
"settings": {
"foreground": "{{dank16.color3}}"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable": {
"foreground": "{{dank16.color15}}"
},
"variable.readonly": {
"foreground": "{{colors.tertiary.default.hex}}"
"foreground": "{{dank16.color12}}"
},
"property": {
"foreground": "{{colors.on_surface.default.hex}}"
"foreground": "{{dank16.color4}}"
},
"function": {
"foreground": "{{colors.primary.default.hex}}"
"foreground": "{{dank16.color2}}"
},
"method": {
"foreground": "{{colors.primary.default.hex}}"
"foreground": "{{dank16.color2}}"
},
"type": {
"foreground": "{{colors.tertiary.default.hex}}"
"foreground": "{{dank16.color12}}"
},
"class": {
"foreground": "{{colors.tertiary.default.hex}}"
"foreground": "{{dank16.color12}}"
},
"typeParameter": {
"foreground": "{{dank16.color13}}"
},
"enumMember": {
"foreground": "{{colors.tertiary.default.hex}}"
"foreground": "{{dank16.color12}}"
},
"string": {
"foreground": "{{colors.secondary.default.hex}}"
"foreground": "{{dank16.color3}}"
},
"number": {
"foreground": "{{colors.tertiary.default.hex}}"
"foreground": "{{dank16.color12}}"
},
"comment": {
"foreground": "{{colors.outline.default.hex}}",
"fontStyle": "italic"
"foreground": "{{dank16.color8}}"
},
"keyword": {
"foreground": "{{colors.primary.default.hex}}"
"foreground": "{{dank16.color5}}"
},
"operator": {
"foreground": "{{colors.on_surface.default.hex}}"
"foreground": "{{dank16.color15}}"
},
"parameter": {
"foreground": "{{colors.on_surface.default.hex}}"
"foreground": "{{dank16.color7}}"
},
"namespace": {
"foreground": "{{colors.secondary.default.hex}}"
"foreground": "{{dank16.color15}}"
}
}
}

View File

@@ -97,6 +97,22 @@
"terminal.background": "{{colors.background.light.hex}}",
"terminal.foreground": "{{colors.on_surface.light.hex}}",
"terminal.ansiBlack": "{{dank16.color0}}",
"terminal.ansiRed": "{{dank16.color1}}",
"terminal.ansiGreen": "{{dank16.color2}}",
"terminal.ansiYellow": "{{dank16.color3}}",
"terminal.ansiBlue": "{{dank16.color4}}",
"terminal.ansiMagenta": "{{dank16.color5}}",
"terminal.ansiCyan": "{{dank16.color6}}",
"terminal.ansiWhite": "{{dank16.color7}}",
"terminal.ansiBrightBlack": "{{dank16.color8}}",
"terminal.ansiBrightRed": "{{dank16.color9}}",
"terminal.ansiBrightGreen": "{{dank16.color10}}",
"terminal.ansiBrightYellow": "{{dank16.color11}}",
"terminal.ansiBrightBlue": "{{dank16.color12}}",
"terminal.ansiBrightMagenta": "{{dank16.color13}}",
"terminal.ansiBrightCyan": "{{dank16.color14}}",
"terminal.ansiBrightWhite": "{{dank16.color15}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.light.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.light.hex}}",
@@ -158,19 +174,6 @@
},
"tokenColors": [
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{colors.outline.light.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["keyword", "storage.type", "storage.modifier"],
"settings": {
"foreground": "{{colors.primary.light.hex}}"
}
},
{
"scope": ["variable", "meta.object-literal.key"],
"settings": {
@@ -263,53 +266,166 @@
"settings": {
"foreground": "{{colors.secondary.light.hex}}"
}
},
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{dank16.color8}}"
}
},
{
"scope": "keyword",
"settings": {
"foreground": "{{dank16.color5}}"
}
},
{
"scope": "storage.type",
"settings": {
"foreground": "{{dank16.color13}}"
}
},
{
"scope": "storage.modifier",
"settings": {
"foreground": "{{dank16.color5}}"
}
},
{
"scope": "variable",
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "variable.parameter",
"settings": {
"foreground": "{{dank16.color7}}"
}
},
{
"scope": ["meta.object-literal.key", "meta.property.object", "variable.other.property"],
"settings": {
"foreground": "{{dank16.color4}}"
}
},
{
"scope": "constant.other.symbol",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": ["constant.numeric", "constant.language"],
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "constant.character",
"settings": {
"foreground": "{{dank16.color3}}"
}
},
{
"scope": ["entity.name.type", "entity.name.class"],
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "support.type",
"settings": {
"foreground": "{{dank16.color13}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{dank16.color2}}"
}
},
{
"scope": ["support.class", "support.variable"],
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "variable.language",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": "entity.name.tag.yaml",
"settings": {
"foreground": "{{dank16.color12}}"
}
},
{
"scope": ["string.unquoted.plain.out.yaml", "string.unquoted.yaml"],
"settings": {
"foreground": "{{dank16.color15}}"
}
},
{
"scope": "string",
"settings": {
"foreground": "{{dank16.color3}}"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable": {
"foreground": "{{dank16.color15}}"
},
"variable.readonly": {
"foreground": "{{colors.tertiary.light.hex}}"
"foreground": "{{dank16.color12}}"
},
"property": {
"foreground": "{{colors.on_surface.light.hex}}"
"foreground": "{{dank16.color4}}"
},
"function": {
"foreground": "{{colors.primary.light.hex}}"
"foreground": "{{dank16.color2}}"
},
"method": {
"foreground": "{{colors.primary.light.hex}}"
"foreground": "{{dank16.color2}}"
},
"type": {
"foreground": "{{colors.tertiary.light.hex}}"
"foreground": "{{dank16.color12}}"
},
"class": {
"foreground": "{{colors.tertiary.light.hex}}"
"foreground": "{{dank16.color12}}"
},
"typeParameter": {
"foreground": "{{dank16.color13}}"
},
"enumMember": {
"foreground": "{{colors.tertiary.light.hex}}"
"foreground": "{{dank16.color12}}"
},
"string": {
"foreground": "{{colors.secondary.light.hex}}"
"foreground": "{{dank16.color3}}"
},
"number": {
"foreground": "{{colors.tertiary.light.hex}}"
"foreground": "{{dank16.color12}}"
},
"comment": {
"foreground": "{{colors.outline.light.hex}}",
"fontStyle": "italic"
"foreground": "{{dank16.color8}}"
},
"keyword": {
"foreground": "{{colors.primary.light.hex}}"
"foreground": "{{dank16.color5}}"
},
"operator": {
"foreground": "{{colors.on_surface.light.hex}}"
"foreground": "{{dank16.color15}}"
},
"parameter": {
"foreground": "{{colors.on_surface.light.hex}}"
"foreground": "{{dank16.color7}}"
},
"namespace": {
"foreground": "{{colors.secondary.light.hex}}"
"foreground": "{{dank16.color15}}"
}
}
}

View File

@@ -8,3 +8,6 @@ cursor_border = '{{colors.primary.default.hex}}'
selection_bg = '{{colors.primary_container.default.hex}}'
selection_fg = '{{colors.on_surface.default.hex}}'
ansi = ['{{dank16.color0}}', '{{dank16.color1}}', '{{dank16.color2}}', '{{dank16.color3}}', '{{dank16.color4}}', '{{dank16.color5}}', '{{dank16.color6}}', '{{dank16.color7}}']
brights = ['{{dank16.color8}}', '{{dank16.color9}}', '{{dank16.color10}}', '{{dank16.color11}}', '{{dank16.color12}}', '{{dank16.color13}}', '{{dank16.color14}}', '{{dank16.color15}}']

View File

@@ -1,467 +1,275 @@
#!/usr/bin/env bash
set -euo pipefail
set -uo pipefail
if [ $# -lt 5 ]; then
echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL TERMINALS_ALWAYS_DARK --run" >&2
exit 1
fi
log() { echo "[matugen-worker] $*" >&2; }
err() { echo "[matugen-worker] ERROR: $*" >&2; }
[[ $# -lt 6 ]] && { echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL TERMINALS_ALWAYS_DARK --run" >&2; exit 1; }
STATE_DIR="$1"
SHELL_DIR="$2"
CONFIG_DIR="$3"
SYNC_MODE_WITH_PORTAL="$4"
TERMINALS_ALWAYS_DARK="$5"
if [ ! -d "$STATE_DIR" ]; then
echo "Error: STATE_DIR '$STATE_DIR' does not exist" >&2
exit 1
fi
if [ ! -d "$SHELL_DIR" ]; then
echo "Error: SHELL_DIR '$SHELL_DIR' does not exist" >&2
exit 1
fi
if [ ! -d "$CONFIG_DIR" ]; then
echo "Error: CONFIG_DIR '$CONFIG_DIR' does not exist" >&2
exit 1
fi
shift 5
[[ "${1:-}" != "--run" ]] && { echo "Usage: $0 ... --run" >&2; exit 1; }
if [[ "${1:-}" != "--run" ]]; then
echo "usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL TERMINALS_ALWAYS_DARK --run" >&2
exit 1
fi
[[ ! -d "$STATE_DIR" ]] && { err "STATE_DIR '$STATE_DIR' does not exist"; exit 1; }
[[ ! -d "$SHELL_DIR" ]] && { err "SHELL_DIR '$SHELL_DIR' does not exist"; exit 1; }
[[ ! -d "$CONFIG_DIR" ]] && { err "CONFIG_DIR '$CONFIG_DIR' does not exist"; exit 1; }
DESIRED_JSON="$STATE_DIR/matugen.desired.json"
BUILT_KEY="$STATE_DIR/matugen.key"
LAST_JSON="$STATE_DIR/last.json"
LOCK="$STATE_DIR/matugen-worker.lock"
COLORS_OUTPUT="$STATE_DIR/dms-colors.json"
exec 9>"$LOCK"
flock 9
rm -f "$BUILT_KEY"
get_matugen_major_version() {
local version_output=$(matugen --version 2>&1)
local version=$(echo "$version_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1)
local major=$(echo "$version" | cut -d. -f1)
echo "$major"
read_json_field() {
local json="$1" field="$2"
echo "$json" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -1
}
MATUGEN_VERSION=$(get_matugen_major_version)
read_desired() {
[[ ! -f "$DESIRED_JSON" ]] && { echo "no desired state" >&2; exit 0; }
cat "$DESIRED_JSON"
read_json_escaped_field() {
local json="$1" field="$2"
local after="${json#*\"$field\":\"}"
[[ "$after" == "$json" ]] && return
local result=""
while [[ -n "$after" ]]; do
local char="${after:0:1}"
after="${after:1}"
[[ "$char" == '"' ]] && break
[[ "$char" == '\' ]] && { result+="${after:0:1}"; after="${after:1}"; continue; }
result+="$char"
done
echo "$result"
}
key_of() {
read_json_bool() {
local json="$1" field="$2"
echo "$json" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\([^,}]*\).*/\1/p" | head -1 | tr -d ' '
}
compute_key() {
local json="$1"
local kind=$(echo "$json" | sed 's/.*"kind": *"\([^"]*\)".*/\1/')
local value=$(echo "$json" | sed 's/.*"value": *"\([^"]*\)".*/\1/')
local mode=$(echo "$json" | sed 's/.*"mode": *"\([^"]*\)".*/\1/')
local icon=$(echo "$json" | sed 's/.*"iconTheme": *"\([^"]*\)".*/\1/')
local matugen_type=$(echo "$json" | sed 's/.*"matugenType": *"\([^"]*\)".*/\1/')
local run_user_templates=$(echo "$json" | sed 's/.*"runUserTemplates": *\([^,}]*\).*/\1/')
[[ -z "$icon" ]] && icon="System Default"
[[ -z "$matugen_type" ]] && matugen_type="scheme-tonal-spot"
[[ -z "$run_user_templates" ]] && run_user_templates="true"
echo "${kind}|${value}|${mode}|${icon}|${matugen_type}|${run_user_templates}" | sha256sum | cut -d' ' -f1
local kind=$(read_json_field "$json" "kind")
local value=$(read_json_field "$json" "value")
local mode=$(read_json_field "$json" "mode")
local icon=$(read_json_field "$json" "iconTheme")
local mtype=$(read_json_field "$json" "matugenType")
local run_user=$(read_json_bool "$json" "runUserTemplates")
local stock_colors=$(read_json_escaped_field "$json" "stockColors")
echo "${kind}|${value}|${mode}|${icon:-default}|${mtype:-scheme-tonal-spot}|${run_user:-true}|${stock_colors:-}" | sha256sum | cut -d' ' -f1
}
append_config() {
local check_cmd="$1" file_name="$2" cfg_file="$3"
local target="$SHELL_DIR/matugen/configs/$file_name"
[[ ! -f "$target" ]] && return
[[ "$check_cmd" != "skip" ]] && ! command -v "$check_cmd" >/dev/null 2>&1 && return
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$target" >> "$cfg_file"
echo "" >> "$cfg_file"
}
build_merged_config() {
local mode="$1" run_user="$2" cfg_file="$3"
if [[ "$run_user" == "true" && -f "$CONFIG_DIR/matugen/config.toml" ]]; then
awk '/^\[config\]/{p=1} /^\[templates\]/{p=0} p' "$CONFIG_DIR/matugen/config.toml" >> "$cfg_file"
else
echo "[config]" >> "$cfg_file"
fi
echo "" >> "$cfg_file"
grep -v '^\[config\]' "$SHELL_DIR/matugen/configs/base.toml" | sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" >> "$cfg_file"
echo "" >> "$cfg_file"
cat >> "$cfg_file" << EOF
[templates.dank]
input_path = '$SHELL_DIR/matugen/templates/dank.json'
output_path = '$COLORS_OUTPUT'
EOF
[[ "$mode" == "light" ]] && append_config "skip" "gtk3-light.toml" "$cfg_file" || append_config "skip" "gtk3-dark.toml" "$cfg_file"
append_config "niri" "niri.toml" "$cfg_file"
append_config "qt5ct" "qt5ct.toml" "$cfg_file"
append_config "qt6ct" "qt6ct.toml" "$cfg_file"
append_config "firefox" "firefox.toml" "$cfg_file"
append_config "pywalfox" "pywalfox.toml" "$cfg_file"
append_config "vesktop" "vesktop.toml" "$cfg_file"
append_config "ghostty" "ghostty.toml" "$cfg_file"
append_config "kitty" "kitty.toml" "$cfg_file"
append_config "foot" "foot.toml" "$cfg_file"
append_config "alacritty" "alacritty.toml" "$cfg_file"
append_config "wezterm" "wezterm.toml" "$cfg_file"
append_config "dgop" "dgop.toml" "$cfg_file"
append_config "code" "vscode.toml" "$cfg_file"
append_config "codium" "codium.toml" "$cfg_file"
if [[ "$run_user" == "true" && -f "$CONFIG_DIR/matugen/config.toml" ]]; then
awk '/^\[templates\]/{p=1} p' "$CONFIG_DIR/matugen/config.toml" >> "$cfg_file"
echo "" >> "$cfg_file"
fi
if [[ -d "$CONFIG_DIR/matugen/dms/configs" ]]; then
for config in "$CONFIG_DIR/matugen/dms/configs"/*.toml; do
[[ -f "$config" ]] || continue
cat "$config" >> "$cfg_file"
echo "" >> "$cfg_file"
done
fi
}
generate_dank16() {
local primary="$1" surface="$2" light_flag="$3"
local args=("$primary" --json)
[[ -n "$light_flag" ]] && args+=("$light_flag")
[[ -n "$surface" ]] && args+=(--background "$surface")
dms dank16 "${args[@]}" 2>/dev/null || echo '{}'
}
set_system_color_scheme() {
[[ "$SYNC_MODE_WITH_PORTAL" != "true" ]] && return
local mode="$1"
local scheme="prefer-dark"
[[ "$mode" == "light" ]] && scheme="default"
gsettings set org.gnome.desktop.interface color-scheme "$scheme" 2>/dev/null || \
dconf write /org/gnome/desktop/interface/color-scheme "'$scheme'" 2>/dev/null || true
}
if [[ "$SYNC_MODE_WITH_PORTAL" != "true" ]]; then
return 0
refresh_gtk() {
local mode="$1"
local gtk_css="$CONFIG_DIR/gtk-3.0/gtk.css"
[[ ! -e "$gtk_css" ]] && return
local should_run=false
if [[ -L "$gtk_css" ]]; then
[[ "$(readlink "$gtk_css")" == *"dank-colors.css"* ]] && should_run=true
elif grep -q "dank-colors.css" "$gtk_css" 2>/dev/null; then
should_run=true
fi
[[ "$should_run" != "true" ]] && return
gsettings set org.gnome.desktop.interface gtk-theme "" 2>/dev/null || true
gsettings set org.gnome.desktop.interface gtk-theme "adw-gtk3-${mode}" 2>/dev/null || true
}
local target_scheme
if [[ "$mode" == "light" ]]; then
target_scheme="default"
else
target_scheme="prefer-dark"
fi
setup_vscode_extension() {
local cmd="$1" ext_dir="$2" config_dir="$3"
command -v "$cmd" >/dev/null 2>&1 || return
[[ ! -d "$config_dir" ]] && return
local theme_dir="$ext_dir/themes"
mkdir -p "$theme_dir"
cp "$SHELL_DIR/matugen/templates/vscode-package.json" "$ext_dir/package.json" 2>/dev/null || true
cp "$SHELL_DIR/matugen/templates/vscode-vsixmanifest.xml" "$ext_dir/.vsixmanifest" 2>/dev/null || true
}
if command -v gsettings >/dev/null 2>&1; then
gsettings set org.gnome.desktop.interface color-scheme "$target_scheme" >/dev/null 2>&1 || true
elif command -v dconf >/dev/null 2>&1; then
dconf write /org/gnome/desktop/interface/color-scheme "'$target_scheme'" >/dev/null 2>&1 || true
fi
signal_terminals() {
pgrep -x kitty >/dev/null 2>&1 && pkill -USR1 kitty
pgrep -x ghostty >/dev/null 2>&1 && pkill -USR2 ghostty
}
build_once() {
local json="$1"
local kind value mode icon matugen_type run_user_templates
kind=$(echo "$json" | sed 's/.*"kind": *"\([^"]*\)".*/\1/')
value=$(echo "$json" | sed 's/.*"value": *"\([^"]*\)".*/\1/')
mode=$(echo "$json" | sed 's/.*"mode": *"\([^"]*\)".*/\1/')
icon=$(echo "$json" | sed 's/.*"iconTheme": *"\([^"]*\)".*/\1/')
matugen_type=$(echo "$json" | sed 's/.*"matugenType": *"\([^"]*\)".*/\1/')
run_user_templates=$(echo "$json" | sed 's/.*"runUserTemplates": *\([^,}]*\).*/\1/')
[[ -z "$icon" ]] && icon="System Default"
[[ -z "$matugen_type" ]] && matugen_type="scheme-tonal-spot"
[[ -z "$run_user_templates" ]] && run_user_templates="true"
local kind=$(read_json_field "$json" "kind")
local value=$(read_json_field "$json" "value")
local mode=$(read_json_field "$json" "mode")
local mtype=$(read_json_field "$json" "matugenType")
local run_user=$(read_json_bool "$json" "runUserTemplates")
local stock_colors=$(read_json_escaped_field "$json" "stockColors")
USER_MATUGEN_DIR="$CONFIG_DIR/matugen/dms"
TMP_CFG="$(mktemp)"
trap 'rm -f "$TMP_CFG"' RETURN
[[ -z "$mtype" ]] && mtype="scheme-tonal-spot"
[[ -z "$run_user" ]] && run_user="true"
if [[ "$run_user_templates" == "true" ]] && [[ -f "$CONFIG_DIR/matugen/config.toml" ]]; then
awk '/^\[config/{p=1} /^\[templates/{p=0} p' "$CONFIG_DIR/matugen/config.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
local TMP_CFG=$(mktemp)
trap "rm -f '$TMP_CFG'" RETURN
build_merged_config "$mode" "$run_user" "$TMP_CFG"
local light_flag=""
[[ "$mode" == "light" ]] && light_flag="--light"
local primary surface dank16_dark dank16_light import_args=()
if [[ -n "$stock_colors" ]]; then
log "Using stock/custom theme colors with matugen base"
primary=$(echo "$stock_colors" | sed -n 's/.*"primary"[^{]*{[^}]*"dark"[^{]*{[^}]*"color"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
surface=$(echo "$stock_colors" | sed -n 's/.*"surface"[^{]*{[^}]*"dark"[^{]*{[^}]*"color"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
[[ -z "$primary" ]] && { err "Failed to extract primary from stock colors"; return 1; }
dank16_dark=$(generate_dank16 "$primary" "$surface" "")
dank16_light=$(generate_dank16 "$primary" "$surface" "--light")
local dank16_current
[[ "$mode" == "light" ]] && dank16_current="$dank16_light" || dank16_current="$dank16_dark"
[[ "$TERMINALS_ALWAYS_DARK" == "true" && "$mode" == "light" ]] && dank16_current="$dank16_dark"
import_args+=(--import-json-string "{\"colors\": $stock_colors, \"dank16\": $dank16_current}")
log "Running matugen color hex with stock color overrides"
if ! matugen color hex "$primary" -m "$mode" -t "${mtype:-scheme-tonal-spot}" -c "$TMP_CFG" "${import_args[@]}"; then
err "matugen failed"
return 1
fi
else
echo "[config]" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
log "Using dynamic theme from $kind: $value"
grep -v '^\[config\]' "$SHELL_DIR/matugen/configs/base.toml" | \
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
local matugen_cmd=("matugen")
[[ "$kind" == "hex" ]] && matugen_cmd+=("color" "hex") || matugen_cmd+=("$kind")
matugen_cmd+=("$value")
cat >> "$TMP_CFG" << EOF
[templates.dank]
input_path = '$SHELL_DIR/matugen/templates/dank.json'
output_path = '$STATE_DIR/dms-colors.json'
local mat_json
mat_json=$("${matugen_cmd[@]}" -m dark -t "$mtype" --json hex --dry-run 2>/dev/null | tr -d '\n')
[[ -z "$mat_json" ]] && { err "matugen dry-run failed"; return 1; }
EOF
primary=$(echo "$mat_json" | sed -n 's/.*"primary"[[:space:]]*:[[:space:]]*{[^}]*"dark"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
surface=$(echo "$mat_json" | sed -n 's/.*"surface"[[:space:]]*:[[:space:]]*{[^}]*"dark"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
# If light mode, use gtk3 light config
if [[ "$mode" == "light" ]]; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/gtk3-light.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
else
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/gtk3-dark.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
[[ -z "$primary" ]] && { err "Failed to extract primary color"; return 1; }
if command -v niri >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/niri.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
dank16_dark=$(generate_dank16 "$primary" "$surface" "")
dank16_light=$(generate_dank16 "$primary" "$surface" "--light")
if command -v qt5ct >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/qt5ct.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
local dank16_current
[[ "$mode" == "light" ]] && dank16_current="$dank16_light" || dank16_current="$dank16_dark"
[[ "$TERMINALS_ALWAYS_DARK" == "true" && "$mode" == "light" ]] && dank16_current="$dank16_dark"
if command -v qt6ct >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/qt6ct.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
import_args+=(--import-json-string "{\"dank16\": $dank16_current}")
if command -v firefox >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/firefox.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
if command -v pywalfox >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/pywalfox.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
if command -v vesktop >/dev/null 2>&1 && [[ -d "$CONFIG_DIR/vesktop" ]]; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/vesktop.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
if [[ "$run_user_templates" == "true" ]] && [[ -f "$CONFIG_DIR/matugen/config.toml" ]]; then
awk '/^\[templates/{p=1} p' "$CONFIG_DIR/matugen/config.toml" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
fi
for config in "$USER_MATUGEN_DIR/configs"/*.toml; do
[[ -f "$config" ]] || continue
cat "$config" >> "$TMP_CFG"
echo "" >> "$TMP_CFG"
done
pushd "$SHELL_DIR" >/dev/null
MAT_MODE=(-m "$mode")
MAT_TYPE=(-t "$matugen_type")
case "$kind" in
image)
[[ -f "$value" ]] || { echo "wallpaper not found: $value" >&2; popd >/dev/null; return 2; }
JSON=$(matugen -c "$TMP_CFG" --json hex image "$value" "${MAT_MODE[@]}" "${MAT_TYPE[@]}")
matugen -c "$TMP_CFG" image "$value" "${MAT_MODE[@]}" "${MAT_TYPE[@]}" >/dev/null
;;
hex)
[[ "$value" =~ ^#[0-9A-Fa-f]{6}$ ]] || { echo "invalid hex: $value" >&2; popd >/dev/null; return 2; }
JSON=$(matugen -c "$TMP_CFG" --json hex color hex "$value" "${MAT_MODE[@]}" "${MAT_TYPE[@]}")
matugen -c "$TMP_CFG" color hex "$value" "${MAT_MODE[@]}" "${MAT_TYPE[@]}" >/dev/null
;;
*)
echo "unknown kind: $kind" >&2; popd >/dev/null; return 2;;
esac
TMP_CONTENT_CFG="$(mktemp)"
echo "[config]" > "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
TERMINAL_MODE="$mode"
if [[ "$TERMINALS_ALWAYS_DARK" == "true" ]]; then
TERMINAL_MODE="dark"
fi
if command -v ghostty >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/ghostty.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if command -v kitty >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/kitty.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if command -v foot >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/foot.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if command -v alacritty >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/alacritty.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if command -v wezterm >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/wezterm.toml" >>"$TMP_CONTENT_CFG"
echo "" >>"$TMP_CONTENT_CFG"
fi
if command -v dgop >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/dgop.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if command -v code >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/vscode.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if command -v codium >/dev/null 2>&1; then
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$SHELL_DIR/matugen/configs/codium.toml" >> "$TMP_CONTENT_CFG"
echo "" >> "$TMP_CONTENT_CFG"
fi
if [[ -s "$TMP_CONTENT_CFG" ]] && grep -q '\[templates\.' "$TMP_CONTENT_CFG"; then
MAT_TERMINAL_MODE=(-m "$TERMINAL_MODE")
case "$kind" in
image)
matugen -c "$TMP_CONTENT_CFG" image "$value" "${MAT_TERMINAL_MODE[@]}" "${MAT_TYPE[@]}" >/dev/null
;;
hex)
matugen -c "$TMP_CONTENT_CFG" color hex "$value" "${MAT_TERMINAL_MODE[@]}" "${MAT_TYPE[@]}" >/dev/null
;;
esac
fi
rm -f "$TMP_CONTENT_CFG"
popd >/dev/null
echo "$JSON" | grep -q '"primary"' || { echo "matugen JSON missing primary" >&2; set_system_color_scheme "$mode"; return 2; }
printf "%s" "$JSON" > "$LAST_JSON"
GTK_CSS="$CONFIG_DIR/gtk-3.0/gtk.css"
SHOULD_RUN_HOOK=false
if [[ -L "$GTK_CSS" ]]; then
LINK_TARGET=$(readlink "$GTK_CSS")
if [[ "$LINK_TARGET" == *"dank-colors.css"* ]]; then
SHOULD_RUN_HOOK=true
fi
elif [[ -f "$GTK_CSS" ]] && grep -q "dank-colors.css" "$GTK_CSS"; then
SHOULD_RUN_HOOK=true
fi
if [[ "$SHOULD_RUN_HOOK" == "true" ]]; then
gsettings set org.gnome.desktop.interface gtk-theme "" >/dev/null 2>&1 || true
gsettings set org.gnome.desktop.interface gtk-theme "adw-gtk3-${mode}" >/dev/null 2>&1 || true
fi
COLOR_EXTRACT_MODE="$mode"
if [[ "$TERMINALS_ALWAYS_DARK" == "true" ]]; then
COLOR_EXTRACT_MODE="dark"
fi
if [[ "$MATUGEN_VERSION" == "2" ]]; then
PRIMARY=$(echo "$JSON" | sed -n "s/.*\"$COLOR_EXTRACT_MODE\":{[^}]*\"primary\":\"\\(#[0-9a-fA-F]\\{6\\}\\)\".*/\\1/p")
SURFACE=$(echo "$JSON" | sed -n "s/.*\"$COLOR_EXTRACT_MODE\":{[^}]*\"surface\":\"\\(#[0-9a-fA-F]\\{6\\}\\)\".*/\\1/p")
else
JSON_FLAT=$(echo "$JSON" | tr -d '\n')
PRIMARY=$(echo "$JSON_FLAT" | sed -n "s/.*\"primary\" *: *{ *[^}]*\"$COLOR_EXTRACT_MODE\" *: *\"\\(#[0-9a-fA-F]\\{6\\}\\)\".*/\\1/p")
SURFACE=$(echo "$JSON_FLAT" | sed -n "s/.*\"surface\" *: *{ *[^}]*\"$COLOR_EXTRACT_MODE\" *: *\"\\(#[0-9a-fA-F]\\{6\\}\\)\".*/\\1/p")
fi
if [[ -z "$PRIMARY" ]]; then
echo "Error: Failed to extract PRIMARY color from matugen JSON (mode: $mode)" >&2
echo "This may indicate an incompatible matugen JSON format" >&2
set_system_color_scheme "$mode"
return 2
fi
TERMINAL_LIGHT_FLAG=""
if [[ "$TERMINALS_ALWAYS_DARK" != "true" ]] && [[ "$mode" == "light" ]]; then
TERMINAL_LIGHT_FLAG="--light"
fi
if command -v ghostty >/dev/null 2>&1 && [[ -f "$CONFIG_DIR/ghostty/config-dankcolors" ]]; then
OUT=$(dms dank16 "$PRIMARY" $TERMINAL_LIGHT_FLAG ${SURFACE:+--background "$SURFACE"} --ghostty 2>/dev/null || true)
if [[ -n "${OUT:-}" ]]; then
printf "\n%s\n" "$OUT" >> "$CONFIG_DIR/ghostty/config-dankcolors"
if [[ -f "$CONFIG_DIR/ghostty/config" ]] && grep -q "^[^#]*config-dankcolors" "$CONFIG_DIR/ghostty/config" 2>/dev/null; then
pkill -USR2 -x 'ghostty|.ghostty-wrappe' >/dev/null 2>&1 || true
fi
log "Running matugen $kind with dank16 injection"
if ! "${matugen_cmd[@]}" -m "$mode" -t "$mtype" -c "$TMP_CFG" "${import_args[@]}"; then
err "matugen failed"
return 1
fi
fi
if command -v kitty >/dev/null 2>&1 && [[ -f "$CONFIG_DIR/kitty/dank-theme.conf" ]]; then
OUT=$(dms dank16 "$PRIMARY" $TERMINAL_LIGHT_FLAG ${SURFACE:+--background "$SURFACE"} --kitty 2>/dev/null || true)
if [[ -n "${OUT:-}" ]]; then
printf "\n%s\n" "$OUT" >> "$CONFIG_DIR/kitty/dank-theme.conf"
if [[ -f "$CONFIG_DIR/kitty/kitty.conf" ]] && grep -q "^[^#]*dank-theme.conf" "$CONFIG_DIR/kitty/kitty.conf" 2>/dev/null; then
pkill -USR1 -x kitty >/dev/null 2>&1 || true
fi
fi
fi
if command -v foot >/dev/null 2>&1; then
FOOT_CONFIG="$CONFIG_DIR/foot/dank-colors.ini"
if [[ ! -f "$FOOT_CONFIG" ]]; then
mkdir -p "$(dirname "$FOOT_CONFIG")"
echo "[colors]" > "$FOOT_CONFIG"
fi
OUT=$(dms dank16 "$PRIMARY" $TERMINAL_LIGHT_FLAG ${SURFACE:+--background "$SURFACE"} --foot 2>/dev/null || true)
if [[ -n "${OUT:-}" ]]; then
printf "\n%s\n" "$OUT" >> "$FOOT_CONFIG"
fi
fi
if command -v wezterm >/dev/null 2>&1; then
WEZTERM_CONFIG="$CONFIG_DIR/wezterm/colors/dank-theme.toml"
if [[ ! -f "$WEZTERM_CONFIG" ]]; then
mkdir -p "$(dirname "$WEZTERM_CONFIG")"
touch "$WEZTERM_CONFIG"
fi
OUT=$(dms dank16 "$PRIMARY" $TERMINAL_LIGHT_FLAG ${SURFACE:+--background "$SURFACE"} --wezterm 2>/dev/null || true)
if [[ -n "${OUT:-}" ]]; then
printf "\n%s\n" "$OUT" >>"$WEZTERM_CONFIG"
fi
fi
if command -v alacritty >/dev/null 2>&1; then
ALACRITTY_CONFIG="$CONFIG_DIR/alacritty/dank-theme.toml"
if [[ ! -f "$ALACRITTY_CONFIG" ]]; then
mkdir -p "$(dirname "$ALACRITTY_CONFIG")"
touch "$ALACRITTY_CONFIG"
fi
OUT=$(dms dank16 "$PRIMARY" $TERMINAL_LIGHT_FLAG ${SURFACE:+--background "$SURFACE"} --alacritty 2>/dev/null || true)
if [[ -n "${OUT:-}" ]]; then
printf "\n%s\n" "$OUT" >> "$ALACRITTY_CONFIG"
fi
fi
if command -v code >/dev/null 2>&1; then
VSCODE_EXT_DIR="$HOME/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1"
VSCODE_THEME_DIR="$VSCODE_EXT_DIR/themes"
VSCODE_BASE_THEME="$VSCODE_THEME_DIR/dankshell-color-theme-base.json"
VSCODE_FINAL_THEME="$VSCODE_THEME_DIR/dankshell-color-theme.json"
mkdir -p "$VSCODE_THEME_DIR"
cp "$SHELL_DIR/matugen/templates/vscode-package.json" "$VSCODE_EXT_DIR/package.json"
cp "$SHELL_DIR/matugen/templates/vscode-vsixmanifest.xml" "$VSCODE_EXT_DIR/.vsixmanifest"
for variant in default dark light; do
VSCODE_BASE="$VSCODE_THEME_DIR/dankshell-${variant}-base.json"
VSCODE_FINAL="$VSCODE_THEME_DIR/dankshell-${variant}.json"
if [[ -f "$VSCODE_BASE" ]]; then
VARIANT_FLAG=""
if [[ "$variant" == "light" ]]; then
VARIANT_FLAG="--light"
elif [[ "$variant" == "default" && "$mode" == "light" ]]; then
VARIANT_FLAG="--light"
fi
dms dank16 "$PRIMARY" $VARIANT_FLAG ${SURFACE:+--background "$SURFACE"} --vscode-enrich "$VSCODE_BASE" > "$VSCODE_FINAL" 2>/dev/null || cp "$VSCODE_BASE" "$VSCODE_FINAL"
fi
done
fi
if command -v codium >/dev/null 2>&1; then
CODIUM_EXT_DIR="$HOME/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1"
CODIUM_THEME_DIR="$CODIUM_EXT_DIR/themes"
CODIUM_BASE_THEME="$CODIUM_THEME_DIR/dankshell-color-theme-base.json"
CODIUM_FINAL_THEME="$CODIUM_THEME_DIR/dankshell-color-theme.json"
CODIUM_EXTENSIONS_JSON="$HOME/.vscode-oss/extensions/extensions.json"
mkdir -p "$CODIUM_THEME_DIR"
cp "$SHELL_DIR/matugen/templates/vscode-package.json" "$CODIUM_EXT_DIR/package.json"
cp "$SHELL_DIR/matugen/templates/vscode-vsixmanifest.xml" "$CODIUM_EXT_DIR/.vsixmanifest"
if [[ -f "$CODIUM_EXTENSIONS_JSON" ]]; then
if ! grep -q "local.dynamic-base16-dankshell" "$CODIUM_EXTENSIONS_JSON" 2>/dev/null; then
cp "$CODIUM_EXTENSIONS_JSON" "$CODIUM_EXTENSIONS_JSON.backup-$(date +%s)" 2>/dev/null || true
CODIUM_ENTRY='{"identifier":{"id":"local.dynamic-base16-dankshell"},"version":"0.0.1","location":{"$mid":1,"path":"'"$CODIUM_EXT_DIR"'","scheme":"file"},"relativeLocation":"local.dynamic-base16-dankshell-0.0.1","metadata":{"id":"local.dynamic-base16-dankshell","publisherId":"local","publisherDisplayName":"local","targetPlatform":"undefined","isApplicationScoped":false,"updated":false,"isPreReleaseVersion":false,"installedTimestamp":'"$(date +%s)000"',"preRelease":false}}'
if [[ "$(cat "$CODIUM_EXTENSIONS_JSON")" == "[]" ]]; then
echo "[$CODIUM_ENTRY]" > "$CODIUM_EXTENSIONS_JSON"
else
TMP_JSON="$(mktemp)"
sed 's/]$/,'"$CODIUM_ENTRY"']/' "$CODIUM_EXTENSIONS_JSON" > "$TMP_JSON"
mv "$TMP_JSON" "$CODIUM_EXTENSIONS_JSON"
fi
fi
fi
for variant in default dark light; do
CODIUM_BASE="$CODIUM_THEME_DIR/dankshell-${variant}-base.json"
CODIUM_FINAL="$CODIUM_THEME_DIR/dankshell-${variant}.json"
if [[ -f "$CODIUM_BASE" ]]; then
VARIANT_FLAG=""
if [[ "$variant" == "light" ]]; then
VARIANT_FLAG="--light"
elif [[ "$variant" == "default" && "$mode" == "light" ]]; then
VARIANT_FLAG="--light"
fi
dms dank16 "$PRIMARY" $VARIANT_FLAG ${SURFACE:+--background "$SURFACE"} --vscode-enrich "$CODIUM_BASE" > "$CODIUM_FINAL" 2>/dev/null || cp "$CODIUM_BASE" "$CODIUM_FINAL"
fi
done
fi
refresh_gtk "$mode"
setup_vscode_extension "code" "$HOME/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1" "$HOME/.vscode"
setup_vscode_extension "codium" "$HOME/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1" "$HOME/.vscode-oss"
set_system_color_scheme "$mode"
signal_terminals
return 0
}
while :; do
DESIRED="$(read_desired)"
WANT_KEY="$(key_of "$DESIRED")"
HAVE_KEY=""
[[ -f "$BUILT_KEY" ]] && HAVE_KEY="$(cat "$BUILT_KEY" 2>/dev/null || true)"
[[ ! -f "$DESIRED_JSON" ]] && { log "No desired state file"; exit 0; }
if [[ "$WANT_KEY" == "$HAVE_KEY" ]]; then
exit 0
fi
DESIRED=$(cat "$DESIRED_JSON")
WANT_KEY=$(compute_key "$DESIRED")
HAVE_KEY=""
[[ -f "$BUILT_KEY" ]] && HAVE_KEY=$(cat "$BUILT_KEY" 2>/dev/null || true)
if build_once "$DESIRED"; then
echo "$WANT_KEY" > "$BUILT_KEY"
else
exit 2
fi
done
[[ "$WANT_KEY" == "$HAVE_KEY" ]] && { log "Already up to date"; exit 0; }
exit 0
log "Building theme (key: ${WANT_KEY:0:12}...)"
if build_once "$DESIRED"; then
echo "$WANT_KEY" > "$BUILT_KEY"
log "Done"
exit 0
else
err "Build failed"
exit 2
fi