fix: sync template sysroot, add script

This commit is contained in:
2026-06-12 11:56:20 +02:00
parent 22691c5c17
commit 44f86a8b19
33 changed files with 5172 additions and 147 deletions
+152 -12
View File
@@ -57,8 +57,6 @@ namespace Montauk {
static constexpr uint64_t SYS_FDELETE = 77;
static constexpr uint64_t SYS_FMKDIR = 78;
static constexpr uint64_t SYS_DRIVELIST = 79;
static constexpr uint64_t SYS_DRIVELABEL = 124;
static constexpr uint64_t SYS_NETSTATUS = 125;
static constexpr uint64_t SYS_TERMSCALE = 43;
static constexpr uint64_t SYS_RESOLVE = 44;
static constexpr uint64_t SYS_GETRANDOM = 45;
@@ -78,6 +76,7 @@ namespace Montauk {
static constexpr uint64_t SYS_WINPOLL = 57;
static constexpr uint64_t SYS_WINENUM = 58;
static constexpr uint64_t SYS_WINMAP = 59;
static constexpr uint64_t SYS_WINUNMAP = 97;
static constexpr uint64_t SYS_WINSENDEVENT = 60;
static constexpr uint64_t SYS_WINRESIZE = 64;
static constexpr uint64_t SYS_WINSETSCALE = 65;
@@ -123,14 +122,100 @@ namespace Montauk {
static constexpr uint64_t SYS_SETTZ = 90;
static constexpr uint64_t SYS_GETTZ = 91;
// Audio control commands (for SYS_AUDIOCTL)
static constexpr int AUDIO_CTL_SET_VOLUME = 0;
static constexpr int AUDIO_CTL_GET_VOLUME = 1;
static constexpr int AUDIO_CTL_GET_POS = 2;
static constexpr int AUDIO_CTL_PAUSE = 3;
static constexpr int AUDIO_CTL_GET_OUTPUT = 4; // 0=HDA, 1=Bluetooth
static constexpr int AUDIO_CTL_SET_OUTPUT = 5; // Switch audio output
static constexpr int AUDIO_CTL_BT_STATUS = 6; // Get Bluetooth connection status
// User management
static constexpr uint64_t SYS_SETUSER = 92;
static constexpr uint64_t SYS_GETUSER = 93;
static constexpr uint64_t SYS_FRENAME = 94;
static constexpr uint64_t SYS_GETCWD = 95;
static constexpr uint64_t SYS_CHDIR = 96;
static constexpr uint64_t SYS_DUPHANDLE = 98;
static constexpr uint64_t SYS_WAIT_HANDLE = 99;
static constexpr uint64_t SYS_STREAM_CREATE = 100;
static constexpr uint64_t SYS_STREAM_READ = 101;
static constexpr uint64_t SYS_STREAM_WRITE = 102;
static constexpr uint64_t SYS_MAILBOX_CREATE = 103;
static constexpr uint64_t SYS_MAILBOX_SEND = 104;
static constexpr uint64_t SYS_MAILBOX_RECV = 105;
static constexpr uint64_t SYS_WAITSET_CREATE = 106;
static constexpr uint64_t SYS_WAITSET_ADD = 107;
static constexpr uint64_t SYS_WAITSET_REMOVE = 108;
static constexpr uint64_t SYS_WAITSET_WAIT = 109;
static constexpr uint64_t SYS_PROC_OPEN = 110;
static constexpr uint64_t SYS_SURFACE_CREATE = 111;
static constexpr uint64_t SYS_SURFACE_MAP = 112;
static constexpr uint64_t SYS_SURFACE_RESIZE = 113;
// Shared library syscalls
static constexpr uint64_t SYS_LOAD_LIB = 114;
static constexpr uint64_t SYS_UNLOAD_LIB = 115;
static constexpr uint64_t SYS_DLSYM = 116;
static constexpr uint64_t SYS_GETLIBBASE = 117;
/* CrashReport */
static constexpr uint64_t SYS_CRASH_REPORT = 118;
static constexpr uint64_t SYS_CLIPBOARD_SET_TEXT = 119;
static constexpr uint64_t SYS_CLIPBOARD_GET_INFO = 120;
static constexpr uint64_t SYS_CLIPBOARD_GET_TEXT = 121;
static constexpr uint64_t SYS_CLIPBOARD_CLEAR = 122;
static constexpr uint64_t SYS_INPUT_WAIT = 123;
static constexpr uint64_t SYS_DRIVELABEL = 124;
static constexpr uint64_t SYS_NETSTATUS = 125;
static constexpr uint64_t SYS_DRIVEKIND = 127;
// Audio mixer enumeration
static constexpr uint64_t SYS_AUDIOLIST = 128;
// Block until the mixer state serial differs from the caller's snapshot.
static constexpr uint64_t SYS_AUDIOWAIT = 129;
// Threading
static constexpr uint64_t SYS_THREAD_SPAWN = 130;
static constexpr uint64_t SYS_THREAD_EXIT = 131;
static constexpr uint64_t SYS_THREAD_JOIN = 132;
static constexpr uint64_t SYS_THREAD_SELF = 133;
// Flush + unmount persistent volumes for power-off
static constexpr uint64_t SYS_FS_SYNC = 134;
// Cross-process graceful power-off request channel
static constexpr uint64_t SYS_POWER_REQUEST = 135;
// Paginated directory read (path, names, max, startIndex)
static constexpr uint64_t SYS_READDIR_AT = 136;
// Graceful power-off request actions (SYS_POWER_REQUEST). The desktop posts
// a pending action and exits; login.elf reads it, runs the shutdown stages,
// then issues the matching SYS_SHUTDOWN / SYS_RESET.
enum PowerRequestAction : int {
POWER_REQ_QUERY = 0, // read-and-clear the pending action
POWER_REQ_SHUTDOWN = 1,
POWER_REQ_REBOOT = 2,
};
static constexpr uint32_t CLIPBOARD_MAX_TEXT_BYTES = 256 * 1024;
static constexpr uint32_t IPC_SIGNAL_READABLE = 1u << 0;
static constexpr uint32_t IPC_SIGNAL_WRITABLE = 1u << 1;
static constexpr uint32_t IPC_SIGNAL_PEER_CLOSED = 1u << 2;
static constexpr uint32_t IPC_SIGNAL_EXITED = 1u << 3;
static constexpr uint32_t IPC_SIGNAL_READY = 1u << 4;
// Audio control commands (for SYS_AUDIOCTL).
//
// Commands 0..3 act on the stream named by the handle argument.
// Commands 7..12 act on the global master and ignore the handle.
static constexpr int AUDIO_CTL_SET_VOLUME = 0;
static constexpr int AUDIO_CTL_GET_VOLUME = 1;
static constexpr int AUDIO_CTL_GET_POS = 2;
static constexpr int AUDIO_CTL_PAUSE = 3;
static constexpr int AUDIO_CTL_GET_OUTPUT = 4; // 0=HDA, 1=Bluetooth
static constexpr int AUDIO_CTL_SET_OUTPUT = 5; // Switch audio output
static constexpr int AUDIO_CTL_BT_STATUS = 6; // Get Bluetooth status
static constexpr int AUDIO_CTL_SET_MASTER_VOLUME = 7; // 0-100
static constexpr int AUDIO_CTL_GET_MASTER_VOLUME = 8;
static constexpr int AUDIO_CTL_SET_MUTE = 9; // 0/1, per-stream
static constexpr int AUDIO_CTL_GET_MUTE = 10;
static constexpr int AUDIO_CTL_SET_MASTER_MUTE = 11;
static constexpr int AUDIO_CTL_GET_MASTER_MUTE = 12;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
@@ -176,6 +261,7 @@ namespace Montauk {
char osVersion[32];
uint32_t apiVersion;
uint32_t maxProcesses;
uint32_t buildNumber; // monotonic kernel build number
};
struct KeyEvent {
@@ -194,6 +280,31 @@ namespace Montauk {
uint8_t buttons;
};
struct IpcWaitResult {
int32_t index;
uint32_t signals;
};
struct ClipboardInfo {
uint32_t textBytes;
uint32_t _pad;
uint64_t serial;
};
// One entry per active mixer stream; returned by SYS_AUDIOLIST.
struct AudioStreamInfo {
int32_t handle;
int32_t ownerPid;
char name[64];
uint32_t sampleRate;
uint8_t channels;
uint8_t bitsPerSample;
uint8_t volume; // 0-100
uint8_t muted; // 0/1
uint8_t paused; // 0/1
uint8_t _pad[7];
};
// Window server shared types
struct WinEvent {
uint8_t type; // 0=key, 1=mouse, 2=resize, 3=close, 4=scale
@@ -226,7 +337,7 @@ namespace Montauk {
};
struct DevInfo {
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI
uint8_t category; // 0=CPU, 1=Interrupt, 2=Timer, 3=Input, 4=USB, 5=Network, 6=Display, 7=Storage, 8=PCI, 9=Audio, 10=ACPI
uint8_t _pad[3];
char name[48];
char detail[48];
@@ -234,7 +345,7 @@ namespace Montauk {
struct DiskInfo {
uint8_t port; // block device index
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe
uint8_t type; // 0=none, 1=SATA, 2=SATAPI, 3=NVMe, 4=USB mass storage
uint8_t sataGen; // SATA gen (1/2/3)
uint8_t _pad0;
uint64_t sectorCount; // Total user-addressable sectors
@@ -322,6 +433,12 @@ namespace Montauk {
char name[64];
};
struct ThermalInfo {
char name[32]; // short zone name (e.g. "THRM", "TZ00")
int32_t temperature; // tenths of degrees Celsius, or -1 if unavailable
uint32_t _pad;
};
struct ProcInfo {
int32_t pid;
int32_t parentPid;
@@ -339,4 +456,27 @@ namespace Montauk {
uint64_t pageSize;
};
// Crash report (filled by kernel on process fault, returned via SYS_CRASH_REPORT)
struct CrashReportInfo {
int pid;
char processName[64];
uint8_t exceptionVector;
char exceptionName[32];
uint64_t faultingAddress;
uint64_t instructionPointer;
uint64_t stackPointer;
uint64_t codeSegment;
uint64_t flags;
uint64_t stackSegment;
uint8_t pfPresent : 1;
uint8_t pfWrite : 1;
uint8_t pfUser : 1;
uint8_t pfReservedWrite : 1;
uint8_t pfInstructionFetch : 1;
uint8_t pfProtectionKey : 1;
uint8_t pfShadowStack : 1;
uint8_t pfSGX : 1;
uint64_t timestampTick;
};
}
+29 -39
View File
@@ -71,6 +71,35 @@ struct Canvas {
}
}
void fill_rect_alpha(int x, int y, int rw, int rh, Color c) {
if (c.a == 0) return;
if (c.a == 255) { fill_rect(x, y, rw, rh, c); return; }
int x0 = gui_max(x, 0), y0 = gui_max(y, 0);
int x1 = gui_min(x + rw, w), y1 = gui_min(y + rh, h);
if (x0 >= x1 || y0 >= y1) return;
uint32_t a = c.a;
uint32_t inv_a = 255 - a;
uint32_t src_r = a * c.r;
uint32_t src_g = a * c.g;
uint32_t src_b = a * c.b;
for (int dy = y0; dy < y1; dy++) {
uint32_t* row = pixels + dy * w;
for (int dx = x0; dx < x1; dx++) {
uint32_t p = row[dx];
uint32_t dr = (p >> 16) & 0xFF;
uint32_t dg = (p >> 8) & 0xFF;
uint32_t db = p & 0xFF;
uint32_t nr = (src_r + dr * inv_a) / 255;
uint32_t ng = (src_g + dg * inv_a) / 255;
uint32_t nb = (src_b + db * inv_a) / 255;
row[dx] = (0xFFu << 24) | (nr << 16) | (ng << 8) | nb;
}
}
}
void fill_rounded_rect(int x, int y, int rw, int rh, int radius, Color c) {
if (radius <= 0) { fill_rect(x, y, rw, rh, c); return; }
uint32_t px = c.to_pixel();
@@ -135,51 +164,12 @@ struct Canvas {
void text(int x, int y, const char* str, Color c) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::UI_SIZE);
return;
}
uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
int cx = x + i * FONT_WIDTH;
for (int fy = 0; fy < FONT_HEIGHT && y + fy < h; fy++) {
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx;
int dy = y + fy;
if (dx >= 0 && dx < w && dy >= 0)
pixels[dy * w + dx] = px;
}
}
}
}
}
void text_2x(int x, int y, const char* str, Color c) {
if (fonts::system_font && fonts::system_font->valid) {
fonts::system_font->draw_to_buffer(pixels, w, h, x, y, str, c, fonts::LARGE_SIZE);
return;
}
uint32_t px = c.to_pixel();
for (int i = 0; str[i] && x + (i + 1) * FONT_WIDTH * 2 <= w; i++) {
const uint8_t* glyph = &font_data[(unsigned char)str[i] * FONT_HEIGHT];
int cx = x + i * FONT_WIDTH * 2;
for (int fy = 0; fy < FONT_HEIGHT; fy++) {
uint8_t bits = glyph[fy];
for (int fx = 0; fx < FONT_WIDTH; fx++) {
if (bits & (0x80 >> fx)) {
int dx = cx + fx * 2;
int dy = y + fy * 2;
for (int sy = 0; sy < 2; sy++)
for (int sx = 0; sx < 2; sx++) {
int pdx = dx + sx;
int pdy = dy + sy;
if (pdx >= 0 && pdx < w && pdy >= 0 && pdy < h)
pixels[pdy * w + pdx] = px;
}
}
}
}
}
}
@@ -0,0 +1,65 @@
/*
* clipboard.hpp
* Simple plain-text clipboard helpers for MontaukOS GUI apps
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <montauk/syscall.h>
#include <montauk/string.h>
#include <montauk/heap.h>
namespace gui {
inline bool clipboard_set_text(const char* text, int len) {
if (len < 0) return false;
if (len > 0 && text == nullptr) return false;
return montauk::clipboard_set_text(text, (uint32_t)len) == 0;
}
inline bool clipboard_set_text(const char* text) {
return clipboard_set_text(text, text ? montauk::slen(text) : 0);
}
inline bool clipboard_get_info(Montauk::ClipboardInfo* out) {
if (out == nullptr) return false;
return montauk::clipboard_get_info(out) == 0;
}
inline bool clipboard_has_text() {
Montauk::ClipboardInfo info = {};
return clipboard_get_info(&info) && info.textBytes > 0;
}
inline char* clipboard_get_text_alloc(int* out_len = nullptr, uint64_t* out_serial = nullptr) {
if (out_len) *out_len = 0;
if (out_serial) *out_serial = 0;
Montauk::ClipboardInfo info = {};
if (!clipboard_get_info(&info) || info.textBytes == 0)
return nullptr;
uint32_t cap = info.textBytes + 1;
char* text = (char*)montauk::malloc(cap);
if (text == nullptr) return nullptr;
uint32_t actual_len = 0;
uint64_t serial = 0;
if (montauk::clipboard_get_text(text, info.textBytes, &actual_len, &serial) != 0 ||
actual_len > info.textBytes) {
montauk::mfree(text);
return nullptr;
}
text[actual_len] = '\0';
if (out_len) *out_len = (int)actual_len;
if (out_serial) *out_serial = serial;
return text;
}
inline bool clipboard_clear() {
return montauk::clipboard_clear() == 0;
}
}
+62 -1
View File
@@ -18,17 +18,37 @@ namespace gui {
static constexpr int MAX_WINDOWS = 8;
static constexpr int PANEL_HEIGHT = 32;
static constexpr int MAX_EXTERNAL_APPS = 32;
static constexpr int MAX_LAUNCHER_ITEMS = 64;
// External app discovered from 0:/apps/ manifest
struct ExternalApp {
char name[48];
char binary_path[128];
char icon_path[128];
char category[24];
SvgIcon icon;
bool menu_visible;
bool launch_with_home;
};
enum LauncherItemKind : uint8_t {
LAUNCHER_ITEM_EXTERNAL_APP = 0,
LAUNCHER_ITEM_BUILTIN_FILES,
LAUNCHER_ITEM_COMMAND_REBOOT,
LAUNCHER_ITEM_COMMAND_SHUTDOWN,
LAUNCHER_ITEM_SPECIAL_FOLDER,
};
struct LauncherItem {
char name[64];
char category[48];
char path[128];
char search_text[192];
SvgIcon* icon;
LauncherItemKind kind;
int ref_index;
};
struct DesktopSettings {
// Background
bool bg_gradient; // true = gradient, false = solid
@@ -72,6 +92,17 @@ struct DesktopState {
uint8_t prev_buttons;
bool app_menu_open;
bool launcher_open;
char launcher_query[64];
int launcher_query_len;
LauncherItem launcher_items[MAX_LAUNCHER_ITEMS];
int launcher_item_count;
int launcher_results[MAX_LAUNCHER_ITEMS];
int launcher_result_count;
int launcher_selected;
int launcher_scroll;
bool super_left_down;
bool super_right_down;
SvgIcon icon_terminal;
SvgIcon icon_filemanager;
@@ -80,11 +111,11 @@ struct DesktopState {
SvgIcon icon_folder;
SvgIcon icon_file;
SvgIcon icon_network;
SvgIcon icon_calculator;
SvgIcon icon_go_up;
SvgIcon icon_go_back;
SvgIcon icon_go_forward;
SvgIcon icon_home;
SvgIcon icon_refresh;
SvgIcon icon_exec;
SvgIcon icon_folder_lg;
@@ -92,7 +123,26 @@ struct DesktopState {
SvgIcon icon_exec_lg;
SvgIcon icon_drive;
SvgIcon icon_drive_lg;
SvgIcon icon_drive_usb;
SvgIcon icon_drive_usb_lg;
SvgIcon icon_delete;
SvgIcon icon_copy;
SvgIcon icon_cut;
SvgIcon icon_paste;
SvgIcon icon_rename;
SvgIcon icon_folder_new;
SvgIcon icon_home_folder;
SvgIcon icon_home_folder_lg;
SvgIcon icon_apps;
SvgIcon icon_apps_lg;
SvgIcon icon_system_configuration_menu;
SvgIcon icon_system_configuration;
SvgIcon icon_system_configuration_lg;
// Special user folder icons (16x16 and 48x48)
static constexpr int SPECIAL_FOLDER_COUNT = 6;
SvgIcon icon_special_folder[SPECIAL_FOLDER_COUNT];
SvgIcon icon_special_folder_lg[SPECIAL_FOLDER_COUNT];
SvgIcon icon_settings;
SvgIcon icon_reboot;
@@ -121,8 +171,19 @@ struct DesktopState {
int vol_pre_mute; // volume before mute
bool vol_dragging; // slider drag in progress
uint64_t vol_last_poll;
uint64_t vol_serial; // last seen mixer state serial
// Temperature monitoring
static constexpr int MAX_THERMAL_ZONES = 8;
Montauk::ThermalInfo thermal_zones[MAX_THERMAL_ZONES];
int thermal_zone_count;
uint64_t thermal_last_poll;
Rect temp_icon_rect;
int screen_w, screen_h;
uint32_t* background_cache;
int background_cache_pitch;
bool background_cache_dirty;
// IDs of external windows we've sent a close event to but that haven't
// been destroyed yet by their owning process. Prevents the poll loop
+248
View File
@@ -0,0 +1,248 @@
/*
* dialogs.hpp
* Shared dialog request/result structures and client helpers
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <montauk/string.h>
#ifndef GUI_DIALOGS_LIBRARY_BUILD
#include <libloader/libloader.h>
#endif
namespace gui::dialogs {
inline constexpr const char* LIBRARY_PATH = "0:/os/libdialogs.lib";
inline constexpr const char* RUN_REQUEST_SYMBOL = "dialogs_run_request";
inline constexpr const char* MESSAGE_BOX_SYMBOL = "dialogs_message_box";
enum RequestKind : uint8_t {
REQUEST_KIND_FILE = 1,
REQUEST_KIND_PRINT = 2,
};
enum FileDialogMode : uint8_t {
FILE_DIALOG_OPEN = 1,
FILE_DIALOG_SAVE = 2,
};
enum PrintDialogMode : uint8_t {
PRINT_DIALOG_SETUP = 1,
PRINT_DIALOG_SUBMIT = 2,
};
enum MessageBoxButtons : uint8_t {
MESSAGE_BOX_OK = 1,
MESSAGE_BOX_OK_CANCEL = 2,
MESSAGE_BOX_YES_NO = 3,
MESSAGE_BOX_YES_NO_CANCEL = 4,
};
enum MessageBoxResult : uint8_t {
MESSAGE_BOX_RESULT_NONE = 0,
MESSAGE_BOX_RESULT_OK = 1,
MESSAGE_BOX_RESULT_CANCEL = 2,
MESSAGE_BOX_RESULT_YES = 3,
MESSAGE_BOX_RESULT_NO = 4,
};
struct Request {
uint8_t kind;
uint8_t mode;
char title[96];
char initial_path[256];
char suggested_name[128];
char source_path[256];
char job_name[128];
char printer_uri[256];
uint32_t copies;
};
struct Result {
char status[16];
char path[256];
char job_id[64];
char printer_uri[256];
char printer_name[128];
uint32_t copies;
char message[160];
};
inline void safe_copy(char* dst, int dst_len, const char* src) {
if (!dst || dst_len <= 0) return;
if (!src) src = "";
montauk::strncpy(dst, src, dst_len - 1);
dst[dst_len - 1] = '\0';
}
inline void reset_request(Request* req) {
if (!req) return;
montauk::memset(req, 0, sizeof(Request));
}
inline void reset_result(Result* res) {
if (!res) return;
montauk::memset(res, 0, sizeof(Result));
}
#ifndef GUI_DIALOGS_LIBRARY_BUILD
using RunRequestFn = bool (*)(const Request*, Result*);
using MessageBoxFn = uint8_t (*)(const char*, const char*, uint8_t);
struct Runtime {
LibHandle* handle;
RunRequestFn run_request;
MessageBoxFn message_box;
};
inline Runtime g_runtime = {};
inline bool ensure_handle(char* out_message, int out_message_len) {
if (g_runtime.handle) return true;
g_runtime.handle = libloader::dlopen(LIBRARY_PATH);
if (!g_runtime.handle) {
if (out_message && out_message_len > 0)
safe_copy(out_message, out_message_len, "failed to load dialog library");
return false;
}
return true;
}
inline RunRequestFn resolve_run_request(char* out_message, int out_message_len) {
if (g_runtime.run_request) return g_runtime.run_request;
if (!ensure_handle(out_message, out_message_len)) return nullptr;
g_runtime.run_request = (RunRequestFn)libloader::dlsym(g_runtime.handle, RUN_REQUEST_SYMBOL);
if (!g_runtime.run_request && out_message && out_message_len > 0)
safe_copy(out_message, out_message_len, "failed to resolve dialog entry point");
return g_runtime.run_request;
}
inline MessageBoxFn resolve_message_box(char* out_message, int out_message_len) {
if (g_runtime.message_box) return g_runtime.message_box;
if (!ensure_handle(out_message, out_message_len)) return nullptr;
g_runtime.message_box = (MessageBoxFn)libloader::dlsym(g_runtime.handle, MESSAGE_BOX_SYMBOL);
if (!g_runtime.message_box && out_message && out_message_len > 0)
safe_copy(out_message, out_message_len, "failed to resolve message box entry point");
return g_runtime.message_box;
}
inline bool run_request(const Request* req, Result& res, char* out_message, int out_message_len) {
if (out_message && out_message_len > 0) out_message[0] = '\0';
if (!req) {
if (out_message && out_message_len > 0)
safe_copy(out_message, out_message_len, "invalid dialog request");
return false;
}
RunRequestFn run_fn = resolve_run_request(out_message, out_message_len);
if (!run_fn) return false;
if (!run_fn(req, &res)) {
if (out_message && out_message_len > 0 && !out_message[0])
safe_copy(out_message, out_message_len, "dialog library invocation failed");
return false;
}
if (out_message && out_message_len > 0)
safe_copy(out_message, out_message_len, res.message);
return montauk::streq(res.status, "ok");
}
inline bool open_file(const char* title,
const char* initial_path,
char* out_path, int out_path_len,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_FILE;
req.mode = FILE_DIALOG_OPEN;
safe_copy(req.title, sizeof(req.title), title ? title : "Open");
safe_copy(req.initial_path, sizeof(req.initial_path), initial_path);
Result res = {};
bool ok = run_request(&req, res, out_message, out_message_len);
if (ok && out_path && out_path_len > 0) safe_copy(out_path, out_path_len, res.path);
return ok;
}
inline bool save_file(const char* title,
const char* initial_path,
const char* suggested_name,
char* out_path, int out_path_len,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_FILE;
req.mode = FILE_DIALOG_SAVE;
safe_copy(req.title, sizeof(req.title), title ? title : "Save");
safe_copy(req.initial_path, sizeof(req.initial_path), initial_path);
safe_copy(req.suggested_name, sizeof(req.suggested_name), suggested_name);
Result res = {};
bool ok = run_request(&req, res, out_message, out_message_len);
if (ok && out_path && out_path_len > 0) safe_copy(out_path, out_path_len, res.path);
return ok;
}
inline bool print_file(const char* title,
const char* source_path,
const char* job_name,
char* out_job_id, int out_job_id_len,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_PRINT;
req.mode = PRINT_DIALOG_SUBMIT;
req.copies = 1;
safe_copy(req.title, sizeof(req.title), title ? title : "Print");
safe_copy(req.source_path, sizeof(req.source_path), source_path);
safe_copy(req.job_name, sizeof(req.job_name), job_name);
Result res = {};
bool ok = run_request(&req, res, out_message, out_message_len);
if (ok && out_job_id && out_job_id_len > 0) safe_copy(out_job_id, out_job_id_len, res.job_id);
return ok;
}
inline bool configure_print(const char* title,
const char* initial_printer_uri,
const char* job_name,
char* out_printer_uri, int out_printer_uri_len,
char* out_printer_name, int out_printer_name_len,
uint32_t* out_copies = nullptr,
char* out_message = nullptr, int out_message_len = 0) {
Request req = {};
req.kind = REQUEST_KIND_PRINT;
req.mode = PRINT_DIALOG_SETUP;
req.copies = out_copies && *out_copies > 0 ? *out_copies : 1;
safe_copy(req.title, sizeof(req.title), title ? title : "Print");
safe_copy(req.job_name, sizeof(req.job_name), job_name ? job_name : "");
safe_copy(req.printer_uri, sizeof(req.printer_uri), initial_printer_uri);
Result res = {};
bool ok = run_request(&req, res, out_message, out_message_len);
if (ok && out_printer_uri && out_printer_uri_len > 0) safe_copy(out_printer_uri, out_printer_uri_len, res.printer_uri);
if (ok && out_printer_name && out_printer_name_len > 0) safe_copy(out_printer_name, out_printer_name_len, res.printer_name);
if (ok && out_copies) *out_copies = res.copies > 0 ? res.copies : 1;
return ok;
}
inline MessageBoxResult message_box(const char* title,
const char* message,
MessageBoxButtons buttons = MESSAGE_BOX_OK,
char* out_message = nullptr, int out_message_len = 0) {
if (out_message && out_message_len > 0) out_message[0] = '\0';
MessageBoxFn fn = resolve_message_box(out_message, out_message_len);
if (!fn) return MESSAGE_BOX_RESULT_NONE;
uint8_t result = fn(title ? title : "Message",
message ? message : "",
(uint8_t)buttons);
if (result == MESSAGE_BOX_RESULT_NONE && out_message && out_message_len > 0 && !out_message[0])
safe_copy(out_message, out_message_len, "message box invocation failed");
return (MessageBoxResult)result;
}
#endif
} // namespace gui::dialogs
+3 -2
View File
@@ -12,16 +12,17 @@ namespace gui {
// Fast horizontal line
inline void draw_hline(Framebuffer& fb, int x, int y, int w, Color c) {
for (int i = 0; i < w; i++) fb.put_pixel(x + i, y, c);
fb.fill_rect(x, y, w, 1, c);
}
// Fast vertical line
inline void draw_vline(Framebuffer& fb, int x, int y, int h, Color c) {
for (int i = 0; i < h; i++) fb.put_pixel(x, y + i, c);
fb.fill_rect(x, y, 1, h, c);
}
// Rectangle outline
inline void draw_rect(Framebuffer& fb, int x, int y, int w, int h, Color c) {
if (w <= 0 || h <= 0) return;
draw_hline(fb, x, y, w, c);
draw_hline(fb, x, y + h - 1, w, c);
draw_vline(fb, x, y, h, c);
+99 -31
View File
@@ -19,6 +19,46 @@ class Framebuffer {
int fb_height;
int fb_pitch; // in bytes
static inline void fill_pixels(uint32_t* dst, int count, uint32_t pixel) {
if (!dst || count <= 0) return;
uint64_t pixel64 = ((uint64_t)pixel << 32) | pixel;
if (((uint64_t)dst & 4) && count > 0) {
*dst++ = pixel;
count--;
}
uint64_t* dst64 = (uint64_t*)dst;
int pairs = count / 2;
for (int i = 0; i < pairs; i++) {
dst64[i] = pixel64;
}
if (count & 1) {
((uint32_t*)(dst64 + pairs))[0] = pixel;
}
}
static inline uint32_t blend_pixel(uint32_t dst, uint32_t src, uint32_t alpha) {
uint32_t inv_a = 255 - alpha;
uint32_t sr = (src >> 16) & 0xFF;
uint32_t sg = (src >> 8) & 0xFF;
uint32_t sb = src & 0xFF;
uint32_t dr = (dst >> 16) & 0xFF;
uint32_t dg = (dst >> 8) & 0xFF;
uint32_t db = dst & 0xFF;
uint32_t rr = alpha * sr + inv_a * dr;
uint32_t gg = alpha * sg + inv_a * dg;
uint32_t bb = alpha * sb + inv_a * db;
rr = (rr + 1 + (rr >> 8)) >> 8;
gg = (gg + 1 + (gg >> 8)) >> 8;
bb = (bb + 1 + (bb >> 8)) >> 8;
return 0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
public:
Framebuffer() : hw_fb(nullptr), back_buf(nullptr), fb_width(0), fb_height(0), fb_pitch(0) {
Montauk::FbInfo info;
@@ -76,6 +116,8 @@ public:
}
inline void fill_rect(int x, int y, int w, int h, Color c) {
if (!back_buf) return;
// Clip to screen bounds
int x0 = x < 0 ? 0 : x;
int y0 = y < 0 ? 0 : y;
@@ -89,9 +131,7 @@ public:
for (int row = y0; row < y1; row++) {
uint32_t* dst = (uint32_t*)((uint8_t*)back_buf + row * fb_pitch) + x0;
for (int col = 0; col < clipped_w; col++) {
dst[col] = pixel;
}
fill_pixels(dst, clipped_w, pixel);
}
}
@@ -137,56 +177,84 @@ public:
}
inline void blit(int x, int y, int w, int h, const uint32_t* pixels) {
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= fb_height) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= fb_width) continue;
uint32_t* dst_row = (uint32_t*)((uint8_t*)back_buf + dy * fb_pitch);
dst_row[dx] = pixels[row * w + col];
}
if (!back_buf || !pixels || w <= 0 || h <= 0) return;
int src_x = 0;
int src_y = 0;
int dst_x = x;
int dst_y = y;
int copy_w = w;
int copy_h = h;
if (dst_x < 0) { src_x = -dst_x; copy_w += dst_x; dst_x = 0; }
if (dst_y < 0) { src_y = -dst_y; copy_h += dst_y; dst_y = 0; }
if (dst_x + copy_w > fb_width) copy_w = fb_width - dst_x;
if (dst_y + copy_h > fb_height) copy_h = fb_height - dst_y;
if (copy_w <= 0 || copy_h <= 0) return;
uint64_t row_bytes = (uint64_t)copy_w * sizeof(uint32_t);
for (int row = 0; row < copy_h; row++) {
uint32_t* dst = (uint32_t*)((uint8_t*)back_buf + (dst_y + row) * fb_pitch) + dst_x;
const uint32_t* src = pixels + (src_y + row) * w + src_x;
montauk::memcpy(dst, src, row_bytes);
}
}
inline void blit_alpha(int x, int y, int w, int h, const uint32_t* pixels) {
for (int row = 0; row < h; row++) {
int dy = y + row;
if (dy < 0 || dy >= fb_height) continue;
for (int col = 0; col < w; col++) {
int dx = x + col;
if (dx < 0 || dx >= fb_width) continue;
if (!back_buf || !pixels || w <= 0 || h <= 0) return;
uint32_t src = pixels[row * w + col];
uint8_t sa = (src >> 24) & 0xFF;
int src_x = 0;
int src_y = 0;
int dst_x = x;
int dst_y = y;
int copy_w = w;
int copy_h = h;
if (dst_x < 0) { src_x = -dst_x; copy_w += dst_x; dst_x = 0; }
if (dst_y < 0) { src_y = -dst_y; copy_h += dst_y; dst_y = 0; }
if (dst_x + copy_w > fb_width) copy_w = fb_width - dst_x;
if (dst_y + copy_h > fb_height) copy_h = fb_height - dst_y;
if (copy_w <= 0 || copy_h <= 0) return;
for (int row = 0; row < copy_h; row++) {
uint32_t* dst = (uint32_t*)((uint8_t*)back_buf + (dst_y + row) * fb_pitch) + dst_x;
const uint32_t* src = pixels + (src_y + row) * w + src_x;
for (int col = 0; col < copy_w; col++) {
uint32_t s = src[col];
uint8_t sa = (s >> 24) & 0xFF;
if (sa == 0) continue;
uint8_t sr = (src >> 16) & 0xFF;
uint8_t sg = (src >> 8) & 0xFF;
uint8_t sb = src & 0xFF;
if (sa == 255) {
uint32_t* dst_row = (uint32_t*)((uint8_t*)back_buf + dy * fb_pitch);
dst_row[dx] = src;
dst[col] = s;
continue;
}
Color sc = {sr, sg, sb, sa};
put_pixel_alpha(dx, dy, sc);
dst[col] = blend_pixel(dst[col], s, sa);
}
}
}
inline void copy_from(const uint32_t* pixels, int src_pitch_bytes) {
if (!back_buf || !pixels || src_pitch_bytes <= 0) return;
uint64_t row_bytes = (uint64_t)fb_width * sizeof(uint32_t);
for (int row = 0; row < fb_height; row++) {
uint32_t* dst = (uint32_t*)((uint8_t*)back_buf + row * fb_pitch);
const uint32_t* src = (const uint32_t*)((const uint8_t*)pixels + row * src_pitch_bytes);
montauk::memcpy(dst, src, row_bytes);
}
}
inline void clear(Color c) {
fill_rect(0, 0, fb_width, fb_height, c);
}
inline void flip() {
if (!hw_fb || !back_buf) return;
// Copy back buffer to hardware framebuffer, row by row (pitch may differ)
uint64_t row_bytes = (uint64_t)fb_width * sizeof(uint32_t);
for (int y = 0; y < fb_height; y++) {
void* src = (void*)((uint8_t*)back_buf + y * fb_pitch);
void* dst = (void*)((uint8_t*)hw_fb + y * fb_pitch);
uint32_t* src = (uint32_t*)((uint8_t*)back_buf + y * fb_pitch);
uint32_t* dst = (uint32_t*)((uint8_t*)hw_fb + y * fb_pitch);
montauk::memcpy(dst, src, row_bytes);
}
}
+13
View File
@@ -0,0 +1,13 @@
/*
* mtk.hpp
* Umbrella include for the Montauk Toolkit
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/mtk/theme.hpp"
#include "gui/mtk/widgets.hpp"
#include "gui/mtk/graph.hpp"
#include "gui/mtk/table.hpp"
#include "gui/mtk/hosts.hpp"
+290
View File
@@ -0,0 +1,290 @@
/*
* graph.hpp
* Montauk Toolkit graph helpers
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/mtk/widgets.hpp"
namespace gui::mtk {
struct GraphHistory {
uint64_t* samples;
int capacity;
int count;
int head;
};
struct GraphStyle {
Color background;
Color border;
Color plot_bg;
Color grid;
Color label;
Color value;
Color line;
Color fill;
int radius;
int padding;
int grid_x;
int grid_y;
int line_thickness;
bool fill_area;
};
inline GraphStyle make_graph_style(const Theme& theme, Color line) {
return {
theme.window_bg,
theme.border,
theme.surface_alt,
mix(theme.surface_alt, line, 18),
theme.text_subtle,
theme.text,
line,
lighten(line, 218),
theme.radius_md,
8,
4,
4,
2,
true,
};
}
inline void graph_history_init(GraphHistory* history, uint64_t* samples, int capacity) {
if (!history) return;
history->samples = samples;
history->capacity = capacity;
history->count = 0;
history->head = 0;
}
inline void graph_history_clear(GraphHistory* history) {
if (!history) return;
history->count = 0;
history->head = 0;
}
inline void graph_history_push(GraphHistory* history, uint64_t value) {
if (!history || !history->samples || history->capacity <= 0) return;
history->samples[history->head] = value;
history->head = (history->head + 1) % history->capacity;
if (history->count < history->capacity)
history->count++;
}
inline uint64_t graph_history_at(const GraphHistory& history, int index) {
if (!history.samples || history.capacity <= 0 ||
index < 0 || index >= history.count) {
return 0;
}
int start = history.head - history.count;
while (start < 0) start += history.capacity;
return history.samples[(start + index) % history.capacity];
}
inline uint64_t graph_history_max(const GraphHistory& history) {
uint64_t max_value = 0;
for (int i = 0; i < history.count; i++) {
uint64_t value = graph_history_at(history, i);
if (value > max_value) max_value = value;
}
return max_value;
}
inline int graph_sample_x(const Rect& plot, int index, int count) {
if (count <= 1) return plot.x + plot.w - 1;
return plot.x + (int)(((int64_t)index * (plot.w - 1)) / (count - 1));
}
inline int graph_sample_y(const Rect& plot, uint64_t sample, uint64_t max_value) {
if (plot.h <= 1) return plot.y;
if (max_value == 0) max_value = 1;
if (sample > max_value) sample = max_value;
uint64_t scaled = (sample * (uint64_t)(plot.h - 1)) / max_value;
return plot.y + plot.h - 1 - (int)scaled;
}
inline void graph_draw_point(Canvas& c,
const Rect& clip,
int x,
int y,
Color color,
int thickness) {
thickness = gui_max(thickness, 1);
int radius = thickness / 2;
for (int py = y - radius; py <= y - radius + thickness - 1; py++) {
for (int px = x - radius; px <= x - radius + thickness - 1; px++) {
if (clip.contains(px, py))
c.put_pixel(px, py, color);
}
}
}
inline void graph_draw_line(Canvas& c,
const Rect& clip,
int x0,
int y0,
int x1,
int y1,
Color color,
int thickness) {
int dx = gui_abs(x1 - x0);
int sx = x0 < x1 ? 1 : -1;
int dy = -gui_abs(y1 - y0);
int sy = y0 < y1 ? 1 : -1;
int err = dx + dy;
for (;;) {
graph_draw_point(c, clip, x0, y0, color, thickness);
if (x0 == x1 && y0 == y1) break;
int e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x0 += sx;
}
if (e2 <= dx) {
err += dx;
y0 += sy;
}
}
}
inline void graph_fill_segment(Canvas& c,
const Rect& clip,
int x0,
int y0,
int x1,
int y1,
Color color) {
int bottom = clip.y + clip.h - 1;
if (x0 == x1) {
int y = gui_min(y0, y1);
if (clip.contains(x0, y))
c.vline(x0, y, bottom - y + 1, color);
return;
}
if (x1 < x0) {
int tx = x0; x0 = x1; x1 = tx;
int ty = y0; y0 = y1; y1 = ty;
}
for (int x = x0; x <= x1; x++) {
if (x < clip.x || x >= clip.x + clip.w) continue;
int y = y0 + (int)(((int64_t)(y1 - y0) * (x - x0)) / (x1 - x0));
if (y < clip.y) y = clip.y;
if (y > bottom) y = bottom;
c.vline(x, y, bottom - y + 1, color);
}
}
inline void draw_line_graph(Canvas& c,
const Rect& bounds,
const GraphHistory& history,
uint64_t max_value,
const char* title,
const char* value_label,
const Theme& theme,
const GraphStyle& style) {
if (bounds.empty()) return;
draw_rounded_frame(c, bounds, style.radius, style.background, style.border);
int fh = system_font_height();
int pad = gui_max(style.padding, 4);
int title_y = bounds.y + pad;
int title_x = bounds.x + pad;
if (title && title[0])
c.text(title_x, title_y, title, style.label);
if (value_label && value_label[0]) {
int value_w = text_width(value_label);
int value_x = bounds.x + bounds.w - pad - value_w;
if (value_x > title_x)
c.text(value_x, title_y, value_label, style.value);
}
Rect plot = {
bounds.x + pad,
bounds.y + pad + fh + 7,
bounds.w - pad * 2,
bounds.h - pad * 2 - fh - 7
};
if (plot.empty()) return;
c.fill_rect(plot.x, plot.y, plot.w, plot.h, style.plot_bg);
int sample_count = history.count;
if (sample_count > 0) {
if (max_value == 0)
max_value = graph_history_max(history);
if (max_value == 0)
max_value = 1;
if (style.fill_area && sample_count > 1) {
int prev_x = graph_sample_x(plot, 0, sample_count);
int prev_y = graph_sample_y(plot, graph_history_at(history, 0), max_value);
for (int i = 1; i < sample_count; i++) {
int x = graph_sample_x(plot, i, sample_count);
int y = graph_sample_y(plot, graph_history_at(history, i), max_value);
graph_fill_segment(c, plot, prev_x, prev_y, x, y, style.fill);
prev_x = x;
prev_y = y;
}
}
}
if (style.grid_y > 0) {
for (int i = 1; i < style.grid_y; i++) {
int y = plot.y + (plot.h * i) / style.grid_y;
c.hline(plot.x, y, plot.w, style.grid);
}
}
if (style.grid_x > 0) {
for (int i = 1; i < style.grid_x; i++) {
int x = plot.x + (plot.w * i) / style.grid_x;
c.vline(x, plot.y, plot.h, style.grid);
}
}
c.rect(plot.x, plot.y, plot.w, plot.h, style.border);
if (sample_count == 1) {
int x = graph_sample_x(plot, 0, sample_count);
int y = graph_sample_y(plot, graph_history_at(history, 0), max_value);
graph_draw_point(c, plot, x, y, style.line, style.line_thickness + 1);
} else if (sample_count > 1) {
int prev_x = graph_sample_x(plot, 0, sample_count);
int prev_y = graph_sample_y(plot, graph_history_at(history, 0), max_value);
for (int i = 1; i < sample_count; i++) {
int x = graph_sample_x(plot, i, sample_count);
int y = graph_sample_y(plot, graph_history_at(history, i), max_value);
graph_draw_line(c, plot, prev_x, prev_y, x, y,
style.line, style.line_thickness);
prev_x = x;
prev_y = y;
}
}
(void)theme;
}
inline void draw_line_graph(Canvas& c,
const Rect& bounds,
const GraphHistory& history,
uint64_t max_value,
const char* title,
const char* value_label,
const Theme& theme,
Color line) {
GraphStyle style = make_graph_style(theme, line);
draw_line_graph(c, bounds, history, max_value, title, value_label, theme, style);
}
} // namespace gui::mtk
@@ -0,0 +1,63 @@
/*
* hosts.hpp
* Montauk Toolkit host adapters for desktop and standalone apps
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/canvas.hpp"
#include "gui/standalone.hpp"
#include "gui/window.hpp"
namespace gui::mtk {
struct DesktopHost {
Window* window;
explicit DesktopHost(Window* win) : window(win) {}
Canvas canvas() const {
return window ? Canvas(window) : Canvas(nullptr, 0, 0);
}
Rect bounds() const {
return {0, 0, window ? window->content_w : 0, window ? window->content_h : 0};
}
bool map_mouse(const MouseEvent& ev, int* out_x, int* out_y) const {
if (!window || !out_x || !out_y) return false;
Rect cr = window->content_rect();
*out_x = ev.x - cr.x;
*out_y = ev.y - cr.y;
return true;
}
void invalidate() const {
if (window) window->dirty = true;
}
};
struct StandaloneHost {
WsWindow* window;
explicit StandaloneHost(WsWindow* win) : window(win) {}
Canvas canvas() const {
return window ? window->canvas() : Canvas(nullptr, 0, 0);
}
Rect bounds() const {
return {0, 0, window ? window->width : 0, window ? window->height : 0};
}
void present() const {
if (window) window->present();
}
void set_cursor(int cursor) const {
if (window) window->set_cursor(cursor);
}
};
} // namespace gui::mtk
@@ -0,0 +1,43 @@
/*
* settings.hpp
* Montauk Toolkit helpers for loading desktop appearance settings
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <montauk/config.h>
#include <montauk/syscall.h>
#include "gui/gui.hpp"
namespace gui::mtk {
inline Color color_from_rgb_int(int64_t rgb, Color fallback = colors::ACCENT) {
if (rgb < 0) return fallback;
return Color::from_rgb((uint8_t)((rgb >> 16) & 0xFF),
(uint8_t)((rgb >> 8) & 0xFF),
(uint8_t)(rgb & 0xFF));
}
inline Color load_system_accent(Color fallback = colors::ACCENT) {
char user[64] = {};
if (montauk::getuser(user, sizeof(user)) > 0 && user[0]) {
auto doc = montauk::config::load_user(user, "desktop");
int64_t accent = doc.get_int("appearance.accent_color", -1);
if (accent >= 0) {
Color color = color_from_rgb_int(accent, fallback);
doc.destroy();
return color;
}
doc.destroy();
}
auto doc = montauk::config::load("desktop");
int64_t accent = doc.get_int("appearance.accent_color", -1);
Color color = color_from_rgb_int(accent, fallback);
doc.destroy();
return color;
}
} // namespace gui::mtk
+150
View File
@@ -0,0 +1,150 @@
/*
* table.hpp
* Montauk Toolkit table/list helpers
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/canvas.hpp"
#include "gui/mtk/theme.hpp"
namespace gui::mtk {
struct TableColumn {
const char* label;
int x;
int w;
};
struct TableLayout {
Rect header;
Rect body;
int row_h;
};
struct TableStyle {
Color header_bg;
Color header_border;
Color header_text;
Color row_selected_bg;
Color row_hover_bg;
};
inline TableStyle make_table_style(const Theme& theme) {
return {
theme.surface_alt,
theme.border,
theme.text_subtle,
theme.accent_soft,
colors::TRANSPARENT,
};
}
inline int table_visible_rows(const TableLayout& layout) {
if (layout.row_h <= 0 || layout.body.h <= 0) return 0;
return layout.body.h / layout.row_h;
}
inline int table_max_scroll(const TableLayout& layout, int row_count) {
int visible = table_visible_rows(layout);
if (row_count > visible && visible > 0) return row_count - visible;
return 0;
}
inline void clamp_table_scroll(int* scroll, const TableLayout& layout, int row_count) {
if (!scroll) return;
*scroll = gui_clamp(*scroll, 0, table_max_scroll(layout, row_count));
}
inline void ensure_table_row_visible(int* scroll,
const TableLayout& layout,
int row_count,
int selected) {
if (!scroll || selected < 0) return;
int visible = table_visible_rows(layout);
if (visible <= 0) return;
if (selected < *scroll) {
*scroll = selected;
} else if (selected >= *scroll + visible) {
*scroll = selected - visible + 1;
}
clamp_table_scroll(scroll, layout, row_count);
}
inline Rect table_row_rect(const TableLayout& layout, int scroll, int row_index) {
return {
layout.body.x,
layout.body.y + (row_index - scroll) * layout.row_h,
layout.body.w,
layout.row_h
};
}
inline Rect table_cell_rect(const Rect& row_rect, const TableColumn& col) {
return {row_rect.x + col.x, row_rect.y, col.w, row_rect.h};
}
inline int table_hit_row(const TableLayout& layout,
int scroll,
int row_count,
int mx,
int my) {
if (!layout.body.contains(mx, my) || layout.row_h <= 0) return -1;
int row = (my - layout.body.y) / layout.row_h;
int index = scroll + row;
if (index < 0 || index >= row_count) return -1;
return index;
}
using TableRowRenderer = void (*)(Canvas& c,
void* userdata,
int row_index,
const Rect& row_rect,
int text_y,
const TableColumn* cols,
int col_count,
const Theme& theme,
const TableStyle& style);
inline void draw_table(Canvas& c,
const TableLayout& layout,
const TableColumn* cols,
int col_count,
int row_count,
int scroll,
int selected,
int hovered,
const Theme& theme,
const TableStyle& style,
TableRowRenderer draw_row,
void* userdata) {
c.fill_rect(layout.header.x, layout.header.y, layout.header.w, layout.header.h, style.header_bg);
c.hline(layout.header.x, layout.header.y + layout.header.h - 1, layout.header.w, style.header_border);
int fh = system_font_height();
int header_text_y = layout.header.y + (layout.header.h - fh) / 2;
for (int i = 0; i < col_count; i++) {
c.text(layout.header.x + cols[i].x, header_text_y, cols[i].label, style.header_text);
}
int visible = table_visible_rows(layout);
for (int row = 0; row < visible; row++) {
int index = scroll + row;
if (index >= row_count) break;
Rect row_rect = table_row_rect(layout, scroll, index);
if (index == selected) {
c.fill_rect(row_rect.x, row_rect.y, row_rect.w, row_rect.h, style.row_selected_bg);
} else if (index == hovered && style.row_hover_bg.a > 0) {
c.fill_rect(row_rect.x, row_rect.y, row_rect.w, row_rect.h, style.row_hover_bg);
}
if (draw_row) {
int text_y = row_rect.y + (layout.row_h - fh) / 2;
draw_row(c, userdata, index, row_rect, text_y, cols, col_count, theme, style);
}
}
}
} // namespace gui::mtk
+133
View File
@@ -0,0 +1,133 @@
/*
* theme.hpp
* Montauk Toolkit theme tokens and color helpers
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include "gui/gui.hpp"
#include "gui/mtk/settings.hpp"
namespace gui::mtk {
inline Color mix(Color base, Color tint, uint8_t tint_alpha) {
auto blend = [tint_alpha](uint8_t a, uint8_t b) -> uint8_t {
return (uint8_t)((((uint16_t)a * (255 - tint_alpha)) +
((uint16_t)b * tint_alpha) + 127) / 255);
};
return Color::from_rgb(blend(base.r, tint.r),
blend(base.g, tint.g),
blend(base.b, tint.b));
}
inline Color lighten(Color c, uint8_t amount) {
return mix(c, colors::WHITE, amount);
}
inline Color darken(Color c, uint8_t amount) {
return mix(c, colors::BLACK, amount);
}
// Light selection/hover tint derived from the desktop accent. Returns the
// canonical MENU_HOVER for the default blue accent so existing visuals are
// preserved exactly; otherwise tints white with ~11% of the accent.
inline Color accent_hover_tint(Color accent) {
if (accent.r == colors::ACCENT.r && accent.g == colors::ACCENT.g
&& accent.b == colors::ACCENT.b)
return colors::MENU_HOVER;
return mix(colors::WHITE, accent, 28);
}
struct Theme {
Color window_bg;
Color surface;
Color surface_alt;
Color surface_hover;
Color border;
Color text;
Color text_muted;
Color text_subtle;
Color text_inverse;
Color accent;
Color accent_hover;
Color accent_soft;
Color accent_fg;
Color selection;
Color text_selection;
Color danger;
Color danger_hover;
Color danger_soft;
Color danger_fg;
Color disabled_bg;
Color disabled_fg;
Color badge_admin_bg;
Color badge_admin_fg;
Color badge_user_bg;
Color badge_user_fg;
int radius_sm;
int radius_md;
int control_h;
int tab_h;
int gap_xs;
int gap_sm;
int gap_md;
int gap_lg;
};
inline Theme make_theme(Color accent) {
Theme theme {};
theme.window_bg = colors::WINDOW_BG;
theme.surface = Color::from_rgb(0xF5, 0xF5, 0xF5);
theme.surface_alt = Color::from_rgb(0xF8, 0xF8, 0xF8);
theme.surface_hover = mix(theme.surface, accent, 18);
theme.border = colors::BORDER;
theme.text = colors::TEXT_COLOR;
theme.text_muted = Color::from_rgb(0x88, 0x88, 0x88);
theme.text_subtle = Color::from_rgb(0x66, 0x66, 0x66);
theme.text_inverse = colors::WHITE;
theme.accent = accent;
theme.accent_hover = darken(accent, 32);
theme.accent_soft = lighten(accent, 214);
theme.accent_fg = colors::WHITE;
theme.selection = accent;
theme.text_selection = Color::from_rgb(0xB0, 0xD0, 0xF0);
theme.danger = Color::from_rgb(0xD0, 0x3E, 0x3E);
theme.danger_hover = Color::from_rgb(0xDD, 0x44, 0x44);
theme.danger_soft = Color::from_rgb(0xF7, 0xDF, 0xDF);
theme.danger_fg = colors::WHITE;
theme.disabled_bg = Color::from_rgb(0xCC, 0xCC, 0xCC);
theme.disabled_fg = colors::WHITE;
theme.badge_admin_bg = Color::from_rgb(0xE8, 0xD8, 0xF0);
theme.badge_admin_fg = Color::from_rgb(0x7B, 0x3E, 0xB8);
theme.badge_user_bg = Color::from_rgb(0xE0, 0xE8, 0xF0);
theme.badge_user_fg = Color::from_rgb(0x36, 0x7B, 0xF0);
theme.radius_sm = 3;
theme.radius_md = 4;
theme.control_h = 30;
theme.tab_h = 36;
theme.gap_xs = 2;
theme.gap_sm = 6;
theme.gap_md = 10;
theme.gap_lg = 16;
return theme;
}
// Zero-arg overload pulls the accent from the desktop appearance settings, so
// MTK widgets (buttons, menus, etc.) automatically pick up the user's chosen
// accent without each caller having to plumb it through.
inline Theme make_theme() {
return make_theme(load_system_accent());
}
} // namespace gui::mtk
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+251 -23
View File
@@ -123,6 +123,34 @@ struct SvgCssTable {
}
};
// ---------------------------------------------------------------------------
// Integer sqrt (binary method, no floating point).
// Returns floor(sqrt(n)) for any uint64.
// ---------------------------------------------------------------------------
inline uint64_t svg_int64_sqrt(uint64_t n) {
if (n == 0) return 0;
uint64_t r = 0;
uint64_t bit = (uint64_t)1 << 62;
while (bit > n) bit >>= 2;
while (bit != 0) {
if (n >= r + bit) {
n -= r + bit;
r = (r >> 1) + bit;
} else {
r >>= 1;
}
bit >>= 2;
}
return r;
}
// 16.16 fixed-point hypotenuse: sqrt(x*x + y*y).
inline fixed_t svg_fixed_hypot(fixed_t x, fixed_t y) {
int64_t sq = (int64_t)x * x + (int64_t)y * y;
if (sq < 0) sq = 0;
return (fixed_t)svg_int64_sqrt((uint64_t)sq);
}
// ---------------------------------------------------------------------------
// Fixed-point number parser (NO floating point)
// Parses strings like "3.25", "-0.5", ".1115", "16"
@@ -566,6 +594,47 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen,
*oy = fixed_mul(y - off_y, scale_y);
};
// Emit a circular arc on (cx, cy, r) from p1 to p2 by recursive
// chord-midpoint subdivision. Assumes the arc is <= 180deg; callers split
// large arcs at the far point first. Stack-based to avoid recursive lambdas.
auto emit_small_arc = [&](fixed_t cx, fixed_t cy, fixed_t r,
fixed_t p1x, fixed_t p1y,
fixed_t p2x, fixed_t p2y) {
constexpr int ARC_MAX_DEPTH = 4; // up to 16 chords per sub-arc
struct Seg { fixed_t ax, ay, bx, by; int depth; };
Seg stk[16];
int sp = 0;
stk[sp++] = { p1x, p1y, p2x, p2y, ARC_MAX_DEPTH };
while (sp > 0) {
Seg s = stk[--sp];
if (s.depth == 0) {
fixed_t sx0, sy0, sx1, sy1;
scale_pt(s.ax, s.ay, &sx0, &sy0);
scale_pt(s.bx, s.by, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
continue;
}
fixed_t mx = (s.ax + s.bx) / 2;
fixed_t my = (s.ay + s.by) / 2;
fixed_t vx = mx - cx;
fixed_t vy = my - cy;
fixed_t vlen = svg_fixed_hypot(vx, vy);
if (vlen == 0) {
// Degenerate: chord midpoint coincides with center.
fixed_t sx0, sy0, sx1, sy1;
scale_pt(s.ax, s.ay, &sx0, &sy0);
scale_pt(s.bx, s.by, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
continue;
}
fixed_t k = fixed_div(r, vlen);
fixed_t amx = cx + fixed_mul(vx, k);
fixed_t amy = cy + fixed_mul(vy, k);
stk[sp++] = { amx, amy, s.bx, s.by, s.depth - 1 };
stk[sp++] = { s.ax, s.ay, amx, amy, s.depth - 1 };
}
};
while (pp.has_more()) {
char cmd = '\0';
@@ -773,23 +842,90 @@ inline void svg_path_to_edges(SvgEdgeList& el, const char* d, int dLen,
break;
}
case 'A': case 'a': {
// Arc command: consume parameters but approximate as a line
// (arcs are rare in these icons)
fixed_t rx = pp.read_number();
fixed_t ry = pp.read_number();
pp.read_number(); // x-rotation
pp.read_number(); // large-arc-flag
pp.read_number(); // sweep-flag
// Elliptical-arc command. We treat the arc as circular using
// r = min(|rx|, |ry|), which is exact for symbolic icons (all use
// equal axes) and a reasonable approximation otherwise.
fixed_t rx_in = pp.read_number();
fixed_t ry_in = pp.read_number();
pp.read_number(); // x-axis rotation (ignored)
fixed_t fa_f = pp.read_number(); // large-arc-flag
fixed_t fs_f = pp.read_number(); // sweep-flag
fixed_t x = pp.read_number();
fixed_t y = pp.read_number();
if (cmd == 'a') { x += cur_x; y += cur_y; }
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x, y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
fixed_t r = (rx_in < 0) ? -rx_in : rx_in;
fixed_t ry_abs = (ry_in < 0) ? -ry_in : ry_in;
if (ry_abs < r) r = ry_abs;
int fa = (fa_f != 0) ? 1 : 0;
int fs = (fs_f != 0) ? 1 : 0;
fixed_t dx = x - cur_x;
fixed_t dy = y - cur_y;
if ((dx == 0 && dy == 0) || r == 0) {
// Per spec: degenerate arc becomes a line.
fixed_t sx0, sy0, sx1, sy1;
scale_pt(cur_x, cur_y, &sx0, &sy0);
scale_pt(x, y, &sx1, &sy1);
el.add(sx0, sy0, sx1, sy1);
cur_x = x; cur_y = y;
last_cmd = cmd;
break;
}
// Endpoint-to-center: midpoint of chord, half-chord length,
// perpendicular height from chord to center.
fixed_t mx = cur_x + dx / 2;
fixed_t my = cur_y + dy / 2;
fixed_t half_chord = svg_fixed_hypot(dx / 2, dy / 2);
if (r < half_chord) r = half_chord; // SVG spec: snap radius up
int64_t r_sq = (int64_t)r * r;
int64_t hc_sq = (int64_t)half_chord * half_chord;
int64_t h_sq = r_sq - hc_sq;
if (h_sq < 0) h_sq = 0;
fixed_t h = (fixed_t)svg_int64_sqrt((uint64_t)h_sq);
// Center = mid + sign * (h / chord_len) * (-dy, dx)
// sign: spec F.6.5 says -1 when fa == fs, else +1.
fixed_t chord_len = 2 * half_chord;
if (chord_len == 0) chord_len = 1;
fixed_t h_over_chord = fixed_div(h, chord_len);
int sign = (fa == fs) ? -1 : 1;
fixed_t off_x_perp = fixed_mul(-dy, h_over_chord);
fixed_t off_y_perp = fixed_mul( dx, h_over_chord);
if (sign < 0) { off_x_perp = -off_x_perp; off_y_perp = -off_y_perp; }
fixed_t cx_ctr = mx + off_x_perp;
fixed_t cy_ctr = my + off_y_perp;
if (fa == 1) {
// Large arc: bisect at the far point (opposite side of center
// from chord midpoint), then walk each half as a small arc.
fixed_t vx = mx - cx_ctr;
fixed_t vy = my - cy_ctr;
fixed_t vlen = svg_fixed_hypot(vx, vy);
fixed_t am_x, am_y;
if (vlen == 0) {
// 180-degree arc: bisect using chord-perpendicular instead.
fixed_t k = fixed_div(r, chord_len);
fixed_t px = fixed_mul(-dy, k);
fixed_t py = fixed_mul( dx, k);
if (fs == 0) { px = -px; py = -py; }
am_x = cx_ctr + px;
am_y = cy_ctr + py;
} else {
fixed_t k = fixed_div(-r, vlen);
am_x = cx_ctr + fixed_mul(vx, k);
am_y = cy_ctr + fixed_mul(vy, k);
}
emit_small_arc(cx_ctr, cy_ctr, r, cur_x, cur_y, am_x, am_y);
emit_small_arc(cx_ctr, cy_ctr, r, am_x, am_y, x, y);
} else {
emit_small_arc(cx_ctr, cy_ctr, r, cur_x, cur_y, x, y);
}
cur_x = x; cur_y = y;
last_cmd = cmd;
(void)rx; (void)ry;
break;
}
case 'Z': case 'z': {
@@ -1027,6 +1163,42 @@ inline bool svg_element_has_filter(const char* elem, int elemLen) {
return svg_get_attr(elem, elemLen, " filter", buf, sizeof(buf)) > 0;
}
// ---------------------------------------------------------------------------
// Parse transform="scale(x)" or transform="scale(x,y)" or translate(x,y)
// Returns true if a scale was found; sx/sy are fixed-point scale factors.
// ---------------------------------------------------------------------------
inline bool svg_get_element_scale(const char* elem, int elemLen,
fixed_t* sx, fixed_t* sy) {
char buf[64];
if (svg_get_attr(elem, elemLen, " transform", buf, sizeof(buf)) <= 0) {
*sx = int_to_fixed(1);
*sy = int_to_fixed(1);
return false;
}
// Look for "scale("
const char* sp = buf;
while (*sp) {
if (sp[0]=='s' && sp[1]=='c' && sp[2]=='a' && sp[3]=='l' && sp[4]=='e' && sp[5]=='(') {
sp += 6;
svg_parse_fixed(sp, sx);
// Skip to comma or closing paren
while (*sp && *sp != ',' && *sp != ')') ++sp;
if (*sp == ',') {
++sp;
while (*sp == ' ') ++sp;
svg_parse_fixed(sp, sy);
} else {
*sy = *sx; // uniform scale
}
return true;
}
++sp;
}
*sx = int_to_fixed(1);
*sy = int_to_fixed(1);
return false;
}
// ---------------------------------------------------------------------------
// Scanline rasterizer with alpha blending (for multi-color SVGs)
// ---------------------------------------------------------------------------
@@ -1314,11 +1486,17 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
int alpha = svg_get_element_opacity(elem_start, elem_len);
// Per-element transform (scale)
fixed_t elem_sx, elem_sy;
svg_get_element_scale(elem_start, elem_len, &elem_sx, &elem_sy);
fixed_t eff_sx = fixed_mul(scale_x, elem_sx);
fixed_t eff_sy = fixed_mul(scale_y, elem_sy);
// Extract and rasterize path
int d_len = svg_get_attr(elem_start, elem_len, " d", d_buf, SVG_MAX_PATH_LEN);
if (d_len > 0) {
el.clear();
svg_path_to_edges(el, d_buf, d_len, scale_x, scale_y, vb_x, vb_y);
svg_path_to_edges(el, d_buf, d_len, eff_sx, eff_sy, vb_x, vb_y);
if (el.count > 0)
svg_rasterize_blend(el, icon.pixels, target_w, target_h, elem_color.to_pixel(), alpha);
}
@@ -1349,6 +1527,11 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
int alpha = svg_get_element_opacity(elem_start, elem_len);
fixed_t elem_sx, elem_sy;
svg_get_element_scale(elem_start, elem_len, &elem_sx, &elem_sy);
fixed_t eff_sx = fixed_mul(scale_x, elem_sx);
fixed_t eff_sy = fixed_mul(scale_y, elem_sy);
char attr_buf[32];
fixed_t cx = 0, cy = 0, r = 0;
if (svg_get_attr(elem_start, elem_len, " cx", attr_buf, sizeof(attr_buf)) > 0)
@@ -1358,10 +1541,10 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
if (svg_get_attr(elem_start, elem_len, " r", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &r);
fixed_t scx = fixed_mul(cx - vb_x, scale_x);
fixed_t scy = fixed_mul(cy - vb_y, scale_y);
fixed_t srx = fixed_mul(r, scale_x);
fixed_t sry = fixed_mul(r, scale_y);
fixed_t scx = fixed_mul(cx - vb_x, eff_sx);
fixed_t scy = fixed_mul(cy - vb_y, eff_sy);
fixed_t srx = fixed_mul(r, eff_sx);
fixed_t sry = fixed_mul(r, eff_sy);
fixed_t sr = (srx + sry) >> 1;
el.clear();
@@ -1395,6 +1578,11 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
int alpha = svg_get_element_opacity(elem_start, elem_len);
fixed_t elem_sx, elem_sy;
svg_get_element_scale(elem_start, elem_len, &elem_sx, &elem_sy);
fixed_t eff_sx = fixed_mul(scale_x, elem_sx);
fixed_t eff_sy = fixed_mul(scale_y, elem_sy);
char attr_buf[32];
fixed_t rx_val = 0, ry_val = 0, rw = 0, rh = 0, rrx = 0, rry = 0;
@@ -1411,12 +1599,12 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
if (svg_get_attr(elem_start, elem_len, " ry", attr_buf, sizeof(attr_buf)) > 0)
svg_parse_fixed(attr_buf, &rry);
fixed_t sx = fixed_mul(rx_val - vb_x, scale_x);
fixed_t sy = fixed_mul(ry_val - vb_y, scale_y);
fixed_t sw = fixed_mul(rw, scale_x);
fixed_t sh = fixed_mul(rh, scale_y);
fixed_t srx = fixed_mul(rrx, scale_x);
fixed_t sry = fixed_mul(rry, scale_y);
fixed_t sx = fixed_mul(rx_val - vb_x, eff_sx);
fixed_t sy = fixed_mul(ry_val - vb_y, eff_sy);
fixed_t sw = fixed_mul(rw, eff_sx);
fixed_t sh = fixed_mul(rh, eff_sy);
fixed_t srx = fixed_mul(rrx, eff_sx);
fixed_t sry = fixed_mul(rry, eff_sy);
el.clear();
svg_rect_edges(el, sx, sy, sw, sh, srx, sry);
@@ -1438,6 +1626,46 @@ inline SvgIcon svg_render(const char* svg_data, int svg_len, int target_w, int t
// ---------------------------------------------------------------------------
// Load SVG from VFS and render
// ---------------------------------------------------------------------------
// Extract intrinsic dimensions from an SVG document. Prefers width/height
// attributes, falls back to viewBox, then to a 256x256 default. Used by the
// image viewer to render SVGs at their natural size rather than a fixed icon
// size.
inline void svg_get_natural_size(const char* svg_data, int svg_len,
int* out_w, int* out_h) {
int w = 0, h = 0;
const char* svg_tag = svg_strstr(svg_data, svg_len, "<svg");
if (svg_tag) {
int tag_offset = (int)(svg_tag - svg_data);
int tag_end = tag_offset;
while (tag_end < svg_len && svg_data[tag_end] != '>') ++tag_end;
int tag_len = tag_end - tag_offset + 1;
char attr_buf[64];
if (svg_get_attr(svg_tag, tag_len, " width", attr_buf, sizeof(attr_buf)) > 0)
w = svg_parse_int(attr_buf);
if (svg_get_attr(svg_tag, tag_len, " height", attr_buf, sizeof(attr_buf)) > 0)
h = svg_parse_int(attr_buf);
if ((w <= 0 || h <= 0) &&
svg_get_attr(svg_tag, tag_len, " viewBox", attr_buf, sizeof(attr_buf)) > 0) {
fixed_t vbx, vby, vbw, vbh;
const char* vp = attr_buf;
int c;
c = svg_parse_fixed(vp, &vbx); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vby); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
c = svg_parse_fixed(vp, &vbw); vp += c; while (*vp && svg_char_is_sep(*vp)) ++vp;
svg_parse_fixed(vp, &vbh);
(void)vbx; (void)vby;
if (w <= 0) w = fixed_to_int(vbw);
if (h <= 0) h = fixed_to_int(vbh);
}
}
if (w <= 0) w = 256;
if (h <= 0) h = 256;
if (out_w) *out_w = w;
if (out_h) *out_h = h;
}
inline SvgIcon svg_load(const char* vfs_path, int target_w, int target_h, Color fill_color) {
int fd = montauk::open(vfs_path);
if (fd < 0) {
+85 -10
View File
@@ -41,6 +41,41 @@
namespace gui {
inline int decode_single_byte_codepoint(int codepoint) {
if (codepoint < 0 || codepoint > 255) return codepoint;
switch (codepoint) {
case 0x80: return 0x20AC; // Euro Sign
case 0x82: return 0x201A; // Single Low-9 Quotation Mark
case 0x83: return 0x0192; // Latin Small Letter F With Hook
case 0x84: return 0x201E; // Double Low-9 Quotation Mark
case 0x85: return 0x2026; // Horizontal Ellipsis
case 0x86: return 0x2020; // Dagger
case 0x87: return 0x2021; // Double Dagger
case 0x88: return 0x02C6; // Modifier Letter Circumflex Accent
case 0x89: return 0x2030; // Per Mille Sign
case 0x8A: return 0x0160; // Latin Capital Letter S With Caron
case 0x8B: return 0x2039; // Single Left-Pointing Angle Quotation Mark
case 0x8C: return 0x0152; // Latin Capital Ligature OE
case 0x8E: return 0x017D; // Latin Capital Letter Z With Caron
case 0x91: return 0x2018; // Left Single Quotation Mark
case 0x92: return 0x2019; // Right Single Quotation Mark
case 0x93: return 0x201C; // Left Double Quotation Mark
case 0x94: return 0x201D; // Right Double Quotation Mark
case 0x95: return 0x2022; // Bullet
case 0x96: return 0x2013; // En Dash
case 0x97: return 0x2014; // Em Dash
case 0x98: return 0x02DC; // Small Tilde
case 0x99: return 0x2122; // Trade Mark Sign
case 0x9A: return 0x0161; // Latin Small Letter S With Caron
case 0x9B: return 0x203A; // Single Right-Pointing Angle Quotation Mark
case 0x9C: return 0x0153; // Latin Small Ligature OE
case 0x9E: return 0x017E; // Latin Small Letter Z With Caron
case 0x9F: return 0x0178; // Latin Capital Letter Y With Diaeresis
default: return codepoint;
}
}
struct CachedGlyph {
uint8_t* bitmap;
int width, height;
@@ -50,7 +85,7 @@ struct CachedGlyph {
};
struct GlyphCache {
CachedGlyph glyphs[256]; // Latin-1 Supplement (U+0080..U+00FF) included
CachedGlyph glyphs[256]; // Single-byte text with Windows-1252 punctuation mapping
int pixel_size;
float scale;
int ascent, descent, line_gap;
@@ -155,12 +190,13 @@ struct TrueTypeFont {
if (g->loaded) return g;
g->loaded = true;
int font_codepoint = decode_single_byte_codepoint(codepoint);
int advance, lsb;
stbtt_GetCodepointHMetrics(&info, codepoint, &advance, &lsb);
stbtt_GetCodepointHMetrics(&info, font_codepoint, &advance, &lsb);
g->advance = (int)(advance * gc->scale);
int x0, y0, x1, y1;
stbtt_GetCodepointBitmapBox(&info, codepoint, gc->scale, gc->scale,
stbtt_GetCodepointBitmapBox(&info, font_codepoint, gc->scale, gc->scale,
&x0, &y0, &x1, &y1);
g->width = x1 - x0;
g->height = y1 - y0;
@@ -169,8 +205,10 @@ struct TrueTypeFont {
if (g->width > 0 && g->height > 0) {
g->bitmap = (uint8_t*)montauk::malloc(g->width * g->height);
if (!g->bitmap)
return g;
stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height,
g->width, gc->scale, gc->scale, codepoint);
g->width, gc->scale, gc->scale, font_codepoint);
}
return g;
@@ -199,6 +237,13 @@ struct TrueTypeFont {
GlyphCache* gc = get_cache(pixel_size);
int cx = x;
int baseline = y + gc->ascent;
int fb_w = fb.width();
int fb_h = fb.height();
int fb_pitch = fb.pitch();
uint32_t* fb_pixels = fb.buffer();
if (!fb_pixels) return;
uint32_t src_rgb = 0xFF000000 | ((uint32_t)color.r << 16) |
((uint32_t)color.g << 8) | color.b;
for (int i = 0; text[i]; i++) {
CachedGlyph* g = get_glyph(gc, (unsigned char)text[i]);
@@ -207,12 +252,41 @@ struct TrueTypeFont {
if (g->bitmap) {
int gx = cx + g->xoff;
int gy = baseline + g->yoff;
for (int row = 0; row < g->height; row++) {
for (int col = 0; col < g->width; col++) {
uint8_t alpha = g->bitmap[row * g->width + col];
if (alpha > 0) {
Color c = {color.r, color.g, color.b, alpha};
fb.put_pixel_alpha(gx + col, gy + row, c);
int row0 = gy < 0 ? -gy : 0;
int col0 = gx < 0 ? -gx : 0;
int row1 = gy + g->height > fb_h ? fb_h - gy : g->height;
int col1 = gx + g->width > fb_w ? fb_w - gx : g->width;
if (row0 < row1 && col0 < col1) {
for (int row = row0; row < row1; row++) {
uint8_t* alpha_row = g->bitmap + row * g->width + col0;
uint32_t* dst = (uint32_t*)((uint8_t*)fb_pixels + (gy + row) * fb_pitch) + gx + col0;
for (int col = col0; col < col1; col++) {
uint8_t alpha = *alpha_row++;
if (alpha == 0) {
dst++;
continue;
}
if (alpha == 255) {
*dst++ = src_rgb;
continue;
}
uint32_t d = *dst;
uint32_t inv_a = 255 - alpha;
uint32_t dr = (d >> 16) & 0xFF;
uint32_t dg = (d >> 8) & 0xFF;
uint32_t db = d & 0xFF;
uint32_t rr = alpha * color.r + inv_a * dr;
uint32_t gg = alpha * color.g + inv_a * dg;
uint32_t bb = alpha * color.b + inv_a * db;
rr = (rr + 1 + (rr >> 8)) >> 8;
gg = (gg + 1 + (gg >> 8)) >> 8;
bb = (bb + 1 + (bb >> 8)) >> 8;
*dst++ = 0xFF000000 | (rr << 16) | (gg << 8) | bb;
}
}
}
@@ -389,6 +463,7 @@ namespace fonts {
inline bool init() {
auto load = [](const char* path) -> TrueTypeFont* {
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
if (!f) return nullptr;
montauk::memset(f, 0, sizeof(TrueTypeFont));
if (!f->init(path)) {
montauk::mfree(f);
+78 -17
View File
@@ -27,7 +27,64 @@ struct Response {
int raw_len;
};
inline const char* find_header_block_end(const char* start, const char* end) {
if (!start || !end || start >= end) return nullptr;
for (const char* s = start; s < end - 3; s++) {
if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n')
return s + 4;
}
for (const char* s = start; s < end - 1; s++) {
if (s[0] == '\n' && s[1] == '\n')
return s + 2;
}
return nullptr;
}
inline int parse_status_code(const char* start, const char* end) {
if (!start || !end || end - start < 12) return -1;
const char* p = start;
while (p < end && *p && *p != ' ') p++;
if (p >= end || *p != ' ') return -1;
p++;
int code = 0;
int digits = 0;
while (digits < 3 && p < end && *p >= '0' && *p <= '9') {
code = code * 10 + (*p - '0');
p++;
digits++;
}
return digits == 3 ? code : -1;
}
inline const char* find_final_response_start(const char* buf, int len) {
if (!buf || len <= 0) return nullptr;
const char* start = buf;
const char* end = buf + len;
for (;;) {
int code = parse_status_code(start, end);
if (code < 0) return nullptr;
if (code < 100 || code >= 200) return start;
const char* next = find_header_block_end(start, end);
if (!next || next >= end) return nullptr;
if (end - next < 5) return nullptr;
if (!(next[0] == 'H' && next[1] == 'T' && next[2] == 'T' && next[3] == 'P' && next[4] == '/'))
return nullptr;
start = next;
}
}
inline const char* skip_informational_responses(const char* buf, int len) {
const char* start = find_final_response_start(buf, len);
return start ? start : buf;
}
// Parse raw HTTP response in-place. Sets pointers into buf (does not copy).
// Skips leading informational 1xx responses such as "100 Continue".
// Returns status code, or -1 if unparseable.
inline int parse_response(char* buf, int len, Response* out) {
out->raw = buf;
@@ -38,21 +95,18 @@ inline int parse_response(char* buf, int len, Response* out) {
out->body = nullptr;
out->body_len = 0;
if (len < 12) return -1; // "HTTP/1.x NNN"
const char* start = find_final_response_start(buf, len);
const char* end = buf + len;
if (!start || end - start < 12) return -1; // "HTTP/1.x NNN"
// Parse status code from "HTTP/1.x NNN"
const char* p = buf;
while (*p && *p != ' ') p++;
if (*p != ' ') return -1;
p++;
int code = 0;
for (int i = 0; i < 3 && *p >= '0' && *p <= '9'; i++, p++)
code = code * 10 + (*p - '0');
int code = parse_status_code(start, end);
if (code < 0) return -1;
out->status = code;
// Headers start after the status line
const char* hdr_start = buf;
while (hdr_start < buf + len - 1) {
const char* hdr_start = start;
while (hdr_start < end - 1) {
if (*hdr_start == '\r' && *(hdr_start + 1) == '\n') { hdr_start += 2; break; }
if (*hdr_start == '\n') { hdr_start++; break; }
hdr_start++;
@@ -60,14 +114,21 @@ inline int parse_response(char* buf, int len, Response* out) {
out->headers = hdr_start;
// Find \r\n\r\n boundary between headers and body
for (const char* s = hdr_start; s < buf + len - 3; s++) {
if (s[0] == '\r' && s[1] == '\n' && s[2] == '\r' && s[3] == '\n') {
out->headers_len = (int)(s - hdr_start);
out->body = s + 4;
out->body_len = len - (int)(out->body - buf);
return code;
}
const char* body = find_header_block_end(hdr_start, end);
if (body) {
if (body >= hdr_start + 4 &&
body[-4] == '\r' && body[-3] == '\n' && body[-2] == '\r' && body[-1] == '\n')
out->headers_len = (int)((body - 4) - hdr_start);
else if (body >= hdr_start + 2 &&
body[-2] == '\n' && body[-1] == '\n')
out->headers_len = (int)((body - 2) - hdr_start);
else
out->headers_len = (int)(body - hdr_start);
out->body = body;
out->body_len = len - (int)(out->body - buf);
return code;
}
// No body separator found — entire remainder is headers
out->headers_len = len - (int)(hdr_start - buf);
return code;
+107
View File
@@ -79,6 +79,7 @@ extern "C" {
#define MTK_SYS_WINPOLL 57
#define MTK_SYS_WINENUM 58
#define MTK_SYS_WINMAP 59
#define MTK_SYS_WINUNMAP 97
#define MTK_SYS_WINSENDEVENT 60
#define MTK_SYS_PROCLIST 61
#define MTK_SYS_KILL 62
@@ -101,9 +102,30 @@ extern "C" {
#define MTK_SYS_GETTZ 91
#define MTK_SYS_GETCWD 95
#define MTK_SYS_CHDIR 96
#define MTK_SYS_DUPHANDLE 98
#define MTK_SYS_WAIT_HANDLE 99
#define MTK_SYS_STREAM_CREATE 100
#define MTK_SYS_STREAM_READ 101
#define MTK_SYS_STREAM_WRITE 102
#define MTK_SYS_MAILBOX_CREATE 103
#define MTK_SYS_MAILBOX_SEND 104
#define MTK_SYS_MAILBOX_RECV 105
#define MTK_SYS_WAITSET_CREATE 106
#define MTK_SYS_WAITSET_ADD 107
#define MTK_SYS_WAITSET_REMOVE 108
#define MTK_SYS_WAITSET_WAIT 109
#define MTK_SYS_PROC_OPEN 110
#define MTK_SYS_SURFACE_CREATE 111
#define MTK_SYS_SURFACE_MAP 112
#define MTK_SYS_SURFACE_RESIZE 113
#define MTK_SOCK_TCP 1
#define MTK_SOCK_UDP 2
#define MTK_IPC_SIGNAL_READABLE (1u << 0)
#define MTK_IPC_SIGNAL_WRITABLE (1u << 1)
#define MTK_IPC_SIGNAL_PEER_CLOSED (1u << 2)
#define MTK_IPC_SIGNAL_EXITED (1u << 3)
#define MTK_IPC_SIGNAL_READY (1u << 4)
/* Window event types */
#define MTK_EVENT_KEY 0
@@ -140,6 +162,11 @@ typedef struct {
uint8_t buttons;
} mtk_mouse_state;
typedef struct {
int32_t index;
uint32_t signals;
} mtk_ipc_wait_result;
typedef struct {
uint8_t type;
uint8_t _pad[3];
@@ -402,6 +429,78 @@ static inline int mtk_readdir(const char *path, const char **names, int max) {
return (int)_mtk_syscall3(MTK_SYS_READDIR, (long)path, (long)names, (long)max);
}
/* ====================================================================
Generic IPC
==================================================================== */
static inline int mtk_dup_handle(int handle) {
return (int)_mtk_syscall1(MTK_SYS_DUPHANDLE, (long)handle);
}
static inline uint32_t mtk_wait_handle(int handle, uint32_t wanted_signals, unsigned long timeout_ms) {
return (uint32_t)_mtk_syscall3(MTK_SYS_WAIT_HANDLE, (long)handle, (long)wanted_signals, (long)timeout_ms);
}
static inline int mtk_stream_create(int *out_read_handle, int *out_write_handle, unsigned long capacity) {
return (int)_mtk_syscall3(MTK_SYS_STREAM_CREATE, (long)out_read_handle, (long)out_write_handle, (long)capacity);
}
static inline int mtk_stream_read(int handle, void *buf, int max_len) {
return (int)_mtk_syscall3(MTK_SYS_STREAM_READ, (long)handle, (long)buf, (long)max_len);
}
static inline int mtk_stream_write(int handle, const void *data, int len) {
return (int)_mtk_syscall3(MTK_SYS_STREAM_WRITE, (long)handle, (long)data, (long)len);
}
static inline int mtk_mailbox_create(int *out_send_handle, int *out_recv_handle) {
return (int)_mtk_syscall2(MTK_SYS_MAILBOX_CREATE, (long)out_send_handle, (long)out_recv_handle);
}
static inline int mtk_mailbox_send(int handle, uint32_t msg_type, const void *data,
uint16_t len, int attach_handle) {
return (int)_mtk_syscall5(MTK_SYS_MAILBOX_SEND, (long)handle, (long)msg_type,
(long)data, (long)len, (long)attach_handle);
}
static inline int mtk_mailbox_recv(int handle, uint32_t *out_msg_type, void *data,
uint16_t *in_out_len, int *out_attach_handle) {
return (int)_mtk_syscall5(MTK_SYS_MAILBOX_RECV, (long)handle, (long)out_msg_type,
(long)data, (long)in_out_len, (long)out_attach_handle);
}
static inline int mtk_waitset_create(void) {
return (int)_mtk_syscall0(MTK_SYS_WAITSET_CREATE);
}
static inline int mtk_waitset_add(int waitset_handle, int target_handle, uint32_t signals) {
return (int)_mtk_syscall3(MTK_SYS_WAITSET_ADD, (long)waitset_handle, (long)target_handle, (long)signals);
}
static inline int mtk_waitset_remove(int waitset_handle, int index) {
return (int)_mtk_syscall2(MTK_SYS_WAITSET_REMOVE, (long)waitset_handle, (long)index);
}
static inline int mtk_waitset_wait(int waitset_handle, mtk_ipc_wait_result *out_ready, unsigned long timeout_ms) {
return (int)_mtk_syscall3(MTK_SYS_WAITSET_WAIT, (long)waitset_handle, (long)out_ready, (long)timeout_ms);
}
static inline int mtk_proc_open(int pid) {
return (int)_mtk_syscall1(MTK_SYS_PROC_OPEN, (long)pid);
}
static inline int mtk_surface_create(unsigned long byte_size) {
return (int)_mtk_syscall1(MTK_SYS_SURFACE_CREATE, (long)byte_size);
}
static inline void *mtk_surface_map(int handle) {
return (void *)_mtk_syscall1(MTK_SYS_SURFACE_MAP, (long)handle);
}
static inline int mtk_surface_resize(int handle, unsigned long new_size) {
return (int)_mtk_syscall2(MTK_SYS_SURFACE_RESIZE, (long)handle, (long)new_size);
}
/* ====================================================================
Memory
==================================================================== */
@@ -484,6 +583,10 @@ static inline int mtk_win_poll(int id, mtk_win_event *event) {
return (int)_mtk_syscall2(MTK_SYS_WINPOLL, (long)id, (long)event);
}
static inline int mtk_win_unmap(int id) {
return (int)_mtk_syscall1(MTK_SYS_WINUNMAP, (long)id);
}
static inline unsigned long mtk_win_resize(int id, int w, int h) {
return (unsigned long)_mtk_syscall3(MTK_SYS_WINRESIZE, (long)id, (long)w, (long)h);
}
@@ -492,6 +595,10 @@ static inline int mtk_win_enumerate(mtk_win_info *info, int max) {
return (int)_mtk_syscall2(MTK_SYS_WINENUM, (long)info, (long)max);
}
static inline unsigned long mtk_win_map(int id) {
return (unsigned long)_mtk_syscall1(MTK_SYS_WINMAP, (long)id);
}
static inline int mtk_win_setcursor(int id, int cursor) {
return (int)_mtk_syscall2(MTK_SYS_WINSETCURSOR, (long)id, (long)cursor);
}
@@ -0,0 +1,34 @@
/*
* libloader.h
* Dynamic library loading interface for MontaukOS
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <montauk/syscall.h>
// Library handle - opaque to users
struct LibHandle {
int handle; // syscall handle (slot + 1)
uint64_t base; // relocation base / load bias for symbol values
char path[256];
};
namespace libloader {
// Maximum libraries that can be loaded per process
static constexpr int MaxLoadedLibs = 8;
// Load a shared library.
// Returns a LibHandle* on success, or nullptr on failure.
LibHandle* dlopen(const char* path);
// Look up a symbol in a loaded library.
// Returns the function/data pointer, or nullptr if not found.
void* dlsym(LibHandle* lib, const char* symbolName);
// Unload a library.
// Returns 0 on success, -1 on failure.
int dlclose(LibHandle* lib);
} // namespace libloader
+39 -10
View File
@@ -37,6 +37,22 @@ namespace heap_detail {
inline FreeNode g_overflow{0, nullptr};
inline bool g_initialized = false;
// Process-wide heap lock. Userspace threads share the heap, so the
// public malloc/mfree/realloc entry points must serialize access to
// g_buckets/g_overflow. Kept inline here (not in thread.h) because
// thread.h depends on heap.h, and the internal helpers below are not
// reentrant into the public API, so a plain spinlock suffices.
inline volatile uint32_t g_heap_lock = 0;
static inline void heap_lock_acquire() {
while (__atomic_exchange_n(&g_heap_lock, 1, __ATOMIC_ACQUIRE) != 0) {
syscall0(Montauk::SYS_YIELD);
}
}
static inline void heap_lock_release() {
__atomic_store_n(&g_heap_lock, 0, __ATOMIC_RELEASE);
}
static inline Header* get_header(void* block) {
return (Header*)((uint8_t*)block - sizeof(Header));
}
@@ -146,15 +162,17 @@ namespace heap_detail {
inline void* malloc(uint64_t size) {
using namespace heap_detail;
// Guard against overflow: size + Header must not wrap
if (size > UINT64_MAX - sizeof(Header) - 15)
return nullptr;
heap_lock_acquire();
if (!g_initialized) {
grow(16 * 0x1000); // seed with 64 KiB
g_initialized = true;
}
// Guard against overflow: size + Header must not wrap
if (size > UINT64_MAX - sizeof(Header) - 15)
return nullptr;
uint64_t needed = size + sizeof(Header);
needed = (needed + 15) & ~15ULL;
@@ -162,8 +180,10 @@ namespace heap_detail {
if (idx >= 0) {
// Small allocation — use segregated bucket (O(1))
if (g_buckets[idx] == nullptr && !refill_bucket(idx))
if (g_buckets[idx] == nullptr && !refill_bucket(idx)) {
heap_lock_release();
return nullptr;
}
FreeNode* node = g_buckets[idx];
g_buckets[idx] = node->next;
@@ -171,20 +191,22 @@ namespace heap_detail {
Header* header = (Header*)node;
header->magic = HEADER_MAGIC;
header->size = size;
heap_lock_release();
return (void*)((uint8_t*)header + sizeof(Header));
}
// Large allocation — search overflow list
void* block = take_from_overflow(needed);
if (block == nullptr) {
if (!grow(needed)) return nullptr;
if (!grow(needed)) { heap_lock_release(); return nullptr; }
block = take_from_overflow(needed);
if (block == nullptr) return nullptr;
if (block == nullptr) { heap_lock_release(); return nullptr; }
}
Header* header = (Header*)block;
header->magic = HEADER_MAGIC;
header->size = size;
heap_lock_release();
return (void*)((uint8_t*)header + sizeof(Header));
}
@@ -195,8 +217,10 @@ namespace heap_detail {
Header* header = get_header(ptr);
if (header->magic == FREED_MAGIC) return; // double-free
if (header->magic != HEADER_MAGIC) return; // corrupt
heap_lock_acquire();
if (header->magic == FREED_MAGIC) { heap_lock_release(); return; } // double-free
if (header->magic != HEADER_MAGIC) { heap_lock_release(); return; } // corrupt
header->magic = FREED_MAGIC;
uint64_t blockSize = header->size + sizeof(Header);
@@ -214,15 +238,18 @@ namespace heap_detail {
// Large block — sorted insert with coalescing
insert_overflow((void*)header, blockSize);
}
heap_lock_release();
}
inline void* realloc(void* ptr, uint64_t size) {
if (ptr == nullptr) return malloc(size);
// Read old size under the lock to avoid racing with another
// thread that might be freeing/recycling this header.
heap_detail::heap_lock_acquire();
auto* header = heap_detail::get_header(ptr);
uint64_t old = header->size;
// Compute actual block size (accounting for bucket rounding)
uint64_t oldBlock = (old + sizeof(heap_detail::Header) + 15) & ~15ULL;
int idx = heap_detail::bucket_index(oldBlock);
if (idx >= 0) oldBlock = heap_detail::BUCKET_SIZES[idx];
@@ -230,8 +257,10 @@ namespace heap_detail {
uint64_t newNeed = (size + sizeof(heap_detail::Header) + 15) & ~15ULL;
if (newNeed <= oldBlock) {
header->size = size;
heap_detail::heap_lock_release();
return ptr;
}
heap_detail::heap_lock_release();
void* newBlock = malloc(size);
if (newBlock == nullptr) return nullptr;
+171
View File
@@ -120,6 +120,12 @@ namespace montauk {
inline int spawn(const char* path, const char* args = nullptr) {
return (int)syscall2(Montauk::SYS_SPAWN, (uint64_t)path, (uint64_t)args);
}
inline int chdir(const char* path) {
return (int)syscall1(Montauk::SYS_CHDIR, (uint64_t)path);
}
inline int getcwd(char* buf, uint64_t maxLen) {
return (int)syscall2(Montauk::SYS_GETCWD, (uint64_t)buf, maxLen);
}
// Console
inline void print(const char* text) { syscall1(Montauk::SYS_PRINT, (uint64_t)text); }
@@ -135,6 +141,13 @@ namespace montauk {
inline int readdir(const char* path, const char** names, int max) {
return (int)syscall3(Montauk::SYS_READDIR, (uint64_t)path, (uint64_t)names, (uint64_t)max);
}
// Paginated directory read: returns entries [startIndex, startIndex+max).
// Call repeatedly with increasing startIndex (advancing by the returned
// count) until it returns 0 to enumerate directories of any size.
inline int readdir_at(const char* path, const char** names, int max, int startIndex) {
return (int)syscall4(Montauk::SYS_READDIR_AT, (uint64_t)path, (uint64_t)names,
(uint64_t)max, (uint64_t)startIndex);
}
// File write/create
inline int fwrite(int handle, const uint8_t* buf, uint64_t off, uint64_t size) {
@@ -149,12 +162,20 @@ namespace montauk {
inline int fmkdir(const char* path) {
return (int)syscall1(Montauk::SYS_FMKDIR, (uint64_t)path);
}
inline int frename(const char* oldPath, const char* newPath) {
return (int)syscall2(Montauk::SYS_FRENAME, (uint64_t)oldPath, (uint64_t)newPath);
}
inline int drivelist(int* outDrives, int max) {
return (int)syscall2(Montauk::SYS_DRIVELIST, (uint64_t)outDrives, (uint64_t)max);
}
inline int drivelabel(int drive, char* outLabel, int maxLen) {
return (int)syscall3(Montauk::SYS_DRIVELABEL, (uint64_t)drive, (uint64_t)outLabel, (uint64_t)maxLen);
}
// Returns the kernel BlockDeviceKind backing the drive: 0=unknown/ramdisk,
// 1=SATA, 2=SATAPI, 3=NVMe, 4=USB MSC.
inline int drivekind(int drive) {
return (int)syscall1(Montauk::SYS_DRIVEKIND, (uint64_t)drive);
}
// Memory
inline void* alloc(uint64_t size) { return (void*)syscall1(Montauk::SYS_ALLOC, size); }
@@ -171,6 +192,9 @@ namespace montauk {
inline bool is_key_available() { return (bool)syscall0(Montauk::SYS_ISKEYAVAILABLE); }
inline void getkey(Montauk::KeyEvent* out) { syscall1(Montauk::SYS_GETKEY, (uint64_t)out); }
inline char getchar() { return (char)syscall0(Montauk::SYS_GETCHAR); }
inline uint64_t input_wait(uint64_t observedSerial, uint64_t timeoutMs) {
return (uint64_t)syscall2(Montauk::SYS_INPUT_WAIT, observedSerial, timeoutMs);
}
// Networking
inline int32_t ping(uint32_t ip, uint32_t timeoutMs = 3000) {
@@ -221,6 +245,81 @@ namespace montauk {
(uint64_t)maxLen, (uint64_t)srcIp, (uint64_t)srcPort);
}
// Generic IPC
inline int dup_handle(int handle) {
return (int)syscall1(Montauk::SYS_DUPHANDLE, (uint64_t)handle);
}
inline uint32_t wait_handle(int handle, uint32_t wantedSignals, uint64_t timeoutMs = ~0ULL) {
return (uint32_t)syscall3(Montauk::SYS_WAIT_HANDLE, (uint64_t)handle,
(uint64_t)wantedSignals, timeoutMs);
}
inline int stream_create(int* outReadHandle, int* outWriteHandle, uint32_t capacity = 0) {
return (int)syscall3(Montauk::SYS_STREAM_CREATE, (uint64_t)outReadHandle,
(uint64_t)outWriteHandle, (uint64_t)capacity);
}
inline int stream_read(int handle, void* buf, int maxLen) {
return (int)syscall3(Montauk::SYS_STREAM_READ, (uint64_t)handle, (uint64_t)buf, (uint64_t)maxLen);
}
inline int stream_write(int handle, const void* data, int len) {
return (int)syscall3(Montauk::SYS_STREAM_WRITE, (uint64_t)handle, (uint64_t)data, (uint64_t)len);
}
inline int mailbox_create(int* outSendHandle, int* outRecvHandle) {
return (int)syscall2(Montauk::SYS_MAILBOX_CREATE, (uint64_t)outSendHandle, (uint64_t)outRecvHandle);
}
inline int mailbox_send(int handle, uint32_t msgType, const void* data, uint16_t len, int attachHandle = -1) {
return (int)syscall5(Montauk::SYS_MAILBOX_SEND, (uint64_t)handle, (uint64_t)msgType,
(uint64_t)data, (uint64_t)len, (uint64_t)(int64_t)attachHandle);
}
inline int mailbox_recv(int handle, uint32_t* outMsgType, void* data,
uint16_t* inOutLen, int* outAttachHandle = nullptr) {
return (int)syscall5(Montauk::SYS_MAILBOX_RECV, (uint64_t)handle, (uint64_t)outMsgType,
(uint64_t)data, (uint64_t)inOutLen, (uint64_t)outAttachHandle);
}
inline int waitset_create() {
return (int)syscall0(Montauk::SYS_WAITSET_CREATE);
}
inline int waitset_add(int waitsetHandle, int targetHandle, uint32_t signals) {
return (int)syscall3(Montauk::SYS_WAITSET_ADD, (uint64_t)waitsetHandle,
(uint64_t)targetHandle, (uint64_t)signals);
}
inline int waitset_remove(int waitsetHandle, int index) {
return (int)syscall2(Montauk::SYS_WAITSET_REMOVE, (uint64_t)waitsetHandle, (uint64_t)index);
}
inline int waitset_wait(int waitsetHandle, Montauk::IpcWaitResult* outReady, uint64_t timeoutMs = ~0ULL) {
return (int)syscall3(Montauk::SYS_WAITSET_WAIT, (uint64_t)waitsetHandle, (uint64_t)outReady, timeoutMs);
}
inline int proc_open(int pid) {
return (int)syscall1(Montauk::SYS_PROC_OPEN, (uint64_t)pid);
}
inline int surface_create(uint64_t byteSize) {
return (int)syscall1(Montauk::SYS_SURFACE_CREATE, byteSize);
}
inline void* surface_map(int handle) {
return (void*)syscall1(Montauk::SYS_SURFACE_MAP, (uint64_t)handle);
}
inline int surface_resize(int handle, uint64_t newSize) {
return (int)syscall2(Montauk::SYS_SURFACE_RESIZE, (uint64_t)handle, newSize);
}
// Shared library support
inline int load_lib(const char* path) {
return (int)syscall1(Montauk::SYS_LOAD_LIB, (uint64_t)path);
}
inline int unload_lib(int handle) {
return (int)syscall1(Montauk::SYS_UNLOAD_LIB, (uint64_t)handle);
}
inline void* dlsym(int handle, uint64_t symbolOffset) {
return (void*)syscall2(Montauk::SYS_DLSYM, (uint64_t)handle, symbolOffset);
}
inline uint64_t get_libbase(int handle) {
return (uint64_t)syscall1(Montauk::SYS_GETLIBBASE, (uint64_t)handle);
}
// Crash report retrieval
inline int crash_report(Montauk::CrashReportInfo* out) {
return (int)syscall1(Montauk::SYS_CRASH_REPORT, (uint64_t)out);
}
// Process management
inline void waitpid(int pid) { syscall1(Montauk::SYS_WAITPID, (uint64_t)pid); }
@@ -273,6 +372,19 @@ namespace montauk {
__builtin_unreachable();
}
inline int suspend() {
return (int)syscall0(Montauk::SYS_SUSPEND);
}
// Graceful power-off request channel. The desktop posts a pending action
// (Montauk::POWER_REQ_SHUTDOWN / POWER_REQ_REBOOT) then exits; login.elf
// reads it with POWER_REQ_QUERY (read-and-clear), runs the shutdown stages,
// and finally calls shutdown()/reset(). Returns the pending action for a
// query, or 0 when posting one.
inline int power_request(int action) {
return (int)syscall1(Montauk::SYS_POWER_REQUEST, (uint64_t)(int64_t)action);
}
// Mouse
inline void mouse_state(Montauk::MouseState* out) { syscall1(Montauk::SYS_MOUSESTATE, (uint64_t)out); }
inline void set_mouse_bounds(int32_t maxX, int32_t maxY) {
@@ -340,6 +452,11 @@ namespace montauk {
inline int fs_mount(int partIndex, int driveNum) {
return (int)syscall2(Montauk::SYS_FSMOUNT, (uint64_t)partIndex, (uint64_t)driveNum);
}
// Flush all block-device write caches and cleanly unmount disk-backed
// volumes ahead of power-off. Returns the number of volumes unmounted.
inline int fs_sync() {
return (int)syscall0(Montauk::SYS_FS_SYNC);
}
inline int fs_format(const Montauk::FsFormatParams* params) {
return (int)syscall1(Montauk::SYS_FSFORMAT, (uint64_t)params);
}
@@ -381,6 +498,33 @@ namespace montauk {
inline int audio_bt_status(int handle) {
return audio_ctl(handle, Montauk::AUDIO_CTL_BT_STATUS, 0);
}
inline int audio_set_master_volume(int percent) {
return audio_ctl(-1, Montauk::AUDIO_CTL_SET_MASTER_VOLUME, percent);
}
inline int audio_get_master_volume() {
return audio_ctl(-1, Montauk::AUDIO_CTL_GET_MASTER_VOLUME, 0);
}
inline int audio_set_master_mute(bool muted) {
return audio_ctl(-1, Montauk::AUDIO_CTL_SET_MASTER_MUTE, muted ? 1 : 0);
}
inline int audio_get_master_mute() {
return audio_ctl(-1, Montauk::AUDIO_CTL_GET_MASTER_MUTE, 0);
}
inline int audio_set_mute(int handle, bool muted) {
return audio_ctl(handle, Montauk::AUDIO_CTL_SET_MUTE, muted ? 1 : 0);
}
inline int audio_get_mute(int handle) {
return audio_ctl(handle, Montauk::AUDIO_CTL_GET_MUTE, 0);
}
inline int audio_list(Montauk::AudioStreamInfo* buf, int maxCount) {
return (int)syscall2(Montauk::SYS_AUDIOLIST, (uint64_t)buf, (uint64_t)maxCount);
}
// Returns the current mixer state serial. With timeoutMs > 0 the call
// blocks until the serial differs from `prevSerial` or the timeout
// elapses. With timeoutMs == 0 it returns immediately (cheap snapshot).
inline uint64_t audio_wait(uint64_t prevSerial, uint64_t timeoutMs) {
return syscall2(Montauk::SYS_AUDIOWAIT, prevSerial, timeoutMs);
}
// Bluetooth
inline int bt_scan(Montauk::BtScanResult* buf, int maxCount, uint32_t timeoutMs) {
@@ -402,6 +546,30 @@ namespace montauk {
// Kernel introspection
inline void memstats(Montauk::MemStats* out) { syscall1(Montauk::SYS_MEMSTATS, (uint64_t)out); }
// User management
inline int setuser(int pid, const char* name) {
return (int)syscall2(Montauk::SYS_SETUSER, (uint64_t)pid, (uint64_t)name);
}
inline int getuser(char* buf, uint64_t maxLen) {
return (int)syscall2(Montauk::SYS_GETUSER, (uint64_t)buf, maxLen);
}
// Clipboard
inline int clipboard_set_text(const char* data, uint32_t len) {
return (int)syscall2(Montauk::SYS_CLIPBOARD_SET_TEXT, (uint64_t)data, (uint64_t)len);
}
inline int clipboard_get_info(Montauk::ClipboardInfo* out) {
return (int)syscall1(Montauk::SYS_CLIPBOARD_GET_INFO, (uint64_t)out);
}
inline int clipboard_get_text(char* buf, uint32_t bufLen, uint32_t* outLen,
uint64_t* outSerial = nullptr) {
return (int)syscall4(Montauk::SYS_CLIPBOARD_GET_TEXT, (uint64_t)buf, (uint64_t)bufLen,
(uint64_t)outLen, (uint64_t)outSerial);
}
inline int clipboard_clear() {
return (int)syscall0(Montauk::SYS_CLIPBOARD_CLEAR);
}
// Window server
inline int win_create(const char* title, int w, int h, Montauk::WinCreateResult* result) {
return (int)syscall4(Montauk::SYS_WINCREATE, (uint64_t)title, (uint64_t)w, (uint64_t)h, (uint64_t)result);
@@ -421,6 +589,9 @@ namespace montauk {
inline uint64_t win_map(int id) {
return (uint64_t)syscall1(Montauk::SYS_WINMAP, (uint64_t)id);
}
inline int win_unmap(int id) {
return (int)syscall1(Montauk::SYS_WINUNMAP, (uint64_t)id);
}
inline int win_sendevent(int id, const Montauk::WinEvent* event) {
return (int)syscall2(Montauk::SYS_WINSENDEVENT, (uint64_t)id, (uint64_t)event);
}
+134
View File
@@ -0,0 +1,134 @@
/*
* thread.h
* Userspace threading primitives for MontaukOS
* Copyright (c) 2026 Daniel Hammer
*
* Threads share their parent's address space, IPC handles, cwd, and
* heap allocator. The caller owns the thread stack: thread_spawn
* allocates one out of the user heap and the kernel uses it verbatim.
*
* Lifetimes:
* - thread_spawn returns a positive TID on success.
* - thread_join blocks until the target TID exits, frees its kernel
* stack, then returns its exit code via *out_code.
* - thread_exit terminates only the current thread. If the main thread
* calls exit() (SYS_EXIT) the whole process tears down, killing any
* surviving sibling threads.
* - thread_self returns the calling thread's TID.
*/
#pragma once
#include <cstdint>
#include <Api/Syscall.hpp>
#include <montauk/syscall.h>
#include <montauk/heap.h>
namespace montauk {
// Default per-thread stack size (64 KiB). Enough for typical app work;
// matches the main thread's 32 KiB lower bound with headroom for
// TrueType rendering call chains.
static constexpr uint64_t DEFAULT_THREAD_STACK_BYTES = 64 * 1024;
using ThreadEntry = int (*)(void* arg);
// Terminate the calling thread. Never returns. If called by the main
// thread this is equivalent to exit() (the whole process exits).
[[noreturn]] inline void thread_exit(int code = 0) {
syscall1(Montauk::SYS_THREAD_EXIT, (uint64_t)code);
__builtin_unreachable();
}
namespace detail {
struct ThreadCtx {
ThreadEntry user_entry;
void* user_arg;
void* stack_base;
};
// Userspace trampoline: bridges from the raw entry the kernel jumps
// to into the typed entry, then funnels into SYS_THREAD_EXIT. We
// route the exit through libc rather than relying on a kernel-side
// exit stub on the user stack -- the kernel never writes to user
// memory on this path.
//
// The thread's stack itself is intentionally not freed here: we are
// still running on it. It is reclaimed when the process exits, or
// the joiner may free it explicitly after thread_join.
[[noreturn]] inline void thread_trampoline(detail::ThreadCtx* ctx) {
int code = ctx->user_entry(ctx->user_arg);
montauk::mfree(ctx);
thread_exit(code);
}
}
// Spawn a new thread that begins executing `entry(arg)`. Returns the
// new TID on success, or -1 on failure. The thread's stack is
// allocated from the user heap; it is leaked on thread exit (the
// thread itself cannot free the stack it is running on). The kernel
// reclaims it on process exit. Callers that need to spawn many short-
// lived threads should pool stacks themselves.
inline int thread_spawn(ThreadEntry entry, void* arg,
uint64_t stack_bytes = 0) {
if (entry == nullptr) return -1;
if (stack_bytes == 0) stack_bytes = DEFAULT_THREAD_STACK_BYTES;
stack_bytes = (stack_bytes + 15) & ~15ULL;
void* stack = montauk::malloc(stack_bytes);
if (stack == nullptr) return -1;
auto* ctx = (detail::ThreadCtx*)montauk::malloc(sizeof(detail::ThreadCtx));
if (ctx == nullptr) {
montauk::mfree(stack);
return -1;
}
ctx->user_entry = entry;
ctx->user_arg = arg;
ctx->stack_base = stack;
uint64_t stack_top = ((uint64_t)stack + stack_bytes) & ~0xFULL;
int tid = (int)syscall3(Montauk::SYS_THREAD_SPAWN,
(uint64_t)&detail::thread_trampoline,
(uint64_t)ctx, stack_top);
if (tid < 0) {
montauk::mfree(ctx);
montauk::mfree(stack);
return -1;
}
return tid;
}
// Block until the thread identified by `tid` terminates. Returns 0 on
// success (with the thread's exit code in *out_code if non-null) or
// -1 if `tid` is not a joinable sibling.
inline int thread_join(int tid, int* out_code = nullptr) {
return (int)syscall2(Montauk::SYS_THREAD_JOIN,
(uint64_t)tid, (uint64_t)out_code);
}
// Return the calling thread's TID (== getpid() for the main thread).
inline int thread_self() {
return (int)syscall0(Montauk::SYS_THREAD_SELF);
}
// Lightweight mutex backed by a single atomic word + the kernel
// yield syscall. Adequate for short critical sections; a heavier
// primitive can wrap mailbox_recv when blocking semantics are needed.
struct Mutex {
volatile uint32_t locked = 0;
void lock() {
while (__atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE) != 0) {
montauk::yield();
}
}
bool try_lock() {
return __atomic_exchange_n(&locked, 1, __ATOMIC_ACQUIRE) == 0;
}
void unlock() {
__atomic_store_n(&locked, 0, __ATOMIC_RELEASE);
}
};
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.