28 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 | File Manager, System Info, Settings |
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::abi::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::abi::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::abi::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::abi::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::abi::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]
id = "my-app"
name = "My App"
binary = "myapp.elf"
icon = "myapp_icon.svg"
[menu]
category = "Applications"
visible = true
[desktop]
section = "apps"
admin_only = false
[launch]
pass_home_dir = true
Categories: Applications, Internet, System, Games
The desktop scans 0:/apps/ at startup and builds one catalog used by the
application menu, launcher search, and the Apps and Settings virtual folders.
app.id must be unique; when omitted, the containing directory name is used.
The binary field names an executable in that app's bundle.
desktop.section accepts apps (the default), settings, or hidden.
Section placement is independent of menu.visible: a Settings item can be
hidden from the application menu while remaining directly searchable in the
launcher. Set desktop.admin_only to hide the item from non-administrator
users. launch.pass_home_dir controls whether the current user's home path is
passed as the executable's first argument.
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.