After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.

This commit is contained in:
Storm Dragon committed 2026-10-27 20:30:55 -04:00
1 parent c018f5fd29
commit 0e50de4c76
1047 files changed
+58364 -63747

No files matched your search

+136
View File
@@ -0,0 +1,136 @@
// Fenrir console screen reader.
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
package input
import (
"bytes"
"fmt"
"strings"
"git.stormux.org/storm/fenrir/internal/command"
"github.com/pelletier/go-toml/v2"
)
type Binding struct {
Name string `toml:"name"`
Keys []string `toml:"keys"`
Command command.ID `toml:"command"`
DoubleCommand command.ID `toml:"double_command,omitempty"`
TripleCommand command.ID `toml:"triple_command,omitempty"`
}
var keyCodes = map[string]uint16{
"F": KeyF, "Q": KeyQ, "H": KeyH, "F4": KeyF4, "BACKSPACE": KeyBackspace,
"KP7": KeyKP7, "KP8": KeyKP8, "KP9": KeyKP9, "KP4": KeyKP4,
"KP5": KeyKP5, "KP6": KeyKP6, "KP1": KeyKP1, "KP2": KeyKP2,
"KP3": KeyKP3, "KPDOT": KeyKPDot, "KPENTER": KeyKPEnter,
"KPASTERISK": 55, "KPPLUS": KeyKPPlus,
"A": 30, "B": 48, "C": 46, "D": 32, "E": 18, "G": 34,
"I": 23, "J": 36, "K": 37, "L": 38, "M": 50, "N": 49,
"O": 24, "P": 25, "R": 19, "S": 31, "T": 20, "U": 22,
"V": 47, "W": 17, "X": 45, "Y": 21, "Z": 44,
"0": 11, "1": 2, "2": 3, "3": 4, "4": 5, "5": 6,
"6": 7, "7": 8, "8": 9, "9": 10,
"COMMA": 51, "DOT": 52, "SLASH": 53,
"UP": KeyUp, "DOWN": KeyDown, "LEFT": KeyLeft, "RIGHT": KeyRight,
"HOME": KeyHome, "END": KeyEnd, "DELETE": KeyDelete,
"TAB": KeyTab, "ENTER": KeyEnter, "SPACE": KeySpace, "ESC": KeyEsc,
"PAGEUP": 104, "PAGEDOWN": 109, "F5": 63, "F6": 64, "F7": 65, "F8": 66,
"F1": 59, "KPMINUS": 74, "KPSLASH": 98, "RIGHTBRACE": 27,
}
type modifiers uint8
const (
fenrirModifier modifiers = 1 << iota
ctrlModifier
shiftModifier
altModifier
)
type chord struct {
key uint16
modifiers modifiers
}
var modifierNames = map[string]modifiers{"FENRIR": fenrirModifier, "CTRL": ctrlModifier, "SHIFT": shiftModifier, "ALT": altModifier}
// Decode_bindings rejects unsupported keys, commands and duplicate chords.
func Decode_bindings(data []byte) ([]Binding, error) {
var file struct {
Version int `toml:"version"`
Bindings []Binding `toml:"bindings"`
}
if err := toml.NewDecoder(bytes.NewReader(data)).DisallowUnknownFields().Decode(&file); err != nil {
return nil, err
}
if file.Version != 1 {
return nil, fmt.Errorf("unsupported keyboard version %d", file.Version)
}
_, err := New_router(file.Bindings)
return file.Bindings, err
}
// New_router compiles a private layout. Reset and capture preserve these bindings.
func New_router(bindings []Binding) (Router, error) {
router := Router{bindings: make(map[chord]command.ID), doubleBindings: make(map[chord]command.ID), tripleBindings: make(map[chord]command.ID), multiPressTimeout: DefaultMultiPressTimeout}
if len(bindings) == 0 {
return Router{}, fmt.Errorf("keyboard layout has no bindings")
}
for _, binding := range bindings {
if binding.TripleCommand != "" && (!command.Known(binding.TripleCommand) || binding.DoubleCommand == "") {
return Router{}, fmt.Errorf("invalid triple command in binding %q (requires a double command)", binding.Name)
}
if binding.DoubleCommand != "" && !command.Known(binding.DoubleCommand) {
return Router{}, fmt.Errorf("invalid double command in binding %q", binding.Name)
}
if strings.TrimSpace(binding.Name) == "" || len(binding.Keys) == 0 || !command.Known(binding.Command) {
return Router{}, fmt.Errorf("invalid keyboard binding %q", binding.Name)
}
key, valid := keyCodes[binding.Keys[len(binding.Keys)-1]]
if !valid {
return Router{}, fmt.Errorf("unsupported key in binding %q", binding.Name)
}
var mods modifiers
for _, name := range binding.Keys[:len(binding.Keys)-1] {
modifier, valid := modifierNames[name]
if !valid || mods&modifier != 0 {
return Router{}, fmt.Errorf("invalid modifiers in binding %q", binding.Name)
}
mods |= modifier
}
// Both adapters can intercept Fenrir chords and bare review keypad keys.
// Other bare keys would require additional X11 passive grabs.
bareKeypad := false
switch key {
case KeyKP1, KeyKP2, KeyKP3, KeyKP4, KeyKP5, KeyKP6, KeyKP7, KeyKP8, KeyKP9, KeyKPDot, KeyKPEnter, KeyKPPlus, 74, 98:
bareKeypad = true
}
if mods&fenrirModifier == 0 && (mods != 0 || !bareKeypad) {
return Router{}, fmt.Errorf("binding %q requires FENRIR outside bare review keypad keys", binding.Name)
}
combo := chord{key: key, modifiers: mods}
if _, exists := router.bindings[combo]; exists {
return Router{}, fmt.Errorf("duplicate keyboard chord in binding %q", binding.Name)
}
router.bindings[combo] = binding.Command
router.doubleBindings[combo] = binding.DoubleCommand
router.tripleBindings[combo] = binding.TripleCommand
}
return router, nil
}