// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include #include #include 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; }