103 lines
3.2 KiB
Go
103 lines
3.2 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package atspi
|
|
|
|
/*
|
|
#include "adapter_native.h"
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"context"
|
|
"unsafe"
|
|
)
|
|
|
|
// CharacterAttributes is a copy of the current AT-SPI text attribute run.
|
|
// Native handles remain on the GLib thread.
|
|
type CharacterAttributes struct {
|
|
Object ObjectID
|
|
Values map[string]string
|
|
}
|
|
|
|
var spokenCharacterAttributeKeys = [...]string{
|
|
"size", "family-name", "weight", "indent", "underline",
|
|
"strikethrough", "justification", "style", "paragraph-style",
|
|
"text-spelling", "fg-color", "bg-color", "invalid", "invalid-spelling", "spelling",
|
|
}
|
|
|
|
func (adapter *Adapter) ReadCharacterAttributes(ctx context.Context, object ObjectID) (CharacterAttributes, error) {
|
|
if !object.Valid() {
|
|
return CharacterAttributes{}, ErrStaleObject
|
|
}
|
|
result, err := adapter.documentOperation(ctx, treeCaptureRequest{reviewFocus: object, readAttributes: true})
|
|
return result.attributes, err
|
|
}
|
|
|
|
func (adapter *Adapter) readNativeCharacterAttributes(ctx context.Context, object ObjectID) (CharacterAttributes, error) {
|
|
if err := ctx.Err(); err != nil {
|
|
return CharacterAttributes{}, err
|
|
}
|
|
if len(adapter.nativeFocus) == 0 || adapter.nativeFocus[len(adapter.nativeFocus)-1].id.BusName != object.BusName {
|
|
return CharacterAttributes{}, ErrStaleObject
|
|
}
|
|
var source *C.AtspiAccessible
|
|
if adapter.nativeFocusIs(object) {
|
|
source = adapter.nativeFocus[len(adapter.nativeFocus)-1].source
|
|
} else {
|
|
source = adapter.nativeDocument[object]
|
|
}
|
|
if source == nil {
|
|
return CharacterAttributes{}, ErrStaleObject
|
|
}
|
|
if C.atspi_accessible_get_role(source, nil) == C.ATSPI_ROLE_PASSWORD_TEXT {
|
|
return CharacterAttributes{Object: object}, nil
|
|
}
|
|
text := C.atspi_accessible_get_text_iface(source)
|
|
if text == nil {
|
|
return CharacterAttributes{Object: object}, nil
|
|
}
|
|
defer C.g_object_unref(C.gpointer(text))
|
|
var nativeErr *C.GError
|
|
count := C.atspi_text_get_character_count(text, &nativeErr)
|
|
if nativeErr != nil {
|
|
err := nativeError("read character attribute text length", nativeErr)
|
|
C.g_error_free(nativeErr)
|
|
return CharacterAttributes{}, err
|
|
}
|
|
if count <= 0 {
|
|
return CharacterAttributes{Object: object}, nil
|
|
}
|
|
offset := C.atspi_text_get_caret_offset(text, &nativeErr)
|
|
if nativeErr != nil {
|
|
err := nativeError("read character attribute caret", nativeErr)
|
|
C.g_error_free(nativeErr)
|
|
return CharacterAttributes{}, err
|
|
}
|
|
if offset < 0 || offset >= count {
|
|
offset = count - 1
|
|
}
|
|
var start, end C.gint
|
|
attributes := C.atspi_text_get_attribute_run(text, offset, 1, &start, &end, &nativeErr)
|
|
if nativeErr != nil {
|
|
err := nativeError("read character attributes", nativeErr)
|
|
C.g_error_free(nativeErr)
|
|
return CharacterAttributes{}, err
|
|
}
|
|
if attributes == nil {
|
|
return CharacterAttributes{Object: object}, nil
|
|
}
|
|
defer C.g_hash_table_unref(attributes)
|
|
result := CharacterAttributes{Object: object, Values: make(map[string]string)}
|
|
for _, key := range spokenCharacterAttributeKeys {
|
|
name := C.CString(key)
|
|
value := C.g_hash_table_lookup(attributes, C.gconstpointer(unsafe.Pointer(name)))
|
|
C.free(unsafe.Pointer(name))
|
|
if value != nil {
|
|
result.Values[key] = sanitizeSnapshotField(C.GoString((*C.char)(value)))
|
|
}
|
|
}
|
|
return result, ctx.Err()
|
|
}
|