package input import ( "strconv" "strings" ) type KeyKind uint8 const ( KeyUnknown KeyKind = iota KeyModifier KeyFunction KeyAction KeyNavigation KeyDiacritical ) type Event struct { Timestamp uint32 // X server milliseconds, for ordering independent listeners. Keycode uint8 Pressed bool Repeat bool // X11 autorepeat, distinct from a new physical key press. Name string Text string Kind KeyKind } type EchoSettings struct { EnableKeyEcho bool EnableModifierKeys bool EnableFunctionKeys bool EnableActionKeys bool EnableNavigationKeys bool EnableDiacriticalKeys bool CthulhuModifierKeys []string } func NewKeyEvent(keycode uint8, pressed bool, name string) Event { kind, text := classifyKey(name) return Event{Keycode: keycode, Pressed: pressed, Name: name, Text: text, Kind: kind} } func EchoText(event Event, settings EchoSettings) string { if !event.Pressed || !settings.EnableKeyEcho { return "" } var enabled bool switch event.Kind { case KeyModifier: enabled = settings.EnableModifierKeys case KeyFunction: enabled = settings.EnableFunctionKeys case KeyAction: enabled = settings.EnableActionKeys case KeyNavigation: enabled = settings.EnableNavigationKeys case KeyDiacritical: enabled = settings.EnableDiacriticalKeys } if !enabled { return "" } return event.Text } func classifyKey(name string) (KeyKind, string) { if text, exists := map[string]string{ "Shift_L": "left shift", "Shift_R": "right shift", "Control_L": "left control", "Control_R": "right control", "Alt_L": "left alt", "Alt_R": "right alt", "Meta_L": "left meta", "Meta_R": "right meta", "ISO_Level3_Shift": "Alt Gr", }[name]; exists { return KeyModifier, text } if strings.HasPrefix(name, "F") { number, err := strconv.Atoi(strings.TrimPrefix(name, "F")) if err == nil && number >= 1 && number <= 12 { return KeyFunction, strings.ToLower(name) } } if text, exists := map[string]string{ "Return": "return", "KP_Enter": "enter", "Escape": "escape", "Tab": "tab", "ISO_Left_Tab": "left tab", "BackSpace": "backspace", "Delete": "delete", "KP_Delete": "delete", "Page_Up": "page up", "KP_Page_Up": "page up", "Prior": "page up", "KP_Prior": "page up", "Page_Down": "page down", "KP_Page_Down": "page down", "Next": "page down", "KP_Next": "page down", }[name]; exists { return KeyAction, text } if text, exists := map[string]string{ "Left": "left", "KP_Left": "left", "Right": "right", "KP_Right": "right", "Up": "up", "KP_Up": "up", "Down": "down", "KP_Down": "down", "Home": "home", "KP_Home": "home", "End": "end", "KP_End": "end", }[name]; exists { return KeyNavigation, text } if strings.HasPrefix(name, "dead_") { text := strings.ReplaceAll(strings.TrimPrefix(name, "dead_"), "_", " ") if text == "abovering" { text = "ring" } return KeyDiacritical, text } return KeyUnknown, "" } type ModifierKeycodes [256]bool func NewModifierKeycodes(keycodes ...uint8) ModifierKeycodes { var modifiers ModifierKeycodes for _, keycode := range keycodes { modifiers[keycode] = true } return modifiers } func (modifiers ModifierKeycodes) Contains(keycode uint8) bool { return modifiers[keycode] } func ShouldInterruptSpeech(event Event) bool { return event.Pressed } // Automatic titles survive held-key repeats, but fresh input and explicit // stop keys cancel both scheduled and already queued title speech. func ShouldInterruptTitle(event Event) bool { return event.Pressed && (!event.Repeat || event.Name == "Control_L" || event.Name == "Control_R" || event.Name == "Escape") }