Commit 33f3fe93 authored by Kirill Unitsaev's avatar Kirill Unitsaev

preset: post-apply config verification and error notification

parent 72107231
...@@ -52,6 +52,7 @@ type HyprModule struct { ...@@ -52,6 +52,7 @@ type HyprModule struct {
type HyprlandEntry struct { type HyprlandEntry struct {
SyncLayouts bool `yaml:"sync-layouts,omitempty"` SyncLayouts bool `yaml:"sync-layouts,omitempty"`
Check bool `yaml:"check,omitempty"`
Vars []HyprVar `yaml:"vars,omitempty"` Vars []HyprVar `yaml:"vars,omitempty"`
Modules []HyprModule `yaml:"modules,omitempty"` Modules []HyprModule `yaml:"modules,omitempty"`
} }
......
...@@ -107,7 +107,7 @@ var OpStatus = struct { ...@@ -107,7 +107,7 @@ var OpStatus = struct {
}, },
Skipped: ItemStatus{ Skipped: ItemStatus{
Symbol: "○", Symbol: "○",
Color: color.YellowString, Color: color.HiBlackString,
Label: "skipped", Label: "skipped",
}, },
DryRun: ItemStatus{ DryRun: ItemStatus{
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"strings" "strings"
"github.com/fatih/color" "github.com/fatih/color"
...@@ -344,6 +345,41 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -344,6 +345,41 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error {
return nil return nil
} }
func HyprlandNotifyErrorsCommand(ctx context.Context, cmd *cli.Command) error {
manager, err := GetHyprlandManager(ctx)
if err != nil {
return err
}
logPath := filepath.Join(manager.Home, ".cache", "ximperconf", "hyprland-check.log")
data, err := os.ReadFile(logPath)
logContent := strings.TrimSpace(string(data))
if err != nil || logContent == "" {
cleanupNotifyModule(manager, logPath)
return nil
}
text := locale.T("The following modules were disabled due to errors:") + "\n\n" + logContent
// Показываем диалог (блокирующий вызов)
dialogCmd := exec.Command(
"hyprland-dialog",
"--title", locale.T("Hyprland configuration errors"),
"--text", text,
"--buttons", "OK",
)
_ = dialogCmd.Run()
cleanupNotifyModule(manager, logPath)
return nil
}
func cleanupNotifyModule(manager *HyprlandManager, logPath string) {
manager.SetModule("disable", "ximperconf-errors", false, false)
os.Remove(logPath)
}
func getEditor() string { func getEditor() string {
if editor := os.Getenv("EDITOR"); editor != "" { if editor := os.Getenv("EDITOR"); editor != "" {
return editor return editor
......
...@@ -33,6 +33,12 @@ func CommandList() *cli.Command { ...@@ -33,6 +33,12 @@ func CommandList() *cli.Command {
}, },
Commands: []*cli.Command{ Commands: []*cli.Command{
{ {
Name: "notify-errors",
Hidden: true,
Usage: locale.T("Show error notification dialog"),
Action: HyprlandNotifyErrorsCommand,
},
{
Name: "check", Name: "check",
Usage: locale.T("Check the Hyprland config"), Usage: locale.T("Check the Hyprland config"),
Action: HyprlandCheckCommand, Action: HyprlandCheckCommand,
......
...@@ -1092,6 +1092,25 @@ func (m *HyprlandManager) removeSource(lineNumber int) { ...@@ -1092,6 +1092,25 @@ func (m *HyprlandManager) removeSource(lineNumber int) {
} }
} }
func (m *HyprlandManager) FindModuleByPath(filePath string) (name string, isUser bool, found bool) {
allModules := []struct {
modules []string
user bool
}{
{m.SystemModules, false},
{m.UserModules, true},
}
for _, group := range allModules {
for _, mod := range group.modules {
if m.GetModuleFile(mod, group.user) == filePath {
return mod, group.user, true
}
}
}
return "", false, false
}
func (m *HyprlandManager) scanModulesDir(user bool) []string { func (m *HyprlandManager) scanModulesDir(user bool) []string {
dir := m.GetModuleDir(user) dir := m.GetModuleDir(user)
......
...@@ -715,6 +715,41 @@ msgstr "Категория:" ...@@ -715,6 +715,41 @@ msgstr "Категория:"
msgid "Global options:" msgid "Global options:"
msgstr "Глобальные параметры:" msgstr "Глобальные параметры:"
#: preset/actions.go
msgid "check"
msgstr "проверка"
#: preset/actions.go
#, c-format
msgid "Check failed: %v"
msgstr "Проверка не удалась: %v"
#: preset/actions.go
msgid "No errors found"
msgstr "Ошибок не найдено"
#: preset/actions.go
#, c-format
msgid "Module '%s' disabled (errors)"
msgstr "Модуль '%s' отключён (ошибки)"
#: preset/actions.go
#, c-format
msgid "Disabled %d module(s) with errors, log saved"
msgstr "Отключено модулей с ошибками: %d, лог сохранён"
#: hyprland/actions.go
msgid "Show error notification dialog"
msgstr "Показать диалог об ошибках"
#: hyprland/actions.go
msgid "The following modules were disabled due to errors:"
msgstr "Следующие модули были отключены из-за ошибок:"
#: hyprland/actions.go
msgid "Hyprland configuration errors"
msgstr "Ошибки конфигурации Hyprland"
#: ui/help.go:125 #: ui/help.go:125
msgid "default" msgid "default"
msgstr "по умолчанию" msgstr "по умолчанию"
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"slices" "slices"
"sort" "sort"
"strings" "strings"
...@@ -199,6 +200,76 @@ func processHyprModules(manager *hyprland.HyprlandManager, prof config.PresetPro ...@@ -199,6 +200,76 @@ func processHyprModules(manager *hyprland.HyprlandManager, prof config.PresetPro
} }
} }
func processVerifyModules(manager *hyprland.HyprlandManager, prof config.PresetProfile, opts opOptions, res *Result) {
if !prof.Hyprland.Check || opts.DryRun {
return
}
if manager.Changed {
manager.Save()
}
logPath := filepath.Join(manager.Home, ".cache", "ximperconf", "hyprland-check.log")
checkErrors, err := manager.Check("")
if err != nil {
res.Add(locale.T("check"), fmt.Sprintf(locale.T("Check failed: %v"), err), config.OpStatus.Error)
return
}
if len(checkErrors) == 0 {
os.Remove(logPath) // Удаляем лог от предыдущего запуска
res.Add(locale.T("check"), locale.T("No errors found"), config.OpStatus.Done)
return
}
disabled := map[string]bool{}
var logLines []string
for _, e := range checkErrors {
name, isUser, found := manager.FindModuleByPath(e.File)
if !found {
logLines = append(logLines, fmt.Sprintf("config:%d - %s", e.Line, e.Text))
continue
}
prefix := "system"
if isUser {
prefix = "user"
}
logLines = append(logLines, fmt.Sprintf("%s/%s:%d - %s", prefix, name, e.Line, e.Text))
key := prefix + "/" + name
if disabled[key] {
continue
}
info := manager.GetModuleInfo(name, isUser)
if !info.Status.IsEqual(config.ModuleStatus.Enabled) {
continue
}
if _, err := manager.SetModule("disable", name, isUser, false); err != nil {
res.Add(locale.T("check"), fmt.Sprintf(locale.T("Failed to disable '%s': %v"), name, err), config.OpStatus.Error)
continue
}
disabled[key] = true
res.Add(locale.T("check"), fmt.Sprintf(locale.T("Module '%s' disabled (errors)"), name), config.OpStatus.Error)
}
os.MkdirAll(filepath.Dir(logPath), 0o755)
os.WriteFile(logPath, []byte(strings.Join(logLines, "\n")+"\n"), 0o644)
if len(disabled) == 0 {
return
}
// Включаем модуль уведомления об ошибках
if _, err := manager.SetModule("enable", "ximperconf-errors", false, false); err != nil {
res.Add(locale.T("check"), fmt.Sprintf(locale.T("Failed to enable notification module: %v"), err), config.OpStatus.Error)
}
}
func processHyprLayoutSync(manager *hyprland.HyprlandManager, prof config.PresetProfile, opts opOptions, res *Result) { func processHyprLayoutSync(manager *hyprland.HyprlandManager, prof config.PresetProfile, opts opOptions, res *Result) {
if !prof.Hyprland.SyncLayouts { if !prof.Hyprland.SyncLayouts {
return return
...@@ -243,6 +314,7 @@ func processProfile(prof config.PresetProfile, opts opOptions, res *Result, mana ...@@ -243,6 +314,7 @@ func processProfile(prof config.PresetProfile, opts opOptions, res *Result, mana
processHyprVars(manager, prof, opts, res) processHyprVars(manager, prof, opts, res)
processHyprModules(manager, prof, opts, res) processHyprModules(manager, prof, opts, res)
processHyprLayoutSync(manager, prof, opts, res) processHyprLayoutSync(manager, prof, opts, res)
processVerifyModules(manager, prof, opts, res)
} }
} }
...@@ -281,17 +353,21 @@ func presetApplyCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -281,17 +353,21 @@ func presetApplyCommand(ctx context.Context, cmd *cli.Command) error {
Force: cmd.Bool("force"), Force: cmd.Bool("force"),
} }
manager := tryCreateManager() var manager *hyprland.HyprlandManager
if strings.EqualFold(prof.Binary, "hyprland") {
manager = tryCreateManager()
}
res := &Result{} res := &Result{}
color.Green(locale.T("Applying profile: %s"), profileName) color.Green(locale.T("Applying profile: %s"), profileName)
processProfile(prof, opts, res, manager) processProfile(prof, opts, res, manager)
res.Render()
if manager != nil && manager.Changed && !opts.DryRun { if manager != nil && manager.Changed && !opts.DryRun {
manager.Save() manager.Save()
} }
res.Render()
return nil return nil
} }
...@@ -327,13 +403,16 @@ func presetApplyAllCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -327,13 +403,16 @@ func presetApplyAllCommand(ctx context.Context, cmd *cli.Command) error {
Force: cmd.Bool("force"), Force: cmd.Bool("force"),
} }
manager := tryCreateManager() var manager *hyprland.HyprlandManager
for _, np := range sortedProfiles(profiles) { for _, np := range sortedProfiles(profiles) {
if !profileAvailable(np.Profile) { if !profileAvailable(np.Profile) {
continue continue
} }
if manager == nil && strings.EqualFold(np.Profile.Binary, "hyprland") {
manager = tryCreateManager()
}
res := &Result{} res := &Result{}
color.Green(locale.T("Applying profile: %s"), np.Name) color.Green(locale.T("Applying profile: %s"), np.Name)
processProfile(np.Profile, opts, res, manager) processProfile(np.Profile, opts, res, manager)
......
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