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,82 @@
|
||||
// Copyright (c) 2026 Storm Dragon
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
package atspi
|
||||
|
||||
/*
|
||||
#include "adapter_native.h"
|
||||
static char *tentacle_attribute(GHashTable *attributes, const char *key) {
|
||||
return attributes ? g_hash_table_lookup(attributes, key) : NULL;
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type TransferItem struct{ Text, Path string }
|
||||
|
||||
// ReadTransferItem reads one current line or one explicit path from the
|
||||
// foreground object. It never walks descendants or reads password contents.
|
||||
func (adapter *Adapter) ReadTransferItem(ctx context.Context, object ObjectID, offset int) (TransferItem, error) {
|
||||
result, err := adapter.documentOperation(ctx, treeCaptureRequest{transferItem: true, reviewFocus: object, positionOffset: offset})
|
||||
return result.item, err
|
||||
}
|
||||
func (adapter *Adapter) nativeTransferItem(ctx context.Context, object ObjectID, offset int) (TransferItem, error) {
|
||||
source, _, err := adapter.native_action_source(object)
|
||||
if err != nil || ctx.Err() != nil {
|
||||
return TransferItem{}, ErrStaleObject
|
||||
}
|
||||
if C.atspi_accessible_get_role(source, nil) == C.ATSPI_ROLE_PASSWORD_TEXT {
|
||||
return TransferItem{}, errors.New("protected item")
|
||||
}
|
||||
attributes := C.atspi_accessible_get_attributes(source, nil)
|
||||
if attributes != nil {
|
||||
defer C.g_hash_table_unref(attributes)
|
||||
for _, key := range []string{"file-uri", "uri", "file-path"} {
|
||||
rawKey := C.CString(key)
|
||||
raw := C.tentacle_attribute(attributes, rawKey)
|
||||
C.free(unsafe.Pointer(rawKey))
|
||||
if raw != nil {
|
||||
path := C.GoString(raw)
|
||||
if strings.HasPrefix(path, "file:") || strings.HasPrefix(path, "/") {
|
||||
return TransferItem{Path: path}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
snapshot := snapshotFromNativeWithCaret(source, false, false, false, false)
|
||||
// File icons and rows without explicit paths must not become text messages.
|
||||
if snapshot.Role == "icon" || snapshot.Role == "list item" || snapshot.Role == "table cell" && !snapshot.Spreadsheet {
|
||||
return TransferItem{}, errors.New("file item does not expose a reliable path")
|
||||
}
|
||||
value := snapshot.Name
|
||||
text := C.atspi_accessible_get_text_iface(source)
|
||||
if text != nil {
|
||||
defer C.g_object_unref(C.gpointer(text))
|
||||
if offset < 0 {
|
||||
offset = int(C.atspi_text_get_caret_offset(text, nil))
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
}
|
||||
line := C.atspi_text_get_string_at_offset(text, C.gint(offset), C.ATSPI_TEXT_GRANULARITY_LINE, nil)
|
||||
if line == nil {
|
||||
return TransferItem{}, errors.New("focused line unavailable")
|
||||
}
|
||||
value = C.GoString(line.content)
|
||||
C.g_free(C.gpointer(line.content))
|
||||
C.g_free(C.gpointer(line))
|
||||
// Native line delimiters are not part of the message; retain actual spacing.
|
||||
value = strings.TrimRight(value, "\r\n")
|
||||
}
|
||||
if len(value) >= 4096 {
|
||||
return TransferItem{}, errors.New("focused item exceeds transfer limit")
|
||||
}
|
||||
if value == "" {
|
||||
return TransferItem{}, errors.New("focused item has no text or path")
|
||||
}
|
||||
return TransferItem{Text: strings.NewReplacer("\r\n", " ", "\r", " ", "\n", " ").Replace(value)}, nil
|
||||
}
|
||||
Reference in new issue
Block a user