From b9dfaba5d1ce8caf42026536f09a0ecd4d5c1cbe Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sat, 28 Mar 2026 10:54:31 +0100 Subject: [PATCH] feat: convert procmgr and mandelbrot to Window Server apps --- programs/GNUmakefile | 18 +- programs/include/gui/desktop.hpp | 8 +- .../desktop/apps/app_external_launchers.cpp | 33 ++ programs/src/desktop/apps/app_klog.cpp | 13 - programs/src/desktop/apps/app_mandelbrot.cpp | 296 --------------- programs/src/desktop/apps/app_procmgr.cpp | 246 ------------- programs/src/desktop/apps/app_terminal.cpp | 16 - programs/src/desktop/apps/app_texteditor.cpp | 22 -- .../src/desktop/apps/app_wordprocessor.cpp | 13 - programs/src/desktop/apps/apps_common.hpp | 5 +- programs/src/desktop/input.cpp | 7 +- programs/src/desktop/main.cpp | 13 +- programs/src/mandelbrot/Makefile | 86 +++++ programs/src/mandelbrot/main.cpp | 346 ++++++++++++++++++ programs/src/mandelbrot/manifest.toml | 11 + programs/src/mandelbrot/stb_truetype_impl.cpp | 33 ++ programs/src/procmgr/Makefile | 74 ++++ programs/src/procmgr/main.cpp | 277 ++++++++++++++ programs/src/procmgr/manifest.toml | 11 + programs/src/procmgr/stb_truetype_impl.cpp | 33 ++ scripts/install_apps.sh | 2 + template/sysroot/include/gui/desktop.hpp | 8 +- 22 files changed, 927 insertions(+), 644 deletions(-) create mode 100644 programs/src/desktop/apps/app_external_launchers.cpp delete mode 100644 programs/src/desktop/apps/app_klog.cpp delete mode 100644 programs/src/desktop/apps/app_mandelbrot.cpp delete mode 100644 programs/src/desktop/apps/app_procmgr.cpp delete mode 100644 programs/src/desktop/apps/app_terminal.cpp delete mode 100644 programs/src/desktop/apps/app_texteditor.cpp delete mode 100644 programs/src/desktop/apps/app_wordprocessor.cpp create mode 100644 programs/src/mandelbrot/Makefile create mode 100644 programs/src/mandelbrot/main.cpp create mode 100644 programs/src/mandelbrot/manifest.toml create mode 100644 programs/src/mandelbrot/stb_truetype_impl.cpp create mode 100644 programs/src/procmgr/Makefile create mode 100644 programs/src/procmgr/main.cpp create mode 100644 programs/src/procmgr/manifest.toml create mode 100644 programs/src/procmgr/stb_truetype_impl.cpp diff --git a/programs/GNUmakefile b/programs/GNUmakefile index 71d30d7..a440193 100644 --- a/programs/GNUmakefile +++ b/programs/GNUmakefile @@ -59,7 +59,7 @@ BINDIR := bin PROGRAMS := $(notdir $(wildcard src/*)) # Programs with custom Makefiles (built separately). -CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog desktop login shell rpgdemo paint tcc lua screenshot texteditor +CUSTOM_BUILDS := doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog procmgr desktop login shell rpgdemo paint tcc lua screenshot texteditor mandelbrot SYSTEM_PROGRAMS := $(filter-out $(CUSTOM_BUILDS),$(PROGRAMS)) # Build targets: system programs go to bin/os/, apps go to bin/apps//. @@ -81,9 +81,9 @@ CA_CERTS := $(BINDIR)/etc/ca-certificates.crt # Common shared assets (wallpapers, etc.) COMMONKEEP := $(BINDIR)/common/.keep -.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog login desktop shell rpgdemo paint tcc lua screenshot texteditor icons fonts bearssl libc tls libjpeg libjpegwrite install-apps +.PHONY: all clean doom fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog procmgr login desktop shell rpgdemo paint tcc lua screenshot texteditor mandelbrot icons fonts bearssl libc tls libjpeg libjpegwrite install-apps -all: bearssl libc libjpeg libjpegwrite tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog doom rpgdemo paint tcc lua screenshot texteditor login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP) +all: bearssl libc libjpeg libjpegwrite tls $(TARGETS) fetch wiki wikipedia weather imageviewer fontpreview spreadsheet wordprocessor pdfviewer disks devexplorer installer volume music bluetooth terminal klog procmgr doom rpgdemo paint tcc lua screenshot texteditor mandelbrot login desktop shell icons fonts install-apps $(MANDST) $(WWWDST) $(CA_CERTS) $(COMMONKEEP) # Build BearSSL static library (cross-compiled for freestanding x86_64). BEARSSL_INCLUDES := -isystem $(shell cd .. && pwd)/kernel/freestnd-c-hdrs/x86_64/include -isystem $(abspath include/libc) @@ -180,6 +180,10 @@ terminal: libc klog: libc $(MAKE) -C src/klog +# Build process manager standalone GUI tool (depends on libc). +procmgr: libc + $(MAKE) -C src/procmgr + # Build login screen via its own Makefile (depends on bearssl and libc). login: bearssl libc $(MAKE) -C src/login @@ -208,6 +212,10 @@ paint: libc texteditor: libc $(MAKE) -C src/texteditor +# Build Mandelbrot standalone GUI app (depends on libc). +mandelbrot: libc + $(MAKE) -C src/mandelbrot + # Build RPG demo game via its own Makefile (depends on libc). rpgdemo: libc $(MAKE) -C src/rpgdemo @@ -234,7 +242,7 @@ lua: libc $(MAKE) -C src/lua # Install app bundles (manifests, icons, data files) into bin/apps//. -install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music bluetooth terminal klog screenshot texteditor +install-apps: doom rpgdemo paint spreadsheet wordprocessor weather wikipedia imageviewer fontpreview pdfviewer disks devexplorer installer volume music bluetooth terminal klog procmgr screenshot texteditor mandelbrot ../scripts/install_apps.sh # Copy man pages into bin/man/ so mkramdisk.sh picks them up. @@ -290,7 +298,9 @@ clean: $(MAKE) -C src/bluetooth clean $(MAKE) -C src/terminal clean $(MAKE) -C src/klog clean + $(MAKE) -C src/procmgr clean $(MAKE) -C src/paint clean $(MAKE) -C src/rpgdemo clean $(MAKE) -C src/screenshot clean + $(MAKE) -C src/mandelbrot clean $(MAKE) -C src/tcc clean diff --git a/programs/include/gui/desktop.hpp b/programs/include/gui/desktop.hpp index 84d78d4..28ad46a 100644 --- a/programs/include/gui/desktop.hpp +++ b/programs/include/gui/desktop.hpp @@ -17,7 +17,7 @@ namespace gui { static constexpr int MAX_WINDOWS = 8; static constexpr int PANEL_HEIGHT = 32; -static constexpr int MAX_EXTERNAL_APPS = 16; +static constexpr int MAX_EXTERNAL_APPS = 32; // External app discovered from 0:/apps/ manifest struct ExternalApp { @@ -79,16 +79,13 @@ struct DesktopState { SvgIcon icon_appmenu; SvgIcon icon_folder; SvgIcon icon_file; - SvgIcon icon_computer; SvgIcon icon_network; SvgIcon icon_calculator; SvgIcon icon_go_up; SvgIcon icon_go_back; SvgIcon icon_go_forward; - SvgIcon icon_save; SvgIcon icon_home; SvgIcon icon_exec; - SvgIcon icon_wikipedia; SvgIcon icon_folder_lg; SvgIcon icon_file_lg; @@ -117,10 +114,7 @@ struct DesktopState { SvgIcon icon_sleep; SvgIcon icon_logout; - SvgIcon icon_procmgr; - SvgIcon icon_mandelbrot; SvgIcon icon_volume; - SvgIcon icon_temperature; // External apps discovered from 0:/apps/ manifests ExternalApp external_apps[MAX_EXTERNAL_APPS]; diff --git a/programs/src/desktop/apps/app_external_launchers.cpp b/programs/src/desktop/apps/app_external_launchers.cpp new file mode 100644 index 0000000..a2f4a6d --- /dev/null +++ b/programs/src/desktop/apps/app_external_launchers.cpp @@ -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"); +} diff --git a/programs/src/desktop/apps/app_klog.cpp b/programs/src/desktop/apps/app_klog.cpp deleted file mode 100644 index ba58795..0000000 --- a/programs/src/desktop/apps/app_klog.cpp +++ /dev/null @@ -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"); -} diff --git a/programs/src/desktop/apps/app_mandelbrot.cpp b/programs/src/desktop/apps/app_mandelbrot.cpp deleted file mode 100644 index 7c7e929..0000000 --- a/programs/src/desktop/apps/app_mandelbrot.cpp +++ /dev/null @@ -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; -} diff --git a/programs/src/desktop/apps/app_procmgr.cpp b/programs/src/desktop/apps/app_procmgr.cpp deleted file mode 100644 index abb547f..0000000 --- a/programs/src/desktop/apps/app_procmgr.cpp +++ /dev/null @@ -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; -} diff --git a/programs/src/desktop/apps/app_terminal.cpp b/programs/src/desktop/apps/app_terminal.cpp deleted file mode 100644 index 5c2bb94..0000000 --- a/programs/src/desktop/apps/app_terminal.cpp +++ /dev/null @@ -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"); - } -} diff --git a/programs/src/desktop/apps/app_texteditor.cpp b/programs/src/desktop/apps/app_texteditor.cpp deleted file mode 100644 index 6554ce9..0000000 --- a/programs/src/desktop/apps/app_texteditor.cpp +++ /dev/null @@ -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); -} diff --git a/programs/src/desktop/apps/app_wordprocessor.cpp b/programs/src/desktop/apps/app_wordprocessor.cpp deleted file mode 100644 index b1a4210..0000000 --- a/programs/src/desktop/apps/app_wordprocessor.cpp +++ /dev/null @@ -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"); -} diff --git a/programs/src/desktop/apps/apps_common.hpp b/programs/src/desktop/apps/apps_common.hpp index 9f2a6b3..73b1223 100644 --- a/programs/src/desktop/apps/apps_common.hpp +++ b/programs/src/desktop/apps/apps_common.hpp @@ -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); diff --git a/programs/src/desktop/input.cpp b/programs/src/desktop/input.cpp index e837cb8..3b4160d 100644 --- a/programs/src/desktop/input.cpp +++ b/programs/src/desktop/input.cpp @@ -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; diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 605d04c..60709d3 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -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); diff --git a/programs/src/mandelbrot/Makefile b/programs/src/mandelbrot/Makefile new file mode 100644 index 0000000..c7799d7 --- /dev/null +++ b/programs/src/mandelbrot/Makefile @@ -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) diff --git a/programs/src/mandelbrot/main.cpp b/programs/src/mandelbrot/main.cpp new file mode 100644 index 0000000..c2744dd --- /dev/null +++ b/programs/src/mandelbrot/main.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +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); +} diff --git a/programs/src/mandelbrot/manifest.toml b/programs/src/mandelbrot/manifest.toml new file mode 100644 index 0000000..27122e1 --- /dev/null +++ b/programs/src/mandelbrot/manifest.toml @@ -0,0 +1,11 @@ +[app] +name = "Mandelbrot" +binary = "mandelbrot.elf" +icon = "applications-science.svg" + +[menu] +category = "Games" +visible = true + +[launch] +pass_home_dir = false diff --git a/programs/src/mandelbrot/stb_truetype_impl.cpp b/programs/src/mandelbrot/stb_truetype_impl.cpp new file mode 100644 index 0000000..dd91785 --- /dev/null +++ b/programs/src/mandelbrot/stb_truetype_impl.cpp @@ -0,0 +1,33 @@ +/* + * stb_truetype_impl.cpp + * Single compilation unit for stb_truetype in MontaukOS freestanding environment + * Copyright (c) 2026 Daniel Hammer + */ + +#include +#include +#include +#include +#include + +#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 diff --git a/programs/src/procmgr/Makefile b/programs/src/procmgr/Makefile new file mode 100644 index 0000000..f08f4e8 --- /dev/null +++ b/programs/src/procmgr/Makefile @@ -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) diff --git a/programs/src/procmgr/main.cpp b/programs/src/procmgr/main.cpp new file mode 100644 index 0000000..6bbb2c4 --- /dev/null +++ b/programs/src/procmgr/main.cpp @@ -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 +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +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); +} diff --git a/programs/src/procmgr/manifest.toml b/programs/src/procmgr/manifest.toml new file mode 100644 index 0000000..58ab806 --- /dev/null +++ b/programs/src/procmgr/manifest.toml @@ -0,0 +1,11 @@ +[app] +name = "Processes" +binary = "procmgr.elf" +icon = "system-monitor.svg" + +[menu] +category = "System" +visible = true + +[launch] +pass_home_dir = false diff --git a/programs/src/procmgr/stb_truetype_impl.cpp b/programs/src/procmgr/stb_truetype_impl.cpp new file mode 100644 index 0000000..dd91785 --- /dev/null +++ b/programs/src/procmgr/stb_truetype_impl.cpp @@ -0,0 +1,33 @@ +/* + * stb_truetype_impl.cpp + * Single compilation unit for stb_truetype in MontaukOS freestanding environment + * Copyright (c) 2026 Daniel Hammer + */ + +#include +#include +#include +#include +#include + +#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 diff --git a/scripts/install_apps.sh b/scripts/install_apps.sh index 89ca9c0..a1a22ad 100755 --- a/scripts/install_apps.sh +++ b/scripts/install_apps.sh @@ -32,10 +32,12 @@ APPS=( "bluetooth|apps/scalable/bluetooth.svg" "terminal|apps/scalable/utilities-terminal.svg" "klog|apps/scalable/utilities-terminal.svg" + "procmgr|apps/scalable/system-monitor.svg" "rpgdemo|apps/scalable/utilities-terminal.svg" "paint|apps/scalable/kolourpaint.svg" "screenshot|apps/scalable/gnome-screenshot.svg" "texteditor|apps/scalable/accessories-text-editor.svg" + "mandelbrot|apps/scalable/applications-science.svg" ) installed=0 diff --git a/template/sysroot/include/gui/desktop.hpp b/template/sysroot/include/gui/desktop.hpp index 0120c52..df67fda 100644 --- a/template/sysroot/include/gui/desktop.hpp +++ b/template/sysroot/include/gui/desktop.hpp @@ -17,7 +17,7 @@ namespace gui { static constexpr int MAX_WINDOWS = 8; static constexpr int PANEL_HEIGHT = 32; -static constexpr int MAX_EXTERNAL_APPS = 16; +static constexpr int MAX_EXTERNAL_APPS = 32; // External app discovered from 0:/apps/ manifest struct ExternalApp { @@ -79,16 +79,13 @@ struct DesktopState { SvgIcon icon_appmenu; SvgIcon icon_folder; SvgIcon icon_file; - SvgIcon icon_computer; SvgIcon icon_network; SvgIcon icon_calculator; SvgIcon icon_go_up; SvgIcon icon_go_back; SvgIcon icon_go_forward; - SvgIcon icon_save; SvgIcon icon_home; SvgIcon icon_exec; - SvgIcon icon_wikipedia; SvgIcon icon_folder_lg; SvgIcon icon_file_lg; @@ -100,10 +97,9 @@ struct DesktopState { SvgIcon icon_settings; SvgIcon icon_reboot; SvgIcon icon_shutdown; + SvgIcon icon_sleep; SvgIcon icon_logout; - SvgIcon icon_procmgr; - SvgIcon icon_mandelbrot; SvgIcon icon_volume; // External apps discovered from 0:/apps/ manifests