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 {
type HyprlandEntry struct {
SyncLayouts bool `yaml:"sync-layouts,omitempty"`
Check bool `yaml:"check,omitempty"`
Vars []HyprVar `yaml:"vars,omitempty"`
Modules []HyprModule `yaml:"modules,omitempty"`
}
......
......@@ -107,7 +107,7 @@ var OpStatus = struct {
},
Skipped: ItemStatus{
Symbol: "○",
Color: color.YellowString,
Color: color.HiBlackString,
Label: "skipped",
},
DryRun: ItemStatus{
......
......@@ -10,6 +10,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/fatih/color"
......@@ -344,6 +345,41 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error {
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 {
if editor := os.Getenv("EDITOR"); editor != "" {
return editor
......
......@@ -33,6 +33,12 @@ func CommandList() *cli.Command {
},
Commands: []*cli.Command{
{
Name: "notify-errors",
Hidden: true,
Usage: locale.T("Show error notification dialog"),
Action: HyprlandNotifyErrorsCommand,
},
{
Name: "check",
Usage: locale.T("Check the Hyprland config"),
Action: HyprlandCheckCommand,
......
......@@ -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 {
dir := m.GetModuleDir(user)
......
......@@ -715,6 +715,41 @@ msgstr "Категория:"
msgid "Global options:"
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
msgid "default"
msgstr "по умолчанию"
......
......@@ -6,6 +6,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"
"sort"
"strings"
......@@ -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) {
if !prof.Hyprland.SyncLayouts {
return
......@@ -243,6 +314,7 @@ func processProfile(prof config.PresetProfile, opts opOptions, res *Result, mana
processHyprVars(manager, prof, opts, res)
processHyprModules(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 {
Force: cmd.Bool("force"),
}
manager := tryCreateManager()
var manager *hyprland.HyprlandManager
if strings.EqualFold(prof.Binary, "hyprland") {
manager = tryCreateManager()
}
res := &Result{}
color.Green(locale.T("Applying profile: %s"), profileName)
processProfile(prof, opts, res, manager)
res.Render()
if manager != nil && manager.Changed && !opts.DryRun {
manager.Save()
}
res.Render()
return nil
}
......@@ -327,13 +403,16 @@ func presetApplyAllCommand(ctx context.Context, cmd *cli.Command) error {
Force: cmd.Bool("force"),
}
manager := tryCreateManager()
var manager *hyprland.HyprlandManager
for _, np := range sortedProfiles(profiles) {
if !profileAvailable(np.Profile) {
continue
}
if manager == nil && strings.EqualFold(np.Profile.Binary, "hyprland") {
manager = tryCreateManager()
}
res := &Result{}
color.Green(locale.T("Applying profile: %s"), np.Name)
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