# m2-GTK4 GNU Modula-2 bindings for **GTK4** and **libadwaita**, for `gm2` (GCC 16 / GNU Modula-2) on Linux. `m2-GTK4` is a hand-written set of `DEFINITION MODULE FOR "C"` bindings — one module per GTK/GIO/libadwaita class — plus a small pure-Modula-2 helper layer, a test suite, runnable examples, and a generator that can emit broad binding coverage from GObject-Introspection `.gir` XML. It is compiled in the **ISO dialect** (`-fiso`). Verified with `gm2` 16.0.1 (experimental), GTK 4.18.6 and libadwaita 1.7.6. ## Features - **414 binding modules** across GLib, GObject, Gio, GTK4, Gdk, Gsk, libadwaita, GtkSourceView 5, libspelling and libpanel — windows, widgets, inputs, containers, model-backed views, actions/menus/accelerators, dialogs, CSS, drawing (Cairo/Pango), rich text, gestures, drag-and-drop, media, OpenGL, printing, expressions/constraints/shortcuts, settings, the **complete GNOME (libadwaita) widget set** — every `Adw*` class — the **GtkSourceView 5** code-editor widgets, **full Gdk/Gsk coverage**, and the libspelling / libpanel platform libraries. All of these are hand-written modules; the generator output (kept in `gen/`, git-ignored) is the broad-coverage companion. - **Pure-Modula-2 helpers**: `GtkUtils` (string/number conversion, `Connect`, `SetAccel`, ownership and `GError` helpers) and `GtkClosures` (per-connection signal contexts with cleanup). - **45 tests**, **30 runnable examples**, and an **m2 gtk-demo** browser (`demos/`) modelled on GTK's own `gtk4-demo`. - **Generator + verifier**: `tools/gir2def.py` turns a `.gir` file into `.def` files; `tools/gir_verify.py` checks every generated identifier against the installed library. Verified clean across 10256 identifiers. ## Requirements - `gm2` (tested: 16.0.1 experimental) - GTK4 development files (`pkg-config --cflags --libs gtk4`, tested 4.18.6) - libadwaita development files (`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. - GtkSourceView 5 development files (`pkg-config --cflags --libs gtksourceview-5`, tested 5.16.0) — **optional**; enables the `GtkSource*` modules, `test_sourceview` and the `sourceview` example. - libspelling and libpanel development files (`libspelling-1`, tested 0.4.8; `libpanel-1`, tested 1.10.0) — **optional**; enable the `Spelling*`/`Panel*` modules, `test_platform` and the `spelling` example. ```sh export PATH=$HOME/bin/Modula2/Gm2/bin:$PATH ``` ## Build and test ```sh make # build all tests, examples and the demo browser into build/ ./tests/run_tests.sh # run the test suite ``` `make check` is `make tests` plus the runner script. GUI tests are skipped automatically when neither `DISPLAY` nor `WAYLAND_DISPLAY` is set. The runner also compiles a private GSettings schema into `build/schemas` so `test_gio` can exercise `GSettings` without touching real user settings. Run an example: ```sh ./build/examples/hello ./build/examples/adw ``` ## Quick start ```modula2 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 (the helper objects must be built first): ```sh gm2 -fiso -Isrc -Ilib myprog.mod \ build/objs/GtkUtils.o build/objs/GtkClosures.o -o myprog \ $(pkg-config --cflags --libs gtk4) ``` ## Examples | Example | Shows | |---|---| | `hello` | the canonical application + window + label | | `counter` | button signal updating a label | | `inputs` | entry, check button, switch, scale | | `containers` | grid, stack/switcher, notebook, scrolled window | | `actions` | menu bar + app/window actions + accelerators | | `headerbar` | header bar, menu button, connected button | | `editor` | search entry, text view, drop-down, spin button | | `layouts` | frame, expander, paned, overlay, list box | | `files` | native file dialog → `GFile` | | `views` | `GtkListView` / `GtkGridView` / `GtkColumnView` from a store | | `builder` | load a window from a `.ui` file with `GtkBuilder` | | `feedback` | progress bar, spinner, separator, image | | `styling` | CSS provider and per-widget CSS classes | | `popover` | popover and menu popover | | `dialogs` | `GtkAlertDialog` and `GtkColorDialog` | | `drawing` | `GtkDrawingArea` + Cairo + Pango | | `demo` | searchable demo browser (filter model + stack of pages) | | `gestures` | click/drag gestures on a drawing area | | `customwidget` | custom `GtkWidget` and `GdkPaintable` via the C shim | | `customlayout` | custom `GtkLayoutManager` via the C shim | | `gl` | OpenGL clearing inside a `GtkGLArea` | | `richtext` | Pango markup, marks and entry completion in a text view | | `snapshot` | custom `GdkPaintable` drawn with `GtkSnapshot` + Graphene | | `media` | `GdkTexture` in a `GtkPicture`, plus `GtkVideo` | | `clipboard` | copy/paste text through `GdkClipboard` | | `adw` | libadwaita preferences app (toolbar view, rows, toast, about) | | `adw_widgets` | view-switcher title, view stack, banner, squeezer, wrap box, avatar, spinner | | `adw_views` | tabbed window: `AdwTabView`/`AdwTabBar`/`AdwTabButton`/`AdwTabOverview` | | `sourceview` | GtkSourceView 5 code editor (highlighting, line numbers, style scheme) | | `spelling` | GtkSourceView 5 + libspelling spell-checking | ## m2 gtk-demo (`demos/`) A small Modula-2 port of GTK's `gtk4-demo`, mirroring its structure: a browser window with a searchable sidebar of demos, where activating a row opens the demo in its own window. | File | Role | |---|---| | `demos/GtkDemo.def` / `.mod` | the demo registry (title, keywords, build function) | | `demos/DemoImpl.def` / `.mod` | the demo implementations | | `demos/m2gtkdemo.mod` | the browser application (`GtkApplication` + sidebar + search) | The demos are simplified ports of the C originals in `gtk/demos/gtk-demo`: **Links** (`links.c`, markup hyperlinks + `activate-link`), **List Box** (`listbox.c`), **Flow Box** (`flowbox.c`, colour swatches drawn with `gdk_rgba_parse` + `gdk_cairo_set_source_rgba`), **Expander** (`expander.c`) and **CSS Basics** (`css_basics.c`, live CSS editing through a `GtkCssProvider`). Each demo follows the `gtk-demo` convention — a build procedure that creates its window once, then toggles its visibility — so the registry in `GtkDemo` is just a table of `PROCEDURE (ADDRESS) : ADDRESS`. ```sh make demos ./build/demos/m2gtkdemo ``` ## Module reference ### Core runtime | Module | C header | Contents | |---|---|---| | `GLib` | `` | base types, main loop, timeouts, memory, clock | | `GObject` | `` | `ref`/`unref`, `GType`, `g_object_new`, signals, properties | | `Gio` | `` | `GApplication` flags, run/quit/hold | | `GVariant` | `` | boolean/string/int32 values, refcount | | `GError` | `` | error value (message/domain/code) | | `GioError` | `` | GIO error domain and codes | | `Gtk` | `` | `gtk_init`, version helpers | | `Gdk` | `` | default display, `GdkRGBA` | | `GtkEnums` | `` | orientation, alignment, wrap, selection, position | ### GIO objects and models | Module | Contents | |---|---| | `GListModel`, `GListStore` | indexed models; in-memory store | | `GtkStringList`, `GtkStringObject` | string model / item | | `GMenu`, `GMenuModel`, `GSimpleAction`, `GAction`, `GActionMap` | menus and actions | | `GtkFilterListModel`, `GtkCustomFilter`, `GtkSortListModel`, `GtkCustomSorter` | M2-callback filter/sort models | | `GtkExpression`, `GtkStringSorter`, `GtkNumericSorter`, `GtkStringFilter`, `GtkBoolFilter` | expression-based sorters/filters | | `GtkConstraintLayout`, `GtkConstraint`, `GtkConstraintGuide` | constraint layout | | `GtkShortcut`, `GtkShortcutController` | keyboard shortcuts | | `Graphene`, `GtkSnapshot` | geometry records and snapshot rendering | | `GtkMultiSelection`, `GtkBitset` | multiple selection and bitsets | | `GtkTreeListModel`, `GtkTreeListRow`, `GtkTreeExpander` | tree models and expanders | | `GtkDirectoryList`, `GtkBookmarkList` | directory / bookmark models | | `GtkDropTarget`, `GtkDragSource`, `GdkContentProvider`, `GdkClipboard` | drag-and-drop and clipboard | | `GdkTexture`, `GdkPixbuf`, `GtkVideo`, `GtkMediaFile`, `GtkMediaStream` | images and media | | `GtkGLArea`, `GdkGLContext`, `GL` | OpenGL rendering (`-lGL`) | | `GtkPrintOperation`, `GtkPrintSettings`, `GtkPageSetup`, `GtkPrintContext` | printing | | `GFile`, `GtkFileDialog` | file handles and the native dialog | | `GSettings`, `GSettingsSchema` | typed settings and schema lookup | ### GTK widgets and views | Module | Contents | |---|---| | `GtkApplication`, `GtkApplicationWindow`, `GtkWindow`, `GtkWidget` | application and windows | | `GtkBox`, `GtkGrid`, `GtkFrame`, `GtkExpander`, `GtkPaned`, `GtkOverlay`, `GtkNotebook`, `GtkStack`, `GtkStackSwitcher`, `GtkScrolledWindow` | layout containers | | `GtkLabel`, `GtkButton`, `GtkCheckButton`, `GtkSwitch`, `GtkScale`, `GtkRange`, `GtkSpinButton`, `GtkEntry`, `GtkEditable`, `GtkSearchEntry`, `GtkDropDown` | controls | | `GtkTextView`, `GtkTextBuffer`, `GtkTextIter` | multi-line text | | `GtkTextTag`, `GtkTextTagTable`, `GtkTextMark`, `GtkEntryBuffer` | rich-text tags, marks and entry buffer | | `GtkEntryCompletion` | entry completion (deprecated; GtkTreeModel-based) | | `GtkAdjustment`, `GtkProgressBar`, `GtkSpinner`, `GtkSeparator`, `GtkImage` | value model and feedback/decoration widgets | | `GtkHeaderBar`, `GtkMenuButton`, `GtkPopoverMenuBar` | window chrome and menus | | `GtkPopover`, `GtkPopoverMenu` | popovers and menu popovers | | `GtkBuilder` | load widget trees from `.ui` XML (with `GError`) | | `GtkAlertDialog`, `GtkColorDialog` | modern alert and colour dialogs | | `GtkCssProvider`, `GtkStyleContext` | CSS styling for the display | | `GtkStackSidebar`, `GtkSearchBar`, `GtkRevealer`, `GtkAspectFrame` | sidebar/search/animation/layout helpers | | `GtkFlowBox`, `GtkFixed`, `GtkPicture`, `GtkToggleButton`, `GtkLinkButton`, `GtkCalendar`, `GtkLevelBar`, `GtkScrollbar` | reflowing grid, fixed/abs, image, toggle/link, date, level, scrollbar | | `GtkAssistant`, `GtkIconView`, `GtkEmojiChooser` | wizard, icon grid (deprecated), emoji picker | | `GtkEventController`, `GtkGestures` | event controllers and gestures (click/drag/swipe/zoom/rotate/pan/long-press) | | `GtkDrawingArea` + `Cairo` + `Pango` + `PangoCairo` | custom drawing with Cairo and text layout | | `GtkListBox`, `GtkListView`, `GtkGridView`, `GtkColumnView`, `GtkColumnViewColumn`, `GtkColumnViewCell`, `GtkListItem`, `GtkSignalListItemFactory`, `GtkSelectionModel`, `GtkSingleSelection`, `GtkNoSelection` | lists, grids and tables | ### libadwaita | Module | Contents | |---|---| | `AdwApplication`, `AdwApplicationWindow`, `AdwWindow`, `AdwWindowTitle` | Adwaita application, window, title | | `AdwHeaderBar`, `AdwToolbarView` | header bar and toolbar view | | `AdwViewStack`, `AdwViewStackPage`, `AdwViewStackPages` | animated page stack | | `AdwViewSwitcher`, `AdwViewSwitcherBar`, `AdwViewSwitcherTitle`, `AdwInlineViewSwitcher` | view switchers | | `AdwNavigationView`, `AdwNavigationPage` | push/pop navigation | | `AdwNavigationSplitView`, `AdwOverlaySplitView` | adaptive split views | | `AdwFlap`, `AdwLeaflet`, `AdwLeafletPage` | fold/overlay sidebars (deprecated) | | `AdwBottomSheet` | bottom sheet with a bottom bar | | `AdwTabView`, `AdwTabPage`, `AdwTabBar`, `AdwTabButton`, `AdwTabOverview` | tabs | | `AdwCarousel`, `AdwCarouselIndicatorDots`, `AdwCarouselIndicatorLines` | carousel pagers | | `AdwToggle`, `AdwToggleGroup` | segmented toggles | | `AdwDialog`, `AdwAlertDialog`, `AdwAboutDialog` | dialogs | | `AdwPreferencesDialog`, `AdwPreferencesWindow` | preferences dialogs/windows | | `AdwPreferencesPage`, `AdwPreferencesGroup`, `AdwPreferencesRow` | preferences layout | | `AdwActionRow`, `AdwSwitchRow`, `AdwEntryRow`, `AdwComboRow`, `AdwExpanderRow` | settings rows | | `AdwButtonRow`, `AdwSpinRow`, `AdwPasswordEntryRow` | button, spin and password rows | | `AdwClamp`, `AdwClampScrollable`, `AdwClampLayout` | clamping containers/layouts | | `AdwSqueezer`, `AdwSqueezerPage` | show-the-largest-that-fits | | `AdwWrapBox`, `AdwWrapLayout` | wrapping lines | | `AdwBreakpoint`, `AdwBreakpointCondition`, `AdwBreakpointBin` | adaptive breakpoints | | `AdwMultiLayoutView`, `AdwLayout`, `AdwLayoutSlot` | multi-layout containers | | `AdwAnimation`, `AdwAnimationTarget`, `AdwTimedAnimation`, `AdwSpringAnimation`, `AdwSpringParams`, `AdwCallbackAnimationTarget`, `AdwPropertyAnimationTarget` | animations | | `AdwSwipeTracker`, `AdwStyleManager` | swipe tracking and Adwaita styling | | `AdwEnumListModel`, `AdwEnumListItem` | enum-backed list models | | `AdwBin`, `AdwSpinner`, `AdwSpinnerPaintable`, `AdwAvatar` | misc widgets | | `AdwButtonContent`, `AdwSplitButton` | rich button content | | `AdwStatusPage`, `AdwBanner` | status/banner | | `AdwToast`, `AdwToastOverlay` | transient notifications | | `AdwAboutWindow`, `AdwMessageDialog` | about and message dialogs | | `AdwEnums` | shared Adw enums (view switcher, animation/easing, flap/leaflet, dialog, tab shortcuts, …) | ### GtkSourceView 5 | Module | Contents | |---|---| | `GtkSourceBuffer`, `GtkSourceView` | source buffer and view (highlighting, line numbers, marks, gutter) | | `GtkSourceLanguage`, `GtkSourceLanguageManager` | syntax-language definitions and lookup | | `GtkSourceStyleScheme`, `GtkSourceStyleSchemeManager`, `GtkSourceStyle` | colour schemes | | `GtkSourceMark`, `GtkSourceMarkAttributes`, `GtkSourceTag` | source marks and tags | | `GtkSourceGutter`, `GtkSourceGutterLines`, `GtkSourceGutterRenderer`, `GtkSourceGutterRendererText`, `GtkSourceGutterRendererPixbuf` | gutters and renderers | | `GtkSourceCompletion`, `GtkSourceCompletionContext`, `GtkSourceCompletionCell`, `GtkSourceCompletionProposal`, `GtkSourceCompletionProvider`, `GtkSourceCompletionWords`, `GtkSourceCompletionSnippets` | code completion | | `GtkSourceSnippet`, `GtkSourceSnippetChunk`, `GtkSourceSnippetContext`, `GtkSourceSnippetManager` | snippets | | `GtkSourceSearchContext`, `GtkSourceSearchSettings` | search and replace | | `GtkSourceRegion`, `GtkSourceRegionIter` | subregions of a buffer | | `GtkSourceFile`, `GtkSourceFileLoader`, `GtkSourceFileSaver`, `GtkSourceEncoding` | file I/O and encodings | | `GtkSourceMap`, `GtkSourceSpaceDrawer`, `GtkSourceStyleSchemeChooser`, `GtkSourceStyleSchemeChooserButton`, `GtkSourceStyleSchemeChooserWidget`, `GtkSourceStyleSchemePreview` | minimap, whitespace and scheme choosers | | `GtkSourceHover`, `GtkSourceHoverContext`, `GtkSourceHoverDisplay`, `GtkSourceHoverProvider` | hover providers | | `GtkSourceIndenter`, `GtkSourcePrintCompositor`, `GtkSourceVimIMContext` | indentation, printing and the Vim input method | | `GtkSourceEnums`, `GtkSourceFunctions` | shared enums and namespace functions | ### Gdk / Gsk (full coverage) | Modules | Contents | |---|---| | `Gdk*` (~58) | displays, monitors, seats, devices, events, surfaces, toplevels/popups, cursors, drag-and-drop, clips, content (de)serialization, textures and draw contexts, Cairo/GL/Vulkan contexts, frame clocks, `RGBA`/`Rectangle` value types, `GdkEnums`, `GdkFunctions` | | `Gsk*` (~48) | render nodes (container, cairo, color, text, texture, transform, shadow, …), renderers, transforms, rounded/clip/path/shadow helpers, GL shaders, `GskEnums`, `GskFunctions` | These namespace modules are hand-curated (the parallel to the rest of `src/`). They are complete: every `Gdk*`/`Gsk*` class/interface/record plus the namespace functions and enums. ### libspelling / libpanel | Modules | Contents | |---|---| | `SpellingChecker`, `SpellingDictionary`, `SpellingLanguage`, `SpellingProvider`, `SpellingTextBufferAdapter` | libspelling spell-checking | | `PanelApplication`, `PanelWorkbench`, `PanelWorkspace`, `PanelDocumentWorkspace` | libpanel workbench/workspace | | `PanelFrame(+Header, HeaderBar, TabBar, Switcher)`, `PanelGrid`, `PanelGridColumn`, `PanelPaned`, `PanelStatusbar`, `PanelOmniBar`, `PanelDock`, `PanelToggleButton` | panel layout and docks | | `PanelWidget`, `PanelSession`, `PanelSessionItem`, `PanelSaveDelegate`, `PanelSaveDialog`, `PanelChangesDialog` | documents and sessions | | `PanelSettings`, `PanelLayeredSettings`, `PanelGSettingsActionGroup`, `PanelActionMuxer`, `PanelMenuManager`, `PanelThemeSelector`, `PanelInhibitor` | settings, actions and menus | | `PanelEnums`, `PanelFunctions`, `SpellingFunctions` | enums and namespace functions | ### Helpers and the C shim (`lib/`) | Module | Contents | |---|---| | `GtkUtils` | `CStrToM2`, `StrAppend`, `IntToStr`, `Connect`, `SetAccel`, `Unref`, `Free`, `ErrorMessage`, `ClearError` | | `GtkClosures` | `Connect2`/`Connect3` (and owned variants) for per-connection signal state | | `M2GtkShim` (`lib/m2gtkshim.c`) | C shim for GObject subclassing: a custom `GtkWidget`, `GdkPaintable`s (Cairo- and snapshot-based), a generic `GListModel`, and a custom `GtkLayoutManager`, all driven by Modula-2 callbacks | ## Signals, ownership and errors `GObject.g_signal_connect_data()` is the real function behind the `g_signal_connect()` macro. For the common case use the helpers: ```modula2 Connect(button, "clicked", ADR(OnClicked), NIL); (* GtkUtils *) Connect2(row, "activated", OnActivate, state); (* GtkClosures: own state *) Connect3(dropdown, "notify::selected", OnSelected, NIL); (* 3-argument form *) ``` Modula-2 has no destructors, so follow the C transfer rules: - GObject handles → `GtkUtils.Unref` (`g_object_unref`); - strings from getters (`g_file_get_basename`, `g_settings_get_string`, …) → `GtkUtils.Free` (`g_free`); - `GError` values → `GtkUtils.ClearError`, and read them with `GtkUtils.ErrorMessage`; - `GVariant` from `g_variant_get_*` / `g_action_get_state` → `g_variant_unref`, while `g_variant_new_*` are floating. ## Dialect and binding notes The build uses the ISO dialect (`-fiso`). Things worth knowing: - `CAST` and `NEW`/`DISPOSE` (with `Storage.ALLOCATE`/`DEALLOCATE`) come from ISO, not PIM. - `EXPORT UNQUALIFIED` makes the C symbol match the Modula-2 name. - Formal parameters in a **procedure type** are unnamed: `GSourceFunc = PROCEDURE (ADDRESS) : INTEGER;`. - An ignorable result is declared `: [ T ]`; otherwise ignoring it is an error. - gm2 `REAL` is C `double`; `SHORTREAL` is `float`; avoid `LONGREAL`. - Pass a procedure **directly** where the parameter is a procedure type, and `ADR(proc)` where the parameter is `ADDRESS`/`GCallback`. - `ARRAY OF CHAR` maps to `char *`; `VAR` parameters map to `T *`; opaque handles travel as `ADDRESS`. - Inside comments, avoid `*)` and `(*` (write `void ptr`, not `void *`). - A large opaque struct always passed by address (e.g. `GtkTextIter`, 80 bytes) can be modelled as `ARRAY [0..N] OF ADDRESS`. ## Generator (broad coverage) For breadth beyond the hand-written modules, `tools/gir2def.py` emits `FOR "C"` `.def` files from GObject-Introspection `.gir` XML: ```sh make gir # Gtk-4.0 -> gen/ make gir GIR=Adw-1 # any namespace make gir-check # generate, parse every module, link a sample make gir-verify # check every identifier exists in the library ``` It produces one module per class/interface/record, namespace functions in `Functions.def`, and enums in `Enums.def`; procedure names are the exact GIR `c:identifier`. Across Gtk/GLib/Gio/Adw/GtkSource/ Gdk/Gsk/Spelling/Panel, 828 generated modules parse under `gm2 -fiso` and all 10256 identifiers resolve in the installed libraries. Output goes to `gen/` (git-ignored) and is meant to be cherry-picked, not to replace `src/`. See `tools/README.md` for the type mapping and limits. ## Repository layout ```text m2-GTK4/ src/ FOR "C" .def bindings (one per GTK/GIO/Adw object) lib/ helpers (GtkUtils, GtkClosures) + C shim (m2gtkshim.c) tests/ gm2 tests + run_tests.sh (+ a private GSettings schema) examples/ runnable GTK4 / libadwaita programs demos/ the m2 gtk-demo browser (registry + demos + application) tools/ gir2def.py, gir_verify.py, run_gir_check.sh gen/ generator output (git-ignored) build/ generated binaries (git-ignored) ``` ## License No license has been chosen yet; there is no `LICENSE` file in this repository.