hyprland/modules: use HyprlandManager

parent d661cf1a
package hyprland package hyprland
import ( import (
"ximperconf/config"
"ximperconf/ui" "ximperconf/ui"
"ximperconf/utils" "ximperconf/utils"
...@@ -9,104 +8,25 @@ import ( ...@@ -9,104 +8,25 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/urfave/cli/v3" "github.com/urfave/cli/v3"
) )
var ( func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error {
HomeDir, _ = os.UserHomeDir() manager, err := GetHyprlandManager(ctx)
)
type HyprConfigError struct {
File string
Line int
Text string
}
type HyprModuleInfo struct {
Status string
Available bool
LineNumber int
}
func HyprlandCheck(configPath string) ([]HyprConfigError, error) {
cmd := exec.Command(
"hyprland", "--verify-config",
"-c", configPath,
)
output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
if _, ok := err.(*exec.ExitError); !ok { return err
return nil, err
}
}
lines := strings.Split(string(output), "\n")
results := []HyprConfigError{}
start := false
for _, line := range lines {
if strings.Contains(line, "======== Config parsing result:") {
start = true
continue
}
if !start {
continue
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
// вложенные дубли
if strings.Contains(line, ": Config error in file ") {
continue
}
prefix, msg, ok := strings.Cut(line, ": ")
if !ok {
continue
} }
var file string result, err := manager.Check(manager.ConfPath)
var lineNum int
_, err := fmt.Sscanf(
prefix,
"Config error in file %s at line %d",
&file, &lineNum,
)
if err != nil { if err != nil {
continue return err
}
results = append(results, HyprConfigError{
File: file,
Line: lineNum,
Text: msg,
})
}
return results, nil
}
func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error {
h := config.Env.Hyprland
if h == nil || h.Config == "" {
fmt.Println("")
} }
result, err := HyprlandCheck(h.Config) if len(result) == 0 {
if err != nil { color.Green("Ошибок нет")
color.Red(err.Error()) return nil
return err
} }
for _, e := range result { for _, e := range result {
...@@ -115,120 +35,8 @@ func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -115,120 +35,8 @@ func HyprlandCheckCommand(ctx context.Context, cmd *cli.Command) error {
return nil return nil
} }
func HyprlandGetModuleFile(module string, user bool) string { func hyprlandModuleStatusStruct(manager *HyprlandManager, module string, user bool) ui.ItemStatus {
if user { info := manager.GetModuleInfo(module, user)
return filepath.Join(config.Env.Hyprland.UserModulesDir, module+".conf")
}
return filepath.Join(config.Env.Hyprland.SystemModulesDir, module+".conf")
}
func hyprlandModuleInfo(module string, user bool) HyprModuleInfo {
sysFile, lineFull, lineTilde := hyprlandModuleLines(module, user)
ModuleAvailable := true
for _, skipModule := range config.HyprlandSkipModules {
if module == skipModule {
ModuleAvailable = false
}
}
// Конфиг Hyprland отсутствует
if !utils.FileExists(config.Env.Hyprland.Config) {
if utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "unused",
Available: ModuleAvailable,
}
}
return HyprModuleInfo{
Status: "missing",
}
}
f, err := os.Open(config.Env.Hyprland.Config)
if err != nil {
if utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "unused",
Available: ModuleAvailable,
}
}
return HyprModuleInfo{
Status: "missing",
}
}
defer f.Close()
reFull := regexp.MustCompile(`^\s*` + regexp.QuoteMeta(lineFull) + `(?:\s|$)`)
reTilde := regexp.MustCompile(`^\s*` + regexp.QuoteMeta(lineTilde) + `(?:\s|$)`)
foundActive := false
foundCommented := false
var LineNumber int
data, _ := os.ReadFile(config.Env.Hyprland.Config)
lines := strings.Split(string(data), "\n")
for i, line := range lines {
trim := strings.TrimSpace(line)
if trim == "#"+lineFull || trim == "# "+lineFull || trim == "#"+lineTilde || trim == "# "+lineTilde {
foundCommented = true
LineNumber = i
continue
}
if reFull.MatchString(line) || reTilde.MatchString(line) {
foundActive = true
LineNumber = i
break
}
}
// подключён, но файла нет
if foundActive && !utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "missing",
Available: ModuleAvailable,
LineNumber: LineNumber,
}
}
// подключён и файл есть
if foundActive && utils.FileExists(sysFile) {
return HyprModuleInfo{
Status: "enabled",
Available: ModuleAvailable,
LineNumber: LineNumber,
}
}
// закомментирован
if foundCommented {
return HyprModuleInfo{
Status: "disabled",
Available: ModuleAvailable,
LineNumber: LineNumber,
}
}
// файл есть, но строка отсутствует
if utils.FileExists(sysFile) && !foundActive && !foundCommented {
return HyprModuleInfo{
Status: "unused",
Available: ModuleAvailable,
}
}
// Файла нет и упоминаний нет
return HyprModuleInfo{
Status: "missing",
}
}
func hyprlandModuleStatusStruct(module string, user bool) ui.ItemStatus {
info := hyprlandModuleInfo(module, user)
switch info.Status { switch info.Status {
case "enabled": case "enabled":
...@@ -244,7 +52,13 @@ func hyprlandModuleStatusStruct(module string, user bool) ui.ItemStatus { ...@@ -244,7 +52,13 @@ func hyprlandModuleStatusStruct(module string, user bool) ui.ItemStatus {
} }
func HyprlandModuleStatusCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandModuleStatusCommand(ctx context.Context, cmd *cli.Command) error {
info := hyprlandModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
info := manager.GetModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
if !info.Available { if !info.Available {
color.Red("недопустимое название для модуля.") color.Red("недопустимое название для модуля.")
return nil return nil
...@@ -253,177 +67,47 @@ func HyprlandModuleStatusCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -253,177 +67,47 @@ func HyprlandModuleStatusCommand(ctx context.Context, cmd *cli.Command) error {
return nil return nil
} }
func HyprlandModuleCheck(module string, user bool, tmp bool) ([]HyprConfigError, error) { func HyprlandModuleCheckCommand(ctx context.Context, cmd *cli.Command) error {
if module == "" {
return nil, fmt.Errorf("укажите модуль для проверки")
}
modulefile := HyprlandGetModuleFile(module, user)
if !utils.FileExists(modulefile) {
return nil, fmt.Errorf("модуль '%s' не найден", module)
}
if tmp {
tmpfile, err := os.CreateTemp("", "ximperconf-hypr-module-check-*.conf")
if err != nil {
return nil, err
}
defer os.Remove(tmpfile.Name())
info, _ := hyprlandVarInfo() if cmd.Args().Get(0) == "" {
for name, value := range info { return fmt.Errorf("укажите модуль для проверки")
fmt.Fprintf(tmpfile, "$%s = %s\n", name, value)
}
fmt.Fprintf(tmpfile, "source = %s\n", modulefile)
tmpfile.Close()
modulefile = tmpfile.Name()
} }
info, err := HyprlandCheck(modulefile) manager, err := GetHyprlandManager(ctx)
if err != nil { if err != nil {
return nil, err return err
} }
return info, nil moduleinfo := manager.GetModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
}
func HyprlandModuleCheckCommand(ctx context.Context, cmd *cli.Command) error {
moduleinfo := hyprlandModuleInfo(cmd.Args().Get(0), cmd.Bool("user"))
if !moduleinfo.Available { if !moduleinfo.Available {
color.Red("недопустимое название для модуля.") return fmt.Errorf("недопустимое название для модуля")
return nil
} }
info, err := HyprlandModuleCheck(cmd.Args().Get(0), cmd.Bool("user"), true) result, err := manager.CheckModule(cmd.Args().Get(0), cmd.Bool("user"))
if err != nil { if err != nil {
color.Red(err.Error())
return err return err
} }
for _, e := range info { if len(result) == 0 {
fmt.Printf("%s:%d -> %s\n", e.File, e.Line, e.Text) color.Green("Ошибок нет")
}
return nil return nil
}
func HyprlandSetModule(action string, module string, user bool, onlyNew bool) (string, error) {
sysFile, lineFull, lineTilde := hyprlandModuleLines(module, user)
info := hyprlandModuleInfo(module, user)
reFull := regexp.MustCompile(`^\s*#\s*` + regexp.QuoteMeta(lineFull) + `$`)
reTilde := regexp.MustCompile(`^\s*#\s*` + regexp.QuoteMeta(lineTilde) + `$`)
var out string
if !info.Available {
return "", fmt.Errorf("недопустимое название для модуля.")
}
if info.Status == "missing" {
return "", fmt.Errorf("модуль '%s' не найден: %s", module, sysFile)
}
data, _ := os.ReadFile(config.Env.Hyprland.Config)
lines := strings.Split(string(data), "\n")
changed := false
switch action {
case "enable":
// Уже включён
if info.Status == "enabled" {
return "", fmt.Errorf("модуль '%s' уже включён", module)
}
// Был закомментирован
if info.Status == "disabled" {
if onlyNew {
return "", fmt.Errorf("модуль '%s' уже присутствует в конфиге (закомментирован) — пропущено", module)
}
lines[info.LineNumber] = lineFull
changed = true
}
// Если модуль нигде не найден — добавляем новую строку
if info.Status == "unused" {
lines = append(lines, lineFull)
changed = true
}
if !changed {
return "", fmt.Errorf("модуль '%s' не изменён", module)
}
out = fmt.Sprintf("Модуль '%s' включён", module)
case "disable":
// Уже выключен
if info.Status == "disabled" {
return "", fmt.Errorf("модуль '%s' уже отключён", module)
}
// Включён
if info.Status == "enabled" {
lines[info.LineNumber] = "# " + lineFull
out = fmt.Sprintf("Модуль '%s' отключён", module)
changed = true
}
if !changed {
return "", fmt.Errorf("модуль '%s' не найден в конфигурации", module)
}
case "remove":
// слайс для строк, которые не нужно удалять
newLines := []string{}
for _, l := range lines {
trim := strings.TrimSpace(l)
// Проверяем все возможные варианты
isModuleLine := trim == lineFull ||
trim == lineTilde ||
reFull.MatchString(l) ||
reTilde.MatchString(l)
// Если это не модуль - добавляем в новый список
if !isModuleLine {
newLines = append(newLines, l)
} else {
changed = true
}
} }
// Заменяем lines на отфильтрованные строки for _, e := range result {
if changed { fmt.Printf("%s:%d -> %s\n", e.File, e.Line, e.Text)
lines = newLines
}
if !changed {
return "", fmt.Errorf("модуль '%s' не найден в конфигурации", module)
}
out = fmt.Sprintf("Модуль '%s' удалён", module)
}
if changed {
_ = os.WriteFile(config.Env.Hyprland.Config, []byte(strings.Join(lines, "\n")), 0644)
} }
return nil
return out, nil
} }
func HyprlandModuleEnableCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandModuleEnableCommand(ctx context.Context, cmd *cli.Command) error {
msg, err := HyprlandSetModule("enable", cmd.Args().Get(0), cmd.Bool("user"), false) manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
msg, err := manager.SetModule("enable", cmd.Args().Get(0), cmd.Bool("user"), false)
if err != nil { if err != nil {
color.Red(err.Error())
return err return err
} }
...@@ -433,17 +117,23 @@ func HyprlandModuleEnableCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -433,17 +117,23 @@ func HyprlandModuleEnableCommand(ctx context.Context, cmd *cli.Command) error {
func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error {
remove := cmd.Bool("remove") remove := cmd.Bool("remove")
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
var msg string var msg string
var err error
action := "disable"
if remove { if remove {
msg, err = HyprlandSetModule("remove", cmd.Args().Get(0), cmd.Bool("user"), false) action = "remove"
} else {
msg, err = HyprlandSetModule("disable", cmd.Args().Get(0), cmd.Bool("user"), false)
} }
msg, err = manager.SetModule(
action, cmd.Args().Get(0), cmd.Bool("user"), false,
)
if err != nil { if err != nil {
color.Red(err.Error())
return err return err
} }
...@@ -453,77 +143,51 @@ func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -453,77 +143,51 @@ func HyprlandModuleDisableCommand(ctx context.Context, cmd *cli.Command) error {
func HyprlandToggleModuleCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandToggleModuleCommand(ctx context.Context, cmd *cli.Command) error {
module := cmd.Args().Get(0) module := cmd.Args().Get(0)
user := cmd.Bool("user") user := cmd.Bool("user")
info := hyprlandModuleInfo(module, user)
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
info := manager.GetModuleInfo(module, user)
var msg string var msg string
var err error
switch info.Status { switch info.Status {
case "enabled": case "enabled":
msg, err = HyprlandSetModule("disable", module, user, false) msg, err = manager.SetModule("disable", module, user, false)
case "disabled", "unused": case "disabled", "unused":
msg, err = HyprlandSetModule("enable", module, user, false) msg, err = manager.SetModule("enable", module, user, false)
} }
if err != nil { if err != nil {
color.Red(err.Error())
return err return err
} }
color.Green(msg) color.Green(msg)
return nil return nil
} }
func hyprlandListModules(user bool, filter string) []string { func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
var dir string user := cmd.Bool("user")
if user { filter := cmd.String("filter")
dir = config.Env.Hyprland.UserModulesDir
} else {
dir = config.Env.Hyprland.SystemModulesDir
}
entries, err := os.ReadDir(dir)
if err != nil {
return nil
}
var modules []string
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".conf") {
continue
}
module := strings.TrimSuffix(e.Name(), ".conf")
info := hyprlandModuleInfo(module, user)
if !info.Available {
continue
}
if filter == "" || filter == "all" {
modules = append(modules, module)
continue
}
if info.Status == filter { manager, err := GetHyprlandManager(ctx)
modules = append(modules, module) if err != nil {
} return err
} }
return modules
}
func hyprlandInfoModules(user bool, filter string) { modules := manager.GetModulesList(user, filter)
modules := hyprlandListModules(user, filter)
if len(modules) == 0 { if len(modules) == 0 {
color.Red("Нет доступных модулей") return fmt.Errorf("нет доступных модулей")
return
} }
items := make([]ui.TreeItem, 0, len(modules)) items := make([]ui.TreeItem, 0, len(modules))
for _, module := range modules { for _, module := range modules {
status := hyprlandModuleStatusStruct(module, user) status := hyprlandModuleStatusStruct(manager, module, user)
errors, err := HyprlandModuleCheck(module, user, true) errors, err := manager.CheckModule(module, user)
if err != nil { if err != nil {
errors = []HyprConfigError{} errors = []HyprConfigError{}
...@@ -547,27 +211,27 @@ func hyprlandInfoModules(user bool, filter string) { ...@@ -547,27 +211,27 @@ func hyprlandInfoModules(user bool, filter string) {
StatusMap: ui.ColorStatusMap, StatusMap: ui.ColorStatusMap,
Sort: true, Sort: true,
}) })
}
func HyprlandInfoModulesCommand(ctx context.Context, cmd *cli.Command) error {
hyprlandInfoModules(cmd.Bool("user"), cmd.String("filter"))
return nil return nil
} }
func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error {
module := cmd.Args().Get(0) module := cmd.Args().Get(0)
user := cmd.Bool("user") user := cmd.Bool("user")
modulefile := HyprlandGetModuleFile(module, user) manager, err := GetHyprlandManager(ctx)
info := hyprlandModuleInfo(module, user) if err != nil {
return err
}
modulefile := manager.GetModuleFile(module, user)
info := manager.GetModuleInfo(module, user)
if !info.Available { if !info.Available {
color.Red("недопустимое название для модуля.") return fmt.Errorf("недопустимое название для модуля")
return nil
} }
if !utils.FileExists(modulefile) { if info.Path == "" {
color.Red("Модуль '%s' не найден: %s", module, modulefile) return fmt.Errorf("модуль '%s' не найден: %s", module, modulefile)
return nil
} }
editor := utils.GetEditor() editor := utils.GetEditor()
...@@ -577,18 +241,10 @@ func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -577,18 +241,10 @@ func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error {
editCmd.Stdout = os.Stdout editCmd.Stdout = os.Stdout
editCmd.Stderr = os.Stderr editCmd.Stderr = os.Stderr
err := editCmd.Run() err = editCmd.Run()
if err != nil { if err != nil {
color.Red("Не удалось запустить редактор: %s", err.Error()) return fmt.Errorf("не удалось запустить редактор: %s", err.Error())
return err
} }
return nil return nil
} }
func hyprlandModuleLines(module string, user bool) (sysFile, lineFull, lineTilde string) {
sysFile = HyprlandGetModuleFile(module, user)
lineFull = "source = " + sysFile
lineTilde = "source = ~" + strings.TrimPrefix(sysFile, HomeDir)
return
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment