The AO toolkit
The native application framework — an AppKit-shaped C API, callable from C, C++ and Go.
AO_ is the native application framework for AnotherOS. It is one GUI API
modelled on macOS's AppKit, callable from C, C++ and Go, drawing through the
display protocol and the Kvarcas 2D renderer. It ships
as libao.so.1.
The rule that shapes it: no stubs
If it is not documented, the symbol is not in the library. Nothing in AO_
is a stub that returns success and does nothing, and the
declared-versus-defined count is checked mechanically on every build.
This is worth stating because it changes how you read the rest of this page. A missing feature that reads as a present one is worse than an absent feature: it fails at the moment it matters and reports success everywhere else. If a call is here, it does the thing.
The stack
your application
AO_ widgets, layout, responder chain, run loop
Kvarcas 2D rendering — paths, text, images
libfasadas the display protocol transport
fasadasd the compositor / window server
Core concepts
| Concept | Behaviour |
|---|---|
| Coordinates | y-down from the top-left of your content. |
| Layout | Springs and struts — autoresizingMask. A resize lays out; it does not scale. |
| Events | A responder chain, AppKit-modelled. |
| Target/action | One signature, deliberately. |
| Windows | Closing a window is a request. Teardown happens once the dispatch depth returns to zero, so you may close a window from inside one of its own handlers. |
| Run loop | Yours. Timers, file descriptors and background work hang off it; AO_ never takes the main loop away from you. |
| Preferences | AO_UserDefaults. A suite is a .conf file; reads are cached, writes are immediate. |
| Modifier keys | AO_CurrentModifierFlags(), and a click carries its own modifiers — which is what lets a window that is not key, and the desktop and dock which never are, see a Control-click. |
| Drawing by hand | A custom view with a drawRect and a mouse handler is the sanctioned escape hatch, not a private toolkit. |
Presented frames
AO_WindowDelegate.did_present(win, time_ms, user) fires once per frame the
compositor has actually put on the scanout, and
AO_WindowPresentedFrameCount(win) is its pull-based twin.
This is the only frame signal that reflects what reached the panel. A benchmark or an animation that paces on its own draw loop is counting something faster than the screen, and will report numbers the user cannot see.
Borderless windows, moving and resizing
AO_WindowCreateBorderless() is the equivalent of
NSWindowStyleMaskBorderless: the compositor draws no chrome and your
application draws its own.
Such a window then moves with AO_WindowPerformDrag(win) and resizes
from a grow box with AO_WindowPerformResize(win, AO_WINDOW_EDGE_*), both
called from a primary-button mouse_down. Both hand the press to the
compositor's own gesture rather than faking a move from your motion events —
motion events jitter by a frame, and a window dragged from them visibly lags the
pointer. Both return 0 against a compositor that does not advertise interactive
gestures.
AO_WindowFrameOrigin() and AO_WindowSetFrameOrigin() read and request the
content origin, and AO_WindowDelegate.did_move reports every change. Together
they are what an application needs to reopen a window where the user left it.
The compositor still places new windows; restoring one is a different act.
Fonts and the system typeface
Ask for a role, never a size or a family.
| Role | px | For |
|---|---|---|
AO_FONT_MENU |
14 | menu bar and menu items |
AO_FONT_REGULAR |
13 | controls, labels, list rows, body — the default |
AO_FONT_SMALL |
11 | help text, sidebar headings, status bars, icon labels |
AO_FONT_MINI |
9 | annotations and badges — rarely the right answer |
AO_FONT_TITLE |
13 | window titles: body size, heavier weight |
AO_FONT_HEADING |
17 | section headings inside a window body |
AO_FONT_DISPLAY |
26 | About-box product names |
Sizes are device pixels, one number one pixel, matching how macOS specifies its system font at 72 dpi.
A bare 11.0 in your source records how big something looked on the day it was
typed, and the next reader cannot tell whether it may be changed. A role can.
AO_FONT_FAMILY is a sentinel, not a family name. A system typeface ships
as two optical cuts — SF Pro Text and Display, Inter and Inter Display — and the
size chooses between them. AO_FontCreate() picks the cut from the size you
pass. Naming a face by hand gets you cramped large text or loose small text,
which is the most visible way to render either family wrongly.
The user picks the typeface in System Preferences → Appearance → System font (SF Pro, Inter or DejaVu Sans). It is read once per process, so a running application keeps the face it started with.
Widgets
| Class | Notes |
|---|---|
AO_Control |
The base: target/action, enable and validate. |
AO_Button |
One class, seven types. |
AO_TextField |
Caret, selection, real clipboard. |
AO_Slider, AO_ProgressIndicator |
|
AO_Box |
A group with a real interior. |
AO_Image, AO_ImageView |
|
AO_ScrollView, AO_ClipView, AO_Scroller |
Scroll wheel supported. |
AO_TextView |
Many lines, a real undo stack. |
AO_Menu, AO_MenuItem |
Global menu bar, key equivalents, validation. |
AO_PopUpButton, fly-outs, context menus |
Each panel is a real popup surface: it leaves the window and flips at the work-area edges, with keyboard and type-select. |
AO_Alert |
|
AO_OpenPanel, AO_SavePanel |
Real sheets. They answer Shift-Cmd-period themselves and share one Show Hidden Files setting with the file manager. |
AO_SegmentedControl |
A row of exclusive choices, three tracking modes; a single segment can be disabled. |
AO_TableView, AO_TableColumn |
A data source, multiple selection, a double action, an optional header — and it scrolls. |
AO_Pasteboard |
Typed, system-wide Cut/Copy/Paste: files, text, images, lazy promises. |
AO_UserNotification, AO_UserNotificationCenter |
Desktop notifications. |
Context menus
Right-click, Control-click, or a trackpad's two-finger click.
A view either has a static menu (AO_ViewSetMenu) or builds one per press
(AO_ViewHandlers.menu_for_event, the equivalent of menuForEvent:).
right_mouse_down runs first, so your selection can follow the click before
the menu is built from it.
Show Hidden Files
Shift-Cmd-period is a system gesture, not a checkbox on your window. On macOS it reveals dotfiles in the Finder and in every open and save panel at once, so the state is not in the file manager and not in a save panel — it is in the toolkit, and every file list reads the same one.
int AO_ShowsHiddenFiles(void); /* 0 by default: hiding */
void AO_SetShowsHiddenFiles(int shows); /* sets, persists, tells observers */
int AO_ShowsHiddenFilesRefresh(void); /* has another process changed it? */
int AO_FileNameIsHidden(const char *dir, const char *name);
int AO_RootNameIsVisible(const char *name);
void AO_ShowsHiddenFilesAddObserver(AO_HiddenFilesChangedFn fn, void *user);
void AO_ShowsHiddenFilesRemoveObserver(AO_HiddenFilesChangedFn fn, void *user);
Two hiding rules, both stated once in AO_FileNameIsHidden():
- a leading dot hides, by convention;
- at the root of the system disk, only
Applications,UsersandSystemare shown.
Rule 2 hides; it does not skip. An entry the user is not being shown is still an entry, which is what lets the hotkey be a redraw rather than a re-read of the disk — and what makes revealing it possible at all.
If you show a file list of your own: read AO_ShowsHiddenFiles() rather
than testing for a leading dot; register an observer so your list follows a
toggle made elsewhere in your process; and call AO_ShowsHiddenFilesRefresh()
once per run-loop turn so it follows one made in another process. The refresh
throttles itself, so calling it every turn costs nothing.
The pasteboard
Modelled on NSPasteboard. AO_PasteboardGeneral() is a singleton handle;
nothing is released. A board holds one item with several typed representations,
and the compositor keeps the bytes — so a copy survives the process that made
it.
AO_Pasteboard *pb = AO_PasteboardGeneral();
AO_PasteboardClearContents(pb); /* take ownership */
AO_PasteboardSetData(pb, AO_PASTEBOARD_TYPE_PNG, png, n);
AO_PasteboardSetData(pb, AO_PASTEBOARD_TYPE_STRING, "a picture", 9);
size_t len;
char *bytes = AO_PasteboardDataForType(pb, AO_PASTEBOARD_TYPE_PNG, &len, 500);
AO_Free(bytes);
- Promises.
AO_PasteboardDeclareTypes()names types without producing them. Yourprovidecallback runs when somebody pastes one, and for every outstanding type when your process exits normally. - Ownership.
AO_PasteboardSetDatareturns −1 once somebody else has copied. It never writes into their copy. - Text.
AO_PasteboardSetStringandAO_PasteboardStringare the same clipboard that GTK, Qt, Firefox and Win32 programs use. - Files.
AO_PasteboardSetFiles()andAO_PasteboardSetFilesOp()write three representations: an RFC 2483text/uri-listof percent-encodedfile:///URLs, the paths as plain text one per line so that pasting into an editor gives you paths, and the copy-or-move intent.
From a menu validator, call AO_PasteboardHasTypeCached() and never
AO_PasteboardHasType(). The plain form asks the compositor and blocks for
the answer, so a validator that calls it costs one synchronous round trip per
run-loop turn — dozens a second on an idle desktop. The cached form re-asks only
when the change count has moved, and answers "unknown, stay enabled" against a
compositor too old to push notifications. After writing the board from your own
process, call AO_PasteboardInvalidateTypeCache(): your own write is not in the
compositor's count until its notification comes back.
Do not build your own cache keyed on "the change count is not zero". A count of zero means both an old compositor and a healthy board nobody has copied to yet, so such a cache never hits from boot until the user's first Copy.
HiDPI
A window has a backing scale factor. Ask for it rather than assuming 1, draw at the backing size, and let the compositor present it. Sizes in the font table above are device pixels at scale 1.
What is deliberately absent
There is no private widget toolkit hiding behind AO_, no retained scene graph,
and no shader language in the drawing path. When a widget does not exist, the
answer is a custom view with a drawRect, which is a supported and ordinary
thing to write.
Bindings
The C API is the API. C++ and Go bindings call the same libao.so.1 and add no
behaviour of their own.
More documentation
- Booting the live image
- Installing to disk
- Packages and the filesystem
- How it boots
- The syscall ABI
- Hardware support
- ACPI
- UEFI boot entries
- NetBooting a Mac
- Grafika — the graphics API
- The display protocol
- Desktop effects
- The level bezel
- Notifications
- Rastinis — the file manager
- File operations
- Audio — Garsas
- Hardware video decode
- Networking — Tinklas
- Network shares — SMB and NFS
- Remote control
- The Win32 subsystem
- DirectX and OpenGL for Win32 programs
- Ports and packaging