163 lines
6.8 KiB
C
163 lines
6.8 KiB
C
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
static gboolean armed;
|
|
static unsigned childQueries;
|
|
static gboolean slowRanges;
|
|
static gint largestRange;
|
|
static GHashTable *measuredRanges;
|
|
static guint repeatedRanges;
|
|
static gboolean lineGeometryOnly;
|
|
static gboolean missingGlyph;
|
|
static GtkWidget *reviewScroller;
|
|
static AtkObject *brokenSubtree;
|
|
static gint (*originalChildCount)(AtkObject *);
|
|
static void (*originalRangeExtents)(AtkText *, gint, gint, AtkCoordType, AtkTextRectangle *);
|
|
|
|
static void range_extents(AtkText *text, gint start, gint end, AtkCoordType coordinates, AtkTextRectangle *rectangle) {
|
|
if (missingGlyph && start == 5 && end == 6) {
|
|
memset(rectangle, 0, sizeof(*rectangle));
|
|
return;
|
|
}
|
|
if (lineGeometryOnly && end - start == 1) {
|
|
memset(rectangle, 0, sizeof(*rectangle));
|
|
return;
|
|
}
|
|
if (slowRanges) {
|
|
largestRange = MAX(largestRange, end - start);
|
|
guint64 key = ((guint64)(guint32)start << 32) | (guint32)end;
|
|
if (g_hash_table_contains(measuredRanges, &key)) repeatedRanges++;
|
|
else {
|
|
guint64 *stored = g_new(guint64, 1);
|
|
*stored = key;
|
|
g_hash_table_add(measuredRanges, stored);
|
|
}
|
|
if (end - start > 128) g_usleep(1200000);
|
|
}
|
|
originalRangeExtents(text, start, end, coordinates, rectangle);
|
|
}
|
|
|
|
static gint count_children(AtkObject *object) {
|
|
(void)object;
|
|
if (armed) {
|
|
childQueries++;
|
|
// The initial cache already reports this label as a leaf. A redundant
|
|
// property query models the temporarily unresponsive live cell.
|
|
g_usleep(1200000);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static gint subtree_child_count(AtkObject *object) {
|
|
if (object == brokenSubtree && armed) return -1;
|
|
return originalChildCount(object);
|
|
}
|
|
|
|
static gboolean command_ready(GIOChannel *channel, GIOCondition condition, gpointer label) {
|
|
if (condition & G_IO_HUP) {
|
|
gtk_main_quit();
|
|
return FALSE;
|
|
}
|
|
gchar *line = NULL;
|
|
if (g_io_channel_read_line(channel, &line, NULL, NULL, NULL) != G_IO_STATUS_NORMAL)
|
|
return TRUE;
|
|
if (g_str_has_prefix(line, "arm")) {
|
|
armed = TRUE;
|
|
puts("armed");
|
|
} else if (g_str_has_prefix(line, "count")) {
|
|
printf("count %u\n", childQueries);
|
|
} else if (g_str_has_prefix(line, "measure")) {
|
|
slowRanges = TRUE;
|
|
g_hash_table_remove_all(measuredRanges);
|
|
repeatedRanges = 0;
|
|
puts("measuring");
|
|
} else if (g_str_has_prefix(line, "repeated")) {
|
|
printf("repeated %u\n", repeatedRanges);
|
|
} else if (g_str_has_prefix(line, "largest")) {
|
|
printf("largest %d\n", largestRange);
|
|
} else if (g_str_has_prefix(line, "scroll")) {
|
|
GtkAdjustment *adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(reviewScroller));
|
|
gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment) * 0.75);
|
|
while (gtk_events_pending()) gtk_main_iteration();
|
|
puts("scrolled");
|
|
} else if (g_str_has_prefix(line, "change")) {
|
|
armed = FALSE;
|
|
gtk_label_set_text(GTK_LABEL(label), "Changed review text");
|
|
puts("changed");
|
|
}
|
|
g_free(line);
|
|
fflush(stdout);
|
|
return TRUE;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
gboolean boundsMode = argc > 1 && strcmp(argv[1], "bounds") == 0;
|
|
gboolean clippedMode = argc > 1 && strcmp(argv[1], "clipped") == 0;
|
|
gboolean longMode = argc > 1 && strcmp(argv[1], "long") == 0;
|
|
gboolean linesMode = argc > 1 && strcmp(argv[1], "lines") == 0;
|
|
gboolean childrenMode = argc > 1 && strcmp(argv[1], "children") == 0;
|
|
gboolean glyphsMode = argc > 1 && strcmp(argv[1], "glyphs") == 0;
|
|
lineGeometryOnly = linesMode;
|
|
missingGlyph = glyphsMode;
|
|
gtk_init(&argc, &argv);
|
|
measuredRanges = g_hash_table_new_full(g_int64_hash, g_int64_equal, g_free, NULL);
|
|
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW(window), "Review cache probe");
|
|
gtk_window_set_default_size(GTK_WINDOW(window), 300, 100);
|
|
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
|
|
GtkWidget *label = gtk_label_new("Cached review text");
|
|
// This fixture has one label. Replace its class vfunc before the bridge
|
|
// builds the initial cache, then arm the delay explicitly over stdin.
|
|
if (!childrenMode)
|
|
ATK_OBJECT_GET_CLASS(gtk_widget_get_accessible(label))->get_n_children = count_children;
|
|
if (childrenMode) {
|
|
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
|
GtkWidget *panel = gtk_event_box_new();
|
|
brokenSubtree = gtk_widget_get_accessible(panel);
|
|
atk_object_set_name(brokenSubtree, "Broken subtree");
|
|
originalChildCount = ATK_OBJECT_GET_CLASS(brokenSubtree)->get_n_children;
|
|
ATK_OBJECT_GET_CLASS(brokenSubtree)->get_n_children = subtree_child_count;
|
|
gtk_container_add(GTK_CONTAINER(panel), gtk_label_new("Subtree text"));
|
|
gtk_container_add(GTK_CONTAINER(box), label);
|
|
gtk_container_add(GTK_CONTAINER(box), panel);
|
|
gtk_container_add(GTK_CONTAINER(window), box);
|
|
} else if (boundsMode || clippedMode || longMode || linesMode || glyphsMode) {
|
|
gchar *content = g_strnfill(linesMode ? 101000 : longMode ? 100001 : clippedMode ? 2020 : 3424, 'x');
|
|
if (longMode) content[100000] = '\n';
|
|
if (linesMode) {
|
|
for (int row = 0; row < 1000; row++) content[row * 101 + 100] = '\n';
|
|
gtk_window_set_default_size(GTK_WINDOW(window), 1000, 100);
|
|
}
|
|
if (clippedMode) {
|
|
for (int row = 0; row < 20; row++) content[row * 101 + 100] = '\n';
|
|
gtk_window_set_default_size(GTK_WINDOW(window), 300, 450);
|
|
}
|
|
gtk_label_set_text(GTK_LABEL(label), content);
|
|
g_free(content);
|
|
gtk_label_set_xalign(GTK_LABEL(label), 0);
|
|
AtkObject *accessible = gtk_widget_get_accessible(label);
|
|
atk_object_set_name(accessible, "Long review label");
|
|
originalRangeExtents = ATK_TEXT_GET_IFACE(accessible)->get_range_extents;
|
|
if (originalRangeExtents == NULL) g_error("fixture needs native GTK range extents");
|
|
ATK_TEXT_GET_IFACE(accessible)->get_range_extents = range_extents;
|
|
GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL);
|
|
reviewScroller = scroller;
|
|
gtk_container_add(GTK_CONTAINER(scroller), label);
|
|
gtk_container_add(GTK_CONTAINER(window), scroller);
|
|
} else {
|
|
gtk_container_add(GTK_CONTAINER(window), label);
|
|
}
|
|
GIOChannel *input = g_io_channel_unix_new(0);
|
|
g_io_add_watch(input, G_IO_IN | G_IO_HUP, command_ready, label);
|
|
gtk_widget_show_all(window);
|
|
gtk_main();
|
|
g_io_channel_unref(input);
|
|
g_hash_table_unref(measuredRanges);
|
|
return 0;
|
|
}
|