Files
cthulhu/tests/review_cache_fixture.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;
}