add hyprland sync-xkb-layout subcommand

parent 96127f2d
package config
import (
"log"
"github.com/godbus/dbus/v5"
)
var DBusConn *dbus.Conn
func InitDBus() {
var err error
DBusConn, err = dbus.SystemBus()
if err != nil {
log.Fatalf("failed to connect to system bus: %v", err)
}
}
...@@ -18,6 +18,7 @@ function __fish_ximperconf_complete ...@@ -18,6 +18,7 @@ function __fish_ximperconf_complete
end end
end end
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from set' -f -a '(__fish_ximperconf_complete)'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from get' -f -a '(__fish_ximperconf_complete)' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from get' -f -a '(__fish_ximperconf_complete)'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from unset' -f -a '(__fish_ximperconf_complete)' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from unset' -f -a '(__fish_ximperconf_complete)'
...@@ -39,9 +40,12 @@ complete -x -c ximperconf -n '__fish_seen_subcommand_from repo; and __fish_seen_ ...@@ -39,9 +40,12 @@ complete -x -c ximperconf -n '__fish_seen_subcommand_from repo; and __fish_seen_
complete -c ximperconf -n '__fish_seen_subcommand_from repo; and __fish_seen_subcommand_from deferred; and __fish_seen_subcommand_from last-update' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from repo; and __fish_seen_subcommand_from deferred; and __fish_seen_subcommand_from last-update' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_ximperconf_no_subcommand' -a 'hyprland' -d 'Hyprland Management' complete -x -c ximperconf -n '__fish_ximperconf_no_subcommand' -a 'hyprland' -d 'Hyprland Management'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check module var' -a 'check' -d 'Check the Hyprland config' complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check sync-xkb-layouts module var' -a 'check' -d 'Check the Hyprland config'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from check' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from check' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check module var' -a 'module' -d 'Hyprland modules' complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check sync-xkb-layouts module var' -a 'sync-xkb-layouts' -d 'Sync layouts with xkb'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from sync-xkb-layouts' -f -l force -s f -d 'Forced update'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from sync-xkb-layouts' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check sync-xkb-layouts module var' -a 'module' -d 'Hyprland modules'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module' -f -l user -s u -d 'use user modules' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module' -f -l user -s u -d 'use user modules'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and not __fish_seen_subcommand_from status info enable disable toggle' -a 'status' -d 'Hyprland module status' complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and not __fish_seen_subcommand_from status info enable disable toggle' -a 'status' -d 'Hyprland module status'
...@@ -55,7 +59,7 @@ complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_s ...@@ -55,7 +59,7 @@ complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_s
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and __fish_seen_subcommand_from disable' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and __fish_seen_subcommand_from disable' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and not __fish_seen_subcommand_from status info enable disable toggle' -a 'toggle' -d 'toggle module' complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and not __fish_seen_subcommand_from status info enable disable toggle' -a 'toggle' -d 'toggle module'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and __fish_seen_subcommand_from toggle' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from module; and __fish_seen_subcommand_from toggle' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check module var' -a 'var' -d 'Hyprland vars' complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and not __fish_seen_subcommand_from check sync-xkb-layouts module var' -a 'var' -d 'Hyprland vars'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var' -f -l help -s h -d 'show help'
complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and not __fish_seen_subcommand_from list info get set unset' -a 'list' -d 'vars list' complete -x -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and not __fish_seen_subcommand_from list info get set unset' -a 'list' -d 'vars list'
complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from list' -f -l help -s h -d 'show help' complete -c ximperconf -n '__fish_seen_subcommand_from hyprland; and __fish_seen_subcommand_from var; and __fish_seen_subcommand_from list' -f -l help -s h -d 'show help'
......
...@@ -4,6 +4,7 @@ go 1.25.0 ...@@ -4,6 +4,7 @@ go 1.25.0
require ( require (
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/godbus/dbus/v5 v5.1.0
github.com/urfave/cli/v3 v3.4.1 github.com/urfave/cli/v3 v3.4.1
) )
......
...@@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c ...@@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
......
...@@ -20,6 +20,19 @@ func CommandList() *cli.Command { ...@@ -20,6 +20,19 @@ func CommandList() *cli.Command {
Action: CheckHyprland, Action: CheckHyprland,
}, },
{ {
Name: "sync-xkb-layouts",
Usage: "Sync layouts with xkb",
Action: HyprlandSyncSystemLayouts,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "force",
Usage: "Forced update",
Aliases: []string{"f"},
Value: false,
},
},
},
{
Name: "module", Name: "module",
Usage: "Hyprland modules", Usage: "Hyprland modules",
Flags: []cli.Flag{ Flags: []cli.Flag{
......
package hyprland
import (
"context"
"fmt"
"ximperconf/config"
"github.com/fatih/color"
"github.com/godbus/dbus/v5"
"github.com/urfave/cli/v3"
)
func hyprlandGetKeyboardLayouts() (string, error) {
obj := config.DBusConn.Object("org.freedesktop.locale1", "/org/freedesktop/locale1")
variant := dbus.Variant{}
err := obj.Call("org.freedesktop.DBus.Properties.Get", 0,
"org.freedesktop.locale1", "X11Layout").Store(&variant)
if err != nil {
return "", err
}
layoutStr, ok := variant.Value().(string)
if !ok {
return "", fmt.Errorf("unexpected type: %T", variant.Value())
}
return layoutStr, nil
}
func HyprlandSyncSystemLayouts(ctx context.Context, cmd *cli.Command) error {
force := cmd.Bool("force")
sysLayouts, err := hyprlandGetKeyboardLayouts()
if err != nil {
return fmt.Errorf("не удалось получить системные раскладки: %w", err)
}
hyprLayouts, err := hyprlandVarGet("kb_layout")
if err != nil {
return fmt.Errorf("не удалось получить kb_layout из Hyprland: %w", err)
}
if force || hyprLayouts == "" {
if _, err := hyprlandVarSet("kb_layout", sysLayouts); err != nil {
return fmt.Errorf("не удалось обновить kb_layout в Hyprland: %w", err)
}
color.Green("Раскладка обновлена!")
} else {
color.Red("Раскладка уже установлена, используйте '--force' для принудительного обновления.")
}
return nil
}
...@@ -119,19 +119,19 @@ func HyprlandVarInfoCommand(ctx context.Context, cmd *cli.Command) error { ...@@ -119,19 +119,19 @@ func HyprlandVarInfoCommand(ctx context.Context, cmd *cli.Command) error {
func hyprlandVarGet(name string) (string, error) { func hyprlandVarGet(name string) (string, error) {
if name == "" { if name == "" {
color.Red("Укажите имя переменной") return "", fmt.Errorf("укажите имя переменной")
os.Exit(1)
} }
if !regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`).MatchString(name) { if !regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`).MatchString(name) {
color.Red("Недопустимое имя переменной: %s", name) return "", fmt.Errorf("недопустимое имя переменной: %s", name)
os.Exit(1)
} }
if !fileExists(config.Env.Hyprland.Config) { if !fileExists(config.Env.Hyprland.Config) {
color.Red("Конфигурация не найдена: %s", config.Env.Hyprland.Config) return "", fmt.Errorf("конфигурация не найдена: %s", config.Env.Hyprland.Config)
os.Exit(1)
} }
f, _ := os.Open(config.Env.Hyprland.Config) f, err := os.Open(config.Env.Hyprland.Config)
if err != nil {
return "", err
}
defer f.Close() defer f.Close()
sc := bufio.NewScanner(f) sc := bufio.NewScanner(f)
...@@ -145,30 +145,30 @@ func hyprlandVarGet(name string) (string, error) { ...@@ -145,30 +145,30 @@ func hyprlandVarGet(name string) (string, error) {
return value, nil return value, nil
} }
} }
color.Red("Переменная %s не найдена", name)
os.Exit(1) return "", fmt.Errorf("переменная %s не найдена", name)
return "", nil
} }
func HyprlandVarGetCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandVarGetCommand(ctx context.Context, cmd *cli.Command) error {
info, _ := hyprlandVarGet(cmd.Args().Get(0)) info, err := hyprlandVarGet(cmd.Args().Get(0))
if err != nil {
color.Red(err.Error())
return err
}
fmt.Println(info) fmt.Println(info)
return nil return nil
} }
func hyprlandVarSet(name, newValue string) error { func hyprlandVarSet(name, newValue string) (string, error) {
if name == "" { if name == "" {
color.Red("Укажите имя переменной") return "", fmt.Errorf("укажите имя переменной")
os.Exit(1)
} }
if newValue == "" { if newValue == "" {
color.Red("Укажите новое значение") return "", fmt.Errorf("укажите новое значение")
os.Exit(1)
} }
if !regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`).MatchString(name) { if !regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`).MatchString(name) {
color.Red("Недопустимое имя переменной: %s", name) return "", fmt.Errorf("недопустимое имя переменной: %s", name)
os.Exit(1)
} }
path := config.Env.Hyprland.Config path := config.Env.Hyprland.Config
...@@ -177,15 +177,21 @@ func hyprlandVarSet(name, newValue string) error { ...@@ -177,15 +177,21 @@ func hyprlandVarSet(name, newValue string) error {
_ = os.WriteFile(path, []byte{}, 0644) _ = os.WriteFile(path, []byte{}, 0644)
} }
data, _ := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil {
return "", err
}
lines := strings.Split(string(data), "\n") lines := strings.Split(string(data), "\n")
changed := false changed := false
msg := ""
re := regexp.MustCompile(`^\s*\$` + regexp.QuoteMeta(name) + `\s*=`) re := regexp.MustCompile(`^\s*\$` + regexp.QuoteMeta(name) + `\s*=`)
for i, l := range lines { for i, l := range lines {
if re.MatchString(l) { if re.MatchString(l) {
lines[i] = fmt.Sprintf("$%s = %s", name, newValue) lines[i] = fmt.Sprintf("$%s = %s", name, newValue)
changed = true changed = true
msg = fmt.Sprintf("Переменная %s обновлена: %s", name, newValue)
} }
} }
...@@ -202,7 +208,7 @@ func hyprlandVarSet(name, newValue string) error { ...@@ -202,7 +208,7 @@ func hyprlandVarSet(name, newValue string) error {
before := append([]string{}, lines[:insertAt]...) before := append([]string{}, lines[:insertAt]...)
after := append([]string{fmt.Sprintf("$%s = %s", name, newValue)}, lines[insertAt:]...) after := append([]string{fmt.Sprintf("$%s = %s", name, newValue)}, lines[insertAt:]...)
lines = append(before, after...) lines = append(before, after...)
color.Green("Переменная %s добавлена: %s", name, newValue) msg = fmt.Sprintf("Переменная %s добавлена: %s", name, newValue)
} else { } else {
// создаём блок VARS // создаём блок VARS
lines = append(lines, lines = append(lines,
...@@ -210,18 +216,27 @@ func hyprlandVarSet(name, newValue string) error { ...@@ -210,18 +216,27 @@ func hyprlandVarSet(name, newValue string) error {
"#---------- ПЕРЕМЕННЫЕ ---- VARS", "#---------- ПЕРЕМЕННЫЕ ---- VARS",
fmt.Sprintf("$%s = %s", name, newValue), fmt.Sprintf("$%s = %s", name, newValue),
) )
color.Green("Блок VARS создан, переменная %s добавлена: %s", name, newValue) msg = fmt.Sprintf("Блок VARS создан, переменная %s добавлена: %s", name, newValue)
} }
} else {
color.Green("Переменная %s обновлена: %s", name, newValue)
} }
return os.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644) err = os.WriteFile(path, []byte(strings.Join(lines, "\n")), 0644)
if err != nil {
return "", err
}
return msg, nil
} }
func HyprlandVarSetCommand(ctx context.Context, cmd *cli.Command) error { func HyprlandVarSetCommand(ctx context.Context, cmd *cli.Command) error {
hyprlandVarSet(cmd.Args().Get(0), cmd.Args().Get(1)) msg, err := hyprlandVarSet(cmd.Args().Get(0), cmd.Args().Get(1))
if err != nil {
color.Red(err.Error())
return err
}
color.Green(msg)
return nil return nil
} }
......
...@@ -13,6 +13,8 @@ import ( ...@@ -13,6 +13,8 @@ import (
func main() { func main() {
config.InitConfig() config.InitConfig()
config.InitDBus()
defer config.DBusConn.Close()
rootCommand := &cli.Command{ rootCommand := &cli.Command{
Name: "ximperconf", Name: "ximperconf",
......
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