Files

179 lines
5.8 KiB
Go

package xorg
/*
#include "commands_native.h"
#include <X11/XKBlib.h>
*/
import "C"
import (
"errors"
"fmt"
"git.stormux.org/storm/cthulhu/internal/input"
)
// Wait for the complete chord: Shift+CapsLock+V remains a version command.
func is_caps_lock_toggle(keys []C.CommandKey) bool {
caps, shift := false, false
for _, key := range keys {
if key.modifiers & ^C.int(1) != 0 {
return false
}
shift = shift || key.modifiers&1 != 0
switch key.symbol {
case 0xffe5:
caps = true
case 0xffe1, 0xffe2:
shift = true
default:
return false
}
}
return caps && shift
}
func (owner *commandOwner) handleKey(key C.CommandKey) bool {
owner.listener.grabActive.Store(true)
// Focus policy can change while the native owner waits for an X
// event. Replay a stale browse-only press against the requested
// policy, including custom symbols outside the default key list.
if !owner.learnMode && owner.uiSession == 0 && len(owner.held) == 0 && key.pressed != 0 && !owner.listener.browse.Load() && !owner.ocrReview &&
!owner.router.IsModifier(uint32(key.symbol)) && owner.globalBindings[input.CommandChord{Symbol: uint32(key.symbol), Modifiers: uint16(key.modifiers)}] == "" {
owner.passThrough = true
}
if key.pressed != 0 {
owner.held[int(key.code)] = true
} else {
delete(owner.held, int(key.code))
}
owner.buffered = append(owner.buffered, key)
if owner.uiSession != 0 {
if !owner.handle_internal_key(key) {
return false
}
} else if owner.learnMode {
if !owner.handleLearnKey(key) {
return false
}
} else if !owner.passThrough && !owner.handleRoutedKey(key) {
return false
}
if len(owner.held) == 0 && !owner.finishChord() {
return false
}
return true
}
func (owner *commandOwner) handleLearnKey(key C.CommandKey) bool {
owner.bound = true
if key.pressed == 0 {
owner.router.Handle(uint32(key.symbol), uint16(key.code), uint16(key.modifiers), false)
return true
}
chord := owner.router.Chord(uint32(key.symbol), uint16(key.modifiers))
_, command := owner.router.Handle(uint32(key.symbol), uint16(key.code), uint16(key.modifiers), true)
action := input.LearnKey
switch {
case uint32(key.symbol) == 0xff1b || command == input.ToggleLearnMode:
action = input.ToggleLearnMode
case chord.Symbol == 0xffbe && chord.Modifiers == 0 && !chord.CthulhuModifier:
action = input.LearnDocumentation
case chord.Symbol == 0xffbf && chord.Modifiers == 0 && !chord.CthulhuModifier:
action = input.LearnDefaultShortcuts
case chord.Symbol == 0xffc0 && chord.Modifiers == 0 && !chord.CthulhuModifier:
action = input.LearnApplicationShortcuts
}
if action != input.LearnKey {
owner.learnMode = false
owner.listener.learnMode.Store(false)
}
name := ""
if nativeName := C.XKeysymToString(C.KeySym(key.symbol)); nativeName != nil {
name = C.GoString(nativeName)
}
if owner.workerContext.Err() == nil && !owner.listener.enqueueSecureCommand(input.CommandEvent{Command: action, Timestamp: uint32(key.timestamp), Chord: chord, KeyName: name}) {
owner.listener.failures <- errors.New("X11 learn mode queue overflow")
return false
}
return true
}
func (owner *commandOwner) handleRoutedKey(key C.CommandKey) bool {
consumed, command := owner.router.Handle(uint32(key.symbol), uint16(key.code), uint16(key.modifiers), key.pressed != 0)
if key.pressed != 0 && !owner.router.IsModifier(uint32(key.symbol)) {
if consumed {
owner.bound = true
} else if !owner.bound {
owner.passThrough = true
}
}
if command != "" {
if owner.ocrReview && (command == input.OCRDismiss || command == input.OCRDismissAndPass) {
owner.dismissOCR = true
if command == input.OCRDismissAndPass {
owner.bound = false
owner.passThrough = true
}
}
if command == input.ToggleLearnMode {
if owner.ocrReview {
owner.dismissOCR = true
}
owner.learnMode = true
owner.listener.learnMode.Store(true)
owner.listener.learnEcho.Store(true)
}
uiSession := uint64(0)
if command == input.ShowActions {
if owner.ocrReview && !owner.leave_ocr_review() {
return false
}
var err error
uiSession, err = owner.begin_internal_ui()
if err != nil {
// An unavailable modal grab is a failed command, not a failed reader.
uiSession = 0
}
}
if owner.workerContext.Err() == nil && !owner.listener.enqueueSecureCommand(input.CommandEvent{UISession: uiSession, Command: command, Timestamp: uint32(key.timestamp), Chord: owner.router.Chord(uint32(key.symbol), uint16(key.modifiers)), LearnModeStarted: command == input.ToggleLearnMode}) {
owner.listener.failures <- errors.New("X11 command queue overflow")
return false
}
}
return true
}
func (owner *commandOwner) finishChord() bool {
if !owner.bound && owner.device.capsCode != 0 && is_caps_lock_toggle(owner.buffered) {
C.toggle_command_caps_lock(owner.device)
if result := C.replay_command_keys(owner.device, &owner.buffered[0], C.int(len(owner.buffered))); result != 0 {
owner.listener.failures <- fmt.Errorf("release Caps Lock toggle chord: native error %d", int(result))
return false
}
} else if owner.passThrough || !owner.bound {
result := C.replay_command_keys(owner.device, &owner.buffered[0], C.int(len(owner.buffered)))
if result != 0 {
owner.listener.failures <- fmt.Errorf("replay unbound X11 chord: native error %d", int(result))
return false
}
} else if !owner.learnMode && owner.uiSession == 0 {
C.release_command_keyboard(owner.device)
}
owner.router.Reset()
owner.buffered = owner.buffered[:0]
owner.bound = false
owner.passThrough = false
if owner.dismissOCR {
owner.dismissOCR = false
if !owner.leave_ocr_review() {
return false
}
}
owner.listener.grabActive.Store(owner.learnMode || owner.uiSession != 0)
owner.listener.uiEcho.Store(owner.uiSession != 0)
owner.listener.learnMode.Store(owner.learnMode)
owner.listener.learnEcho.Store(owner.learnMode)
return true
}