A Gtk4 binding for GNU Modula.
|
|
3 天之前 | |
|---|---|---|
| examples | 3 天之前 | |
| lib | 3 天之前 | |
| src | 3 天之前 | |
| tests | 3 天之前 | |
| tools | 3 天之前 | |
| .gitignore | 3 天之前 | |
| Makefile | 3 天之前 | |
| README.md | 3 天之前 |
Thin DEFINITION MODULE FOR "C" bindings of GTK 4 for gm2
(GCC 16 / GNU Modula-2), plus a small pure-Modula-2 helper layer.
The first slice covers the canonical GTK4 application shape
(GtkApplication + GtkWindow + GtkLabel / GtkButton / GtkBox),
a working "activate" / "clicked" signal path, and the GLib/GObject
runtime underneath it. The second slice adds the common input widgets:
GtkEntry/GtkEditable, GtkCheckButton, GtkSwitch, GtkScale
(GtkRange), plus a shared GtkEnums module. The third slice adds the
layout containers: GtkGrid, GtkStack + GtkStackSwitcher,
GtkNotebook, GtkScrolledWindow. The fourth slice adds actions,
menus and accelerators: GIO's GAction / GActionMap /
GSimpleAction, GMenu / GMenuModel / GVariant, plus
GtkApplicationWindow and GtkPopoverMenuBar. The fifth slice switches
the project to the ISO dialect (-fiso) and adds per-connection signal
closures (GtkClosures) and window chrome (GtkHeaderBar,
GtkMenuButton, gtk_window_set_titlebar). The sixth slice adds more
inputs: GtkSpinButton, GtkSearchEntry, GtkDropDown (with
GtkStringList / GListModel) and GtkTextView + GtkTextBuffer.
The seventh slice adds more containers: GtkFrame, GtkExpander,
GtkPaned, GtkOverlay, GtkListBox (+ GtkListBoxRow). The eighth
slice adds GIO extras: GListStore (+ GtkStringObject), GFile,
GtkFileDialog and GSettings (+ GSettingsSchema). The ninth slice
adds model-backed views: GtkListView, GtkGridView, GtkColumnView
(+ GtkColumnViewColumn / GtkColumnViewCell), the selection models
GtkSingleSelection / GtkNoSelection, and the
GtkSignalListItemFactory / GtkListItem pair. A
tools/gir2def.py generator additionally
emits broad .def coverage from GObject-Introspection .gir XML.
A tenth slice adds libadwaita (AdwApplication, header/toolbar,
preferences rows, toast, about/message dialogs) and GError handling
plus an ownership helper layer.
Verified with gm2 16.0.1 (experimental) and GTK 4.18.6 on Linux.
| Module | C header | Contents |
|---|---|---|
GLib |
<glib.h> |
base C types, GMainLoop, timeouts/idle sources, memory, strings, clock |
GObject |
<glib-object.h> |
ref/unref, GType, g_object_new, g_signal_connect_data, handler disconnect, properties |
Gio |
<gio/gapplication.h> |
GApplication flags, run/quit/hold/release |
Gtk |
<gtk/gtk.h> |
gtk_init, library version helpers |
GtkApplication |
<gtk/gtkapplication.h> |
create/add/remove windows |
GtkWidget |
<gtk/gtkwidget.h> |
visibility, sensitivity, margins, expand, align, size |
GtkWindow |
<gtk/gtkwindow.h> |
create, title, size, child, present/close/destroy |
GtkBox |
<gtk/gtkbox.h> |
orientation, append/prepend/remove, spacing |
GtkLabel |
<gtk/gtklabel.h> |
text, markup |
GtkButton |
<gtk/gtkbutton.h> |
label buttons |
GtkEnums |
<gtk/gtkenums.h> |
orientation, alignment, position constants |
GtkEditable |
<gtk/gtkeditable.h> |
text get/set, editable, alignment, selection |
GtkEntry |
<gtk/gtkentry.h> |
placeholder, max length, visibility, progress |
GtkCheckButton |
<gtk/gtkcheckbutton.h> |
active, label, inconsistent |
GtkSwitch |
<gtk/gtkswitch.h> |
active / state |
GtkRange |
<gtk/gtkrange.h> |
numeric value (base of GtkScale) |
GtkScale |
<gtk/gtkscale.h> |
slider, digits, value position |
GtkGrid |
<gtk/gtkgrid.h> |
cell attach, spacing, homogeneous |
GtkStack |
<gtk/gtkstack.h> |
named/titled pages, visible child, transitions |
GtkStackSwitcher |
<gtk/gtkstackswitcher.h> |
button row that drives a stack |
GtkNotebook |
<gtk/gtknotebook.h> |
tabbed pages |
GtkScrolledWindow |
<gtk/gtkscrolledwindow.h> |
scrollable viewport, policy |
GtkApplicationWindow |
<gtk/gtkapplicationwindow.h> |
app window, window-scoped actions, show-menubar |
GtkPopoverMenuBar |
<gtk/gtkpopovermenubar.h> |
in-window menu bar from a model |
GtkHeaderBar |
<gtk/gtkheaderbar.h> |
title widget, pack start/end, title buttons, decoration layout |
GtkMenuButton |
<gtk/gtkmenubutton.h> |
button that pops up a menu model |
GAction |
<gio/gaction.h> |
activate, name, enabled, state |
GActionMap |
<gio/gactionmap.h> |
add / lookup / remove named actions |
GSimpleAction |
<gio/gsimpleaction.h> |
parameterless and stateful actions |
GMenu |
<gio/gmenu.h> |
GMenu + GMenuItem builder |
GMenuModel |
<gio/gmenumodel.h> |
model item count |
GVariant |
<glib/gvariant.h> |
boolean / string / int32 values |
GtkSpinButton |
<gtk/gtkspinbutton.h> |
numeric entry, range/increments/digits/wrap |
GtkSearchEntry |
<gtk/gtksearchentry.h> |
search-styled entry, placeholder |
GListModel |
<gio/glistmodel.h> |
indexed model interface |
GtkStringList |
<gtk/gtkstringlist.h> |
simple string model |
GtkDropDown |
<gtk/gtkdropdown.h> |
choose one item from a model |
GtkTextIter |
<gtk/gtktextiter.h> |
opaque 80-byte iterator (by address) |
GtkTextBuffer |
<gtk/gtktextbuffer.h> |
text model for a text view |
GtkTextView |
<gtk/gtktextview.h> |
multi-line text widget |
GtkFrame |
<gtk/gtkframe.h> |
labelled border around one child |
GtkExpander |
<gtk/gtkexpander.h> |
collapsible section |
GtkPaned |
<gtk/gtkpaned.h> |
two panes with a draggable divider |
GtkOverlay |
<gtk/gtkoverlay.h> |
stack overlays on a main child |
GtkListBox |
<gtk/gtklistbox.h> |
vertical row list + GtkListBoxRow |
GListStore |
<gio/gliststore.h> |
in-memory list model |
GtkStringObject |
<gtk/gtkstringobject.h> |
GObject string wrapper (item type) |
GtkSignalListItemFactory |
<gtk/gtksignallistitemfactory.h> |
setup/bind/teardown item factory |
GtkListItem |
<gtk/gtklistitem.h> |
per-row object passed to a factory |
GtkSelectionModel |
<gtk/gtkselectionmodel.h> |
selection interface for a view |
GtkSingleSelection |
<gtk/gtksingleselection.h> |
zero/one selected item |
GtkNoSelection |
<gtk/gtknoselection.h> |
display-only (no selection) |
GtkListView |
<gtk/gtklistview.h> |
model-driven list |
GtkGridView |
<gtk/gtkgridview.h> |
model-driven wrapping grid |
GtkColumnView |
<gtk/gtkcolumnview.h> |
model-driven multi-column table |
GtkColumnViewColumn |
<gtk/gtkcolumnviewcolumn.h> |
one column |
GtkColumnViewCell |
<gtk/gtkcolumnviewcell.h> |
column cell (GtkListItem subclass) |
GFile |
<gio/gfile.h> |
path/URI file handle |
GtkFileDialog |
<gtk/gtkfiledialog.h> |
async native open/save/folder dialog |
GSettings |
<gio/gsettings.h> |
typed schema keys |
GSettingsSchema |
<gio/gsettingsschema.h> |
schema lookup / key check |
GError |
<glib/gerror.h> |
error value (message/domain/code) |
GioError |
<gio/gioerror.h> |
GIO error domain and codes |
AdwApplication |
<libadwaita-1/adw-application.h> |
Adwaita application |
AdwApplicationWindow |
<libadwaita-1/adw-application-window.h> |
Adwaita window |
AdwHeaderBar |
<libadwaita-1/adw-header-bar.h> |
header bar |
AdwToolbarView |
<libadwaita-1/adw-toolbar-view.h> |
top/bottom bars + content |
AdwPreferencesPage |
<libadwaita-1/adw-preferences-page.h> |
page of groups |
AdwPreferencesGroup |
<libadwaita-1/adw-preferences-group.h> |
titled group of rows |
AdwPreferencesRow |
<libadwaita-1/adw-preferences-row.h> |
base row (title) |
AdwActionRow |
<libadwaita-1/adw-action-row.h> |
row with subtitle/prefix/suffix |
AdwSwitchRow |
<libadwaita-1/adw-switch-row.h> |
row with a switch |
AdwEntryRow |
<libadwaita-1/adw-entry-row.h> |
row with an entry |
AdwComboRow |
<libadwaita-1/adw-combo-row.h> |
row with a drop-down |
AdwExpanderRow |
<libadwaita-1/adw-expander-row.h> |
row that expands |
AdwStatusPage |
<libadwaita-1/adw-status-page.h> |
placeholder page |
AdwToast |
<libadwaita-1/adw-toast.h> |
transient notification |
AdwToastOverlay |
<libadwaita-1/adw-toast-overlay.h> |
toast host |
AdwBanner |
<libadwaita-1/adw-banner.h> |
dismissible info bar |
AdwClamp |
<libadwaita-1/adw-clamp.h> |
max-size centred container |
AdwAboutWindow |
<libadwaita-1/adw-about-window.h> |
about dialog |
AdwMessageDialog |
<libadwaita-1/adw-message-dialog.h> |
message / confirm dialog |
GtkUtils (lib/) |
— | CStrToM2, StrAppend, IntToStr, Connect, SetAccel, Unref, Free, ErrorMessage, ClearError |
GtkClosures (lib/) |
— | per-connection signal contexts (Connect2/Connect3, owned variants) |
m2-GTK4/
src/ FOR "C" .def bindings (one per GTK/GIO object)
lib/ pure-M2 helpers built on the bindings (GtkUtils, GtkClosures)
tests/ gm2 smoke tests + run_tests.sh (+ a private GSettings schema)
examples/ runnable GTK4 programs (hello, counter, ...)
tools/ gir2def.py (GIR -> .def generator) + run_gir_check.sh
gen/ generator output (git-ignored)
build/ generated binaries (git-ignored)
gm2 (tested: 16.0.1 experimental, ~/bin/Modula2/Gm2/bin/gm2)pkg-config --cflags --libs gtk4, tested: 4.18.6)pkg-config --cflags --libs libadwaita-1, tested:
1.7.6) — optional; enables the Adw* modules, test_adw and the
adw example. The build detects them automatically.The build uses the ISO dialect (-fiso). That is required for
SYSTEM.CAST and for NEW/DISPOSE (which need ALLOCATE/DEALLOCATE
from the ISO Storage module), both used by GtkClosures. make
passes -fiso by default.
export PATH=$HOME/bin/Modula2/Gm2/bin:$PATH
make # tests + examples -> build/
./tests/run_tests.sh
./build/examples/hello
./build/examples/counter
./build/examples/inputs
./build/examples/containers
./build/examples/actions
./build/examples/headerbar
./build/examples/editor
./build/examples/layouts
./build/examples/files
./build/examples/views
./build/examples/adw
make check is make tests + the runner script. The GUI tests are
skipped automatically when neither DISPLAY nor WAYLAND_DISPLAY is
set. The runner also compiles tests/schemas/*.gschema.xml into
build/schemas and points GSETTINGS_SCHEMA_DIR at it, so test_gio
can exercise GSettings without touching real user settings.
The hand-written modules in src/ are the idiomatic API. For breadth,
tools/gir2def.py generates FOR "C" .def files directly from
GObject-Introspection .gir XML:
make gir # Gtk-4.0 -> gen/ (301 modules, ~3.7k procedures)
make gir GIR=Gio-2.0 # any namespace
make gir-check # generate + compile/link a sample
It emits one module per class/interface/record (named <Namespace><Name>,
e.g. GtkButton), namespace functions in <Namespace>Functions.def, and
enums/bitfields in <Namespace>Enums.def. Procedure names are the exact
GIR c:identifier, so the output links straight against the C library.
All 633 generated modules across Gtk/GLib/Gio/Adw parse under gm2 -fiso.
make gir-verify closes the loop by checking that every generated
identifier actually exists as an exported symbol in the installed
library (via nm); it reports 0 unexpected across Gtk, GLib, Gio and
Adw (a short allowlist covers macros/deprecated no-ops).
See tools/README.md for the type mapping and the known limits (value
structs are opaque, signals/properties are not generated, variadics and
GError** are dropped). Output goes to gen/ and is meant to be
cherry-picked, not to replace src/.
MODULE hello ;
FROM Gio IMPORT g_application_run;
FROM GtkApplication IMPORT gtk_application_new;
FROM GtkWindow IMPORT gtk_application_window_new, gtk_window_set_title,
gtk_window_set_default_size, gtk_window_set_child, gtk_window_present;
FROM GtkLabel IMPORT gtk_label_new;
FROM GtkUtils IMPORT Connect;
FROM SYSTEM IMPORT ADDRESS, ADR;
PROCEDURE OnActivate (application: ADDRESS; data: ADDRESS);
VAR window, label: ADDRESS;
BEGIN
window := gtk_application_window_new(application);
gtk_window_set_title(window, "Hello");
gtk_window_set_default_size(window, 360, 120);
label := gtk_label_new("Hello, GNU Modula-2 + GTK4!");
gtk_window_set_child(window, label);
gtk_window_present(window)
END OnActivate;
VAR app: ADDRESS;
BEGIN
app := gtk_application_new("org.example.Hello", 0);
Connect(app, "activate", ADR(OnActivate), NIL);
g_application_run(app, 0, NIL)
END hello.
Compile a program against the bindings:
gm2 -fiso -Isrc -Ilib myprog.mod build/objs/GtkUtils.o \
build/objs/GtkClosures.o -o myprog \
$(pkg-config --cflags --libs gtk4)
Step 0 — feasibility spike. Before writing anything, the riskiest
interop points were proved against the real library: creating a GTK4
window from Modula-2, running the GLib main loop, and invoking a
Modula-2 procedure from C as a GSourceFunc. This fixed the design.
Step 1 — discover the GNU Modula-2 ↔ C rules. See the next section.
These rules decide every line of a binding .def.
Step 2 — GLib and GObject. Everything else sits on the base types, the main loop and the signal machinery, so these came first.
Step 3 — application and window. GtkApplication + GApplication
drive the loop; GtkWindow gives something to show. Using
gtk_application_window_new(app) (not gtk_window_new()) is what
keeps the application alive while the window is open.
Step 4 — widgets and layout. GtkBox, GtkLabel, GtkButton, and
the per-widget GtkWidget layout helpers.
Step 5 — helpers and signal ergonomics. GtkUtils.Connect wraps the
g_signal_connect macro (which has no C symbol) by calling the real
g_signal_connect_data; CStrToM2 and IntToStr handle the
string/number conversions GTK code always needs.
Step 6 — tests and examples. A headless GLib loop test, a
version/link test, a widget + signal round-trip test, and an
application test; plus hello and counter examples.
Step 7 — input widgets. GtkEnums was extracted as the shared home
for enum constants, then GtkEditable/GtkEntry,
GtkCheckButton, GtkSwitch, GtkRange/GtkScale were added,
with a test_inputs round-trip test and the inputs example. This
step also pinned down the real-number mapping (REAL = C double).
Step 8 — layout containers. GtkGrid, GtkStack +
GtkStackSwitcher, GtkNotebook and GtkScrolledWindow, with a
test_containers test and the containers example (a notebook whose
pages are a grid, a switcher-driven stack, and a scrolled list).
Step 9 — actions, menus, accelerators. GIO's action/menu stack
(GAction, GActionMap, GSimpleAction, GMenu, GMenuModel,
GVariant), GtkApplicationWindow, and GtkPopoverMenuBar, wired
together in the actions example (menu bar + app/win actions +
Ctrl+N/Q/B accelerators via GtkUtils.SetAccel) and checked by
test_actions.
Step 10 — ISO dialect and per-connection closures. The project was
switched to -fiso, which provides SYSTEM.CAST and intrinsic
NEW/DISPOSE (with Storage's ALLOCATE/DEALLOCATE). That made
the GtkClosures module possible: each signal connection gets its own
heap context holding the handler and payload, a trampoline forwards the
call, and a GClosure destroy notify frees the context - and optionally
the payload - on disconnect or finalize. Covered by test_closure.
Step 11 — header bar and window controls. GtkHeaderBar,
GtkMenuButton, and gtk_window_set_titlebar() were added, then
combined in the headerbar example (title widget, a connected "New"
button packed start, a menu button packed end) and checked by
test_headerbar.
Step 12 — more inputs. GtkSpinButton, GtkSearchEntry,
GtkStringList + GListModel, GtkDropDown, and GtkTextView +
GtkTextBuffer with an opaque 80-byte GtkTextIter marshalled by
address. Checked by test_inputs2 and shown in the editor example
(search entry, text view, wrap-mode drop-down, padding spin button).
Step 13 — more containers. GtkFrame, GtkExpander, GtkPaned,
GtkOverlay, and GtkListBox + GtkListBoxRow (with
GtkSelectionMode in GtkEnums). Checked by test_containers2 and
shown in the layouts example (a paned split, frame/expander on the
left, overlay + list box on the right, row-activated via
GtkClosures.Connect3).
Step 14 — GIO extras. GListStore + GtkStringObject (a concrete
list model), GFile, the asynchronous GtkFileDialog, and GSettings
GSettingsSchema. Checked by test_gio (headless) and
test_filedialog; shown in the files example (button -> native open
dialog -> open_finish -> GFile basename). The GSettings test uses a
private schema compiled by the runner.Step 15 — model-backed views. GtkSignalListItemFactory +
GtkListItem, the selection models GtkSingleSelection /
GtkNoSelection, and the views GtkListView, GtkGridView,
GtkColumnView (+ GtkColumnViewColumn, GtkColumnViewCell). Checked
by test_views, which presents a window and asserts (from the main
loop) that the factory "setup"/"bind" callbacks ran, and shown in the
views example (one store feeding list, grid and column views).
Step 16 — generator verification. tools/gir_verify.py collects every
c:identifier that gir2def.py would emit and checks it against the
exported symbols of the libraries pkg-config links for the namespace
(symbol-version suffixes stripped). make gir-verify reports 0
unexpected across Gtk/GLib/Gio/Adw, with a small allowlist for GIR-listed
macros and per-module ABI functions. tools/run_gir_check.sh also
bulk-parses every generated module.
Step 17 — libadwaita. Curated Adw* modules (application, window,
header bar, toolbar view, preferences page/group and the action/switch/
entry/combo/expander rows, status page, toast/overlay, banner, clamp,
about and message dialogs), checked by test_adw and shown in the adw
example. libadwaita is optional in the build.
Step 18 — GError and ownership. GError (with its public struct
layout) and GioError (domain + codes), plus GtkUtils.Unref/Free/
ErrorMessage/ClearError and a documented ownership discipline.
Checked by test_gerror (a real GIO_ERROR_NOT_FOUND from
g_file_delete).
DEFINITION MODULE FOR "C" Name ; and list every
export in EXPORT UNQUALIFIED. Without EXPORT UNQUALIFIED the
compiler prefixes symbols with Module_, and the link fails.GSourceFunc = PROCEDURE (ADDRESS) : INTEGER; (named parameters are
a syntax error in gm2's default dialect).: [ T ]
(e.g. PROCEDURE g_timeout_add (...) : [ guint ];). Without the
brackets, ignoring the result is a hard error. This also works in
ordinary modules — GtkUtils.Connect uses it.ARRAY OF CHAR maps onto char *, so string literals and CHAR
arrays can be passed straight to const char * parameters.VAR record/string parameters map onto T *; opaque handles
(GObject *, GtkWidget *, ...) travel as ADDRESS (use NIL to
test for NULL).BOOLEAN is not gboolean: pass INTEGER with GLib.gTrue /
GLib.gFalse, or use INTEGER directly.ADR(proc) where
the binding takes an ADDRESS/GCallback.REAL is 8 bytes and matches C double;
gm2 SHORTREAL is 4 bytes and matches C float; gm2 LONGREAL is
16 bytes and matches nothing in GTK. Use REAL for gdouble /
double (e.g. GtkRange values, GtkScale ranges).*) and (* inside comments — they close/open a comment
early. Write void ptr, not void *, in prose.T(x) cast requires equal
sizes; CStrToM2 therefore copies with libc strncpy instead of
walking a char *.GVariant values from g_variant_new_* are floating: ownership
transfers to the callee (e.g. g_simple_action_new_stateful).
Values returned by g_action_get_state() / g_variant_get_* own a
reference and must be released with g_variant_unref().-fiso: CAST(T, expr) (ISO
SYSTEM) and NEW/DISPOSE (with Storage's
ALLOCATE/DEALLOCATE) are available there but not in PIM.Connect2(b, "clicked", OnClick, ...)), not ADR(OnClick)
— gm2 type-checks it. Use ADR(proc) only where the parameter is a
plain ADDRESS / GCallback.GtkTextIter) can be modelled as a record of ADDRESS words sized
to sizeof it: that gives the right size and 64-bit alignment.
GtkTextIter is 80 bytes, hence ARRAY [0..9] OF ADDRESS.The original roadmap is complete: GLib/GObject core, widgets, inputs,
containers, actions/menus/accelerators, header bars, GIO extras,
model-backed views, and the GIR generator are all in. On top of that:
generator verification (make gir-verify), curated libadwaita, and
GError + ownership helpers.
Possible next steps:
GtkCssProvider styling).ADDRESS placeholders into cross-module type
aliases and [ ]/VAR refinements using the GIR annotations.gen/ output into src/ as it proves out.