97 lines
4.5 KiB
Go
97 lines
4.5 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"
|
|
|
|
// resolveNativeEventTarget runs on the GLib owner. The returned menu item 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
|
|
ownedTarget := false
|
|
if target == nil {
|
|
target = adapter.libreOfficeFocusTarget(event, eventType)
|
|
ownedTarget = target != nil
|
|
}
|
|
if target == nil {
|
|
target, ownedTarget = selectNativeEventTarget(event, eventType)
|
|
}
|
|
return target, ownedTarget
|
|
}
|
|
|
|
// Refresh only the established provider caches, in query order, before capture.
|
|
// Both accessibles are borrowed and stay on the GLib owner thread.
|
|
func refreshNativeEventTarget(event *C.AtspiEvent, target *C.AtspiAccessible, eventType string) {
|
|
if (eventType == FocusEvent || eventType == FocusedStateEvent) &&
|
|
C.atspi_accessible_get_role(target, nil) == C.ATSPI_ROLE_MENU_ITEM {
|
|
// Qt menus can change their checked state without a state-change
|
|
// event. Reopening the menu must capture the current provider state.
|
|
C.atspi_accessible_clear_cache_single(target)
|
|
}
|
|
if (eventType == SelectedStateEvent || eventType == SelectionChangedEvent) &&
|
|
C.atspi_accessible_get_role(target, nil) == C.ATSPI_ROLE_PAGE_TAB {
|
|
// Firefox can emit selected-state events from the previous tab;
|
|
// query the current state/name before using either event path.
|
|
C.atspi_accessible_clear_cache_single(target)
|
|
}
|
|
if eventType == WindowDeactivatedEvent {
|
|
// Match Python's key-grab churn check against fresh window state rather
|
|
// than an AT-SPI cache entry from before the deactivation event.
|
|
C.atspi_accessible_clear_cache_single(event.source)
|
|
}
|
|
}
|
|
|
|
// captureNativeEventContent borrows event/target and owns all payload buffers.
|
|
// A completed atomic live region replaces the event payload before copying it.
|
|
// No native references escape this helper; all calls remain on the GLib owner.
|
|
func (adapter *Adapter) captureNativeEventContent(event *C.AtspiEvent, target *C.AtspiAccessible, eventType string, originID ObjectID, live nativeLiveProperties, completeLive bool) (Snapshot, ObjectID, string) {
|
|
payload := C.cthulhu_event_payload(event)
|
|
defer func() { C.g_free(C.gpointer(payload)) }()
|
|
snapshot, popupFor := adapter.captureNativeEventSnapshot(event, target, eventType, originID, live)
|
|
if completeLive {
|
|
text := C.cthulhu_live_content(event.source)
|
|
snapshot.Text = sanitizeSnapshotField(C.GoString(text))
|
|
snapshot.LiveAtomic = true
|
|
C.g_free(C.gpointer(payload))
|
|
payload = text
|
|
}
|
|
return snapshot, popupFor, C.GoString(payload)
|
|
}
|
|
|
|
// normalizeNativeMenuFocus applies provider evidence to this event's snapshot.
|
|
// It borrows target and releases the temporary inline-menu reference locally.
|
|
func normalizeNativeMenuFocus(event *C.AtspiEvent, target *C.AtspiAccessible, eventType string, originID ObjectID, snapshot *Snapshot) (string, ObjectID) {
|
|
// Shared Qt actions can report focus through their original, hidden menu
|
|
// even while another menu emits focus-gained for that very accessible.
|
|
// The explicit event establishes focus; the queried state may disagree.
|
|
// Do not transfer this evidence to a redirected descendant or a dead item.
|
|
if snapshot.ID() == originID && !snapshot.Defunct &&
|
|
(eventType == FocusEvent || eventType == FocusedStateEvent && C.cthulhu_event_detail1(event) == 1) {
|
|
switch snapshot.Role {
|
|
case "menu item", "check menu item", "radio menu item":
|
|
snapshot.Focused = true
|
|
}
|
|
}
|
|
var inlineMenuID ObjectID
|
|
if eventType == SelectedStateEvent && C.cthulhu_event_detail1(event) == 1 && !snapshot.Defunct &&
|
|
(snapshot.Role == "button" || snapshot.Role == "push button" || snapshot.Role == "toggle button" ||
|
|
snapshot.Role == "menu item" || snapshot.Role == "check menu item" || snapshot.Role == "radio menu item") {
|
|
if menu := C.cthulhu_inline_menu_container(target); menu != nil {
|
|
inlineMenuID = nativeObjectID(menu)
|
|
C.g_object_unref(C.gpointer(menu))
|
|
if inlineMenuID.Valid() && inlineMenuID.BusName == snapshot.BusName {
|
|
// The event's detail is authoritative even when libatspi still
|
|
// caches the previous SELECTED state on the first visit.
|
|
eventType, snapshot.Selected = ActiveDescendantEvent, true
|
|
}
|
|
}
|
|
}
|
|
return eventType, inlineMenuID
|
|
}
|