package xorg /* #include "commands_native.h" #include */ 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 && owner.remote == nil && 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.remote != nil { owner.handle_remote_key(key) } else 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.device.bypass == 0 && !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 && owner.remote == nil { 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.remote != nil) owner.listener.uiEcho.Store(owner.uiSession != 0) owner.listener.learnMode.Store(owner.learnMode) owner.listener.learnEcho.Store(owner.learnMode) return true }