Files
cthulhu/internal/atspi/libreoffice_selection_native.go

136 lines
4.2 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"
// Only the native owner calls this, after foreground validation. Counts use a
// fixed number of RPCs; no selected-row/column arrays or sheet walks are used.
// LibreOffice itself scans its row marks when answering selected-row counts.
func captureLibreOfficeSelection(source *C.AtspiAccessible) *LibreOfficeSelection {
selection := C.atspi_accessible_get_selection_iface(source)
table := C.atspi_accessible_get_table_iface(source)
if selection == nil || table == nil {
if selection != nil {
C.g_object_unref(C.gpointer(selection))
}
if table != nil {
C.g_object_unref(C.gpointer(table))
}
return nil
}
defer C.g_object_unref(C.gpointer(selection))
defer C.g_object_unref(C.gpointer(table))
var queryError *C.GError
count := int(C.atspi_selection_get_n_selected_children(selection, &queryError))
if queryError != nil {
C.g_error_free(queryError)
return nil
}
if count < 0 {
return nil
}
result := &LibreOfficeSelection{Kind: "cells", Count: count, CountLimited: count == 1<<31-1}
if count == 0 {
result.Complete = true
return result
}
rows := int(C.atspi_table_get_n_rows(table, &queryError))
if queryError != nil {
C.g_error_free(queryError)
return nil
}
columns := int(C.atspi_table_get_n_columns(table, &queryError))
if queryError != nil {
C.g_error_free(queryError)
return nil
}
selectedRows, selectedColumns := 0, 0
// A small selection cannot contain a whole row or column. Avoid making
// LibreOffice scan all its row marks for ordinary Shift+Arrow movement.
if count >= columns {
selectedRows = int(C.atspi_table_get_n_selected_rows(table, &queryError))
if queryError != nil {
C.g_error_free(queryError)
return nil
}
}
if count >= rows {
selectedColumns = int(C.atspi_table_get_n_selected_columns(table, &queryError))
if queryError != nil {
C.g_error_free(queryError)
return nil
}
}
if rows <= 0 || columns <= 0 || selectedRows < 0 || selectedColumns < 0 {
return nil
}
if selectedRows == rows || selectedColumns == columns {
return &LibreOfficeSelection{Kind: "all", Complete: true}
}
stride := 1
// Only classify pure row/column selections. Mixed selections retain an
// honest cell count. GTK caps counts above INT_MAX, rather than wrapping.
if !result.CountLimited && selectedRows > 0 && int64(selectedRows)*int64(columns) == int64(count) {
result.Kind, result.Count, result.CountLimited = "rows", selectedRows, false
stride = columns
} else if !result.CountLimited && selectedColumns > 0 && int64(selectedColumns)*int64(rows) == int64(count) {
result.Kind, result.Count, result.CountLimited = "columns", selectedColumns, false
}
indices := make([]int, 0, libreOfficeSelectionLimit)
if result.Count > libreOfficeSelectionLimit {
indices = append(indices, 0, count-1)
} else {
for index := 0; index < result.Count; index++ {
indices = append(indices, index*stride)
}
}
for _, index := range indices {
child := C.atspi_selection_get_selected_child(selection, C.gint(index), &queryError)
if queryError != nil {
C.g_error_free(queryError)
if child != nil {
C.g_object_unref(C.gpointer(child))
}
return nil
}
if child == nil {
return nil
}
cell := C.atspi_accessible_get_table_cell(child)
var row, column C.gint
valid := false
if cell != nil {
valid = C.atspi_table_cell_get_position(cell, &row, &column, &queryError) != 0
C.g_object_unref(C.gpointer(cell))
}
C.g_object_unref(C.gpointer(child))
if queryError != nil {
C.g_error_free(queryError)
return nil
}
if !valid || row < 0 || column < 0 || int(row) >= rows || int(column) >= columns {
return nil
}
position := LibreOfficeCell{Row: int(row), Column: int(column)}
if result.Kind == "rows" {
position.Column = 0
}
if result.Kind == "columns" {
position.Row = 0
}
result.Cells = append(result.Cells, position)
}
result.First, result.Last = result.Cells[0], result.Cells[len(result.Cells)-1]
result.Complete = result.Count <= libreOfficeSelectionLimit
if !result.Complete {
result.Cells = nil
}
return result
}