package xorg import ( "context" "sync" "sync/atomic" "git.stormux.org/storm/cthulhu/internal/input" ) type CommandListener struct { secureMutex sync.Mutex secure atomic.Bool ocrApplied atomic.Bool ocrPending atomic.Bool browse atomic.Bool browseApplied atomic.Bool bypass atomic.Bool bypassApplied atomic.Bool nativeButtonFocus atomic.Bool nativeButtonApplied atomic.Bool grabActive atomic.Bool commandsEnabled atomic.Bool commandsApplied atomic.Bool learnMode atomic.Bool learnEcho atomic.Bool uiSession atomic.Uint64 uiSequence atomic.Uint64 uiClose atomic.Uint64 uiEcho atomic.Bool uiUpdates chan uiUpdate remoteUpdates chan remoteCaptureUpdate remoteSession atomic.Uint64 remoteSequence atomic.Uint64 focusChanges chan FocusState modifiers []string layout input.KeyboardLayout bindingOverrides []input.BindingOverride tentacleBindings []input.BindingOverride registrationMutex sync.RWMutex appliedTentacles []input.BindingOverride bindingUpdates chan bindingUpdate // Bounded FIFO; overflow fails the listener rather than blocking key releases. commands chan input.CommandEvent lockStates chan input.LockState failures chan error done chan struct{} cancel context.CancelFunc mutex sync.Mutex } type FocusState struct { Known bool XTerm bool } func (listener *CommandListener) publishFocusState(state FocusState) { select { case listener.focusChanges <- state: default: select { case <-listener.focusChanges: default: } listener.focusChanges <- state } } func NewCommands(modifiers []string, layout input.KeyboardLayout) *CommandListener { return &CommandListener{ modifiers: append([]string(nil), modifiers...), bindingUpdates: make(chan bindingUpdate), uiUpdates: make(chan uiUpdate), remoteUpdates: make(chan remoteCaptureUpdate), layout: layout, commands: make(chan input.CommandEvent, 16), lockStates: make(chan input.LockState, 16), failures: make(chan error, 1), focusChanges: make(chan FocusState, 1), } } // SetBrowseNavigation publishes policy; the X11 owner changes grabs between // complete chords, preserving every key release during a mode transition. func (listener *CommandListener) SetBrowseNavigation(enabled bool) { listener.browse.Store(enabled) } // SetCommandBypass retains only the bypass shortcut after held keys release. func (listener *CommandListener) SetCommandBypass(enabled bool) { listener.bypass.Store(enabled) } func (listener *CommandListener) CommandBypassEnabled() bool { return listener.bypassApplied.Load() } // SetNativeButtonFocus leaves Return to a standard button with real DOM focus. // Virtual browse targets elsewhere still use Cthulhu's document activation. func (listener *CommandListener) SetNativeButtonFocus(focused bool) { listener.nativeButtonFocus.Store(focused) } func (listener *CommandListener) SetCommandGrabs(enabled bool) { listener.commandsEnabled.Store(enabled) } // BrowseNavigationEnabled reports whether requested browse grabs have reached X11. func (listener *CommandListener) BrowseNavigationEnabled() bool { return !listener.bypass.Load() && !listener.ocrApplied.Load() && !listener.secure.Load() && listener.browse.Load() && listener.browseApplied.Load() && listener.commandsApplied.Load() } func (listener *CommandListener) CommandGrabsEnabled() bool { return (listener.commandsEnabled.Load() && !listener.secure.Load()) && listener.commandsApplied.Load() } // CommandGrabActive reports whether the listener currently owns an explicit // keyboard grab for a possible command chord. This becomes true on the first // grabbed press, before the complete chord can be resolved and dispatched. func (listener *CommandListener) CommandGrabActive() bool { return listener.grabActive.Load() } func (listener *CommandListener) LearnModeActive() bool { return listener.learnMode.Load() } func (listener *CommandListener) LearnModeEchoActive() bool { return listener.learnEcho.Load() } func (listener *CommandListener) Commands() <-chan input.CommandEvent { return listener.commands } func (listener *CommandListener) LockStates() <-chan input.LockState { return listener.lockStates } func (listener *CommandListener) Failures() <-chan error { return listener.failures } func (listener *CommandListener) FocusChanges() <-chan FocusState { return listener.focusChanges }