feat: convert procmgr and mandelbrot to Window Server apps
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* app_external_launchers.cpp
|
||||
* Desktop launch wrappers for standalone Window Server apps
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
static void spawn_app(const char* path, const char* args = nullptr) {
|
||||
if (path && path[0]) {
|
||||
montauk::spawn(path, args);
|
||||
}
|
||||
}
|
||||
|
||||
void open_terminal(DesktopState* ds) {
|
||||
const char* home = (ds && ds->home_dir[0]) ? ds->home_dir : nullptr;
|
||||
spawn_app("0:/apps/terminal/terminal.elf", home);
|
||||
}
|
||||
|
||||
void open_texteditor(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/texteditor/texteditor.elf");
|
||||
}
|
||||
|
||||
void open_klog(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/klog/klog.elf");
|
||||
}
|
||||
|
||||
void open_wordprocessor(DesktopState* ds) {
|
||||
(void)ds;
|
||||
spawn_app("0:/apps/wordprocessor/wordprocessor.elf");
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* app_klog.cpp
|
||||
* MontaukOS Desktop - Kernel Log launcher
|
||||
* Spawns standalone klog.elf Window Server app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_klog(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/apps/klog/klog.elf");
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* app_mandelbrot.cpp
|
||||
* MontaukOS Desktop - Mandelbrot set visualizer
|
||||
* Supports zoom (scroll wheel), pan (drag), and reset (R key)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Fixed-point Mandelbrot (avoids FPU/SSE dependency issues)
|
||||
// Uses 28.36 fixed-point: 36 fractional bits gives ~1e-10 precision
|
||||
// ============================================================================
|
||||
|
||||
using fp_t = int64_t;
|
||||
static constexpr int FP_SHIFT = 36;
|
||||
static constexpr fp_t FP_ONE = (fp_t)1 << FP_SHIFT;
|
||||
|
||||
static inline fp_t fp_from_int(int v) { return (fp_t)v << FP_SHIFT; }
|
||||
|
||||
static inline fp_t fp_mul(fp_t a, fp_t b) {
|
||||
// Split to avoid overflow: a * b >> SHIFT
|
||||
// Use 128-bit intermediate via compiler builtin
|
||||
__int128 r = (__int128)a * b;
|
||||
return (fp_t)(r >> FP_SHIFT);
|
||||
}
|
||||
|
||||
// fp_div for small numerators (avoids __divti3 by using 64-bit division)
|
||||
// Only valid when a is small enough that a << FP_SHIFT fits in int64_t
|
||||
static inline fp_t fp_div_small(int a, int b) {
|
||||
return ((fp_t)a << FP_SHIFT) / (fp_t)b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// State
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MB_MAX_ITER = 256;
|
||||
static constexpr int MB_TOOLBAR_H = 32;
|
||||
|
||||
struct MandelbrotState {
|
||||
DesktopState* desktop;
|
||||
fp_t center_x, center_y; // center of view in fractal coords
|
||||
fp_t scale; // units per pixel (fixed-point)
|
||||
int max_iter;
|
||||
bool needs_render;
|
||||
// Drag state
|
||||
bool dragging;
|
||||
int drag_start_x, drag_start_y;
|
||||
fp_t drag_start_cx, drag_start_cy;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Color palette
|
||||
// ============================================================================
|
||||
|
||||
static uint32_t mandelbrot_color(int iter, int max_iter) {
|
||||
if (iter >= max_iter) return 0xFF000000; // black for inside set
|
||||
|
||||
// Smooth cycling palette using bit manipulation
|
||||
int t = (iter * 7) & 0xFF;
|
||||
int phase = (iter * 7) >> 8;
|
||||
|
||||
uint8_t r, g, b;
|
||||
switch (phase % 6) {
|
||||
case 0: r = 255; g = (uint8_t)t; b = 0; break;
|
||||
case 1: r = (uint8_t)(255 - t); g = 255; b = 0; break;
|
||||
case 2: r = 0; g = 255; b = (uint8_t)t; break;
|
||||
case 3: r = 0; g = (uint8_t)(255 - t); b = 255; break;
|
||||
case 4: r = (uint8_t)t; g = 0; b = 255; break;
|
||||
default: r = 255; g = 0; b = (uint8_t)(255 - t); break;
|
||||
}
|
||||
|
||||
return 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render
|
||||
// ============================================================================
|
||||
|
||||
static void mandelbrot_render(MandelbrotState* mb, uint32_t* pixels, int w, int h) {
|
||||
int render_h = h - MB_TOOLBAR_H;
|
||||
if (render_h <= 0) return;
|
||||
|
||||
fp_t half_w = fp_mul(fp_from_int(w / 2), mb->scale);
|
||||
fp_t half_h = fp_mul(fp_from_int(render_h / 2), mb->scale);
|
||||
fp_t x_min = mb->center_x - half_w;
|
||||
fp_t y_min = mb->center_y - half_h;
|
||||
fp_t bailout = fp_from_int(4);
|
||||
|
||||
for (int py = 0; py < render_h; py++) {
|
||||
fp_t ci = y_min + fp_mul(fp_from_int(py), mb->scale);
|
||||
uint32_t* row = pixels + (py + MB_TOOLBAR_H) * w;
|
||||
|
||||
for (int px = 0; px < w; px++) {
|
||||
fp_t cr = x_min + fp_mul(fp_from_int(px), mb->scale);
|
||||
|
||||
fp_t zr = 0, zi = 0;
|
||||
int iter = 0;
|
||||
|
||||
while (iter < mb->max_iter) {
|
||||
fp_t zr2 = fp_mul(zr, zr);
|
||||
fp_t zi2 = fp_mul(zi, zi);
|
||||
if (zr2 + zi2 > bailout) break;
|
||||
fp_t new_zr = zr2 - zi2 + cr;
|
||||
zi = fp_mul(fp_from_int(2), fp_mul(zr, zi)) + ci;
|
||||
zr = new_zr;
|
||||
iter++;
|
||||
}
|
||||
|
||||
row[px] = mandelbrot_color(iter, mb->max_iter);
|
||||
}
|
||||
}
|
||||
|
||||
mb->needs_render = false;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
static void mb_on_draw(Window* win, Framebuffer& fb) {
|
||||
MandelbrotState* mb = (MandelbrotState*)win->app_data;
|
||||
if (!mb) return;
|
||||
|
||||
Canvas c(win);
|
||||
|
||||
if (mb->needs_render) {
|
||||
mandelbrot_render(mb, win->content, c.w, c.h);
|
||||
}
|
||||
|
||||
// Draw toolbar over the render
|
||||
c.fill_rect(0, 0, c.w, MB_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, MB_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// Reset button
|
||||
c.button(8, 4, 60, 24, "Reset", colors::ACCENT, colors::WHITE, 4);
|
||||
|
||||
// Iter +/- buttons
|
||||
char iter_str[24];
|
||||
snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter);
|
||||
int fh = system_font_height();
|
||||
c.text(80, (MB_TOOLBAR_H - fh) / 2, iter_str, colors::TEXT_COLOR);
|
||||
|
||||
int iter_text_w = text_width(iter_str);
|
||||
int btn_x = 80 + iter_text_w + 8;
|
||||
c.button(btn_x, 4, 24, 24, "-", Color::from_rgb(0xAA, 0xAA, 0xAA), colors::WHITE, 4);
|
||||
c.button(btn_x + 28, 4, 24, 24, "+", Color::from_rgb(0xAA, 0xAA, 0xAA), colors::WHITE, 4);
|
||||
|
||||
// Hint
|
||||
const char* hint = "Scroll=zoom Drag=pan";
|
||||
int hw = text_width(hint);
|
||||
c.text(c.w - hw - 8, (MB_TOOLBAR_H - fh) / 2, hint, Color::from_rgb(0x99, 0x99, 0x99));
|
||||
}
|
||||
|
||||
static void mb_on_mouse(Window* win, MouseEvent& ev) {
|
||||
MandelbrotState* mb = (MandelbrotState*)win->app_data;
|
||||
if (!mb) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int lx = ev.x - cr.x;
|
||||
int ly = ev.y - cr.y;
|
||||
|
||||
// Handle toolbar clicks
|
||||
if (ev.left_pressed() && ly < MB_TOOLBAR_H) {
|
||||
// Reset button
|
||||
Rect reset_r = {8, 4, 60, 24};
|
||||
if (reset_r.contains(lx, ly)) {
|
||||
mb->center_x = -fp_from_int(1) / 2; // -0.5
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Iter buttons
|
||||
char iter_str[24];
|
||||
snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter);
|
||||
int iter_text_w = text_width(iter_str);
|
||||
int btn_x = 80 + iter_text_w + 8;
|
||||
|
||||
Rect minus_r = {btn_x, 4, 24, 24};
|
||||
Rect plus_r = {btn_x + 28, 4, 24, 24};
|
||||
|
||||
if (minus_r.contains(lx, ly)) {
|
||||
if (mb->max_iter > 32) { mb->max_iter /= 2; mb->needs_render = true; }
|
||||
return;
|
||||
}
|
||||
if (plus_r.contains(lx, ly)) {
|
||||
if (mb->max_iter < 4096) { mb->max_iter *= 2; mb->needs_render = true; }
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Drag panning
|
||||
if (ev.left_pressed() && ly >= MB_TOOLBAR_H) {
|
||||
mb->dragging = true;
|
||||
mb->drag_start_x = lx;
|
||||
mb->drag_start_y = ly;
|
||||
mb->drag_start_cx = mb->center_x;
|
||||
mb->drag_start_cy = mb->center_y;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mb->dragging && ev.left_held()) {
|
||||
int dx = lx - mb->drag_start_x;
|
||||
int dy = ly - mb->drag_start_y;
|
||||
mb->center_x = mb->drag_start_cx - fp_mul(fp_from_int(dx), mb->scale);
|
||||
mb->center_y = mb->drag_start_cy - fp_mul(fp_from_int(dy), mb->scale);
|
||||
mb->needs_render = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (mb->dragging && !ev.left_held()) {
|
||||
mb->dragging = false;
|
||||
}
|
||||
|
||||
// Scroll zoom (centered on mouse position)
|
||||
if (ev.scroll != 0 && ly >= MB_TOOLBAR_H) {
|
||||
int render_h = cr.h - MB_TOOLBAR_H;
|
||||
// Mouse position in fractal coords before zoom
|
||||
fp_t mx_frac = mb->center_x + fp_mul(fp_from_int(lx - cr.w / 2), mb->scale);
|
||||
fp_t my_frac = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale);
|
||||
|
||||
if (ev.scroll < 0) {
|
||||
// Zoom in
|
||||
mb->scale = fp_mul(mb->scale, fp_from_int(3) / 4); // * 0.75
|
||||
} else {
|
||||
// Zoom out
|
||||
mb->scale = fp_mul(mb->scale, fp_from_int(4) / 3); // * 1.33
|
||||
}
|
||||
|
||||
// Adjust center so mouse stays at same fractal point
|
||||
fp_t new_mx = mb->center_x + fp_mul(fp_from_int(lx - cr.w / 2), mb->scale);
|
||||
fp_t new_my = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale);
|
||||
mb->center_x += mx_frac - new_mx;
|
||||
mb->center_y += my_frac - new_my;
|
||||
|
||||
mb->needs_render = true;
|
||||
}
|
||||
}
|
||||
|
||||
static void mb_on_key(Window* win, const Montauk::KeyEvent& key) {
|
||||
MandelbrotState* mb = (MandelbrotState*)win->app_data;
|
||||
if (!mb || !key.pressed) return;
|
||||
|
||||
if (key.ascii == 'r' || key.ascii == 'R') {
|
||||
Rect cr = win->content_rect();
|
||||
mb->center_x = -fp_from_int(1) / 2;
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
} else if (key.ascii == '+' || key.ascii == '=') {
|
||||
if (mb->max_iter < 4096) { mb->max_iter *= 2; mb->needs_render = true; }
|
||||
} else if (key.ascii == '-') {
|
||||
if (mb->max_iter > 32) { mb->max_iter /= 2; mb->needs_render = true; }
|
||||
}
|
||||
}
|
||||
|
||||
static void mb_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
montauk::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mandelbrot launcher
|
||||
// ============================================================================
|
||||
|
||||
void open_mandelbrot(DesktopState* ds) {
|
||||
int idx = desktop_create_window(ds, "Mandelbrot", 120, 60, 500, 400);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
Rect cr = win->content_rect();
|
||||
|
||||
MandelbrotState* mb = (MandelbrotState*)montauk::malloc(sizeof(MandelbrotState));
|
||||
montauk::memset(mb, 0, sizeof(MandelbrotState));
|
||||
mb->desktop = ds;
|
||||
mb->center_x = -fp_from_int(1) / 2; // -0.5
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, cr.w > 0 ? cr.w : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
mb->dragging = false;
|
||||
|
||||
win->app_data = mb;
|
||||
win->on_draw = mb_on_draw;
|
||||
win->on_mouse = mb_on_mouse;
|
||||
win->on_key = mb_on_key;
|
||||
win->on_close = mb_on_close;
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* app_procmgr.cpp
|
||||
* MontaukOS Desktop - Process Manager
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Process Manager state
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int PM_TOOLBAR_H = 36;
|
||||
static constexpr int PM_HEADER_H = 24;
|
||||
static constexpr int PM_ITEM_H = 28;
|
||||
static constexpr int PM_MAX_PROCS = 16;
|
||||
static constexpr int PM_POLL_MS = 1000;
|
||||
|
||||
struct ProcMgrState {
|
||||
DesktopState* desktop;
|
||||
Montauk::ProcInfo procs[PM_MAX_PROCS];
|
||||
int proc_count;
|
||||
int selected; // selected row index (-1 = none)
|
||||
uint64_t last_poll_ms;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Callbacks
|
||||
// ============================================================================
|
||||
|
||||
static void procmgr_on_poll(Window* win) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm) return;
|
||||
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
if (now - pm->last_poll_ms < PM_POLL_MS) return;
|
||||
pm->last_poll_ms = now;
|
||||
|
||||
// Remember previously selected PID
|
||||
int prev_pid = -1;
|
||||
if (pm->selected >= 0 && pm->selected < pm->proc_count) {
|
||||
prev_pid = pm->procs[pm->selected].pid;
|
||||
}
|
||||
|
||||
pm->proc_count = montauk::proclist(pm->procs, PM_MAX_PROCS);
|
||||
|
||||
// Restore selection by matching PID
|
||||
pm->selected = -1;
|
||||
if (prev_pid >= 0) {
|
||||
for (int i = 0; i < pm->proc_count; i++) {
|
||||
if (pm->procs[i].pid == prev_pid) {
|
||||
pm->selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
win->dirty = true;
|
||||
}
|
||||
|
||||
static void procmgr_on_draw(Window* win, Framebuffer& fb) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm) return;
|
||||
|
||||
Canvas c(win);
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
int fh = system_font_height();
|
||||
|
||||
// --- Toolbar ---
|
||||
c.fill_rect(0, 0, c.w, PM_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, PM_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// "End Process" button
|
||||
int btn_w = 100;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (PM_TOOLBAR_H - btn_h) / 2;
|
||||
Color btn_bg = (pm->selected >= 0 && pm->procs[pm->selected].pid != 0)
|
||||
? Color::from_rgb(0xCC, 0x33, 0x33)
|
||||
: Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
c.button(btn_x, btn_y, btn_w, btn_h, "End Process", btn_bg, colors::WHITE, 4);
|
||||
|
||||
// Process count on right
|
||||
char count_str[24];
|
||||
snprintf(count_str, sizeof(count_str), "%d processes", pm->proc_count);
|
||||
int cw = text_width(count_str);
|
||||
c.text(c.w - cw - 12, (PM_TOOLBAR_H - fh) / 2, count_str, colors::TEXT_COLOR);
|
||||
|
||||
// --- Header row ---
|
||||
int header_y = PM_TOOLBAR_H;
|
||||
c.fill_rect(0, header_y, c.w, PM_HEADER_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
int col_pid = 12;
|
||||
int col_name = 64;
|
||||
int col_mem = c.w - 120;
|
||||
int col_state = c.w - 52;
|
||||
|
||||
int ty = header_y + (PM_HEADER_H - fh) / 2;
|
||||
c.text(col_pid, ty, "PID", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
c.text(col_name, ty, "Name", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
c.text(col_mem, ty, "Mem", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
c.text(col_state, ty, "St", Color::from_rgb(0x66, 0x66, 0x66));
|
||||
|
||||
c.hline(0, header_y + PM_HEADER_H - 1, c.w, colors::BORDER);
|
||||
|
||||
// --- Process rows ---
|
||||
int list_y = PM_TOOLBAR_H + PM_HEADER_H;
|
||||
for (int i = 0; i < pm->proc_count; i++) {
|
||||
int row_y = list_y + i * PM_ITEM_H;
|
||||
if (row_y + PM_ITEM_H > c.h) break;
|
||||
|
||||
// Selected row highlight
|
||||
if (i == pm->selected) {
|
||||
c.fill_rect(0, row_y, c.w, PM_ITEM_H, colors::MENU_HOVER);
|
||||
}
|
||||
|
||||
int ry = row_y + (PM_ITEM_H - fh) / 2;
|
||||
|
||||
// PID (right-aligned in PID column)
|
||||
char pid_str[8];
|
||||
snprintf(pid_str, sizeof(pid_str), "%d", (int)pm->procs[i].pid);
|
||||
int pid_w = text_width(pid_str);
|
||||
c.text(col_pid + 32 - pid_w, ry, pid_str, colors::TEXT_COLOR);
|
||||
|
||||
// Name (truncated to fit)
|
||||
c.text(col_name, ry, pm->procs[i].name, colors::TEXT_COLOR);
|
||||
|
||||
// Memory
|
||||
char mem_str[16];
|
||||
format_size(mem_str, (int)pm->procs[i].heapUsed);
|
||||
c.text(col_mem, ry, mem_str, colors::TEXT_COLOR);
|
||||
|
||||
// State
|
||||
const char* st_str;
|
||||
Color st_color;
|
||||
switch (pm->procs[i].state) {
|
||||
case 2: // Running
|
||||
st_str = "Run";
|
||||
st_color = Color::from_rgb(0x22, 0x88, 0x22);
|
||||
break;
|
||||
case 1: // Ready
|
||||
st_str = "Rdy";
|
||||
st_color = Color::from_rgb(0x33, 0x66, 0xCC);
|
||||
break;
|
||||
case 3: // Terminated
|
||||
st_str = "Term";
|
||||
st_color = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
break;
|
||||
default:
|
||||
st_str = "?";
|
||||
st_color = colors::TEXT_COLOR;
|
||||
break;
|
||||
}
|
||||
c.text(col_state, ry, st_str, st_color);
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_mouse(Window* win, MouseEvent& ev) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm) return;
|
||||
|
||||
Rect cr = win->content_rect();
|
||||
int lx = ev.x - cr.x;
|
||||
int ly = ev.y - cr.y;
|
||||
|
||||
if (ev.left_pressed()) {
|
||||
// Check "End Process" button click
|
||||
int btn_w = 100;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (PM_TOOLBAR_H - btn_h) / 2;
|
||||
Rect btn_rect = {btn_x, btn_y, btn_w, btn_h};
|
||||
if (btn_rect.contains(lx, ly)) {
|
||||
if (pm->selected >= 0 && pm->selected < pm->proc_count
|
||||
&& pm->procs[pm->selected].pid != 0) {
|
||||
montauk::kill(pm->procs[pm->selected].pid);
|
||||
pm->last_poll_ms = 0; // force refresh
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check row click
|
||||
int list_y = PM_TOOLBAR_H + PM_HEADER_H;
|
||||
if (ly >= list_y) {
|
||||
int row = (ly - list_y) / PM_ITEM_H;
|
||||
if (row >= 0 && row < pm->proc_count) {
|
||||
pm->selected = row;
|
||||
} else {
|
||||
pm->selected = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_key(Window* win, const Montauk::KeyEvent& key) {
|
||||
ProcMgrState* pm = (ProcMgrState*)win->app_data;
|
||||
if (!pm || !key.pressed) return;
|
||||
|
||||
if (key.scancode == 0x48) { // Up arrow
|
||||
if (pm->selected > 0) pm->selected--;
|
||||
else if (pm->proc_count > 0) pm->selected = 0;
|
||||
} else if (key.scancode == 0x50) { // Down arrow
|
||||
if (pm->selected < pm->proc_count - 1) pm->selected++;
|
||||
} else if (key.scancode == 0x53) { // Delete key
|
||||
if (pm->selected >= 0 && pm->selected < pm->proc_count
|
||||
&& pm->procs[pm->selected].pid != 0) {
|
||||
montauk::kill(pm->procs[pm->selected].pid);
|
||||
pm->last_poll_ms = 0; // force refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void procmgr_on_close(Window* win) {
|
||||
if (win->app_data) {
|
||||
montauk::mfree(win->app_data);
|
||||
win->app_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Process Manager launcher
|
||||
// ============================================================================
|
||||
|
||||
void open_procmgr(DesktopState* ds) {
|
||||
int idx = desktop_create_window(ds, "Processes", 180, 80, 520, 400);
|
||||
if (idx < 0) return;
|
||||
|
||||
Window* win = &ds->windows[idx];
|
||||
|
||||
ProcMgrState* pm = (ProcMgrState*)montauk::malloc(sizeof(ProcMgrState));
|
||||
montauk::memset(pm, 0, sizeof(ProcMgrState));
|
||||
pm->desktop = ds;
|
||||
pm->selected = -1;
|
||||
pm->last_poll_ms = 0;
|
||||
|
||||
// Initial poll
|
||||
pm->proc_count = montauk::proclist(pm->procs, PM_MAX_PROCS);
|
||||
|
||||
win->app_data = pm;
|
||||
win->on_draw = procmgr_on_draw;
|
||||
win->on_mouse = procmgr_on_mouse;
|
||||
win->on_key = procmgr_on_key;
|
||||
win->on_close = procmgr_on_close;
|
||||
win->on_poll = procmgr_on_poll;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/*
|
||||
* app_terminal.cpp
|
||||
* MontaukOS Desktop - Terminal launcher
|
||||
* Spawns standalone terminal.elf Window Server app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_terminal(DesktopState* ds) {
|
||||
if (ds && ds->home_dir[0] != '\0') {
|
||||
montauk::spawn("0:/apps/terminal/terminal.elf", ds->home_dir);
|
||||
} else {
|
||||
montauk::spawn("0:/apps/terminal/terminal.elf");
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* app_texteditor.cpp
|
||||
* MontaukOS Desktop - Text Editor launcher
|
||||
* Spawns standalone texteditor.elf Window Server app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// Text Editor launchers (spawn standalone Window Server app)
|
||||
// ============================================================================
|
||||
|
||||
void open_texteditor(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/apps/texteditor/texteditor.elf");
|
||||
}
|
||||
|
||||
void open_texteditor_with_file(DesktopState* ds, const char* path) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/apps/texteditor/texteditor.elf", path);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* app_wordprocessor.cpp
|
||||
* MontaukOS Desktop - Word Processor launcher
|
||||
* Spawns standalone wordprocessor.elf Window Server app
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_wordprocessor(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/apps/wordprocessor/wordprocessor.elf");
|
||||
}
|
||||
@@ -156,7 +156,7 @@ inline void format_size(char* buf, int size) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Forward declarations for app launchers (embedded apps only)
|
||||
// Forward declarations for desktop launch entry points
|
||||
// ============================================================================
|
||||
|
||||
void open_terminal(DesktopState* ds);
|
||||
@@ -164,10 +164,7 @@ void open_filemanager(DesktopState* ds);
|
||||
void open_sysinfo(DesktopState* ds);
|
||||
void open_calculator(DesktopState* ds);
|
||||
void open_texteditor(DesktopState* ds);
|
||||
void open_texteditor_with_file(DesktopState* ds, const char* path);
|
||||
void open_klog(DesktopState* ds);
|
||||
void open_procmgr(DesktopState* ds);
|
||||
void open_mandelbrot(DesktopState* ds);
|
||||
void open_settings(DesktopState* ds);
|
||||
void open_reboot_dialog(DesktopState* ds);
|
||||
void open_wordprocessor(DesktopState* ds);
|
||||
|
||||
@@ -345,16 +345,11 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
montauk::spawn(row.binary_path);
|
||||
}
|
||||
} else {
|
||||
// Dispatch embedded app
|
||||
// Dispatch desktop-owned launcher entry
|
||||
switch (row.app_id) {
|
||||
case 0: open_terminal(ds); break;
|
||||
case 1: open_filemanager(ds); break;
|
||||
case 2: open_sysinfo(ds); break;
|
||||
case 3: open_calculator(ds); break;
|
||||
case 4: open_texteditor(ds); break;
|
||||
case 5: open_klog(ds); break;
|
||||
case 6: open_procmgr(ds); break;
|
||||
case 7: open_mandelbrot(ds); break;
|
||||
case 11: open_settings(ds); break;
|
||||
case 12: open_reboot_dialog(ds); break;
|
||||
case 14: open_shutdown_dialog(ds); break;
|
||||
|
||||
@@ -119,8 +119,6 @@ static const EmbeddedAppDef embedded_apps[] = {
|
||||
{ "Files", 1, 0 },
|
||||
{ "Calculator", 3, 0 },
|
||||
{ "System Info", 2, 2 },
|
||||
{ "Processes", 6, 2 },
|
||||
{ "Mandelbrot", 7, 3 },
|
||||
};
|
||||
|
||||
static constexpr int NUM_EMBEDDED = sizeof(embedded_apps) / sizeof(embedded_apps[0]);
|
||||
@@ -131,8 +129,6 @@ static SvgIcon* icon_for_embedded(DesktopState* ds, int app_id) {
|
||||
case 1: return &ds->icon_filemanager;
|
||||
case 2: return &ds->icon_sysinfo;
|
||||
case 3: return &ds->icon_calculator;
|
||||
case 6: return &ds->icon_procmgr;
|
||||
case 7: return &ds->icon_mandelbrot;
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -203,16 +199,13 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
ds->icon_appmenu = svg_load("0:/icons/view-app-grid-symbolic.svg", 20, 20, colors::PANEL_TEXT);
|
||||
ds->icon_folder = svg_load("0:/icons/folder.svg", 16, 16, defColor);
|
||||
ds->icon_file = svg_load("0:/icons/text-x-generic.svg", 16, 16, defColor);
|
||||
ds->icon_computer = svg_load("0:/icons/computer.svg", 20, 20, defColor);
|
||||
ds->icon_network = svg_load("0:/icons/network-wired-symbolic.svg", 16, 16, colors::PANEL_TEXT);
|
||||
ds->icon_calculator = svg_load("0:/icons/accessories-calculator.svg", 20, 20, defColor);
|
||||
ds->icon_go_up = svg_load("0:/icons/go-up-symbolic.svg", 16, 16, defColor);
|
||||
ds->icon_go_back = svg_load("0:/icons/go-previous-symbolic.svg", 16, 16, defColor);
|
||||
ds->icon_go_forward = svg_load("0:/icons/go-next-symbolic.svg", 16, 16, defColor);
|
||||
ds->icon_save = svg_load("0:/icons/document-save-symbolic.svg", 16, 16, defColor);
|
||||
ds->icon_home = svg_load("0:/icons/user-home.svg", 16, 16, defColor);
|
||||
ds->icon_exec = svg_load("0:/icons/utilities-terminal.svg", 16, 16, defColor);
|
||||
ds->icon_wikipedia = svg_load("0:/icons/web-browser.svg", 20, 20, defColor);
|
||||
|
||||
ds->icon_folder_lg = svg_load("0:/icons/folder.svg", 48, 48, defColor);
|
||||
ds->icon_file_lg = svg_load("0:/icons/text-x-generic.svg", 48, 48, defColor);
|
||||
@@ -225,10 +218,8 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
ds->icon_sleep = svg_load("0:/icons/sleep.svg", 20, 20, defColor);
|
||||
ds->icon_logout = svg_load("0:/icons/gnome-logout.svg", 20, 20, defColor);
|
||||
|
||||
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
||||
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
|
||||
ds->icon_volume = svg_load("0:/icons/audio-volume-high-symbolic.svg", 16, 16, colors::PANEL_TEXT);
|
||||
ds->icon_lock = svg_load("0:/icons/lock.svg", 20, 20, defColor);
|
||||
ds->icon_volume = svg_load("0:/icons/audio-volume-high-symbolic.svg", 16, 16, colors::PANEL_TEXT);
|
||||
ds->icon_lock = svg_load("0:/icons/lock.svg", 20, 20, defColor);
|
||||
|
||||
// Scan 0:/apps/ for external app manifests and build the menu
|
||||
desktop_scan_apps(ds);
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# Makefile for mandelbrot (standalone Window Server app) on MontaukOS
|
||||
# Copyright (c) 2026 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
# ---- Toolchain ----
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
else
|
||||
CXX := g++
|
||||
endif
|
||||
|
||||
# ---- Paths ----
|
||||
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
LIBDIR := ../../lib
|
||||
|
||||
# ---- Compiler flags ----
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||
|
||||
# ---- Linker flags ----
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
# ---- Source files ----
|
||||
|
||||
SRCS := main.cpp stb_truetype_impl.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/apps/mandelbrot/mandelbrot.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/apps/mandelbrot
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Mandelbrot - standalone Window Server app
|
||||
* Preserves the desktop app's fixed-point renderer and interaction model
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
#include <gui/widgets.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
using namespace gui;
|
||||
|
||||
// Match the old desktop window's outer 500x400 frame once the desktop adds
|
||||
// its usual titlebar and borders around this external app.
|
||||
static constexpr int INIT_W = 498;
|
||||
static constexpr int INIT_H = 369;
|
||||
|
||||
static constexpr int MB_MAX_ITER = 256;
|
||||
static constexpr int MB_TOOLBAR_H = 32;
|
||||
static constexpr int UI_FONT_SIZE = 18;
|
||||
|
||||
using fp_t = int64_t;
|
||||
static constexpr int FP_SHIFT = 36;
|
||||
|
||||
static TrueTypeFont* g_font = nullptr;
|
||||
|
||||
static inline fp_t fp_from_int(int v) { return (fp_t)v << FP_SHIFT; }
|
||||
|
||||
static inline fp_t fp_mul(fp_t a, fp_t b) {
|
||||
__int128 r = (__int128)a * b;
|
||||
return (fp_t)(r >> FP_SHIFT);
|
||||
}
|
||||
|
||||
static inline fp_t fp_div_small(int a, int b) {
|
||||
return ((fp_t)a << FP_SHIFT) / (fp_t)b;
|
||||
}
|
||||
|
||||
struct MandelbrotState {
|
||||
fp_t center_x;
|
||||
fp_t center_y;
|
||||
fp_t scale;
|
||||
int max_iter;
|
||||
bool needs_render;
|
||||
|
||||
bool dragging;
|
||||
int drag_start_x;
|
||||
int drag_start_y;
|
||||
fp_t drag_start_cx;
|
||||
fp_t drag_start_cy;
|
||||
};
|
||||
|
||||
static int ui_text_h() {
|
||||
return text_height(g_font, UI_FONT_SIZE);
|
||||
}
|
||||
|
||||
static int ui_text_w(const char* text) {
|
||||
return text_width(g_font, text, UI_FONT_SIZE);
|
||||
}
|
||||
|
||||
static void ui_draw_text(Canvas& c, int x, int y, const char* text, Color color) {
|
||||
if (g_font && g_font->valid)
|
||||
draw_text(c, g_font, x, y, text, color, UI_FONT_SIZE);
|
||||
}
|
||||
|
||||
static void reset_view(MandelbrotState* mb, int width_hint) {
|
||||
mb->center_x = -fp_from_int(1) / 2;
|
||||
mb->center_y = 0;
|
||||
mb->scale = fp_div_small(3, width_hint > 0 ? width_hint : 400);
|
||||
mb->max_iter = MB_MAX_ITER;
|
||||
mb->needs_render = true;
|
||||
mb->dragging = false;
|
||||
}
|
||||
|
||||
static uint32_t mandelbrot_color(int iter, int max_iter) {
|
||||
if (iter >= max_iter) return 0xFF000000;
|
||||
|
||||
int t = (iter * 7) & 0xFF;
|
||||
int phase = (iter * 7) >> 8;
|
||||
|
||||
uint8_t r, g, b;
|
||||
switch (phase % 6) {
|
||||
case 0: r = 255; g = (uint8_t)t; b = 0; break;
|
||||
case 1: r = (uint8_t)(255 - t); g = 255; b = 0; break;
|
||||
case 2: r = 0; g = 255; b = (uint8_t)t; break;
|
||||
case 3: r = 0; g = (uint8_t)(255 - t); b = 255; break;
|
||||
case 4: r = (uint8_t)t; g = 0; b = 255; break;
|
||||
default: r = 255; g = 0; b = (uint8_t)(255 - t); break;
|
||||
}
|
||||
|
||||
return 0xFF000000 | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
|
||||
}
|
||||
|
||||
static void mandelbrot_render(MandelbrotState* mb, uint32_t* pixels, int w, int h) {
|
||||
int render_h = h - MB_TOOLBAR_H;
|
||||
if (render_h <= 0) return;
|
||||
|
||||
fp_t half_w = fp_mul(fp_from_int(w / 2), mb->scale);
|
||||
fp_t half_h = fp_mul(fp_from_int(render_h / 2), mb->scale);
|
||||
fp_t x_min = mb->center_x - half_w;
|
||||
fp_t y_min = mb->center_y - half_h;
|
||||
fp_t bailout = fp_from_int(4);
|
||||
|
||||
for (int py = 0; py < render_h; py++) {
|
||||
fp_t ci = y_min + fp_mul(fp_from_int(py), mb->scale);
|
||||
uint32_t* row = pixels + (py + MB_TOOLBAR_H) * w;
|
||||
|
||||
for (int px = 0; px < w; px++) {
|
||||
fp_t cr = x_min + fp_mul(fp_from_int(px), mb->scale);
|
||||
|
||||
fp_t zr = 0;
|
||||
fp_t zi = 0;
|
||||
int iter = 0;
|
||||
|
||||
while (iter < mb->max_iter) {
|
||||
fp_t zr2 = fp_mul(zr, zr);
|
||||
fp_t zi2 = fp_mul(zi, zi);
|
||||
if (zr2 + zi2 > bailout) break;
|
||||
fp_t new_zr = zr2 - zi2 + cr;
|
||||
zi = fp_mul(fp_from_int(2), fp_mul(zr, zi)) + ci;
|
||||
zr = new_zr;
|
||||
iter++;
|
||||
}
|
||||
|
||||
row[px] = mandelbrot_color(iter, mb->max_iter);
|
||||
}
|
||||
}
|
||||
|
||||
mb->needs_render = false;
|
||||
}
|
||||
|
||||
static void draw_toolbar(MandelbrotState* mb, Canvas& c) {
|
||||
c.fill_rect(0, 0, c.w, MB_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, MB_TOOLBAR_H - 1, c.w, colors::BORDER);
|
||||
|
||||
draw_button(c, g_font, 8, 4, 60, 24, "Reset",
|
||||
colors::ACCENT, colors::WHITE, 4, UI_FONT_SIZE);
|
||||
|
||||
char iter_str[24];
|
||||
snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter);
|
||||
|
||||
int text_y = (MB_TOOLBAR_H - ui_text_h()) / 2;
|
||||
ui_draw_text(c, 80, text_y, iter_str, colors::TEXT_COLOR);
|
||||
|
||||
int iter_text_w = ui_text_w(iter_str);
|
||||
int btn_x = 80 + iter_text_w + 8;
|
||||
Color step_bg = Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
draw_button(c, g_font, btn_x, 4, 24, 24, "-",
|
||||
step_bg, colors::WHITE, 4, UI_FONT_SIZE);
|
||||
draw_button(c, g_font, btn_x + 28, 4, 24, 24, "+",
|
||||
step_bg, colors::WHITE, 4, UI_FONT_SIZE);
|
||||
|
||||
const char* hint = "Scroll=zoom Drag=pan";
|
||||
int hint_w = ui_text_w(hint);
|
||||
ui_draw_text(c, c.w - hint_w - 8, text_y, hint, Color::from_rgb(0x99, 0x99, 0x99));
|
||||
}
|
||||
|
||||
static void render(MandelbrotState* mb, WsWindow& win) {
|
||||
Canvas c = win.canvas();
|
||||
|
||||
if (mb->needs_render)
|
||||
mandelbrot_render(mb, c.pixels, c.w, c.h);
|
||||
|
||||
draw_toolbar(mb, c);
|
||||
}
|
||||
|
||||
static bool handle_mouse(MandelbrotState* mb, const MouseEvent& ev, int win_w, int win_h) {
|
||||
int lx = ev.x;
|
||||
int ly = ev.y;
|
||||
|
||||
if (ev.left_pressed() && ly < MB_TOOLBAR_H) {
|
||||
Rect reset_r = {8, 4, 60, 24};
|
||||
if (reset_r.contains(lx, ly)) {
|
||||
reset_view(mb, win_w);
|
||||
return true;
|
||||
}
|
||||
|
||||
char iter_str[24];
|
||||
snprintf(iter_str, sizeof(iter_str), "Iter: %d", mb->max_iter);
|
||||
int iter_text_w = ui_text_w(iter_str);
|
||||
int btn_x = 80 + iter_text_w + 8;
|
||||
|
||||
Rect minus_r = {btn_x, 4, 24, 24};
|
||||
Rect plus_r = {btn_x + 28, 4, 24, 24};
|
||||
|
||||
if (minus_r.contains(lx, ly)) {
|
||||
if (mb->max_iter > 32) {
|
||||
mb->max_iter /= 2;
|
||||
mb->needs_render = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plus_r.contains(lx, ly)) {
|
||||
if (mb->max_iter < 4096) {
|
||||
mb->max_iter *= 2;
|
||||
mb->needs_render = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ev.left_pressed() && ly >= MB_TOOLBAR_H) {
|
||||
mb->dragging = true;
|
||||
mb->drag_start_x = lx;
|
||||
mb->drag_start_y = ly;
|
||||
mb->drag_start_cx = mb->center_x;
|
||||
mb->drag_start_cy = mb->center_y;
|
||||
}
|
||||
|
||||
if (mb->dragging && ev.left_held()) {
|
||||
int dx = lx - mb->drag_start_x;
|
||||
int dy = ly - mb->drag_start_y;
|
||||
mb->center_x = mb->drag_start_cx - fp_mul(fp_from_int(dx), mb->scale);
|
||||
mb->center_y = mb->drag_start_cy - fp_mul(fp_from_int(dy), mb->scale);
|
||||
mb->needs_render = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mb->dragging && !ev.left_held())
|
||||
mb->dragging = false;
|
||||
|
||||
if (ev.scroll != 0 && ly >= MB_TOOLBAR_H) {
|
||||
int render_h = win_h - MB_TOOLBAR_H;
|
||||
fp_t mx_frac = mb->center_x + fp_mul(fp_from_int(lx - win_w / 2), mb->scale);
|
||||
fp_t my_frac = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale);
|
||||
|
||||
if (ev.scroll < 0)
|
||||
mb->scale = fp_mul(mb->scale, fp_from_int(3) / 4);
|
||||
else
|
||||
mb->scale = fp_mul(mb->scale, fp_from_int(4) / 3);
|
||||
|
||||
fp_t new_mx = mb->center_x + fp_mul(fp_from_int(lx - win_w / 2), mb->scale);
|
||||
fp_t new_my = mb->center_y + fp_mul(fp_from_int((ly - MB_TOOLBAR_H) - render_h / 2), mb->scale);
|
||||
mb->center_x += mx_frac - new_mx;
|
||||
mb->center_y += my_frac - new_my;
|
||||
mb->needs_render = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_key(MandelbrotState* mb, const Montauk::KeyEvent& key, int win_w) {
|
||||
if (!key.pressed) return false;
|
||||
|
||||
if (key.ascii == 'r' || key.ascii == 'R') {
|
||||
reset_view(mb, win_w);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key.ascii == '+' || key.ascii == '=') {
|
||||
if (mb->max_iter < 4096) {
|
||||
mb->max_iter *= 2;
|
||||
mb->needs_render = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key.ascii == '-') {
|
||||
if (mb->max_iter > 32) {
|
||||
mb->max_iter /= 2;
|
||||
mb->needs_render = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" void _start() {
|
||||
g_font = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
|
||||
if (g_font) {
|
||||
montauk::memset(g_font, 0, sizeof(TrueTypeFont));
|
||||
if (!g_font->init("0:/fonts/Roboto-Medium.ttf")) {
|
||||
montauk::mfree(g_font);
|
||||
g_font = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
MandelbrotState mb = {};
|
||||
reset_view(&mb, INIT_W);
|
||||
|
||||
WsWindow win;
|
||||
if (!win.create("Mandelbrot", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
render(&mb, win);
|
||||
win.present();
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
int r = win.poll(&ev);
|
||||
|
||||
if (r < 0) break;
|
||||
|
||||
if (r == 0) {
|
||||
montauk::sleep_ms(16);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.type == 3) break;
|
||||
|
||||
bool redraw = false;
|
||||
|
||||
if (ev.type == 2) {
|
||||
mb.needs_render = true;
|
||||
redraw = true;
|
||||
} else if (ev.type == 1) {
|
||||
MouseEvent mev = {
|
||||
ev.mouse.x,
|
||||
ev.mouse.y,
|
||||
ev.mouse.buttons,
|
||||
ev.mouse.prev_buttons,
|
||||
ev.mouse.scroll
|
||||
};
|
||||
redraw = handle_mouse(&mb, mev, win.width, win.height);
|
||||
} else if (ev.type == 0) {
|
||||
redraw = handle_key(&mb, ev.key, win.width);
|
||||
} else if (ev.type == 4) {
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (redraw || mb.needs_render) {
|
||||
render(&mb, win);
|
||||
win.present();
|
||||
}
|
||||
}
|
||||
|
||||
win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[app]
|
||||
name = "Mandelbrot"
|
||||
binary = "mandelbrot.elf"
|
||||
icon = "applications-science.svg"
|
||||
|
||||
[menu]
|
||||
category = "Games"
|
||||
visible = true
|
||||
|
||||
[launch]
|
||||
pass_home_dir = false
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* stb_truetype_impl.cpp
|
||||
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||
#define STBTT_cos(x) stb_cos(x)
|
||||
#define STBTT_acos(x) stb_acos(x)
|
||||
#define STBTT_fabs(x) stb_fabs(x)
|
||||
|
||||
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
|
||||
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
|
||||
|
||||
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
|
||||
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
|
||||
|
||||
#define STBTT_strlen(x) montauk::slen(x)
|
||||
|
||||
#define STBTT_assert(x) ((void)(x))
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <gui/stb_truetype.h>
|
||||
@@ -0,0 +1,74 @@
|
||||
# Makefile for procmgr (standalone Process Manager) on MontaukOS
|
||||
# Copyright (c) 2026 Daniel Hammer
|
||||
|
||||
MAKEFLAGS += -rR
|
||||
.SUFFIXES:
|
||||
|
||||
TOOLCHAIN_PREFIX := $(shell cd ../../.. && pwd)/toolchain/local/bin/x86_64-elf-
|
||||
ifneq ($(wildcard $(TOOLCHAIN_PREFIX)gcc),)
|
||||
CXX := $(TOOLCHAIN_PREFIX)g++
|
||||
else
|
||||
CXX := g++
|
||||
endif
|
||||
|
||||
PROG_INC := ../../include
|
||||
LINK_LD := ../../link.ld
|
||||
BINDIR := ../../bin
|
||||
OBJDIR := obj
|
||||
LIBDIR := ../../lib
|
||||
|
||||
CXXFLAGS := \
|
||||
-std=gnu++20 \
|
||||
-g -O2 -pipe \
|
||||
-Wall \
|
||||
-Wextra \
|
||||
-Wno-unused-parameter \
|
||||
-Wno-unused-function \
|
||||
-nostdinc \
|
||||
-ffreestanding \
|
||||
-fno-stack-protector \
|
||||
-fno-stack-check \
|
||||
-fno-PIC \
|
||||
-fno-rtti \
|
||||
-fno-exceptions \
|
||||
-ffunction-sections \
|
||||
-fdata-sections \
|
||||
-m64 \
|
||||
-march=x86-64 \
|
||||
-msse \
|
||||
-msse2 \
|
||||
-mno-red-zone \
|
||||
-mcmodel=small \
|
||||
-I $(PROG_INC) \
|
||||
-isystem $(PROG_INC)/libc \
|
||||
-isystem ../../../kernel/freestnd-c-hdrs/x86_64/include \
|
||||
-isystem ../../../kernel/freestnd-cxx-hdrs/x86_64/include
|
||||
|
||||
LDFLAGS := \
|
||||
-nostdlib \
|
||||
-static \
|
||||
-Wl,--build-id=none \
|
||||
-Wl,--gc-sections \
|
||||
-Wl,-m,elf_x86_64 \
|
||||
-z max-page-size=0x1000 \
|
||||
-T $(LINK_LD)
|
||||
|
||||
SRCS := main.cpp stb_truetype_impl.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
TARGET := $(BINDIR)/apps/procmgr/procmgr.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/apps/procmgr
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
mkdir -p $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Process Manager - standalone Window Server app
|
||||
* Preserves the old desktop-integrated Processes window layout
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/canvas.hpp>
|
||||
#include <gui/standalone.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
using namespace gui;
|
||||
|
||||
static constexpr int INIT_W = 520;
|
||||
static constexpr int INIT_H = 400;
|
||||
static constexpr int PM_TOOLBAR_H = 36;
|
||||
static constexpr int PM_HEADER_H = 24;
|
||||
static constexpr int PM_ITEM_H = 28;
|
||||
static constexpr int PM_MAX_PROCS = 16;
|
||||
static constexpr int PM_POLL_MS = 1000;
|
||||
static constexpr int FONT_SIZE = 18;
|
||||
|
||||
struct ProcMgrState {
|
||||
Montauk::ProcInfo procs[PM_MAX_PROCS];
|
||||
int proc_count;
|
||||
int selected;
|
||||
uint64_t last_poll_ms;
|
||||
};
|
||||
|
||||
static WsWindow g_win;
|
||||
static ProcMgrState g_pm = {};
|
||||
|
||||
static void format_size(char* buf, int size) {
|
||||
if (size < 1024) {
|
||||
snprintf(buf, 16, "%d B", size);
|
||||
} else if (size < 1024 * 1024) {
|
||||
int kb = size / 1024;
|
||||
int frac = ((size % 1024) * 10) / 1024;
|
||||
if (kb < 10) snprintf(buf, 16, "%d.%d KB", kb, frac);
|
||||
else snprintf(buf, 16, "%d KB", kb);
|
||||
} else {
|
||||
int mb = size / (1024 * 1024);
|
||||
int frac = ((size % (1024 * 1024)) * 10) / (1024 * 1024);
|
||||
if (mb < 10) snprintf(buf, 16, "%d.%d MB", mb, frac);
|
||||
else snprintf(buf, 16, "%d MB", mb);
|
||||
}
|
||||
}
|
||||
|
||||
static bool refresh_processes(bool force) {
|
||||
uint64_t now = montauk::get_milliseconds();
|
||||
if (!force && now - g_pm.last_poll_ms < PM_POLL_MS)
|
||||
return false;
|
||||
|
||||
g_pm.last_poll_ms = now;
|
||||
|
||||
int prev_pid = -1;
|
||||
if (g_pm.selected >= 0 && g_pm.selected < g_pm.proc_count)
|
||||
prev_pid = g_pm.procs[g_pm.selected].pid;
|
||||
|
||||
g_pm.proc_count = montauk::proclist(g_pm.procs, PM_MAX_PROCS);
|
||||
g_pm.selected = -1;
|
||||
|
||||
if (prev_pid >= 0) {
|
||||
for (int i = 0; i < g_pm.proc_count; i++) {
|
||||
if (g_pm.procs[i].pid == prev_pid) {
|
||||
g_pm.selected = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool can_kill_selected() {
|
||||
return g_pm.selected >= 0
|
||||
&& g_pm.selected < g_pm.proc_count
|
||||
&& g_pm.procs[g_pm.selected].pid != 0;
|
||||
}
|
||||
|
||||
static void kill_selected() {
|
||||
if (!can_kill_selected())
|
||||
return;
|
||||
|
||||
montauk::kill(g_pm.procs[g_pm.selected].pid);
|
||||
refresh_processes(true);
|
||||
}
|
||||
|
||||
static void render() {
|
||||
TrueTypeFont* font = fonts::system_font;
|
||||
Canvas c = g_win.canvas();
|
||||
c.fill(colors::WINDOW_BG);
|
||||
|
||||
int fh = text_height(font, FONT_SIZE);
|
||||
|
||||
c.fill_rect(0, 0, g_win.width, PM_TOOLBAR_H, Color::from_rgb(0xF5, 0xF5, 0xF5));
|
||||
c.hline(0, PM_TOOLBAR_H - 1, g_win.width, colors::BORDER);
|
||||
|
||||
int btn_w = 100;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (PM_TOOLBAR_H - btn_h) / 2;
|
||||
Color btn_bg = can_kill_selected()
|
||||
? Color::from_rgb(0xCC, 0x33, 0x33)
|
||||
: Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
draw_button(c, font, btn_x, btn_y, btn_w, btn_h, "End Process",
|
||||
btn_bg, colors::WHITE, 4, FONT_SIZE);
|
||||
|
||||
char count_str[24];
|
||||
snprintf(count_str, sizeof(count_str), "%d processes", g_pm.proc_count);
|
||||
int count_w = text_width(font, count_str, FONT_SIZE);
|
||||
draw_text(c, font, g_win.width - count_w - 12, (PM_TOOLBAR_H - fh) / 2,
|
||||
count_str, colors::TEXT_COLOR, FONT_SIZE);
|
||||
|
||||
int header_y = PM_TOOLBAR_H;
|
||||
c.fill_rect(0, header_y, g_win.width, PM_HEADER_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
|
||||
int col_pid = 12;
|
||||
int col_name = 64;
|
||||
int col_mem = g_win.width - 120;
|
||||
int col_state = g_win.width - 52;
|
||||
int header_text_y = header_y + (PM_HEADER_H - fh) / 2;
|
||||
Color header_color = Color::from_rgb(0x66, 0x66, 0x66);
|
||||
|
||||
draw_text(c, font, col_pid, header_text_y, "PID", header_color, FONT_SIZE);
|
||||
draw_text(c, font, col_name, header_text_y, "Name", header_color, FONT_SIZE);
|
||||
draw_text(c, font, col_mem, header_text_y, "Mem", header_color, FONT_SIZE);
|
||||
draw_text(c, font, col_state, header_text_y, "St", header_color, FONT_SIZE);
|
||||
c.hline(0, header_y + PM_HEADER_H - 1, g_win.width, colors::BORDER);
|
||||
|
||||
int list_y = PM_TOOLBAR_H + PM_HEADER_H;
|
||||
for (int i = 0; i < g_pm.proc_count; i++) {
|
||||
int row_y = list_y + i * PM_ITEM_H;
|
||||
if (row_y + PM_ITEM_H > g_win.height)
|
||||
break;
|
||||
|
||||
if (i == g_pm.selected)
|
||||
c.fill_rect(0, row_y, g_win.width, PM_ITEM_H, colors::MENU_HOVER);
|
||||
|
||||
int text_y = row_y + (PM_ITEM_H - fh) / 2;
|
||||
|
||||
char pid_str[8];
|
||||
snprintf(pid_str, sizeof(pid_str), "%d", (int)g_pm.procs[i].pid);
|
||||
int pid_w = text_width(font, pid_str, FONT_SIZE);
|
||||
draw_text(c, font, col_pid + 32 - pid_w, text_y, pid_str, colors::TEXT_COLOR, FONT_SIZE);
|
||||
|
||||
draw_text(c, font, col_name, text_y, g_pm.procs[i].name, colors::TEXT_COLOR, FONT_SIZE);
|
||||
|
||||
char mem_str[16];
|
||||
format_size(mem_str, (int)g_pm.procs[i].heapUsed);
|
||||
draw_text(c, font, col_mem, text_y, mem_str, colors::TEXT_COLOR, FONT_SIZE);
|
||||
|
||||
const char* state_str = "?";
|
||||
Color state_color = colors::TEXT_COLOR;
|
||||
switch (g_pm.procs[i].state) {
|
||||
case 1:
|
||||
state_str = "Rdy";
|
||||
state_color = Color::from_rgb(0x33, 0x66, 0xCC);
|
||||
break;
|
||||
case 2:
|
||||
state_str = "Run";
|
||||
state_color = Color::from_rgb(0x22, 0x88, 0x22);
|
||||
break;
|
||||
case 3:
|
||||
state_str = "Term";
|
||||
state_color = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
break;
|
||||
}
|
||||
draw_text(c, font, col_state, text_y, state_str, state_color, FONT_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
static bool handle_mouse(const Montauk::WinEvent& ev) {
|
||||
bool left_pressed = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||
if (!left_pressed)
|
||||
return false;
|
||||
|
||||
int mx = ev.mouse.x;
|
||||
int my = ev.mouse.y;
|
||||
|
||||
int btn_w = 100;
|
||||
int btn_h = 26;
|
||||
int btn_x = 8;
|
||||
int btn_y = (PM_TOOLBAR_H - btn_h) / 2;
|
||||
Rect btn_rect = {btn_x, btn_y, btn_w, btn_h};
|
||||
if (btn_rect.contains(mx, my)) {
|
||||
kill_selected();
|
||||
return true;
|
||||
}
|
||||
|
||||
int list_y = PM_TOOLBAR_H + PM_HEADER_H;
|
||||
if (my < list_y)
|
||||
return false;
|
||||
|
||||
int row = (my - list_y) / PM_ITEM_H;
|
||||
if (row >= 0 && row < g_pm.proc_count) g_pm.selected = row;
|
||||
else g_pm.selected = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool handle_key(const Montauk::KeyEvent& key) {
|
||||
if (!key.pressed)
|
||||
return false;
|
||||
|
||||
if (key.scancode == 0x48) {
|
||||
if (g_pm.selected > 0) g_pm.selected--;
|
||||
else if (g_pm.proc_count > 0) g_pm.selected = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key.scancode == 0x50) {
|
||||
if (g_pm.selected < g_pm.proc_count - 1) g_pm.selected++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key.scancode == 0x53) {
|
||||
if (can_kill_selected()) {
|
||||
kill_selected();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" void _start() {
|
||||
if (!fonts::init())
|
||||
montauk::exit(1);
|
||||
|
||||
if (!g_win.create("Processes", INIT_W, INIT_H))
|
||||
montauk::exit(1);
|
||||
|
||||
g_pm.selected = -1;
|
||||
refresh_processes(true);
|
||||
render();
|
||||
g_win.present();
|
||||
|
||||
while (!g_win.closed) {
|
||||
bool redraw = refresh_processes(false);
|
||||
|
||||
Montauk::WinEvent ev;
|
||||
int r = g_win.poll(&ev);
|
||||
if (r < 0) break;
|
||||
|
||||
if (r == 0) {
|
||||
if (redraw) {
|
||||
render();
|
||||
g_win.present();
|
||||
}
|
||||
montauk::sleep_ms(16);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.type == 3)
|
||||
break;
|
||||
|
||||
if (ev.type == 1) redraw |= handle_mouse(ev);
|
||||
else if (ev.type == 0) redraw |= handle_key(ev.key);
|
||||
else if (ev.type == 2 || ev.type == 4) redraw = true;
|
||||
|
||||
if (redraw) {
|
||||
render();
|
||||
g_win.present();
|
||||
}
|
||||
}
|
||||
|
||||
g_win.destroy();
|
||||
montauk::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[app]
|
||||
name = "Processes"
|
||||
binary = "procmgr.elf"
|
||||
icon = "system-monitor.svg"
|
||||
|
||||
[menu]
|
||||
category = "System"
|
||||
visible = true
|
||||
|
||||
[launch]
|
||||
pass_home_dir = false
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* stb_truetype_impl.cpp
|
||||
* Single compilation unit for stb_truetype in MontaukOS freestanding environment
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <montauk/heap.h>
|
||||
#include <montauk/string.h>
|
||||
#include <gui/stb_math.h>
|
||||
|
||||
#define STBTT_ifloor(x) ((int) stb_floor(x))
|
||||
#define STBTT_iceil(x) ((int) stb_ceil(x))
|
||||
#define STBTT_sqrt(x) stb_sqrt(x)
|
||||
#define STBTT_pow(x,y) stb_pow(x,y)
|
||||
#define STBTT_fmod(x,y) stb_fmod(x,y)
|
||||
#define STBTT_cos(x) stb_cos(x)
|
||||
#define STBTT_acos(x) stb_acos(x)
|
||||
#define STBTT_fabs(x) stb_fabs(x)
|
||||
|
||||
#define STBTT_malloc(x,u) ((void)(u), montauk::malloc(x))
|
||||
#define STBTT_free(x,u) ((void)(u), montauk::mfree(x))
|
||||
|
||||
#define STBTT_memcpy(d,s,n) montauk::memcpy(d,s,n)
|
||||
#define STBTT_memset(d,v,n) montauk::memset(d,v,n)
|
||||
|
||||
#define STBTT_strlen(x) montauk::slen(x)
|
||||
|
||||
#define STBTT_assert(x) ((void)(x))
|
||||
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#include <gui/stb_truetype.h>
|
||||
Reference in New Issue
Block a user