Merge origin/testing into hunter

This commit is contained in:
hjozwiak committed 2026-11-03 10:35:41 -05:00
commit a4d427099e
19 files changed
+424 -35

No files matched your search

@@ -420,7 +420,7 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
}
return
}
eventType := canonicalTextChangeEventType(nativeEventType)
eventType := canonicalObjectChangeEventType(nativeEventType)
originID := nativeObjectID(event.source)
if eventType == AccessibleNameEvent || eventType == DefunctStateEvent && C.cthulhu_event_detail1(event) != 0 {
C.cthulhu_forget_menu_checkability(event.source)
@@ -449,9 +449,9 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
adapter.forgetWindowDocuments(originID)
}
invalidated := adapter.applyTreeInvalidation(eventType, event)
returnedMenuItem := adapter.returned_menu_item(event, eventType, originID)
if returnedMenuItem != nil {
defer C.g_object_unref(C.gpointer(returnedMenuItem))
returnedTarget := adapter.returned_menu_item(event, eventType, originID)
if returnedTarget != nil {
defer C.g_object_unref(C.gpointer(returnedTarget))
// Keep the original showing event and popup origin in diagnostics.
// Only fresh focus on its parent supplies this normalized focus event.
eventType, invalidated = FocusEvent, false
@@ -505,6 +505,11 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
adapter.backgroundEventsFiltered.Add(1)
return
}
if tab := addedSelectedBrowserTab(event, eventType); tab != nil {
defer C.g_object_unref(C.gpointer(tab))
returnedTarget = tab
eventType, invalidated = SelectionChangedEvent, false
}
var live nativeLiveProperties
if eventType == TextInsertedEvent || eventType == ChildrenAddedEvent || eventType == BusyStateEvent {
live = capture_live_properties(event.source)
@@ -523,7 +528,7 @@ func cthulhuAtspiEventCallback(event *C.AtspiEvent, userData unsafe.Pointer) {
return
}
snapshotStarted := time.Now()
target, ownedTarget := adapter.resolveNativeEventTarget(event, eventType, returnedMenuItem)
target, ownedTarget := adapter.resolveNativeEventTarget(event, eventType, returnedTarget)
if target == nil {
return
}
+50 -3
View File
@@ -9,11 +9,58 @@ package atspi
*/
import "C"
// resolveNativeEventTarget runs on the GLib owner. The returned menu item is
// addedSelectedBrowserTab handles providers which emit selection before the new
// tab enters their accessible child list. The later child-added event supplies
// the actual target; only a freshly selected browser chrome tab qualifies.
// The caller releases the retained target on the GLib owner.
func addedSelectedBrowserTab(event *C.AtspiEvent, eventType string) *C.AtspiAccessible {
if eventType != ChildrenAddedEvent || C.atspi_accessible_get_role(event.source, nil) != C.ATSPI_ROLE_PAGE_TAB_LIST {
return nil
}
child := C.cthulhu_event_active_descendant(event)
if child == nil || C.atspi_accessible_get_role(child, nil) != C.ATSPI_ROLE_PAGE_TAB {
return nil
}
parent := C.atspi_accessible_get_parent(child, nil)
if parent == nil {
return nil
}
matchesParent := nativeObjectID(parent) == nativeObjectID(event.source)
C.g_object_unref(C.gpointer(parent))
if !matchesParent {
return nil
}
C.atspi_accessible_clear_cache_single(child)
states := C.atspi_accessible_get_state_set(child)
if states == nil {
return nil
}
selected := C.atspi_state_set_contains(states, C.ATSPI_STATE_SELECTED) != 0 &&
C.atspi_state_set_contains(states, C.ATSPI_STATE_DEFUNCT) == 0
C.g_object_unref(C.gpointer(states))
if !selected {
return nil
}
toolkit := C.atspi_accessible_get_toolkit_name(child, nil)
snapshot := Snapshot{Role: "page tab", ToolkitName: C.GoString(toolkit)}
C.g_free(C.gpointer(toolkit))
document := C.cthulhu_document_ancestor(child)
if document != nil {
C.g_object_unref(C.gpointer(document))
return nil
}
if !isBrowserChromeTab(snapshot, false) {
return nil
}
C.g_object_ref(C.gpointer(child))
return child
}
// resolveNativeEventTarget runs on the GLib owner. The override target is
// borrowed from the callback; LibreOffice and selected descendants can return
// owned references. The caller releases an owned target after event capture.
func (adapter *Adapter) resolveNativeEventTarget(event *C.AtspiEvent, eventType string, returnedMenuItem *C.AtspiAccessible) (*C.AtspiAccessible, bool) {
target := returnedMenuItem
func (adapter *Adapter) resolveNativeEventTarget(event *C.AtspiEvent, eventType string, overrideTarget *C.AtspiAccessible) (*C.AtspiAccessible, bool) {
target := overrideTarget
ownedTarget := false
if target == nil {
target = adapter.libreOfficeFocusTarget(event, eventType)
+4 -4
View File
@@ -349,18 +349,18 @@ func NewEvent(eventType string, source Snapshot) Event {
if eventType == "object:state-changed:checked" && source.Role == "menu item" {
source.Checkable = true
}
event := Event{Type: canonicalTextChangeEventType(eventType), Source: source, OriginPath: source.Path}
event := Event{Type: canonicalObjectChangeEventType(eventType), Source: source, OriginPath: source.Path}
if event.Type != eventType {
event.NativeType = eventType
}
return event
}
// AT-SPI appends an origin to some text mutations (for example, :system).
// AT-SPI appends an origin to some object mutations (for example, :system).
// The reader handles those mutations by kind; leaving the suffix on Type
// makes them fall through as ordinary object presentation events.
func canonicalTextChangeEventType(eventType string) string {
for _, kind := range []string{TextInsertedEvent, TextDeletedEvent} {
func canonicalObjectChangeEventType(eventType string) string {
for _, kind := range []string{TextInsertedEvent, TextDeletedEvent, ChildrenAddedEvent} {
if eventType == kind || strings.HasPrefix(eventType, kind+":") {
return kind
}
+7 -3
View File
@@ -45,17 +45,21 @@ func TestShouldSpeakEvent(t *testing.T) {
}
}
func TestTextChangeOriginIsCanonicalized(t *testing.T) {
func TestObjectChangeOriginIsCanonicalized(t *testing.T) {
for _, test := range []struct{ incoming, want string }{
{TextInsertedEvent + ":system", TextInsertedEvent},
{TextDeletedEvent + ":system", TextDeletedEvent},
{TextInsertedEvent, TextInsertedEvent},
{TextDeletedEvent, TextDeletedEvent},
{ChildrenAddedEvent + ":system", ChildrenAddedEvent},
{ChildrenAddedEvent, ChildrenAddedEvent},
{ChildrenRemovedEvent + ":system", ChildrenRemovedEvent + ":system"},
{"object:children-changed:additional", "object:children-changed:additional"},
{"object:text-caret-moved", "object:text-caret-moved"},
{"object:text-changed:insertion", "object:text-changed:insertion"},
} {
if got := canonicalTextChangeEventType(test.incoming); got != test.want {
t.Errorf("canonicalTextChangeEventType(%q) = %q, want %q", test.incoming, got, test.want)
if got := canonicalObjectChangeEventType(test.incoming); got != test.want {
t.Errorf("canonicalObjectChangeEventType(%q) = %q, want %q", test.incoming, got, test.want)
}
if got := NewEvent(test.incoming, Snapshot{}).Type; got != test.want {
t.Errorf("NewEvent(%q).Type = %q, want %q", test.incoming, got, test.want)
+1 -1
View File
@@ -35,7 +35,7 @@ import (
// title or application-supplied name. Only verified Pidgin transcript insertions
// bypass background filtering. Entry typing and buddy-list changes do not.
func (adapter *Adapter) pidginMessage(event *C.AtspiEvent) *messaging.Message {
if canonicalTextChangeEventType(C.GoString(event._type)) != TextInsertedEvent || adapter.applicationProcess(event.source) != "pidgin" ||
if canonicalObjectChangeEventType(C.GoString(event._type)) != TextInsertedEvent || adapter.applicationProcess(event.source) != "pidgin" ||
C.atspi_accessible_get_role(event.source, nil) != C.ATSPI_ROLE_TEXT {
return nil
}