Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// Authors: Storm Dragon, Angus Kola
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package review
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
)
|
||||
|
||||
// Interest belongs to the review worker. Zone bounds are provider coordinates;
|
||||
// callers must resolve fresh source/window geometry before screen capture.
|
||||
// A flat zone must never silently fall back to the larger object's bounds.
|
||||
type Interest struct {
|
||||
Offset int // -1 means use the native caret; virtual browser carets supply an offset.
|
||||
Object atspi.ObjectID
|
||||
Zone atspi.Rectangle
|
||||
HasZone bool
|
||||
}
|
||||
|
||||
type interestMode uint8
|
||||
|
||||
const (
|
||||
implicitInterest interestMode = iota
|
||||
objectInterest
|
||||
flatInterest
|
||||
)
|
||||
|
||||
type interestReply struct {
|
||||
interest Interest
|
||||
err error
|
||||
}
|
||||
type interestRequest struct {
|
||||
ctx context.Context
|
||||
result chan interestReply
|
||||
}
|
||||
|
||||
// ObjectOfInterest is ordered with navigation, and never captures a tree. Call
|
||||
// it on a worker. Explicit review wins over a virtual browser caret, then focus.
|
||||
// Failed/stale explicit review returns an error instead of capturing focus.
|
||||
func (worker *Worker) ObjectOfInterest(ctx context.Context) (Interest, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Interest{}, err
|
||||
}
|
||||
worker.stateMutex.Lock()
|
||||
done, started := worker.done, worker.started
|
||||
worker.stateMutex.Unlock()
|
||||
if !started {
|
||||
return Interest{}, errors.New("review worker is not running")
|
||||
}
|
||||
select {
|
||||
case <-done:
|
||||
return Interest{}, ErrNoCurrent
|
||||
default:
|
||||
}
|
||||
query := &interestRequest{ctx: ctx, result: make(chan interestReply, 1)}
|
||||
select {
|
||||
case worker.requests <- request{interest: query}:
|
||||
case <-ctx.Done():
|
||||
return Interest{}, ctx.Err()
|
||||
case <-done:
|
||||
return Interest{}, ErrNoCurrent
|
||||
}
|
||||
select {
|
||||
case reply := <-query.result:
|
||||
return reply.interest, reply.err
|
||||
case <-ctx.Done():
|
||||
return Interest{}, ctx.Err()
|
||||
case <-done:
|
||||
return Interest{}, ErrNoCurrent
|
||||
}
|
||||
}
|
||||
|
||||
func resolve_interest(ctx context.Context, focus atspi.ObjectID, explicit interestMode, cursor *Cursor, flat *FlatCursor, flatTree *atspi.AccessibleTree, document *DocumentCursor) (Interest, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return Interest{}, err
|
||||
}
|
||||
if !focus.Valid() {
|
||||
return Interest{}, ErrNoCurrent
|
||||
}
|
||||
switch explicit {
|
||||
case objectInterest:
|
||||
if cursor == nil {
|
||||
return Interest{}, ErrNoCurrent
|
||||
}
|
||||
position, err := cursor.Current()
|
||||
return Interest{Object: position.Current.ID(), Offset: -1}, err
|
||||
case flatInterest:
|
||||
if err := flat.Refresh(ctx, flatTree, focus); err != nil {
|
||||
return Interest{}, err
|
||||
}
|
||||
position := flat.position(Character)
|
||||
if !position.Bounds.Valid() {
|
||||
return Interest{}, atspi.ErrStaleGeometry
|
||||
}
|
||||
return Interest{Object: position.Object, Offset: -1, Zone: position.Bounds, HasZone: true}, nil
|
||||
}
|
||||
if document != nil {
|
||||
position, err := document.SyncFocus(ctx, focus)
|
||||
if err == nil && position.Origin != NativeNavigation {
|
||||
return Interest{Object: position.Object.ID(), Offset: position.Offset}, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, atspi.ErrNotInDocument) && !errors.Is(err, atspi.ErrObjectNotFound) {
|
||||
return Interest{}, err
|
||||
}
|
||||
}
|
||||
return Interest{Object: focus, Offset: -1}, nil
|
||||
}
|
||||
Reference in new issue
Block a user