package xorg /* #cgo pkg-config: x11 xtst #include "commands_native.h" #include */ import "C" import ( "context" "errors" "fmt" "runtime" "time" "git.stormux.org/storm/cthulhu/internal/input" ) const xtermFocusCheckInterval = 100 * time.Millisecond func (listener *CommandListener) Start(ctx context.Context) error { if err := ctx.Err(); err != nil { return err } listener.mutex.Lock() defer listener.mutex.Unlock() if listener.done != nil { return errors.New("command listener already started") } symbols, err := command_modifier_symbols(listener.modifiers) if err != nil { return err } var useInsert, useKeypad, useCaps C.int for _, symbol := range symbols { switch symbol { case 0xff63: useInsert = 1 case 0xff9e: useKeypad = 1 case 0xffe5: useCaps = 1 } } workerContext, cancel := context.WithCancel(ctx) listener.cancel = cancel listener.done = make(chan struct{}) setup := make(chan error, 1) go func() { runtime.LockOSThread() defer runtime.UnlockOSThread() defer close(listener.done) defer listener.grabActive.Store(false) defer listener.learnMode.Store(false) defer listener.learnEcho.Store(false) defer listener.uiSession.Store(0) defer listener.uiEcho.Store(false) var nativeError C.int listener.commandsEnabled.Store(true) device := C.open_commands(useInsert, useKeypad, useCaps, &nativeError) if device == nil { setup <- fmt.Errorf("open X11 command listener: native error %d (a conflicting command-key grab may already exist)", int(nativeError)) return } defer C.close_commands(device) defer listener.browseApplied.Store(false) defer listener.ocrApplied.Store(false) defer listener.commandsApplied.Store(false) defer listener.bypassApplied.Store(false) listener.commandsApplied.Store(true) layout := listener.layout modifiers := append([]string(nil), listener.modifiers...) core := input.CloneBindingOverrides(listener.bindingOverrides) tentacles := input.CloneBindingOverrides(listener.tentacleBindings) overrides := input.MergeTentacleBindings(core, tentacles) if err := configureNativeBindings(device, layout, modifiers, overrides); err != nil { setup <- err return } listener.publishTentacleBindings(overrides) defer listener.publishTentacleBindings(nil) setup <- nil listener.runCommandLoop(workerContext, device, layout, modifiers, core, tentacles, symbols) }() select { case err := <-setup: if err != nil { cancel() <-listener.done } return err case <-ctx.Done(): cancel() <-listener.done return ctx.Err() } } func (listener *CommandListener) Stop(ctx context.Context) error { listener.mutex.Lock() cancel, done := listener.cancel, listener.done listener.mutex.Unlock() if done == nil { return nil } cancel() select { case <-done: return nil case <-ctx.Done(): return ctx.Err() } }