mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-05 21:15:38 -05:00
368 lines
8.5 KiB
Go
368 lines
8.5 KiB
Go
package providers
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
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 := os.ExpandEnv(path)
|
|
expandedPath = filepath.Clean(expandedPath)
|
|
if strings.HasPrefix(expandedPath, "~") {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
expandedPath = filepath.Join(home, expandedPath[1:])
|
|
}
|
|
|
|
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
|
|
}
|