Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
X
ximperconf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Ximper Linux
ximperconf
Commits
72107231
Verified
Commit
72107231
authored
Feb 14, 2026
by
Kirill Unitsaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
preset: refactor preset system and YAML config format
parent
5b29e43c
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
684 additions
and
811 deletions
+684
-811
config.go
config/config.go
+12
-17
presets.go
config/presets.go
+29
-40
actions.go
hyprland/actions.go
+8
-2
manager.go
hyprland/manager.go
+3
-4
var-actions.go
hyprland/var-actions.go
+1
-2
POTFILES
po/POTFILES
+2
-1
ru.po
po/ru.po
+155
-119
ximperconf.pot
po/ximperconf.pot
+110
-110
actions.go
preset/actions.go
+150
-287
commands.go
preset/commands.go
+19
-36
fileops.go
preset/fileops.go
+50
-106
helpers.go
preset/helpers.go
+58
-39
result.go
preset/result.go
+87
-8
rendertreelines.go
ui/rendertreelines.go
+0
-40
No files found.
config/config.go
View file @
72107231
...
...
@@ -3,13 +3,8 @@ package config
import
(
"os"
"path/filepath"
"ximperconf/utils"
)
type
presetEnv
struct
{
Profiles
map
[
string
]
PresetProfile
}
type
hyprEnv
struct
{
Config
string
SystemModulesDir
string
...
...
@@ -24,7 +19,7 @@ type repoEnv struct {
type
environment
struct
{
Version
string
IsRoot
bool
Pr
eset
*
presetEnv
Pr
ofiles
map
[
string
]
PresetProfile
Hyprland
*
hyprEnv
Repo
*
repoEnv
}
...
...
@@ -35,19 +30,14 @@ func InitConfig() error {
Env
.
Version
=
"0.1.0"
Env
.
IsRoot
=
os
.
Geteuid
()
==
0
pr
esetCfg
,
err
:=
loadPresetConfig
(
"/etc/ximperdistro/ximperconf/preset.d/"
)
pr
ofiles
,
err
:=
loadPresetProfiles
(
"/etc/ximperdistro/ximperconf/preset.d/"
)
if
err
!=
nil
{
// Если ошибка, пустой map
Env
.
Preset
=
&
presetEnv
{
Profiles
:
map
[
string
]
PresetProfile
{},
}
Env
.
Profiles
=
map
[
string
]
PresetProfile
{}
}
else
{
Env
.
Preset
=
&
presetEnv
{
Profiles
:
presetCfg
.
Profiles
,
}
Env
.
Profiles
=
profiles
}
if
utils
.
FileExists
(
"/usr/bin/hyprland"
)
{
if
FileExists
(
"/usr/bin/hyprland"
)
{
home
,
_
:=
os
.
UserHomeDir
()
userModules
:=
filepath
.
Join
(
home
,
".config"
,
"hypr"
)
systemModules
:=
"/etc/ximperdistro/hyprland/hypr"
...
...
@@ -63,14 +53,19 @@ func InitConfig() error {
}
Env
.
Repo
=
&
repoEnv
{
DeferredInfoURL
:
"https://download.etersoft.ru/pub/
Etersoft/Sisyphus
/Deferred_Info.html"
,
DeferredInfoURL
:
"https://download.etersoft.ru/pub/
Ximper
/Deferred_Info.html"
,
}
return
nil
}
func
FileExists
(
path
string
)
bool
{
_
,
err
:=
os
.
Stat
(
path
)
return
err
==
nil
}
func
optionalPath
(
path
string
)
string
{
if
utils
.
FileExists
(
path
)
{
if
FileExists
(
path
)
{
return
path
}
return
""
...
...
config/presets.go
View file @
72107231
...
...
@@ -9,40 +9,39 @@ import (
"gopkg.in/yaml.v3"
)
type
c
opyEntry
struct
{
type
C
opyEntry
struct
{
Src
string
`yaml:"src"`
Dest
string
`yaml:"dest"`
}
type
linkEntry
struct
{
type
LinkEntry
struct
{
Src
string
`yaml:"src"`
Dest
string
`yaml:"dest"`
Apps
[]
string
`yaml:"apps,omitempty"`
App
string
`yaml:"app,omitempty"`
Sys
string
`yaml:"sys"`
User
string
`yaml:"user"`
}
type
release
ReplaceEntry
struct
{
type
ReplaceEntry
struct
{
Src
string
`yaml:"src"`
Dest
string
`yaml:"dest"`
}
type
g
rubEntry
struct
{
type
G
rubEntry
struct
{
FixResolution
bool
`yaml:"fix-resolution,omitempty"`
Update
bool
`yaml:"update,omitempty"`
}
type
s
ystemEntry
struct
{
Grub
g
rubEntry
`yaml:"grub,omitempty"`
type
S
ystemEntry
struct
{
Grub
G
rubEntry
`yaml:"grub,omitempty"`
}
type
h
yprVar
struct
{
Var
string
`yaml:"var
"`
type
H
yprVar
struct
{
Name
string
`yaml:"name
"`
Value
string
`yaml:"value"`
Force
bool
`yaml:"force,omitempty"`
}
type
h
yprModule
struct
{
Module
string
`yaml:"modul
e"`
type
H
yprModule
struct
{
Name
string
`yaml:"nam
e"`
Action
string
`yaml:"action"`
User
bool
`yaml:"user,omitempty"`
NewOnly
bool
`yaml:"new-only,omitempty"`
...
...
@@ -51,35 +50,26 @@ type hyprModule struct {
Init
bool
`yaml:"init,omitempty"`
}
type
hyprOptions
struct
{
SyncSystemLayouts
bool
`yaml:"sync-system-layouts,omitempty"`
}
type
hyprlandEntry
struct
{
Option
hyprOptions
`yaml:"options,omitempty"`
Vars
[]
hyprVar
`yaml:"vars,omitempty"`
Modules
[]
hyprModule
`yaml:"modules,omitempty"`
type
HyprlandEntry
struct
{
SyncLayouts
bool
`yaml:"sync-layouts,omitempty"`
Vars
[]
HyprVar
`yaml:"vars,omitempty"`
Modules
[]
HyprModule
`yaml:"modules,omitempty"`
}
type
PresetProfile
struct
{
Binary
string
`yaml:"binary,omitempty"`
Description
string
`yaml:"description,omitempty"`
Root
bool
`yaml:"root,omitempty"`
Copy
[]
copyEntry
`yaml:"copy,omitempty"`
Links
[]
linkEntry
`yaml:"links,omitempty"`
ReleaseReplace
[]
releaseReplaceEntry
`yaml:"release-replace,omitempty"`
System
systemEntry
`yaml:"system,omitempty"`
// ----- hyprland -----
Hyprland
hyprlandEntry
`yaml:"hyprland,omitempty"`
Binary
string
`yaml:"binary,omitempty"`
Description
string
`yaml:"description,omitempty"`
Root
bool
`yaml:"root,omitempty"`
Priority
int
`yaml:"priority,omitempty"`
Copy
[]
CopyEntry
`yaml:"copy,omitempty"`
Links
[]
LinkEntry
`yaml:"links,omitempty"`
Replace
[]
ReplaceEntry
`yaml:"replace,omitempty"`
System
SystemEntry
`yaml:"system,omitempty"`
Hyprland
HyprlandEntry
`yaml:"hyprland,omitempty"`
}
type
Presets
struct
{
Profiles
map
[
string
]
PresetProfile
`yaml:"profiles"`
}
func
loadPresetConfig
(
dir
string
)
(
*
Presets
,
error
)
{
cfg
:=
&
Presets
{
Profiles
:
map
[
string
]
PresetProfile
{}}
func
loadPresetProfiles
(
dir
string
)
(
map
[
string
]
PresetProfile
,
error
)
{
profiles
:=
map
[
string
]
PresetProfile
{}
entries
,
err
:=
os
.
ReadDir
(
dir
)
if
err
!=
nil
{
...
...
@@ -108,9 +98,8 @@ func loadPresetConfig(dir string) (*Presets, error) {
}
profileName
:=
strings
.
TrimSuffix
(
name
,
filepath
.
Ext
(
name
))
cfg
.
Profiles
[
profileName
]
=
p
profiles
[
profileName
]
=
p
}
return
cfg
,
nil
return
profiles
,
nil
}
hyprland/actions.go
View file @
72107231
...
...
@@ -4,7 +4,6 @@ import (
"ximperconf/config"
"ximperconf/locale"
"ximperconf/ui"
"ximperconf/utils"
"context"
"errors"
...
...
@@ -267,7 +266,7 @@ func HyprlandModuleEditCommand(ctx context.Context, cmd *cli.Command) error {
return
fmt
.
Errorf
(
locale
.
T
(
"module '%s' not found: %s"
),
module
,
modulefile
)
}
editor
:=
utils
.
G
etEditor
()
editor
:=
g
etEditor
()
editCmd
:=
exec
.
Command
(
editor
,
modulefile
)
editCmd
.
Stdin
=
os
.
Stdin
...
...
@@ -344,3 +343,10 @@ func HyprlandModuleShowCommand(ctx context.Context, cmd *cli.Command) error {
return
nil
}
func
getEditor
()
string
{
if
editor
:=
os
.
Getenv
(
"EDITOR"
);
editor
!=
""
{
return
editor
}
return
"micro"
}
hyprland/manager.go
View file @
72107231
...
...
@@ -13,7 +13,6 @@ import (
"time"
"ximperconf/config"
"ximperconf/locale"
"ximperconf/utils"
)
type
HyprlandManager
struct
{
...
...
@@ -193,10 +192,10 @@ func (m *HyprlandManager) GetModuleInfo(module string, user bool) HyprModule {
}
}
FileExists
:=
utils
.
FileExists
(
sysFile
)
FileExists
:=
config
.
FileExists
(
sysFile
)
// Конфиг Hyprland отсутствует
if
!
utils
.
FileExists
(
config
.
Env
.
Hyprland
.
Config
)
{
if
!
config
.
FileExists
(
config
.
Env
.
Hyprland
.
Config
)
{
if
FileExists
{
return
HyprModule
{
Name
:
module
,
...
...
@@ -796,7 +795,7 @@ func (m *HyprlandManager) GetPluginFile(name string) (string, error) {
}
path
:=
filepath
.
Join
(
m
.
PluginsDir
,
name
+
".so"
)
if
!
utils
.
FileExists
(
path
)
{
if
!
config
.
FileExists
(
path
)
{
return
""
,
errors
.
New
(
locale
.
T
(
"plugin not found"
))
}
...
...
hyprland/var-actions.go
View file @
72107231
...
...
@@ -5,7 +5,6 @@ import (
"ximperconf/config"
"ximperconf/locale"
"ximperconf/ui"
"ximperconf/utils"
"fmt"
"os"
...
...
@@ -121,7 +120,7 @@ func hyprlandVarUnset(name string) error {
color
.
Red
(
locale
.
T
(
"Invalid variable name: %s"
),
name
)
os
.
Exit
(
1
)
}
if
!
utils
.
FileExists
(
config
.
Env
.
Hyprland
.
Config
)
{
if
!
config
.
FileExists
(
config
.
Env
.
Hyprland
.
Config
)
{
color
.
Red
(
locale
.
T
(
"Configuration not found: %s"
),
config
.
Env
.
Hyprland
.
Config
)
os
.
Exit
(
1
)
}
...
...
po/POTFILES
View file @
72107231
...
...
@@ -7,7 +7,8 @@ hyprland/var-actions.go
main.go
preset/actions.go
preset/commands.go
preset/tools.go
preset/helpers.go
preset/result.go
repo/actions.go
repo/commands.go
system/commands.go
...
...
po/ru.po
View file @
72107231
...
...
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: ximperconf\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-14
18:34
+0300\n"
"POT-Creation-Date: 2026-02-14
21:56
+0300\n"
"PO-Revision-Date: 2026-02-14 17:13+0300\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
...
...
@@ -229,21 +229,21 @@ msgstr "выгрузить плагин"
msgid "toggle plugin"
msgstr "переключить плагин"
#: hyprland/keyboard-actions.go:4
2
#: hyprland/keyboard-actions.go:4
3
msgid "failed to get system layouts: %w"
msgstr "не удалось получить системные раскладки: %w"
#: hyprland/keyboard-actions.go:
49
#: hyprland/keyboard-actions.go:
50
msgid "failed to update kb_layout in Hyprland: %w"
msgstr "не удалось обновить kb_layout в Hyprland: %w"
#: hyprland/keyboard-actions.go:5
1
#: hyprland/keyboard-actions.go:5
2
msgid "Layout updated!"
msgstr "Раскладка обновлена!"
#: hyprland/keyboard-actions.go:5
3
#: hyprland/keyboard-actions.go:5
4
msgid "layout is already set, use '--force' for forced update"
msgstr ""
msgstr "
раскладка уже установлена, используйте '--force' для принудительного обновления
"
"раскладка уже установлена, используйте '--force' для принудительного "
"обновления"
...
...
@@ -284,7 +284,7 @@ msgstr "модуль '%s' не изменён"
msgid "module '%s' is already disabled"
msgstr "модуль '%s' уже отключён"
#: hyprland/manager.go:416
preset/actions.go:240 preset/actions.go:267
#: hyprland/manager.go:416
#, c-format
msgid "Module '%s' disabled"
msgstr "Модуль '%s' отключён"
...
...
@@ -294,12 +294,12 @@ msgstr "Модуль '%s' отключён"
msgid "module '%s' not found in config"
msgstr "модуль '%s' не найден в конфигурации"
#: hyprland/manager.go:428
preset/actions.go:242 preset/actions.go:269
#: hyprland/manager.go:428
#, c-format
msgid "Module '%s' removed"
msgstr "Модуль '%s' удалён"
#: hyprland/manager.go:434
preset/actions.go:238 preset/actions.go:265
#: hyprland/manager.go:434
#, c-format
msgid "Module '%s' enabled"
msgstr "Модуль '%s' включён"
...
...
@@ -343,7 +343,7 @@ msgstr "плагин не загружен"
msgid "Plugin '%s' unloaded"
msgstr "Плагин '%s' выгружен"
#: hyprland/manager.go:886 hyprland/manager.go:925 hyprland/var-actions.go:11
7
#: hyprland/manager.go:886 hyprland/manager.go:925 hyprland/var-actions.go:11
6
msgid "specify variable name"
msgstr "укажите имя переменной"
...
...
@@ -371,43 +371,43 @@ msgstr "Переменная '%s' установлена: %s"
msgid "VARS block created, variable '%s' set: %s"
msgstr "Блок VARS создан, переменная '%s' установлена: %s"
#: hyprland/manager.go:935 hyprland/var-actions.go:14
5
#: hyprland/manager.go:935 hyprland/var-actions.go:14
4
#, c-format
msgid "variable '%s' not found"
msgstr "переменная '%s' не найдена"
#: hyprland/plugin-actions.go:5
3
#: hyprland/plugin-actions.go:5
4
msgid "no available plugins"
msgstr "нет доступных плагинов"
#: hyprland/plugin-actions.go:8
0
#: hyprland/plugin-actions.go:8
1
msgid "Plugins"
msgstr "Плагины"
#: hyprland/var-actions.go:5
2
#: hyprland/var-actions.go:5
1
msgid "No variables in configuration"
msgstr "Нет переменных в конфигурации"
#: hyprland/var-actions.go:7
7
#: hyprland/var-actions.go:7
6
msgid "Vars"
msgstr "Переменные"
#: hyprland/var-actions.go:9
4
#: hyprland/var-actions.go:9
3
#, c-format
msgid "variable '%s' is not set"
msgstr "переменная '%s' не установлена"
#: hyprland/var-actions.go:12
1
#: hyprland/var-actions.go:12
0
#, c-format
msgid "Invalid variable name: %s"
msgstr "Недопустимое имя переменной: %s"
#: hyprland/var-actions.go:12
5
#: hyprland/var-actions.go:12
4
#, c-format
msgid "Configuration not found: %s"
msgstr "Конфигурация не найдена: %s"
#: hyprland/var-actions.go:14
9
#: hyprland/var-actions.go:14
8
#, c-format
msgid "Variable '%s' removed"
msgstr "Переменная '%s' удалена"
...
...
@@ -420,194 +420,194 @@ msgstr "Инструмент настройки Ximper Linux"
msgid "show help"
msgstr "показать справку"
#: preset/actions.go:77
#: preset/actions.go:30
msgid "copy"
msgstr "копия"
#: preset/actions.go:40
msgid "link"
msgstr "ссылка"
#: preset/actions.go:64
msgid "replace"
msgstr "замена"
#: preset/actions.go:70
#, c-format
msgid "Not found: '%s'"
msgstr "Не найден: '%s'"
#: preset/actions.go:
8
5
#: preset/actions.go:
7
5
msgid "Failed to read %s: %v"
msgstr "Не удалось прочитать %s: %v"
#: preset/actions.go:
93
#: preset/actions.go:
79
#, c-format
msgid "Locked: %s"
msgstr "Заблокирован: %s"
#: preset/actions.go:
105
#: preset/actions.go:
88
#, c-format
msgid "Up to date: '%s'"
msgstr "Актуально: '%s'"
#: preset/actions.go:
120
#: preset/actions.go:
97
msgid "Replace error %s: %v"
msgstr "Ошибка замены %s: %v"
#: preset/actions.go:136 preset/tools.go:45
#: preset/actions.go:111 preset/actions.go:113 preset/actions.go:119
#: preset/actions.go:123 preset/actions.go:125
msgid "system"
msgstr "система"
#: preset/actions.go:119
msgid "GRUB error: %v"
msgstr "Ошибка GRUB: %v"
#: preset/actions.go:123 system/grub-actions.go:86
msgid "GRUB_GFXMODE fixed"
msgstr "GRUB_GFXMODE исправлен"
#: preset/actions.go:125 system/grub-actions.go:89
msgid "update-grub completed"
msgstr "update-grub выполнен"
#: preset/actions.go:133 preset/actions.go:139 preset/actions.go:144
#: preset/actions.go:146
msgid "var"
msgstr "переменная"
#: preset/actions.go:133
#, c-format
msgid "Already exists: %s"
msgstr "Уже существует: %s"
#: preset/actions.go:1
53
#: preset/actions.go:1
44
msgid "Error setting %s: %v"
msgstr "Ошибка установки %s: %v"
#: preset/actions.go:170
#: preset/actions.go:154 preset/actions.go:159 preset/actions.go:167
#: preset/actions.go:173 preset/actions.go:181 preset/actions.go:184
#: preset/actions.go:190 preset/actions.go:195 preset/actions.go:197
msgid "module"
msgstr "модуль"
#: preset/actions.go:154
msgid "Skipped module without name"
msgstr "Пропущен модуль без имени"
#: preset/actions.go:1
78
#: preset/actions.go:1
59
#, c-format
msgid "Module '%s': unsupported action"
msgstr "Модуль '%s': неподдерживаемый action"
#: preset/actions.go:1
90
#: preset/actions.go:1
67
#, c-format
msgid "Module '%s': co
mmand '%s' failed
"
msgstr "Модуль '%s':
команда '%s' завершилась с ошибкой
"
msgid "Module '%s': co
ndition not met
"
msgstr "Модуль '%s':
условие не выполнено
"
#: preset/actions.go:198
#, c-format
msgid "Module '%s': command '%s' returned no data"
msgstr "Модуль '%s': команда '%s' не вернула данных"
#: preset/actions.go:208
#: preset/actions.go:173
#, c-format
msgid "Module '%s': binary '%s' not found"
msgstr "Модуль '%s': бинарник '%s' не найден"
#: preset/actions.go:
220 preset/actions.go:226
#: preset/actions.go:
181 preset/actions.go:184
#, c-format
msgid "Created empty module: %s"
msgstr "Создан пустой модуль: %s"
#: preset/actions.go:
258
#: preset/actions.go:
195
msgid "Error (%s): %v"
msgstr "Ошибка (%s): %v"
#: preset/actions.go:286
#: preset/actions.go:208 preset/actions.go:214 preset/actions.go:222
#: preset/actions.go:225 preset/actions.go:227
msgid "layout"
msgstr "раскладка"
#: preset/actions.go:208
msgid "Sync Hyprland layouts"
msgstr "Синхронизация раскладок Hyprland"
#: preset/actions.go:2
95
#: preset/actions.go:2
14
msgid "Failed to get system layouts"
msgstr "Не удалось получить системные раскладки"
#: preset/actions.go:308
#, c-format
msgid "XCB layout: %s"
msgstr "Раскладка XCB: %s"
#: preset/actions.go:312
#, c-format
msgid "Hyprland layout: %s"
msgstr "Раскладка Hyprland: %s"
#: preset/actions.go:319
#: preset/actions.go:222
msgid "Failed to update kb_layout"
msgstr "Не удалось обновить kb_layout"
#: preset/actions.go:
3
25
#: preset/actions.go:
2
25
#, c-format
msgid "Layout updated: %s"
msgstr "Раскладка обновлена: %s"
#: preset/actions.go:
330
#: preset/actions.go:
227
#, c-format
msgid "Layout already set: %s"
msgstr "Раскладка уже установлена: %s"
#: preset/actions.go:354
msgid "GRUB error: %v"
msgstr "Ошибка GRUB: %v"
#: preset/actions.go:359 system/grub-actions.go:86
msgid "GRUB_GFXMODE fixed"
msgstr "GRUB_GFXMODE исправлен"
#: preset/actions.go:364 system/grub-actions.go:89
msgid "update-grub completed"
msgstr "update-grub выполнен"
#: preset/actions.go:392
#: preset/actions.go:270
#, c-format
msgid "
Creating profile: %s
"
msgstr "
Создаётся профиль: %s
"
msgid "
Profile %s not found
"
msgstr "
Профиль %s не найден
"
#: preset/actions.go:
394
#: preset/actions.go:
275
#, c-format
msgid "Profile %s unavailable"
msgstr "Профиль %s недоступен"
#: preset/actions.go:408 preset/actions.go:468
#: preset/actions.go:287 preset/actions.go:338
#, c-format
msgid "Applying profile: %s"
msgstr "Применяем профиль: %s"
#: preset/actions.go:321 preset/helpers.go:73
msgid "no available profiles"
msgstr "нет доступных профилей"
#: preset/actions.go:430
msgid "Profiles"
msgstr "Профили"
#: preset/actions.go:451
#, c-format
msgid "Profile %s not found"
msgstr "Профиль %s не найден"
#: preset/commands.go:13
msgid "Show what would be created without making changes"
msgstr "Показать что будет создано без внесения изменений"
#: preset/
actions.go:472
msgid "
Creating all available profiles...
"
msgstr "
Создаём все доступные профили...
"
#: preset/
commands.go:18
msgid "
Force overwrite existing files (backup to .bak)
"
msgstr "
Принудительная перезапись файлов (бэкап в .bak)
"
#: preset/commands.go:
15
#: preset/commands.go:
26
msgid "Manage preset configuration profiles"
msgstr "Управление профилями конфигурации"
#: preset/commands.go:
19
#: preset/commands.go:
30
msgid "Show information about preset profiles"
msgstr "Показать информацию о профилях"
#: preset/commands.go:
27
#: preset/commands.go:
38
msgid "Apply a profile"
msgstr "Применить профиль"
#: preset/commands.go:31 preset/commands.go:45
msgid "Show what would be created without making changes"
msgstr "Показать что будет создано без внесения изменений"
#: preset/commands.go:41
#: preset/commands.go:45
msgid "Apply all available profiles"
msgstr "Применить все доступные профили"
#: preset/tools.go:51
#, c-format
msgid "Error: %s"
msgstr "Ошибка: %s"
#: preset/tools.go:85
msgid "Copying"
msgstr "Копируем"
#: preset/tools.go:86
msgid "Creating links"
msgstr "Создаём ссылки"
#: preset/tools.go:87
msgid "Updating"
msgstr "Обновляем"
#: preset/tools.go:88
msgid "Configuring system"
msgstr "Настраиваем систему"
#: preset/helpers.go:93
msgid "Profiles"
msgstr "Профили"
#: preset/
tools.go:89
msgid "
Creating Hyprland variables
"
msgstr "
Создаём переменные Hyprland
"
#: preset/
result.go:74
msgid "
done
"
msgstr "
выполнено
"
#: preset/
tools.go:90
msgid "
Enabling Hyprland modules
"
msgstr "
Подключаем модули Hyprland
"
#: preset/
result.go:77
msgid "
skipped
"
msgstr "
пропущено
"
#: preset/tools.go:91
msgid "Syncing Hyprland layout"
msgstr "Синхронизируем раскладку Hyprland"
#: preset/result.go:80
msgid "error"
msgid_plural "errors"
msgstr[0] "ошибка"
msgstr[1] "ошибки"
msgstr[2] "ошибок"
#: repo/actions.go:31 repo/actions.go:38
msgid "Error fetching data"
...
...
@@ -718,3 +718,39 @@ msgstr "Глобальные параметры:"
#: ui/help.go:125
msgid "default"
msgstr "по умолчанию"
#, c-format
#~ msgid "Module '%s': command '%s' failed"
#~ msgstr "Модуль '%s': команда '%s' завершилась с ошибкой"
#, c-format
#~ msgid "Hyprland layout: %s"
#~ msgstr "Раскладка Hyprland: %s"
#~ msgid "Creating all available profiles..."
#~ msgstr "Создаём все доступные профили..."
#, c-format
#~ msgid "Error: %s"
#~ msgstr "Ошибка: %s"
#~ msgid "Copying"
#~ msgstr "Копируем"
#~ msgid "Creating links"
#~ msgstr "Создаём ссылки"
#~ msgid "Updating"
#~ msgstr "Обновляем"
#~ msgid "Configuring system"
#~ msgstr "Настраиваем систему"
#~ msgid "Creating Hyprland variables"
#~ msgstr "Создаём переменные Hyprland"
#~ msgid "Enabling Hyprland modules"
#~ msgstr "Подключаем модули Hyprland"
#~ msgid "Syncing Hyprland layout"
#~ msgstr "Синхронизируем раскладку Hyprland"
po/ximperconf.pot
View file @
72107231
...
...
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: ximperconf\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-02-14
18:34
+0300\n"
"POT-Creation-Date: 2026-02-14
21:56
+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
...
...
@@ -16,6 +16,7 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: hyprland/actions.go:27
msgid "Configuration fixed"
...
...
@@ -229,19 +230,19 @@ msgstr ""
msgid "toggle plugin"
msgstr ""
#: hyprland/keyboard-actions.go:4
2
#: hyprland/keyboard-actions.go:4
3
msgid "failed to get system layouts: %w"
msgstr ""
#: hyprland/keyboard-actions.go:
49
#: hyprland/keyboard-actions.go:
50
msgid "failed to update kb_layout in Hyprland: %w"
msgstr ""
#: hyprland/keyboard-actions.go:5
1
#: hyprland/keyboard-actions.go:5
2
msgid "Layout updated!"
msgstr ""
#: hyprland/keyboard-actions.go:5
3
#: hyprland/keyboard-actions.go:5
4
msgid "layout is already set, use '--force' for forced update"
msgstr ""
...
...
@@ -282,7 +283,7 @@ msgstr ""
msgid "module '%s' is already disabled"
msgstr ""
#: hyprland/manager.go:416
preset/actions.go:240 preset/actions.go:267
#: hyprland/manager.go:416
#, c-format
msgid "Module '%s' disabled"
msgstr ""
...
...
@@ -292,12 +293,12 @@ msgstr ""
msgid "module '%s' not found in config"
msgstr ""
#: hyprland/manager.go:428
preset/actions.go:242 preset/actions.go:269
#: hyprland/manager.go:428
#, c-format
msgid "Module '%s' removed"
msgstr ""
#: hyprland/manager.go:434
preset/actions.go:238 preset/actions.go:265
#: hyprland/manager.go:434
#, c-format
msgid "Module '%s' enabled"
msgstr ""
...
...
@@ -339,7 +340,7 @@ msgstr ""
msgid "Plugin '%s' unloaded"
msgstr ""
#: hyprland/manager.go:886 hyprland/manager.go:925 hyprland/var-actions.go:11
7
#: hyprland/manager.go:886 hyprland/manager.go:925 hyprland/var-actions.go:11
6
msgid "specify variable name"
msgstr ""
...
...
@@ -367,43 +368,43 @@ msgstr ""
msgid "VARS block created, variable '%s' set: %s"
msgstr ""
#: hyprland/manager.go:935 hyprland/var-actions.go:14
5
#: hyprland/manager.go:935 hyprland/var-actions.go:14
4
#, c-format
msgid "variable '%s' not found"
msgstr ""
#: hyprland/plugin-actions.go:5
3
#: hyprland/plugin-actions.go:5
4
msgid "no available plugins"
msgstr ""
#: hyprland/plugin-actions.go:8
0
#: hyprland/plugin-actions.go:8
1
msgid "Plugins"
msgstr ""
#: hyprland/var-actions.go:5
2
#: hyprland/var-actions.go:5
1
msgid "No variables in configuration"
msgstr ""
#: hyprland/var-actions.go:7
7
#: hyprland/var-actions.go:7
6
msgid "Vars"
msgstr ""
#: hyprland/var-actions.go:9
4
#: hyprland/var-actions.go:9
3
#, c-format
msgid "variable '%s' is not set"
msgstr ""
#: hyprland/var-actions.go:12
1
#: hyprland/var-actions.go:12
0
#, c-format
msgid "Invalid variable name: %s"
msgstr ""
#: hyprland/var-actions.go:12
5
#: hyprland/var-actions.go:12
4
#, c-format
msgid "Configuration not found: %s"
msgstr ""
#: hyprland/var-actions.go:14
9
#: hyprland/var-actions.go:14
8
#, c-format
msgid "Variable '%s' removed"
msgstr ""
...
...
@@ -416,194 +417,193 @@ msgstr ""
msgid "show help"
msgstr ""
#: preset/actions.go:77
#: preset/actions.go:30
msgid "copy"
msgstr ""
#: preset/actions.go:40
msgid "link"
msgstr ""
#: preset/actions.go:64
msgid "replace"
msgstr ""
#: preset/actions.go:70
#, c-format
msgid "Not found: '%s'"
msgstr ""
#: preset/actions.go:
8
5
#: preset/actions.go:
7
5
msgid "Failed to read %s: %v"
msgstr ""
#: preset/actions.go:
93
#: preset/actions.go:
79
#, c-format
msgid "Locked: %s"
msgstr ""
#: preset/actions.go:
105
#: preset/actions.go:
88
#, c-format
msgid "Up to date: '%s'"
msgstr ""
#: preset/actions.go:
120
#: preset/actions.go:
97
msgid "Replace error %s: %v"
msgstr ""
#: preset/actions.go:136 preset/tools.go:45
#: preset/actions.go:111 preset/actions.go:113 preset/actions.go:119
#: preset/actions.go:123 preset/actions.go:125
msgid "system"
msgstr ""
#: preset/actions.go:119
msgid "GRUB error: %v"
msgstr ""
#: preset/actions.go:123 system/grub-actions.go:86
msgid "GRUB_GFXMODE fixed"
msgstr ""
#: preset/actions.go:125 system/grub-actions.go:89
msgid "update-grub completed"
msgstr ""
#: preset/actions.go:133 preset/actions.go:139 preset/actions.go:144
#: preset/actions.go:146
msgid "var"
msgstr ""
#: preset/actions.go:133
#, c-format
msgid "Already exists: %s"
msgstr ""
#: preset/actions.go:1
53
#: preset/actions.go:1
44
msgid "Error setting %s: %v"
msgstr ""
#: preset/actions.go:170
msgid "Skipped module without name"
#: preset/actions.go:154 preset/actions.go:159 preset/actions.go:167
#: preset/actions.go:173 preset/actions.go:181 preset/actions.go:184
#: preset/actions.go:190 preset/actions.go:195 preset/actions.go:197
msgid "module"
msgstr ""
#: preset/actions.go:178
#, c-format
msgid "Module '%s': unsupported action"
#: preset/actions.go:154
msgid "Skipped module without name"
msgstr ""
#: preset/actions.go:1
90
#: preset/actions.go:1
59
#, c-format
msgid "Module '%s':
command '%s' failed
"
msgid "Module '%s':
unsupported action
"
msgstr ""
#: preset/actions.go:1
98
#: preset/actions.go:1
67
#, c-format
msgid "Module '%s': co
mmand '%s' returned no data
"
msgid "Module '%s': co
ndition not met
"
msgstr ""
#: preset/actions.go:
208
#: preset/actions.go:
173
#, c-format
msgid "Module '%s': binary '%s' not found"
msgstr ""
#: preset/actions.go:
220 preset/actions.go:226
#: preset/actions.go:
181 preset/actions.go:184
#, c-format
msgid "Created empty module: %s"
msgstr ""
#: preset/actions.go:
258
#: preset/actions.go:
195
msgid "Error (%s): %v"
msgstr ""
#: preset/actions.go:286
msgid "Sync Hyprland layouts"
msgstr ""
#: preset/actions.go:295
msgid "Failed to get system layouts"
#: preset/actions.go:208 preset/actions.go:214 preset/actions.go:222
#: preset/actions.go:225 preset/actions.go:227
msgid "layout"
msgstr ""
#: preset/actions.go:308
#, c-format
msgid "XCB layout: %s"
#: preset/actions.go:208
msgid "Sync Hyprland layouts"
msgstr ""
#: preset/actions.go:312
#, c-format
msgid "Hyprland layout: %s"
#: preset/actions.go:214
msgid "Failed to get system layouts"
msgstr ""
#: preset/actions.go:
319
#: preset/actions.go:
222
msgid "Failed to update kb_layout"
msgstr ""
#: preset/actions.go:
3
25
#: preset/actions.go:
2
25
#, c-format
msgid "Layout updated: %s"
msgstr ""
#: preset/actions.go:
330
#: preset/actions.go:
227
#, c-format
msgid "Layout already set: %s"
msgstr ""
#: preset/actions.go:354
msgid "GRUB error: %v"
msgstr ""
#: preset/actions.go:359 system/grub-actions.go:86
msgid "GRUB_GFXMODE fixed"
msgstr ""
#: preset/actions.go:364 system/grub-actions.go:89
msgid "update-grub completed"
msgstr ""
#: preset/actions.go:392
#: preset/actions.go:270
#, c-format
msgid "
Creating profile: %s
"
msgid "
Profile %s not found
"
msgstr ""
#: preset/actions.go:
394
#: preset/actions.go:
275
#, c-format
msgid "Profile %s unavailable"
msgstr ""
#: preset/actions.go:408 preset/actions.go:468
msgid "no available profiles"
#: preset/actions.go:287 preset/actions.go:338
#, c-format
msgid "Applying profile: %s"
msgstr ""
#: preset/actions.go:
430
msgid "
P
rofiles"
#: preset/actions.go:
321 preset/helpers.go:73
msgid "
no available p
rofiles"
msgstr ""
#: preset/actions.go:451
#, c-format
msgid "Profile %s not found"
#: preset/commands.go:13
msgid "Show what would be created without making changes"
msgstr ""
#: preset/
actions.go:472
msgid "
Creating all available profiles...
"
#: preset/
commands.go:18
msgid "
Force overwrite existing files (backup to .bak)
"
msgstr ""
#: preset/commands.go:
15
#: preset/commands.go:
26
msgid "Manage preset configuration profiles"
msgstr ""
#: preset/commands.go:
19
#: preset/commands.go:
30
msgid "Show information about preset profiles"
msgstr ""
#: preset/commands.go:
27
#: preset/commands.go:
38
msgid "Apply a profile"
msgstr ""
#: preset/commands.go:31 preset/commands.go:45
msgid "Show what would be created without making changes"
msgstr ""
#: preset/commands.go:41
#: preset/commands.go:45
msgid "Apply all available profiles"
msgstr ""
#: preset/tools.go:51
#, c-format
msgid "Error: %s"
msgstr ""
#: preset/tools.go:85
msgid "Copying"
msgstr ""
#: preset/tools.go:86
msgid "Creating links"
msgstr ""
#: preset/tools.go:87
msgid "Updating"
msgstr ""
#: preset/tools.go:88
msgid "Configuring system"
#: preset/helpers.go:93
msgid "Profiles"
msgstr ""
#: preset/
tools.go:89
msgid "
Creating Hyprland variables
"
#: preset/
result.go:74
msgid "
done
"
msgstr ""
#: preset/
tools.go:90
msgid "
Enabling Hyprland modules
"
#: preset/
result.go:77
msgid "
skipped
"
msgstr ""
#: preset/tools.go:91
msgid "Syncing Hyprland layout"
msgstr ""
#: preset/result.go:80
msgid "error"
msgid_plural "errors"
msgstr[0] ""
msgstr[1] ""
#: repo/actions.go:31 repo/actions.go:38
msgid "Error fetching data"
...
...
preset/actions.go
View file @
72107231
...
...
@@ -2,44 +2,42 @@ package preset
import
(
"bytes"
"slices"
"ximperconf/config"
"ximperconf/hyprland"
"ximperconf/locale"
"ximperconf/system"
"ximperconf/ui"
"context"
"fmt"
"os"
"os/exec"
"slices"
"sort"
"strings"
"ximperconf/config"
"ximperconf/hyprland"
"ximperconf/locale"
"ximperconf/system"
"github.com/fatih/color"
"github.com/urfave/cli/v3"
)
var
(
validActions
=
[]
string
{
"enable"
,
"disable"
,
"remove"
}
)
var
validActions
=
[]
string
{
"enable"
,
"disable"
,
"remove"
}
// ===== Process functions =====
func
processCopies
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
for
_
,
c
:=
range
prof
.
Copy
{
src
:=
expandPath
(
c
.
Src
,
""
)
dest
:=
expandPath
(
c
.
Dest
,
""
)
r
:=
copyIfMissing
(
src
,
dest
,
opts
)
AppendOpResult
(
res
,
r
)
status
,
msg
:=
execCopy
(
src
,
dest
,
opts
)
res
.
Add
(
locale
.
T
(
"copy"
),
msg
,
status
)
}
}
func
processLinks
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
for
_
,
entry
:=
range
prof
.
Links
{
handle
:=
func
(
app
string
)
{
s
ys
:=
expandPath
(
entry
.
Sys
,
app
)
user
:=
expandPath
(
entry
.
User
,
app
)
r
:=
createLinkIfMissing
(
sys
,
user
,
opts
)
AppendOpResult
(
res
,
r
)
s
rc
:=
expandPath
(
entry
.
Src
,
app
)
dest
:=
expandPath
(
entry
.
Dest
,
app
)
status
,
msg
:=
execLink
(
src
,
dest
,
opts
)
res
.
Add
(
locale
.
T
(
"link"
),
msg
,
status
)
}
if
len
(
entry
.
Apps
)
>
0
{
...
...
@@ -51,48 +49,34 @@ func processLinks(prof config.PresetProfile, opts opOptions, res *Result) {
continue
}
if
entry
.
App
!=
""
&&
!
commandExists
(
entry
.
App
)
{
continue
}
handle
(
""
)
}
}
func
processRe
leaseRe
place
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
func
processReplace
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
sysVer
:=
getSystemVersion
()
for
_
,
rr
:=
range
prof
.
Re
leaseRe
place
{
for
_
,
rr
:=
range
prof
.
Replace
{
src
:=
expandPath
(
rr
.
Src
,
""
)
dest
:=
expandPath
(
rr
.
Dest
,
""
)
item
:=
handleReleas
eReplace
(
src
,
dest
,
sysVer
,
opts
)
res
.
Replaced
=
append
(
res
.
Replaced
,
item
)
status
,
msg
:=
handl
eReplace
(
src
,
dest
,
sysVer
,
opts
)
res
.
Add
(
locale
.
T
(
"replace"
),
msg
,
status
)
}
}
func
handleRe
leaseReplace
(
src
,
dest
,
sysVer
string
,
opts
opOptions
)
ui
.
TreeItem
{
func
handleRe
place
(
src
,
dest
,
sysVer
string
,
opts
opOptions
)
(
config
.
ItemStatus
,
string
)
{
if
_
,
err
:=
os
.
Stat
(
dest
);
os
.
IsNotExist
(
err
)
{
return
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Not found: '%s'"
),
dest
),
Status
:
config
.
OpStatus
.
Skipped
,
}
return
config
.
OpStatus
.
Skipped
,
fmt
.
Sprintf
(
locale
.
T
(
"Not found: '%s'"
),
dest
)
}
data
,
err
:=
os
.
ReadFile
(
dest
)
if
err
!=
nil
{
return
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Failed to read %s: %v"
),
dest
,
err
),
Status
:
config
.
OpStatus
.
Error
,
}
return
config
.
OpStatus
.
Error
,
fmt
.
Sprintf
(
locale
.
T
(
"Failed to read %s: %v"
),
dest
,
err
)
}
content
:=
string
(
data
)
if
strings
.
Contains
(
content
,
"XIMPER_LOCK"
)
{
return
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Locked: %s"
),
dest
),
Status
:
config
.
OpStatus
.
Skipped
,
}
if
strings
.
Contains
(
string
(
data
),
"XIMPER_LOCK"
)
{
return
config
.
OpStatus
.
Skipped
,
fmt
.
Sprintf
(
locale
.
T
(
"Locked: %s"
),
dest
)
}
fileVer
:=
getFileVersionTag
(
dest
)
...
...
@@ -100,341 +84,178 @@ func handleReleaseReplace(src, dest, sysVer string, opts opOptions) ui.TreeItem
fileVer
=
"0.9.3"
}
if
fileVer
==
sysVer
{
return
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Up to date: '%s'"
),
dest
),
Status
:
config
.
OpStatus
.
Skipped
,
}
if
!
opts
.
Force
&&
fileVer
==
sysVer
{
return
config
.
OpStatus
.
Skipped
,
fmt
.
Sprintf
(
locale
.
T
(
"Up to date: '%s'"
),
dest
)
}
if
opts
.
DryRun
{
return
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
"%s → %s"
,
src
,
dest
),
Status
:
config
.
OpStatus
.
DryRun
,
}
return
config
.
OpStatus
.
DryRun
,
fmt
.
Sprintf
(
"%s → %s"
,
src
,
dest
)
}
_
=
os
.
Rename
(
dest
,
dest
+
".
old
"
)
_
=
os
.
Rename
(
dest
,
dest
+
".
bak
"
)
if
err
:=
copyFile
(
src
,
dest
);
err
!=
nil
{
return
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Replace error %s: %v"
),
dest
,
err
),
Status
:
config
.
OpStatus
.
Error
,
return
config
.
OpStatus
.
Error
,
fmt
.
Sprintf
(
locale
.
T
(
"Replace error %s: %v"
),
dest
,
err
)
}
return
config
.
OpStatus
.
Done
,
fmt
.
Sprintf
(
"%s (%s → %s)"
,
dest
,
fileVer
,
sysVer
)
}
func
processSystemGrub
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
if
!
prof
.
System
.
Grub
.
FixResolution
{
return
}
autoUpdate
:=
prof
.
System
.
Grub
.
Update
if
opts
.
DryRun
{
res
.
Add
(
locale
.
T
(
"system"
),
"GRUB_GFXMODE"
,
config
.
OpStatus
.
DryRun
)
if
autoUpdate
{
res
.
Add
(
locale
.
T
(
"system"
),
"update-grub"
,
config
.
OpStatus
.
DryRun
)
}
return
}
if
err
:=
system
.
SystemGrubFixResolution
(
autoUpdate
,
false
);
err
!=
nil
{
res
.
Add
(
locale
.
T
(
"system"
),
fmt
.
Sprintf
(
locale
.
T
(
"GRUB error: %v"
),
err
),
config
.
OpStatus
.
Error
)
return
}
re
turn
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
"%s (%s → %s)"
,
dest
,
fileVer
,
sysVer
),
Status
:
config
.
OpStatus
.
Done
,
re
s
.
Add
(
locale
.
T
(
"system"
),
locale
.
T
(
"GRUB_GFXMODE fixed"
),
config
.
OpStatus
.
Done
)
if
autoUpdate
{
res
.
Add
(
locale
.
T
(
"system"
),
locale
.
T
(
"update-grub completed"
),
config
.
OpStatus
.
Done
)
}
}
func
processHyprVars
(
manager
*
hyprland
.
HyprlandManager
,
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
for
_
,
v
:=
range
prof
.
Hyprland
.
Vars
{
if
!
v
.
Force
{
if
V
:=
manager
.
GetVar
(
v
.
Var
);
V
!=
""
{
res
.
HyprVars
=
append
(
res
.
HyprVars
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Already exists: %s"
),
v
.
Var
),
Status
:
config
.
OpStatus
.
Skipped
,
})
if
existing
:=
manager
.
GetVar
(
v
.
Name
);
existing
!=
""
{
res
.
Add
(
locale
.
T
(
"var"
),
fmt
.
Sprintf
(
locale
.
T
(
"Already exists: %s"
),
v
.
Name
),
config
.
OpStatus
.
Skipped
)
continue
}
}
if
opts
.
DryRun
{
res
.
HyprVars
=
append
(
res
.
HyprVars
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
"%s = %s"
,
v
.
Var
,
v
.
Value
),
Status
:
config
.
OpStatus
.
DryRun
,
})
res
.
Add
(
locale
.
T
(
"var"
),
fmt
.
Sprintf
(
"%s = %s"
,
v
.
Name
,
v
.
Value
),
config
.
OpStatus
.
DryRun
)
continue
}
if
_
,
err
:=
manager
.
SetVar
(
v
.
Var
,
v
.
Value
);
err
!=
nil
{
res
.
HyprVars
=
append
(
res
.
HyprVars
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Error setting %s: %v"
),
v
.
Var
,
err
),
Status
:
config
.
OpStatus
.
Error
,
})
if
_
,
err
:=
manager
.
SetVar
(
v
.
Name
,
v
.
Value
);
err
!=
nil
{
res
.
Add
(
locale
.
T
(
"var"
),
fmt
.
Sprintf
(
locale
.
T
(
"Error setting %s: %v"
),
v
.
Name
,
err
),
config
.
OpStatus
.
Error
)
}
else
{
res
.
HyprVars
=
append
(
res
.
HyprVars
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
"%s = %s"
,
v
.
Var
,
v
.
Value
),
Status
:
config
.
OpStatus
.
Done
,
})
res
.
Add
(
locale
.
T
(
"var"
),
fmt
.
Sprintf
(
"%s = %s"
,
v
.
Name
,
v
.
Value
),
config
.
OpStatus
.
Done
)
}
}
}
func
processHyprModules
(
manager
*
hyprland
.
HyprlandManager
,
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
for
_
,
m
:=
range
prof
.
Hyprland
.
Modules
{
if
m
.
Module
==
""
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
locale
.
T
(
"Skipped module without name"
),
Status
:
config
.
OpStatus
.
Skipped
,
})
if
m
.
Name
==
""
{
res
.
Add
(
locale
.
T
(
"module"
),
locale
.
T
(
"Skipped module without name"
),
config
.
OpStatus
.
Skipped
)
continue
}
if
!
slices
.
Contains
(
validActions
,
m
.
Action
)
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': unsupported action"
),
m
.
Module
),
Status
:
config
.
OpStatus
.
Skipped
,
})
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': unsupported action"
),
m
.
Name
),
config
.
OpStatus
.
Skipped
)
continue
}
// if-exec
if
m
.
IfExec
!=
""
{
cmd
:=
exec
.
Command
(
"sh"
,
"-c"
,
m
.
IfExec
)
output
,
err
:=
cmd
.
Output
()
if
err
!=
nil
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': command '%s' failed"
),
m
.
Module
,
m
.
IfExec
),
Status
:
config
.
OpStatus
.
Skipped
,
})
continue
}
if
len
(
bytes
.
TrimSpace
(
output
))
==
0
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': command '%s' returned no data"
),
m
.
Module
,
m
.
IfExec
),
Status
:
config
.
OpStatus
.
Skipped
,
})
if
err
!=
nil
||
len
(
bytes
.
TrimSpace
(
output
))
==
0
{
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': condition not met"
),
m
.
Name
),
config
.
OpStatus
.
Skipped
)
continue
}
}
// if-binary
if
m
.
IfBinary
!=
""
&&
!
commandExists
(
m
.
IfBinary
)
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': binary '%s' not found"
),
m
.
Module
,
m
.
IfBinary
),
Status
:
config
.
OpStatus
.
Skipped
,
})
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s': binary '%s' not found"
),
m
.
Name
,
m
.
IfBinary
),
config
.
OpStatus
.
Skipped
)
continue
}
if
m
.
Init
&&
m
.
User
&&
m
.
Action
==
"enable"
{
modulefile
:=
manager
.
GetModuleFile
(
m
.
Module
,
m
.
User
)
modulefile
:=
manager
.
GetModuleFile
(
m
.
Name
,
m
.
User
)
if
_
,
err
:=
os
.
Stat
(
modulefile
);
os
.
IsNotExist
(
err
)
{
if
opts
.
DryRun
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Created empty module: %s"
),
m
.
Module
),
Status
:
config
.
OpStatus
.
DryRun
,
})
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
locale
.
T
(
"Created empty module: %s"
),
m
.
Name
),
config
.
OpStatus
.
DryRun
)
}
else
{
_
=
os
.
WriteFile
(
modulefile
,
[]
byte
(
""
),
0
o644
)
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Created empty module: %s"
),
m
.
Module
),
Status
:
config
.
OpStatus
.
Done
,
})
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
locale
.
T
(
"Created empty module: %s"
),
m
.
Name
),
config
.
OpStatus
.
Done
)
}
}
}
// dry-run
if
opts
.
DryRun
{
var
msg
string
switch
m
.
Action
{
case
"enable"
:
msg
=
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s' enabled"
),
m
.
Module
)
case
"disable"
:
msg
=
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s' disabled"
),
m
.
Module
)
case
"remove"
:
msg
=
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s' removed"
),
m
.
Module
)
}
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
msg
,
Status
:
config
.
OpStatus
.
DryRun
,
})
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
"%s: %s"
,
m
.
Name
,
m
.
Action
),
config
.
OpStatus
.
DryRun
)
continue
}
if
_
,
err
:=
manager
.
SetModule
(
m
.
Action
,
m
.
Module
,
m
.
User
,
m
.
NewOnly
,
);
err
!=
nil
{
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Error (%s): %v"
),
m
.
Module
,
err
),
Status
:
config
.
OpStatus
.
Error
,
})
if
_
,
err
:=
manager
.
SetModule
(
m
.
Action
,
m
.
Name
,
m
.
User
,
m
.
NewOnly
);
err
!=
nil
{
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
locale
.
T
(
"Error (%s): %v"
),
m
.
Name
,
err
),
config
.
OpStatus
.
Error
)
}
else
{
var
msg
string
switch
m
.
Action
{
case
"enable"
:
msg
=
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s' enabled"
),
m
.
Module
)
case
"disable"
:
msg
=
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s' disabled"
),
m
.
Module
)
case
"remove"
:
msg
=
fmt
.
Sprintf
(
locale
.
T
(
"Module '%s' removed"
),
m
.
Module
)
}
res
.
HyprModules
=
append
(
res
.
HyprModules
,
ui
.
TreeItem
{
Name
:
msg
,
Status
:
config
.
OpStatus
.
Done
,
})
res
.
Add
(
locale
.
T
(
"module"
),
fmt
.
Sprintf
(
"%s: %s"
,
m
.
Name
,
m
.
Action
),
config
.
OpStatus
.
Done
)
}
}
}
func
processHyprLayoutSync
(
manager
*
hyprland
.
HyprlandManager
,
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
if
!
prof
.
Hyprland
.
Option
.
SyncSystem
Layouts
{
if
!
prof
.
Hyprland
.
Sync
Layouts
{
return
}
if
opts
.
DryRun
{
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
locale
.
T
(
"Sync Hyprland layouts"
),
Status
:
config
.
OpStatus
.
DryRun
,
})
res
.
Add
(
locale
.
T
(
"layout"
),
locale
.
T
(
"Sync Hyprland layouts"
),
config
.
OpStatus
.
DryRun
)
return
}
sysLayouts
,
err
:=
hyprland
.
HyprlandGetKeyboardLayouts
()
if
err
!=
nil
{
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
locale
.
T
(
"Failed to get system layouts"
),
Status
:
config
.
OpStatus
.
Error
,
})
res
.
Add
(
locale
.
T
(
"layout"
),
locale
.
T
(
"Failed to get system layouts"
),
config
.
OpStatus
.
Error
)
return
}
hyprLayouts
:=
manager
.
GetVar
(
"kb_layout"
)
if
hyprLayouts
==
""
{
hyprLayouts
=
"<empty>"
}
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"XCB layout: %s"
),
sysLayouts
),
Status
:
config
.
OpStatus
.
Done
,
})
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Hyprland layout: %s"
),
hyprLayouts
),
Status
:
config
.
OpStatus
.
Done
,
})
if
hyprLayouts
==
"<empty>"
{
if
_
,
err
:=
manager
.
SetVar
(
"kb_layout"
,
sysLayouts
);
err
!=
nil
{
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
locale
.
T
(
"Failed to update kb_layout"
),
Status
:
config
.
OpStatus
.
Error
,
})
res
.
Add
(
locale
.
T
(
"layout"
),
locale
.
T
(
"Failed to update kb_layout"
),
config
.
OpStatus
.
Error
)
return
}
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Layout updated: %s"
),
sysLayouts
),
Status
:
config
.
OpStatus
.
Done
,
})
res
.
Add
(
locale
.
T
(
"layout"
),
fmt
.
Sprintf
(
locale
.
T
(
"Layout updated: %s"
),
sysLayouts
),
config
.
OpStatus
.
Done
)
}
else
{
res
.
SyncSystemLayouts
=
append
(
res
.
SyncSystemLayouts
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"Layout already set: %s"
),
hyprLayouts
),
Status
:
config
.
OpStatus
.
Skipped
,
})
}
}
func
processSystemGrub
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
)
{
if
prof
.
System
.
Grub
.
FixResolution
{
autoUpdate
:=
prof
.
System
.
Grub
.
Update
if
opts
.
DryRun
{
res
.
System
=
append
(
res
.
System
,
ui
.
TreeItem
{
Name
:
"GRUB_GFXMODE"
,
Status
:
config
.
OpStatus
.
DryRun
,
})
if
autoUpdate
{
res
.
System
=
append
(
res
.
System
,
ui
.
TreeItem
{
Name
:
"update-grub"
,
Status
:
config
.
OpStatus
.
DryRun
,
})
}
}
else
{
err
:=
system
.
SystemGrubFixResolution
(
autoUpdate
,
false
)
if
err
!=
nil
{
res
.
System
=
append
(
res
.
System
,
ui
.
TreeItem
{
Name
:
fmt
.
Sprintf
(
locale
.
T
(
"GRUB error: %v"
),
err
),
Status
:
config
.
OpStatus
.
Error
,
})
}
else
{
res
.
System
=
append
(
res
.
System
,
ui
.
TreeItem
{
Name
:
locale
.
T
(
"GRUB_GFXMODE fixed"
),
Status
:
config
.
OpStatus
.
Done
,
})
if
autoUpdate
{
res
.
System
=
append
(
res
.
System
,
ui
.
TreeItem
{
Name
:
locale
.
T
(
"update-grub completed"
),
Status
:
config
.
OpStatus
.
Done
,
})
}
}
}
res
.
Add
(
locale
.
T
(
"layout"
),
fmt
.
Sprintf
(
locale
.
T
(
"Layout already set: %s"
),
hyprLayouts
),
config
.
OpStatus
.
Skipped
)
}
}
func
processProfile
(
prof
config
.
PresetProfile
,
dryRun
bool
,
res
*
Result
)
{
opts
:=
opOptions
{
DryRun
:
dryRun
}
// ===== Orchestration =====
func
processProfile
(
prof
config
.
PresetProfile
,
opts
opOptions
,
res
*
Result
,
manager
*
hyprland
.
HyprlandManager
)
{
processCopies
(
prof
,
opts
,
res
)
processLinks
(
prof
,
opts
,
res
)
processReleaseReplace
(
prof
,
opts
,
res
)
processReplace
(
prof
,
opts
,
res
)
if
config
.
Env
.
IsRoot
{
processSystemGrub
(
prof
,
opts
,
res
)
}
manager
,
err
:=
hyprland
.
NewHyprlandManager
()
if
err
!=
nil
{
return
}
processHyprVars
(
manager
,
prof
,
opts
,
res
)
processHyprModules
(
manager
,
prof
,
opts
,
res
)
processHyprLayoutSync
(
manager
,
prof
,
opts
,
res
)
}
func
createProfile
(
profileName
string
,
prof
config
.
PresetProfile
,
dryRun
bool
,
res
*
Result
)
{
color
.
Green
(
locale
.
T
(
"Creating profile: %s"
),
profileName
)
if
!
profileAvailable
(
prof
)
{
color
.
Red
(
locale
.
T
(
"Profile %s unavailable"
),
profileName
)
return
if
manager
!=
nil
{
processHyprVars
(
manager
,
prof
,
opts
,
res
)
processHyprModules
(
manager
,
prof
,
opts
,
res
)
processHyprLayoutSync
(
manager
,
prof
,
opts
,
res
)
}
processProfile
(
prof
,
dryRun
,
res
)
}
func
ShowProfilesInfo
(
jsonFormat
bool
)
{
profiles
:=
config
.
Env
.
Preset
.
Profiles
if
len
(
profiles
)
==
0
{
if
jsonFormat
{
ui
.
PrintJSON
([]
ui
.
JSONItem
{})
return
}
color
.
Red
(
locale
.
T
(
"no available profiles"
))
return
}
items
:=
make
([]
ui
.
TreeItem
,
0
,
len
(
profiles
))
for
name
,
profile
:=
range
profiles
{
status
:=
profileStatus
(
profile
)
desc
:=
profile
.
Description
items
=
append
(
items
,
ui
.
TreeItem
{
Name
:
name
,
Status
:
status
,
Description
:
desc
,
})
}
if
jsonFormat
{
ui
.
PrintJSON
(
ui
.
TreeItemsToJSON
(
items
))
return
func
tryCreateManager
()
*
hyprland
.
HyprlandManager
{
manager
,
err
:=
hyprland
.
NewHyprlandManager
()
if
err
!=
nil
{
return
nil
}
ui
.
RenderTree
(
ui
.
RenderTreeOptions
{
Title
:
locale
.
T
(
"Profiles"
),
Items
:
items
,
Style
:
ui
.
DefaultTreeStyle
,
Color
:
true
,
Sort
:
true
,
})
return
manager
}
// ===== Command handlers =====
func
presetApplyCommand
(
ctx
context
.
Context
,
cmd
*
cli
.
Command
)
error
{
profileName
:=
cmd
.
Args
()
.
Get
(
0
)
...
...
@@ -443,42 +264,84 @@ func presetApplyCommand(ctx context.Context, cmd *cli.Command) error {
return
nil
}
dryRun
:=
cmd
.
Bool
(
"dry-run"
)
profiles
:=
config
.
Env
.
Preset
.
Profiles
mainProf
,
ok
:=
profiles
[
profileName
]
profiles
:=
config
.
Env
.
Profiles
prof
,
ok
:=
profiles
[
profileName
]
if
!
ok
{
color
.
Red
(
locale
.
T
(
"Profile %s not found"
),
profileName
)
return
nil
}
if
!
profileAvailable
(
prof
)
{
color
.
Red
(
locale
.
T
(
"Profile %s unavailable"
),
profileName
)
return
nil
}
opts
:=
opOptions
{
DryRun
:
cmd
.
Bool
(
"dry-run"
),
Force
:
cmd
.
Bool
(
"force"
),
}
manager
:=
tryCreateManager
()
res
:=
&
Result
{}
createProfile
(
profileName
,
mainProf
,
dryRun
,
res
)
renderPresetResult
(
res
)
color
.
Green
(
locale
.
T
(
"Applying profile: %s"
),
profileName
)
processProfile
(
prof
,
opts
,
res
,
manager
)
res
.
Render
()
if
manager
!=
nil
&&
manager
.
Changed
&&
!
opts
.
DryRun
{
manager
.
Save
()
}
return
nil
}
func
presetApplyAllCommand
(
ctx
context
.
Context
,
cmd
*
cli
.
Command
)
error
{
dryRun
:=
cmd
.
Bool
(
"dry-run"
)
type
namedProfile
struct
{
Name
string
Profile
config
.
PresetProfile
}
profiles
:=
config
.
Env
.
Preset
.
Profiles
func
sortedProfiles
(
profiles
map
[
string
]
config
.
PresetProfile
)
[]
namedProfile
{
sorted
:=
make
([]
namedProfile
,
0
,
len
(
profiles
))
for
name
,
prof
:=
range
profiles
{
sorted
=
append
(
sorted
,
namedProfile
{
Name
:
name
,
Profile
:
prof
})
}
sort
.
Slice
(
sorted
,
func
(
i
,
j
int
)
bool
{
if
sorted
[
i
]
.
Profile
.
Priority
!=
sorted
[
j
]
.
Profile
.
Priority
{
return
sorted
[
i
]
.
Profile
.
Priority
<
sorted
[
j
]
.
Profile
.
Priority
}
return
sorted
[
i
]
.
Name
<
sorted
[
j
]
.
Name
})
return
sorted
}
func
presetApplyAllCommand
(
ctx
context
.
Context
,
cmd
*
cli
.
Command
)
error
{
profiles
:=
config
.
Env
.
Profiles
if
len
(
profiles
)
==
0
{
color
.
Red
(
locale
.
T
(
"no available profiles"
))
return
nil
}
color
.
Cyan
(
locale
.
T
(
"Creating all available profiles..."
)
+
"
\n
"
)
opts
:=
opOptions
{
DryRun
:
cmd
.
Bool
(
"dry-run"
),
Force
:
cmd
.
Bool
(
"force"
),
}
for
name
,
prof
:=
range
profiles
{
if
!
profileAvailable
(
prof
)
{
manager
:=
tryCreateManager
()
for
_
,
np
:=
range
sortedProfiles
(
profiles
)
{
if
!
profileAvailable
(
np
.
Profile
)
{
continue
}
res
:=
&
Result
{}
createProfile
(
name
,
prof
,
dryRun
,
res
)
renderPresetResult
(
res
)
color
.
Green
(
locale
.
T
(
"Applying profile: %s"
),
np
.
Name
)
processProfile
(
np
.
Profile
,
opts
,
res
,
manager
)
res
.
Render
()
}
if
manager
!=
nil
&&
manager
.
Changed
&&
!
opts
.
DryRun
{
manager
.
Save
()
}
return
nil
...
...
preset/commands.go
View file @
72107231
package
preset
import
(
"context"
"fmt"
"ximperconf/config"
"ximperconf/locale"
"github.com/urfave/cli/v3"
)
var
presetFlags
=
[]
cli
.
Flag
{
&
cli
.
BoolFlag
{
Name
:
"dry-run"
,
Usage
:
locale
.
T
(
"Show what would be created without making changes"
),
Aliases
:
[]
string
{
"d"
},
},
&
cli
.
BoolFlag
{
Name
:
"force"
,
Usage
:
locale
.
T
(
"Force overwrite existing files (backup to .bak)"
),
Aliases
:
[]
string
{
"f"
},
},
}
func
CommandList
()
*
cli
.
Command
{
return
&
cli
.
Command
{
Name
:
"preset"
,
...
...
@@ -23,46 +34,18 @@ func CommandList() *cli.Command {
Action
:
presetInfoCommand
,
},
{
Name
:
"apply"
,
Usage
:
locale
.
T
(
"Apply a profile"
),
Flags
:
[]
cli
.
Flag
{
&
cli
.
BoolFlag
{
Name
:
"dry-run"
,
Usage
:
locale
.
T
(
"Show what would be created without making changes"
),
Aliases
:
[]
string
{
"d"
},
Value
:
false
,
},
},
Name
:
"apply"
,
Usage
:
locale
.
T
(
"Apply a profile"
),
Flags
:
presetFlags
,
Action
:
presetApplyCommand
,
ShellComplete
:
ShellCompleteProfiles
,
},
{
Name
:
"apply-all"
,
Usage
:
locale
.
T
(
"Apply all available profiles"
),
Flags
:
[]
cli
.
Flag
{
&
cli
.
BoolFlag
{
Name
:
"dry-run"
,
Usage
:
locale
.
T
(
"Show what would be created without making changes"
),
Aliases
:
[]
string
{
"d"
},
Value
:
false
,
},
},
Name
:
"apply-all"
,
Usage
:
locale
.
T
(
"Apply all available profiles"
),
Flags
:
presetFlags
,
Action
:
presetApplyAllCommand
,
},
},
}
}
func
ShellCompleteProfiles
(
ctx
context
.
Context
,
cmd
*
cli
.
Command
)
{
if
cmd
.
NArg
()
>
0
{
return
}
profiles
:=
config
.
Env
.
Preset
.
Profiles
for
profileName
,
profile
:=
range
profiles
{
if
profileAvailable
(
profile
)
{
fmt
.
Println
(
profileName
)
}
}
}
preset/files.go
→
preset/file
op
s.go
View file @
72107231
...
...
@@ -2,67 +2,35 @@ package preset
import
(
"os"
"os/exec"
"path/filepath"
"strings"
"ximperconf/config"
)
type
opKind
string
const
(
OpCopy
opKind
=
"copy"
OpLink
opKind
=
"link"
OpReplace
opKind
=
"replace"
)
type
opOptions
struct
{
DryRun
bool
Force
bool
}
type
opResult
struct
{
Kind
opKind
Status
config
.
ItemStatus
Source
string
Target
string
Reason
string
}
func
commandExists
(
name
string
)
bool
{
_
,
err
:=
exec
.
LookPath
(
name
)
return
err
==
nil
}
// ===== Copy =====
func
copyIfMissing
(
src
,
dest
string
,
opts
opOptions
)
opResult
{
func
execCopy
(
src
,
dest
string
,
opts
opOptions
)
(
config
.
ItemStatus
,
string
)
{
info
,
err
:=
os
.
Stat
(
src
)
if
err
!=
nil
{
return
opResult
{
Kind
:
OpCopy
,
Status
:
config
.
OpStatus
.
Error
,
Source
:
src
,
Target
:
dest
,
Reason
:
err
.
Error
(),
}
return
config
.
OpStatus
.
Error
,
err
.
Error
()
}
exists
:=
false
if
_
,
err
:=
os
.
Stat
(
dest
);
err
==
nil
{
return
opResult
{
Kind
:
OpCopy
,
Status
:
config
.
OpStatus
.
Skipped
,
Target
:
dest
,
Reason
:
"already exists"
,
if
!
opts
.
Force
{
return
config
.
OpStatus
.
Skipped
,
dest
+
" (exists)"
}
exists
=
true
if
!
opts
.
DryRun
{
_
=
os
.
Rename
(
dest
,
dest
+
".bak"
)
}
}
if
opts
.
DryRun
{
return
opResult
{
Kind
:
OpCopy
,
Status
:
config
.
OpStatus
.
DryRun
,
Source
:
src
,
Target
:
dest
,
}
return
config
.
OpStatus
.
DryRun
,
src
+
" → "
+
dest
}
if
info
.
IsDir
()
{
...
...
@@ -72,21 +40,49 @@ func copyIfMissing(src, dest string, opts opOptions) opResult {
}
if
err
!=
nil
{
return
opResult
{
Kind
:
OpCopy
,
Status
:
config
.
OpStatus
.
Error
,
Source
:
src
,
Target
:
dest
,
Reason
:
err
.
Error
(),
return
config
.
OpStatus
.
Error
,
err
.
Error
()
}
msg
:=
src
+
" → "
+
dest
if
exists
{
msg
+=
" (overwritten)"
}
return
config
.
OpStatus
.
Done
,
msg
}
func
execLink
(
src
,
dest
string
,
opts
opOptions
)
(
config
.
ItemStatus
,
string
)
{
if
_
,
err
:=
os
.
Stat
(
src
);
os
.
IsNotExist
(
err
)
{
return
config
.
OpStatus
.
Skipped
,
src
+
" → "
+
dest
+
" (source missing)"
}
exists
:=
false
if
_
,
err
:=
os
.
Lstat
(
dest
);
err
==
nil
{
if
!
opts
.
Force
{
return
config
.
OpStatus
.
Skipped
,
dest
+
" (exists)"
}
exists
=
true
if
!
opts
.
DryRun
{
_
=
os
.
Rename
(
dest
,
dest
+
".bak"
)
}
}
return
opResult
{
Kind
:
OpCopy
,
Status
:
config
.
OpStatus
.
Done
,
Source
:
src
,
Target
:
dest
,
if
opts
.
DryRun
{
return
config
.
OpStatus
.
DryRun
,
src
+
" → "
+
dest
}
if
err
:=
os
.
MkdirAll
(
filepath
.
Dir
(
dest
),
0755
);
err
!=
nil
{
return
config
.
OpStatus
.
Error
,
err
.
Error
()
}
if
err
:=
os
.
Symlink
(
src
,
dest
);
err
!=
nil
{
return
config
.
OpStatus
.
Error
,
err
.
Error
()
}
msg
:=
src
+
" → "
+
dest
if
exists
{
msg
+=
" (overwritten)"
}
return
config
.
OpStatus
.
Done
,
msg
}
func
copyFile
(
src
,
dest
string
)
error
{
...
...
@@ -121,60 +117,8 @@ func copyDir(src, dest string) error {
})
}
// ===== Link =====
func
createLinkIfMissing
(
src
,
dest
string
,
opts
opOptions
)
opResult
{
if
_
,
err
:=
os
.
Stat
(
src
);
os
.
IsNotExist
(
err
)
{
return
opResult
{
Kind
:
OpLink
,
Status
:
config
.
OpStatus
.
Skipped
,
Target
:
dest
,
Reason
:
"source missing"
,
}
}
if
_
,
err
:=
os
.
Lstat
(
dest
);
err
==
nil
{
return
opResult
{
Kind
:
OpLink
,
Status
:
config
.
OpStatus
.
Skipped
,
Target
:
dest
,
Reason
:
"already exists"
,
}
}
if
opts
.
DryRun
{
return
opResult
{
Kind
:
OpLink
,
Status
:
config
.
OpStatus
.
DryRun
,
Source
:
src
,
Target
:
dest
,
}
}
if
err
:=
os
.
MkdirAll
(
filepath
.
Dir
(
dest
),
0755
);
err
!=
nil
{
return
opResult
{
Kind
:
OpLink
,
Status
:
config
.
OpStatus
.
Error
,
Reason
:
err
.
Error
(),
}
}
if
err
:=
os
.
Symlink
(
src
,
dest
);
err
!=
nil
{
return
opResult
{
Kind
:
OpLink
,
Status
:
config
.
OpStatus
.
Error
,
Reason
:
err
.
Error
(),
}
}
return
opResult
{
Kind
:
OpLink
,
Status
:
config
.
OpStatus
.
Done
,
Source
:
src
,
Target
:
dest
,
}
}
// ===== Versions =====
func
getSystemVersion
()
string
{
data
,
err
:=
os
.
ReadFile
(
"/etc/os-release"
)
if
err
!=
nil
{
...
...
preset/
tool
s.go
→
preset/
helper
s.go
View file @
72107231
package
preset
import
(
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"ximperconf/config"
"ximperconf/locale"
"ximperconf/ui"
"github.com/urfave/cli/v3"
)
func
profileAvailable
(
p
config
.
PresetProfile
)
bool
{
allowed
:=
false
if
p
.
Binary
==
""
{
allowed
=
true
}
else
if
_
,
err
:=
exec
.
LookPath
(
p
.
Binary
);
err
==
nil
{
allowed
=
true
if
p
.
Binary
!=
""
{
if
_
,
err
:=
exec
.
LookPath
(
p
.
Binary
);
err
!=
nil
{
return
false
}
}
if
p
.
Root
!=
config
.
Env
.
IsRoot
{
allowed
=
false
return
false
}
return
allowed
return
true
}
func
profileStatus
(
p
config
.
PresetProfile
)
config
.
ItemStatus
{
...
...
@@ -35,28 +35,9 @@ func profileStatus(p config.PresetProfile) config.ItemStatus {
return
config
.
ProfileStatus
.
Unavailable
}
func
AppendOpResult
(
res
*
Result
,
r
opResult
)
{
item
:=
ui
.
TreeItem
{
Status
:
r
.
Status
,
}
switch
{
case
r
.
Status
.
IsEqual
(
config
.
OpStatus
.
Skipped
)
:
item
.
Name
=
fmt
.
Sprintf
(
locale
.
T
(
"Already exists: %s"
),
r
.
Target
)
case
r
.
Status
.
IsEqual
(
config
.
OpStatus
.
DryRun
)
:
item
.
Name
=
fmt
.
Sprintf
(
"%s → %s"
,
r
.
Source
,
r
.
Target
)
case
r
.
Status
.
IsEqual
(
config
.
OpStatus
.
Done
)
:
item
.
Name
=
fmt
.
Sprintf
(
"%s → %s"
,
r
.
Source
,
r
.
Target
)
case
r
.
Status
.
IsEqual
(
config
.
OpStatus
.
Error
)
:
item
.
Name
=
fmt
.
Sprintf
(
locale
.
T
(
"Error: %s"
),
r
.
Reason
)
}
switch
r
.
Kind
{
case
OpCopy
:
res
.
Copies
=
append
(
res
.
Copies
,
item
)
case
OpLink
:
res
.
Links
=
append
(
res
.
Links
,
item
)
}
func
commandExists
(
name
string
)
bool
{
_
,
err
:=
exec
.
LookPath
(
name
)
return
err
==
nil
}
func
expandPath
(
path
,
name
string
)
string
{
...
...
@@ -81,12 +62,50 @@ func expandPath(path, name string) string {
return
path
}
func
renderPresetResult
(
res
*
Result
)
{
ui
.
RenderTreeItems
(
locale
.
T
(
"Copying"
),
res
.
Copies
)
ui
.
RenderTreeItems
(
locale
.
T
(
"Creating links"
),
res
.
Links
)
ui
.
RenderTreeItems
(
locale
.
T
(
"Updating"
),
res
.
Replaced
)
ui
.
RenderTreeItems
(
locale
.
T
(
"Configuring system"
),
res
.
System
)
ui
.
RenderTreeItems
(
locale
.
T
(
"Creating Hyprland variables"
),
res
.
HyprVars
)
ui
.
RenderTreeItems
(
locale
.
T
(
"Enabling Hyprland modules"
),
res
.
HyprModules
)
ui
.
RenderTreeItems
(
locale
.
T
(
"Syncing Hyprland layout"
),
res
.
SyncSystemLayouts
)
func
ShowProfilesInfo
(
jsonFormat
bool
)
{
profiles
:=
config
.
Env
.
Profiles
if
len
(
profiles
)
==
0
{
if
jsonFormat
{
ui
.
PrintJSON
([]
ui
.
JSONItem
{})
return
}
fmt
.
Println
(
config
.
OpStatus
.
Error
.
Color
(
locale
.
T
(
"no available profiles"
)))
return
}
items
:=
make
([]
ui
.
TreeItem
,
0
,
len
(
profiles
))
for
name
,
profile
:=
range
profiles
{
items
=
append
(
items
,
ui
.
TreeItem
{
Name
:
name
,
Status
:
profileStatus
(
profile
),
Description
:
profile
.
Description
,
})
}
if
jsonFormat
{
ui
.
PrintJSON
(
ui
.
TreeItemsToJSON
(
items
))
return
}
ui
.
RenderTree
(
ui
.
RenderTreeOptions
{
Title
:
locale
.
T
(
"Profiles"
),
Items
:
items
,
Style
:
ui
.
DefaultTreeStyle
,
Color
:
true
,
Sort
:
true
,
})
}
func
ShellCompleteProfiles
(
ctx
context
.
Context
,
cmd
*
cli
.
Command
)
{
if
cmd
.
NArg
()
>
0
{
return
}
for
name
,
profile
:=
range
config
.
Env
.
Profiles
{
if
profileAvailable
(
profile
)
{
fmt
.
Println
(
name
)
}
}
}
preset/result.go
View file @
72107231
package
preset
import
"ximperconf/ui"
import
(
"fmt"
"ximperconf/config"
"ximperconf/locale"
)
type
LogEntry
struct
{
Category
string
Name
string
Status
config
.
ItemStatus
}
type
Result
struct
{
Copies
[]
ui
.
TreeItem
Links
[]
ui
.
TreeItem
Replaced
[]
ui
.
TreeItem
System
[]
ui
.
TreeItem
HyprVars
[]
ui
.
TreeItem
HyprModules
[]
ui
.
TreeItem
SyncSystemLayouts
[]
ui
.
TreeItem
entries
[]
LogEntry
}
func
(
r
*
Result
)
Add
(
category
,
name
string
,
status
config
.
ItemStatus
)
{
r
.
entries
=
append
(
r
.
entries
,
LogEntry
{
Category
:
category
,
Name
:
name
,
Status
:
status
,
})
}
func
(
r
*
Result
)
Render
()
{
if
len
(
r
.
entries
)
==
0
{
return
}
maxCat
:=
0
for
_
,
e
:=
range
r
.
entries
{
if
len
([]
rune
(
e
.
Category
))
>
maxCat
{
maxCat
=
len
([]
rune
(
e
.
Category
))
}
}
for
_
,
e
:=
range
r
.
entries
{
symbol
:=
e
.
Status
.
Symbol
cat
:=
fmt
.
Sprintf
(
"%-*s"
,
maxCat
,
e
.
Category
)
if
e
.
Status
.
Color
!=
nil
{
symbol
=
e
.
Status
.
Color
(
symbol
)
cat
=
e
.
Status
.
Color
(
cat
)
}
fmt
.
Printf
(
" %s %s %s
\n
"
,
symbol
,
cat
,
e
.
Name
)
}
summary
:=
r
.
Summary
()
if
summary
!=
""
{
fmt
.
Println
()
fmt
.
Printf
(
" %s
\n
"
,
summary
)
}
}
func
(
r
*
Result
)
Summary
()
string
{
var
done
,
skipped
,
errors
int
for
_
,
e
:=
range
r
.
entries
{
switch
{
case
e
.
Status
.
IsEqual
(
config
.
OpStatus
.
Done
)
:
done
++
case
e
.
Status
.
IsEqual
(
config
.
OpStatus
.
Skipped
)
:
skipped
++
case
e
.
Status
.
IsEqual
(
config
.
OpStatus
.
Error
)
:
errors
++
case
e
.
Status
.
IsEqual
(
config
.
OpStatus
.
DryRun
)
:
done
++
}
}
parts
:=
[]
string
{}
if
done
>
0
{
parts
=
append
(
parts
,
fmt
.
Sprintf
(
"%d %s"
,
done
,
locale
.
T
(
"done"
)))
}
if
skipped
>
0
{
parts
=
append
(
parts
,
fmt
.
Sprintf
(
"%d %s"
,
skipped
,
locale
.
T
(
"skipped"
)))
}
if
errors
>
0
{
parts
=
append
(
parts
,
fmt
.
Sprintf
(
"%d %s"
,
errors
,
locale
.
TN
(
"error"
,
"errors"
,
errors
)))
}
if
len
(
parts
)
==
0
{
return
""
}
result
:=
parts
[
0
]
for
i
:=
1
;
i
<
len
(
parts
);
i
++
{
result
+=
", "
+
parts
[
i
]
}
return
result
}
ui/rendertreelines.go
deleted
100644 → 0
View file @
5b29e43c
package
ui
import
(
"github.com/fatih/color"
)
func
RenderTreeLines
(
title
string
,
lines
[]
string
)
{
if
len
(
lines
)
==
0
{
return
}
color
.
Blue
(
"%s"
,
title
)
items
:=
make
([]
TreeItem
,
len
(
lines
))
for
i
,
line
:=
range
lines
{
items
[
i
]
=
TreeItem
{
Name
:
line
,
}
}
RenderTree
(
RenderTreeOptions
{
Items
:
items
,
Style
:
DefaultTreeStyle
,
Color
:
false
,
})
}
func
RenderTreeItems
(
title
string
,
items
[]
TreeItem
)
{
if
len
(
items
)
==
0
{
return
}
color
.
Blue
(
"%s"
,
title
)
RenderTree
(
RenderTreeOptions
{
Items
:
items
,
Style
:
DefaultTreeStyle
,
Color
:
true
,
})
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment