Files

115 lines
4.0 KiB
Go

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
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
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),
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) }
// 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.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
}