27 KiB
Writing GUI Applications for MontaukOS
This guide covers how to build graphical applications for MontaukOS, from standalone Window Server clients to desktop-integrated apps.
Table of Contents
- App Types
- Standalone Window Server Apps
- Desktop-Integrated Apps
- Drawing and Rendering
- Text and Fonts
- Input Handling
- Widgets
- Colors and Theming
- Memory Management
- Networking and HTTPS
- App Manifests
- Build System
App Types
MontaukOS supports two kinds of GUI applications:
| Standalone Apps | Desktop Apps | |
|---|---|---|
| Location | programs/src/<appname>/ |
programs/src/desktop/apps/ |
| Window | Own process, shared-memory pixel buffer | Embedded in desktop compositor |
| Event loop | win_poll() syscall |
Callback-driven (on_draw, on_mouse, on_key) |
| Drawing | Direct pixel buffer writes | Canvas abstraction |
| Examples | Spreadsheet, Music, Wikipedia | Calculator, File Manager, Terminal |
Choose standalone when you need a separate process (e.g., networking, heavy computation, isolation). Choose desktop-integrated for lightweight tools that benefit from tight compositor integration.
Standalone Window Server Apps
Standalone apps are separate ELF binaries that communicate with the Window Server through syscalls. They get a shared-memory pixel buffer and manage their own event loop.
Minimal Example
#include <montauk/syscall.h>
#include <montauk/heap.h>
#include <montauk/string.h>
#include <gui/gui.hpp>
#include <gui/truetype.hpp>
static TrueTypeFont* g_font;
static int g_win_w, g_win_h;
static void render(uint32_t* pixels) {
// Clear background
for (int i = 0; i < g_win_w * g_win_h; i++)
pixels[i] = Color::from_rgb(0xFF, 0xFF, 0xFF).to_pixel();
// Draw text
if (g_font)
g_font->draw_to_buffer(pixels, g_win_w, g_win_h,
20, 30, "Hello, MontaukOS!",
Color::from_rgb(0x33, 0x33, 0x33), 18);
}
extern "C" void _start() {
// Load font
g_font = new TrueTypeFont();
g_font->init("0:/fonts/Roboto-Medium.ttf");
// Create window
g_win_w = 400;
g_win_h = 300;
Montauk::WinCreateResult wres;
montauk::win_create("My App", g_win_w, g_win_h, &wres);
int win_id = wres.id;
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
// Initial render
render(pixels);
montauk::win_present(win_id);
// Event loop
while (true) {
Montauk::WinEvent ev;
int r = montauk::win_poll(win_id, &ev);
if (r < 0) break; // Window destroyed externally
if (r == 0) {
montauk::sleep_ms(16); // ~60 FPS idle
continue;
}
if (ev.type == 3) break; // Close event
if (ev.type == 2) { // Resize
g_win_w = ev.resize.w;
g_win_h = ev.resize.h;
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
}
if (ev.type == 0) { // Keyboard
// ev.key.ascii, ev.key.scancode, ev.key.pressed
}
if (ev.type == 1) { // Mouse
// ev.mouse.x, ev.mouse.y, ev.mouse.buttons
}
render(pixels);
montauk::win_present(win_id);
}
montauk::win_destroy(win_id);
montauk::exit(0);
}
Window Lifecycle
win_create() Create window, get pixel buffer pointer
|
v
win_present() Push current pixel buffer to screen
|
v
win_poll() Receive events (returns 0 if none, <0 if closed)
|
v
win_resize() Handle resize, get new pixel buffer pointer
|
v
win_destroy() Clean up window
Event Types
Events are delivered via win_poll() into a WinEvent struct:
ev.type |
Event | Fields |
|---|---|---|
| 0 | Keyboard | ev.key.scancode, ev.key.ascii, ev.key.pressed, ev.key.shift, ev.key.ctrl, ev.key.alt |
| 1 | Mouse | ev.mouse.x, ev.mouse.y, ev.mouse.buttons, ev.mouse.prev_buttons, ev.mouse.scroll |
| 2 | Resize | ev.resize.w, ev.resize.h |
| 3 | Close | (none) |
Drawing Helpers
For new standalone apps, prefer the shared helpers in #include <gui/standalone.hpp>. It provides:
gui::WsWindowforwin_create()/win_poll()/win_resize()/win_present()gui::Canvasfor drawing into the window buffer- immediate-mode helpers like
draw_text(),draw_button(), andfill_circle()
Older apps in the tree still define local px_* helpers, but new code should not need to.
gui::WsWindow win;
win.create("My App", 400, 300);
gui::Canvas c = win.canvas();
c.fill(gui::colors::WHITE);
draw_text(c, g_font, 20, 30, "Hello", gui::colors::TEXT_COLOR, 18);
draw_button(c, g_font, 20, 60, 96, 28, "OK",
gui::colors::ACCENT, gui::colors::WHITE, 6, 16);
Rounded Rectangles
static void px_fill_rounded(uint32_t* px, int bw, int bh,
int x, int y, int w, int h, int r, uint32_t color) {
for (int row = y; row < y + h && row < bh; row++) {
for (int col = x; col < x + w && col < bw; col++) {
int dx = 0, dy = 0;
if (col < x + r && row < y + r) { dx = x + r - col; dy = y + r - row; }
else if (col >= x+w-r && row < y + r) { dx = col - (x+w-r-1); dy = y + r - row; }
else if (col < x + r && row >= y+h-r) { dx = x + r - col; dy = row - (y+h-r-1); }
else if (col >= x+w-r && row >= y+h-r) { dx = col - (x+w-r-1); dy = row - (y+h-r-1); }
if (dx * dx + dy * dy <= r * r)
px[row * bw + col] = color;
else if (dx == 0 && dy == 0)
px[row * bw + col] = color;
}
}
}
Desktop-Integrated Apps
Desktop apps are compiled into the desktop binary itself. They register callback functions that the compositor calls during its event loop.
Creating a Desktop App
Step 1: Define your app state
// In apps/app_myapp.cpp
struct MyAppState {
int counter;
char label[64];
};
Step 2: Implement callbacks
static void myapp_on_draw(Window* win, Framebuffer& fb) {
MyAppState* state = (MyAppState*)win->app_data;
Canvas c(win);
c.fill(colors::WINDOW_BG);
// Draw toolbar
c.fill_rect(0, 0, win->content_w, 36, Color::from_rgb(0xF5, 0xF5, 0xF5));
c.hline(0, 36, win->content_w, colors::BORDER);
// Draw content
c.text(20, 60, state->label, colors::TEXT_COLOR);
}
static void myapp_on_mouse(Window* win, MouseEvent& ev) {
MyAppState* state = (MyAppState*)win->app_data;
if (ev.left_pressed()) {
state->counter++;
win->dirty = true; // Request redraw
}
}
static void myapp_on_key(Window* win, const Montauk::KeyEvent& key) {
if (!key.pressed) return;
MyAppState* state = (MyAppState*)win->app_data;
// Handle keystrokes...
win->dirty = true;
}
static void myapp_on_close(Window* win) {
MyAppState* state = (MyAppState*)win->app_data;
montauk::mfree(state);
}
Step 3: Write the open function
void open_myapp(DesktopState* ds) {
int idx = desktop_create_window(ds, "My App", 400, 300, 320, 400);
if (idx < 0) return;
Window* win = &ds->windows[idx];
MyAppState* state = (MyAppState*)montauk::malloc(sizeof(MyAppState));
montauk::memset(state, 0, sizeof(MyAppState));
win->app_data = state;
win->on_draw = myapp_on_draw;
win->on_mouse = myapp_on_mouse;
win->on_key = myapp_on_key;
win->on_close = myapp_on_close;
win->dirty = true;
}
Step 4: Register in the app menu
Add an entry in desktop_init() (main.cpp) to the app menu so users can launch it. Include the open_myapp function in apps_common.hpp or similar.
Callback Reference
| Callback | Signature | When Called |
|---|---|---|
on_draw |
void(Window*, Framebuffer&) |
Every frame when win->dirty is true |
on_mouse |
void(Window*, MouseEvent&) |
Mouse event within window content area |
on_key |
void(Window*, const KeyEvent&) |
Keyboard event while window is focused |
on_close |
void(Window*) |
Window close button clicked |
on_poll |
void(Window*) |
Every frame, for background processing |
The dirty Flag
Desktop apps use a dirty flag to control redraws. Set win->dirty = true after any state change that requires a visual update. The compositor skips on_draw for non-dirty windows.
Drawing and Rendering
Canvas API (Desktop Apps)
The Canvas wraps a window's pixel buffer with drawing primitives:
Canvas c(win); // Construct from Window*
// Fills
c.fill(Color c); // Entire buffer
c.fill_rect(int x, int y, int w, int h, Color c); // Rectangle
c.fill_rounded_rect(int x, int y, int w, int h, int r, Color c); // Rounded rect
// Lines
c.hline(int x, int y, int len, Color c); // Horizontal
c.vline(int x, int y, int len, Color c); // Vertical
c.rect(int x, int y, int w, int h, Color c); // Outline
// Text
c.text(int x, int y, const char* str, Color c); // TrueType or bitmap
c.text_2x(int x, int y, const char* str, Color c); // 2x scaled bitmap
c.text_mono(int x, int y, const char* str, Color c); // Monospace
// UI elements
c.button(int x, int y, int w, int h, const char* label,
Color bg, Color fg, int radius); // Styled button
c.icon(int x, int y, const SvgIcon& ic); // SVG icon
// Layout helpers
c.kv_line(int x, int* y, const char* line, Color c, int line_h); // Key-value line
c.separator(int x_start, int x_end, int* y, Color c, int spacing); // Horizontal separator
Framebuffer API (Low-Level)
For direct framebuffer access (used by the compositor itself and fullscreen apps):
Framebuffer fb;
fb.put_pixel(x, y, color);
fb.put_pixel_alpha(x, y, color); // With alpha blending
fb.fill_rect(x, y, w, h, color);
fb.fill_rect_alpha(x, y, w, h, color);
fb.blit(x, y, w, h, pixels); // Copy pixel region
fb.blit_alpha(x, y, w, h, pixels); // With alpha blending
fb.clear(color);
fb.flip(); // Swap to hardware
Drawing Primitives
From gui/draw.hpp, available for both Framebuffer-based rendering:
draw_hline(fb, x, y, w, color);
draw_vline(fb, x, y, h, color);
draw_rect(fb, x, y, w, h, color);
fill_rounded_rect(fb, x, y, w, h, radius, color);
fill_circle(fb, cx, cy, r, color);
draw_circle(fb, cx, cy, r, color);
draw_line(fb, x0, y0, x1, y1, color); // Bresenham's
draw_shadow(fb, x, y, w, h, offset, color);
Text and Fonts
TrueType Fonts (Preferred)
MontaukOS uses stb_truetype for font rendering. Fonts are loaded from the VFS:
TrueTypeFont* font = new TrueTypeFont();
font->init("0:/fonts/Roboto-Medium.ttf");
// Render text to a pixel buffer
font->draw_to_buffer(pixels, buf_w, buf_h, x, y, "Hello", color, 18);
// Measure text width before drawing
int width = font->measure_text("Hello", 18);
// Get line height for layout
int line_h = font->get_line_height(18);
System Fonts
The desktop initializes a set of global fonts:
fonts::init(); // Call once at startup
// Available fonts
fonts::system_font // Roboto-Medium.ttf (UI text)
fonts::system_bold // Roboto-Bold.ttf (headings)
fonts::mono // JetBrainsMono-Regular.ttf (code/terminal)
fonts::mono_bold // JetBrainsMono-Bold.ttf
// Standard sizes
fonts::UI_SIZE // 18 (body text)
fonts::TITLE_SIZE // 18 (window titles)
fonts::LARGE_SIZE // 28 (headings)
fonts::TERM_SIZE // 18 (terminal)
Glyph Caching
TrueType fonts cache rasterized glyphs per pixel size. Up to 4 size caches are maintained per font. Access the cache directly for advanced metrics:
GlyphCache* cache = font->get_cache(18);
// cache->ascent, cache->descent — for line-height calculation
Bitmap Font (Fallback)
An 8x8 bitmap font is always available for basic text rendering when TrueType is not loaded:
draw_char(fb, x, y, 'A', color);
draw_text(fb, x, y, "Hello", color);
int w = text_width("Hello");
Input Handling
Keyboard
// In standalone apps (via win_poll)
if (ev.type == 0) {
Montauk::KeyEvent& key = ev.key;
if (!key.pressed) { /* key release */ }
if (key.ascii >= 0x20 && key.ascii < 0x7F) {
// Printable character
}
// Special keys by scancode
switch (key.scancode) {
case 0x01: /* Escape */ break;
case 0x0E: /* Backspace */ break;
case 0x1C: /* Enter */ break;
case 0x0F: /* Tab */ break;
case 0x53: /* Delete */ break;
case 0x48: /* Up */ break;
case 0x50: /* Down */ break;
case 0x4B: /* Left */ break;
case 0x4D: /* Right */ break;
case 0x47: /* Home */ break;
case 0x4F: /* End */ break;
case 0x49: /* Page Up */ break;
case 0x51: /* Page Down */ break;
}
// Modifiers
if (key.ctrl) { /* Ctrl held */ }
if (key.shift) { /* Shift held */ }
if (key.alt) { /* Alt held */ }
}
Mouse
// In standalone apps (via win_poll)
if (ev.type == 1) {
int mx = ev.mouse.x;
int my = ev.mouse.y;
// Button state
bool left_down = ev.mouse.buttons & 0x01;
bool right_down = ev.mouse.buttons & 0x02;
// Detect clicks (press edge)
bool left_pressed = (ev.mouse.buttons & 0x01) && !(ev.mouse.prev_buttons & 0x01);
bool left_released = !(ev.mouse.buttons & 0x01) && (ev.mouse.prev_buttons & 0x01);
// Scroll wheel
int scroll = ev.mouse.scroll; // Positive = up, negative = down
}
In desktop apps, the MouseEvent struct provides convenience methods:
void myapp_on_mouse(Window* win, MouseEvent& ev) {
if (ev.left_pressed()) { /* click start */ }
if (ev.left_released()) { /* click end */ }
if (ev.left_held()) { /* dragging */ }
if (ev.right_pressed()) { /* context menu */ }
if (ev.scroll != 0) { /* scroll */ }
}
Hit Testing
A common pattern for clickable UI regions:
struct ButtonRect { int x, y, w, h; };
bool hit_test(ButtonRect& btn, int mx, int my) {
return mx >= btn.x && mx < btn.x + btn.w &&
my >= btn.y && my < btn.y + btn.h;
}
Widgets
MontaukOS provides built-in widget types in gui/widgets.hpp:
Button
Button btn;
btn.init(x, y, width, height, "Click Me");
btn.bg = colors::ACCENT;
btn.fg = colors::WHITE;
btn.on_click = [](void* data) { /* handle click */ };
btn.userdata = my_state;
// In draw callback
btn.draw(fb);
// In mouse callback
btn.handle_mouse(ev);
TextBox
TextBox tb;
tb.init(x, y, width, height);
// In draw callback
tb.draw(fb);
// In mouse callback (sets focus)
tb.handle_mouse(ev);
// In key callback (text input)
tb.handle_key(key);
// Read value
const char* value = tb.text;
Scrollbar
Scrollbar sb;
sb.init(x, y, width, height);
sb.content_height = 2000; // Total content height
sb.view_height = 400; // Visible area height
// In draw callback
sb.draw(fb);
// In mouse callback
sb.handle_mouse(ev);
// Use scroll_offset for content positioning
int offset = sb.scroll_offset;
Colors and Theming
Color Construction
Color c1 = Color::from_rgb(0xFF, 0x00, 0x00); // Red
Color c2 = Color::from_rgba(0x00, 0x00, 0xFF, 0x80); // Semi-transparent blue
Color c3 = Color::from_hex(0x367BF0); // From hex
uint32_t pixel = c3.to_pixel(); // ARGB for pixel buffer
System Colors
Defined in gui/gui.hpp under the colors namespace:
| Constant | Hex | Usage |
|---|---|---|
WINDOW_BG |
#FFFFFF |
Window content background |
TEXT_COLOR |
#333333 |
Primary text |
ACCENT |
#367BF0 |
Links, selections, active elements |
BORDER |
#D0D0D0 |
Window/widget borders |
PANEL_BG |
#2B3E50 |
Taskbar/panel background |
PANEL_TEXT |
#FFFFFF |
Panel text |
TITLEBAR_BG |
#F5F5F5 |
Window titlebar |
DESKTOP_BG |
#E0E0E0 |
Desktop background |
CLOSE_BTN |
#FF5F57 |
Close button (red) |
MAX_BTN |
#28CA42 |
Maximize button (green) |
MIN_BTN |
#FFBD2E |
Minimize button (yellow) |
TERM_BG |
#2D2D2D |
Terminal background |
TERM_FG |
#CCCCCC |
Terminal text |
SCROLLBAR_BG |
Scrollbar track | |
SCROLLBAR_FG |
Scrollbar thumb |
Toolbar Convention
Standard toolbar pattern used across desktop apps:
// 36px tall, light gray background, thin bottom border
c.fill_rect(0, 0, win->content_w, 36, Color::from_rgb(0xF5, 0xF5, 0xF5));
c.hline(0, 36, win->content_w, colors::BORDER);
// 24x24 icon buttons centered at y=6
c.icon(8, 6, my_icon);
// Content starts below toolbar
int content_y = 37;
Memory Management
Userspace Heap
Use montauk::malloc, montauk::mfree, and montauk::realloc for dynamic allocation:
#include <montauk/heap.h>
MyState* state = (MyState*)montauk::malloc(sizeof(MyState));
montauk::memset(state, 0, sizeof(MyState));
// ... use state ...
montauk::mfree(state);
// Resize
char* buf = (char*)montauk::malloc(256);
buf = (char*)montauk::realloc(buf, 512);
The allocator uses size-class buckets (32 to 4096 bytes) with an overflow list for larger allocations.
Kernel Page Allocation
montauk::alloc / montauk::free allocate kernel pages. Avoid for temporary buffers; use the heap instead.
Important Notes
- User stack is 32 KiB (8 pages). Deep call chains (e.g., TrueType rendering) can approach this limit. Avoid large stack allocations.
- Use
inline(notstatic) for shared globals in headers to avoid per-translation-unit copies and heap corruption. - The libc needs
-fno-tree-loop-distribute-patternsin CFLAGS to prevent GCC from convertingmemcpy/memsetinto calls to themselves.
Networking and HTTPS
MontaukOS provides a shared TLS library (tls/tls.hpp) backed by BearSSL, and the MontaukAI dev environment adds a higher-level HTTP wrapper (http/http.hpp) on top. Build with USE_TLS=1 to link TLS support.
HTTP Wrapper (http/http.hpp)
Header-only library that handles DNS resolution, request building, TLS, response parsing, and cleanup. All functions return an http::Response struct.
Setup
#include <http/http.hpp>
// Load CA certificates once at startup (required for HTTPS)
tls::TrustAnchors tas = tls::load_trust_anchors();
GET
auto resp = http::get("api.example.com", "/v1/data", tas);
if (resp.status == 200) {
// resp.body is a pointer to the response body
// resp.body_len is its length
}
http::free_response(&resp);
POST
const char* json = "{\"name\":\"MontaukOS\",\"version\":1}";
auto resp = http::post("api.example.com", "/v1/submit",
"application/json",
json, montauk::slen(json),
tas);
if (resp.status == 201) {
// Created successfully
}
http::free_response(&resp);
Other Methods (PUT, PATCH, DELETE, ...)
auto resp = http::request("PUT", "api.example.com", "/v1/item/42",
"application/json",
body, bodyLen, tas);
http::free_response(&resp);
// DELETE with no body
auto resp2 = http::request("DELETE", "api.example.com", "/v1/item/42",
nullptr, nullptr, 0, tas);
http::free_response(&resp2);
Plain HTTP (No TLS, Port 80)
auto resp = http::get_plain("example.com", "/");
if (resp.status == 200) {
// resp.body ...
}
http::free_response(&resp);
Reading Response Headers
char content_type[128];
if (http::get_header(&resp, "Content-Type", content_type, sizeof(content_type))) {
// content_type is e.g. "application/json; charset=utf-8"
}
Custom Headers
Pass extra headers as a string with \r\n terminators:
auto resp = http::get("api.example.com", "/v1/data", tas,
32768, // response buffer size
"Authorization: Bearer tok_abc123\r\n"
"Accept: application/json\r\n");
http::free_response(&resp);
Cancellable Requests
For GUI apps that need to stay responsive during network I/O:
static bool g_quit = false;
static bool check_abort() { return g_quit; }
auto resp = http::get("api.example.com", "/v1/slow", tas,
32768, nullptr, check_abort);
http::free_response(&resp);
Set g_quit = true from your keyboard handler (e.g., on Escape) to cancel mid-request.
Response Struct Reference
struct http::Response {
int status; // HTTP status code (200, 404, ...) or -1 on error
const char* headers; // Pointer to header block (within raw buffer)
int headers_len;
const char* body; // Pointer to body (within raw buffer)
int body_len;
char* raw; // Owned buffer — freed by free_response()
int raw_len;
};
Function Signatures
// HTTPS GET
http::Response http::get(const char* host, const char* path,
const tls::TrustAnchors& tas,
int resp_buf_size = 32768,
const char* extra_headers = nullptr,
tls::AbortCheckFn abort_check = nullptr);
// HTTPS POST
http::Response http::post(const char* host, const char* path,
const char* content_type,
const char* body, int body_len,
const tls::TrustAnchors& tas,
int resp_buf_size = 32768,
const char* extra_headers = nullptr,
tls::AbortCheckFn abort_check = nullptr);
// HTTPS with any method
http::Response http::request(const char* method,
const char* host, const char* path,
const char* content_type,
const char* body, int body_len,
const tls::TrustAnchors& tas,
int resp_buf_size = 32768,
const char* extra_headers = nullptr,
tls::AbortCheckFn abort_check = nullptr);
// Plain HTTP GET (port 80, no TLS)
http::Response http::get_plain(const char* host, const char* path,
int resp_buf_size = 32768,
const char* extra_headers = nullptr);
// Parse raw HTTP response buffer (used internally, available if needed)
int http::parse_response(char* buf, int len, http::Response* out);
// Case-insensitive header lookup
bool http::get_header(const http::Response* resp, const char* name,
char* out_val, int max_len);
// Free the response's raw buffer
void http::free_response(http::Response* resp);
Low-Level TLS API (tls/tls.hpp)
If you need more control than http:: provides (e.g., streaming responses, custom BearSSL setup), use the TLS layer directly:
#include <tls/tls.hpp>
tls::TrustAnchors tas = tls::load_trust_anchors();
// Build raw HTTP request yourself
char req[512];
// ... "GET /stream HTTP/1.1\r\nHost: ...\r\n\r\n" ...
char buf[65536];
int n = tls::https_fetch("example.com", ip, 443, req, reqLen, tas, buf, sizeof(buf));
See syscalls.md for the full tls:: API reference.
Plain TCP/UDP
For non-TLS networking without the HTTP wrapper, use the raw socket syscalls directly (see syscalls.md):
int sock = montauk::socket(Montauk::SOCK_TCP); // or SOCK_UDP
montauk::connect(sock, ip, port);
montauk::send(sock, data, len);
int n = montauk::recv(sock, buf, maxLen);
montauk::closesocket(sock);
App Manifests
External standalone apps can be discovered by the desktop through TOML manifest files placed in 0:/apps/:
[app]
name = "My App"
binary = "myapp"
icon = "myapp_icon.svg"
[menu]
category = "Applications"
visible = true
Categories: Applications, Internet, System, Games
The desktop scans 0:/apps/ at startup and adds matching entries to the application menu. The binary field is the executable name (looked up in the system path).
Build System
Independent Development (MontaukAI)
The MontaukAI/ directory provides a self-contained build environment. Edit the top of Makefile to configure your app:
APP_NAME := myapp
SRCS := src/main.cpp src/stb_truetype_impl.cpp src/cxxrt.cpp src/network.cpp
Build with optional feature flags:
make # GUI-only app
make USE_TLS=1 # With HTTPS/TLS (links libtls + libbearssl)
make USE_JPEG=1 # With JPEG decoding (links libjpeg)
make USE_TLS=1 USE_JPEG=1 # Both
make install # Copy ELF to MontaukOS ramdisk
The sysroot contains all headers and pre-built libraries:
sysroot/
├── include/
│ ├── montauk/ syscall.h, heap.h, string.h, config.h, toml.h, user.h
│ ├── gui/ gui.hpp, canvas.hpp, truetype.hpp, widgets.hpp, svg.hpp, ...
│ ├── tls/ tls.hpp (HTTPS/TLS)
│ ├── Api/ Syscall.hpp (low-level syscall numbers)
│ ├── libc/ stdio.h, stdlib.h, string.h, ... (freestanding libc)
│ ├── bearssl*.h BearSSL headers (for USE_TLS=1)
│ └── (freestanding C/C++ standard headers)
└── lib/
├── crt1.o Startup shim for main(argc, argv) ports
├── crti.o CRT init prologue placeholder
├── crtn.o CRT init epilogue placeholder
├── liblibc.a C library (always linked)
├── libtls.a TLS helper library
├── libbearssl.a BearSSL crypto
└── libjpeg.a JPEG decoding (stb_image)
Key build details:
- Toolchain:
x86_64-elf-g++cross-compiler (falls back to system g++) - Standard: C++20 (
-std=gnu++20), freestanding, no exceptions/RTTI - SSE: Enabled (
-msse -msse2) for floating-point / TrueType rendering - Entry point:
extern "C" void _start()by default, ormain(argc, argv)withUSE_CRT=1 - Load address:
0x400000(set inlink.ld) - Runtime support:
src/cxxrt.cppprovidesoperator new/deleteviamontauk::malloc/mfree - TrueType support:
src/stb_truetype_impl.cppprovides the stb_truetype implementation
USE_CRT=1 is primarily for plain C ports and other code that already expects main(argc, argv). The shared CRT does not run C++ global constructors or destructors yet, so the default template still uses _start().
In-Tree Development
Standalone apps can also live in MontaukOS/programs/src/<appname>/ with their own Makefile. Each app's Makefile sets SRCS, CXXFLAGS, LDFLAGS, and links against libraries in programs/lib/.
Desktop-integrated apps are compiled as part of the desktop binary — add your .cpp file to the desktop's source list in programs/src/desktop/Makefile.