Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,282 @@
|
||||
package xorg
|
||||
|
||||
/*
|
||||
#include "commands_native.h"
|
||||
#include <X11/XKBlib.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
)
|
||||
|
||||
// commandOwner is confined to the X11 command goroutine and its locked OS thread.
|
||||
// Its chord buffer and router are reset on completed chords and secure input;
|
||||
// the whole owner is discarded when the listener stops.
|
||||
type commandOwner struct {
|
||||
listener *CommandListener
|
||||
workerContext context.Context
|
||||
device *C.CommandDevice
|
||||
layout input.KeyboardLayout
|
||||
modifiers []string
|
||||
overrides []input.BindingOverride
|
||||
core, tentacles []input.BindingOverride
|
||||
symbols []uint32
|
||||
globalBindings input.CommandBindings
|
||||
router *input.CommandRouter
|
||||
held map[int]bool
|
||||
buffered []C.CommandKey
|
||||
bound, passThrough, learnMode bool
|
||||
ocrReview, dismissOCR bool
|
||||
ocrPending bool
|
||||
uiSession uint64
|
||||
uiWindow, uiFocus C.Window
|
||||
lastLocks, lastFocusMatch C.int
|
||||
lastFocusCheck, drainDeadline time.Time
|
||||
steamFocus steamFocusRepair
|
||||
}
|
||||
|
||||
func (listener *CommandListener) runCommandLoop(workerContext context.Context, device *C.CommandDevice, layout input.KeyboardLayout, modifiers []string, core, tentacles []input.BindingOverride, symbols []uint32) {
|
||||
owner := &commandOwner{listener: listener, workerContext: workerContext, device: device, layout: layout, modifiers: modifiers, core: core, tentacles: tentacles, overrides: input.MergeTentacleBindings(core, tentacles), symbols: symbols, held: make(map[int]bool)}
|
||||
owner.run()
|
||||
}
|
||||
|
||||
func (owner *commandOwner) run() {
|
||||
owner.publishFocus()
|
||||
owner.globalBindings = resolvedBindings(owner.layout, false, owner.overrides)
|
||||
owner.router = input.NewCommandRouter(owner.symbols, owner.globalBindings)
|
||||
owner.lastLocks = C.command_lock_state(owner.device)
|
||||
for {
|
||||
if owner.workerContext.Err() != nil {
|
||||
if !owner.bound || len(owner.held) == 0 {
|
||||
break
|
||||
}
|
||||
// Quit dispatches on press. Keep consuming its physical releases
|
||||
// during shutdown, bounded in case a device disappears.
|
||||
if owner.drainDeadline.IsZero() {
|
||||
owner.drainDeadline = time.Now().Add(time.Second)
|
||||
}
|
||||
if time.Now().After(owner.drainDeadline) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !owner.sync_internal_ui() {
|
||||
return
|
||||
}
|
||||
if !owner.applyBindingUpdate() {
|
||||
return
|
||||
}
|
||||
if time.Since(owner.lastFocusCheck) >= xtermFocusCheckInterval {
|
||||
owner.check_steam_focus(owner.publishFocus())
|
||||
}
|
||||
owner.resetForSecureInput()
|
||||
if !owner.syncGrabs() {
|
||||
return
|
||||
}
|
||||
var key C.CommandKey
|
||||
result := C.next_command_key(owner.device, &key)
|
||||
if result < 0 {
|
||||
owner.listener.failures <- fmt.Errorf("X11 command listener failed: %d", int(result))
|
||||
return
|
||||
}
|
||||
if result == 0 {
|
||||
continue
|
||||
}
|
||||
if result == 2 {
|
||||
owner.publishLocks(key)
|
||||
continue
|
||||
}
|
||||
if !owner.handleKey(key) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(owner.buffered) != 0 && !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 interrupted X11 chord: native error %d", int(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (owner *commandOwner) publishFocus() steamFocusSample {
|
||||
match, steam := read_command_focus(owner.device.display, !owner.listener.secure.Load() && owner.workerContext.Err() == nil)
|
||||
owner.lastFocusCheck = time.Now()
|
||||
if match == 0 || C.int(match) == owner.lastFocusMatch {
|
||||
return steam
|
||||
}
|
||||
owner.lastFocusMatch = C.int(match)
|
||||
state := FocusState{Known: true, XTerm: match == 1}
|
||||
owner.listener.SetCommandGrabs(!state.XTerm)
|
||||
owner.listener.publishFocusState(state)
|
||||
return steam
|
||||
}
|
||||
|
||||
// Native xterm focus vetoes delayed accessibility requests to restore grabs.
|
||||
// lastFocusMatch belongs to this owner and survives unknown focus samples;
|
||||
// only a known non-xterm window ends the handoff.
|
||||
func (owner *commandOwner) commandGrabsAllowed() bool {
|
||||
return owner.lastFocusMatch != 1 && owner.listener.commandsEnabled.Load() && !owner.listener.secure.Load()
|
||||
}
|
||||
|
||||
func (owner *commandOwner) applyBindingUpdate() bool {
|
||||
if len(owner.held) == 0 {
|
||||
select {
|
||||
case update := <-owner.listener.bindingUpdates:
|
||||
nextLayout, nextModifiers := owner.layout, owner.modifiers
|
||||
if update.replaceKeyboard {
|
||||
nextLayout, nextModifiers = update.layout, update.modifiers
|
||||
}
|
||||
nextCore, nextTentacles := owner.core, owner.tentacles
|
||||
if update.replaceTentacles {
|
||||
nextTentacles = update.tentacles
|
||||
} else if update.ocrReview == nil {
|
||||
nextCore = update.overrides
|
||||
}
|
||||
nextOverrides, nextOCR := input.MergeTentacleBindings(nextCore, nextTentacles), owner.ocrReview
|
||||
nextPending := owner.ocrPending
|
||||
if update.ocrPending != nil {
|
||||
nextPending = *update.ocrPending
|
||||
}
|
||||
if update.ocrReview != nil {
|
||||
nextOverrides, nextOCR = owner.overrides, *update.ocrReview
|
||||
}
|
||||
err := update.ctx.Err()
|
||||
if err == nil && nextOCR && (!owner.commandGrabsAllowed() || owner.learnMode) {
|
||||
err = errors.New("OCR review input unavailable")
|
||||
}
|
||||
if err == nil && nextOCR {
|
||||
foreground := C.read_foreground(owner.device.display)
|
||||
if foreground.known == 0 || foreground.window == 0 || update.ocrForeground != 0 && uint64(foreground.window) != update.ocrForeground {
|
||||
err = errors.New("OCR foreground unavailable")
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = configure_context_bindings(owner.device, nextLayout, nextModifiers, nextOverrides, owner.listener.browse.Load(), nextOCR, nextPending)
|
||||
}
|
||||
if err == nil {
|
||||
owner.layout, owner.modifiers, owner.overrides = nextLayout, nextModifiers, nextOverrides
|
||||
owner.core, owner.tentacles = nextCore, nextTentacles
|
||||
owner.listener.publishTentacleBindings(nextOverrides)
|
||||
owner.ocrReview, owner.ocrPending = nextOCR, nextPending
|
||||
owner.listener.ocrPending.Store(nextPending)
|
||||
owner.listener.ocrApplied.Store(nextOCR)
|
||||
owner.listener.browseApplied.Store(owner.device.browse != 0 && !nextOCR)
|
||||
owner.symbols, _ = command_modifier_symbols(owner.modifiers)
|
||||
owner.globalBindings = resolvedBindings(owner.layout, false, owner.overrides)
|
||||
owner.router = input.NewCommandRouter(owner.symbols, owner.context_bindings())
|
||||
}
|
||||
|
||||
update.result <- err
|
||||
if owner.device.commands == 0 && owner.listener.commandsApplied.Load() {
|
||||
owner.listener.failures <- errors.New("X11 could not restore command grabs")
|
||||
return false
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (owner *commandOwner) resetForSecureInput() {
|
||||
if owner.listener.secure.Load() && (owner.ocrReview || owner.device.commands != 0 || owner.device.active != 0 || len(owner.held) != 0) {
|
||||
C.release_command_keyboard(owner.device)
|
||||
C.set_command_keys(owner.device, 0)
|
||||
owner.listener.commandsApplied.Store(false)
|
||||
owner.listener.grabActive.Store(false)
|
||||
owner.learnMode = false
|
||||
owner.listener.learnMode.Store(false)
|
||||
owner.listener.learnEcho.Store(false)
|
||||
owner.dismiss_internal_ui()
|
||||
owner.listener.uiEcho.Store(false)
|
||||
clear(owner.held)
|
||||
owner.buffered = nil
|
||||
owner.bound, owner.passThrough = false, false
|
||||
owner.ocrReview, owner.ocrPending = false, false
|
||||
owner.listener.ocrPending.Store(false)
|
||||
owner.listener.ocrApplied.Store(false)
|
||||
if err := configure_context_bindings(owner.device, owner.layout, owner.modifiers, owner.overrides, false, false); err != nil {
|
||||
owner.listener.failures <- err
|
||||
}
|
||||
owner.router = input.NewCommandRouter(owner.symbols, resolvedBindings(owner.layout, false, owner.overrides))
|
||||
}
|
||||
}
|
||||
|
||||
func (owner *commandOwner) syncGrabs() bool {
|
||||
if owner.ocrReview && len(owner.held) == 0 {
|
||||
foreground := C.read_foreground(owner.device.display)
|
||||
if foreground.known == 0 || foreground.window == 0 || foreground.window != owner.device.browseWindow {
|
||||
if !owner.leave_ocr_review() {
|
||||
return false
|
||||
}
|
||||
if !owner.listener.enqueueSecureCommand(input.CommandEvent{Command: input.OCRDismiss}) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
enabled := owner.commandGrabsAllowed()
|
||||
if owner.ocrReview && !enabled && len(owner.held) == 0 {
|
||||
if !owner.leave_ocr_review() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(owner.held) == 0 && !enabled && owner.learnMode {
|
||||
// Learn mode keeps an explicit keyboard grab between chords. Removing
|
||||
// passive command grabs alone cannot give that keyboard back to Fenrir.
|
||||
C.release_command_keyboard(owner.device)
|
||||
owner.learnMode = false
|
||||
owner.router.Reset()
|
||||
owner.listener.grabActive.Store(false)
|
||||
owner.listener.learnMode.Store(false)
|
||||
owner.listener.learnEcho.Store(false)
|
||||
}
|
||||
if len(owner.held) == 0 && (owner.device.commands != 0) != enabled {
|
||||
value := C.int(0)
|
||||
if enabled {
|
||||
value = 1
|
||||
}
|
||||
if err := C.set_command_keys(owner.device, value); err != 0 {
|
||||
owner.listener.failures <- fmt.Errorf("change X11 command grabs: native error %d", int(err))
|
||||
return false
|
||||
}
|
||||
owner.listener.commandsApplied.Store(enabled)
|
||||
}
|
||||
if !owner.ocrReview && len(owner.held) == 0 && (owner.device.commands != 0) &&
|
||||
(owner.device.browse != 0) != owner.listener.browse.Load() {
|
||||
enabled := owner.listener.browse.Load()
|
||||
value := C.int(0)
|
||||
if enabled {
|
||||
value = 1
|
||||
}
|
||||
if err := C.set_browse_keys(owner.device, value); err != 0 {
|
||||
owner.listener.failures <- fmt.Errorf("change X11 browse grabs: native error %d", int(err))
|
||||
return false
|
||||
}
|
||||
bindings := resolvedBindings(owner.layout, false, owner.overrides)
|
||||
if enabled {
|
||||
bindings = resolvedBindings(owner.layout, true, owner.overrides)
|
||||
}
|
||||
owner.router.ReplaceBindings(bindings)
|
||||
owner.listener.browseApplied.Store(enabled)
|
||||
}
|
||||
owner.device.nativeButtonFocus = 0
|
||||
if owner.listener.nativeButtonFocus.Load() && !owner.ocrReview {
|
||||
owner.device.nativeButtonFocus = 1
|
||||
}
|
||||
owner.listener.nativeButtonApplied.Store(owner.device.nativeButtonFocus != 0)
|
||||
return true
|
||||
}
|
||||
|
||||
func (owner *commandOwner) publishLocks(key C.CommandKey) {
|
||||
for index, name := range []string{"Caps Lock", "Num Lock", "Scroll Lock"} {
|
||||
mask := C.int(1 << index)
|
||||
if (key.locks^owner.lastLocks)&mask != 0 {
|
||||
owner.listener.publish_lock_state(input.LockState{Name: name, Locked: key.locks&mask != 0, Timestamp: uint32(key.timestamp)})
|
||||
}
|
||||
}
|
||||
owner.lastLocks = key.locks
|
||||
}
|
||||
Reference in new issue
Block a user