Migrate lib/pcl off the curated gtk3_c.h
Filed 2026-08-29 by frankC (Track C) out of
[[feature-c-gtk3-header-final-wiring]]. The compiler side is proven: stock GTK3
headers import cleanly, resolve to libgtk-3.so.0 and run a real window — gated
by test/test_c_gtk3_stock.pas, whose forwarding header is literally
#include <gtk/gtk.h>. What remains is a library job in Track B's files,
which is why it is not in the Track C ticket.
Why bother
lib/pcl/gtk3_c.h is 135 hand-written prototypes with deliberately simplified
types (void* for every object pointer, char* for every string) plus an
invented typedef void* PGtkWidget. Every one of them is an independent guess
that happens to be call-compatible. test/test_c_gtk_window.pas already carries
the scar in a comment: PGtkWidget was never declared — it was silently a 4-byte int, TRUNCATING the pointer. Stock headers make the compiler check what is
currently trusted.
The blast radius is smaller than it looks — measured, not estimated
All 135 curated functions exist in the stock GTK3/cairo headers. Verified by
compiling an address-taking probe over every name against
pkg-config --cflags gtk+-3.0: gcc exit 0, zero undeclared. So nothing has to be
kept behind.
The real blockers are two, and both are small:
-
Three functions are variadic in the stock headers where the curated file declared a fixed arity. Found with
gcc -aux-infoover all 14,453 emitted prototypes, not by reading:g_signal_emit_by_namegtk_file_chooser_dialog_newgtk_message_dialog_new
These are the actual failure a naive swap produces first (
no overload of gtk_message_dialog_new matches these arguments). The curated header's fixed-arity forms are a deliberate simplification — see its own comment ongtk_file_chooser_dialog_newabout passing a NULL terminator. -
PGtkWidgetdoes not exist in stock GTK3 (it isGtkWidget*). Used in 4 files, all tests —test/test_c_gtk_window.pas,test/test_c_gtk_types.pas,test/gui/test_gtk_window.pas,test/gui/test_gtk_signals.pas. Zero uses inlib/pclorexamples.
Constants were checked too and are all correct against the real enums
(GTK_BUTTONS_*, GTK_DIALOG_*, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
GTK_POLICY_*, CAIRO_FORMAT_*) — nothing to fix there.
Consumers to keep green
lib/pcl/gtk3.pas, gtk3widgets.pas, gtk3gl.pas, graphics.pas,
glarea.pas, interfaces.pas; 11 test/gui/test_*.pas; and
examples/{mandelbrot,life,solitaire_gui,raytracer} + examples/gl/triangle.pas.
Note lib/pcl/gtk3.pas's PC() hands GTK a raw Pointer where the stock
headers want const gchar*; whether that still type-checks is the first thing to
try, and is not covered by the two blockers above.
Suggested shape
Replace the body of lib/pcl/gtk3_c.h with #include <gtk/gtk.h> (keeping the
file, so the gtk3_c unit name and its gtk-3 stem→soname mapping are
untouched), then fix the 3 variadic call sites and the 4 PGtkWidget test uses.
Build with $(PXX_STABLE) per Track B's gate; make lib-test / demos.
Depends on
[[decide-which-gtk-a-bare-gtk-gtk-h-means]] — until that is settled, every
consumer needs an explicit -I/usr/include/gtk-3.0/, because gtk-2.0 is a
default system include root and gtk-3.0 is not. Doing this migration first would
mean threading that flag through the PCL build and every example.
DONE (2026-08-29, frankR)
lib/pcl/gtk3_c.h is now #include <gtk/gtk.h> against the installed headers.
135 hand-written prototypes with void*-for-everything types are gone, and the
whole PCL widget stack, the GUI tests and the GUI examples build against the real
declarations and link libgtk-3.so.0.
What the swap actually cost
Less than the ticket estimated, and in a different place.
gtk_file_chooser_dialog_new was never a blocker. It is variadic in the
stock headers, as frankC found, but pxx imports its four fixed parameters
(title, parent, action, first_button_text) and SelectFolder already passes
exactly four. It compiled unchanged.
gtk_message_dialog_new was the only library blocker, and its rewrite is the
one behaviour-affecting change in this migration:
{ was } gtk_message_dialog_new(nil, MODAL, INFO, OK, PC('%s'), PC(AText))
{ now } dlg := gtk_message_dialog_new(nil, MODAL, INFO, OK, nil);
esc := g_markup_escape_text(PC(AText), -1);
gtk_message_dialog_set_markup(dlg, esc);
g_free(esc);
The escape is load-bearing and is the trap in this rewrite. "%s" set the
dialog's text, which is literal; set_markup INTERPRETS Pango markup. Without
g_markup_escape_text a message containing & or < renders wrong or vanishes
— a silent behaviour change that no existing test would have caught, because
test/gui/test_pcl_showmessage.pas is not in the suite and was already failing
at baseline (Runtime error 216, identical before and after this change).
Verified instead by a direct probe of the exact sequence against real GTK3:
a<b&c → a<b&c, markup set, freed, dialog destroyed.
lib/pcl/gl_c.h had to be cleaned up too, which the ticket did not
anticipate. It re-declared eight GTK functions (gtk_gl_area_*,
gtk_widget_get_allocated_width/height) that the stock headers now provide, and
examples/gl/triangle.pas broke on the collision. They are GTK, not GL — the
comment above them already said "linked via libgtk-3.so.0" — so they were
removed and gtk3gl.pas now uses gtk3_c for them. Leaving them would have kept
one arm of exactly the hazard this ticket exists to remove: two headers declaring
the same eight functions with different types.
PGtkWidget was two files, not four. test/test_c_gtk_window.pas and
test/test_c_gtk_types.pas already used Pointer (they carry the scar comment).
The two that still declared it — test/gui/test_gtk_window.pas and
test/gui/test_gtk_signals.pas — are converted. Note both are orphans:
neither is referenced by tools/gui_suite.sh or the Makefile, so nothing runs
them. Worth a separate look.
PC()'s raw Pointer where the stock headers want const gchar* type-checks
fine — the ticket's "first thing to try" is a non-issue.
The include root, and how this stays off the Track U fork
GTK3_INC, one definition in the Makefile and one in tools/gui_suite.sh,
pkg-config --cflags-only-I gtk+-3.0 with the literal path as fallback. Nothing
depends on [[decide-which-gtk-a-bare-gtk-gtk-h-means]] being settled, and the
variable is what deletes itself if gtk-3.0 ever becomes a default include root.
A consumer that forgets the flag gets a compile ERROR, not a silent GTK2
build — measured: undefined variable (gdk_event_get_button). State that
precisely, though: it is loud because PCL happens to call a GTK3-only function,
which is a property of today's PCL surface and not a designed guard. A PCL
consumer whose own surface is entirely GTK2-compatible would build silently
against the wrong library.
tools/gui_suite.sh now opens with gtk_version_check, which asserts
libgtk-3.so.0 is in DT_NEEDED and that libgtk-x11-2.0.so.0 is not. Per
frankC's rule: gtk_main, gtk_main_quit and most of the surface PCL uses exist
in both GTK2 and GTK3, so a suite that only compiles and runs would pass just as
happily against the wrong library.
GTK2 stays authoritative: test_c_gtk* use uses gtk (a different unit that
resolves through the default GTK2 root) and are untouched by this change.
What is left, and why it is left
test/gui/test_pcl_input.pas no longer compiles, and is deliberately not
fixed. Its three g_signal_emit_by_name(handle, signal, event, @handled) calls
need the variadic tail that pxx's C import drops
([[bug-a-a-c-headers-variadic-tail-is-dropped-on-import]], filed with this work —
C mode itself calls varargs correctly, so only the import discards it). The
obvious reroute, gtk_widget_event / gtk_widget_size_allocate, is both the
compiler-appeasement workaround the platonic-code rule forbids AND worse on its
merits: the comment already in that file records that those impose a
realized-window assertion the test cannot satisfy headlessly. The platonic call
stays; the file carries a comment pointing at the ticket.
Suite delta vs baseline, measured on the same box, same pin: gtk version is a
new OK, test_pcl_input is a new FAIL, and the two pre-existing failures
(solitaire_gui real toplevel, eliah_ide compile) are unchanged. Nothing else
moved.