Files
cthulhu/internal/atspi/current_line_native.go

87 lines
2.7 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
/*
#cgo pkg-config: atspi-2 glib-2.0 gobject-2.0
#include <atspi/atspi.h>
*/
import "C"
import (
"context"
"errors"
"strings"
)
// ReadFocusedLine reads only the line containing the current native caret.
func (adapter *Adapter) ReadFocusedLine(ctx context.Context, focus ObjectID) (DocumentContent, error) {
result, err := adapter.documentOperation(ctx, treeCaptureRequest{reviewFocus: focus, readFocusedLine: true})
return result.content, err
}
func (adapter *Adapter) read_native_focused_line(ctx context.Context, focus ObjectID) (DocumentContent, error) {
if err := ctx.Err(); err != nil {
return DocumentContent{}, err
}
if !adapter.nativeFocusIs(focus) {
return DocumentContent{}, ErrStaleObject
}
source := adapter.nativeFocus[len(adapter.nativeFocus)-1].source
snapshot := snapshotFromNativeWithCaret(source, false, false, false, false)
if snapshot.Role == "password text" {
return DocumentContent{}, ErrNoDocumentCaret
}
content := DocumentContent{Snapshot: snapshot, Text: snapshot.Name}
text := C.atspi_accessible_get_text_iface(source)
if text == nil {
return content, ctx.Err()
}
defer C.g_object_unref(C.gpointer(text))
reader := nativeReviewText{text: text, terminal: snapshot.Role == "terminal"}
count, err := reader.count(ctx)
if err != nil {
return DocumentContent{}, err
}
content.Text = ""
if count == 0 {
return content, ctx.Err()
}
var nativeErr *C.GError
offset := int(C.atspi_text_get_caret_offset(text, &nativeErr))
if nativeErr != nil {
err := nativeError("read current line caret", nativeErr)
C.g_error_free(nativeErr)
return DocumentContent{}, err
}
// A text object without a caret reads from its first line, as NVDA does.
offset = min(max(0, offset), count)
if offset == count {
last := C.atspi_text_get_text(text, C.gint(count-1), C.gint(count), &nativeErr)
if last != nil {
defer C.g_free(C.gpointer(last))
}
if nativeErr != nil {
err := nativeError("read current line end", nativeErr)
C.g_error_free(nativeErr)
return DocumentContent{}, err
}
// A trailing newline puts the caret on an empty final line.
if strings.HasSuffix(C.GoString(last), "\n") {
content.Start, content.End = count, count
return content, ctx.Err()
}
offset--
}
line, err := reader.line(ctx, offset)
if err != nil {
return DocumentContent{}, err
}
if line.Start < 0 || line.Start > offset || line.End <= offset || line.End > count {
return DocumentContent{}, errors.New("read current line: invalid text range")
}
content.Start, content.End, content.Text, content.RawText = line.Start, line.End, line.Text, line.Text
return content, ctx.Err()
}