Reset master to testing content
This commit is contained in:
1 parent
0f2587b719
commit
42afa2a56c
1483 files changed
+145308
-938069
No files matched your search
@@ -0,0 +1,237 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// native_review_table_children enumerates a selectable table's viewport, as
|
||||
// Orca's flat review does. GTK exposes every offscreen row as a child; fetching
|
||||
// that list creates thousands of transient cell proxies during timeline refresh.
|
||||
// Returned references belong to the caller. All queries run on the GLib owner.
|
||||
func native_review_table_children(ctx context.Context, source *C.AtspiAccessible, clip Rectangle) (children []*C.AtspiAccessible, handled bool, err error) {
|
||||
if !clip.Valid() {
|
||||
return nil, false, nil
|
||||
}
|
||||
table := C.atspi_accessible_get_table_iface(source)
|
||||
if table == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
defer C.g_object_unref(C.gpointer(table))
|
||||
selection := C.atspi_accessible_get_selection_iface(source)
|
||||
if selection == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
defer C.g_object_unref(C.gpointer(selection))
|
||||
component := C.atspi_accessible_get_component_iface(source)
|
||||
if component == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
defer C.g_object_unref(C.gpointer(component))
|
||||
defer func() {
|
||||
if err != nil {
|
||||
for _, child := range children {
|
||||
C.g_object_unref(C.gpointer(child))
|
||||
}
|
||||
children = nil
|
||||
}
|
||||
}()
|
||||
var queryError *C.GError
|
||||
rows := int(C.atspi_table_get_n_rows(table, &queryError))
|
||||
if queryError != nil {
|
||||
err = nativeError("query review table rows", queryError)
|
||||
C.g_error_free(queryError)
|
||||
return nil, true, err
|
||||
}
|
||||
columns := int(C.atspi_table_get_n_columns(table, &queryError))
|
||||
if queryError != nil {
|
||||
err = nativeError("query review table columns", queryError)
|
||||
C.g_error_free(queryError)
|
||||
return nil, true, err
|
||||
}
|
||||
if rows < 0 || columns < 0 {
|
||||
return nil, true, fmt.Errorf("%w: negative review table dimensions", ErrInvalidTree)
|
||||
}
|
||||
// Native focus normalization uses the first selected cell of a uniquely
|
||||
// selected row. Keep that identity as an anchor when horizontal scrolling
|
||||
// hides its column; its geometry still excludes it from review output.
|
||||
if rows > 0 {
|
||||
if selected := C.atspi_selection_get_selected_child(selection, 0, nil); selected != nil {
|
||||
children = append(children, selected)
|
||||
}
|
||||
}
|
||||
appendHeaders := func(first, last int) error {
|
||||
if last-first >= maxTreeNodes {
|
||||
return fmt.Errorf("%w: visible table exceeds limit %d", ErrInvalidTree, maxTreeNodes)
|
||||
}
|
||||
for column := first; column <= last; column++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
header := C.atspi_table_get_column_header(table, C.gint(column), nil)
|
||||
if header == nil {
|
||||
continue
|
||||
}
|
||||
geometry, geometryErr := geometryFromNative(ctx, header)
|
||||
_, intersects := geometry.Bounds.Intersection(clip)
|
||||
if geometryErr == nil && geometry.Visible && geometry.Showing && intersects {
|
||||
children = append(children, header)
|
||||
} else {
|
||||
C.g_object_unref(C.gpointer(header))
|
||||
}
|
||||
if geometryErr != nil {
|
||||
return geometryErr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if rows == 0 || columns == 0 {
|
||||
err = appendHeaders(0, columns-1)
|
||||
return children, true, err
|
||||
}
|
||||
// Start just below the table's header. Hit testing avoids walking from row
|
||||
// zero when the viewport is thousands of rows down the model. A header's
|
||||
// bounds let us skip directly to its bottom edge.
|
||||
seedRow, seedColumn := -1, -1
|
||||
bottom := int64(clip.Y) + int64(clip.Height)
|
||||
for y := int64(clip.Y) + 1; y < bottom && seedRow < 0; y++ {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return children, true, err
|
||||
}
|
||||
for _, x := range []int64{int64(clip.X) + 1, int64(clip.X) + int64(clip.Width)/2} {
|
||||
child := C.atspi_component_get_accessible_at_point(component, C.gint(x), C.gint(y), C.ATSPI_COORD_TYPE_SCREEN, &queryError)
|
||||
if queryError != nil {
|
||||
err = nativeError("locate visible review table cell", queryError)
|
||||
C.g_error_free(queryError)
|
||||
if child != nil {
|
||||
C.g_object_unref(C.gpointer(child))
|
||||
}
|
||||
return children, true, err
|
||||
}
|
||||
if child == nil {
|
||||
continue
|
||||
}
|
||||
row, column := -1, -1
|
||||
if C.atspi_accessible_get_role(child, nil) == C.ATSPI_ROLE_TABLE_CELL {
|
||||
if cell := C.atspi_accessible_get_table_cell(child); cell != nil {
|
||||
var nativeRow, nativeColumn C.gint
|
||||
C.atspi_table_cell_get_position(cell, &nativeRow, &nativeColumn, &queryError)
|
||||
if queryError == nil {
|
||||
row, column = int(nativeRow), int(nativeColumn)
|
||||
} else {
|
||||
err = nativeError("query visible review cell position", queryError)
|
||||
C.g_error_free(queryError)
|
||||
C.g_object_unref(C.gpointer(cell))
|
||||
C.g_object_unref(C.gpointer(child))
|
||||
return children, true, err
|
||||
}
|
||||
C.g_object_unref(C.gpointer(cell))
|
||||
} else {
|
||||
index := C.atspi_accessible_get_index_in_parent(child, nil)
|
||||
row = int(C.atspi_table_get_row_at_index(table, index, nil))
|
||||
column = int(C.atspi_table_get_column_at_index(table, index, nil))
|
||||
}
|
||||
}
|
||||
geometry, geometryErr := geometryFromNative(ctx, child)
|
||||
C.g_object_unref(C.gpointer(child))
|
||||
if geometryErr != nil {
|
||||
return children, true, geometryErr
|
||||
}
|
||||
if row >= 0 && row < rows && column >= 0 && column < columns {
|
||||
seedRow, seedColumn = row, column
|
||||
break
|
||||
}
|
||||
if geometry.BoundsKnown {
|
||||
y = max(y, int64(geometry.Bounds.Y)+int64(geometry.Bounds.Height)-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if seedRow < 0 {
|
||||
// An empty or clipped body can still have visible column headers.
|
||||
err = appendHeaders(0, columns-1)
|
||||
return children, true, err
|
||||
}
|
||||
visible := func(row, column int, keep bool) (bool, error) {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
child := C.atspi_table_get_accessible_at(table, C.gint(row), C.gint(column), &queryError)
|
||||
if queryError != nil {
|
||||
err := nativeError("query visible review table cell", queryError)
|
||||
C.g_error_free(queryError)
|
||||
queryError = nil
|
||||
if child != nil {
|
||||
C.g_object_unref(C.gpointer(child))
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
if child == nil {
|
||||
return false, nil
|
||||
}
|
||||
geometry, geometryErr := geometryFromNative(ctx, child)
|
||||
_, intersects := geometry.Bounds.Intersection(clip)
|
||||
showing := geometry.Visible && geometry.Showing && geometry.BoundsKnown && intersects
|
||||
if keep && showing && geometryErr == nil {
|
||||
children = append(children, child)
|
||||
} else {
|
||||
C.g_object_unref(C.gpointer(child))
|
||||
}
|
||||
return showing, geometryErr
|
||||
}
|
||||
firstRow, lastRow := seedRow, seedRow
|
||||
firstColumn, lastColumn := seedColumn, seedColumn
|
||||
for _, axis := range []struct {
|
||||
first, last *int
|
||||
limit int
|
||||
row bool
|
||||
}{{&firstRow, &lastRow, rows, true}, {&firstColumn, &lastColumn, columns, false}} {
|
||||
for _, step := range []int{-1, 1} {
|
||||
edge := axis.first
|
||||
if step > 0 {
|
||||
edge = axis.last
|
||||
}
|
||||
for next := *edge + step; next >= 0 && next < axis.limit; next += step {
|
||||
if *axis.last-*axis.first >= maxTreeNodes {
|
||||
return children, true, fmt.Errorf("%w: visible table exceeds limit %d", ErrInvalidTree, maxTreeNodes)
|
||||
}
|
||||
row, column := seedRow, seedColumn
|
||||
if axis.row {
|
||||
row = next
|
||||
} else {
|
||||
column = next
|
||||
}
|
||||
showing, err := visible(row, column, false)
|
||||
if err != nil {
|
||||
return children, true, err
|
||||
}
|
||||
if !showing {
|
||||
break
|
||||
}
|
||||
*edge = next
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := appendHeaders(firstColumn, lastColumn); err != nil {
|
||||
return children, true, err
|
||||
}
|
||||
for row := firstRow; row <= lastRow; row++ {
|
||||
for column := firstColumn; column <= lastColumn; column++ {
|
||||
if len(children) >= maxTreeNodes {
|
||||
return children, true, fmt.Errorf("%w: visible table exceeds limit %d", ErrInvalidTree, maxTreeNodes)
|
||||
}
|
||||
if _, err := visible(row, column, true); err != nil {
|
||||
return children, true, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return children, true, nil
|
||||
}
|
||||
Reference in new issue
Block a user