Files
cthulhu/internal/atspi/character_attributes_native.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()
}