summaryrefslogtreecommitdiff
path: root/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds')
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery.go107
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery_test.go287
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland.go497
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser.go627
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser_test.go519
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_test.go220
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile.go119
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile_test.go281
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc.go442
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser.go613
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser_test.go499
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_test.go320
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle.go95
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle_parser.go320
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri.go650
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser.go400
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser_test.go630
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_test.go661
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway.go150
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser.go364
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser_test.go471
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_test.go293
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry.go79
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry_test.go183
-rw-r--r--raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/types.go47
25 files changed, 0 insertions, 8874 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery.go
deleted file mode 100644
index 4990975..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery.go
+++ /dev/null
@@ -1,107 +0,0 @@
-package keybinds
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-type DiscoveryConfig struct {
- SearchPaths []string
-}
-
-func DefaultDiscoveryConfig() *DiscoveryConfig {
- var searchPaths []string
-
- configDir, err := os.UserConfigDir()
- if err == nil && configDir != "" {
- searchPaths = append(searchPaths, filepath.Join(configDir, "DankMaterialShell", "cheatsheets"))
- }
-
- configDirs := os.Getenv("XDG_CONFIG_DIRS")
- if configDirs != "" {
- for dir := range strings.SplitSeq(configDirs, ":") {
- if dir != "" {
- searchPaths = append(searchPaths, filepath.Join(dir, "DankMaterialShell", "cheatsheets"))
- }
- }
- }
-
- return &DiscoveryConfig{
- SearchPaths: searchPaths,
- }
-}
-
-func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
- var files []string
-
- for _, searchPath := range d.SearchPaths {
- expandedPath, err := utils.ExpandPath(searchPath)
- if err != nil {
- continue
- }
-
- if _, err := os.Stat(expandedPath); os.IsNotExist(err) {
- continue
- }
-
- entries, err := os.ReadDir(expandedPath)
- if err != nil {
- continue
- }
-
- for _, entry := range entries {
- if entry.IsDir() {
- continue
- }
-
- if !strings.HasSuffix(entry.Name(), ".json") {
- continue
- }
-
- fullPath := filepath.Join(expandedPath, entry.Name())
- files = append(files, fullPath)
- }
- }
-
- return files, nil
-}
-
-type JSONProviderFactory func(filePath string) (Provider, error)
-
-var jsonProviderFactory JSONProviderFactory
-
-func SetJSONProviderFactory(factory JSONProviderFactory) {
- jsonProviderFactory = factory
-}
-
-func AutoDiscoverProviders(registry *Registry, config *DiscoveryConfig) error {
- if config == nil {
- config = DefaultDiscoveryConfig()
- }
-
- if jsonProviderFactory == nil {
- return nil
- }
-
- files, err := config.FindJSONFiles()
- if err != nil {
- return fmt.Errorf("failed to discover JSON files: %w", err)
- }
-
- for _, file := range files {
- provider, err := jsonProviderFactory(file)
- if err != nil {
- continue
- }
-
- if err := registry.Register(provider); err != nil {
- continue
- }
- }
-
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery_test.go
deleted file mode 100644
index a78724e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/discovery_test.go
+++ /dev/null
@@ -1,287 +0,0 @@
-package keybinds
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-func TestDefaultDiscoveryConfig(t *testing.T) {
- oldConfigHome := os.Getenv("XDG_CONFIG_HOME")
- oldConfigDirs := os.Getenv("XDG_CONFIG_DIRS")
- defer func() {
- os.Setenv("XDG_CONFIG_HOME", oldConfigHome)
- os.Setenv("XDG_CONFIG_DIRS", oldConfigDirs)
- }()
-
- tests := []struct {
- name string
- configHome string
- configDirs string
- expectedCount int
- checkFirstPath bool
- firstPath string
- }{
- {
- name: "default with no XDG vars",
- configHome: "",
- configDirs: "",
- expectedCount: 1,
- checkFirstPath: true,
- },
- {
- name: "with XDG_CONFIG_HOME set",
- configHome: "/custom/config",
- configDirs: "",
- expectedCount: 1,
- checkFirstPath: true,
- firstPath: "/custom/config/DankMaterialShell/cheatsheets",
- },
- {
- name: "with XDG_CONFIG_DIRS set",
- configHome: "/home/user/.config",
- configDirs: "/etc/xdg:/opt/config",
- expectedCount: 3,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- os.Setenv("XDG_CONFIG_HOME", tt.configHome)
- os.Setenv("XDG_CONFIG_DIRS", tt.configDirs)
-
- config := DefaultDiscoveryConfig()
-
- if config == nil {
- t.Fatal("DefaultDiscoveryConfig returned nil")
- }
-
- if len(config.SearchPaths) != tt.expectedCount {
- t.Errorf("SearchPaths count = %d, want %d", len(config.SearchPaths), tt.expectedCount)
- }
-
- if tt.checkFirstPath && len(config.SearchPaths) > 0 {
- if tt.firstPath != "" && config.SearchPaths[0] != tt.firstPath {
- t.Errorf("SearchPaths[0] = %q, want %q", config.SearchPaths[0], tt.firstPath)
- }
- }
- })
- }
-}
-
-func TestFindJSONFiles(t *testing.T) {
- tmpDir := t.TempDir()
-
- file1 := filepath.Join(tmpDir, "tmux.json")
- file2 := filepath.Join(tmpDir, "vim.json")
- txtFile := filepath.Join(tmpDir, "readme.txt")
- subdir := filepath.Join(tmpDir, "subdir")
-
- if err := os.WriteFile(file1, []byte("{}"), 0o644); err != nil {
- t.Fatalf("Failed to create file1: %v", err)
- }
- if err := os.WriteFile(file2, []byte("{}"), 0o644); err != nil {
- t.Fatalf("Failed to create file2: %v", err)
- }
- if err := os.WriteFile(txtFile, []byte("text"), 0o644); err != nil {
- t.Fatalf("Failed to create txt file: %v", err)
- }
- if err := os.MkdirAll(subdir, 0o755); err != nil {
- t.Fatalf("Failed to create subdir: %v", err)
- }
-
- config := &DiscoveryConfig{
- SearchPaths: []string{tmpDir},
- }
-
- files, err := config.FindJSONFiles()
- if err != nil {
- t.Fatalf("FindJSONFiles failed: %v", err)
- }
-
- if len(files) != 2 {
- t.Errorf("expected 2 JSON files, got %d", len(files))
- }
-
- found := make(map[string]bool)
- for _, f := range files {
- found[filepath.Base(f)] = true
- }
-
- if !found["tmux.json"] {
- t.Error("tmux.json not found")
- }
- if !found["vim.json"] {
- t.Error("vim.json not found")
- }
- if found["readme.txt"] {
- t.Error("readme.txt should not be included")
- }
-}
-
-func TestFindJSONFilesNonexistentPath(t *testing.T) {
- config := &DiscoveryConfig{
- SearchPaths: []string{"/nonexistent/path"},
- }
-
- files, err := config.FindJSONFiles()
- if err != nil {
- t.Fatalf("FindJSONFiles failed: %v", err)
- }
-
- if len(files) != 0 {
- t.Errorf("expected 0 files for nonexistent path, got %d", len(files))
- }
-}
-
-func TestFindJSONFilesMultiplePaths(t *testing.T) {
- tmpDir1 := t.TempDir()
- tmpDir2 := t.TempDir()
-
- file1 := filepath.Join(tmpDir1, "app1.json")
- file2 := filepath.Join(tmpDir2, "app2.json")
-
- if err := os.WriteFile(file1, []byte("{}"), 0o644); err != nil {
- t.Fatalf("Failed to create file1: %v", err)
- }
- if err := os.WriteFile(file2, []byte("{}"), 0o644); err != nil {
- t.Fatalf("Failed to create file2: %v", err)
- }
-
- config := &DiscoveryConfig{
- SearchPaths: []string{tmpDir1, tmpDir2},
- }
-
- files, err := config.FindJSONFiles()
- if err != nil {
- t.Fatalf("FindJSONFiles failed: %v", err)
- }
-
- if len(files) != 2 {
- t.Errorf("expected 2 JSON files from multiple paths, got %d", len(files))
- }
-}
-
-func TestAutoDiscoverProviders(t *testing.T) {
- tmpDir := t.TempDir()
-
- jsonContent := `{
- "title": "Test App",
- "provider": "testapp",
- "binds": {}
-}`
-
- file := filepath.Join(tmpDir, "testapp.json")
- if err := os.WriteFile(file, []byte(jsonContent), 0o644); err != nil {
- t.Fatalf("Failed to create test file: %v", err)
- }
-
- config := &DiscoveryConfig{
- SearchPaths: []string{tmpDir},
- }
-
- registry := NewRegistry()
-
- factoryCalled := false
- SetJSONProviderFactory(func(filePath string) (Provider, error) {
- factoryCalled = true
- return &mockProvider{name: "testapp"}, nil
- })
-
- err := AutoDiscoverProviders(registry, config)
- if err != nil {
- t.Fatalf("AutoDiscoverProviders failed: %v", err)
- }
-
- if !factoryCalled {
- t.Error("factory was not called")
- }
-
- provider, err := registry.Get("testapp")
- if err != nil {
- t.Fatalf("provider not registered: %v", err)
- }
-
- if provider.Name() != "testapp" {
- t.Errorf("provider name = %q, want %q", provider.Name(), "testapp")
- }
-}
-
-func TestAutoDiscoverProvidersNilConfig(t *testing.T) {
- registry := NewRegistry()
-
- SetJSONProviderFactory(func(filePath string) (Provider, error) {
- return &mockProvider{name: "test"}, nil
- })
-
- err := AutoDiscoverProviders(registry, nil)
- if err != nil {
- t.Fatalf("AutoDiscoverProviders with nil config failed: %v", err)
- }
-}
-
-func TestAutoDiscoverProvidersNoFactory(t *testing.T) {
- tmpDir := t.TempDir()
-
- file := filepath.Join(tmpDir, "test.json")
- if err := os.WriteFile(file, []byte("{}"), 0o644); err != nil {
- t.Fatalf("Failed to create test file: %v", err)
- }
-
- config := &DiscoveryConfig{
- SearchPaths: []string{tmpDir},
- }
-
- registry := NewRegistry()
-
- SetJSONProviderFactory(nil)
-
- err := AutoDiscoverProviders(registry, config)
- if err != nil {
- t.Fatalf("AutoDiscoverProviders should not fail without factory: %v", err)
- }
-
- providers := registry.List()
- if len(providers) != 0 {
- t.Errorf("expected 0 providers without factory, got %d", len(providers))
- }
-}
-
-func TestExpandPathInDiscovery(t *testing.T) {
- home, err := os.UserHomeDir()
- if err != nil {
- t.Skip("Cannot get home directory")
- }
-
- tests := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "tilde expansion",
- input: "~/test",
- expected: filepath.Join(home, "test"),
- },
- {
- name: "absolute path",
- input: "/tmp/test",
- expected: "/tmp/test",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result, err := utils.ExpandPath(tt.input)
- if err != nil {
- t.Fatalf("expandPath failed: %v", err)
- }
-
- if result != tt.expected {
- t.Errorf("utils.ExpandPath(%q) = %q, want %q", tt.input, result, tt.expected)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland.go
deleted file mode 100644
index e43a9eb..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland.go
+++ /dev/null
@@ -1,497 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-type HyprlandProvider struct {
- configPath string
- dmsBindsIncluded bool
- parsed bool
-}
-
-func NewHyprlandProvider(configPath string) *HyprlandProvider {
- if configPath == "" {
- configPath = defaultHyprlandConfigDir()
- }
- return &HyprlandProvider{
- configPath: configPath,
- }
-}
-
-func defaultHyprlandConfigDir() string {
- configDir, err := os.UserConfigDir()
- if err != nil {
- return ""
- }
- return filepath.Join(configDir, "hypr")
-}
-
-func (h *HyprlandProvider) Name() string {
- return "hyprland"
-}
-
-func (h *HyprlandProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
- result, err := ParseHyprlandKeysWithDMS(h.configPath)
- if err != nil {
- return nil, fmt.Errorf("failed to parse hyprland config: %w", err)
- }
-
- h.dmsBindsIncluded = result.DMSBindsIncluded
- h.parsed = true
-
- categorizedBinds := make(map[string][]keybinds.Keybind)
- h.convertSection(result.Section, "", categorizedBinds, result.ConflictingConfigs)
-
- sheet := &keybinds.CheatSheet{
- Title: "Hyprland Keybinds",
- Provider: h.Name(),
- Binds: categorizedBinds,
- DMSBindsIncluded: result.DMSBindsIncluded,
- }
-
- if result.DMSStatus != nil {
- sheet.DMSStatus = &keybinds.DMSBindsStatus{
- Exists: result.DMSStatus.Exists,
- Included: result.DMSStatus.Included,
- IncludePosition: result.DMSStatus.IncludePosition,
- TotalIncludes: result.DMSStatus.TotalIncludes,
- BindsAfterDMS: result.DMSStatus.BindsAfterDMS,
- Effective: result.DMSStatus.Effective,
- OverriddenBy: result.DMSStatus.OverriddenBy,
- StatusMessage: result.DMSStatus.StatusMessage,
- }
- }
-
- return sheet, nil
-}
-
-func (h *HyprlandProvider) HasDMSBindsIncluded() bool {
- if h.parsed {
- return h.dmsBindsIncluded
- }
-
- result, err := ParseHyprlandKeysWithDMS(h.configPath)
- if err != nil {
- return false
- }
-
- h.dmsBindsIncluded = result.DMSBindsIncluded
- h.parsed = true
- return h.dmsBindsIncluded
-}
-
-func (h *HyprlandProvider) convertSection(section *HyprlandSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind, conflicts map[string]*HyprlandKeyBinding) {
- currentSubcat := subcategory
- if section.Name != "" {
- currentSubcat = section.Name
- }
-
- for _, kb := range section.Keybinds {
- category := h.categorizeByDispatcher(kb.Dispatcher)
- bind := h.convertKeybind(&kb, currentSubcat, conflicts)
- categorizedBinds[category] = append(categorizedBinds[category], bind)
- }
-
- for _, child := range section.Children {
- h.convertSection(&child, currentSubcat, categorizedBinds, conflicts)
- }
-}
-
-func (h *HyprlandProvider) categorizeByDispatcher(dispatcher string) string {
- switch {
- case strings.Contains(dispatcher, "workspace"):
- return "Workspace"
- case strings.Contains(dispatcher, "monitor"):
- return "Monitor"
- case strings.Contains(dispatcher, "window") ||
- strings.Contains(dispatcher, "focus") ||
- strings.Contains(dispatcher, "move") ||
- strings.Contains(dispatcher, "swap") ||
- strings.Contains(dispatcher, "resize") ||
- dispatcher == "killactive" ||
- dispatcher == "fullscreen" ||
- dispatcher == "togglefloating" ||
- dispatcher == "pin" ||
- dispatcher == "fakefullscreen" ||
- dispatcher == "splitratio" ||
- dispatcher == "resizeactive":
- return "Window"
- case dispatcher == "exec":
- return "Execute"
- case dispatcher == "exit" || strings.Contains(dispatcher, "dpms"):
- return "System"
- default:
- return "Other"
- }
-}
-
-func (h *HyprlandProvider) convertKeybind(kb *HyprlandKeyBinding, subcategory string, conflicts map[string]*HyprlandKeyBinding) keybinds.Keybind {
- keyStr := h.formatKey(kb)
- rawAction := h.formatRawAction(kb.Dispatcher, kb.Params)
- desc := kb.Comment
-
- if desc == "" {
- desc = rawAction
- }
-
- source := "config"
- if strings.Contains(kb.Source, "dms/binds.conf") {
- source = "dms"
- }
-
- bind := keybinds.Keybind{
- Key: keyStr,
- Description: desc,
- Action: rawAction,
- Subcategory: subcategory,
- Source: source,
- Flags: kb.Flags,
- }
-
- if source == "dms" && conflicts != nil {
- normalizedKey := strings.ToLower(keyStr)
- if conflictKb, ok := conflicts[normalizedKey]; ok {
- bind.Conflict = &keybinds.Keybind{
- Key: keyStr,
- Description: conflictKb.Comment,
- Action: h.formatRawAction(conflictKb.Dispatcher, conflictKb.Params),
- Source: "config",
- }
- }
- }
-
- return bind
-}
-
-func (h *HyprlandProvider) formatRawAction(dispatcher, params string) string {
- if params != "" {
- return dispatcher + " " + params
- }
- return dispatcher
-}
-
-func (h *HyprlandProvider) formatKey(kb *HyprlandKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (h *HyprlandProvider) GetOverridePath() string {
- expanded, err := utils.ExpandPath(h.configPath)
- if err != nil {
- return filepath.Join(h.configPath, "dms", "binds.conf")
- }
- return filepath.Join(expanded, "dms", "binds.conf")
-}
-
-func (h *HyprlandProvider) validateAction(action string) error {
- action = strings.TrimSpace(action)
- switch {
- case action == "":
- return fmt.Errorf("action cannot be empty")
- case action == "exec" || action == "exec ":
- return fmt.Errorf("exec dispatcher requires arguments")
- case strings.HasPrefix(action, "exec "):
- rest := strings.TrimSpace(strings.TrimPrefix(action, "exec "))
- if rest == "" {
- return fmt.Errorf("exec dispatcher requires arguments")
- }
- }
- return nil
-}
-
-func (h *HyprlandProvider) SetBind(key, action, description string, options map[string]any) error {
- if err := h.validateAction(action); err != nil {
- return err
- }
-
- overridePath := h.GetOverridePath()
-
- if err := os.MkdirAll(filepath.Dir(overridePath), 0o755); err != nil {
- return fmt.Errorf("failed to create dms directory: %w", err)
- }
-
- existingBinds, err := h.loadOverrideBinds()
- if err != nil {
- existingBinds = make(map[string]*hyprlandOverrideBind)
- }
-
- // Extract flags from options
- var flags string
- if options != nil {
- if f, ok := options["flags"].(string); ok {
- flags = f
- }
- }
-
- normalizedKey := strings.ToLower(key)
- existingBinds[normalizedKey] = &hyprlandOverrideBind{
- Key: key,
- Action: action,
- Description: description,
- Flags: flags,
- Options: options,
- }
-
- return h.writeOverrideBinds(existingBinds)
-}
-
-func (h *HyprlandProvider) RemoveBind(key string) error {
- existingBinds, err := h.loadOverrideBinds()
- if err != nil {
- return nil
- }
-
- normalizedKey := strings.ToLower(key)
- delete(existingBinds, normalizedKey)
- return h.writeOverrideBinds(existingBinds)
-}
-
-type hyprlandOverrideBind struct {
- Key string
- Action string
- Description string
- Flags string // Bind flags: l=locked, r=release, e=repeat, n=non-consuming, m=mouse, t=transparent, i=ignore-mods, s=separate, d=description, o=long-press
- Options map[string]any
-}
-
-func (h *HyprlandProvider) loadOverrideBinds() (map[string]*hyprlandOverrideBind, error) {
- overridePath := h.GetOverridePath()
- binds := make(map[string]*hyprlandOverrideBind)
-
- data, err := os.ReadFile(overridePath)
- if os.IsNotExist(err) {
- return binds, nil
- }
- if err != nil {
- return nil, err
- }
-
- lines := strings.Split(string(data), "\n")
- for _, line := range lines {
- line = strings.TrimSpace(line)
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
-
- if !strings.HasPrefix(line, "bind") {
- continue
- }
-
- parts := strings.SplitN(line, "=", 2)
- if len(parts) < 2 {
- continue
- }
-
- // Extract flags from bind type
- bindType := strings.TrimSpace(parts[0])
- flags := extractBindFlags(bindType)
- hasDescFlag := strings.Contains(flags, "d")
-
- content := strings.TrimSpace(parts[1])
- commentParts := strings.SplitN(content, "#", 2)
- bindContent := strings.TrimSpace(commentParts[0])
-
- var comment string
- if len(commentParts) > 1 {
- comment = strings.TrimSpace(commentParts[1])
- }
-
- // For bindd, format is: mods, key, description, dispatcher, params
- var minFields, descIndex, dispatcherIndex int
- if hasDescFlag {
- minFields = 4
- descIndex = 2
- dispatcherIndex = 3
- } else {
- minFields = 3
- dispatcherIndex = 2
- }
-
- fields := strings.SplitN(bindContent, ",", minFields+2)
- if len(fields) < minFields {
- continue
- }
-
- mods := strings.TrimSpace(fields[0])
- keyName := strings.TrimSpace(fields[1])
-
- var dispatcher, params string
- if hasDescFlag {
- if comment == "" {
- comment = strings.TrimSpace(fields[descIndex])
- }
- dispatcher = strings.TrimSpace(fields[dispatcherIndex])
- if len(fields) > dispatcherIndex+1 {
- paramParts := fields[dispatcherIndex+1:]
- params = strings.TrimSpace(strings.Join(paramParts, ","))
- }
- } else {
- dispatcher = strings.TrimSpace(fields[dispatcherIndex])
- if len(fields) > dispatcherIndex+1 {
- paramParts := fields[dispatcherIndex+1:]
- params = strings.TrimSpace(strings.Join(paramParts, ","))
- }
- }
-
- keyStr := h.buildKeyString(mods, keyName)
- normalizedKey := strings.ToLower(keyStr)
- action := dispatcher
- if params != "" {
- action = dispatcher + " " + params
- }
-
- binds[normalizedKey] = &hyprlandOverrideBind{
- Key: keyStr,
- Action: action,
- Description: comment,
- Flags: flags,
- }
- }
-
- return binds, nil
-}
-
-func (h *HyprlandProvider) buildKeyString(mods, key string) string {
- if mods == "" {
- return key
- }
-
- modList := strings.FieldsFunc(mods, func(r rune) bool {
- return r == '+' || r == ' '
- })
-
- parts := append(modList, key)
- return strings.Join(parts, "+")
-}
-
-func (h *HyprlandProvider) getBindSortPriority(action string) int {
- switch {
- case strings.HasPrefix(action, "exec") && strings.Contains(action, "dms"):
- return 0
- case strings.Contains(action, "workspace"):
- return 1
- case strings.Contains(action, "window") || strings.Contains(action, "focus") ||
- strings.Contains(action, "move") || strings.Contains(action, "swap") ||
- strings.Contains(action, "resize"):
- return 2
- case strings.Contains(action, "monitor"):
- return 3
- case strings.HasPrefix(action, "exec"):
- return 4
- case action == "exit" || strings.Contains(action, "dpms"):
- return 5
- default:
- return 6
- }
-}
-
-func (h *HyprlandProvider) writeOverrideBinds(binds map[string]*hyprlandOverrideBind) error {
- overridePath := h.GetOverridePath()
- content := h.generateBindsContent(binds)
- return os.WriteFile(overridePath, []byte(content), 0o644)
-}
-
-func (h *HyprlandProvider) generateBindsContent(binds map[string]*hyprlandOverrideBind) string {
- if len(binds) == 0 {
- return ""
- }
-
- bindList := make([]*hyprlandOverrideBind, 0, len(binds))
- for _, bind := range binds {
- bindList = append(bindList, bind)
- }
-
- sort.Slice(bindList, func(i, j int) bool {
- pi, pj := h.getBindSortPriority(bindList[i].Action), h.getBindSortPriority(bindList[j].Action)
- if pi != pj {
- return pi < pj
- }
- return bindList[i].Key < bindList[j].Key
- })
-
- var sb strings.Builder
- for _, bind := range bindList {
- h.writeBindLine(&sb, bind)
- }
-
- return sb.String()
-}
-
-func (h *HyprlandProvider) writeBindLine(sb *strings.Builder, bind *hyprlandOverrideBind) {
- mods, key := h.parseKeyString(bind.Key)
- dispatcher, params := h.parseAction(bind.Action)
-
- // Write bind type with flags (e.g., "bind", "binde", "bindel")
- sb.WriteString("bind")
- if bind.Flags != "" {
- sb.WriteString(bind.Flags)
- }
- sb.WriteString(" = ")
- sb.WriteString(mods)
- sb.WriteString(", ")
- sb.WriteString(key)
- sb.WriteString(", ")
-
- // For bindd (description flag), include description before dispatcher
- if strings.Contains(bind.Flags, "d") && bind.Description != "" {
- sb.WriteString(bind.Description)
- sb.WriteString(", ")
- }
-
- sb.WriteString(dispatcher)
-
- if params != "" {
- sb.WriteString(", ")
- sb.WriteString(params)
- }
-
- // Only add comment if not using bindd (which has inline description)
- if bind.Description != "" && !strings.Contains(bind.Flags, "d") {
- sb.WriteString(" # ")
- sb.WriteString(bind.Description)
- }
-
- sb.WriteString("\n")
-}
-
-func (h *HyprlandProvider) parseKeyString(keyStr string) (mods, key string) {
- parts := strings.Split(keyStr, "+")
- switch len(parts) {
- case 0:
- return "", keyStr
- case 1:
- return "", parts[0]
- default:
- return strings.Join(parts[:len(parts)-1], " "), parts[len(parts)-1]
- }
-}
-
-func (h *HyprlandProvider) parseAction(action string) (dispatcher, params string) {
- parts := strings.SplitN(action, " ", 2)
- switch len(parts) {
- case 0:
- return action, ""
- case 1:
- dispatcher = parts[0]
- default:
- dispatcher = parts[0]
- params = parts[1]
- }
-
- // Convert internal spawn format to Hyprland's exec
- if dispatcher == "spawn" {
- dispatcher = "exec"
- }
-
- return dispatcher, params
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser.go
deleted file mode 100644
index 04b5ada..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser.go
+++ /dev/null
@@ -1,627 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-const (
- TitleRegex = "#+!"
- HideComment = "[hidden]"
- CommentBindPattern = "#/#"
-)
-
-var ModSeparators = []rune{'+', ' '}
-
-type HyprlandKeyBinding struct {
- Mods []string `json:"mods"`
- Key string `json:"key"`
- Dispatcher string `json:"dispatcher"`
- Params string `json:"params"`
- Comment string `json:"comment"`
- Source string `json:"source"`
- Flags string `json:"flags"` // Bind flags: l=locked, r=release, e=repeat, n=non-consuming, m=mouse, t=transparent, i=ignore-mods, s=separate, d=description, o=long-press
-}
-
-type HyprlandSection struct {
- Children []HyprlandSection `json:"children"`
- Keybinds []HyprlandKeyBinding `json:"keybinds"`
- Name string `json:"name"`
-}
-
-type HyprlandParser struct {
- contentLines []string
- readingLine int
- configDir string
- currentSource string
- dmsBindsExists bool
- dmsBindsIncluded bool
- includeCount int
- dmsIncludePos int
- bindsAfterDMS int
- dmsBindKeys map[string]bool
- configBindKeys map[string]bool
- conflictingConfigs map[string]*HyprlandKeyBinding
- bindMap map[string]*HyprlandKeyBinding
- bindOrder []string
- processedFiles map[string]bool
- dmsProcessed bool
-}
-
-func NewHyprlandParser(configDir string) *HyprlandParser {
- return &HyprlandParser{
- contentLines: []string{},
- readingLine: 0,
- configDir: configDir,
- dmsIncludePos: -1,
- dmsBindKeys: make(map[string]bool),
- configBindKeys: make(map[string]bool),
- conflictingConfigs: make(map[string]*HyprlandKeyBinding),
- bindMap: make(map[string]*HyprlandKeyBinding),
- bindOrder: []string{},
- processedFiles: make(map[string]bool),
- }
-}
-
-func (p *HyprlandParser) ReadContent(directory string) error {
- expandedDir, err := utils.ExpandPath(directory)
- if err != nil {
- return err
- }
-
- info, err := os.Stat(expandedDir)
- if err != nil {
- return err
- }
- if !info.IsDir() {
- return os.ErrNotExist
- }
-
- confFiles, err := filepath.Glob(filepath.Join(expandedDir, "*.conf"))
- if err != nil {
- return err
- }
- if len(confFiles) == 0 {
- return os.ErrNotExist
- }
-
- var combinedContent []string
- for _, confFile := range confFiles {
- if fileInfo, err := os.Stat(confFile); err == nil && fileInfo.Mode().IsRegular() {
- data, err := os.ReadFile(confFile)
- if err == nil {
- combinedContent = append(combinedContent, string(data))
- }
- }
- }
-
- if len(combinedContent) == 0 {
- return os.ErrNotExist
- }
-
- fullContent := strings.Join(combinedContent, "\n")
- p.contentLines = strings.Split(fullContent, "\n")
- return nil
-}
-
-func hyprlandAutogenerateComment(dispatcher, params string) string {
- switch dispatcher {
- case "resizewindow":
- return "Resize window"
-
- case "movewindow":
- if params == "" {
- return "Move window"
- }
- dirMap := map[string]string{
- "l": "left",
- "r": "right",
- "u": "up",
- "d": "down",
- }
- if dir, ok := dirMap[params]; ok {
- return "move in " + dir + " direction"
- }
- return "move in null direction"
-
- case "pin":
- return "pin (show on all workspaces)"
-
- case "splitratio":
- return "Window split ratio " + params
-
- case "togglefloating":
- return "Float/unfloat window"
-
- case "resizeactive":
- return "Resize window by " + params
-
- case "killactive":
- return "Close window"
-
- case "fullscreen":
- fsMap := map[string]string{
- "0": "fullscreen",
- "1": "maximization",
- "2": "fullscreen on Hyprland's side",
- }
- if fs, ok := fsMap[params]; ok {
- return "Toggle " + fs
- }
- return "Toggle null"
-
- case "fakefullscreen":
- return "Toggle fake fullscreen"
-
- case "workspace":
- switch params {
- case "+1":
- return "focus right"
- case "-1":
- return "focus left"
- }
- return "focus workspace " + params
- case "movefocus":
- dirMap := map[string]string{
- "l": "left",
- "r": "right",
- "u": "up",
- "d": "down",
- }
- if dir, ok := dirMap[params]; ok {
- return "move focus " + dir
- }
- return "move focus null"
-
- case "swapwindow":
- dirMap := map[string]string{
- "l": "left",
- "r": "right",
- "u": "up",
- "d": "down",
- }
- if dir, ok := dirMap[params]; ok {
- return "swap in " + dir + " direction"
- }
- return "swap in null direction"
-
- case "movetoworkspace":
- switch params {
- case "+1":
- return "move to right workspace (non-silent)"
- case "-1":
- return "move to left workspace (non-silent)"
- }
- return "move to workspace " + params + " (non-silent)"
- case "movetoworkspacesilent":
- switch params {
- case "+1":
- return "move to right workspace"
- case "-1":
- return "move to right workspace"
- }
- return "move to workspace " + params
-
- case "togglespecialworkspace":
- return "toggle special"
-
- case "exec":
- return params
-
- default:
- return ""
- }
-}
-
-func (p *HyprlandParser) getKeybindAtLine(lineNumber int) *HyprlandKeyBinding {
- line := p.contentLines[lineNumber]
- return p.parseBindLine(line)
-}
-
-func (p *HyprlandParser) getBindsRecursive(currentContent *HyprlandSection, scope int) *HyprlandSection {
- titleRegex := regexp.MustCompile(TitleRegex)
-
- for p.readingLine < len(p.contentLines) {
- line := p.contentLines[p.readingLine]
-
- loc := titleRegex.FindStringIndex(line)
- if loc != nil && loc[0] == 0 {
- headingScope := strings.Index(line, "!")
-
- if headingScope <= scope {
- p.readingLine--
- return currentContent
- }
-
- sectionName := strings.TrimSpace(line[headingScope+1:])
- p.readingLine++
-
- childSection := &HyprlandSection{
- Children: []HyprlandSection{},
- Keybinds: []HyprlandKeyBinding{},
- Name: sectionName,
- }
- result := p.getBindsRecursive(childSection, headingScope)
- currentContent.Children = append(currentContent.Children, *result)
-
- } else if strings.HasPrefix(line, CommentBindPattern) {
- keybind := p.getKeybindAtLine(p.readingLine)
- if keybind != nil {
- currentContent.Keybinds = append(currentContent.Keybinds, *keybind)
- }
-
- } else if line == "" || !strings.HasPrefix(strings.TrimSpace(line), "bind") {
-
- } else {
- keybind := p.getKeybindAtLine(p.readingLine)
- if keybind != nil {
- currentContent.Keybinds = append(currentContent.Keybinds, *keybind)
- }
- }
-
- p.readingLine++
- }
-
- return currentContent
-}
-
-func (p *HyprlandParser) ParseKeys() *HyprlandSection {
- p.readingLine = 0
- rootSection := &HyprlandSection{
- Children: []HyprlandSection{},
- Keybinds: []HyprlandKeyBinding{},
- Name: "",
- }
- return p.getBindsRecursive(rootSection, 0)
-}
-
-func ParseHyprlandKeys(path string) (*HyprlandSection, error) {
- parser := NewHyprlandParser(path)
- if err := parser.ReadContent(path); err != nil {
- return nil, err
- }
- return parser.ParseKeys(), nil
-}
-
-type HyprlandParseResult struct {
- Section *HyprlandSection
- DMSBindsIncluded bool
- DMSStatus *HyprlandDMSStatus
- ConflictingConfigs map[string]*HyprlandKeyBinding
-}
-
-type HyprlandDMSStatus struct {
- Exists bool
- Included bool
- IncludePosition int
- TotalIncludes int
- BindsAfterDMS int
- Effective bool
- OverriddenBy int
- StatusMessage string
-}
-
-func (p *HyprlandParser) buildDMSStatus() *HyprlandDMSStatus {
- status := &HyprlandDMSStatus{
- Exists: p.dmsBindsExists,
- Included: p.dmsBindsIncluded,
- IncludePosition: p.dmsIncludePos,
- TotalIncludes: p.includeCount,
- BindsAfterDMS: p.bindsAfterDMS,
- }
-
- switch {
- case !p.dmsBindsExists:
- status.Effective = false
- status.StatusMessage = "dms/binds.conf does not exist"
- case !p.dmsBindsIncluded:
- status.Effective = false
- status.StatusMessage = "dms/binds.conf is not sourced in config"
- case p.bindsAfterDMS > 0:
- status.Effective = true
- status.OverriddenBy = p.bindsAfterDMS
- status.StatusMessage = "Some DMS binds may be overridden by config binds"
- default:
- status.Effective = true
- status.StatusMessage = "DMS binds are active"
- }
-
- return status
-}
-
-func (p *HyprlandParser) formatBindKey(kb *HyprlandKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (p *HyprlandParser) normalizeKey(key string) string {
- return strings.ToLower(key)
-}
-
-func (p *HyprlandParser) addBind(kb *HyprlandKeyBinding) bool {
- key := p.formatBindKey(kb)
- normalizedKey := p.normalizeKey(key)
- isDMSBind := strings.Contains(kb.Source, "dms/binds.conf")
-
- if isDMSBind {
- p.dmsBindKeys[normalizedKey] = true
- } else if p.dmsBindKeys[normalizedKey] {
- p.bindsAfterDMS++
- p.conflictingConfigs[normalizedKey] = kb
- p.configBindKeys[normalizedKey] = true
- return false
- } else {
- p.configBindKeys[normalizedKey] = true
- }
-
- if _, exists := p.bindMap[normalizedKey]; !exists {
- p.bindOrder = append(p.bindOrder, key)
- }
- p.bindMap[normalizedKey] = kb
- return true
-}
-
-func (p *HyprlandParser) ParseWithDMS() (*HyprlandSection, error) {
- expandedDir, err := utils.ExpandPath(p.configDir)
- if err != nil {
- return nil, err
- }
-
- dmsBindsPath := filepath.Join(expandedDir, "dms", "binds.conf")
- if _, err := os.Stat(dmsBindsPath); err == nil {
- p.dmsBindsExists = true
- }
-
- mainConfig := filepath.Join(expandedDir, "hyprland.conf")
- section, err := p.parseFileWithSource(mainConfig, "")
- if err != nil {
- return nil, err
- }
-
- if p.dmsBindsExists && !p.dmsProcessed {
- p.parseDMSBindsDirectly(dmsBindsPath, section)
- }
-
- return section, nil
-}
-
-func (p *HyprlandParser) parseFileWithSource(filePath, sectionName string) (*HyprlandSection, error) {
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return nil, err
- }
-
- if p.processedFiles[absPath] {
- return &HyprlandSection{Name: sectionName}, nil
- }
- p.processedFiles[absPath] = true
-
- data, err := os.ReadFile(absPath)
- if err != nil {
- return nil, err
- }
-
- prevSource := p.currentSource
- p.currentSource = absPath
-
- section := &HyprlandSection{Name: sectionName}
- lines := strings.Split(string(data), "\n")
-
- for _, line := range lines {
- trimmed := strings.TrimSpace(line)
-
- if strings.HasPrefix(trimmed, "source") {
- p.handleSource(trimmed, section, filepath.Dir(absPath))
- continue
- }
-
- if !strings.HasPrefix(trimmed, "bind") {
- continue
- }
-
- kb := p.parseBindLine(line)
- if kb == nil {
- continue
- }
- kb.Source = p.currentSource
- if p.addBind(kb) {
- section.Keybinds = append(section.Keybinds, *kb)
- }
- }
-
- p.currentSource = prevSource
- return section, nil
-}
-
-func (p *HyprlandParser) handleSource(line string, section *HyprlandSection, baseDir string) {
- parts := strings.SplitN(line, "=", 2)
- if len(parts) < 2 {
- return
- }
-
- sourcePath := strings.TrimSpace(parts[1])
- isDMSSource := sourcePath == "dms/binds.conf" || strings.HasSuffix(sourcePath, "/dms/binds.conf")
-
- p.includeCount++
- if isDMSSource {
- p.dmsBindsIncluded = true
- p.dmsIncludePos = p.includeCount
- p.dmsProcessed = true
- }
-
- fullPath := sourcePath
- if !filepath.IsAbs(sourcePath) {
- fullPath = filepath.Join(baseDir, sourcePath)
- }
-
- expanded, err := utils.ExpandPath(fullPath)
- if err != nil {
- return
- }
-
- includedSection, err := p.parseFileWithSource(expanded, "")
- if err != nil {
- return
- }
-
- section.Children = append(section.Children, *includedSection)
-}
-
-func (p *HyprlandParser) parseDMSBindsDirectly(dmsBindsPath string, section *HyprlandSection) {
- data, err := os.ReadFile(dmsBindsPath)
- if err != nil {
- return
- }
-
- prevSource := p.currentSource
- p.currentSource = dmsBindsPath
-
- lines := strings.Split(string(data), "\n")
- for _, line := range lines {
- trimmed := strings.TrimSpace(line)
- if !strings.HasPrefix(trimmed, "bind") {
- continue
- }
-
- kb := p.parseBindLine(line)
- if kb == nil {
- continue
- }
- kb.Source = dmsBindsPath
- if p.addBind(kb) {
- section.Keybinds = append(section.Keybinds, *kb)
- }
- }
-
- p.currentSource = prevSource
- p.dmsProcessed = true
-}
-
-func (p *HyprlandParser) parseBindLine(line string) *HyprlandKeyBinding {
- parts := strings.SplitN(line, "=", 2)
- if len(parts) < 2 {
- return nil
- }
-
- // Extract bind type and flags from the left side of "="
- bindType := strings.TrimSpace(parts[0])
- flags := extractBindFlags(bindType)
- hasDescFlag := strings.Contains(flags, "d")
-
- keys := parts[1]
- keyParts := strings.SplitN(keys, "#", 2)
- keys = keyParts[0]
-
- var comment string
- if len(keyParts) > 1 {
- comment = strings.TrimSpace(keyParts[1])
- }
-
- // For bindd, the format is: bindd = MODS, key, description, dispatcher, params
- // For regular binds: bind = MODS, key, dispatcher, params
- var minFields, descIndex, dispatcherIndex int
- if hasDescFlag {
- minFields = 4 // mods, key, description, dispatcher
- descIndex = 2
- dispatcherIndex = 3
- } else {
- minFields = 3 // mods, key, dispatcher
- dispatcherIndex = 2
- }
-
- keyFields := strings.SplitN(keys, ",", minFields+2) // Allow for params
- if len(keyFields) < minFields {
- return nil
- }
-
- mods := strings.TrimSpace(keyFields[0])
- key := strings.TrimSpace(keyFields[1])
-
- var dispatcher, params string
- if hasDescFlag {
- // bindd format: description is in the bind itself
- if comment == "" {
- comment = strings.TrimSpace(keyFields[descIndex])
- }
- dispatcher = strings.TrimSpace(keyFields[dispatcherIndex])
- if len(keyFields) > dispatcherIndex+1 {
- paramParts := keyFields[dispatcherIndex+1:]
- params = strings.TrimSpace(strings.Join(paramParts, ","))
- }
- } else {
- dispatcher = strings.TrimSpace(keyFields[dispatcherIndex])
- if len(keyFields) > dispatcherIndex+1 {
- paramParts := keyFields[dispatcherIndex+1:]
- params = strings.TrimSpace(strings.Join(paramParts, ","))
- }
- }
-
- if comment != "" && strings.HasPrefix(comment, HideComment) {
- return nil
- }
-
- if comment == "" {
- comment = hyprlandAutogenerateComment(dispatcher, params)
- }
-
- var modList []string
- if mods != "" {
- modstring := mods + string(ModSeparators[0])
- idx := 0
- for index, char := range modstring {
- isModSep := false
- for _, sep := range ModSeparators {
- if char == sep {
- isModSep = true
- break
- }
- }
- if isModSep {
- if index-idx > 1 {
- modList = append(modList, modstring[idx:index])
- }
- idx = index + 1
- }
- }
- }
-
- return &HyprlandKeyBinding{
- Mods: modList,
- Key: key,
- Dispatcher: dispatcher,
- Params: params,
- Comment: comment,
- Flags: flags,
- }
-}
-
-// extractBindFlags extracts the flags from a bind type string
-// e.g., "binde" -> "e", "bindel" -> "el", "bindd" -> "d"
-func extractBindFlags(bindType string) string {
- bindType = strings.TrimSpace(bindType)
- if !strings.HasPrefix(bindType, "bind") {
- return ""
- }
- return bindType[4:] // Everything after "bind"
-}
-
-func ParseHyprlandKeysWithDMS(path string) (*HyprlandParseResult, error) {
- parser := NewHyprlandParser(path)
- section, err := parser.ParseWithDMS()
- if err != nil {
- return nil, err
- }
-
- return &HyprlandParseResult{
- Section: section,
- DMSBindsIncluded: parser.dmsBindsIncluded,
- DMSStatus: parser.buildDMSStatus(),
- ConflictingConfigs: parser.conflictingConfigs,
- }, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser_test.go
deleted file mode 100644
index c2872e5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_parser_test.go
+++ /dev/null
@@ -1,519 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestHyprlandAutogenerateComment(t *testing.T) {
- tests := []struct {
- dispatcher string
- params string
- expected string
- }{
- {"resizewindow", "", "Resize window"},
- {"movewindow", "", "Move window"},
- {"movewindow", "l", "move in left direction"},
- {"movewindow", "r", "move in right direction"},
- {"movewindow", "u", "move in up direction"},
- {"movewindow", "d", "move in down direction"},
- {"pin", "", "pin (show on all workspaces)"},
- {"splitratio", "0.5", "Window split ratio 0.5"},
- {"togglefloating", "", "Float/unfloat window"},
- {"resizeactive", "10 20", "Resize window by 10 20"},
- {"killactive", "", "Close window"},
- {"fullscreen", "0", "Toggle fullscreen"},
- {"fullscreen", "1", "Toggle maximization"},
- {"fullscreen", "2", "Toggle fullscreen on Hyprland's side"},
- {"fakefullscreen", "", "Toggle fake fullscreen"},
- {"workspace", "+1", "focus right"},
- {"workspace", "-1", "focus left"},
- {"workspace", "5", "focus workspace 5"},
- {"movefocus", "l", "move focus left"},
- {"movefocus", "r", "move focus right"},
- {"movefocus", "u", "move focus up"},
- {"movefocus", "d", "move focus down"},
- {"swapwindow", "l", "swap in left direction"},
- {"swapwindow", "r", "swap in right direction"},
- {"swapwindow", "u", "swap in up direction"},
- {"swapwindow", "d", "swap in down direction"},
- {"movetoworkspace", "+1", "move to right workspace (non-silent)"},
- {"movetoworkspace", "-1", "move to left workspace (non-silent)"},
- {"movetoworkspace", "3", "move to workspace 3 (non-silent)"},
- {"movetoworkspacesilent", "+1", "move to right workspace"},
- {"movetoworkspacesilent", "-1", "move to right workspace"},
- {"movetoworkspacesilent", "2", "move to workspace 2"},
- {"togglespecialworkspace", "", "toggle special"},
- {"exec", "firefox", "firefox"},
- {"unknown", "", ""},
- }
-
- for _, tt := range tests {
- t.Run(tt.dispatcher+"_"+tt.params, func(t *testing.T) {
- result := hyprlandAutogenerateComment(tt.dispatcher, tt.params)
- if result != tt.expected {
- t.Errorf("hyprlandAutogenerateComment(%q, %q) = %q, want %q",
- tt.dispatcher, tt.params, result, tt.expected)
- }
- })
- }
-}
-
-func TestHyprlandGetKeybindAtLine(t *testing.T) {
- tests := []struct {
- name string
- line string
- expected *HyprlandKeyBinding
- }{
- {
- name: "basic_keybind",
- line: "bind = SUPER, Q, killactive",
- expected: &HyprlandKeyBinding{
- Mods: []string{"SUPER"},
- Key: "Q",
- Dispatcher: "killactive",
- Params: "",
- Comment: "Close window",
- },
- },
- {
- name: "keybind_with_params",
- line: "bind = SUPER, left, movefocus, l",
- expected: &HyprlandKeyBinding{
- Mods: []string{"SUPER"},
- Key: "left",
- Dispatcher: "movefocus",
- Params: "l",
- Comment: "move focus left",
- },
- },
- {
- name: "keybind_with_comment",
- line: "bind = SUPER, T, exec, kitty # Open terminal",
- expected: &HyprlandKeyBinding{
- Mods: []string{"SUPER"},
- Key: "T",
- Dispatcher: "exec",
- Params: "kitty",
- Comment: "Open terminal",
- },
- },
- {
- name: "keybind_hidden",
- line: "bind = SUPER, H, exec, secret # [hidden]",
- expected: nil,
- },
- {
- name: "keybind_multiple_mods",
- line: "bind = SUPER+SHIFT, F, fullscreen, 0",
- expected: &HyprlandKeyBinding{
- Mods: []string{"SUPER", "SHIFT"},
- Key: "F",
- Dispatcher: "fullscreen",
- Params: "0",
- Comment: "Toggle fullscreen",
- },
- },
- {
- name: "keybind_no_mods",
- line: "bind = , Print, exec, screenshot",
- expected: &HyprlandKeyBinding{
- Mods: []string{},
- Key: "Print",
- Dispatcher: "exec",
- Params: "screenshot",
- Comment: "screenshot",
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- parser := NewHyprlandParser("")
- parser.contentLines = []string{tt.line}
- result := parser.getKeybindAtLine(0)
-
- if tt.expected == nil {
- if result != nil {
- t.Errorf("expected nil, got %+v", result)
- }
- return
- }
-
- if result == nil {
- t.Errorf("expected %+v, got nil", tt.expected)
- return
- }
-
- if result.Key != tt.expected.Key {
- t.Errorf("Key = %q, want %q", result.Key, tt.expected.Key)
- }
- if result.Dispatcher != tt.expected.Dispatcher {
- t.Errorf("Dispatcher = %q, want %q", result.Dispatcher, tt.expected.Dispatcher)
- }
- if result.Params != tt.expected.Params {
- t.Errorf("Params = %q, want %q", result.Params, tt.expected.Params)
- }
- if result.Comment != tt.expected.Comment {
- t.Errorf("Comment = %q, want %q", result.Comment, tt.expected.Comment)
- }
- if len(result.Mods) != len(tt.expected.Mods) {
- t.Errorf("Mods length = %d, want %d", len(result.Mods), len(tt.expected.Mods))
- } else {
- for i := range result.Mods {
- if result.Mods[i] != tt.expected.Mods[i] {
- t.Errorf("Mods[%d] = %q, want %q", i, result.Mods[i], tt.expected.Mods[i])
- }
- }
- }
- })
- }
-}
-
-func TestHyprlandParseKeysWithSections(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "hyprland.conf")
-
- content := `##! Window Management
-bind = SUPER, Q, killactive
-bind = SUPER, F, fullscreen, 0
-
-###! Movement
-bind = SUPER, left, movefocus, l
-bind = SUPER, right, movefocus, r
-
-##! Applications
-bind = SUPER, T, exec, kitty # Terminal
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseHyprlandKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseHyprlandKeys failed: %v", err)
- }
-
- if len(section.Children) != 2 {
- t.Errorf("Expected 2 top-level sections, got %d", len(section.Children))
- }
-
- if len(section.Children) >= 1 {
- windowMgmt := section.Children[0]
- if windowMgmt.Name != "Window Management" {
- t.Errorf("First section name = %q, want %q", windowMgmt.Name, "Window Management")
- }
- if len(windowMgmt.Keybinds) != 2 {
- t.Errorf("Window Management keybinds = %d, want 2", len(windowMgmt.Keybinds))
- }
-
- if len(windowMgmt.Children) != 1 {
- t.Errorf("Window Management children = %d, want 1", len(windowMgmt.Children))
- } else {
- movement := windowMgmt.Children[0]
- if movement.Name != "Movement" {
- t.Errorf("Movement section name = %q, want %q", movement.Name, "Movement")
- }
- if len(movement.Keybinds) != 2 {
- t.Errorf("Movement keybinds = %d, want 2", len(movement.Keybinds))
- }
- }
- }
-
- if len(section.Children) >= 2 {
- apps := section.Children[1]
- if apps.Name != "Applications" {
- t.Errorf("Second section name = %q, want %q", apps.Name, "Applications")
- }
- if len(apps.Keybinds) != 1 {
- t.Errorf("Applications keybinds = %d, want 1", len(apps.Keybinds))
- }
- if len(apps.Keybinds) > 0 && apps.Keybinds[0].Comment != "Terminal" {
- t.Errorf("Applications keybind comment = %q, want %q", apps.Keybinds[0].Comment, "Terminal")
- }
- }
-}
-
-func TestHyprlandParseKeysWithCommentBinds(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "test.conf")
-
- content := `#/# = SUPER, A, exec, app1
-bind = SUPER, B, exec, app2
-#/# = SUPER, C, exec, app3 # Custom comment
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseHyprlandKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseHyprlandKeys failed: %v", err)
- }
-
- if len(section.Keybinds) != 3 {
- t.Errorf("Expected 3 keybinds, got %d", len(section.Keybinds))
- }
-
- if len(section.Keybinds) > 0 && section.Keybinds[0].Key != "A" {
- t.Errorf("First keybind key = %q, want %q", section.Keybinds[0].Key, "A")
- }
- if len(section.Keybinds) > 1 && section.Keybinds[1].Key != "B" {
- t.Errorf("Second keybind key = %q, want %q", section.Keybinds[1].Key, "B")
- }
- if len(section.Keybinds) > 2 && section.Keybinds[2].Comment != "Custom comment" {
- t.Errorf("Third keybind comment = %q, want %q", section.Keybinds[2].Comment, "Custom comment")
- }
-}
-
-func TestHyprlandReadContentMultipleFiles(t *testing.T) {
- tmpDir := t.TempDir()
-
- file1 := filepath.Join(tmpDir, "a.conf")
- file2 := filepath.Join(tmpDir, "b.conf")
-
- content1 := "bind = SUPER, Q, killactive\n"
- content2 := "bind = SUPER, T, exec, kitty\n"
-
- if err := os.WriteFile(file1, []byte(content1), 0o644); err != nil {
- t.Fatalf("Failed to write file1: %v", err)
- }
- if err := os.WriteFile(file2, []byte(content2), 0o644); err != nil {
- t.Fatalf("Failed to write file2: %v", err)
- }
-
- parser := NewHyprlandParser("")
- if err := parser.ReadContent(tmpDir); err != nil {
- t.Fatalf("ReadContent failed: %v", err)
- }
-
- section := parser.ParseKeys()
- if len(section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds from multiple files, got %d", len(section.Keybinds))
- }
-}
-
-func TestHyprlandReadContentErrors(t *testing.T) {
- tests := []struct {
- name string
- path string
- }{
- {
- name: "nonexistent_directory",
- path: "/nonexistent/path/that/does/not/exist",
- },
- {
- name: "empty_directory",
- path: t.TempDir(),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := ParseHyprlandKeys(tt.path)
- if err == nil {
- t.Error("Expected error, got nil")
- }
- })
- }
-}
-
-func TestHyprlandReadContentWithTildeExpansion(t *testing.T) {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- t.Skip("Cannot get home directory")
- }
-
- tmpSubdir := filepath.Join(homeDir, ".config", "test-hypr-"+t.Name())
- if err := os.MkdirAll(tmpSubdir, 0o755); err != nil {
- t.Skip("Cannot create test directory in home")
- }
- defer os.RemoveAll(tmpSubdir)
-
- configFile := filepath.Join(tmpSubdir, "test.conf")
- if err := os.WriteFile(configFile, []byte("bind = SUPER, Q, killactive\n"), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- relPath, err := filepath.Rel(homeDir, tmpSubdir)
- if err != nil {
- t.Skip("Cannot create relative path")
- }
-
- parser := NewHyprlandParser("")
- tildePathMatch := "~/" + relPath
- err = parser.ReadContent(tildePathMatch)
-
- if err != nil {
- t.Errorf("ReadContent with tilde path failed: %v", err)
- }
-}
-
-func TestHyprlandKeybindWithParamsContainingCommas(t *testing.T) {
- parser := NewHyprlandParser("")
- parser.contentLines = []string{"bind = SUPER, R, exec, notify-send 'Title' 'Message, with comma'"}
-
- result := parser.getKeybindAtLine(0)
-
- if result == nil {
- t.Fatal("Expected keybind, got nil")
- }
-
- expected := "notify-send 'Title' 'Message, with comma'"
- if result.Params != expected {
- t.Errorf("Params = %q, want %q", result.Params, expected)
- }
-}
-
-func TestHyprlandEmptyAndCommentLines(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "test.conf")
-
- content := `
-# This is a comment
-bind = SUPER, Q, killactive
-
-# Another comment
-
-bind = SUPER, T, exec, kitty
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseHyprlandKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseHyprlandKeys failed: %v", err)
- }
-
- if len(section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds (comments ignored), got %d", len(section.Keybinds))
- }
-}
-
-func TestExtractBindFlags(t *testing.T) {
- tests := []struct {
- bindType string
- expected string
- }{
- {"bind", ""},
- {"binde", "e"},
- {"bindl", "l"},
- {"bindr", "r"},
- {"bindd", "d"},
- {"bindo", "o"},
- {"bindel", "el"},
- {"bindler", "ler"},
- {"bindem", "em"},
- {" bind ", ""},
- {" binde ", "e"},
- {"notbind", ""},
- {"", ""},
- }
-
- for _, tt := range tests {
- t.Run(tt.bindType, func(t *testing.T) {
- result := extractBindFlags(tt.bindType)
- if result != tt.expected {
- t.Errorf("extractBindFlags(%q) = %q, want %q", tt.bindType, result, tt.expected)
- }
- })
- }
-}
-
-func TestHyprlandBindFlags(t *testing.T) {
- tests := []struct {
- name string
- line string
- expectedFlags string
- expectedKey string
- expectedDisp string
- expectedDesc string
- }{
- {
- name: "regular bind",
- line: "bind = SUPER, Q, killactive",
- expectedFlags: "",
- expectedKey: "Q",
- expectedDisp: "killactive",
- expectedDesc: "Close window",
- },
- {
- name: "binde (repeat on hold)",
- line: "binde = , XF86AudioRaiseVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+",
- expectedFlags: "e",
- expectedKey: "XF86AudioRaiseVolume",
- expectedDisp: "exec",
- expectedDesc: "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+",
- },
- {
- name: "bindl (locked/inhibitor bypass)",
- line: "bindl = , XF86AudioLowerVolume, exec, wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-",
- expectedFlags: "l",
- expectedKey: "XF86AudioLowerVolume",
- expectedDisp: "exec",
- expectedDesc: "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-",
- },
- {
- name: "bindr (release trigger)",
- line: "bindr = SUPER, SUPER_L, exec, pkill wofi || wofi",
- expectedFlags: "r",
- expectedKey: "SUPER_L",
- expectedDisp: "exec",
- expectedDesc: "pkill wofi || wofi",
- },
- {
- name: "bindd (description)",
- line: "bindd = SUPER, Q, Open my favourite terminal, exec, kitty",
- expectedFlags: "d",
- expectedKey: "Q",
- expectedDisp: "exec",
- expectedDesc: "Open my favourite terminal",
- },
- {
- name: "bindo (long press)",
- line: "bindo = SUPER, XF86AudioNext, exec, playerctl next",
- expectedFlags: "o",
- expectedKey: "XF86AudioNext",
- expectedDisp: "exec",
- expectedDesc: "playerctl next",
- },
- {
- name: "bindel (combined flags)",
- line: "bindel = , XF86AudioRaiseVolume, exec, wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+",
- expectedFlags: "el",
- expectedKey: "XF86AudioRaiseVolume",
- expectedDisp: "exec",
- expectedDesc: "wpctl set-volume -l 1.5 @DEFAULT_AUDIO_SINK@ 5%+",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- parser := NewHyprlandParser("")
- parser.contentLines = []string{tt.line}
- result := parser.getKeybindAtLine(0)
-
- if result == nil {
- t.Fatal("Expected keybind, got nil")
- }
-
- if result.Flags != tt.expectedFlags {
- t.Errorf("Flags = %q, want %q", result.Flags, tt.expectedFlags)
- }
- if result.Key != tt.expectedKey {
- t.Errorf("Key = %q, want %q", result.Key, tt.expectedKey)
- }
- if result.Dispatcher != tt.expectedDisp {
- t.Errorf("Dispatcher = %q, want %q", result.Dispatcher, tt.expectedDisp)
- }
- if result.Comment != tt.expectedDesc {
- t.Errorf("Comment = %q, want %q", result.Comment, tt.expectedDesc)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_test.go
deleted file mode 100644
index 90dfe97..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/hyprland_test.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestNewHyprlandProvider(t *testing.T) {
- t.Run("custom path", func(t *testing.T) {
- p := NewHyprlandProvider("/custom/path")
- if p == nil {
- t.Fatal("NewHyprlandProvider returned nil")
- }
- if p.configPath != "/custom/path" {
- t.Errorf("configPath = %q, want %q", p.configPath, "/custom/path")
- }
- })
-
- t.Run("empty path defaults", func(t *testing.T) {
- p := NewHyprlandProvider("")
- if p == nil {
- t.Fatal("NewHyprlandProvider returned nil")
- }
- configDir, err := os.UserConfigDir()
- if err != nil {
- t.Fatalf("UserConfigDir failed: %v", err)
- }
- expected := filepath.Join(configDir, "hypr")
- if p.configPath != expected {
- t.Errorf("configPath = %q, want %q", p.configPath, expected)
- }
- })
-}
-
-func TestHyprlandProviderName(t *testing.T) {
- p := NewHyprlandProvider("")
- if p.Name() != "hyprland" {
- t.Errorf("Name() = %q, want %q", p.Name(), "hyprland")
- }
-}
-
-func TestHyprlandProviderGetCheatSheet(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "hyprland.conf")
-
- content := `##! Window Management
-bind = SUPER, Q, killactive
-bind = SUPER, F, fullscreen, 0
-
-###! Movement
-bind = SUPER, left, movefocus, l
-bind = SUPER, right, movefocus, r
-
-##! Applications
-bind = SUPER, T, exec, kitty # Terminal
-bind = SUPER, 1, workspace, 1
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- p := NewHyprlandProvider(tmpDir)
- sheet, err := p.GetCheatSheet()
-
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- if sheet.Title != "Hyprland Keybinds" {
- t.Errorf("Title = %q, want %q", sheet.Title, "Hyprland Keybinds")
- }
-
- if sheet.Provider != "hyprland" {
- t.Errorf("Provider = %q, want %q", sheet.Provider, "hyprland")
- }
-
- if len(sheet.Binds) == 0 {
- t.Error("expected categorized bindings, got none")
- }
-
- if windowBinds, ok := sheet.Binds["Window"]; !ok || len(windowBinds) == 0 {
- t.Error("expected Window category with bindings")
- }
-
- if execBinds, ok := sheet.Binds["Execute"]; !ok || len(execBinds) == 0 {
- t.Error("expected Execute category with bindings")
- }
-
- if wsBinds, ok := sheet.Binds["Workspace"]; !ok || len(wsBinds) == 0 {
- t.Error("expected Workspace category with bindings")
- }
-}
-
-func TestHyprlandProviderGetCheatSheetError(t *testing.T) {
- p := NewHyprlandProvider("/nonexistent/path")
- _, err := p.GetCheatSheet()
-
- if err == nil {
- t.Error("expected error for nonexistent path, got nil")
- }
-}
-
-func TestFormatKey(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "hyprland.conf")
-
- tests := []struct {
- name string
- content string
- expected string
- category string
- }{
- {
- name: "single mod",
- content: "bind = SUPER, Q, killactive",
- expected: "SUPER+Q",
- category: "Window",
- },
- {
- name: "multiple mods",
- content: "bind = SUPER+SHIFT, F, fullscreen, 0",
- expected: "SUPER+SHIFT+F",
- category: "Window",
- },
- {
- name: "no mods",
- content: "bind = , Print, exec, screenshot",
- expected: "Print",
- category: "Execute",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := os.WriteFile(configFile, []byte(tt.content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- p := NewHyprlandProvider(tmpDir)
- sheet, err := p.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- categoryBinds, ok := sheet.Binds[tt.category]
- if !ok || len(categoryBinds) == 0 {
- t.Fatalf("expected binds in category %q", tt.category)
- }
-
- if categoryBinds[0].Key != tt.expected {
- t.Errorf("Key = %q, want %q", categoryBinds[0].Key, tt.expected)
- }
- })
- }
-}
-
-func TestDescriptionFallback(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "hyprland.conf")
-
- tests := []struct {
- name string
- content string
- wantDesc string
- }{
- {
- name: "autogenerated description for known dispatcher",
- content: "bind = SUPER, Q, killactive",
- wantDesc: "Close window",
- },
- {
- name: "custom comment overrides autogeneration",
- content: "bind = SUPER, T, exec, kitty # Open terminal",
- wantDesc: "Open terminal",
- },
- {
- name: "fallback for unknown dispatcher without params",
- content: "bind = SUPER, W, unknowndispatcher",
- wantDesc: "unknowndispatcher",
- },
- {
- name: "fallback for unknown dispatcher with params",
- content: "bind = SUPER, X, customdispatcher, arg1",
- wantDesc: "customdispatcher arg1",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if err := os.WriteFile(configFile, []byte(tt.content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- p := NewHyprlandProvider(tmpDir)
- sheet, err := p.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- found := false
- for _, binds := range sheet.Binds {
- for _, bind := range binds {
- if bind.Description == tt.wantDesc {
- found = true
- break
- }
- }
- if found {
- break
- }
- }
-
- if !found {
- t.Errorf("expected description %q not found in any bind", tt.wantDesc)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile.go
deleted file mode 100644
index 08cf449..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package providers
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-type JSONFileProvider struct {
- filePath string
- name string
-}
-
-func NewJSONFileProvider(filePath string) (*JSONFileProvider, error) {
- if filePath == "" {
- return nil, fmt.Errorf("file path cannot be empty")
- }
-
- expandedPath, err := utils.ExpandPath(filePath)
- if err != nil {
- return nil, fmt.Errorf("failed to expand path: %w", err)
- }
-
- name := filepath.Base(expandedPath)
- name = name[:len(name)-len(filepath.Ext(name))]
-
- return &JSONFileProvider{
- filePath: expandedPath,
- name: name,
- }, nil
-}
-
-func (j *JSONFileProvider) Name() string {
- return j.name
-}
-
-func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
- data, err := os.ReadFile(j.filePath)
- if err != nil {
- return nil, fmt.Errorf("failed to read file: %w", err)
- }
-
- var rawData map[string]any
- if err := json.Unmarshal(data, &rawData); err != nil {
- return nil, fmt.Errorf("failed to parse JSON: %w", err)
- }
-
- title, _ := rawData["title"].(string)
- provider, _ := rawData["provider"].(string)
- if provider == "" {
- provider = j.name
- }
-
- categorizedBinds := make(map[string][]keybinds.Keybind)
-
- bindsRaw, ok := rawData["binds"]
- if !ok {
- return nil, fmt.Errorf("missing 'binds' field")
- }
-
- switch binds := bindsRaw.(type) {
- case map[string]any:
- for category, categoryBindsRaw := range binds {
- categoryBindsList, ok := categoryBindsRaw.([]any)
- if !ok {
- continue
- }
-
- var keybindsList []keybinds.Keybind
- categoryBindsJSON, _ := json.Marshal(categoryBindsList)
- if err := json.Unmarshal(categoryBindsJSON, &keybindsList); err != nil {
- continue
- }
-
- categorizedBinds[category] = keybindsList
- }
-
- case []any:
- flatBindsJSON, _ := json.Marshal(binds)
- var flatBinds []struct {
- Key string `json:"key"`
- Description string `json:"desc"`
- Action string `json:"action,omitempty"`
- Category string `json:"cat,omitempty"`
- Subcategory string `json:"subcat,omitempty"`
- }
- if err := json.Unmarshal(flatBindsJSON, &flatBinds); err != nil {
- return nil, fmt.Errorf("failed to parse flat binds array: %w", err)
- }
-
- for _, bind := range flatBinds {
- category := bind.Category
- if category == "" {
- category = "Other"
- }
-
- kb := keybinds.Keybind{
- Key: bind.Key,
- Description: bind.Description,
- Action: bind.Action,
- Subcategory: bind.Subcategory,
- }
- categorizedBinds[category] = append(categorizedBinds[category], kb)
- }
-
- default:
- return nil, fmt.Errorf("'binds' must be either an object (categorized) or array (flat)")
- }
-
- return &keybinds.CheatSheet{
- Title: title,
- Provider: provider,
- Binds: categorizedBinds,
- }, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile_test.go
deleted file mode 100644
index ded4385..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/jsonfile_test.go
+++ /dev/null
@@ -1,281 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-func TestNewJSONFileProvider(t *testing.T) {
- tmpDir := t.TempDir()
- testFile := filepath.Join(tmpDir, "test.json")
-
- if err := os.WriteFile(testFile, []byte("{}"), 0o644); err != nil {
- t.Fatalf("Failed to create test file: %v", err)
- }
-
- tests := []struct {
- name string
- filePath string
- expectError bool
- wantName string
- }{
- {
- name: "valid file",
- filePath: testFile,
- expectError: false,
- wantName: "test",
- },
- {
- name: "empty path",
- filePath: "",
- expectError: true,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- p, err := NewJSONFileProvider(tt.filePath)
-
- if tt.expectError {
- if err == nil {
- t.Error("expected error, got nil")
- }
- return
- }
-
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- return
- }
-
- if p.Name() != tt.wantName {
- t.Errorf("Name() = %q, want %q", p.Name(), tt.wantName)
- }
- })
- }
-}
-
-func TestJSONFileProviderGetCheatSheet(t *testing.T) {
- tmpDir := t.TempDir()
- testFile := filepath.Join(tmpDir, "tmux.json")
-
- content := `{
- "title": "Tmux Binds",
- "provider": "tmux",
- "binds": {
- "Pane": [
- {
- "key": "Ctrl+Alt+J",
- "desc": "Resize split downward",
- "subcat": "Sizing"
- },
- {
- "key": "Ctrl+K",
- "desc": "Move Focus Up",
- "subcat": "Navigation"
- }
- ]
- }
-}`
-
- if err := os.WriteFile(testFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test file: %v", err)
- }
-
- p, err := NewJSONFileProvider(testFile)
- if err != nil {
- t.Fatalf("NewJSONFileProvider failed: %v", err)
- }
-
- sheet, err := p.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- if sheet.Title != "Tmux Binds" {
- t.Errorf("Title = %q, want %q", sheet.Title, "Tmux Binds")
- }
-
- if sheet.Provider != "tmux" {
- t.Errorf("Provider = %q, want %q", sheet.Provider, "tmux")
- }
-
- paneBinds, ok := sheet.Binds["Pane"]
- if !ok {
- t.Fatal("expected Pane category")
- }
-
- if len(paneBinds) != 2 {
- t.Errorf("len(Pane binds) = %d, want 2", len(paneBinds))
- }
-
- if len(paneBinds) > 0 {
- bind := paneBinds[0]
- if bind.Key != "Ctrl+Alt+J" {
- t.Errorf("Pane[0].Key = %q, want %q", bind.Key, "Ctrl+Alt+J")
- }
- if bind.Description != "Resize split downward" {
- t.Errorf("Pane[0].Description = %q, want %q", bind.Description, "Resize split downward")
- }
- if bind.Subcategory != "Sizing" {
- t.Errorf("Pane[0].Subcategory = %q, want %q", bind.Subcategory, "Sizing")
- }
- }
-}
-
-func TestJSONFileProviderGetCheatSheetNoProvider(t *testing.T) {
- tmpDir := t.TempDir()
- testFile := filepath.Join(tmpDir, "custom.json")
-
- content := `{
- "title": "Custom Binds",
- "binds": {}
-}`
-
- if err := os.WriteFile(testFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test file: %v", err)
- }
-
- p, err := NewJSONFileProvider(testFile)
- if err != nil {
- t.Fatalf("NewJSONFileProvider failed: %v", err)
- }
-
- sheet, err := p.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- if sheet.Provider != "custom" {
- t.Errorf("Provider = %q, want %q (should default to filename)", sheet.Provider, "custom")
- }
-}
-
-func TestJSONFileProviderFlatArrayBackwardsCompat(t *testing.T) {
- tmpDir := t.TempDir()
- testFile := filepath.Join(tmpDir, "legacy.json")
-
- content := `{
- "title": "Legacy Format",
- "provider": "legacy",
- "binds": [
- {
- "key": "Ctrl+S",
- "desc": "Save file",
- "cat": "File",
- "subcat": "Operations"
- },
- {
- "key": "Ctrl+O",
- "desc": "Open file",
- "cat": "File"
- },
- {
- "key": "Ctrl+Q",
- "desc": "Quit",
- "subcat": "Exit"
- }
- ]
-}`
-
- if err := os.WriteFile(testFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test file: %v", err)
- }
-
- p, err := NewJSONFileProvider(testFile)
- if err != nil {
- t.Fatalf("NewJSONFileProvider failed: %v", err)
- }
-
- sheet, err := p.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- fileBinds, ok := sheet.Binds["File"]
- if !ok || len(fileBinds) != 2 {
- t.Errorf("expected 2 binds in File category, got %d", len(fileBinds))
- }
-
- otherBinds, ok := sheet.Binds["Other"]
- if !ok || len(otherBinds) != 1 {
- t.Errorf("expected 1 bind in Other category (no cat specified), got %d", len(otherBinds))
- }
-
- if len(fileBinds) > 0 {
- if fileBinds[0].Subcategory != "Operations" {
- t.Errorf("expected subcategory %q, got %q", "Operations", fileBinds[0].Subcategory)
- }
- }
-}
-
-func TestJSONFileProviderInvalidJSON(t *testing.T) {
- tmpDir := t.TempDir()
- testFile := filepath.Join(tmpDir, "invalid.json")
-
- if err := os.WriteFile(testFile, []byte("not valid json"), 0o644); err != nil {
- t.Fatalf("Failed to write test file: %v", err)
- }
-
- p, err := NewJSONFileProvider(testFile)
- if err != nil {
- t.Fatalf("NewJSONFileProvider failed: %v", err)
- }
-
- _, err = p.GetCheatSheet()
- if err == nil {
- t.Error("expected error for invalid JSON, got nil")
- }
-}
-
-func TestJSONFileProviderNonexistentFile(t *testing.T) {
- p, err := NewJSONFileProvider("/nonexistent/file.json")
- if err != nil {
- t.Fatalf("NewJSONFileProvider failed: %v", err)
- }
-
- _, err = p.GetCheatSheet()
- if err == nil {
- t.Error("expected error for nonexistent file, got nil")
- }
-}
-
-func TestExpandPath(t *testing.T) {
- home, err := os.UserHomeDir()
- if err != nil {
- t.Skip("Cannot get home directory")
- }
-
- tests := []struct {
- name string
- input string
- expected string
- }{
- {
- name: "tilde expansion",
- input: "~/test",
- expected: filepath.Join(home, "test"),
- },
- {
- name: "no expansion needed",
- input: "/absolute/path",
- expected: "/absolute/path",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result, err := utils.ExpandPath(tt.input)
- if err != nil {
- t.Fatalf("expandPath failed: %v", err)
- }
-
- if result != tt.expected {
- t.Errorf("utils.ExpandPath(%q) = %q, want %q", tt.input, result, tt.expected)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc.go
deleted file mode 100644
index 348ee7c..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc.go
+++ /dev/null
@@ -1,442 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "sort"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-type MangoWCProvider struct {
- configPath string
- dmsBindsIncluded bool
- parsed bool
-}
-
-func NewMangoWCProvider(configPath string) *MangoWCProvider {
- if configPath == "" {
- configPath = defaultMangoWCConfigDir()
- }
- return &MangoWCProvider{
- configPath: configPath,
- }
-}
-
-func defaultMangoWCConfigDir() string {
- configDir, err := os.UserConfigDir()
- if err != nil {
- return ""
- }
- return filepath.Join(configDir, "mango")
-}
-
-func (m *MangoWCProvider) Name() string {
- return "mangowc"
-}
-
-func (m *MangoWCProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
- result, err := ParseMangoWCKeysWithDMS(m.configPath)
- if err != nil {
- return nil, fmt.Errorf("failed to parse mangowc config: %w", err)
- }
-
- m.dmsBindsIncluded = result.DMSBindsIncluded
- m.parsed = true
-
- categorizedBinds := make(map[string][]keybinds.Keybind)
- for _, kb := range result.Keybinds {
- category := m.categorizeByCommand(kb.Command)
- bind := m.convertKeybind(&kb, result.ConflictingConfigs)
- categorizedBinds[category] = append(categorizedBinds[category], bind)
- }
-
- sheet := &keybinds.CheatSheet{
- Title: "MangoWC Keybinds",
- Provider: m.Name(),
- Binds: categorizedBinds,
- DMSBindsIncluded: result.DMSBindsIncluded,
- }
-
- if result.DMSStatus != nil {
- sheet.DMSStatus = &keybinds.DMSBindsStatus{
- Exists: result.DMSStatus.Exists,
- Included: result.DMSStatus.Included,
- IncludePosition: result.DMSStatus.IncludePosition,
- TotalIncludes: result.DMSStatus.TotalIncludes,
- BindsAfterDMS: result.DMSStatus.BindsAfterDMS,
- Effective: result.DMSStatus.Effective,
- OverriddenBy: result.DMSStatus.OverriddenBy,
- StatusMessage: result.DMSStatus.StatusMessage,
- }
- }
-
- return sheet, nil
-}
-
-func (m *MangoWCProvider) HasDMSBindsIncluded() bool {
- if m.parsed {
- return m.dmsBindsIncluded
- }
-
- result, err := ParseMangoWCKeysWithDMS(m.configPath)
- if err != nil {
- return false
- }
-
- m.dmsBindsIncluded = result.DMSBindsIncluded
- m.parsed = true
- return m.dmsBindsIncluded
-}
-
-func (m *MangoWCProvider) categorizeByCommand(command string) string {
- switch {
- case strings.Contains(command, "mon"):
- return "Monitor"
- case command == "toggleoverview":
- return "Overview"
- case command == "toggle_scratchpad":
- return "Scratchpad"
- case strings.Contains(command, "layout") || strings.Contains(command, "proportion"):
- return "Layout"
- case strings.Contains(command, "gaps"):
- return "Gaps"
- case strings.Contains(command, "view") || strings.Contains(command, "tag"):
- return "Tags"
- case command == "focusstack" ||
- command == "focusdir" ||
- command == "exchange_client" ||
- command == "killclient" ||
- command == "togglefloating" ||
- command == "togglefullscreen" ||
- command == "togglefakefullscreen" ||
- command == "togglemaximizescreen" ||
- command == "toggleglobal" ||
- command == "toggleoverlay" ||
- command == "minimized" ||
- command == "restore_minimized" ||
- command == "movewin" ||
- command == "resizewin":
- return "Window"
- case command == "spawn" || command == "spawn_shell":
- return "Execute"
- case command == "quit" || command == "reload_config":
- return "System"
- default:
- return "Other"
- }
-}
-
-func (m *MangoWCProvider) convertKeybind(kb *MangoWCKeyBinding, conflicts map[string]*MangoWCKeyBinding) keybinds.Keybind {
- keyStr := m.formatKey(kb)
- rawAction := m.formatRawAction(kb.Command, kb.Params)
- desc := kb.Comment
-
- if desc == "" {
- desc = rawAction
- }
-
- source := "config"
- if strings.Contains(kb.Source, "dms/binds.conf") || strings.Contains(kb.Source, "dms"+string(filepath.Separator)+"binds.conf") {
- source = "dms"
- }
-
- bind := keybinds.Keybind{
- Key: keyStr,
- Description: desc,
- Action: rawAction,
- Source: source,
- }
-
- if source == "dms" && conflicts != nil {
- normalizedKey := strings.ToLower(keyStr)
- if conflictKb, ok := conflicts[normalizedKey]; ok {
- bind.Conflict = &keybinds.Keybind{
- Key: keyStr,
- Description: conflictKb.Comment,
- Action: m.formatRawAction(conflictKb.Command, conflictKb.Params),
- Source: "config",
- }
- }
- }
-
- return bind
-}
-
-func (m *MangoWCProvider) formatRawAction(command, params string) string {
- if params != "" {
- return command + " " + params
- }
- return command
-}
-
-func (m *MangoWCProvider) formatKey(kb *MangoWCKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (m *MangoWCProvider) GetOverridePath() string {
- expanded, err := utils.ExpandPath(m.configPath)
- if err != nil {
- return filepath.Join(m.configPath, "dms", "binds.conf")
- }
- return filepath.Join(expanded, "dms", "binds.conf")
-}
-
-func (m *MangoWCProvider) validateAction(action string) error {
- action = strings.TrimSpace(action)
- switch {
- case action == "":
- return fmt.Errorf("action cannot be empty")
- case action == "spawn" || action == "spawn ":
- return fmt.Errorf("spawn command requires arguments")
- case action == "spawn_shell" || action == "spawn_shell ":
- return fmt.Errorf("spawn_shell command requires arguments")
- case strings.HasPrefix(action, "spawn "):
- rest := strings.TrimSpace(strings.TrimPrefix(action, "spawn "))
- if rest == "" {
- return fmt.Errorf("spawn command requires arguments")
- }
- case strings.HasPrefix(action, "spawn_shell "):
- rest := strings.TrimSpace(strings.TrimPrefix(action, "spawn_shell "))
- if rest == "" {
- return fmt.Errorf("spawn_shell command requires arguments")
- }
- }
- return nil
-}
-
-func (m *MangoWCProvider) SetBind(key, action, description string, options map[string]any) error {
- if err := m.validateAction(action); err != nil {
- return err
- }
-
- overridePath := m.GetOverridePath()
-
- if err := os.MkdirAll(filepath.Dir(overridePath), 0o755); err != nil {
- return fmt.Errorf("failed to create dms directory: %w", err)
- }
-
- existingBinds, err := m.loadOverrideBinds()
- if err != nil {
- existingBinds = make(map[string]*mangowcOverrideBind)
- }
-
- normalizedKey := strings.ToLower(key)
- existingBinds[normalizedKey] = &mangowcOverrideBind{
- Key: key,
- Action: action,
- Description: description,
- Options: options,
- }
-
- return m.writeOverrideBinds(existingBinds)
-}
-
-func (m *MangoWCProvider) RemoveBind(key string) error {
- existingBinds, err := m.loadOverrideBinds()
- if err != nil {
- return nil
- }
-
- normalizedKey := strings.ToLower(key)
- delete(existingBinds, normalizedKey)
- return m.writeOverrideBinds(existingBinds)
-}
-
-type mangowcOverrideBind struct {
- Key string
- Action string
- Description string
- Options map[string]any
-}
-
-func (m *MangoWCProvider) loadOverrideBinds() (map[string]*mangowcOverrideBind, error) {
- overridePath := m.GetOverridePath()
- binds := make(map[string]*mangowcOverrideBind)
-
- data, err := os.ReadFile(overridePath)
- if os.IsNotExist(err) {
- return binds, nil
- }
- if err != nil {
- return nil, err
- }
-
- lines := strings.Split(string(data), "\n")
- for _, line := range lines {
- line = strings.TrimSpace(line)
- if line == "" || strings.HasPrefix(line, "#") {
- continue
- }
-
- if !strings.HasPrefix(line, "bind") {
- continue
- }
-
- parts := strings.SplitN(line, "=", 2)
- if len(parts) < 2 {
- continue
- }
-
- content := strings.TrimSpace(parts[1])
- commentParts := strings.SplitN(content, "#", 2)
- bindContent := strings.TrimSpace(commentParts[0])
-
- var comment string
- if len(commentParts) > 1 {
- comment = strings.TrimSpace(commentParts[1])
- }
-
- fields := strings.SplitN(bindContent, ",", 4)
- if len(fields) < 3 {
- continue
- }
-
- mods := strings.TrimSpace(fields[0])
- keyName := strings.TrimSpace(fields[1])
- command := strings.TrimSpace(fields[2])
-
- var params string
- if len(fields) > 3 {
- params = strings.TrimSpace(fields[3])
- }
-
- keyStr := m.buildKeyString(mods, keyName)
- normalizedKey := strings.ToLower(keyStr)
- action := command
- if params != "" {
- action = command + " " + params
- }
-
- binds[normalizedKey] = &mangowcOverrideBind{
- Key: keyStr,
- Action: action,
- Description: comment,
- }
- }
-
- return binds, nil
-}
-
-func (m *MangoWCProvider) buildKeyString(mods, key string) string {
- if mods == "" || strings.EqualFold(mods, "none") {
- return key
- }
-
- modList := strings.FieldsFunc(mods, func(r rune) bool {
- return r == '+' || r == ' '
- })
-
- parts := append(modList, key)
- return strings.Join(parts, "+")
-}
-
-func (m *MangoWCProvider) getBindSortPriority(action string) int {
- switch {
- case strings.HasPrefix(action, "spawn") && strings.Contains(action, "dms"):
- return 0
- case strings.Contains(action, "view") || strings.Contains(action, "tag"):
- return 1
- case strings.Contains(action, "focus") || strings.Contains(action, "exchange") ||
- strings.Contains(action, "resize") || strings.Contains(action, "move"):
- return 2
- case strings.Contains(action, "mon"):
- return 3
- case strings.HasPrefix(action, "spawn"):
- return 4
- case action == "quit" || action == "reload_config":
- return 5
- default:
- return 6
- }
-}
-
-func (m *MangoWCProvider) writeOverrideBinds(binds map[string]*mangowcOverrideBind) error {
- overridePath := m.GetOverridePath()
- content := m.generateBindsContent(binds)
- return os.WriteFile(overridePath, []byte(content), 0o644)
-}
-
-func (m *MangoWCProvider) generateBindsContent(binds map[string]*mangowcOverrideBind) string {
- if len(binds) == 0 {
- return ""
- }
-
- bindList := make([]*mangowcOverrideBind, 0, len(binds))
- for _, bind := range binds {
- bindList = append(bindList, bind)
- }
-
- sort.Slice(bindList, func(i, j int) bool {
- pi, pj := m.getBindSortPriority(bindList[i].Action), m.getBindSortPriority(bindList[j].Action)
- if pi != pj {
- return pi < pj
- }
- return bindList[i].Key < bindList[j].Key
- })
-
- var sb strings.Builder
- for _, bind := range bindList {
- m.writeBindLine(&sb, bind)
- }
-
- return sb.String()
-}
-
-func (m *MangoWCProvider) writeBindLine(sb *strings.Builder, bind *mangowcOverrideBind) {
- mods, key := m.parseKeyString(bind.Key)
- command, params := m.parseAction(bind.Action)
-
- sb.WriteString("bind=")
- if mods == "" {
- sb.WriteString("none")
- } else {
- sb.WriteString(mods)
- }
- sb.WriteString(",")
- sb.WriteString(key)
- sb.WriteString(",")
- sb.WriteString(command)
-
- if params != "" {
- sb.WriteString(",")
- sb.WriteString(params)
- }
-
- if bind.Description != "" {
- sb.WriteString(" # ")
- sb.WriteString(bind.Description)
- }
-
- sb.WriteString("\n")
-}
-
-func (m *MangoWCProvider) parseKeyString(keyStr string) (mods, key string) {
- parts := strings.Split(keyStr, "+")
- switch len(parts) {
- case 0:
- return "", keyStr
- case 1:
- return "", parts[0]
- default:
- return strings.Join(parts[:len(parts)-1], "+"), parts[len(parts)-1]
- }
-}
-
-func (m *MangoWCProvider) parseAction(action string) (command, params string) {
- parts := strings.SplitN(action, " ", 2)
- switch len(parts) {
- case 0:
- return action, ""
- case 1:
- return parts[0], ""
- default:
- return parts[0], parts[1]
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser.go
deleted file mode 100644
index 5dac4b6..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser.go
+++ /dev/null
@@ -1,613 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-const (
- MangoWCHideComment = "[hidden]"
-)
-
-var MangoWCModSeparators = []rune{'+', ' '}
-
-type MangoWCKeyBinding struct {
- Mods []string `json:"mods"`
- Key string `json:"key"`
- Command string `json:"command"`
- Params string `json:"params"`
- Comment string `json:"comment"`
- Source string `json:"source"`
-}
-
-type MangoWCParser struct {
- contentLines []string
- readingLine int
- configDir string
- currentSource string
- dmsBindsExists bool
- dmsBindsIncluded bool
- includeCount int
- dmsIncludePos int
- bindsAfterDMS int
- dmsBindKeys map[string]bool
- configBindKeys map[string]bool
- conflictingConfigs map[string]*MangoWCKeyBinding
- bindMap map[string]*MangoWCKeyBinding
- bindOrder []string
- processedFiles map[string]bool
- dmsProcessed bool
-}
-
-func NewMangoWCParser(configDir string) *MangoWCParser {
- return &MangoWCParser{
- contentLines: []string{},
- readingLine: 0,
- configDir: configDir,
- dmsIncludePos: -1,
- dmsBindKeys: make(map[string]bool),
- configBindKeys: make(map[string]bool),
- conflictingConfigs: make(map[string]*MangoWCKeyBinding),
- bindMap: make(map[string]*MangoWCKeyBinding),
- bindOrder: []string{},
- processedFiles: make(map[string]bool),
- }
-}
-
-func (p *MangoWCParser) ReadContent(path string) error {
- expandedPath, err := utils.ExpandPath(path)
- if err != nil {
- return err
- }
-
- info, err := os.Stat(expandedPath)
- if err != nil {
- return err
- }
-
- var files []string
- if info.IsDir() {
- confFiles, err := filepath.Glob(filepath.Join(expandedPath, "*.conf"))
- if err != nil {
- return err
- }
- if len(confFiles) == 0 {
- return os.ErrNotExist
- }
- files = confFiles
- } else {
- files = []string{expandedPath}
- }
-
- var combinedContent []string
- for _, file := range files {
- if fileInfo, err := os.Stat(file); err == nil && fileInfo.Mode().IsRegular() {
- data, err := os.ReadFile(file)
- if err == nil {
- combinedContent = append(combinedContent, string(data))
- }
- }
- }
-
- if len(combinedContent) == 0 {
- return os.ErrNotExist
- }
-
- fullContent := strings.Join(combinedContent, "\n")
- p.contentLines = strings.Split(fullContent, "\n")
- return nil
-}
-
-func mangowcAutogenerateComment(command, params string) string {
- switch command {
- case "spawn", "spawn_shell":
- return params
- case "killclient":
- return "Close window"
- case "quit":
- return "Exit MangoWC"
- case "reload_config":
- return "Reload configuration"
- case "focusstack":
- if params == "next" {
- return "Focus next window"
- }
- if params == "prev" {
- return "Focus previous window"
- }
- return "Focus stack " + params
- case "focusdir":
- dirMap := map[string]string{
- "left": "left",
- "right": "right",
- "up": "up",
- "down": "down",
- }
- if dir, ok := dirMap[params]; ok {
- return "Focus " + dir
- }
- return "Focus " + params
- case "exchange_client":
- dirMap := map[string]string{
- "left": "left",
- "right": "right",
- "up": "up",
- "down": "down",
- }
- if dir, ok := dirMap[params]; ok {
- return "Swap window " + dir
- }
- return "Swap window " + params
- case "togglefloating":
- return "Float/unfloat window"
- case "togglefullscreen":
- return "Toggle fullscreen"
- case "togglefakefullscreen":
- return "Toggle fake fullscreen"
- case "togglemaximizescreen":
- return "Toggle maximize"
- case "toggleglobal":
- return "Toggle global"
- case "toggleoverview":
- return "Toggle overview"
- case "toggleoverlay":
- return "Toggle overlay"
- case "minimized":
- return "Minimize window"
- case "restore_minimized":
- return "Restore minimized"
- case "toggle_scratchpad":
- return "Toggle scratchpad"
- case "setlayout":
- return "Set layout " + params
- case "switch_layout":
- return "Switch layout"
- case "view":
- parts := strings.Split(params, ",")
- if len(parts) > 0 {
- return "View tag " + parts[0]
- }
- return "View tag"
- case "tag":
- parts := strings.Split(params, ",")
- if len(parts) > 0 {
- return "Move to tag " + parts[0]
- }
- return "Move to tag"
- case "toggleview":
- parts := strings.Split(params, ",")
- if len(parts) > 0 {
- return "Toggle tag " + parts[0]
- }
- return "Toggle tag"
- case "viewtoleft", "viewtoleft_have_client":
- return "View left tag"
- case "viewtoright", "viewtoright_have_client":
- return "View right tag"
- case "tagtoleft":
- return "Move to left tag"
- case "tagtoright":
- return "Move to right tag"
- case "focusmon":
- return "Focus monitor " + params
- case "tagmon":
- return "Move to monitor " + params
- case "incgaps":
- if strings.HasPrefix(params, "-") {
- return "Decrease gaps"
- }
- return "Increase gaps"
- case "togglegaps":
- return "Toggle gaps"
- case "movewin":
- return "Move window by " + params
- case "resizewin":
- return "Resize window by " + params
- case "set_proportion":
- return "Set proportion " + params
- case "switch_proportion_preset":
- return "Switch proportion preset"
- default:
- return ""
- }
-}
-
-func (p *MangoWCParser) getKeybindAtLine(lineNumber int) *MangoWCKeyBinding {
- if lineNumber >= len(p.contentLines) {
- return nil
- }
-
- line := p.contentLines[lineNumber]
-
- bindMatch := regexp.MustCompile(`^(bind[lsr]*)\s*=\s*(.+)$`)
- matches := bindMatch.FindStringSubmatch(line)
- if len(matches) < 3 {
- return nil
- }
-
- bindType := matches[1]
- content := matches[2]
-
- parts := strings.SplitN(content, "#", 2)
- keys := parts[0]
-
- var comment string
- if len(parts) > 1 {
- comment = strings.TrimSpace(parts[1])
- }
-
- if strings.HasPrefix(comment, MangoWCHideComment) {
- return nil
- }
-
- keyFields := strings.SplitN(keys, ",", 4)
- if len(keyFields) < 3 {
- return nil
- }
-
- mods := strings.TrimSpace(keyFields[0])
- key := strings.TrimSpace(keyFields[1])
- command := strings.TrimSpace(keyFields[2])
-
- var params string
- if len(keyFields) > 3 {
- params = strings.TrimSpace(keyFields[3])
- }
-
- if comment == "" {
- comment = mangowcAutogenerateComment(command, params)
- }
-
- var modList []string
- if mods != "" && !strings.EqualFold(mods, "none") {
- modstring := mods + string(MangoWCModSeparators[0])
- p := 0
- for index, char := range modstring {
- isModSep := false
- for _, sep := range MangoWCModSeparators {
- if char == sep {
- isModSep = true
- break
- }
- }
- if isModSep {
- if index-p > 1 {
- modList = append(modList, modstring[p:index])
- }
- p = index + 1
- }
- }
- }
-
- _ = bindType
-
- return &MangoWCKeyBinding{
- Mods: modList,
- Key: key,
- Command: command,
- Params: params,
- Comment: comment,
- }
-}
-
-func (p *MangoWCParser) ParseKeys() []MangoWCKeyBinding {
- var keybinds []MangoWCKeyBinding
-
- for lineNumber := 0; lineNumber < len(p.contentLines); lineNumber++ {
- line := p.contentLines[lineNumber]
- if line == "" || strings.HasPrefix(strings.TrimSpace(line), "#") {
- continue
- }
-
- if !strings.HasPrefix(strings.TrimSpace(line), "bind") {
- continue
- }
-
- keybind := p.getKeybindAtLine(lineNumber)
- if keybind != nil {
- keybinds = append(keybinds, *keybind)
- }
- }
-
- return keybinds
-}
-
-func ParseMangoWCKeys(path string) ([]MangoWCKeyBinding, error) {
- parser := NewMangoWCParser(path)
- if err := parser.ReadContent(path); err != nil {
- return nil, err
- }
- return parser.ParseKeys(), nil
-}
-
-type MangoWCParseResult struct {
- Keybinds []MangoWCKeyBinding
- DMSBindsIncluded bool
- DMSStatus *MangoWCDMSStatus
- ConflictingConfigs map[string]*MangoWCKeyBinding
-}
-
-type MangoWCDMSStatus struct {
- Exists bool
- Included bool
- IncludePosition int
- TotalIncludes int
- BindsAfterDMS int
- Effective bool
- OverriddenBy int
- StatusMessage string
-}
-
-func (p *MangoWCParser) buildDMSStatus() *MangoWCDMSStatus {
- status := &MangoWCDMSStatus{
- Exists: p.dmsBindsExists,
- Included: p.dmsBindsIncluded,
- IncludePosition: p.dmsIncludePos,
- TotalIncludes: p.includeCount,
- BindsAfterDMS: p.bindsAfterDMS,
- }
-
- switch {
- case !p.dmsBindsExists:
- status.Effective = false
- status.StatusMessage = "dms/binds.conf does not exist"
- case !p.dmsBindsIncluded:
- status.Effective = false
- status.StatusMessage = "dms/binds.conf is not sourced in config"
- case p.bindsAfterDMS > 0:
- status.Effective = true
- status.OverriddenBy = p.bindsAfterDMS
- status.StatusMessage = "Some DMS binds may be overridden by config binds"
- default:
- status.Effective = true
- status.StatusMessage = "DMS binds are active"
- }
-
- return status
-}
-
-func (p *MangoWCParser) formatBindKey(kb *MangoWCKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (p *MangoWCParser) normalizeKey(key string) string {
- return strings.ToLower(key)
-}
-
-func (p *MangoWCParser) addBind(kb *MangoWCKeyBinding) {
- key := p.formatBindKey(kb)
- normalizedKey := p.normalizeKey(key)
- isDMSBind := strings.Contains(kb.Source, "dms/binds.conf") || strings.Contains(kb.Source, "dms"+string(os.PathSeparator)+"binds.conf")
-
- if isDMSBind {
- p.dmsBindKeys[normalizedKey] = true
- } else if p.dmsBindKeys[normalizedKey] {
- p.bindsAfterDMS++
- p.conflictingConfigs[normalizedKey] = kb
- p.configBindKeys[normalizedKey] = true
- return
- } else {
- p.configBindKeys[normalizedKey] = true
- }
-
- if _, exists := p.bindMap[normalizedKey]; !exists {
- p.bindOrder = append(p.bindOrder, key)
- }
- p.bindMap[normalizedKey] = kb
-}
-
-func (p *MangoWCParser) ParseWithDMS() ([]MangoWCKeyBinding, error) {
- expandedDir, err := utils.ExpandPath(p.configDir)
- if err != nil {
- return nil, err
- }
-
- dmsBindsPath := filepath.Join(expandedDir, "dms", "binds.conf")
- if _, err := os.Stat(dmsBindsPath); err == nil {
- p.dmsBindsExists = true
- }
-
- mainConfig := filepath.Join(expandedDir, "config.conf")
- if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
- mainConfig = filepath.Join(expandedDir, "mango.conf")
- }
-
- _, err = p.parseFileWithSource(mainConfig)
- if err != nil {
- return nil, err
- }
-
- if p.dmsBindsExists && !p.dmsProcessed {
- p.parseDMSBindsDirectly(dmsBindsPath)
- }
-
- var keybinds []MangoWCKeyBinding
- for _, key := range p.bindOrder {
- normalizedKey := p.normalizeKey(key)
- if kb, exists := p.bindMap[normalizedKey]; exists {
- keybinds = append(keybinds, *kb)
- }
- }
-
- return keybinds, nil
-}
-
-func (p *MangoWCParser) parseFileWithSource(filePath string) ([]MangoWCKeyBinding, error) {
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return nil, err
- }
-
- if p.processedFiles[absPath] {
- return nil, nil
- }
- p.processedFiles[absPath] = true
-
- data, err := os.ReadFile(absPath)
- if err != nil {
- return nil, err
- }
-
- prevSource := p.currentSource
- p.currentSource = absPath
-
- var keybinds []MangoWCKeyBinding
- lines := strings.Split(string(data), "\n")
-
- for lineNum, line := range lines {
- trimmed := strings.TrimSpace(line)
-
- if strings.HasPrefix(trimmed, "source") {
- p.handleSource(trimmed, filepath.Dir(absPath), &keybinds)
- continue
- }
-
- if !strings.HasPrefix(trimmed, "bind") {
- continue
- }
-
- kb := p.getKeybindAtLineContent(line, lineNum)
- if kb == nil {
- continue
- }
- kb.Source = p.currentSource
- p.addBind(kb)
- keybinds = append(keybinds, *kb)
- }
-
- p.currentSource = prevSource
- return keybinds, nil
-}
-
-func (p *MangoWCParser) handleSource(line, baseDir string, keybinds *[]MangoWCKeyBinding) {
- parts := strings.SplitN(line, "=", 2)
- if len(parts) < 2 {
- return
- }
-
- sourcePath := strings.TrimSpace(parts[1])
- isDMSSource := sourcePath == "dms/binds.conf" || sourcePath == "./dms/binds.conf" || strings.HasSuffix(sourcePath, "/dms/binds.conf")
-
- p.includeCount++
- if isDMSSource {
- p.dmsBindsIncluded = true
- p.dmsIncludePos = p.includeCount
- p.dmsProcessed = true
- }
-
- expanded, err := utils.ExpandPath(sourcePath)
- if err != nil {
- return
- }
-
- fullPath := expanded
- if !filepath.IsAbs(expanded) {
- fullPath = filepath.Join(baseDir, expanded)
- }
-
- includedBinds, err := p.parseFileWithSource(fullPath)
- if err != nil {
- return
- }
-
- *keybinds = append(*keybinds, includedBinds...)
-}
-
-func (p *MangoWCParser) parseDMSBindsDirectly(dmsBindsPath string) []MangoWCKeyBinding {
- keybinds, err := p.parseFileWithSource(dmsBindsPath)
- if err != nil {
- return nil
- }
- p.dmsProcessed = true
- return keybinds
-}
-
-func (p *MangoWCParser) getKeybindAtLineContent(line string, _ int) *MangoWCKeyBinding {
- bindMatch := regexp.MustCompile(`^(bind[lsr]*)\s*=\s*(.+)$`)
- matches := bindMatch.FindStringSubmatch(line)
- if len(matches) < 3 {
- return nil
- }
-
- content := matches[2]
- parts := strings.SplitN(content, "#", 2)
- keys := parts[0]
-
- var comment string
- if len(parts) > 1 {
- comment = strings.TrimSpace(parts[1])
- }
-
- if strings.HasPrefix(comment, MangoWCHideComment) {
- return nil
- }
-
- keyFields := strings.SplitN(keys, ",", 4)
- if len(keyFields) < 3 {
- return nil
- }
-
- mods := strings.TrimSpace(keyFields[0])
- key := strings.TrimSpace(keyFields[1])
- command := strings.TrimSpace(keyFields[2])
-
- var params string
- if len(keyFields) > 3 {
- params = strings.TrimSpace(keyFields[3])
- }
-
- if comment == "" {
- comment = mangowcAutogenerateComment(command, params)
- }
-
- var modList []string
- if mods != "" && !strings.EqualFold(mods, "none") {
- modstring := mods + string(MangoWCModSeparators[0])
- idx := 0
- for index, char := range modstring {
- isModSep := false
- for _, sep := range MangoWCModSeparators {
- if char == sep {
- isModSep = true
- break
- }
- }
- if isModSep {
- if index-idx > 1 {
- modList = append(modList, modstring[idx:index])
- }
- idx = index + 1
- }
- }
- }
-
- return &MangoWCKeyBinding{
- Mods: modList,
- Key: key,
- Command: command,
- Params: params,
- Comment: comment,
- }
-}
-
-func ParseMangoWCKeysWithDMS(path string) (*MangoWCParseResult, error) {
- parser := NewMangoWCParser(path)
- keybinds, err := parser.ParseWithDMS()
- if err != nil {
- return nil, err
- }
-
- return &MangoWCParseResult{
- Keybinds: keybinds,
- DMSBindsIncluded: parser.dmsBindsIncluded,
- DMSStatus: parser.buildDMSStatus(),
- ConflictingConfigs: parser.conflictingConfigs,
- }, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser_test.go
deleted file mode 100644
index 016f50e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_parser_test.go
+++ /dev/null
@@ -1,499 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestMangoWCAutogenerateComment(t *testing.T) {
- tests := []struct {
- command string
- params string
- expected string
- }{
- {"spawn", "kitty", "kitty"},
- {"spawn_shell", "firefox", "firefox"},
- {"killclient", "", "Close window"},
- {"quit", "", "Exit MangoWC"},
- {"reload_config", "", "Reload configuration"},
- {"focusstack", "next", "Focus next window"},
- {"focusstack", "prev", "Focus previous window"},
- {"focusdir", "left", "Focus left"},
- {"focusdir", "right", "Focus right"},
- {"focusdir", "up", "Focus up"},
- {"focusdir", "down", "Focus down"},
- {"exchange_client", "left", "Swap window left"},
- {"exchange_client", "right", "Swap window right"},
- {"togglefloating", "", "Float/unfloat window"},
- {"togglefullscreen", "", "Toggle fullscreen"},
- {"togglefakefullscreen", "", "Toggle fake fullscreen"},
- {"togglemaximizescreen", "", "Toggle maximize"},
- {"toggleglobal", "", "Toggle global"},
- {"toggleoverview", "", "Toggle overview"},
- {"toggleoverlay", "", "Toggle overlay"},
- {"minimized", "", "Minimize window"},
- {"restore_minimized", "", "Restore minimized"},
- {"toggle_scratchpad", "", "Toggle scratchpad"},
- {"setlayout", "tile", "Set layout tile"},
- {"switch_layout", "", "Switch layout"},
- {"view", "1,0", "View tag 1"},
- {"tag", "2,0", "Move to tag 2"},
- {"toggleview", "3,0", "Toggle tag 3"},
- {"viewtoleft", "", "View left tag"},
- {"viewtoright", "", "View right tag"},
- {"viewtoleft_have_client", "", "View left tag"},
- {"viewtoright_have_client", "", "View right tag"},
- {"tagtoleft", "", "Move to left tag"},
- {"tagtoright", "", "Move to right tag"},
- {"focusmon", "left", "Focus monitor left"},
- {"tagmon", "right", "Move to monitor right"},
- {"incgaps", "1", "Increase gaps"},
- {"incgaps", "-1", "Decrease gaps"},
- {"togglegaps", "", "Toggle gaps"},
- {"movewin", "+0,-50", "Move window by +0,-50"},
- {"resizewin", "+0,+50", "Resize window by +0,+50"},
- {"set_proportion", "1.0", "Set proportion 1.0"},
- {"switch_proportion_preset", "", "Switch proportion preset"},
- {"unknown", "", ""},
- }
-
- for _, tt := range tests {
- t.Run(tt.command+"_"+tt.params, func(t *testing.T) {
- result := mangowcAutogenerateComment(tt.command, tt.params)
- if result != tt.expected {
- t.Errorf("mangowcAutogenerateComment(%q, %q) = %q, want %q",
- tt.command, tt.params, result, tt.expected)
- }
- })
- }
-}
-
-func TestMangoWCGetKeybindAtLine(t *testing.T) {
- tests := []struct {
- name string
- line string
- expected *MangoWCKeyBinding
- }{
- {
- name: "basic_keybind",
- line: "bind=ALT,q,killclient,",
- expected: &MangoWCKeyBinding{
- Mods: []string{"ALT"},
- Key: "q",
- Command: "killclient",
- Params: "",
- Comment: "Close window",
- },
- },
- {
- name: "keybind_with_params",
- line: "bind=ALT,Left,focusdir,left",
- expected: &MangoWCKeyBinding{
- Mods: []string{"ALT"},
- Key: "Left",
- Command: "focusdir",
- Params: "left",
- Comment: "Focus left",
- },
- },
- {
- name: "keybind_with_comment",
- line: "bind=Alt,t,spawn,kitty # Open terminal",
- expected: &MangoWCKeyBinding{
- Mods: []string{"Alt"},
- Key: "t",
- Command: "spawn",
- Params: "kitty",
- Comment: "Open terminal",
- },
- },
- {
- name: "keybind_hidden",
- line: "bind=SUPER,h,spawn,secret # [hidden]",
- expected: nil,
- },
- {
- name: "keybind_multiple_mods",
- line: "bind=SUPER+SHIFT,Up,exchange_client,up",
- expected: &MangoWCKeyBinding{
- Mods: []string{"SUPER", "SHIFT"},
- Key: "Up",
- Command: "exchange_client",
- Params: "up",
- Comment: "Swap window up",
- },
- },
- {
- name: "keybind_no_mods",
- line: "bind=NONE,Print,spawn,screenshot",
- expected: &MangoWCKeyBinding{
- Mods: []string{},
- Key: "Print",
- Command: "spawn",
- Params: "screenshot",
- Comment: "screenshot",
- },
- },
- {
- name: "keybind_multiple_params",
- line: "bind=Ctrl,1,view,1,0",
- expected: &MangoWCKeyBinding{
- Mods: []string{"Ctrl"},
- Key: "1",
- Command: "view",
- Params: "1,0",
- Comment: "View tag 1",
- },
- },
- {
- name: "bindl_flag",
- line: "bindl=SUPER+ALT,l,spawn,dms ipc call lock lock",
- expected: &MangoWCKeyBinding{
- Mods: []string{"SUPER", "ALT"},
- Key: "l",
- Command: "spawn",
- Params: "dms ipc call lock lock",
- Comment: "dms ipc call lock lock",
- },
- },
- {
- name: "keybind_with_spaces",
- line: "bind = SUPER, r, reload_config",
- expected: &MangoWCKeyBinding{
- Mods: []string{"SUPER"},
- Key: "r",
- Command: "reload_config",
- Params: "",
- Comment: "Reload configuration",
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- parser := NewMangoWCParser("")
- parser.contentLines = []string{tt.line}
- result := parser.getKeybindAtLine(0)
-
- if tt.expected == nil {
- if result != nil {
- t.Errorf("expected nil, got %+v", result)
- }
- return
- }
-
- if result == nil {
- t.Errorf("expected %+v, got nil", tt.expected)
- return
- }
-
- if result.Key != tt.expected.Key {
- t.Errorf("Key = %q, want %q", result.Key, tt.expected.Key)
- }
- if result.Command != tt.expected.Command {
- t.Errorf("Command = %q, want %q", result.Command, tt.expected.Command)
- }
- if result.Params != tt.expected.Params {
- t.Errorf("Params = %q, want %q", result.Params, tt.expected.Params)
- }
- if result.Comment != tt.expected.Comment {
- t.Errorf("Comment = %q, want %q", result.Comment, tt.expected.Comment)
- }
- if len(result.Mods) != len(tt.expected.Mods) {
- t.Errorf("Mods length = %d, want %d", len(result.Mods), len(tt.expected.Mods))
- } else {
- for i := range result.Mods {
- if result.Mods[i] != tt.expected.Mods[i] {
- t.Errorf("Mods[%d] = %q, want %q", i, result.Mods[i], tt.expected.Mods[i])
- }
- }
- }
- })
- }
-}
-
-func TestMangoWCParseKeys(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.conf")
-
- content := `# MangoWC Configuration
-blur=0
-border_radius=12
-
-# Key Bindings
-bind=SUPER,r,reload_config
-bind=Alt,t,spawn,kitty # Terminal
-bind=ALT,q,killclient,
-bind=ALT,Left,focusdir,left
-
-# Hidden binding
-bind=SUPER,h,spawn,secret # [hidden]
-
-# Multiple modifiers
-bind=SUPER+SHIFT,Up,exchange_client,up
-
-# Workspace bindings
-bind=Ctrl,1,view,1,0
-bind=Ctrl,2,view,2,0
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- keybinds, err := ParseMangoWCKeys(configFile)
- if err != nil {
- t.Fatalf("ParseMangoWCKeys failed: %v", err)
- }
-
- expectedCount := 7
- if len(keybinds) != expectedCount {
- t.Errorf("Expected %d keybinds, got %d", expectedCount, len(keybinds))
- }
-
- if len(keybinds) > 0 && keybinds[0].Command != "reload_config" {
- t.Errorf("First keybind command = %q, want %q", keybinds[0].Command, "reload_config")
- }
-
- foundHidden := false
- for _, kb := range keybinds {
- if kb.Command == "spawn" && kb.Params == "secret" {
- foundHidden = true
- }
- }
- if foundHidden {
- t.Error("Hidden keybind should not be included in results")
- }
-}
-
-func TestMangoWCReadContentMultipleFiles(t *testing.T) {
- tmpDir := t.TempDir()
-
- file1 := filepath.Join(tmpDir, "a.conf")
- file2 := filepath.Join(tmpDir, "b.conf")
-
- content1 := "bind=ALT,q,killclient,\n"
- content2 := "bind=Alt,t,spawn,kitty\n"
-
- if err := os.WriteFile(file1, []byte(content1), 0o644); err != nil {
- t.Fatalf("Failed to write file1: %v", err)
- }
- if err := os.WriteFile(file2, []byte(content2), 0o644); err != nil {
- t.Fatalf("Failed to write file2: %v", err)
- }
-
- parser := NewMangoWCParser("")
- if err := parser.ReadContent(tmpDir); err != nil {
- t.Fatalf("ReadContent failed: %v", err)
- }
-
- keybinds := parser.ParseKeys()
- if len(keybinds) != 2 {
- t.Errorf("Expected 2 keybinds from multiple files, got %d", len(keybinds))
- }
-}
-
-func TestMangoWCReadContentSingleFile(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.conf")
-
- content := "bind=ALT,q,killclient,\n"
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write config: %v", err)
- }
-
- parser := NewMangoWCParser("")
- if err := parser.ReadContent(configFile); err != nil {
- t.Fatalf("ReadContent failed: %v", err)
- }
-
- keybinds := parser.ParseKeys()
- if len(keybinds) != 1 {
- t.Errorf("Expected 1 keybind, got %d", len(keybinds))
- }
-}
-
-func TestMangoWCReadContentErrors(t *testing.T) {
- tests := []struct {
- name string
- path string
- }{
- {
- name: "nonexistent_directory",
- path: "/nonexistent/path/that/does/not/exist",
- },
- {
- name: "empty_directory",
- path: t.TempDir(),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := ParseMangoWCKeys(tt.path)
- if err == nil {
- t.Error("Expected error, got nil")
- }
- })
- }
-}
-
-func TestMangoWCReadContentWithTildeExpansion(t *testing.T) {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- t.Skip("Cannot get home directory")
- }
-
- tmpSubdir := filepath.Join(homeDir, ".config", "test-mango-"+t.Name())
- if err := os.MkdirAll(tmpSubdir, 0o755); err != nil {
- t.Skip("Cannot create test directory in home")
- }
- defer os.RemoveAll(tmpSubdir)
-
- configFile := filepath.Join(tmpSubdir, "config.conf")
- if err := os.WriteFile(configFile, []byte("bind=ALT,q,killclient,\n"), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- relPath, err := filepath.Rel(homeDir, tmpSubdir)
- if err != nil {
- t.Skip("Cannot create relative path")
- }
-
- parser := NewMangoWCParser("")
- tildePathMatch := "~/" + relPath
- err = parser.ReadContent(tildePathMatch)
-
- if err != nil {
- t.Errorf("ReadContent with tilde path failed: %v", err)
- }
-}
-
-func TestMangoWCEmptyAndCommentLines(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.conf")
-
- content := `
-# This is a comment
-bind=ALT,q,killclient,
-
-# Another comment
-
-bind=Alt,t,spawn,kitty
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- keybinds, err := ParseMangoWCKeys(configFile)
- if err != nil {
- t.Fatalf("ParseMangoWCKeys failed: %v", err)
- }
-
- if len(keybinds) != 2 {
- t.Errorf("Expected 2 keybinds (comments ignored), got %d", len(keybinds))
- }
-}
-
-func TestMangoWCInvalidBindLines(t *testing.T) {
- tests := []struct {
- name string
- line string
- }{
- {
- name: "missing_parts",
- line: "bind=SUPER,q",
- },
- {
- name: "not_bind",
- line: "blur=0",
- },
- {
- name: "empty_line",
- line: "",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- parser := NewMangoWCParser("")
- parser.contentLines = []string{tt.line}
- result := parser.getKeybindAtLine(0)
-
- if result != nil {
- t.Errorf("expected nil for invalid line, got %+v", result)
- }
- })
- }
-}
-
-func TestMangoWCRealWorldConfig(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.conf")
-
- content := `# Application Launchers
-bind=Alt,t,spawn,kitty
-bind=Alt,space,spawn,dms ipc call spotlight toggle
-bind=Alt,v,spawn,dms ipc call clipboard toggle
-
-# exit
-bind=ALT+SHIFT,e,quit
-bind=ALT,q,killclient,
-
-# switch window focus
-bind=SUPER,Tab,focusstack,next
-bind=ALT,Left,focusdir,left
-bind=ALT,Right,focusdir,right
-
-# tag switch
-bind=SUPER,Left,viewtoleft,0
-bind=CTRL,Left,viewtoleft_have_client,0
-bind=SUPER,Right,viewtoright,0
-
-bind=Ctrl,1,view,1,0
-bind=Ctrl,2,view,2,0
-bind=Ctrl,3,view,3,0
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- keybinds, err := ParseMangoWCKeys(configFile)
- if err != nil {
- t.Fatalf("ParseMangoWCKeys failed: %v", err)
- }
-
- if len(keybinds) < 14 {
- t.Errorf("Expected at least 14 keybinds, got %d", len(keybinds))
- }
-
- foundSpawn := false
- foundQuit := false
- foundView := false
-
- for _, kb := range keybinds {
- if kb.Command == "spawn" && kb.Params == "kitty" {
- foundSpawn = true
- }
- if kb.Command == "quit" {
- foundQuit = true
- }
- if kb.Command == "view" && kb.Params == "1,0" {
- foundView = true
- }
- }
-
- if !foundSpawn {
- t.Error("Did not find spawn kitty keybind")
- }
- if !foundQuit {
- t.Error("Did not find quit keybind")
- }
- if !foundView {
- t.Error("Did not find view workspace 1 keybind")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_test.go
deleted file mode 100644
index d417ce1..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/mangowc_test.go
+++ /dev/null
@@ -1,320 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestMangoWCProviderName(t *testing.T) {
- provider := NewMangoWCProvider("")
- if provider.Name() != "mangowc" {
- t.Errorf("Name() = %q, want %q", provider.Name(), "mangowc")
- }
-}
-
-func TestMangoWCProviderDefaultPath(t *testing.T) {
- provider := NewMangoWCProvider("")
- configDir, err := os.UserConfigDir()
- if err != nil {
- // Fall back to testing for non-empty path
- if provider.configPath == "" {
- t.Error("configPath should not be empty")
- }
- return
- }
- expected := filepath.Join(configDir, "mango")
- if provider.configPath != expected {
- t.Errorf("configPath = %q, want %q", provider.configPath, expected)
- }
-}
-
-func TestMangoWCProviderCustomPath(t *testing.T) {
- customPath := "/custom/path"
- provider := NewMangoWCProvider(customPath)
- if provider.configPath != customPath {
- t.Errorf("configPath = %q, want %q", provider.configPath, customPath)
- }
-}
-
-func TestMangoWCCategorizeByCommand(t *testing.T) {
- tests := []struct {
- command string
- expected string
- }{
- {"view", "Tags"},
- {"tag", "Tags"},
- {"toggleview", "Tags"},
- {"viewtoleft", "Tags"},
- {"viewtoright", "Tags"},
- {"viewtoleft_have_client", "Tags"},
- {"tagtoleft", "Tags"},
- {"tagtoright", "Tags"},
- {"focusmon", "Monitor"},
- {"tagmon", "Monitor"},
- {"focusstack", "Window"},
- {"focusdir", "Window"},
- {"exchange_client", "Window"},
- {"killclient", "Window"},
- {"togglefloating", "Window"},
- {"togglefullscreen", "Window"},
- {"togglefakefullscreen", "Window"},
- {"togglemaximizescreen", "Window"},
- {"toggleglobal", "Window"},
- {"toggleoverlay", "Window"},
- {"minimized", "Window"},
- {"restore_minimized", "Window"},
- {"movewin", "Window"},
- {"resizewin", "Window"},
- {"toggleoverview", "Overview"},
- {"toggle_scratchpad", "Scratchpad"},
- {"setlayout", "Layout"},
- {"switch_layout", "Layout"},
- {"set_proportion", "Layout"},
- {"switch_proportion_preset", "Layout"},
- {"incgaps", "Gaps"},
- {"togglegaps", "Gaps"},
- {"spawn", "Execute"},
- {"spawn_shell", "Execute"},
- {"quit", "System"},
- {"reload_config", "System"},
- {"unknown_command", "Other"},
- }
-
- provider := NewMangoWCProvider("")
- for _, tt := range tests {
- t.Run(tt.command, func(t *testing.T) {
- result := provider.categorizeByCommand(tt.command)
- if result != tt.expected {
- t.Errorf("categorizeByCommand(%q) = %q, want %q", tt.command, result, tt.expected)
- }
- })
- }
-}
-
-func TestMangoWCFormatKey(t *testing.T) {
- tests := []struct {
- name string
- keybind *MangoWCKeyBinding
- expected string
- }{
- {
- name: "single_mod",
- keybind: &MangoWCKeyBinding{
- Mods: []string{"ALT"},
- Key: "q",
- },
- expected: "ALT+q",
- },
- {
- name: "multiple_mods",
- keybind: &MangoWCKeyBinding{
- Mods: []string{"SUPER", "SHIFT"},
- Key: "Up",
- },
- expected: "SUPER+SHIFT+Up",
- },
- {
- name: "no_mods",
- keybind: &MangoWCKeyBinding{
- Mods: []string{},
- Key: "Print",
- },
- expected: "Print",
- },
- }
-
- provider := NewMangoWCProvider("")
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := provider.formatKey(tt.keybind)
- if result != tt.expected {
- t.Errorf("formatKey() = %q, want %q", result, tt.expected)
- }
- })
- }
-}
-
-func TestMangoWCConvertKeybind(t *testing.T) {
- tests := []struct {
- name string
- keybind *MangoWCKeyBinding
- wantKey string
- wantDesc string
- }{
- {
- name: "with_comment",
- keybind: &MangoWCKeyBinding{
- Mods: []string{"ALT"},
- Key: "t",
- Command: "spawn",
- Params: "kitty",
- Comment: "Open terminal",
- },
- wantKey: "ALT+t",
- wantDesc: "Open terminal",
- },
- {
- name: "without_comment",
- keybind: &MangoWCKeyBinding{
- Mods: []string{"SUPER"},
- Key: "r",
- Command: "reload_config",
- Params: "",
- Comment: "",
- },
- wantKey: "SUPER+r",
- wantDesc: "reload_config",
- },
- {
- name: "with_params_no_comment",
- keybind: &MangoWCKeyBinding{
- Mods: []string{"CTRL"},
- Key: "1",
- Command: "view",
- Params: "1,0",
- Comment: "",
- },
- wantKey: "CTRL+1",
- wantDesc: "view 1,0",
- },
- }
-
- provider := NewMangoWCProvider("")
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := provider.convertKeybind(tt.keybind, nil)
- if result.Key != tt.wantKey {
- t.Errorf("convertKeybind().Key = %q, want %q", result.Key, tt.wantKey)
- }
- if result.Description != tt.wantDesc {
- t.Errorf("convertKeybind().Description = %q, want %q", result.Description, tt.wantDesc)
- }
- })
- }
-}
-
-func TestMangoWCGetCheatSheet(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.conf")
-
- content := `# MangoWC Configuration
-blur=0
-
-# Key Bindings
-bind=SUPER,r,reload_config
-bind=Alt,t,spawn,kitty # Terminal
-bind=ALT,q,killclient,
-
-# Window management
-bind=ALT,Left,focusdir,left
-bind=ALT,Right,focusdir,right
-bind=SUPER+SHIFT,Up,exchange_client,up
-
-# Tags
-bind=Ctrl,1,view,1,0
-bind=Ctrl,2,view,2,0
-bind=Alt,1,tag,1,0
-
-# Layout
-bind=SUPER,n,switch_layout
-
-# Gaps
-bind=ALT+SHIFT,X,incgaps,1
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- provider := NewMangoWCProvider(tmpDir)
- sheet, err := provider.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- if sheet == nil {
- t.Fatal("Expected non-nil CheatSheet")
- }
-
- if sheet.Title != "MangoWC Keybinds" {
- t.Errorf("Title = %q, want %q", sheet.Title, "MangoWC Keybinds")
- }
-
- if sheet.Provider != "mangowc" {
- t.Errorf("Provider = %q, want %q", sheet.Provider, "mangowc")
- }
-
- categories := []string{"System", "Execute", "Window", "Tags", "Layout", "Gaps"}
- for _, category := range categories {
- if _, exists := sheet.Binds[category]; !exists {
- t.Errorf("Expected category %q to exist", category)
- }
- }
-
- if len(sheet.Binds["System"]) < 1 {
- t.Error("Expected at least 1 System keybind")
- }
- if len(sheet.Binds["Execute"]) < 1 {
- t.Error("Expected at least 1 Execute keybind")
- }
- if len(sheet.Binds["Window"]) < 3 {
- t.Error("Expected at least 3 Window keybinds")
- }
- if len(sheet.Binds["Tags"]) < 3 {
- t.Error("Expected at least 3 Tags keybinds")
- }
-}
-
-func TestMangoWCGetCheatSheetError(t *testing.T) {
- provider := NewMangoWCProvider("/nonexistent/path")
- _, err := provider.GetCheatSheet()
- if err == nil {
- t.Error("Expected error for nonexistent path, got nil")
- }
-}
-
-func TestMangoWCIntegration(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.conf")
-
- content := `bind=Alt,t,spawn,kitty # Open terminal
-bind=ALT,q,killclient,
-bind=SUPER,r,reload_config # Reload config
-bind=ALT,Left,focusdir,left
-bind=Ctrl,1,view,1,0
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- provider := NewMangoWCProvider(tmpDir)
- sheet, err := provider.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- totalBinds := 0
- for _, binds := range sheet.Binds {
- totalBinds += len(binds)
- }
-
- expectedBinds := 5
- if totalBinds != expectedBinds {
- t.Errorf("Expected %d total keybinds, got %d", expectedBinds, totalBinds)
- }
-
- foundTerminal := false
- for _, binds := range sheet.Binds {
- for _, bind := range binds {
- if bind.Description == "Open terminal" && bind.Key == "Alt+t" {
- foundTerminal = true
- }
- }
- }
-
- if !foundTerminal {
- t.Error("Did not find terminal keybind with correct key and description")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle.go
deleted file mode 100644
index 43fc58e..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle.go
+++ /dev/null
@@ -1,95 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-type MiracleProvider struct {
- configPath string
-}
-
-func NewMiracleProvider(configPath string) *MiracleProvider {
- if configPath == "" {
- configDir, err := os.UserConfigDir()
- if err == nil {
- configPath = filepath.Join(configDir, "miracle-wm")
- }
- }
- return &MiracleProvider{configPath: configPath}
-}
-
-func (m *MiracleProvider) Name() string {
- return "miracle"
-}
-
-func (m *MiracleProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
- config, err := ParseMiracleConfig(m.configPath)
- if err != nil {
- return nil, fmt.Errorf("failed to parse miracle-wm config: %w", err)
- }
-
- bindings := MiracleConfigToBindings(config)
- categorizedBinds := make(map[string][]keybinds.Keybind)
-
- for _, kb := range bindings {
- category := m.categorizeAction(kb.Action)
- bind := keybinds.Keybind{
- Key: m.formatKey(kb),
- Description: kb.Comment,
- Action: kb.Action,
- }
- categorizedBinds[category] = append(categorizedBinds[category], bind)
- }
-
- return &keybinds.CheatSheet{
- Title: "Miracle WM Keybinds",
- Provider: m.Name(),
- Binds: categorizedBinds,
- }, nil
-}
-
-func (m *MiracleProvider) GetOverridePath() string {
- expanded, err := utils.ExpandPath(m.configPath)
- if err != nil {
- return filepath.Join(m.configPath, "config.yaml")
- }
- return filepath.Join(expanded, "config.yaml")
-}
-
-func (m *MiracleProvider) formatKey(kb MiracleKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (m *MiracleProvider) categorizeAction(action string) string {
- switch {
- case strings.HasPrefix(action, "select_workspace_") || strings.HasPrefix(action, "move_to_workspace_"):
- return "Workspace"
- case strings.Contains(action, "select_") || strings.Contains(action, "move_"):
- return "Window"
- case action == "toggle_resize" || strings.HasPrefix(action, "resize_"):
- return "Window"
- case action == "fullscreen" || action == "toggle_floating" || action == "quit_active_window" || action == "toggle_pinned_to_workspace":
- return "Window"
- case action == "toggle_tabbing" || action == "toggle_stacking" || action == "request_vertical" || action == "request_horizontal":
- return "Layout"
- case action == "quit_compositor":
- return "System"
- case action == "terminal":
- return "Execute"
- case strings.HasPrefix(action, "magnifier_"):
- return "Accessibility"
- case strings.HasPrefix(action, "dms ") || strings.Contains(action, "dms ipc"):
- return "Execute"
- default:
- return "Execute"
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle_parser.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle_parser.go
deleted file mode 100644
index 425c64a..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/miracle_parser.go
+++ /dev/null
@@ -1,320 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
- "gopkg.in/yaml.v3"
-)
-
-type MiracleConfig struct {
- Terminal string `yaml:"terminal"`
- ActionKey string `yaml:"action_key"`
- DefaultActionOverrides []MiracleActionOverride `yaml:"default_action_overrides"`
- CustomActions []MiracleCustomAction `yaml:"custom_actions"`
-}
-
-type MiracleActionOverride struct {
- Name string `yaml:"name"`
- Action string `yaml:"action"`
- Modifiers []string `yaml:"modifiers"`
- Key string `yaml:"key"`
-}
-
-type MiracleCustomAction struct {
- Command string `yaml:"command"`
- Action string `yaml:"action"`
- Modifiers []string `yaml:"modifiers"`
- Key string `yaml:"key"`
-}
-
-type MiracleKeyBinding struct {
- Mods []string
- Key string
- Action string
- Comment string
-}
-
-var miracleDefaultBinds = []MiracleKeyBinding{
- {Mods: []string{"Super"}, Key: "Return", Action: "terminal", Comment: "Open terminal"},
- {Mods: []string{"Super"}, Key: "v", Action: "request_vertical", Comment: "Layout windows vertically"},
- {Mods: []string{"Super"}, Key: "h", Action: "request_horizontal", Comment: "Layout windows horizontally"},
- {Mods: []string{"Super"}, Key: "Up", Action: "select_up", Comment: "Select window above"},
- {Mods: []string{"Super"}, Key: "Down", Action: "select_down", Comment: "Select window below"},
- {Mods: []string{"Super"}, Key: "Left", Action: "select_left", Comment: "Select window left"},
- {Mods: []string{"Super"}, Key: "Right", Action: "select_right", Comment: "Select window right"},
- {Mods: []string{"Super", "Shift"}, Key: "Up", Action: "move_up", Comment: "Move window up"},
- {Mods: []string{"Super", "Shift"}, Key: "Down", Action: "move_down", Comment: "Move window down"},
- {Mods: []string{"Super", "Shift"}, Key: "Left", Action: "move_left", Comment: "Move window left"},
- {Mods: []string{"Super", "Shift"}, Key: "Right", Action: "move_right", Comment: "Move window right"},
- {Mods: []string{"Super"}, Key: "r", Action: "toggle_resize", Comment: "Toggle resize mode"},
- {Mods: []string{"Super"}, Key: "f", Action: "fullscreen", Comment: "Toggle fullscreen"},
- {Mods: []string{"Super", "Shift"}, Key: "q", Action: "quit_active_window", Comment: "Close window"},
- {Mods: []string{"Super", "Shift"}, Key: "e", Action: "quit_compositor", Comment: "Exit compositor"},
- {Mods: []string{"Super"}, Key: "Space", Action: "toggle_floating", Comment: "Toggle floating"},
- {Mods: []string{"Super", "Shift"}, Key: "p", Action: "toggle_pinned_to_workspace", Comment: "Toggle pinned to workspace"},
- {Mods: []string{"Super"}, Key: "w", Action: "toggle_tabbing", Comment: "Toggle tabbing layout"},
- {Mods: []string{"Super"}, Key: "s", Action: "toggle_stacking", Comment: "Toggle stacking layout"},
- {Mods: []string{"Super"}, Key: "1", Action: "select_workspace_0", Comment: "Workspace 1"},
- {Mods: []string{"Super"}, Key: "2", Action: "select_workspace_1", Comment: "Workspace 2"},
- {Mods: []string{"Super"}, Key: "3", Action: "select_workspace_2", Comment: "Workspace 3"},
- {Mods: []string{"Super"}, Key: "4", Action: "select_workspace_3", Comment: "Workspace 4"},
- {Mods: []string{"Super"}, Key: "5", Action: "select_workspace_4", Comment: "Workspace 5"},
- {Mods: []string{"Super"}, Key: "6", Action: "select_workspace_5", Comment: "Workspace 6"},
- {Mods: []string{"Super"}, Key: "7", Action: "select_workspace_6", Comment: "Workspace 7"},
- {Mods: []string{"Super"}, Key: "8", Action: "select_workspace_7", Comment: "Workspace 8"},
- {Mods: []string{"Super"}, Key: "9", Action: "select_workspace_8", Comment: "Workspace 9"},
- {Mods: []string{"Super"}, Key: "0", Action: "select_workspace_9", Comment: "Workspace 10"},
- {Mods: []string{"Super", "Shift"}, Key: "1", Action: "move_to_workspace_0", Comment: "Move to workspace 1"},
- {Mods: []string{"Super", "Shift"}, Key: "2", Action: "move_to_workspace_1", Comment: "Move to workspace 2"},
- {Mods: []string{"Super", "Shift"}, Key: "3", Action: "move_to_workspace_2", Comment: "Move to workspace 3"},
- {Mods: []string{"Super", "Shift"}, Key: "4", Action: "move_to_workspace_3", Comment: "Move to workspace 4"},
- {Mods: []string{"Super", "Shift"}, Key: "5", Action: "move_to_workspace_4", Comment: "Move to workspace 5"},
- {Mods: []string{"Super", "Shift"}, Key: "6", Action: "move_to_workspace_5", Comment: "Move to workspace 6"},
- {Mods: []string{"Super", "Shift"}, Key: "7", Action: "move_to_workspace_6", Comment: "Move to workspace 7"},
- {Mods: []string{"Super", "Shift"}, Key: "8", Action: "move_to_workspace_7", Comment: "Move to workspace 8"},
- {Mods: []string{"Super", "Shift"}, Key: "9", Action: "move_to_workspace_8", Comment: "Move to workspace 9"},
- {Mods: []string{"Super", "Shift"}, Key: "0", Action: "move_to_workspace_9", Comment: "Move to workspace 10"},
-}
-
-func ParseMiracleConfig(configPath string) (*MiracleConfig, error) {
- expanded, err := utils.ExpandPath(configPath)
- if err != nil {
- return nil, err
- }
-
- info, err := os.Stat(expanded)
- if err != nil {
- return nil, err
- }
-
- var configFile string
- if info.IsDir() {
- configFile = filepath.Join(expanded, "config.yaml")
- } else {
- configFile = expanded
- }
-
- data, err := os.ReadFile(configFile)
- if err != nil {
- return nil, err
- }
-
- var config MiracleConfig
- if err := yaml.Unmarshal(data, &config); err != nil {
- return nil, err
- }
-
- if config.ActionKey == "" {
- config.ActionKey = "meta"
- }
-
- return &config, nil
-}
-
-func resolveMiracleModifier(mod, actionKey string) string {
- switch mod {
- case "primary":
- return resolveActionKey(actionKey)
- case "alt", "alt_left", "alt_right":
- return "Alt"
- case "shift", "shift_left", "shift_right":
- return "Shift"
- case "ctrl", "ctrl_left", "ctrl_right":
- return "Ctrl"
- case "meta", "meta_left", "meta_right":
- return "Super"
- default:
- return mod
- }
-}
-
-func resolveActionKey(actionKey string) string {
- switch actionKey {
- case "meta":
- return "Super"
- case "alt":
- return "Alt"
- case "ctrl":
- return "Ctrl"
- default:
- return "Super"
- }
-}
-
-func miracleKeyCodeToName(keyCode string) string {
- name := strings.TrimPrefix(keyCode, "KEY_")
- name = strings.ToLower(name)
-
- switch name {
- case "enter":
- return "Return"
- case "space":
- return "Space"
- case "up":
- return "Up"
- case "down":
- return "Down"
- case "left":
- return "Left"
- case "right":
- return "Right"
- case "tab":
- return "Tab"
- case "escape", "esc":
- return "Escape"
- case "delete":
- return "Delete"
- case "backspace":
- return "BackSpace"
- case "home":
- return "Home"
- case "end":
- return "End"
- case "pageup":
- return "Page_Up"
- case "pagedown":
- return "Page_Down"
- case "print":
- return "Print"
- case "pause":
- return "Pause"
- case "volumeup":
- return "XF86AudioRaiseVolume"
- case "volumedown":
- return "XF86AudioLowerVolume"
- case "mute":
- return "XF86AudioMute"
- case "micmute":
- return "XF86AudioMicMute"
- case "brightnessup":
- return "XF86MonBrightnessUp"
- case "brightnessdown":
- return "XF86MonBrightnessDown"
- case "kbdillumup":
- return "XF86KbdBrightnessUp"
- case "kbdillumdown":
- return "XF86KbdBrightnessDown"
- case "comma":
- return "comma"
- case "minus":
- return "minus"
- case "equal":
- return "equal"
- }
-
- if len(name) == 1 {
- return name
- }
-
- return name
-}
-
-func MiracleConfigToBindings(config *MiracleConfig) []MiracleKeyBinding {
- overridden := make(map[string]bool)
- var bindings []MiracleKeyBinding
-
- for _, override := range config.DefaultActionOverrides {
- mods := make([]string, 0, len(override.Modifiers))
- for _, mod := range override.Modifiers {
- mods = append(mods, resolveMiracleModifier(mod, config.ActionKey))
- }
-
- bindings = append(bindings, MiracleKeyBinding{
- Mods: mods,
- Key: miracleKeyCodeToName(override.Key),
- Action: override.Name,
- Comment: miracleActionDescription(override.Name),
- })
- overridden[override.Name] = true
- }
-
- for _, def := range miracleDefaultBinds {
- if overridden[def.Action] {
- continue
- }
- bindings = append(bindings, def)
- }
-
- for _, custom := range config.CustomActions {
- mods := make([]string, 0, len(custom.Modifiers))
- for _, mod := range custom.Modifiers {
- mods = append(mods, resolveMiracleModifier(mod, config.ActionKey))
- }
-
- bindings = append(bindings, MiracleKeyBinding{
- Mods: mods,
- Key: miracleKeyCodeToName(custom.Key),
- Action: custom.Command,
- Comment: custom.Command,
- })
- }
-
- return bindings
-}
-
-func miracleActionDescription(action string) string {
- switch action {
- case "terminal":
- return "Open terminal"
- case "request_vertical":
- return "Layout windows vertically"
- case "request_horizontal":
- return "Layout windows horizontally"
- case "select_up":
- return "Select window above"
- case "select_down":
- return "Select window below"
- case "select_left":
- return "Select window left"
- case "select_right":
- return "Select window right"
- case "move_up":
- return "Move window up"
- case "move_down":
- return "Move window down"
- case "move_left":
- return "Move window left"
- case "move_right":
- return "Move window right"
- case "toggle_resize":
- return "Toggle resize mode"
- case "fullscreen":
- return "Toggle fullscreen"
- case "quit_active_window":
- return "Close window"
- case "quit_compositor":
- return "Exit compositor"
- case "toggle_floating":
- return "Toggle floating"
- case "toggle_pinned_to_workspace":
- return "Toggle pinned to workspace"
- case "toggle_tabbing":
- return "Toggle tabbing layout"
- case "toggle_stacking":
- return "Toggle stacking layout"
- case "magnifier_on":
- return "Enable magnifier"
- case "magnifier_off":
- return "Disable magnifier"
- case "magnifier_increase_size":
- return "Increase magnifier area"
- case "magnifier_decrease_size":
- return "Decrease magnifier area"
- case "magnifier_increase_scale":
- return "Increase magnifier scale"
- case "magnifier_decrease_scale":
- return "Decrease magnifier scale"
- }
-
- if num, ok := strings.CutPrefix(action, "select_workspace_"); ok {
- return "Workspace " + num
- }
- if num, ok := strings.CutPrefix(action, "move_to_workspace_"); ok {
- return "Move to workspace " + num
- }
-
- return action
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri.go
deleted file mode 100644
index aec20b5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri.go
+++ /dev/null
@@ -1,650 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "os/exec"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
- "github.com/sblinch/kdl-go"
- "github.com/sblinch/kdl-go/document"
-)
-
-type NiriProvider struct {
- configDir string
- dmsBindsIncluded bool
- parsed bool
-}
-
-func NewNiriProvider(configDir string) *NiriProvider {
- if configDir == "" {
- configDir = defaultNiriConfigDir()
- }
- return &NiriProvider{
- configDir: configDir,
- }
-}
-
-func defaultNiriConfigDir() string {
- configDir, err := os.UserConfigDir()
- if err != nil {
- return ""
- }
- return filepath.Join(configDir, "niri")
-}
-
-func (n *NiriProvider) Name() string {
- return "niri"
-}
-
-func (n *NiriProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
- result, err := ParseNiriKeys(n.configDir)
- if err != nil {
- return nil, fmt.Errorf("failed to parse niri config: %w", err)
- }
-
- n.dmsBindsIncluded = result.DMSBindsIncluded
- n.parsed = true
-
- categorizedBinds := make(map[string][]keybinds.Keybind)
- n.convertSection(result.Section, "", categorizedBinds, result.ConflictingConfigs)
-
- sheet := &keybinds.CheatSheet{
- Title: "Niri Keybinds",
- Provider: n.Name(),
- Binds: categorizedBinds,
- DMSBindsIncluded: result.DMSBindsIncluded,
- }
-
- if result.DMSStatus != nil {
- sheet.DMSStatus = &keybinds.DMSBindsStatus{
- Exists: result.DMSStatus.Exists,
- Included: result.DMSStatus.Included,
- IncludePosition: result.DMSStatus.IncludePosition,
- TotalIncludes: result.DMSStatus.TotalIncludes,
- BindsAfterDMS: result.DMSStatus.BindsAfterDMS,
- Effective: result.DMSStatus.Effective,
- OverriddenBy: result.DMSStatus.OverriddenBy,
- StatusMessage: result.DMSStatus.StatusMessage,
- }
- }
-
- return sheet, nil
-}
-
-func (n *NiriProvider) HasDMSBindsIncluded() bool {
- if n.parsed {
- return n.dmsBindsIncluded
- }
-
- result, err := ParseNiriKeys(n.configDir)
- if err != nil {
- return false
- }
-
- n.dmsBindsIncluded = result.DMSBindsIncluded
- n.parsed = true
- return n.dmsBindsIncluded
-}
-
-func (n *NiriProvider) convertSection(section *NiriSection, subcategory string, categorizedBinds map[string][]keybinds.Keybind, conflicts map[string]*NiriKeyBinding) {
- currentSubcat := subcategory
- if section.Name != "" {
- currentSubcat = section.Name
- }
-
- for _, kb := range section.Keybinds {
- category := n.categorizeByAction(kb.Action)
- bind := n.convertKeybind(&kb, currentSubcat, conflicts)
- categorizedBinds[category] = append(categorizedBinds[category], bind)
- }
-
- for _, child := range section.Children {
- n.convertSection(&child, currentSubcat, categorizedBinds, conflicts)
- }
-}
-
-func (n *NiriProvider) categorizeByAction(action string) string {
- switch {
- case action == "next-window" || action == "previous-window":
- return "Alt-Tab"
- case strings.Contains(action, "screenshot"):
- return "Screenshot"
- case action == "show-hotkey-overlay" || action == "toggle-overview":
- return "Overview"
- case action == "quit" ||
- action == "power-off-monitors" ||
- action == "power-on-monitors" ||
- action == "suspend" ||
- action == "do-screen-transition" ||
- action == "toggle-keyboard-shortcuts-inhibit" ||
- strings.Contains(action, "dpms"):
- return "System"
- case action == "spawn":
- return "Execute"
- case strings.Contains(action, "workspace"):
- return "Workspace"
- case strings.HasPrefix(action, "focus-monitor") ||
- strings.HasPrefix(action, "move-column-to-monitor") ||
- strings.HasPrefix(action, "move-window-to-monitor"):
- return "Monitor"
- case strings.Contains(action, "window") ||
- strings.Contains(action, "focus") ||
- strings.Contains(action, "move") ||
- strings.Contains(action, "swap") ||
- strings.Contains(action, "resize") ||
- strings.Contains(action, "column"):
- return "Window"
- default:
- return "Other"
- }
-}
-
-func (n *NiriProvider) convertKeybind(kb *NiriKeyBinding, subcategory string, conflicts map[string]*NiriKeyBinding) keybinds.Keybind {
- rawAction := n.formatRawAction(kb.Action, kb.Args)
- keyStr := n.formatKey(kb)
-
- source := "config"
- if strings.Contains(kb.Source, "dms/binds.kdl") {
- source = "dms"
- }
-
- bind := keybinds.Keybind{
- Key: keyStr,
- Description: kb.Description,
- Action: rawAction,
- Subcategory: subcategory,
- Source: source,
- HideOnOverlay: kb.HideOnOverlay,
- CooldownMs: kb.CooldownMs,
- AllowWhenLocked: kb.AllowWhenLocked,
- AllowInhibiting: kb.AllowInhibiting,
- Repeat: kb.Repeat,
- }
-
- if source == "dms" && conflicts != nil {
- if conflictKb, ok := conflicts[keyStr]; ok {
- bind.Conflict = &keybinds.Keybind{
- Key: keyStr,
- Description: conflictKb.Description,
- Action: n.formatRawAction(conflictKb.Action, conflictKb.Args),
- Source: "config",
- }
- }
- }
-
- return bind
-}
-
-func (n *NiriProvider) formatRawAction(action string, args []string) string {
- if len(args) == 0 {
- return action
- }
-
- if action == "spawn" && len(args) >= 3 && args[1] == "-c" {
- switch args[0] {
- case "sh", "bash":
- cmd := strings.Join(args[2:], " ")
- return fmt.Sprintf("spawn %s -c \"%s\"", args[0], strings.ReplaceAll(cmd, "\"", "\\\""))
- }
- }
-
- quotedArgs := make([]string, len(args))
- for i, arg := range args {
- if arg == "" {
- quotedArgs[i] = `""`
- } else {
- quotedArgs[i] = arg
- }
- }
- return action + " " + strings.Join(quotedArgs, " ")
-}
-
-func (n *NiriProvider) formatKey(kb *NiriKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (n *NiriProvider) GetOverridePath() string {
- return filepath.Join(n.configDir, "dms", "binds.kdl")
-}
-
-func (n *NiriProvider) validateAction(action string) error {
- action = strings.TrimSpace(action)
- switch {
- case action == "":
- return fmt.Errorf("action cannot be empty")
- case action == "spawn" || action == "spawn ":
- return fmt.Errorf("spawn command requires arguments")
- case strings.HasPrefix(action, "spawn "):
- rest := strings.TrimSpace(strings.TrimPrefix(action, "spawn "))
- switch rest {
- case "":
- return fmt.Errorf("spawn command requires arguments")
- case "sh -c \"\"", "sh -c ''", "bash -c \"\"", "bash -c ''":
- return fmt.Errorf("shell command cannot be empty")
- }
- }
- return nil
-}
-
-func (n *NiriProvider) SetBind(key, action, description string, options map[string]any) error {
- if err := n.validateAction(action); err != nil {
- return err
- }
-
- overridePath := n.GetOverridePath()
-
- if err := os.MkdirAll(filepath.Dir(overridePath), 0o755); err != nil {
- return fmt.Errorf("failed to create dms directory: %w", err)
- }
-
- existingBinds, err := n.loadOverrideBinds()
- if err != nil {
- existingBinds = make(map[string]*overrideBind)
- }
-
- existingBinds[key] = &overrideBind{
- Key: key,
- Action: action,
- Description: description,
- Options: options,
- }
-
- return n.writeOverrideBinds(existingBinds)
-}
-
-func (n *NiriProvider) RemoveBind(key string) error {
- existingBinds, err := n.loadOverrideBinds()
- if err != nil {
- return nil
- }
-
- delete(existingBinds, key)
- return n.writeOverrideBinds(existingBinds)
-}
-
-type overrideBind struct {
- Key string
- Action string
- Description string
- Options map[string]any
-}
-
-func (n *NiriProvider) loadOverrideBinds() (map[string]*overrideBind, error) {
- overridePath := n.GetOverridePath()
- binds := make(map[string]*overrideBind)
-
- data, err := os.ReadFile(overridePath)
- if os.IsNotExist(err) {
- return binds, nil
- }
- if err != nil {
- return nil, err
- }
-
- parser := NewNiriParser(filepath.Dir(overridePath))
- parser.currentSource = overridePath
-
- doc, err := kdl.Parse(strings.NewReader(string(data)))
- if err != nil {
- return nil, err
- }
-
- for _, node := range doc.Nodes {
- if node.Name.String() != "binds" || node.Children == nil {
- continue
- }
- for _, child := range node.Children {
- kb := parser.parseKeybindNode(child, "")
- if kb == nil {
- continue
- }
- keyStr := parser.formatBindKey(kb)
-
- action := n.buildActionFromNode(child)
- if action == "" {
- action = n.formatRawAction(kb.Action, kb.Args)
- }
-
- binds[keyStr] = &overrideBind{
- Key: keyStr,
- Action: action,
- Description: kb.Description,
- Options: n.extractOptions(child),
- }
- }
- }
-
- return binds, nil
-}
-
-func (n *NiriProvider) buildActionFromNode(bindNode *document.Node) string {
- if len(bindNode.Children) == 0 {
- return ""
- }
-
- actionNode := bindNode.Children[0]
- actionName := actionNode.Name.String()
- if actionName == "" {
- return ""
- }
-
- parts := []string{actionName}
- for _, arg := range actionNode.Arguments {
- val := arg.ValueString()
- if val == "" {
- parts = append(parts, `""`)
- } else if strings.ContainsAny(val, " \t") {
- parts = append(parts, `"`+strings.ReplaceAll(val, `"`, `\"`)+`"`)
- } else {
- parts = append(parts, val)
- }
- }
-
- if actionNode.Properties != nil {
- for _, propName := range []string{"focus", "show-pointer", "write-to-disk", "skip-confirmation", "delay-ms"} {
- if val, ok := actionNode.Properties.Get(propName); ok {
- parts = append(parts, propName+"="+val.String())
- }
- }
- }
-
- return strings.Join(parts, " ")
-}
-
-func (n *NiriProvider) extractOptions(node *document.Node) map[string]any {
- if node.Properties == nil {
- return make(map[string]any)
- }
-
- opts := make(map[string]any)
- if val, ok := node.Properties.Get("repeat"); ok {
- opts["repeat"] = val.String() == "true"
- }
- if val, ok := node.Properties.Get("cooldown-ms"); ok {
- if ms, err := strconv.Atoi(val.String()); err == nil {
- opts["cooldown-ms"] = ms
- }
- }
- if val, ok := node.Properties.Get("allow-when-locked"); ok {
- opts["allow-when-locked"] = val.String() == "true"
- }
- if val, ok := node.Properties.Get("allow-inhibiting"); ok {
- opts["allow-inhibiting"] = val.String() == "true"
- }
- return opts
-}
-
-func (n *NiriProvider) isRecentWindowsAction(action string) bool {
- switch action {
- case "next-window", "previous-window":
- return true
- default:
- return false
- }
-}
-
-func (n *NiriProvider) buildBindNode(bind *overrideBind) *document.Node {
- node := document.NewNode()
- node.SetName(bind.Key)
-
- if bind.Options != nil {
- if v, ok := bind.Options["repeat"]; ok && v == false {
- node.AddProperty("repeat", false, "")
- }
- if v, ok := bind.Options["cooldown-ms"]; ok {
- switch val := v.(type) {
- case int:
- node.AddProperty("cooldown-ms", val, "")
- case string:
- if ms, err := strconv.Atoi(val); err == nil {
- node.AddProperty("cooldown-ms", ms, "")
- }
- }
- }
- if v, ok := bind.Options["allow-when-locked"]; ok && v == true {
- node.AddProperty("allow-when-locked", true, "")
- }
- if v, ok := bind.Options["allow-inhibiting"]; ok && v == false {
- node.AddProperty("allow-inhibiting", false, "")
- }
- }
-
- if bind.Description != "" {
- node.AddProperty("hotkey-overlay-title", bind.Description, "")
- }
-
- actionNode := n.buildActionNode(bind.Action)
- node.AddNode(actionNode)
-
- return node
-}
-
-func (n *NiriProvider) buildActionNode(action string) *document.Node {
- action = strings.TrimSpace(action)
- node := document.NewNode()
-
- parts := n.parseActionParts(action)
- if len(parts) == 0 {
- node.SetName(action)
- return node
- }
-
- node.SetName(parts[0])
- for _, arg := range parts[1:] {
- if strings.Contains(arg, "=") {
- kv := strings.SplitN(arg, "=", 2)
- switch kv[1] {
- case "true":
- node.AddProperty(kv[0], true, "")
- case "false":
- node.AddProperty(kv[0], false, "")
- default:
- node.AddProperty(kv[0], kv[1], "")
- }
- continue
- }
- node.AddArgument(arg, "")
- }
- return node
-}
-
-func (n *NiriProvider) parseActionParts(action string) []string {
- var parts []string
- var current strings.Builder
- var inQuote, escaped, wasQuoted bool
-
- for _, r := range action {
- switch {
- case escaped:
- current.WriteRune(r)
- escaped = false
- case r == '\\':
- escaped = true
- case r == '"':
- wasQuoted = true
- inQuote = !inQuote
- case r == ' ' && !inQuote:
- if current.Len() > 0 || wasQuoted {
- parts = append(parts, current.String())
- current.Reset()
- wasQuoted = false
- }
- default:
- current.WriteRune(r)
- }
- }
- if current.Len() > 0 || wasQuoted {
- parts = append(parts, current.String())
- }
- return parts
-}
-
-func (n *NiriProvider) writeOverrideBinds(binds map[string]*overrideBind) error {
- overridePath := n.GetOverridePath()
- content := n.generateBindsContent(binds)
-
- if err := n.validateBindsContent(content); err != nil {
- return err
- }
-
- return os.WriteFile(overridePath, []byte(content), 0o644)
-}
-
-func (n *NiriProvider) getBindSortPriority(action string) int {
- switch {
- case strings.HasPrefix(action, "spawn") && strings.Contains(action, "dms"):
- return 0
- case strings.Contains(action, "workspace"):
- return 1
- case strings.Contains(action, "window") || strings.Contains(action, "column") ||
- strings.Contains(action, "focus") || strings.Contains(action, "move") ||
- strings.Contains(action, "swap") || strings.Contains(action, "resize"):
- return 2
- case strings.HasPrefix(action, "focus-monitor") || strings.Contains(action, "monitor"):
- return 3
- case strings.Contains(action, "screenshot"):
- return 4
- case action == "quit" || action == "power-off-monitors" || strings.Contains(action, "dpms"):
- return 5
- case strings.HasPrefix(action, "spawn"):
- return 6
- default:
- return 7
- }
-}
-
-func (n *NiriProvider) generateBindsContent(binds map[string]*overrideBind) string {
- if len(binds) == 0 {
- return "binds {}\n"
- }
-
- var regularBinds, recentWindowsBinds []*overrideBind
- for _, bind := range binds {
- switch {
- case n.isRecentWindowsAction(bind.Action):
- recentWindowsBinds = append(recentWindowsBinds, bind)
- default:
- regularBinds = append(regularBinds, bind)
- }
- }
-
- sort.Slice(regularBinds, func(i, j int) bool {
- pi, pj := n.getBindSortPriority(regularBinds[i].Action), n.getBindSortPriority(regularBinds[j].Action)
- if pi != pj {
- return pi < pj
- }
- return regularBinds[i].Key < regularBinds[j].Key
- })
-
- sort.Slice(recentWindowsBinds, func(i, j int) bool {
- return recentWindowsBinds[i].Key < recentWindowsBinds[j].Key
- })
-
- var sb strings.Builder
-
- sb.WriteString("binds {\n")
- for _, bind := range regularBinds {
- n.writeBindNode(&sb, bind, " ")
- }
- sb.WriteString("}\n")
-
- if len(recentWindowsBinds) > 0 {
- sb.WriteString("\nrecent-windows {\n")
- sb.WriteString(" binds {\n")
- for _, bind := range recentWindowsBinds {
- n.writeBindNode(&sb, bind, " ")
- }
- sb.WriteString(" }\n")
- sb.WriteString("}\n")
- }
-
- return sb.String()
-}
-
-func (n *NiriProvider) writeBindNode(sb *strings.Builder, bind *overrideBind, indent string) {
- node := n.buildBindNode(bind)
-
- sb.WriteString(indent)
- sb.WriteString(node.Name.String())
-
- if node.Properties.Exist() {
- sb.WriteString(" ")
- sb.WriteString(strings.TrimLeft(node.Properties.String(), " "))
- }
-
- sb.WriteString(" { ")
- if len(node.Children) > 0 {
- child := node.Children[0]
- actionName := child.Name.String()
- sb.WriteString(actionName)
- forceQuote := actionName == "spawn"
- for _, arg := range child.Arguments {
- sb.WriteString(" ")
- n.writeArg(sb, arg.ValueString(), forceQuote)
- }
- if child.Properties.Exist() {
- sb.WriteString(" ")
- sb.WriteString(strings.TrimLeft(child.Properties.String(), " "))
- }
- }
- sb.WriteString("; }\n")
-}
-
-func (n *NiriProvider) writeArg(sb *strings.Builder, val string, forceQuote bool) {
- if !forceQuote && n.isNumericArg(val) {
- sb.WriteString(val)
- return
- }
- sb.WriteString("\"")
- sb.WriteString(strings.ReplaceAll(val, "\"", "\\\""))
- sb.WriteString("\"")
-}
-
-func (n *NiriProvider) isNumericArg(val string) bool {
- if val == "" {
- return false
- }
- start := 0
- if val[0] == '-' || val[0] == '+' {
- if len(val) == 1 {
- return false
- }
- start = 1
- }
- for i := start; i < len(val); i++ {
- if val[i] < '0' || val[i] > '9' {
- return false
- }
- }
- return true
-}
-
-func (n *NiriProvider) validateBindsContent(content string) error {
- tmpFile, err := os.CreateTemp("", "dms-binds-*.kdl")
- if err != nil {
- return fmt.Errorf("failed to create temp file: %w", err)
- }
- defer os.Remove(tmpFile.Name())
-
- if _, err := tmpFile.WriteString(content); err != nil {
- tmpFile.Close()
- return fmt.Errorf("failed to write temp file: %w", err)
- }
- tmpFile.Close()
-
- cmd := exec.Command("niri", "validate", "-c", tmpFile.Name())
- output, err := cmd.CombinedOutput()
- if err != nil {
- return fmt.Errorf("invalid config: %s", strings.TrimSpace(string(output)))
- }
-
- return nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser.go
deleted file mode 100644
index 46520ff..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser.go
+++ /dev/null
@@ -1,400 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/sblinch/kdl-go"
- "github.com/sblinch/kdl-go/document"
-)
-
-type NiriKeyBinding struct {
- Mods []string
- Key string
- Action string
- Args []string
- Description string
- HideOnOverlay bool
- CooldownMs int
- AllowWhenLocked bool
- AllowInhibiting *bool
- Repeat *bool
- Source string
-}
-
-type NiriSection struct {
- Name string
- Keybinds []NiriKeyBinding
- Children []NiriSection
-}
-
-type NiriParser struct {
- configDir string
- processedFiles map[string]bool
- bindMap map[string]*NiriKeyBinding
- bindOrder []string
- currentSource string
- dmsBindsIncluded bool
- dmsBindsExists bool
- includeCount int
- dmsIncludePos int
- bindsBeforeDMS int
- bindsAfterDMS int
- dmsBindKeys map[string]bool
- configBindKeys map[string]bool
- dmsProcessed bool
- dmsBindMap map[string]*NiriKeyBinding
- conflictingConfigs map[string]*NiriKeyBinding
-}
-
-func NewNiriParser(configDir string) *NiriParser {
- return &NiriParser{
- configDir: configDir,
- processedFiles: make(map[string]bool),
- bindMap: make(map[string]*NiriKeyBinding),
- bindOrder: []string{},
- currentSource: "",
- dmsIncludePos: -1,
- dmsBindKeys: make(map[string]bool),
- configBindKeys: make(map[string]bool),
- dmsBindMap: make(map[string]*NiriKeyBinding),
- conflictingConfigs: make(map[string]*NiriKeyBinding),
- }
-}
-
-func (p *NiriParser) Parse() (*NiriSection, error) {
- dmsBindsPath := filepath.Join(p.configDir, "dms", "binds.kdl")
- if _, err := os.Stat(dmsBindsPath); err == nil {
- p.dmsBindsExists = true
- }
-
- configPath := filepath.Join(p.configDir, "config.kdl")
- section, err := p.parseFile(configPath, "")
- if err != nil {
- return nil, err
- }
-
- if p.dmsBindsExists && !p.dmsProcessed {
- p.parseDMSBindsDirectly(dmsBindsPath, section)
- }
-
- section.Keybinds = p.finalizeBinds()
- return section, nil
-}
-
-func (p *NiriParser) parseDMSBindsDirectly(dmsBindsPath string, section *NiriSection) {
- data, err := os.ReadFile(dmsBindsPath)
- if err != nil {
- return
- }
-
- doc, err := kdl.Parse(strings.NewReader(string(data)))
- if err != nil {
- return
- }
-
- prevSource := p.currentSource
- p.currentSource = dmsBindsPath
- baseDir := filepath.Dir(dmsBindsPath)
- p.processNodes(doc.Nodes, section, baseDir)
- p.currentSource = prevSource
- p.dmsProcessed = true
-}
-
-func (p *NiriParser) finalizeBinds() []NiriKeyBinding {
- binds := make([]NiriKeyBinding, 0, len(p.bindOrder))
- for _, key := range p.bindOrder {
- if kb, ok := p.bindMap[key]; ok {
- binds = append(binds, *kb)
- }
- }
- return binds
-}
-
-func (p *NiriParser) addBind(kb *NiriKeyBinding) {
- key := p.formatBindKey(kb)
- isDMSBind := strings.Contains(kb.Source, "dms/binds.kdl")
-
- if isDMSBind {
- p.dmsBindKeys[key] = true
- p.dmsBindMap[key] = kb
- } else if p.dmsBindKeys[key] {
- p.bindsAfterDMS++
- p.conflictingConfigs[key] = kb
- p.configBindKeys[key] = true
- return
- } else {
- p.configBindKeys[key] = true
- }
-
- if _, exists := p.bindMap[key]; !exists {
- p.bindOrder = append(p.bindOrder, key)
- }
- p.bindMap[key] = kb
-}
-
-func (p *NiriParser) formatBindKey(kb *NiriKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
-
-func (p *NiriParser) parseFile(filePath, sectionName string) (*NiriSection, error) {
- absPath, err := filepath.Abs(filePath)
- if err != nil {
- return nil, fmt.Errorf("failed to resolve path %s: %w", filePath, err)
- }
-
- if p.processedFiles[absPath] {
- return &NiriSection{Name: sectionName}, nil
- }
- p.processedFiles[absPath] = true
-
- data, err := os.ReadFile(absPath)
- if err != nil {
- return nil, fmt.Errorf("failed to read %s: %w", absPath, err)
- }
-
- doc, err := kdl.Parse(strings.NewReader(string(data)))
- if err != nil {
- return nil, fmt.Errorf("failed to parse KDL in %s: %w", absPath, err)
- }
-
- section := &NiriSection{
- Name: sectionName,
- }
-
- prevSource := p.currentSource
- p.currentSource = absPath
- baseDir := filepath.Dir(absPath)
- p.processNodes(doc.Nodes, section, baseDir)
- p.currentSource = prevSource
-
- return section, nil
-}
-
-func (p *NiriParser) processNodes(nodes []*document.Node, section *NiriSection, baseDir string) {
- for _, node := range nodes {
- name := node.Name.String()
-
- switch name {
- case "include":
- p.handleInclude(node, section, baseDir)
- case "binds":
- p.extractBinds(node, section, "")
- case "recent-windows":
- p.handleRecentWindows(node, section)
- }
- }
-}
-
-func (p *NiriParser) handleInclude(node *document.Node, section *NiriSection, baseDir string) {
- if len(node.Arguments) == 0 {
- return
- }
-
- includePath := strings.Trim(node.Arguments[0].String(), "\"")
- isDMSInclude := includePath == "dms/binds.kdl" || strings.HasSuffix(includePath, "/dms/binds.kdl")
-
- p.includeCount++
- if isDMSInclude {
- p.dmsBindsIncluded = true
- p.dmsIncludePos = p.includeCount
- p.bindsBeforeDMS = len(p.bindMap)
- }
-
- fullPath := filepath.Join(baseDir, includePath)
- if filepath.IsAbs(includePath) {
- fullPath = includePath
- }
-
- if isDMSInclude {
- p.dmsProcessed = true
- }
-
- includedSection, err := p.parseFile(fullPath, "")
- if err != nil {
- return
- }
-
- section.Children = append(section.Children, includedSection.Children...)
-}
-
-func (p *NiriParser) HasDMSBindsIncluded() bool {
- return p.dmsBindsIncluded
-}
-
-func (p *NiriParser) handleRecentWindows(node *document.Node, section *NiriSection) {
- if node.Children == nil {
- return
- }
-
- for _, child := range node.Children {
- if child.Name.String() != "binds" {
- continue
- }
- p.extractBinds(child, section, "Alt-Tab")
- }
-}
-
-func (p *NiriParser) extractBinds(node *document.Node, section *NiriSection, subcategory string) {
- if node.Children == nil {
- return
- }
-
- for _, child := range node.Children {
- kb := p.parseKeybindNode(child, subcategory)
- if kb == nil {
- continue
- }
- p.addBind(kb)
- }
-}
-
-func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBinding {
- keyCombo := node.Name.String()
- if keyCombo == "" {
- return nil
- }
-
- mods, key := p.parseKeyCombo(keyCombo)
-
- var action string
- var args []string
- if len(node.Children) > 0 {
- actionNode := node.Children[0]
- action = actionNode.Name.String()
- for _, arg := range actionNode.Arguments {
- args = append(args, arg.ValueString())
- }
- if actionNode.Properties != nil {
- for _, propName := range []string{"focus", "show-pointer", "write-to-disk", "skip-confirmation", "delay-ms"} {
- if val, ok := actionNode.Properties.Get(propName); ok {
- args = append(args, propName+"="+val.String())
- }
- }
- }
- }
-
- var description string
- var hideOnOverlay bool
- var cooldownMs int
- var allowWhenLocked bool
- var allowInhibiting *bool
- var repeat *bool
- if node.Properties != nil {
- if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
- switch val.ValueString() {
- case "null", "":
- hideOnOverlay = true
- default:
- description = val.ValueString()
- }
- }
- if val, ok := node.Properties.Get("cooldown-ms"); ok {
- cooldownMs, _ = strconv.Atoi(val.String())
- }
- if val, ok := node.Properties.Get("allow-when-locked"); ok {
- allowWhenLocked = val.String() == "true"
- }
- if val, ok := node.Properties.Get("allow-inhibiting"); ok {
- v := val.String() == "true"
- allowInhibiting = &v
- }
- if val, ok := node.Properties.Get("repeat"); ok {
- v := val.String() == "true"
- repeat = &v
- }
- }
-
- return &NiriKeyBinding{
- Mods: mods,
- Key: key,
- Action: action,
- Args: args,
- Description: description,
- HideOnOverlay: hideOnOverlay,
- CooldownMs: cooldownMs,
- AllowWhenLocked: allowWhenLocked,
- AllowInhibiting: allowInhibiting,
- Repeat: repeat,
- Source: p.currentSource,
- }
-}
-
-func (p *NiriParser) parseKeyCombo(combo string) ([]string, string) {
- parts := strings.Split(combo, "+")
-
- switch len(parts) {
- case 0:
- return nil, combo
- case 1:
- return nil, parts[0]
- default:
- return parts[:len(parts)-1], parts[len(parts)-1]
- }
-}
-
-type NiriParseResult struct {
- Section *NiriSection
- DMSBindsIncluded bool
- DMSStatus *DMSBindsStatusInfo
- ConflictingConfigs map[string]*NiriKeyBinding
-}
-
-type DMSBindsStatusInfo struct {
- Exists bool
- Included bool
- IncludePosition int
- TotalIncludes int
- BindsAfterDMS int
- Effective bool
- OverriddenBy int
- StatusMessage string
-}
-
-func (p *NiriParser) buildDMSStatus() *DMSBindsStatusInfo {
- status := &DMSBindsStatusInfo{
- Exists: p.dmsBindsExists,
- Included: p.dmsBindsIncluded,
- IncludePosition: p.dmsIncludePos,
- TotalIncludes: p.includeCount,
- BindsAfterDMS: p.bindsAfterDMS,
- }
-
- switch {
- case !p.dmsBindsExists:
- status.Effective = false
- status.StatusMessage = "dms/binds.kdl does not exist"
- case !p.dmsBindsIncluded:
- status.Effective = false
- status.StatusMessage = "dms/binds.kdl is not included in config.kdl"
- case p.bindsAfterDMS > 0:
- status.Effective = true
- status.OverriddenBy = p.bindsAfterDMS
- status.StatusMessage = "Some DMS binds may be overridden by config binds"
- default:
- status.Effective = true
- status.StatusMessage = "DMS binds are active"
- }
-
- return status
-}
-
-func ParseNiriKeys(configDir string) (*NiriParseResult, error) {
- parser := NewNiriParser(configDir)
- section, err := parser.Parse()
- if err != nil {
- return nil, err
- }
- return &NiriParseResult{
- Section: section,
- DMSBindsIncluded: parser.HasDMSBindsIncluded(),
- DMSStatus: parser.buildDMSStatus(),
- ConflictingConfigs: parser.conflictingConfigs,
- }, nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser_test.go
deleted file mode 100644
index e21fde5..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_parser_test.go
+++ /dev/null
@@ -1,630 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestNiriParseKeyCombo(t *testing.T) {
- tests := []struct {
- combo string
- expectedMods []string
- expectedKey string
- }{
- {"Mod+Q", []string{"Mod"}, "Q"},
- {"Mod+Shift+F", []string{"Mod", "Shift"}, "F"},
- {"Ctrl+Alt+Delete", []string{"Ctrl", "Alt"}, "Delete"},
- {"Print", nil, "Print"},
- {"XF86AudioMute", nil, "XF86AudioMute"},
- {"Super+Tab", []string{"Super"}, "Tab"},
- {"Mod+Shift+Ctrl+H", []string{"Mod", "Shift", "Ctrl"}, "H"},
- }
-
- parser := NewNiriParser("")
- for _, tt := range tests {
- t.Run(tt.combo, func(t *testing.T) {
- mods, key := parser.parseKeyCombo(tt.combo)
-
- if len(mods) != len(tt.expectedMods) {
- t.Errorf("Mods length = %d, want %d", len(mods), len(tt.expectedMods))
- } else {
- for i := range mods {
- if mods[i] != tt.expectedMods[i] {
- t.Errorf("Mods[%d] = %q, want %q", i, mods[i], tt.expectedMods[i])
- }
- }
- }
-
- if key != tt.expectedKey {
- t.Errorf("Key = %q, want %q", key, tt.expectedKey)
- }
- })
- }
-}
-
-func TestNiriParseBasicBinds(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+Q { close-window; }
- Mod+F { fullscreen-window; }
- Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 3 {
- t.Errorf("Expected 3 keybinds, got %d", len(result.Section.Keybinds))
- }
-
- foundClose := false
- foundFullscreen := false
- foundTerminal := false
-
- for _, kb := range result.Section.Keybinds {
- switch kb.Action {
- case "close-window":
- foundClose = true
- if kb.Key != "Q" || len(kb.Mods) != 1 || kb.Mods[0] != "Mod" {
- t.Errorf("close-window keybind mismatch: %+v", kb)
- }
- case "fullscreen-window":
- foundFullscreen = true
- case "spawn":
- foundTerminal = true
- if kb.Description != "Open Terminal" {
- t.Errorf("spawn description = %q, want %q", kb.Description, "Open Terminal")
- }
- if len(kb.Args) != 1 || kb.Args[0] != "kitty" {
- t.Errorf("spawn args = %v, want [kitty]", kb.Args)
- }
- }
- }
-
- if !foundClose {
- t.Error("close-window keybind not found")
- }
- if !foundFullscreen {
- t.Error("fullscreen-window keybind not found")
- }
- if !foundTerminal {
- t.Error("spawn keybind not found")
- }
-}
-
-func TestNiriParseRecentWindows(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `recent-windows {
- binds {
- Alt+Tab { next-window scope="output"; }
- Alt+Shift+Tab { previous-window scope="output"; }
- }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds from recent-windows, got %d", len(result.Section.Keybinds))
- }
-
- foundNext := false
- foundPrev := false
-
- for _, kb := range result.Section.Keybinds {
- switch kb.Action {
- case "next-window":
- foundNext = true
- case "previous-window":
- foundPrev = true
- }
- }
-
- if !foundNext {
- t.Error("next-window keybind not found")
- }
- if !foundPrev {
- t.Error("previous-window keybind not found")
- }
-}
-
-func TestNiriParseInclude(t *testing.T) {
- tmpDir := t.TempDir()
- subDir := filepath.Join(tmpDir, "dms")
- if err := os.MkdirAll(subDir, 0o755); err != nil {
- t.Fatalf("Failed to create subdir: %v", err)
- }
-
- mainConfig := filepath.Join(tmpDir, "config.kdl")
- includeConfig := filepath.Join(subDir, "binds.kdl")
-
- mainContent := `binds {
- Mod+Q { close-window; }
-}
-include "dms/binds.kdl"
-`
- includeContent := `binds {
- Mod+T hotkey-overlay-title="Terminal" { spawn "kitty"; }
-}
-`
-
- if err := os.WriteFile(mainConfig, []byte(mainContent), 0o644); err != nil {
- t.Fatalf("Failed to write main config: %v", err)
- }
- if err := os.WriteFile(includeConfig, []byte(includeContent), 0o644); err != nil {
- t.Fatalf("Failed to write include config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds (1 main + 1 include), got %d", len(result.Section.Keybinds))
- }
-}
-
-func TestNiriParseIncludeOverride(t *testing.T) {
- tmpDir := t.TempDir()
- subDir := filepath.Join(tmpDir, "dms")
- if err := os.MkdirAll(subDir, 0o755); err != nil {
- t.Fatalf("Failed to create subdir: %v", err)
- }
-
- mainConfig := filepath.Join(tmpDir, "config.kdl")
- includeConfig := filepath.Join(subDir, "binds.kdl")
-
- mainContent := `binds {
- Mod+T hotkey-overlay-title="Main Terminal" { spawn "alacritty"; }
-}
-include "dms/binds.kdl"
-`
- includeContent := `binds {
- Mod+T hotkey-overlay-title="Override Terminal" { spawn "kitty"; }
-}
-`
-
- if err := os.WriteFile(mainConfig, []byte(mainContent), 0o644); err != nil {
- t.Fatalf("Failed to write main config: %v", err)
- }
- if err := os.WriteFile(includeConfig, []byte(includeContent), 0o644); err != nil {
- t.Fatalf("Failed to write include config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 1 {
- t.Errorf("Expected 1 keybind (later overrides earlier), got %d", len(result.Section.Keybinds))
- }
-
- if len(result.Section.Keybinds) > 0 {
- kb := result.Section.Keybinds[0]
- if kb.Description != "Override Terminal" {
- t.Errorf("Expected description 'Override Terminal' (from include), got %q", kb.Description)
- }
- if len(kb.Args) != 1 || kb.Args[0] != "kitty" {
- t.Errorf("Expected args [kitty] (from include), got %v", kb.Args)
- }
- }
-}
-
-func TestNiriParseCircularInclude(t *testing.T) {
- tmpDir := t.TempDir()
-
- mainConfig := filepath.Join(tmpDir, "config.kdl")
- otherConfig := filepath.Join(tmpDir, "other.kdl")
-
- mainContent := `binds {
- Mod+Q { close-window; }
-}
-include "other.kdl"
-`
- otherContent := `binds {
- Mod+T { spawn "kitty"; }
-}
-include "config.kdl"
-`
-
- if err := os.WriteFile(mainConfig, []byte(mainContent), 0o644); err != nil {
- t.Fatalf("Failed to write main config: %v", err)
- }
- if err := os.WriteFile(otherConfig, []byte(otherContent), 0o644); err != nil {
- t.Fatalf("Failed to write other config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed (should handle circular includes): %v", err)
- }
-
- if len(result.Section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds (circular include handled), got %d", len(result.Section.Keybinds))
- }
-}
-
-func TestNiriParseMissingInclude(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+Q { close-window; }
-}
-include "nonexistent/file.kdl"
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed (should skip missing include): %v", err)
- }
-
- if len(result.Section.Keybinds) != 1 {
- t.Errorf("Expected 1 keybind (missing include skipped), got %d", len(result.Section.Keybinds))
- }
-}
-
-func TestNiriParseNoBinds(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `cursor {
- xcursor-theme "Bibata"
- xcursor-size 24
-}
-
-input {
- keyboard {
- numlock
- }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 0 {
- t.Errorf("Expected 0 keybinds, got %d", len(result.Section.Keybinds))
- }
-}
-
-func TestNiriParseErrors(t *testing.T) {
- tests := []struct {
- name string
- path string
- }{
- {
- name: "nonexistent_directory",
- path: "/nonexistent/path/that/does/not/exist",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := ParseNiriKeys(tt.path)
- if err == nil {
- t.Error("Expected error, got nil")
- }
- })
- }
-}
-
-func TestNiriBindOverrideBehavior(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+T hotkey-overlay-title="First" { spawn "first"; }
- Mod+Q { close-window; }
- Mod+T hotkey-overlay-title="Second" { spawn "second"; }
- Mod+F { fullscreen-window; }
- Mod+T hotkey-overlay-title="Third" { spawn "third"; }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 3 {
- t.Fatalf("Expected 3 unique keybinds, got %d", len(result.Section.Keybinds))
- }
-
- var modT *NiriKeyBinding
- for i := range result.Section.Keybinds {
- kb := &result.Section.Keybinds[i]
- if len(kb.Mods) == 1 && kb.Mods[0] == "Mod" && kb.Key == "T" {
- modT = kb
- break
- }
- }
-
- if modT == nil {
- t.Fatal("Mod+T keybind not found")
- }
-
- if modT.Description != "Third" {
- t.Errorf("Mod+T description = %q, want 'Third' (last definition wins)", modT.Description)
- }
-
- if len(modT.Args) != 1 || modT.Args[0] != "third" {
- t.Errorf("Mod+T args = %v, want [third] (last definition wins)", modT.Args)
- }
-}
-
-func TestNiriBindOverrideWithIncludes(t *testing.T) {
- tmpDir := t.TempDir()
- subDir := filepath.Join(tmpDir, "custom")
- if err := os.MkdirAll(subDir, 0o755); err != nil {
- t.Fatalf("Failed to create subdir: %v", err)
- }
-
- mainConfig := filepath.Join(tmpDir, "config.kdl")
- includeConfig := filepath.Join(subDir, "overrides.kdl")
-
- mainContent := `binds {
- Mod+1 { focus-workspace 1; }
- Mod+2 { focus-workspace 2; }
- Mod+T hotkey-overlay-title="Default Terminal" { spawn "xterm"; }
-}
-include "custom/overrides.kdl"
-binds {
- Mod+3 { focus-workspace 3; }
-}
-`
- includeContent := `binds {
- Mod+T hotkey-overlay-title="Custom Terminal" { spawn "kitty"; }
- Mod+2 { focus-workspace 22; }
-}
-`
-
- if err := os.WriteFile(mainConfig, []byte(mainContent), 0o644); err != nil {
- t.Fatalf("Failed to write main config: %v", err)
- }
- if err := os.WriteFile(includeConfig, []byte(includeContent), 0o644); err != nil {
- t.Fatalf("Failed to write include config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 4 {
- t.Errorf("Expected 4 unique keybinds, got %d", len(result.Section.Keybinds))
- }
-
- bindMap := make(map[string]*NiriKeyBinding)
- for i := range result.Section.Keybinds {
- kb := &result.Section.Keybinds[i]
- key := ""
- for _, m := range kb.Mods {
- key += m + "+"
- }
- key += kb.Key
- bindMap[key] = kb
- }
-
- if kb, ok := bindMap["Mod+T"]; ok {
- if kb.Description != "Custom Terminal" {
- t.Errorf("Mod+T should be overridden by include, got description %q", kb.Description)
- }
- } else {
- t.Error("Mod+T not found")
- }
-
- if kb, ok := bindMap["Mod+2"]; ok {
- if len(kb.Args) != 1 || kb.Args[0] != "22" {
- t.Errorf("Mod+2 should be overridden by include with workspace 22, got args %v", kb.Args)
- }
- } else {
- t.Error("Mod+2 not found")
- }
-
- if _, ok := bindMap["Mod+1"]; !ok {
- t.Error("Mod+1 should exist (not overridden)")
- }
-
- if _, ok := bindMap["Mod+3"]; !ok {
- t.Error("Mod+3 should exist (added after include)")
- }
-}
-
-func TestNiriParseMultipleArgs(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+Space hotkey-overlay-title="Application Launcher" {
- spawn "dms" "ipc" "call" "spotlight" "toggle";
- }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 1 {
- t.Fatalf("Expected 1 keybind, got %d", len(result.Section.Keybinds))
- }
-
- kb := result.Section.Keybinds[0]
- if len(kb.Args) != 5 {
- t.Errorf("Expected 5 args, got %d: %v", len(kb.Args), kb.Args)
- }
-
- expectedArgs := []string{"dms", "ipc", "call", "spotlight", "toggle"}
- for i, arg := range expectedArgs {
- if i < len(kb.Args) && kb.Args[i] != arg {
- t.Errorf("Args[%d] = %q, want %q", i, kb.Args[i], arg)
- }
- }
-}
-
-func TestNiriParseNumericWorkspaceBinds(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+1 hotkey-overlay-title="Focus Workspace 1" { focus-workspace 1; }
- Mod+2 hotkey-overlay-title="Focus Workspace 2" { focus-workspace 2; }
- Mod+0 hotkey-overlay-title="Focus Workspace 10" { focus-workspace 10; }
- Mod+Shift+1 hotkey-overlay-title="Move to Workspace 1" { move-column-to-workspace 1; }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 4 {
- t.Errorf("Expected 4 keybinds, got %d", len(result.Section.Keybinds))
- }
-
- for _, kb := range result.Section.Keybinds {
- switch kb.Key {
- case "1":
- if len(kb.Mods) == 1 && kb.Mods[0] == "Mod" {
- if kb.Action != "focus-workspace" || len(kb.Args) != 1 || kb.Args[0] != "1" {
- t.Errorf("Mod+1 action/args mismatch: %+v", kb)
- }
- if kb.Description != "Focus Workspace 1" {
- t.Errorf("Mod+1 description = %q, want 'Focus Workspace 1'", kb.Description)
- }
- }
- case "0":
- if kb.Action != "focus-workspace" || len(kb.Args) != 1 || kb.Args[0] != "10" {
- t.Errorf("Mod+0 action/args mismatch: %+v", kb)
- }
- }
- }
-}
-
-func TestNiriParseQuotedStringArgs(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Super+Minus hotkey-overlay-title="Adjust Column Width -10%" { set-column-width "-10%"; }
- Super+Equal hotkey-overlay-title="Adjust Column Width +10%" { set-column-width "+10%"; }
- Super+Shift+Minus hotkey-overlay-title="Adjust Window Height -10%" { set-window-height "-10%"; }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 3 {
- t.Errorf("Expected 3 keybinds, got %d", len(result.Section.Keybinds))
- }
-
- for _, kb := range result.Section.Keybinds {
- if kb.Action == "set-column-width" {
- if len(kb.Args) != 1 {
- t.Errorf("set-column-width should have 1 arg, got %d", len(kb.Args))
- continue
- }
- if kb.Args[0] != "-10%" && kb.Args[0] != "+10%" {
- t.Errorf("set-column-width arg = %q, want -10%% or +10%%", kb.Args[0])
- }
- }
- }
-}
-
-func TestNiriParseActionWithProperties(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+Shift+1 hotkey-overlay-title="Move to Workspace 1" { move-column-to-workspace 1 focus=false; }
- Mod+Shift+2 hotkey-overlay-title="Move to Workspace 2" { move-column-to-workspace 2 focus=false; }
- Alt+Tab { next-window scope="output"; }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseNiriKeys failed: %v", err)
- }
-
- if len(result.Section.Keybinds) != 3 {
- t.Errorf("Expected 3 keybinds, got %d", len(result.Section.Keybinds))
- }
-
- for _, kb := range result.Section.Keybinds {
- switch kb.Action {
- case "move-column-to-workspace":
- if len(kb.Args) != 2 {
- t.Errorf("move-column-to-workspace should have 2 args (index + focus), got %d", len(kb.Args))
- }
- hasIndex := false
- hasFocus := false
- for _, arg := range kb.Args {
- if arg == "1" || arg == "2" {
- hasIndex = true
- }
- if arg == "focus=false" {
- hasFocus = true
- }
- }
- if !hasIndex {
- t.Errorf("move-column-to-workspace missing index arg")
- }
- if !hasFocus {
- t.Errorf("move-column-to-workspace missing focus=false arg")
- }
- case "next-window":
- if kb.Key != "Tab" {
- t.Errorf("next-window key = %q, want 'Tab'", kb.Key)
- }
- }
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_test.go
deleted file mode 100644
index b04c5b2..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/niri_test.go
+++ /dev/null
@@ -1,661 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestNiriProviderName(t *testing.T) {
- provider := NewNiriProvider("")
- if provider.Name() != "niri" {
- t.Errorf("Name() = %q, want %q", provider.Name(), "niri")
- }
-}
-
-func TestNiriProviderGetCheatSheet(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+Q { close-window; }
- Mod+F { fullscreen-window; }
- Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
- Mod+1 { focus-workspace 1; }
- Mod+Shift+1 { move-column-to-workspace 1; }
- Print { screenshot; }
- Mod+Shift+E { quit; }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- provider := NewNiriProvider(tmpDir)
- cheatSheet, err := provider.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- if cheatSheet.Title != "Niri Keybinds" {
- t.Errorf("Title = %q, want %q", cheatSheet.Title, "Niri Keybinds")
- }
-
- if cheatSheet.Provider != "niri" {
- t.Errorf("Provider = %q, want %q", cheatSheet.Provider, "niri")
- }
-
- windowBinds := cheatSheet.Binds["Window"]
- if len(windowBinds) < 2 {
- t.Errorf("Expected at least 2 Window binds, got %d", len(windowBinds))
- }
-
- execBinds := cheatSheet.Binds["Execute"]
- if len(execBinds) < 1 {
- t.Errorf("Expected at least 1 Execute bind, got %d", len(execBinds))
- }
-
- workspaceBinds := cheatSheet.Binds["Workspace"]
- if len(workspaceBinds) < 2 {
- t.Errorf("Expected at least 2 Workspace binds, got %d", len(workspaceBinds))
- }
-
- screenshotBinds := cheatSheet.Binds["Screenshot"]
- if len(screenshotBinds) < 1 {
- t.Errorf("Expected at least 1 Screenshot bind, got %d", len(screenshotBinds))
- }
-
- systemBinds := cheatSheet.Binds["System"]
- if len(systemBinds) < 1 {
- t.Errorf("Expected at least 1 System bind, got %d", len(systemBinds))
- }
-}
-
-func TestNiriCategorizeByAction(t *testing.T) {
- provider := NewNiriProvider("")
-
- tests := []struct {
- action string
- expected string
- }{
- {"focus-workspace", "Workspace"},
- {"focus-workspace-up", "Workspace"},
- {"move-column-to-workspace", "Workspace"},
- {"focus-monitor-left", "Monitor"},
- {"move-column-to-monitor-right", "Monitor"},
- {"close-window", "Window"},
- {"fullscreen-window", "Window"},
- {"maximize-column", "Window"},
- {"toggle-window-floating", "Window"},
- {"focus-column-left", "Window"},
- {"move-column-right", "Window"},
- {"spawn", "Execute"},
- {"quit", "System"},
- {"power-off-monitors", "System"},
- {"screenshot", "Screenshot"},
- {"screenshot-window", "Screenshot"},
- {"toggle-overview", "Overview"},
- {"show-hotkey-overlay", "Overview"},
- {"next-window", "Alt-Tab"},
- {"previous-window", "Alt-Tab"},
- {"unknown-action", "Other"},
- }
-
- for _, tt := range tests {
- t.Run(tt.action, func(t *testing.T) {
- result := provider.categorizeByAction(tt.action)
- if result != tt.expected {
- t.Errorf("categorizeByAction(%q) = %q, want %q", tt.action, result, tt.expected)
- }
- })
- }
-}
-
-func TestNiriFormatRawAction(t *testing.T) {
- provider := NewNiriProvider("")
-
- tests := []struct {
- action string
- args []string
- expected string
- }{
- {"spawn", []string{"kitty"}, "spawn kitty"},
- {"spawn", []string{"dms", "ipc", "call"}, "spawn dms ipc call"},
- {"spawn", []string{"dms", "ipc", "call", "brightness", "increment", "5", ""}, `spawn dms ipc call brightness increment 5 ""`},
- {"spawn", []string{"dms", "ipc", "call", "dash", "toggle", ""}, `spawn dms ipc call dash toggle ""`},
- {"close-window", nil, "close-window"},
- {"fullscreen-window", nil, "fullscreen-window"},
- {"focus-workspace", []string{"1"}, "focus-workspace 1"},
- {"move-column-to-workspace", []string{"5"}, "move-column-to-workspace 5"},
- {"set-column-width", []string{"+10%"}, "set-column-width +10%"},
- }
-
- for _, tt := range tests {
- t.Run(tt.action, func(t *testing.T) {
- result := provider.formatRawAction(tt.action, tt.args)
- if result != tt.expected {
- t.Errorf("formatRawAction(%q, %v) = %q, want %q", tt.action, tt.args, result, tt.expected)
- }
- })
- }
-}
-
-func TestNiriFormatKey(t *testing.T) {
- provider := NewNiriProvider("")
-
- tests := []struct {
- mods []string
- key string
- expected string
- }{
- {[]string{"Mod"}, "Q", "Mod+Q"},
- {[]string{"Mod", "Shift"}, "F", "Mod+Shift+F"},
- {[]string{"Ctrl", "Alt"}, "Delete", "Ctrl+Alt+Delete"},
- {nil, "Print", "Print"},
- {[]string{}, "XF86AudioMute", "XF86AudioMute"},
- }
-
- for _, tt := range tests {
- t.Run(tt.expected, func(t *testing.T) {
- kb := &NiriKeyBinding{
- Mods: tt.mods,
- Key: tt.key,
- }
- result := provider.formatKey(kb)
- if result != tt.expected {
- t.Errorf("formatKey(%v) = %q, want %q", kb, result, tt.expected)
- }
- })
- }
-}
-
-func TestNiriDefaultConfigDir(t *testing.T) {
- originalXDG := os.Getenv("XDG_CONFIG_HOME")
- defer os.Setenv("XDG_CONFIG_HOME", originalXDG)
-
- os.Setenv("XDG_CONFIG_HOME", "/custom/config")
- dir := defaultNiriConfigDir()
- if dir != "/custom/config/niri" {
- t.Errorf("With XDG_CONFIG_HOME set, got %q, want %q", dir, "/custom/config/niri")
- }
-
- os.Unsetenv("XDG_CONFIG_HOME")
- dir = defaultNiriConfigDir()
- home, _ := os.UserHomeDir()
- expected := filepath.Join(home, ".config", "niri")
- if dir != expected {
- t.Errorf("Without XDG_CONFIG_HOME, got %q, want %q", dir, expected)
- }
-}
-
-func TestNiriGenerateBindsContent(t *testing.T) {
- provider := NewNiriProvider("")
-
- tests := []struct {
- name string
- binds map[string]*overrideBind
- expected string
- }{
- {
- name: "empty binds",
- binds: map[string]*overrideBind{},
- expected: "binds {}\n",
- },
- {
- name: "simple spawn bind",
- binds: map[string]*overrideBind{
- "Mod+T": {
- Key: "Mod+T",
- Action: "spawn kitty",
- Description: "Open Terminal",
- },
- },
- expected: `binds {
- Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
-}
-`,
- },
- {
- name: "spawn with multiple args",
- binds: map[string]*overrideBind{
- "Mod+Space": {
- Key: "Mod+Space",
- Action: `spawn "dms" "ipc" "call" "spotlight" "toggle"`,
- Description: "Application Launcher",
- },
- },
- expected: `binds {
- Mod+Space hotkey-overlay-title="Application Launcher" { spawn "dms" "ipc" "call" "spotlight" "toggle"; }
-}
-`,
- },
- {
- name: "bind with allow-when-locked",
- binds: map[string]*overrideBind{
- "XF86AudioMute": {
- Key: "XF86AudioMute",
- Action: `spawn "dms" "ipc" "call" "audio" "mute"`,
- Options: map[string]any{"allow-when-locked": true},
- },
- },
- expected: `binds {
- XF86AudioMute allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "mute"; }
-}
-`,
- },
- {
- name: "simple action without args",
- binds: map[string]*overrideBind{
- "Mod+Q": {
- Key: "Mod+Q",
- Action: "close-window",
- Description: "Close Window",
- },
- },
- expected: `binds {
- Mod+Q hotkey-overlay-title="Close Window" { close-window; }
-}
-`,
- },
- {
- name: "recent-windows action",
- binds: map[string]*overrideBind{
- "Alt+Tab": {
- Key: "Alt+Tab",
- Action: "next-window",
- },
- },
- expected: `binds {
-}
-
-recent-windows {
- binds {
- Alt+Tab { next-window; }
- }
-}
-`,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := provider.generateBindsContent(tt.binds)
- if result != tt.expected {
- t.Errorf("generateBindsContent() =\n%q\nwant:\n%q", result, tt.expected)
- }
- })
- }
-}
-
-func TestNiriGenerateBindsContentRoundTrip(t *testing.T) {
- provider := NewNiriProvider("")
-
- binds := map[string]*overrideBind{
- "Mod+Space": {
- Key: "Mod+Space",
- Action: `spawn "dms" "ipc" "call" "spotlight" "toggle"`,
- Description: "Application Launcher",
- },
- "XF86AudioMute": {
- Key: "XF86AudioMute",
- Action: `spawn "dms" "ipc" "call" "audio" "mute"`,
- Options: map[string]any{"allow-when-locked": true},
- },
- "Mod+Q": {
- Key: "Mod+Q",
- Action: "close-window",
- Description: "Close Window",
- },
- }
-
- content := provider.generateBindsContent(binds)
-
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write temp file: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("Failed to parse generated content: %v\nContent was:\n%s", err, content)
- }
-
- if len(result.Section.Keybinds) != 3 {
- t.Errorf("Expected 3 keybinds after round-trip, got %d", len(result.Section.Keybinds))
- }
-}
-
-func TestNiriEmptyArgsPreservation(t *testing.T) {
- provider := NewNiriProvider("")
-
- binds := map[string]*overrideBind{
- "XF86MonBrightnessUp": {
- Key: "XF86MonBrightnessUp",
- Action: `spawn dms ipc call brightness increment 5 ""`,
- Description: "Brightness Up",
- },
- "XF86MonBrightnessDown": {
- Key: "XF86MonBrightnessDown",
- Action: `spawn dms ipc call brightness decrement 5 ""`,
- Description: "Brightness Down",
- },
- "Super+Alt+Page_Up": {
- Key: "Super+Alt+Page_Up",
- Action: `spawn dms ipc call dash toggle ""`,
- Description: "Dashboard Toggle",
- },
- }
-
- content := provider.generateBindsContent(binds)
-
- tmpDir := t.TempDir()
- dmsDir := filepath.Join(tmpDir, "dms")
- if err := os.MkdirAll(dmsDir, 0o755); err != nil {
- t.Fatalf("Failed to create dms directory: %v", err)
- }
-
- bindsFile := filepath.Join(dmsDir, "binds.kdl")
- if err := os.WriteFile(bindsFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write binds file: %v", err)
- }
-
- testProvider := NewNiriProvider(tmpDir)
- loadedBinds, err := testProvider.loadOverrideBinds()
- if err != nil {
- t.Fatalf("Failed to load binds: %v\nContent was:\n%s", err, content)
- }
-
- for key, expected := range binds {
- loaded, ok := loadedBinds[key]
- if !ok {
- t.Errorf("Missing bind for key %s", key)
- continue
- }
- if loaded.Action != expected.Action {
- t.Errorf("Action mismatch for %s:\n got: %q\n want: %q", key, loaded.Action, expected.Action)
- }
- }
-}
-
-func TestNiriProviderWithRealWorldConfig(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
-
- content := `binds {
- Mod+Shift+Ctrl+D { debug-toggle-damage; }
- Super+D { spawn "niri" "msg" "action" "toggle-overview"; }
- Super+Tab repeat=false { toggle-overview; }
- Mod+Shift+Slash { show-hotkey-overlay; }
-
- Mod+T hotkey-overlay-title="Open Terminal" { spawn "kitty"; }
- Mod+Space hotkey-overlay-title="Application Launcher" {
- spawn "dms" "ipc" "call" "spotlight" "toggle";
- }
-
- XF86AudioRaiseVolume allow-when-locked=true {
- spawn "dms" "ipc" "call" "audio" "increment" "3";
- }
- XF86AudioLowerVolume allow-when-locked=true {
- spawn "dms" "ipc" "call" "audio" "decrement" "3";
- }
-
- Mod+Q repeat=false { close-window; }
- Mod+F { maximize-column; }
- Mod+Shift+F { fullscreen-window; }
-
- Mod+Left { focus-column-left; }
- Mod+Down { focus-window-down; }
- Mod+Up { focus-window-up; }
- Mod+Right { focus-column-right; }
-
- Mod+1 { focus-workspace 1; }
- Mod+2 { focus-workspace 2; }
- Mod+Shift+1 { move-column-to-workspace 1; }
- Mod+Shift+2 { move-column-to-workspace 2; }
-
- Print { screenshot; }
- Ctrl+Print { screenshot-screen; }
- Alt+Print { screenshot-window; }
-
- Mod+Shift+E { quit; }
-}
-
-recent-windows {
- binds {
- Alt+Tab { next-window scope="output"; }
- Alt+Shift+Tab { previous-window scope="output"; }
- }
-}
-`
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- provider := NewNiriProvider(tmpDir)
- cheatSheet, err := provider.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- totalBinds := 0
- for _, binds := range cheatSheet.Binds {
- totalBinds += len(binds)
- }
-
- if totalBinds < 20 {
- t.Errorf("Expected at least 20 keybinds, got %d", totalBinds)
- }
-
- if len(cheatSheet.Binds["Alt-Tab"]) < 2 {
- t.Errorf("Expected at least 2 Alt-Tab binds, got %d", len(cheatSheet.Binds["Alt-Tab"]))
- }
-}
-
-func TestNiriGenerateBindsContentNumericArgs(t *testing.T) {
- provider := NewNiriProvider("")
-
- tests := []struct {
- name string
- binds map[string]*overrideBind
- expected string
- }{
- {
- name: "workspace with numeric arg",
- binds: map[string]*overrideBind{
- "Mod+1": {
- Key: "Mod+1",
- Action: "focus-workspace 1",
- Description: "Focus Workspace 1",
- },
- },
- expected: `binds {
- Mod+1 hotkey-overlay-title="Focus Workspace 1" { focus-workspace 1; }
-}
-`,
- },
- {
- name: "workspace with large numeric arg",
- binds: map[string]*overrideBind{
- "Mod+0": {
- Key: "Mod+0",
- Action: "focus-workspace 10",
- Description: "Focus Workspace 10",
- },
- },
- expected: `binds {
- Mod+0 hotkey-overlay-title="Focus Workspace 10" { focus-workspace 10; }
-}
-`,
- },
- {
- name: "percentage string arg (should be quoted)",
- binds: map[string]*overrideBind{
- "Super+Minus": {
- Key: "Super+Minus",
- Action: `set-column-width "-10%"`,
- Description: "Adjust Column Width -10%",
- },
- },
- expected: `binds {
- Super+Minus hotkey-overlay-title="Adjust Column Width -10%" { set-column-width "-10%"; }
-}
-`,
- },
- {
- name: "positive percentage string arg",
- binds: map[string]*overrideBind{
- "Super+Equal": {
- Key: "Super+Equal",
- Action: `set-column-width "+10%"`,
- Description: "Adjust Column Width +10%",
- },
- },
- expected: `binds {
- Super+Equal hotkey-overlay-title="Adjust Column Width +10%" { set-column-width "+10%"; }
-}
-`,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := provider.generateBindsContent(tt.binds)
- if result != tt.expected {
- t.Errorf("generateBindsContent() =\n%q\nwant:\n%q", result, tt.expected)
- }
- })
- }
-}
-
-func TestNiriGenerateActionWithUnquotedPercentArg(t *testing.T) {
- provider := NewNiriProvider("")
-
- binds := map[string]*overrideBind{
- "Super+Equal": {
- Key: "Super+Equal",
- Action: "set-window-height +10%",
- Description: "Adjust Window Height +10%",
- },
- }
-
- content := provider.generateBindsContent(binds)
- expected := `binds {
- Super+Equal hotkey-overlay-title="Adjust Window Height +10%" { set-window-height "+10%"; }
-}
-`
- if content != expected {
- t.Errorf("Content mismatch.\nGot:\n%s\nWant:\n%s", content, expected)
- }
-}
-
-func TestNiriGenerateSpawnWithNumericArgs(t *testing.T) {
- provider := NewNiriProvider("")
-
- binds := map[string]*overrideBind{
- "XF86AudioLowerVolume": {
- Key: "XF86AudioLowerVolume",
- Action: `spawn "dms" "ipc" "call" "audio" "decrement" "3"`,
- Options: map[string]any{"allow-when-locked": true},
- },
- }
-
- content := provider.generateBindsContent(binds)
- expected := `binds {
- XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
-}
-`
- if content != expected {
- t.Errorf("Content mismatch.\nGot:\n%s\nWant:\n%s", content, expected)
- }
-}
-
-func TestNiriGenerateSpawnNumericArgFromCLI(t *testing.T) {
- provider := NewNiriProvider("")
-
- binds := map[string]*overrideBind{
- "XF86AudioLowerVolume": {
- Key: "XF86AudioLowerVolume",
- Action: "spawn dms ipc call audio decrement 3",
- Options: map[string]any{"allow-when-locked": true},
- },
- }
-
- content := provider.generateBindsContent(binds)
- expected := `binds {
- XF86AudioLowerVolume allow-when-locked=true { spawn "dms" "ipc" "call" "audio" "decrement" "3"; }
-}
-`
- if content != expected {
- t.Errorf("Content mismatch.\nGot:\n%s\nWant:\n%s", content, expected)
- }
-}
-
-func TestNiriGenerateWorkspaceBindsRoundTrip(t *testing.T) {
- provider := NewNiriProvider("")
-
- binds := map[string]*overrideBind{
- "Mod+1": {
- Key: "Mod+1",
- Action: "focus-workspace 1",
- Description: "Focus Workspace 1",
- },
- "Mod+2": {
- Key: "Mod+2",
- Action: "focus-workspace 2",
- Description: "Focus Workspace 2",
- },
- "Mod+Shift+1": {
- Key: "Mod+Shift+1",
- Action: "move-column-to-workspace 1",
- Description: "Move to Workspace 1",
- },
- "Super+Minus": {
- Key: "Super+Minus",
- Action: "set-column-width -10%",
- Description: "Adjust Column Width -10%",
- },
- }
-
- content := provider.generateBindsContent(binds)
-
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config.kdl")
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write temp file: %v", err)
- }
-
- result, err := ParseNiriKeys(tmpDir)
- if err != nil {
- t.Fatalf("Failed to parse generated content: %v\nContent was:\n%s", err, content)
- }
-
- if len(result.Section.Keybinds) != 4 {
- t.Errorf("Expected 4 keybinds after round-trip, got %d", len(result.Section.Keybinds))
- }
-
- foundFocusWS1 := false
- foundMoveWS1 := false
- foundSetWidth := false
-
- for _, kb := range result.Section.Keybinds {
- switch {
- case kb.Action == "focus-workspace" && len(kb.Args) > 0 && kb.Args[0] == "1":
- foundFocusWS1 = true
- case kb.Action == "move-column-to-workspace" && len(kb.Args) > 0 && kb.Args[0] == "1":
- foundMoveWS1 = true
- case kb.Action == "set-column-width" && len(kb.Args) > 0 && kb.Args[0] == "-10%":
- foundSetWidth = true
- }
- }
-
- if !foundFocusWS1 {
- t.Error("focus-workspace 1 not found after round-trip")
- }
- if !foundMoveWS1 {
- t.Error("move-column-to-workspace 1 not found after round-trip")
- }
- if !foundSetWidth {
- t.Error("set-column-width -10% not found after round-trip")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway.go
deleted file mode 100644
index 93ceb73..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway.go
+++ /dev/null
@@ -1,150 +0,0 @@
-package providers
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
-)
-
-type SwayProvider struct {
- configPath string
- isScroll bool
-}
-
-func NewSwayProvider(configPath string) *SwayProvider {
- isScroll := false
- _, scrollEnvSet := os.LookupEnv("SCROLLSOCK")
-
- if configPath == "" {
- configDir, err := os.UserConfigDir()
- if err != nil {
- configDir = ""
- }
- if scrollEnvSet {
- if configDir != "" {
- configPath = filepath.Join(configDir, "scroll")
- }
- isScroll = true
- } else {
- if configDir != "" {
- configPath = filepath.Join(configDir, "sway")
- }
- }
- } else {
- isScroll = strings.Contains(configPath, "scroll")
- }
-
- return &SwayProvider{
- configPath: configPath,
- isScroll: isScroll,
- }
-}
-
-func (s *SwayProvider) Name() string {
- if s == nil {
- if os.Getenv("SCROLLSOCK") != "" {
- return "scroll"
- }
- return "sway"
- }
-
- if s.isScroll {
- return "scroll"
- }
- return "sway"
-}
-
-func (s *SwayProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
- section, err := ParseSwayKeys(s.configPath)
- if err != nil {
- return nil, fmt.Errorf("failed to parse sway config: %w", err)
- }
-
- categorizedBinds := make(map[string][]keybinds.Keybind)
- s.convertSection(section, "", categorizedBinds)
-
- cheatSheetTitle := "Sway Keybinds"
- if s != nil && s.isScroll {
- cheatSheetTitle = "Scroll Keybinds"
- }
-
- return &keybinds.CheatSheet{
- Title: cheatSheetTitle,
- Provider: s.Name(),
- Binds: categorizedBinds,
- }, nil
-}
-
-func (s *SwayProvider) convertSection(section *SwaySection, subcategory string, categorizedBinds map[string][]keybinds.Keybind) {
- currentSubcat := subcategory
- if section.Name != "" {
- currentSubcat = section.Name
- }
-
- for _, kb := range section.Keybinds {
- category := s.categorizeByCommand(kb.Command)
- bind := s.convertKeybind(&kb, currentSubcat)
- categorizedBinds[category] = append(categorizedBinds[category], bind)
- }
-
- for _, child := range section.Children {
- s.convertSection(&child, currentSubcat, categorizedBinds)
- }
-}
-
-func (s *SwayProvider) categorizeByCommand(command string) string {
- command = strings.ToLower(command)
-
- switch {
- case strings.Contains(command, "scratchpad"):
- return "Scratchpad"
- case strings.Contains(command, "workspace") && strings.Contains(command, "output"):
- return "Monitor"
- case strings.Contains(command, "workspace"):
- return "Workspace"
- case strings.Contains(command, "output"):
- return "Monitor"
- case strings.Contains(command, "layout"):
- return "Layout"
- case command == "kill" ||
- command == "fullscreen" || strings.Contains(command, "fullscreen") ||
- command == "floating toggle" || strings.Contains(command, "floating") ||
- strings.Contains(command, "focus") ||
- strings.Contains(command, "move") ||
- strings.Contains(command, "resize") ||
- strings.Contains(command, "split"):
- return "Window"
- case strings.HasPrefix(command, "exec"):
- return "Execute"
- case command == "exit" || command == "reload":
- return "System"
- default:
- return "Other"
- }
-}
-
-func (s *SwayProvider) convertKeybind(kb *SwayKeyBinding, subcategory string) keybinds.Keybind {
- key := s.formatKey(kb)
- desc := kb.Comment
-
- if desc == "" {
- desc = kb.Command
- }
-
- return keybinds.Keybind{
- Key: key,
- Description: desc,
- Action: kb.Command,
- Subcategory: subcategory,
- }
-}
-
-func (s *SwayProvider) formatKey(kb *SwayKeyBinding) string {
- parts := make([]string, 0, len(kb.Mods)+1)
- parts = append(parts, kb.Mods...)
- parts = append(parts, kb.Key)
- return strings.Join(parts, "+")
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser.go
deleted file mode 100644
index 647c9f7..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser.go
+++ /dev/null
@@ -1,364 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "regexp"
- "strings"
-
- "github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
-)
-
-const (
- SwayTitleRegex = "#+!"
- SwayHideComment = "[hidden]"
-)
-
-var SwayModSeparators = []rune{'+', ' '}
-
-type SwayKeyBinding struct {
- Mods []string `json:"mods"`
- Key string `json:"key"`
- Command string `json:"command"`
- Comment string `json:"comment"`
-}
-
-type SwaySection struct {
- Children []SwaySection `json:"children"`
- Keybinds []SwayKeyBinding `json:"keybinds"`
- Name string `json:"name"`
-}
-
-type SwayParser struct {
- contentLines []string
- readingLine int
- variables map[string]string
-}
-
-func NewSwayParser() *SwayParser {
- return &SwayParser{
- contentLines: []string{},
- readingLine: 0,
- variables: make(map[string]string),
- }
-}
-
-func (p *SwayParser) ReadContent(path string) error {
- expandedPath, err := utils.ExpandPath(path)
- if err != nil {
- return err
- }
-
- info, err := os.Stat(expandedPath)
- if err != nil {
- return err
- }
-
- var files []string
- if info.IsDir() {
- mainConfig := filepath.Join(expandedPath, "config")
- if fileInfo, err := os.Stat(mainConfig); err == nil && fileInfo.Mode().IsRegular() {
- files = []string{mainConfig}
- } else {
- return os.ErrNotExist
- }
- } else {
- files = []string{expandedPath}
- }
-
- var combinedContent []string
- for _, file := range files {
- data, err := os.ReadFile(file)
- if err != nil {
- return err
- }
- combinedContent = append(combinedContent, string(data))
- }
-
- if len(combinedContent) == 0 {
- return os.ErrNotExist
- }
-
- fullContent := strings.Join(combinedContent, "\n")
- p.contentLines = strings.Split(fullContent, "\n")
- p.parseVariables()
- return nil
-}
-
-func (p *SwayParser) parseVariables() {
- setRegex := regexp.MustCompile(`^\s*set\s+\$(\w+)\s+(.+)$`)
- for _, line := range p.contentLines {
- matches := setRegex.FindStringSubmatch(line)
- if len(matches) == 3 {
- varName := matches[1]
- varValue := strings.TrimSpace(matches[2])
- p.variables[varName] = varValue
- }
- }
-}
-
-func (p *SwayParser) expandVariables(text string) string {
- result := text
- for varName, varValue := range p.variables {
- result = strings.ReplaceAll(result, "$"+varName, varValue)
- }
- return result
-}
-
-func swayAutogenerateComment(command string) string {
- command = strings.TrimSpace(command)
-
- if strings.HasPrefix(command, "exec ") {
- cmdPart := strings.TrimPrefix(command, "exec ")
- cmdPart = strings.TrimPrefix(cmdPart, "--no-startup-id ")
- return cmdPart
- }
-
- switch {
- case command == "kill":
- return "Close window"
- case command == "exit":
- return "Exit Sway"
- case command == "reload":
- return "Reload configuration"
- case strings.HasPrefix(command, "fullscreen"):
- return "Toggle fullscreen"
- case strings.HasPrefix(command, "floating toggle"):
- return "Float/unfloat window"
- case strings.HasPrefix(command, "focus mode_toggle"):
- return "Toggle focus mode"
- case strings.HasPrefix(command, "focus parent"):
- return "Focus parent container"
- case strings.HasPrefix(command, "focus left"):
- return "Focus left"
- case strings.HasPrefix(command, "focus right"):
- return "Focus right"
- case strings.HasPrefix(command, "focus up"):
- return "Focus up"
- case strings.HasPrefix(command, "focus down"):
- return "Focus down"
- case strings.HasPrefix(command, "focus output"):
- return "Focus monitor"
- case strings.HasPrefix(command, "move left"):
- return "Move window left"
- case strings.HasPrefix(command, "move right"):
- return "Move window right"
- case strings.HasPrefix(command, "move up"):
- return "Move window up"
- case strings.HasPrefix(command, "move down"):
- return "Move window down"
- case strings.HasPrefix(command, "move container to workspace"):
- if strings.Contains(command, "prev") {
- return "Move to previous workspace"
- }
- if strings.Contains(command, "next") {
- return "Move to next workspace"
- }
- parts := strings.Fields(command)
- if len(parts) > 4 {
- return "Move to workspace " + parts[len(parts)-1]
- }
- return "Move to workspace"
- case strings.HasPrefix(command, "move workspace to output"):
- return "Move workspace to monitor"
- case strings.HasPrefix(command, "workspace"):
- if strings.Contains(command, "prev") {
- return "Previous workspace"
- }
- if strings.Contains(command, "next") {
- return "Next workspace"
- }
- parts := strings.Fields(command)
- if len(parts) > 1 {
- wsNum := parts[len(parts)-1]
- return "Workspace " + wsNum
- }
- return "Switch workspace"
- case strings.HasPrefix(command, "layout"):
- parts := strings.Fields(command)
- if len(parts) > 1 {
- return "Layout " + parts[1]
- }
- return "Change layout"
- case strings.HasPrefix(command, "split"):
- if strings.Contains(command, "h") {
- return "Split horizontal"
- }
- if strings.Contains(command, "v") {
- return "Split vertical"
- }
- return "Split container"
- case strings.HasPrefix(command, "resize"):
- return "Resize window"
- case strings.Contains(command, "scratchpad"):
- return "Toggle scratchpad"
- default:
- return command
- }
-}
-
-func (p *SwayParser) getKeybindAtLine(lineNumber int) *SwayKeyBinding {
- if lineNumber >= len(p.contentLines) {
- return nil
- }
-
- line := p.contentLines[lineNumber]
-
- bindMatch := regexp.MustCompile(`^\s*(bindsym|bindcode)\s+(.+)$`)
- matches := bindMatch.FindStringSubmatch(line)
- if len(matches) < 3 {
- return nil
- }
-
- content := matches[2]
-
- parts := strings.SplitN(content, "#", 2)
- keys := strings.TrimSpace(parts[0])
-
- var comment string
- if len(parts) > 1 {
- comment = strings.TrimSpace(parts[1])
- }
-
- if strings.HasPrefix(comment, SwayHideComment) {
- return nil
- }
-
- flags := ""
- if strings.HasPrefix(keys, "--") {
- spaceIdx := strings.Index(keys, " ")
- if spaceIdx > 0 {
- flags = keys[:spaceIdx]
- keys = strings.TrimSpace(keys[spaceIdx+1:])
- }
- }
-
- keyParts := strings.Fields(keys)
- if len(keyParts) < 2 {
- return nil
- }
-
- keyCombo := keyParts[0]
- keyCombo = p.expandVariables(keyCombo)
- command := strings.Join(keyParts[1:], " ")
- command = p.expandVariables(command)
-
- var modList []string
- var key string
-
- modstring := keyCombo + string(SwayModSeparators[0])
- pos := 0
- for index, char := range modstring {
- isModSep := false
- for _, sep := range SwayModSeparators {
- if char == sep {
- isModSep = true
- break
- }
- }
- if isModSep {
- if index-pos > 0 {
- part := modstring[pos:index]
- if swayIsMod(part) {
- modList = append(modList, part)
- } else {
- key = part
- }
- }
- pos = index + 1
- }
- }
-
- if comment == "" {
- comment = swayAutogenerateComment(command)
- }
-
- _ = flags
-
- return &SwayKeyBinding{
- Mods: modList,
- Key: key,
- Command: command,
- Comment: comment,
- }
-}
-
-func swayIsMod(s string) bool {
- s = strings.ToLower(s)
- if s == "mod1" || s == "mod2" || s == "mod3" || s == "mod4" || s == "mod5" ||
- s == "shift" || s == "control" || s == "ctrl" || s == "alt" || s == "super" ||
- strings.HasPrefix(s, "$") {
- return true
- }
-
- isNumeric := true
- for _, c := range s {
- if c < '0' || c > '9' {
- isNumeric = false
- break
- }
- }
- if isNumeric && len(s) >= 2 {
- return true
- }
- return false
-}
-
-func (p *SwayParser) getBindsRecursive(currentContent *SwaySection, scope int) *SwaySection {
- titleRegex := regexp.MustCompile(SwayTitleRegex)
-
- for p.readingLine < len(p.contentLines) {
- line := p.contentLines[p.readingLine]
-
- loc := titleRegex.FindStringIndex(line)
- if loc != nil && loc[0] == 0 {
- headingScope := strings.Index(line, "!")
-
- if headingScope <= scope {
- p.readingLine--
- return currentContent
- }
-
- sectionName := strings.TrimSpace(line[headingScope+1:])
- p.readingLine++
-
- childSection := &SwaySection{
- Children: []SwaySection{},
- Keybinds: []SwayKeyBinding{},
- Name: sectionName,
- }
- result := p.getBindsRecursive(childSection, headingScope)
- currentContent.Children = append(currentContent.Children, *result)
-
- } else if line == "" || (!strings.Contains(line, "bindsym") && !strings.Contains(line, "bindcode")) {
-
- } else {
- keybind := p.getKeybindAtLine(p.readingLine)
- if keybind != nil {
- currentContent.Keybinds = append(currentContent.Keybinds, *keybind)
- }
- }
-
- p.readingLine++
- }
-
- return currentContent
-}
-
-func (p *SwayParser) ParseKeys() *SwaySection {
- p.readingLine = 0
- rootSection := &SwaySection{
- Children: []SwaySection{},
- Keybinds: []SwayKeyBinding{},
- Name: "",
- }
- return p.getBindsRecursive(rootSection, 0)
-}
-
-func ParseSwayKeys(path string) (*SwaySection, error) {
- parser := NewSwayParser()
- if err := parser.ReadContent(path); err != nil {
- return nil, err
- }
- return parser.ParseKeys(), nil
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser_test.go
deleted file mode 100644
index 6bb5ecf..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_parser_test.go
+++ /dev/null
@@ -1,471 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestSwayAutogenerateComment(t *testing.T) {
- tests := []struct {
- command string
- expected string
- }{
- {"exec kitty", "kitty"},
- {"exec --no-startup-id firefox", "firefox"},
- {"kill", "Close window"},
- {"exit", "Exit Sway"},
- {"reload", "Reload configuration"},
- {"fullscreen toggle", "Toggle fullscreen"},
- {"floating toggle", "Float/unfloat window"},
- {"focus mode_toggle", "Toggle focus mode"},
- {"focus parent", "Focus parent container"},
- {"focus left", "Focus left"},
- {"focus right", "Focus right"},
- {"focus up", "Focus up"},
- {"focus down", "Focus down"},
- {"focus output left", "Focus monitor"},
- {"move left", "Move window left"},
- {"move right", "Move window right"},
- {"move up", "Move window up"},
- {"move down", "Move window down"},
- {"move container to workspace number 1", "Move to workspace 1"},
- {"move container to workspace prev", "Move to previous workspace"},
- {"move container to workspace next", "Move to next workspace"},
- {"move workspace to output left", "Move workspace to monitor"},
- {"workspace number 1", "Workspace 1"},
- {"workspace prev", "Previous workspace"},
- {"workspace next", "Next workspace"},
- {"layout tabbed", "Layout tabbed"},
- {"layout stacking", "Layout stacking"},
- {"splith", "Split horizontal"},
- {"splitv", "Split vertical"},
- {"resize grow width 10 ppt", "Resize window"},
- {"move scratchpad", "Toggle scratchpad"},
- }
-
- for _, tt := range tests {
- t.Run(tt.command, func(t *testing.T) {
- result := swayAutogenerateComment(tt.command)
- if result != tt.expected {
- t.Errorf("swayAutogenerateComment(%q) = %q, want %q",
- tt.command, result, tt.expected)
- }
- })
- }
-}
-
-func TestSwayGetKeybindAtLine(t *testing.T) {
- tests := []struct {
- name string
- line string
- expected *SwayKeyBinding
- }{
- {
- name: "basic_keybind",
- line: "bindsym Mod4+q kill",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "q",
- Command: "kill",
- Comment: "Close window",
- },
- },
- {
- name: "keybind_with_exec",
- line: "bindsym Mod4+t exec kitty",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "t",
- Command: "exec kitty",
- Comment: "kitty",
- },
- },
- {
- name: "keybind_with_comment",
- line: "bindsym Mod4+Space exec dms ipc call spotlight toggle # Open launcher",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "Space",
- Command: "exec dms ipc call spotlight toggle",
- Comment: "Open launcher",
- },
- },
- {
- name: "keybind_hidden",
- line: "bindsym Mod4+h exec secret # [hidden]",
- expected: nil,
- },
- {
- name: "keybind_multiple_mods",
- line: "bindsym Mod4+Shift+e exit",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4", "Shift"},
- Key: "e",
- Command: "exit",
- Comment: "Exit Sway",
- },
- },
- {
- name: "keybind_no_mods",
- line: "bindsym Print exec grim screenshot.png",
- expected: &SwayKeyBinding{
- Mods: []string{},
- Key: "Print",
- Command: "exec grim screenshot.png",
- Comment: "grim screenshot.png",
- },
- },
- {
- name: "keybind_with_flags",
- line: "bindsym --release Mod4+x exec notify-send released",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "x",
- Command: "exec notify-send released",
- Comment: "notify-send released",
- },
- },
- {
- name: "keybind_focus_direction",
- line: "bindsym Mod4+Left focus left",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "Left",
- Command: "focus left",
- Comment: "Focus left",
- },
- },
- {
- name: "keybind_workspace",
- line: "bindsym Mod4+1 workspace number 1",
- expected: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "1",
- Command: "workspace number 1",
- Comment: "Workspace 1",
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- parser := NewSwayParser()
- parser.contentLines = []string{tt.line}
- result := parser.getKeybindAtLine(0)
-
- if tt.expected == nil {
- if result != nil {
- t.Errorf("expected nil, got %+v", result)
- }
- return
- }
-
- if result == nil {
- t.Errorf("expected %+v, got nil", tt.expected)
- return
- }
-
- if result.Key != tt.expected.Key {
- t.Errorf("Key = %q, want %q", result.Key, tt.expected.Key)
- }
- if result.Command != tt.expected.Command {
- t.Errorf("Command = %q, want %q", result.Command, tt.expected.Command)
- }
- if result.Comment != tt.expected.Comment {
- t.Errorf("Comment = %q, want %q", result.Comment, tt.expected.Comment)
- }
- if len(result.Mods) != len(tt.expected.Mods) {
- t.Errorf("Mods length = %d, want %d", len(result.Mods), len(tt.expected.Mods))
- } else {
- for i := range result.Mods {
- if result.Mods[i] != tt.expected.Mods[i] {
- t.Errorf("Mods[%d] = %q, want %q", i, result.Mods[i], tt.expected.Mods[i])
- }
- }
- }
- })
- }
-}
-
-func TestSwayVariableExpansion(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config")
-
- content := `set $mod Mod4
-set $term kitty
-set $menu rofi
-
-bindsym $mod+t exec $term
-bindsym $mod+d exec $menu
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseSwayKeys(configFile)
- if err != nil {
- t.Fatalf("ParseSwayKeys failed: %v", err)
- }
-
- if len(section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds, got %d", len(section.Keybinds))
- }
-
- if len(section.Keybinds) > 0 {
- if section.Keybinds[0].Mods[0] != "Mod4" {
- t.Errorf("Expected Mod4, got %q", section.Keybinds[0].Mods[0])
- }
- if section.Keybinds[0].Command != "exec kitty" {
- t.Errorf("Expected 'exec kitty', got %q", section.Keybinds[0].Command)
- }
- }
-
- if len(section.Keybinds) > 1 {
- if section.Keybinds[1].Command != "exec rofi" {
- t.Errorf("Expected 'exec rofi', got %q", section.Keybinds[1].Command)
- }
- }
-}
-
-func TestSwayParseKeysWithSections(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config")
-
- content := `set $mod Mod4
-
-##! Window Management
-bindsym $mod+q kill
-bindsym $mod+f fullscreen toggle
-
-###! Focus
-bindsym $mod+Left focus left
-bindsym $mod+Right focus right
-
-##! Applications
-bindsym $mod+t exec kitty # Terminal
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseSwayKeys(tmpDir)
- if err != nil {
- t.Fatalf("ParseSwayKeys failed: %v", err)
- }
-
- if len(section.Children) != 2 {
- t.Errorf("Expected 2 top-level sections, got %d", len(section.Children))
- }
-
- if len(section.Children) >= 1 {
- windowMgmt := section.Children[0]
- if windowMgmt.Name != "Window Management" {
- t.Errorf("First section name = %q, want %q", windowMgmt.Name, "Window Management")
- }
- if len(windowMgmt.Keybinds) != 2 {
- t.Errorf("Window Management keybinds = %d, want 2", len(windowMgmt.Keybinds))
- }
-
- if len(windowMgmt.Children) != 1 {
- t.Errorf("Window Management children = %d, want 1", len(windowMgmt.Children))
- } else {
- focus := windowMgmt.Children[0]
- if focus.Name != "Focus" {
- t.Errorf("Focus section name = %q, want %q", focus.Name, "Focus")
- }
- if len(focus.Keybinds) != 2 {
- t.Errorf("Focus keybinds = %d, want 2", len(focus.Keybinds))
- }
- }
- }
-
- if len(section.Children) >= 2 {
- apps := section.Children[1]
- if apps.Name != "Applications" {
- t.Errorf("Second section name = %q, want %q", apps.Name, "Applications")
- }
- if len(apps.Keybinds) != 1 {
- t.Errorf("Applications keybinds = %d, want 1", len(apps.Keybinds))
- }
- if len(apps.Keybinds) > 0 && apps.Keybinds[0].Comment != "Terminal" {
- t.Errorf("Applications keybind comment = %q, want %q", apps.Keybinds[0].Comment, "Terminal")
- }
- }
-}
-
-func TestSwayReadContentErrors(t *testing.T) {
- tests := []struct {
- name string
- path string
- }{
- {
- name: "nonexistent_directory",
- path: "/nonexistent/path/that/does/not/exist",
- },
- {
- name: "empty_directory",
- path: t.TempDir(),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- _, err := ParseSwayKeys(tt.path)
- if err == nil {
- t.Error("Expected error, got nil")
- }
- })
- }
-}
-
-func TestSwayReadContentWithTildeExpansion(t *testing.T) {
- homeDir, err := os.UserHomeDir()
- if err != nil {
- t.Skip("Cannot get home directory")
- }
-
- tmpSubdir := filepath.Join(homeDir, ".config", "test-sway-"+t.Name())
- if err := os.MkdirAll(tmpSubdir, 0o755); err != nil {
- t.Skip("Cannot create test directory in home")
- }
- defer os.RemoveAll(tmpSubdir)
-
- configFile := filepath.Join(tmpSubdir, "config")
- if err := os.WriteFile(configFile, []byte("bindsym Mod4+q kill\n"), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- relPath, err := filepath.Rel(homeDir, tmpSubdir)
- if err != nil {
- t.Skip("Cannot create relative path")
- }
-
- parser := NewSwayParser()
- tildePathMatch := "~/" + relPath
- err = parser.ReadContent(tildePathMatch)
-
- if err != nil {
- t.Errorf("ReadContent with tilde path failed: %v", err)
- }
-}
-
-func TestSwayEmptyAndCommentLines(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config")
-
- content := `
-# This is a comment
-bindsym Mod4+q kill
-
-# Another comment
-
-bindsym Mod4+t exec kitty
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseSwayKeys(configFile)
- if err != nil {
- t.Fatalf("ParseSwayKeys failed: %v", err)
- }
-
- if len(section.Keybinds) != 2 {
- t.Errorf("Expected 2 keybinds (comments ignored), got %d", len(section.Keybinds))
- }
-}
-
-func TestSwayRealWorldConfig(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config")
-
- content := `set $mod Mod4
-set $term kitty
-
-## Application Launchers
-bindsym $mod+t exec $term
-bindsym $mod+Space exec rofi
-
-## Window Management
-bindsym $mod+q kill
-bindsym $mod+f fullscreen toggle
-
-## Focus Navigation
-bindsym $mod+Left focus left
-bindsym $mod+Right focus right
-
-## Workspace Navigation
-bindsym $mod+1 workspace number 1
-bindsym $mod+2 workspace number 2
-bindsym $mod+Shift+1 move container to workspace number 1
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- section, err := ParseSwayKeys(configFile)
- if err != nil {
- t.Fatalf("ParseSwayKeys failed: %v", err)
- }
-
- if len(section.Keybinds) < 9 {
- t.Errorf("Expected at least 9 keybinds, got %d", len(section.Keybinds))
- }
-
- foundExec := false
- foundKill := false
- foundWorkspace := false
-
- for _, kb := range section.Keybinds {
- if kb.Command == "exec kitty" {
- foundExec = true
- }
- if kb.Command == "kill" {
- foundKill = true
- }
- if kb.Command == "workspace number 1" {
- foundWorkspace = true
- }
- }
-
- if !foundExec {
- t.Error("Did not find exec kitty keybind")
- }
- if !foundKill {
- t.Error("Did not find kill keybind")
- }
- if !foundWorkspace {
- t.Error("Did not find workspace 1 keybind")
- }
-}
-
-func TestSwayIsMod(t *testing.T) {
- tests := []struct {
- input string
- expected bool
- }{
- {"Mod4", true},
- {"Shift", true},
- {"Control", true},
- {"Alt", true},
- {"Super", true},
- {"$mod", true},
- {"Left", false},
- {"q", false},
- {"1", false},
- }
-
- for _, tt := range tests {
- t.Run(tt.input, func(t *testing.T) {
- result := swayIsMod(tt.input)
- if result != tt.expected {
- t.Errorf("swayIsMod(%q) = %v, want %v", tt.input, result, tt.expected)
- }
- })
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_test.go
deleted file mode 100644
index fd476d8..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/providers/sway_test.go
+++ /dev/null
@@ -1,293 +0,0 @@
-package providers
-
-import (
- "os"
- "path/filepath"
- "testing"
-)
-
-func TestSwayProviderName(t *testing.T) {
- provider := NewSwayProvider("")
- if provider.Name() != "sway" {
- t.Errorf("Name() = %q, want %q", provider.Name(), "sway")
- }
-}
-
-func TestSwayProviderDefaultPath(t *testing.T) {
- provider := NewSwayProvider("")
- configDir, err := os.UserConfigDir()
- if err != nil {
- t.Skip("UserConfigDir not available")
- }
- expected := filepath.Join(configDir, "sway")
- if provider.configPath != expected {
- t.Errorf("configPath = %q, want %q", provider.configPath, expected)
- }
-}
-
-func TestSwayProviderCustomPath(t *testing.T) {
- customPath := "/custom/path"
- provider := NewSwayProvider(customPath)
- if provider.configPath != customPath {
- t.Errorf("configPath = %q, want %q", provider.configPath, customPath)
- }
-}
-
-func TestSwayCategorizeByCommand(t *testing.T) {
- tests := []struct {
- command string
- expected string
- }{
- {"workspace number 1", "Workspace"},
- {"workspace prev", "Workspace"},
- {"workspace next", "Workspace"},
- {"move container to workspace number 1", "Workspace"},
- {"focus output left", "Monitor"},
- {"move workspace to output right", "Monitor"},
- {"kill", "Window"},
- {"fullscreen toggle", "Window"},
- {"floating toggle", "Window"},
- {"focus left", "Window"},
- {"focus right", "Window"},
- {"move left", "Window"},
- {"move right", "Window"},
- {"resize grow width 10px", "Window"},
- {"splith", "Window"},
- {"splitv", "Window"},
- {"layout tabbed", "Layout"},
- {"layout stacking", "Layout"},
- {"move scratchpad", "Scratchpad"},
- {"scratchpad show", "Scratchpad"},
- {"exec kitty", "Execute"},
- {"exec --no-startup-id firefox", "Execute"},
- {"exit", "System"},
- {"reload", "System"},
- {"unknown command", "Other"},
- }
-
- provider := NewSwayProvider("")
- for _, tt := range tests {
- t.Run(tt.command, func(t *testing.T) {
- result := provider.categorizeByCommand(tt.command)
- if result != tt.expected {
- t.Errorf("categorizeByCommand(%q) = %q, want %q", tt.command, result, tt.expected)
- }
- })
- }
-}
-
-func TestSwayFormatKey(t *testing.T) {
- tests := []struct {
- name string
- keybind *SwayKeyBinding
- expected string
- }{
- {
- name: "single_mod",
- keybind: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "q",
- },
- expected: "Mod4+q",
- },
- {
- name: "multiple_mods",
- keybind: &SwayKeyBinding{
- Mods: []string{"Mod4", "Shift"},
- Key: "e",
- },
- expected: "Mod4+Shift+e",
- },
- {
- name: "no_mods",
- keybind: &SwayKeyBinding{
- Mods: []string{},
- Key: "Print",
- },
- expected: "Print",
- },
- }
-
- provider := NewSwayProvider("")
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := provider.formatKey(tt.keybind)
- if result != tt.expected {
- t.Errorf("formatKey() = %q, want %q", result, tt.expected)
- }
- })
- }
-}
-
-func TestSwayConvertKeybind(t *testing.T) {
- tests := []struct {
- name string
- keybind *SwayKeyBinding
- wantKey string
- wantDesc string
- }{
- {
- name: "with_comment",
- keybind: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "t",
- Command: "exec kitty",
- Comment: "Open terminal",
- },
- wantKey: "Mod4+t",
- wantDesc: "Open terminal",
- },
- {
- name: "without_comment",
- keybind: &SwayKeyBinding{
- Mods: []string{"Mod4"},
- Key: "r",
- Command: "reload",
- Comment: "",
- },
- wantKey: "Mod4+r",
- wantDesc: "reload",
- },
- }
-
- provider := NewSwayProvider("")
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- result := provider.convertKeybind(tt.keybind, "")
- if result.Key != tt.wantKey {
- t.Errorf("convertKeybind().Key = %q, want %q", result.Key, tt.wantKey)
- }
- if result.Description != tt.wantDesc {
- t.Errorf("convertKeybind().Description = %q, want %q", result.Description, tt.wantDesc)
- }
- })
- }
-}
-
-func TestSwayGetCheatSheet(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config")
-
- content := `set $mod Mod4
-set $term kitty
-
-# System
-bindsym $mod+Shift+c reload
-bindsym $mod+Shift+e exit
-
-# Applications
-bindsym $mod+t exec $term
-bindsym $mod+Space exec rofi
-
-# Window Management
-bindsym $mod+q kill
-bindsym $mod+f fullscreen toggle
-bindsym $mod+Left focus left
-bindsym $mod+Right focus right
-
-# Workspace
-bindsym $mod+1 workspace number 1
-bindsym $mod+2 workspace number 2
-bindsym $mod+Shift+1 move container to workspace number 1
-
-# Layout
-bindsym $mod+s layout stacking
-bindsym $mod+w layout tabbed
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- provider := NewSwayProvider(tmpDir)
- sheet, err := provider.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- if sheet == nil {
- t.Fatal("Expected non-nil CheatSheet")
- }
-
- if sheet.Title != "Sway Keybinds" {
- t.Errorf("Title = %q, want %q", sheet.Title, "Sway Keybinds")
- }
-
- if sheet.Provider != "sway" {
- t.Errorf("Provider = %q, want %q", sheet.Provider, "sway")
- }
-
- categories := []string{"System", "Execute", "Window", "Workspace", "Layout"}
- for _, category := range categories {
- if _, exists := sheet.Binds[category]; !exists {
- t.Errorf("Expected category %q to exist", category)
- }
- }
-
- if len(sheet.Binds["System"]) < 2 {
- t.Error("Expected at least 2 System keybinds")
- }
- if len(sheet.Binds["Execute"]) < 2 {
- t.Error("Expected at least 2 Execute keybinds")
- }
- if len(sheet.Binds["Window"]) < 4 {
- t.Error("Expected at least 4 Window keybinds")
- }
- if len(sheet.Binds["Workspace"]) < 3 {
- t.Error("Expected at least 3 Workspace keybinds")
- }
-}
-
-func TestSwayGetCheatSheetError(t *testing.T) {
- provider := NewSwayProvider("/nonexistent/path")
- _, err := provider.GetCheatSheet()
- if err == nil {
- t.Error("Expected error for nonexistent path, got nil")
- }
-}
-
-func TestSwayIntegration(t *testing.T) {
- tmpDir := t.TempDir()
- configFile := filepath.Join(tmpDir, "config")
-
- content := `set $mod Mod4
-
-bindsym $mod+t exec kitty # Terminal
-bindsym $mod+q kill
-bindsym $mod+f fullscreen toggle
-bindsym $mod+1 workspace number 1
-`
-
- if err := os.WriteFile(configFile, []byte(content), 0o644); err != nil {
- t.Fatalf("Failed to write test config: %v", err)
- }
-
- provider := NewSwayProvider(tmpDir)
- sheet, err := provider.GetCheatSheet()
- if err != nil {
- t.Fatalf("GetCheatSheet failed: %v", err)
- }
-
- totalBinds := 0
- for _, binds := range sheet.Binds {
- totalBinds += len(binds)
- }
-
- expectedBinds := 4
- if totalBinds != expectedBinds {
- t.Errorf("Expected %d total keybinds, got %d", expectedBinds, totalBinds)
- }
-
- foundTerminal := false
- for _, binds := range sheet.Binds {
- for _, bind := range binds {
- if bind.Description == "Terminal" && bind.Key == "Mod4+t" {
- foundTerminal = true
- }
- }
- }
-
- if !foundTerminal {
- t.Error("Did not find terminal keybind with correct key and description")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry.go
deleted file mode 100644
index d726838..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package keybinds
-
-import (
- "fmt"
- "sync"
-)
-
-type Registry struct {
- mu sync.RWMutex
- providers map[string]Provider
-}
-
-func NewRegistry() *Registry {
- return &Registry{
- providers: make(map[string]Provider),
- }
-}
-
-func (r *Registry) Register(provider Provider) error {
- if provider == nil {
- return fmt.Errorf("cannot register nil provider")
- }
-
- name := provider.Name()
- if name == "" {
- return fmt.Errorf("provider name cannot be empty")
- }
-
- r.mu.Lock()
- defer r.mu.Unlock()
-
- if _, exists := r.providers[name]; exists {
- return fmt.Errorf("provider %q already registered", name)
- }
-
- r.providers[name] = provider
- return nil
-}
-
-func (r *Registry) Get(name string) (Provider, error) {
- r.mu.RLock()
- defer r.mu.RUnlock()
-
- provider, exists := r.providers[name]
- if !exists {
- return nil, fmt.Errorf("provider %q not found", name)
- }
-
- return provider, nil
-}
-
-func (r *Registry) List() []string {
- r.mu.RLock()
- defer r.mu.RUnlock()
-
- names := make([]string, 0, len(r.providers))
- for name := range r.providers {
- names = append(names, name)
- }
- return names
-}
-
-var defaultRegistry = NewRegistry()
-
-func GetDefaultRegistry() *Registry {
- return defaultRegistry
-}
-
-func Register(provider Provider) error {
- return defaultRegistry.Register(provider)
-}
-
-func Get(name string) (Provider, error) {
- return defaultRegistry.Get(name)
-}
-
-func List() []string {
- return defaultRegistry.List()
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry_test.go
deleted file mode 100644
index c3ddfee..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/registry_test.go
+++ /dev/null
@@ -1,183 +0,0 @@
-package keybinds
-
-import (
- "testing"
-)
-
-type mockProvider struct {
- name string
- err error
-}
-
-func (m *mockProvider) Name() string {
- return m.name
-}
-
-func (m *mockProvider) GetCheatSheet() (*CheatSheet, error) {
- if m.err != nil {
- return nil, m.err
- }
- return &CheatSheet{
- Title: "Test",
- Provider: m.name,
- Binds: make(map[string][]Keybind),
- }, nil
-}
-
-func TestNewRegistry(t *testing.T) {
- r := NewRegistry()
- if r == nil {
- t.Fatal("NewRegistry returned nil")
- }
-
- if r.providers == nil {
- t.Error("providers map is nil")
- }
-}
-
-func TestRegisterProvider(t *testing.T) {
- tests := []struct {
- name string
- provider Provider
- expectError bool
- errorMsg string
- }{
- {
- name: "valid provider",
- provider: &mockProvider{name: "test"},
- expectError: false,
- },
- {
- name: "nil provider",
- provider: nil,
- expectError: true,
- errorMsg: "cannot register nil provider",
- },
- {
- name: "empty name",
- provider: &mockProvider{name: ""},
- expectError: true,
- errorMsg: "provider name cannot be empty",
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- r := NewRegistry()
- err := r.Register(tt.provider)
-
- if tt.expectError {
- if err == nil {
- t.Error("expected error, got nil")
- }
- return
- }
-
- if err != nil {
- t.Errorf("unexpected error: %v", err)
- }
- })
- }
-}
-
-func TestRegisterDuplicate(t *testing.T) {
- r := NewRegistry()
- p := &mockProvider{name: "test"}
-
- if err := r.Register(p); err != nil {
- t.Fatalf("first registration failed: %v", err)
- }
-
- err := r.Register(p)
- if err == nil {
- t.Error("expected error when registering duplicate, got nil")
- }
-}
-
-func TestGetProvider(t *testing.T) {
- r := NewRegistry()
- p := &mockProvider{name: "test"}
-
- if err := r.Register(p); err != nil {
- t.Fatalf("registration failed: %v", err)
- }
-
- retrieved, err := r.Get("test")
- if err != nil {
- t.Fatalf("Get failed: %v", err)
- }
-
- if retrieved.Name() != "test" {
- t.Errorf("Got provider name %q, want %q", retrieved.Name(), "test")
- }
-}
-
-func TestGetNonexistent(t *testing.T) {
- r := NewRegistry()
-
- _, err := r.Get("nonexistent")
- if err == nil {
- t.Error("expected error for nonexistent provider, got nil")
- }
-}
-
-func TestListProviders(t *testing.T) {
- r := NewRegistry()
-
- p1 := &mockProvider{name: "test1"}
- p2 := &mockProvider{name: "test2"}
- p3 := &mockProvider{name: "test3"}
-
- r.Register(p1)
- r.Register(p2)
- r.Register(p3)
-
- list := r.List()
-
- if len(list) != 3 {
- t.Errorf("expected 3 providers, got %d", len(list))
- }
-
- found := make(map[string]bool)
- for _, name := range list {
- found[name] = true
- }
-
- expected := []string{"test1", "test2", "test3"}
- for _, name := range expected {
- if !found[name] {
- t.Errorf("expected provider %q not found in list", name)
- }
- }
-}
-
-func TestDefaultRegistry(t *testing.T) {
- p := &mockProvider{name: "default-test"}
-
- err := Register(p)
- if err != nil {
- t.Fatalf("Register failed: %v", err)
- }
-
- retrieved, err := Get("default-test")
- if err != nil {
- t.Fatalf("Get failed: %v", err)
- }
-
- if retrieved.Name() != "default-test" {
- t.Errorf("Got provider name %q, want %q", retrieved.Name(), "default-test")
- }
-
- list := List()
- found := false
- for _, name := range list {
- if name == "default-test" {
- found = true
- break
- }
- }
-
- if !found {
- t.Error("provider not found in default registry list")
- }
-}
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/types.go
deleted file mode 100644
index 109b53b..0000000
--- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/keybinds/types.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package keybinds
-
-type Keybind struct {
- Key string `json:"key"`
- Description string `json:"desc"`
- Action string `json:"action,omitempty"`
- Subcategory string `json:"subcat,omitempty"`
- Source string `json:"source,omitempty"`
- HideOnOverlay bool `json:"hideOnOverlay,omitempty"`
- CooldownMs int `json:"cooldownMs,omitempty"`
- Flags string `json:"flags,omitempty"` // Hyprland bind flags: e=repeat, l=locked, r=release, o=long-press
- AllowWhenLocked bool `json:"allowWhenLocked,omitempty"`
- AllowInhibiting *bool `json:"allowInhibiting,omitempty"` // nil=default(true), false=explicitly disabled
- Repeat *bool `json:"repeat,omitempty"` // nil=default(true), false=explicitly disabled
- Conflict *Keybind `json:"conflict,omitempty"`
-}
-
-type DMSBindsStatus struct {
- Exists bool `json:"exists"`
- Included bool `json:"included"`
- IncludePosition int `json:"includePosition"`
- TotalIncludes int `json:"totalIncludes"`
- BindsAfterDMS int `json:"bindsAfterDms"`
- Effective bool `json:"effective"`
- OverriddenBy int `json:"overriddenBy"`
- StatusMessage string `json:"statusMessage"`
-}
-
-type CheatSheet struct {
- Title string `json:"title"`
- Provider string `json:"provider"`
- Binds map[string][]Keybind `json:"binds"`
- DMSBindsIncluded bool `json:"dmsBindsIncluded"`
- DMSStatus *DMSBindsStatus `json:"dmsStatus,omitempty"`
-}
-
-type Provider interface {
- Name() string
- GetCheatSheet() (*CheatSheet, error)
-}
-
-type WritableProvider interface {
- Provider
- SetBind(key, action, description string, options map[string]any) error
- RemoveBind(key string) error
- GetOverridePath() string
-}