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,161 @@
|
||||
// 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"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type AccessibleAction struct{ Name, Label string }
|
||||
type ActionTarget struct {
|
||||
Object, Window, Focus ObjectID
|
||||
Epoch uint64
|
||||
Role string
|
||||
}
|
||||
type ActionSet struct {
|
||||
Target ActionTarget
|
||||
Actions []AccessibleAction
|
||||
}
|
||||
|
||||
func (adapter *Adapter) AccessibleActions(ctx context.Context, object ObjectID) (ActionSet, error) {
|
||||
result, err := adapter.documentOperation(ctx, treeCaptureRequest{listActions: true, reviewFocus: object})
|
||||
return result.actions, err
|
||||
}
|
||||
func (adapter *Adapter) PerformAccessibleAction(ctx context.Context, target ActionTarget, name string) error {
|
||||
_, err := adapter.documentOperation(ctx, treeCaptureRequest{actionTarget: &target, actionName: name})
|
||||
return err
|
||||
}
|
||||
|
||||
// Native action work stays on the GLib owner, using retained review/document
|
||||
// handles as well as focus. Never redirect a stale action to a new focus.
|
||||
func (adapter *Adapter) native_action_source(object ObjectID) (*C.AtspiAccessible, ActionTarget, error) {
|
||||
var source *C.AtspiAccessible
|
||||
for _, item := range adapter.nativeFocus {
|
||||
if item.id == object {
|
||||
source = item.source
|
||||
break
|
||||
}
|
||||
}
|
||||
if source == nil {
|
||||
source = adapter.nativeReview[object]
|
||||
}
|
||||
if source == nil {
|
||||
source = adapter.nativeDocument[object]
|
||||
}
|
||||
if source == nil {
|
||||
return nil, ActionTarget{}, ErrObjectNotFound
|
||||
}
|
||||
C.atspi_accessible_clear_cache_single(source)
|
||||
states := C.atspi_accessible_get_state_set(source)
|
||||
if states == nil {
|
||||
return nil, ActionTarget{}, ErrStaleObject
|
||||
}
|
||||
defunct := C.atspi_state_set_contains(states, C.ATSPI_STATE_DEFUNCT) != 0
|
||||
C.g_object_unref(C.gpointer(states))
|
||||
if defunct {
|
||||
return nil, ActionTarget{}, ErrStaleObject
|
||||
}
|
||||
window := C.cthulhu_window_ancestor(source)
|
||||
if window == nil {
|
||||
return nil, ActionTarget{}, ErrStaleObject
|
||||
}
|
||||
defer C.g_object_unref(C.gpointer(window))
|
||||
adapter.refreshForeground()
|
||||
windowID := nativeObjectID(window)
|
||||
if !adapter.matchesForeground(window, windowID) {
|
||||
return nil, ActionTarget{}, ErrStaleObject
|
||||
}
|
||||
role := C.atspi_accessible_get_role_name(source, nil)
|
||||
defer C.g_free(C.gpointer(role))
|
||||
target := ActionTarget{Object: object, Window: windowID, Epoch: adapter.Foreground().Epoch, Role: C.GoString(role)}
|
||||
if len(adapter.nativeFocus) != 0 {
|
||||
target.Focus = adapter.nativeFocus[len(adapter.nativeFocus)-1].id
|
||||
}
|
||||
return source, target, nil
|
||||
}
|
||||
|
||||
func (adapter *Adapter) native_actions(ctx context.Context, object ObjectID, expected *ActionTarget, name string) (ActionSet, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return ActionSet{}, err
|
||||
}
|
||||
source, target, err := adapter.native_action_source(object)
|
||||
if err != nil {
|
||||
return ActionSet{}, err
|
||||
}
|
||||
if expected != nil && target != *expected {
|
||||
return ActionSet{}, ErrStaleObject
|
||||
}
|
||||
action := C.atspi_accessible_get_action_iface(source)
|
||||
if action == nil {
|
||||
return ActionSet{Target: target}, nil
|
||||
}
|
||||
defer C.g_object_unref(C.gpointer(action))
|
||||
var nativeErr *C.GError
|
||||
count := int(C.atspi_action_get_n_actions(action, &nativeErr))
|
||||
if nativeErr != nil {
|
||||
C.g_error_free(nativeErr)
|
||||
return ActionSet{}, errors.New("could not read accessible actions")
|
||||
}
|
||||
if count < 0 || count > 256 {
|
||||
return ActionSet{}, errors.New("invalid accessible action count")
|
||||
}
|
||||
result := ActionSet{Target: target}
|
||||
seen := map[string]bool{}
|
||||
for index := 0; index < count; index++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return ActionSet{}, err
|
||||
}
|
||||
rawName := C.atspi_action_get_name(action, C.gint(index), nil)
|
||||
actionName := sanitizeSnapshotField(C.GoString(rawName))
|
||||
C.g_free(C.gpointer(rawName))
|
||||
if actionName == "" || seen[actionName] {
|
||||
continue
|
||||
}
|
||||
seen[actionName] = true
|
||||
if expected != nil && actionName == name {
|
||||
if adapter.secure.Load() || ctx.Err() != nil {
|
||||
return ActionSet{}, ErrStaleObject
|
||||
}
|
||||
// Recheck ownership immediately before the external side effect.
|
||||
_, current, checkErr := adapter.native_action_source(object)
|
||||
if checkErr != nil || current != target {
|
||||
return ActionSet{}, ErrStaleObject
|
||||
}
|
||||
if C.atspi_action_do_action(action, C.gint(index), &nativeErr) == 0 {
|
||||
if nativeErr != nil {
|
||||
C.g_error_free(nativeErr)
|
||||
}
|
||||
return ActionSet{}, errors.New("accessible action failed")
|
||||
}
|
||||
if nativeErr != nil {
|
||||
C.g_error_free(nativeErr)
|
||||
return ActionSet{}, errors.New("accessible action failed")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
labelRaw := C.atspi_action_get_localized_name(action, C.gint(index), nil)
|
||||
label := sanitizeSnapshotField(C.GoString(labelRaw))
|
||||
C.g_free(C.gpointer(labelRaw))
|
||||
if label == "" {
|
||||
description := C.atspi_action_get_description(action, C.gint(index), nil)
|
||||
label = sanitizeSnapshotField(C.GoString(description))
|
||||
C.g_free(C.gpointer(description))
|
||||
}
|
||||
if label == "" {
|
||||
label = actionName
|
||||
}
|
||||
result.Actions = append(result.Actions, AccessibleAction{Name: actionName, Label: label})
|
||||
}
|
||||
if expected != nil {
|
||||
return ActionSet{}, ErrStaleObject
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
Reference in new issue
Block a user