Native extension authors can use the public
include/basic_gtk_v1.h
together with the matching SDK’s
native/include/basic_ffi_v1.h. Borrow/type-check GObjects, acquire or consume native
references, return owned BASIL wrappers and construct GtkError through versioned
helpers. Initialize GTK first and stay on its owning thread. The
adapter guide/generator
documents ownership,
retention, checked errors and guarded calls; applications need no private Handle,
allocator or trampoline declarations. An independent source/moved-package extension
is covered by acceptance
.
Mixed documents now have typed Image/DocumentSnapshot/DocumentHistory/DocumentEdit owners. DecodeImage preserves exact source bytes; capture/restore includes inline images, Unicode positions and selection/cursor. Bounded grouped undo/redo includes images and tracks saved-content state. Groups close automatically on scope exit, failure or cancellation. API/limits , small editor , real private-display tests . Decode/snapshotting are synchronous native operations. Image clipboard and text-tag serialization are separate; this API does not silently promise either.
Linux x86-64 application-facing GTK for BASIL: opaque owners, queued events, widgets/layouts, menus, models, dialogs, text editing, drawing, clipboard and background file work. The application logic is BASIL; native adapters handle GTK, GObject references, marshalling and platform readiness. Completion evidence and limits record the full gate, real GUI acceptance and sanitizer checks.
GTK 4.14 or newer, development headers, pkg-config, cc and ar are required
to build the binding. Programs use installed GTK/GObject/GLib/GIO/Pango/Cairo and
libc system libraries through the hosted link path. gtk.blib bundles its BASIL
code, native bridge and platform archive; it does not bundle or download GTK.
Ordinary programs without GUI dependencies keep the standalone BASIL link path.
python3 tools/build_gtk.py build/selfhost/stage3
build/selfhost/stage3 build --manifest examples/notepad/basic.toml
examples/notepad/build/notepad
examples/notepad/build/notepad notes.txt
Dependencies may name libraries/gtk for source or the movable
libraries/gtk/build/gtk.blib for prebuilt use. Rebuild old prototype packages:
GtkError now contains Code AS INTEGER and Message AS STRING.
The build helper verifies generated declarations and caches the archive/package
against source, headers, compiler, platform archive, flags and output hashes.
Application API
| Area | Entry points |
|---|---|
| Initialization/lifetime | Constructors initialize GTK automatically; optional Initialize. NewWindow, NewApplication, Present, Close, Quit, IsRunning. All GTK access/destruction stays on its initialization thread. |
| Event loop | NextEvent(app) or NextEvent(app, Duration), Kind, EventName, Command, ArgumentCount, EventInteger/String/Double/Object. On(app, object, signal, command, handled) returns an owned Subscription. |
| Builder/properties | LoadInterface, GetWidget, GetWindow, GetTextView, GetEntry, GetObject; checked SetProperty, GetString/Integer/Boolean/UInt64/Double/Object. Wrong property type, missing name and unsupported signals are checked errors. |
| Widgets/layout | NewButton/Label/Box/Entry/TextView/Widget/Picture, Add, SetChild, Attach for grids, AddPage for notebooks/stacks, Remove, Show, Focus, Expand, Margin, SetFont, ApplyCss, Accessible. Standard widget properties and declarative layouts cover checks, switches, dropdowns, scales/spins, progress, scrolling, panes, stacks and notebooks. |
| Menus/shortcuts | NewMenu, AddItem, AddSubmenu, AddSection, MenuBar, Action, Activate. Action names, GTK shortcut strings and optional Boolean check states are checked. Keep returned action subscriptions alive. |
| Lists/grids | NewStringModel, AppendItem, InsertItem, RemoveItem, Item, Count, NewListView, Selected, SelectItem. SetMultipleSelection, SetSelected, SelectAll, UnselectAll, IsSelected, GetSelection and immutable owned selection snapshots work with list/grid/table/tree views. |
| Tables | NewTableModel(columns), AppendRow, ReplaceRow, RemoveRow, Cell, Count, NewTableView, SortBy, SourceRow. Rows are copied display strings; keep typed application data in BASIL. SourceRow maps sorted display positions to source-model rows. |
| Trees | NewTreeNode, AddChild, RemoveChild, NewTreeModel, AddRoot, RemoveRoot, NewTreeView, ExpandRow, Node, NodeText, Count. Duplicate parenting and cycles are rejected. |
| Text | Buffer, GetText, SetText, Length, SelectText, SelectionStart/End, Cursor, CurrentLine/Column, LineCount, GoToLine, ScrollToCursor, InsertText, ReplaceText, DeleteSelection, Modified, SetModified, EnableUndo, CanUndo/Redo, Undo, Redo, Edit for exception-safe undo grouping, and Search. Positions are zero-based Unicode scalars. |
| Clipboard/input | Cut, Copy, Paste, ReadClipboard, WriteClipboard; Controller plus queued On for keys, focus, mouse/gestures and scrolling. DragText, AcceptTextDrop, AcceptFileDrop, RemoveController. File drops expose local paths as string event arguments. |
| Drawing/images | NewCanvas, Brush, ClearCanvas, DrawLine/Rectangle/Ellipse/Text, NewPicture. Commands are retained natively and replayed on paint; BASIL never runs on a paint callback stack. |
| Dialogs/printing | ChooseFile, ChooseFolder, ChooseFont, ChooseColor, MessageBox, About, PrintText. User-dismissed choices return NONE or the specified cancel answer. PrintText uses the selected font, line-aware pagination, GTK print dialog or optional PDF path. |
| Files/encoding | LoadFile returns bytes plus a version; SaveFile accepts an expected version and returns the new version. GIO jobs retain input snapshots until native acknowledgement. EncodeText, DecodeText use explicit encodings (UTF-8 default). LocalTimeText is a GTK/GLib display helper, not a replacement for the planned BASIL datetime library. |
| Diagnostics | LiveResources, Screenshot. Test helper drivers are not included in the library or editor. |
The original OnClicked/Click/Disconnect/DisconnectAll direct callback API
remains available. It requires an infallible, noncapturing CALLABLE NOSUSPEND(Button);
catch declared failures inside it. Prefer queued events for ordinary application
state, suspending handlers, JSON and background work.
Event and ownership rules
Application code handles events in an ordinary BASIL loop. It can mutate its own
state, call fallible functions, suspend, or start checked tasks. Native callbacks
retain no pointer to that BASIL state. Scalar/string signal arguments are snapshots;
object arguments retain native references, not frozen copies of widget properties.
EventObject creates a fresh reference owner, and string results are independent
BASIL values. A read-only BASIL borrow does not freeze a GTK object.
EventKind distinguishes Signal, Command, CloseRequested, Stopped and TimedOut.
A timeout starts afresh on each timed NextEvent call. The root window’s close
request is intercepted and delivered automatically; decide whether to Quit after
an unsaved-change prompt. Secondary window close signals can use On with
handled=TRUE and then hide or close that window in BASIL.
Default GUI signal handlers return immediately after enqueueing. A Boolean signal
return is an explicit constant policy selected at registration; a later BASIL event
cannot retroactively veto it. Unsupported pointer/boxed arguments or non-Boolean
return contracts fail at registration. notify parameters become property-name
strings. No raw C closures can be smuggled through Builder XML.
Keep Subscription/Connection owners alive while registered. They disconnect on scope exit; action cleanup also removes its shortcut/action. Containers keep their own GTK child references. Releasing a child reference does not remove the widget. Window cleanup destroys the window. Application cleanup stops delivery, discards unhandled queued events, detaches its native drop registrations and closes its root. A Builder destroys unclaimed windows, including on partial parse failure; GetWindow/GetWidget/GetObject transfer responsibility for a window to its returned owner. BASIL resources still cannot be implicitly copied.
GLib prepare/query, kernel poll, check/dispatch and release form one coordinator transaction. No BASIL activation runs while Wayland/native reads are prepared. The process-lifetime GLib source retains no application state. It participates in scheduler readiness/deadlines without polling or an extra thread per wait, including when a BASIL handler waits on a dialog/file job. Native frames use the existing guarded foreign stack. Job cleanup requests cancellation and acknowledges native completion before releasing buffers; cleanup’s final acknowledgement may run a native GLib iteration after the BASIL pump has been canceled.
Run(window)/RunFor(window, Duration) are simple window-only event loops.
Pump(EventSource(app)) is an optional scoped lifetime task for applications using
explicit SPAWN/Join; ordinary NextEvent already drives GLib through the scheduler.
A stopping pump wakes event consumers; joining still observes its typed failure.
Library CPU-loop scheduling and broader foreign-thread attachment retain the
compiler/runtime limits
.
Checked boundaries and limits
GtkError carries a code and explanation; NumericError/StringError validate scalar and text inputs, IoError covers BASIL readiness, and DialogError preserves native operational Message, Domain and Code; IsMissingFile/IsVersionConflict classify common cases. Allocation failure remains fatal. Canceling a write can race with its commit; it never marks an unobserved save clean. The editor compares buffer revisions before clearing its dirty flag. Files use conditional version checks; a failed overwrite preserves the editor buffer and dirty state. Version strings are opaque, combining a content fingerprint with the native etag: timestamp-only etags can collide on rapid replacements. Fingerprinting and save validation use workers, retaining native snapshots until acknowledgement. Validation detects changes already present before the save check; an unrelated writer can still race the final filesystem replacement. This is not a filesystem compare-and-swap or cross-process locking protocol. Do not persist/construct version tokens.
Text/file conversion currently accepts at most 128 MiB; malformed UTF-8/UTF-16,
embedded NUL and unsupported conversions are rejected. Builder supports the reviewed
classes in builder.c
, not arbitrary registered native classes,
type-func, XML C callbacks/closures or C callback factories. Connect application
behavior with the BASIL event API. This is an application toolkit, not every public
GObject subclassing/rendering-internal API. Captured C callback environments,
foreign-thread attachment and arbitrary C layouts remain separate FFI work.
Notepad , direct callback demo , compiler/foreign contracts , application tests , callback tests , third-party notices .
Validation commands (the last three require a graphical session):
python3 tools/test_gtk_application.py build/selfhost/stage3
python3 tools/test_gtk.py build/selfhost/stage3 --display
python3 tools/test_gtk_application.py build/selfhost/stage3 --display
python3 tools/test_gtk_sanitizers.py
The sanitizer runner instruments the optional native adapters in a separate build; production binaries are unaffected. ASan/UBSan check native memory/undefined behavior; GTK global-cache leak scanning is disabled and binding ownership counts are checked.
Image clipboard: checked asynchronous requests provide OnImagePaste/PasteClipboard and focus-routed Paste(window). Native reads retain no BASIL storage and cancel/acknowledge on cleanup. Styles/events : LoadStylesheet returns a scoped provider owner for full display-wide selectors; ApplyCss accepts widget declarations. OnActivate/ActivatedPosition validate actual queued row activation.