// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package preferences import ( "fmt" "path/filepath" "slices" "git.stormux.org/storm/fenrir/internal/config" "git.stormux.org/storm/fenrir/internal/input" "git.stormux.org/storm/fenrir/internal/interaction" ) type modifierOption struct{ name, Label string } var modifierOptions = []modifierOption{ {name: "KEY_KP0", Label: "Keypad Insert"}, {name: "KEY_INSERT", Label: "Insert"}, {name: "KEY_CAPSLOCK", Label: "Caps Lock"}, {name: "KEY_META", Label: "Super"}, } func reader_names(settings config.Settings) []string { if settings.Keyboard.FenrirKeys != nil { return settings.Keyboard.FenrirKeys } // Running applications supply the resolved layout's keys at startup. // Standalone forms use the built-in layout defaults. var names []string switch filepath.Base(settings.Keyboard.Layout) { case "laptop", "laptop.toml": names = []string{"KEY_KP0", "KEY_INSERT", "KEY_CAPSLOCK"} default: names = []string{"KEY_KP0", "KEY_INSERT", "KEY_META"} } available, err := input.Available_fenrir_keys(names, settings.General.ScriptKeys) if err != nil { return []string{} } return available } func modifier_checked(names []string, name string) bool { keys, _ := input.Modifier_codes(names) option, _ := input.Modifier_codes([]string{name}) for key := range option { if keys[key] { return true } } return false } func modifier_controls(settings config.Settings) []interaction.Control { var controls []interaction.Control for _, role := range []struct { id, Label string names []string }{ {id: "fenrir_key.", Label: "Fenrir keys", names: reader_names(settings)}, {id: "script_key.", Label: "Script keys", names: settings.General.ScriptKeys}, } { options := slices.Clone(modifierOptions) if role.id == "script_key." { options = append(options, modifierOption{name: "KEY_COMPOSE", Label: "Compose"}) } for _, option := range options { controls = append(controls, interaction.Control{ID: role.id + option.name, Label: option.Label, Group: role.Label, Kind: interaction.CheckBox, Checked: modifier_checked(role.names, option.name)}) } } return controls } func selected_modifiers(values map[string]interaction.Control, prefix string, names []string, options []modifierOption) ([]string, bool, error) { selected := slices.Clone(names) changed := false for _, option := range options { control, ok := values["keyboard."+prefix+option.name] if !ok { return nil, false, fmt.Errorf("missing modifier checkbox %s", option.name) } if control.Checked == modifier_checked(names, option.name) { continue } changed = true if control.Checked { selected = append(selected, option.name) continue } selected = slices.DeleteFunc(selected, func(name string) bool { return modifier_checked([]string{name}, option.name) }) } return selected, changed, nil } func modifier_settings(values map[string]interaction.Control, base *config.Settings) error { reader, changed, err := selected_modifiers(values, "fenrir_key.", reader_names(*base), modifierOptions) if err != nil { return err } scripts, _, err := selected_modifiers(values, "script_key.", base.General.ScriptKeys, append(slices.Clone(modifierOptions), modifierOption{name: "KEY_COMPOSE", Label: "Compose"})) if err != nil { return err } if err := input.Validate_modifier_keys(reader, scripts); err != nil { return err } if changed { base.Keyboard.FenrirKeys = reader } base.General.ScriptKeys = scripts return nil }