118 lines
3.9 KiB
Go
118 lines
3.9 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package xorg
|
|
|
|
/*
|
|
#include "commands_native.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/input"
|
|
)
|
|
|
|
// SetOCRReview acknowledges a transactional context change only after every
|
|
// held chord has released. It belongs on a session worker, not the command
|
|
// consumer. Failure retains the old context. Secure/terminal policy still wins.
|
|
// The session must dismiss on real focus changes and ordinary XRecord input.
|
|
func (listener *CommandListener) SetOCRReview(ctx context.Context, enabled bool) error {
|
|
return listener.set_ocr_context(ctx, enabled, false, 0)
|
|
}
|
|
|
|
// SetOCRPending owns Escape only; recognition never claims document navigation.
|
|
func (listener *CommandListener) SetOCRPending(ctx context.Context, enabled bool) error {
|
|
return listener.set_ocr_context(ctx, enabled, enabled, 0)
|
|
}
|
|
|
|
// SetOCRContext admits a live session only in its expected foreground client.
|
|
func (listener *CommandListener) SetOCRContext(ctx context.Context, foreground uint64, pending bool) error {
|
|
if foreground == 0 {
|
|
return errors.New("OCR foreground unavailable")
|
|
}
|
|
return listener.set_ocr_context(ctx, true, pending, foreground)
|
|
}
|
|
|
|
func (listener *CommandListener) set_ocr_context(ctx context.Context, enabled, pending bool, foreground uint64) error {
|
|
if err := ctx.Err(); err != nil {
|
|
return err
|
|
}
|
|
listener.mutex.Lock()
|
|
done := listener.done
|
|
listener.mutex.Unlock()
|
|
if done == nil {
|
|
return errors.New("command listener is not running")
|
|
}
|
|
update := bindingUpdate{ctx: ctx, ocrReview: &enabled, ocrPending: &pending, ocrForeground: foreground, result: make(chan error, 1)}
|
|
select {
|
|
case listener.bindingUpdates <- update:
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-done:
|
|
return errors.New("command listener has stopped")
|
|
}
|
|
select {
|
|
case err := <-update.result:
|
|
return err
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-done:
|
|
return errors.New("command listener has stopped")
|
|
}
|
|
}
|
|
|
|
func (listener *CommandListener) OCRReviewEnabled() bool {
|
|
return !listener.ocrPending.Load() && !listener.secure.Load() && listener.commandsEnabled.Load() && listener.commandsApplied.Load() && listener.ocrApplied.Load()
|
|
}
|
|
|
|
func (owner *commandOwner) context_bindings() input.CommandBindings {
|
|
if owner.ocrReview {
|
|
if owner.ocrPending {
|
|
return input.OCRPendingBindings(owner.globalBindings)
|
|
}
|
|
return input.OCRCommandBindings(owner.globalBindings)
|
|
}
|
|
return resolvedBindings(owner.layout, owner.device.browse != 0, owner.overrides)
|
|
}
|
|
|
|
// Configure grabs and routing together. Native replacement restores old
|
|
// bindings on error; restore its old mode too before reporting a failed entry.
|
|
func configure_context_bindings(device *C.CommandDevice, layout input.KeyboardLayout, modifiers []string, overrides []input.BindingOverride, browse, ocrReview bool, pending ...bool) error {
|
|
previous, enabled := device.browse, device.commands
|
|
device.browse = 0
|
|
if browse || ocrReview {
|
|
device.browse = 1
|
|
}
|
|
err := configure_native_bindings(device, layout, modifiers, overrides, ocrReview, pending...)
|
|
if err != nil {
|
|
device.browse = previous
|
|
if result := C.set_command_keys(device, enabled); result != 0 {
|
|
return fmt.Errorf("restore X11 input context: native error %d", int(result))
|
|
}
|
|
return err
|
|
}
|
|
device.browseWindow = C.read_foreground(device.display).window
|
|
return nil
|
|
}
|
|
|
|
func (owner *commandOwner) leave_ocr_review() bool {
|
|
if !owner.ocrReview {
|
|
return true
|
|
}
|
|
if err := configure_context_bindings(owner.device, owner.layout, owner.modifiers, owner.overrides, owner.listener.browse.Load(), false); err != nil {
|
|
owner.listener.failures <- err
|
|
return false
|
|
}
|
|
owner.ocrReview, owner.ocrPending = false, false
|
|
owner.listener.ocrPending.Store(false)
|
|
owner.listener.ocrApplied.Store(false)
|
|
owner.listener.browseApplied.Store(owner.device.browse != 0)
|
|
owner.router.ReplaceBindings(owner.context_bindings())
|
|
return true
|
|
}
|