179 lines
5.8 KiB
Go
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
|
|
}
|