mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 14:07:47 -04:00
330 lines
7.4 KiB
Go
330 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/duxlabs-dev/dux-shell/core/internal/log"
|
|
"github.com/duxlabs-dev/dux-shell/core/internal/luaconfig"
|
|
"github.com/duxlabs-dev/dux-shell/core/internal/mangoconf"
|
|
"github.com/duxlabs-dev/dux-shell/core/internal/utils"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var configCmd = &cobra.Command{
|
|
Use: "config",
|
|
Short: "Configuration utilities",
|
|
}
|
|
|
|
var resolveIncludeCmd = &cobra.Command{
|
|
Use: "resolve-include <compositor> <filename>",
|
|
Short: "Check if a file is included in compositor config",
|
|
Long: "Recursively check if a file is included/sourced in compositor configuration. Returns JSON with exists and included status.",
|
|
Args: cobra.ExactArgs(2),
|
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
switch len(args) {
|
|
case 0:
|
|
return []string{"hyprland", "niri", "mangowc"}, cobra.ShellCompDirectiveNoFileComp
|
|
case 1:
|
|
return []string{
|
|
"binds.lua",
|
|
"binds-user.lua",
|
|
"colors.lua",
|
|
"layout.lua",
|
|
"outputs.lua",
|
|
"cursor.lua",
|
|
"input.lua",
|
|
"windowrules.lua",
|
|
"cursor.kdl",
|
|
"layout.kdl",
|
|
"outputs.kdl",
|
|
"binds.kdl",
|
|
"input.kdl",
|
|
"cursor.conf",
|
|
"input.conf",
|
|
"layout.conf",
|
|
"outputs.conf",
|
|
"binds.conf",
|
|
}, cobra.ShellCompDirectiveNoFileComp
|
|
}
|
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
|
},
|
|
Run: runResolveInclude,
|
|
}
|
|
|
|
func init() {
|
|
configCmd.AddCommand(resolveIncludeCmd)
|
|
}
|
|
|
|
type IncludeResult struct {
|
|
Exists bool `json:"exists"`
|
|
Included bool `json:"included"`
|
|
ConfigFormat string `json:"configFormat,omitempty"`
|
|
ReadOnly bool `json:"readOnly,omitempty"`
|
|
}
|
|
|
|
func runResolveInclude(cmd *cobra.Command, args []string) {
|
|
compositor := strings.ToLower(args[0])
|
|
filename := args[1]
|
|
|
|
var result IncludeResult
|
|
var err error
|
|
|
|
switch compositor {
|
|
case "hyprland":
|
|
result, err = checkHyprlandInclude(filename)
|
|
case "niri":
|
|
result, err = checkNiriInclude(filename)
|
|
case "mangowc", "mango":
|
|
result, err = checkMangoWCInclude(filename)
|
|
default:
|
|
log.Fatalf("Unknown compositor: %s", compositor)
|
|
}
|
|
|
|
if err != nil {
|
|
log.Fatalf("Error checking include: %v", err)
|
|
}
|
|
|
|
output, _ := json.Marshal(result)
|
|
fmt.Fprintln(os.Stdout, string(output))
|
|
}
|
|
|
|
func checkHyprlandInclude(filename string) (IncludeResult, error) {
|
|
configDir := filepath.Join(utils.XDGConfigHome(), "hypr")
|
|
|
|
targetPath := filepath.Join(configDir, "dms", filename)
|
|
result := IncludeResult{}
|
|
|
|
if _, err := os.Stat(targetPath); err == nil {
|
|
result.Exists = true
|
|
}
|
|
|
|
targetAbs, err := filepath.Abs(targetPath)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
mainLua := filepath.Join(configDir, "hyprland.lua")
|
|
if _, err := os.Stat(mainLua); err == nil {
|
|
result.ConfigFormat = "lua"
|
|
result.Included = luaconfig.RequiresTarget(mainLua, targetAbs, make(map[string]bool))
|
|
return result, nil
|
|
}
|
|
|
|
// 0.55/0.56 installs still on hyprland.conf: read-only until migrated.
|
|
mainConf := filepath.Join(configDir, "hyprland.conf")
|
|
if _, err := os.Stat(mainConf); err != nil {
|
|
return result, nil
|
|
}
|
|
result.ConfigFormat = "hyprlang"
|
|
result.ReadOnly = true
|
|
result.Included = hyprlandFindIncludeHyprlang(mainConf, targetAbs, make(map[string]bool))
|
|
return result, nil
|
|
}
|
|
|
|
func hyprlandFindIncludeHyprlang(filePath, target string, processed map[string]bool) bool {
|
|
absPath, err := filepath.Abs(filePath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if processed[absPath] {
|
|
return false
|
|
}
|
|
processed[absPath] = true
|
|
|
|
data, err := os.ReadFile(absPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
baseDir := filepath.Dir(absPath)
|
|
for line := range strings.SplitSeq(string(data), "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
if !strings.HasPrefix(trimmed, "source") {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(trimmed, "=", 2)
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
|
|
fullPath, err := resolveSourcePath(baseDir, strings.TrimSpace(parts[1]))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if fullPath == target || hyprlandFindIncludeHyprlang(fullPath, target, processed) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func checkNiriInclude(filename string) (IncludeResult, error) {
|
|
configDir := filepath.Join(utils.XDGConfigHome(), "niri")
|
|
|
|
targetPath := filepath.Join(configDir, "dms", filename)
|
|
result := IncludeResult{}
|
|
|
|
if _, err := os.Stat(targetPath); err == nil {
|
|
result.Exists = true
|
|
}
|
|
|
|
mainConfig := filepath.Join(configDir, "config.kdl")
|
|
if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
|
|
return result, nil
|
|
}
|
|
|
|
targetAbs, err := filepath.Abs(targetPath)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
processed := make(map[string]bool)
|
|
result.Included = niriFindInclude(mainConfig, targetAbs, processed)
|
|
return result, nil
|
|
}
|
|
|
|
func niriFindInclude(filePath, target string, processed map[string]bool) bool {
|
|
absPath, err := filepath.Abs(filePath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if processed[absPath] {
|
|
return false
|
|
}
|
|
processed[absPath] = true
|
|
|
|
data, err := os.ReadFile(absPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
baseDir := filepath.Dir(absPath)
|
|
content := string(data)
|
|
|
|
for line := range strings.SplitSeq(content, "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmed, "//") || trimmed == "" {
|
|
continue
|
|
}
|
|
|
|
if !strings.HasPrefix(trimmed, "include") {
|
|
continue
|
|
}
|
|
|
|
startQuote := strings.Index(trimmed, "\"")
|
|
if startQuote == -1 {
|
|
continue
|
|
}
|
|
endQuote := strings.LastIndex(trimmed, "\"")
|
|
if endQuote <= startQuote {
|
|
continue
|
|
}
|
|
|
|
fullPath, err := resolveSourcePath(baseDir, trimmed[startQuote+1:endQuote])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if fullPath == target || niriFindInclude(fullPath, target, processed) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func checkMangoWCInclude(filename string) (IncludeResult, error) {
|
|
configDir := mangoconf.Dir()
|
|
|
|
targetPath := filepath.Join(configDir, "dms", filename)
|
|
result := IncludeResult{}
|
|
|
|
if _, err := os.Stat(targetPath); err == nil {
|
|
result.Exists = true
|
|
}
|
|
|
|
mainConfig := filepath.Join(configDir, "config.conf")
|
|
if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
|
|
mainConfig = filepath.Join(configDir, "mango.conf")
|
|
}
|
|
if _, err := os.Stat(mainConfig); os.IsNotExist(err) {
|
|
return result, nil
|
|
}
|
|
|
|
targetAbs, err := filepath.Abs(targetPath)
|
|
if err != nil {
|
|
return result, err
|
|
}
|
|
|
|
processed := make(map[string]bool)
|
|
result.Included = mangowcFindInclude(mainConfig, targetAbs, processed)
|
|
return result, nil
|
|
}
|
|
|
|
func mangowcFindInclude(filePath, target string, processed map[string]bool) bool {
|
|
absPath, err := filepath.Abs(filePath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
if processed[absPath] {
|
|
return false
|
|
}
|
|
processed[absPath] = true
|
|
|
|
data, err := os.ReadFile(absPath)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
baseDir := filepath.Dir(absPath)
|
|
lines := strings.SplitSeq(string(data), "\n")
|
|
|
|
for line := range lines {
|
|
trimmed := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmed, "#") || trimmed == "" {
|
|
continue
|
|
}
|
|
|
|
if !strings.HasPrefix(trimmed, "source") {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(trimmed, "=", 2)
|
|
if len(parts) < 2 {
|
|
continue
|
|
}
|
|
|
|
fullPath, err := resolveSourcePath(baseDir, strings.TrimSpace(parts[1]))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if fullPath == target || mangowcFindInclude(fullPath, target, processed) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func resolveSourcePath(baseDir, raw string) (string, error) {
|
|
expanded, err := utils.ExpandPath(raw)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !filepath.IsAbs(expanded) {
|
|
expanded = filepath.Join(baseDir, expanded)
|
|
}
|
|
return filepath.Abs(expanded)
|
|
}
|