Files
cthulhu/internal/atspi/libreoffice_headers_native.go

104 lines
3.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"
import "strings"
// Sheet names are unique within a Calc document. Conservatively retire choices
// for this window when sheet tabs are added, removed or renamed, so a
// deleted/recreated name cannot inherit an old sheet's choice. Switching tabs
// only changes selected state and does not retire choices.
func (adapter *Adapter) retireLibreOfficeHeaders(source *C.AtspiAccessible, eventType string, origin ObjectID) {
if eventType == WindowDestroyedEvent || eventType == DefunctStateEvent {
adapter.libreOfficeHeaders.forget(origin)
}
if (eventType != AccessibleNameEvent && !strings.HasPrefix(eventType, ChildrenChangedEvent)) ||
!adapter.libreOfficeHeaders.hasBus(origin.BusName) {
return
}
role := C.atspi_accessible_get_role(source, nil)
if eventType == AccessibleNameEvent && role == C.ATSPI_ROLE_PAGE_TAB ||
strings.HasPrefix(eventType, ChildrenChangedEvent) && role == C.ATSPI_ROLE_PAGE_TAB_LIST {
window := C.cthulhu_window_ancestor(source)
if window != nil {
adapter.libreOfficeHeaders.forget(nativeObjectID(window))
C.g_object_unref(C.gpointer(window))
}
}
}
// GLib owner only: at most two direct cell lookups, with no sheet enumeration
// or retained native references. Read fresh text on each accepted snapshot so
// edits to a heading are reflected when the user next visits a data cell.
func (adapter *Adapter) captureLibreOfficeHeaders(source *C.AtspiAccessible, snapshot *Snapshot, window ObjectID) {
key, valid := libreOfficeHeaderIdentity(*snapshot, window)
if !valid {
return
}
choice := adapter.libreOfficeHeaders.get(key)
if !choice.rowSet && !choice.columnSet {
return
}
cell := C.atspi_accessible_get_table_cell(source)
if cell == nil {
return
}
defer C.g_object_unref(C.gpointer(cell))
accessible := C.atspi_table_cell_get_table(cell, nil)
if accessible == nil {
return
}
defer C.g_object_unref(C.gpointer(accessible))
if nativeObjectID(accessible) != NewObjectID(snapshot.BusName, snapshot.TablePath) {
return
}
table := C.atspi_accessible_get_table_iface(accessible)
if table == nil {
return
}
defer C.g_object_unref(C.gpointer(table))
if choice.columnSet && choice.column != snapshot.TableColumnIndex {
snapshot.TableRowHeaders = libreOfficeHeaderText(table, snapshot.TableRowIndex, choice.column)
}
if choice.rowSet && choice.row != snapshot.TableRowIndex {
snapshot.TableColumnHeaders = libreOfficeHeaderText(table, choice.row, snapshot.TableColumnIndex)
}
}
func libreOfficeHeaderText(table *C.AtspiTable, row, column int) string {
cell := C.atspi_table_get_accessible_at(table, C.gint(row), C.gint(column), nil)
if cell == nil {
return ""
}
defer C.g_object_unref(C.gpointer(cell))
if C.atspi_accessible_get_role(cell, nil) != C.ATSPI_ROLE_TABLE_CELL {
return ""
}
C.atspi_accessible_clear_cache_single(cell)
states := C.atspi_accessible_get_state_set(cell)
if states == nil {
return ""
}
defunct := C.atspi_state_set_contains(states, C.ATSPI_STATE_DEFUNCT) != 0
C.g_object_unref(C.gpointer(states))
if defunct {
return ""
}
text := C.atspi_accessible_get_text_iface(cell)
if text == nil {
return ""
}
defer C.g_object_unref(C.gpointer(text))
length := max(0, min(int(C.atspi_text_get_character_count(text, nil)), maxSnapshotFieldLength))
content := C.atspi_text_get_text(text, 0, C.gint(length), nil)
defer C.g_free(C.gpointer(content))
return sanitizeSnapshotField(C.GoString(content))
}