mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
230 lines
4.9 KiB
Go
230 lines
4.9 KiB
Go
package providers
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"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
|
|
}
|
|
|
|
type NiriSection struct {
|
|
Name string
|
|
Keybinds []NiriKeyBinding
|
|
Children []NiriSection
|
|
}
|
|
|
|
type NiriParser struct {
|
|
configDir string
|
|
processedFiles map[string]bool
|
|
bindMap map[string]*NiriKeyBinding
|
|
bindOrder []string
|
|
}
|
|
|
|
func NewNiriParser(configDir string) *NiriParser {
|
|
return &NiriParser{
|
|
configDir: configDir,
|
|
processedFiles: make(map[string]bool),
|
|
bindMap: make(map[string]*NiriKeyBinding),
|
|
bindOrder: []string{},
|
|
}
|
|
}
|
|
|
|
func (p *NiriParser) Parse() (*NiriSection, error) {
|
|
configPath := filepath.Join(p.configDir, "config.kdl")
|
|
section, err := p.parseFile(configPath, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
section.Keybinds = p.finalizeBinds()
|
|
return section, nil
|
|
}
|
|
|
|
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)
|
|
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,
|
|
}
|
|
|
|
baseDir := filepath.Dir(absPath)
|
|
p.processNodes(doc.Nodes, section, baseDir)
|
|
|
|
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 := node.Arguments[0].String()
|
|
includePath = strings.Trim(includePath, "\"")
|
|
|
|
var fullPath string
|
|
if filepath.IsAbs(includePath) {
|
|
fullPath = includePath
|
|
} else {
|
|
fullPath = filepath.Join(baseDir, includePath)
|
|
}
|
|
|
|
includedSection, err := p.parseFile(fullPath, "")
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
section.Children = append(section.Children, includedSection.Children...)
|
|
}
|
|
|
|
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, subcategory 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, strings.Trim(arg.String(), "\""))
|
|
}
|
|
}
|
|
|
|
description := ""
|
|
if node.Properties != nil {
|
|
if val, ok := node.Properties.Get("hotkey-overlay-title"); ok {
|
|
description = strings.Trim(val.String(), "\"")
|
|
}
|
|
}
|
|
|
|
return &NiriKeyBinding{
|
|
Mods: mods,
|
|
Key: key,
|
|
Action: action,
|
|
Args: args,
|
|
Description: description,
|
|
}
|
|
}
|
|
|
|
func (p *NiriParser) parseKeyCombo(combo string) ([]string, string) {
|
|
parts := strings.Split(combo, "+")
|
|
if len(parts) == 0 {
|
|
return nil, combo
|
|
}
|
|
|
|
if len(parts) == 1 {
|
|
return nil, parts[0]
|
|
}
|
|
|
|
mods := parts[:len(parts)-1]
|
|
key := parts[len(parts)-1]
|
|
|
|
return mods, key
|
|
}
|
|
|
|
func ParseNiriKeys(configDir string) (*NiriSection, error) {
|
|
parser := NewNiriParser(configDir)
|
|
return parser.Parse()
|
|
}
|