feat: MontaukOS installer app, toml config, app directory with manifests, and more
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* app_devexplorer.cpp
|
||||
* MontaukOS Desktop - Device Explorer launcher (spawns standalone devexplorer.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_devexplorer(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/devexplorer.elf");
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* app_disks.cpp
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_disks(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/disks.elf");
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* app_doom.cpp
|
||||
* MontaukOS Desktop - DOOM launcher (spawns standalone doom.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_doom(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/games/doom.elf");
|
||||
}
|
||||
@@ -92,7 +92,12 @@ static void filemanager_read_drives(FileManagerState* fm) {
|
||||
fm->at_drives_root = true;
|
||||
fm->current_path[0] = '\0';
|
||||
|
||||
for (int d = 0; d < FM_MAX_DRIVES; d++) {
|
||||
int drives[FM_MAX_DRIVES];
|
||||
int driveCount = montauk::drivelist(drives, FM_MAX_DRIVES);
|
||||
|
||||
for (int di = 0; di < driveCount; di++) {
|
||||
int d = drives[di];
|
||||
int i = fm->entry_count;
|
||||
char probe[8];
|
||||
if (d < 10) {
|
||||
probe[0] = '0' + d;
|
||||
@@ -106,21 +111,16 @@ static void filemanager_read_drives(FileManagerState* fm) {
|
||||
probe[3] = '/';
|
||||
probe[4] = '\0';
|
||||
}
|
||||
const char* tmp[1];
|
||||
int r = montauk::readdir(probe, tmp, 1);
|
||||
if (r >= 0) {
|
||||
int i = fm->entry_count;
|
||||
char label[64];
|
||||
montauk::strcpy(label, "Drive ");
|
||||
str_append(label, probe, 64);
|
||||
montauk::strncpy(fm->entry_names[i], label, 63);
|
||||
fm->entry_types[i] = 3; // drive
|
||||
fm->entry_sizes[i] = 0;
|
||||
fm->is_dir[i] = true;
|
||||
fm->drive_indices[i] = d;
|
||||
fm->entry_count++;
|
||||
if (fm->entry_count >= 64) break;
|
||||
}
|
||||
char label[64];
|
||||
montauk::strcpy(label, "Drive ");
|
||||
str_append(label, probe, 64);
|
||||
montauk::strncpy(fm->entry_names[i], label, 63);
|
||||
fm->entry_types[i] = 3; // drive
|
||||
fm->entry_sizes[i] = 0;
|
||||
fm->is_dir[i] = true;
|
||||
fm->drive_indices[i] = d;
|
||||
fm->entry_count++;
|
||||
if (fm->entry_count >= 64) break;
|
||||
}
|
||||
|
||||
fm->selected = -1;
|
||||
@@ -688,13 +688,13 @@ static void filemanager_on_mouse(Window* win, MouseEvent& ev) {
|
||||
}
|
||||
str_append(fullpath, fm->entry_names[clicked_idx], 512);
|
||||
if (is_image_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/imageviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/imageviewer/imageviewer.elf", fullpath);
|
||||
} else if (is_font_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/fontpreview.elf", fullpath);
|
||||
montauk::spawn("0:/apps/fontpreview/fontpreview.elf", fullpath);
|
||||
} else if (is_pdf_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/pdfviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/pdfviewer/pdfviewer.elf", fullpath);
|
||||
} else if (is_spreadsheet_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/spreadsheet.elf", fullpath);
|
||||
montauk::spawn("0:/apps/spreadsheet/spreadsheet.elf", fullpath);
|
||||
} else if (fm->desktop) {
|
||||
open_texteditor_with_file(fm->desktop, fullpath);
|
||||
}
|
||||
@@ -744,13 +744,13 @@ static void filemanager_on_mouse(Window* win, MouseEvent& ev) {
|
||||
}
|
||||
str_append(fullpath, fm->entry_names[clicked_idx], 512);
|
||||
if (is_image_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/imageviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/imageviewer/imageviewer.elf", fullpath);
|
||||
} else if (is_font_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/fontpreview.elf", fullpath);
|
||||
montauk::spawn("0:/apps/fontpreview/fontpreview.elf", fullpath);
|
||||
} else if (is_pdf_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/pdfviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/pdfviewer/pdfviewer.elf", fullpath);
|
||||
} else if (is_spreadsheet_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/spreadsheet.elf", fullpath);
|
||||
montauk::spawn("0:/apps/spreadsheet/spreadsheet.elf", fullpath);
|
||||
} else if (fm->desktop) {
|
||||
open_texteditor_with_file(fm->desktop, fullpath);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* app_spreadsheet.cpp
|
||||
* MontaukOS Desktop - Spreadsheet launcher (spawns standalone spreadsheet.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_spreadsheet(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/spreadsheet.elf");
|
||||
}
|
||||
@@ -17,12 +17,7 @@ static constexpr int TERM_TAB_GAP = 4;
|
||||
static constexpr int TERM_PLUS_W = 28;
|
||||
static constexpr int TERM_PLUS_PAD = 8;
|
||||
|
||||
// Compute the x-offset for left-aligned tabs within bar_w
|
||||
static int term_tabs_origin(int bar_w, int tab_count) {
|
||||
(void)bar_w;
|
||||
(void)tab_count;
|
||||
return 8;
|
||||
}
|
||||
static constexpr int TERM_TAB_PAD = 8;
|
||||
|
||||
struct TermTabState {
|
||||
TerminalState* tabs[TERM_MAX_TABS];
|
||||
@@ -150,7 +145,7 @@ static void terminal_on_draw(Window* win, Framebuffer& fb) {
|
||||
c.fill_rect(0, 0, cr.w, TERM_TAB_BAR_H, bar_bg);
|
||||
|
||||
int fh = system_font_height();
|
||||
int tab_x = term_tabs_origin(cr.w, tts->tab_count);
|
||||
int tab_x = TERM_TAB_PAD;
|
||||
|
||||
for (int i = 0; i < tts->tab_count; i++) {
|
||||
bool active = (i == tts->active_tab);
|
||||
@@ -241,7 +236,7 @@ static void terminal_on_mouse(Window* win, MouseEvent& ev) {
|
||||
|
||||
if (ly >= TERM_TAB_BAR_H) return; // click in terminal area, ignore
|
||||
|
||||
int tab_x = term_tabs_origin(cr.w, tts->tab_count);
|
||||
int tab_x = TERM_TAB_PAD;
|
||||
for (int i = 0; i < tts->tab_count; i++) {
|
||||
if (lx >= tab_x && lx < tab_x + TERM_TAB_W) {
|
||||
// Check if click is on the close "x" (last 24px of tab)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* app_weather.cpp
|
||||
* MontaukOS Desktop - Weather app launcher
|
||||
* Spawns weather.elf as a standalone Window Server process
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_weather(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/weather.elf");
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* app_wiki.cpp
|
||||
* MontaukOS Desktop - Wikipedia launcher
|
||||
* Spawns wikipedia.elf as a standalone Window Server process
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_wiki(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/wikipedia.elf");
|
||||
}
|
||||
@@ -156,7 +156,7 @@ inline void format_size(char* buf, int size) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Forward declarations for app launchers
|
||||
// Forward declarations for app launchers (embedded apps only)
|
||||
// ============================================================================
|
||||
|
||||
void open_terminal(DesktopState* ds);
|
||||
@@ -166,16 +166,10 @@ 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_wiki(DesktopState* ds);
|
||||
void open_weather(DesktopState* ds);
|
||||
void open_procmgr(DesktopState* ds);
|
||||
void open_mandelbrot(DesktopState* ds);
|
||||
void open_devexplorer(DesktopState* ds);
|
||||
void open_settings(DesktopState* ds);
|
||||
void open_doom(DesktopState* ds);
|
||||
void open_reboot_dialog(DesktopState* ds);
|
||||
void open_wordprocessor(DesktopState* ds);
|
||||
void open_spreadsheet(DesktopState* ds);
|
||||
void open_shutdown_dialog(DesktopState* ds);
|
||||
void open_disks(DesktopState* ds);
|
||||
void desktop_poll_external_windows(DesktopState* ds);
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
#include "apps/apps_common.hpp"
|
||||
#include "wallpaper.hpp"
|
||||
#include <montauk/toml.h>
|
||||
|
||||
// ============================================================================
|
||||
// App Menu Data
|
||||
// App Menu Data (dynamic — built at startup from embedded + external apps)
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MENU_W = 220;
|
||||
@@ -20,36 +21,16 @@ static constexpr int MENU_DIV_H = 10;
|
||||
|
||||
struct MenuRow {
|
||||
bool is_category;
|
||||
const char* label; // "" for divider-only rows
|
||||
int app_id; // -1 for category headers / dividers
|
||||
char label[48];
|
||||
int app_id; // embedded dispatch ID, or -1 for categories/dividers
|
||||
bool external; // true = spawn binary_path
|
||||
char binary_path[128]; // full VFS path for external apps
|
||||
SvgIcon* icon; // loaded menu icon (null for categories/dividers)
|
||||
};
|
||||
|
||||
static constexpr int MENU_ROW_COUNT = 23;
|
||||
static const MenuRow menu_rows[MENU_ROW_COUNT] = {
|
||||
{ true, "Applications", -1 }, // cat 0
|
||||
{ false, "Terminal", 0 },
|
||||
{ false, "Files", 1 },
|
||||
{ false, "Text Editor", 4 },
|
||||
{ false, "Word Processor", 15 },
|
||||
{ false, "Spreadsheet", 16 },
|
||||
{ false, "Calculator", 3 },
|
||||
{ true, "Internet", -1 }, // cat 1
|
||||
{ false, "Wikipedia", 9 },
|
||||
{ false, "Weather", 13 },
|
||||
{ true, "System", -1 }, // cat 2
|
||||
{ false, "System Info", 2 },
|
||||
{ false, "Kernel Log", 5 },
|
||||
{ false, "Processes", 6 },
|
||||
{ false, "Devices", 8 },
|
||||
{ false, "Disks", 17 },
|
||||
{ true, "Games", -1 }, // cat 3
|
||||
{ false, "Mandelbrot", 7 },
|
||||
{ false, "DOOM", 10 },
|
||||
{ true, "", -1 }, // divider (always visible)
|
||||
{ false, "Settings", 11 },
|
||||
{ false, "Reboot", 12 },
|
||||
{ false, "Shutdown", 14 },
|
||||
};
|
||||
static constexpr int MAX_MENU_ROWS = 48;
|
||||
inline MenuRow menu_rows[MAX_MENU_ROWS];
|
||||
inline int menu_row_count = 0;
|
||||
|
||||
// Collapsible category state (categories 0-3 are toggleable; divider category always expanded)
|
||||
static constexpr int MENU_NUM_CATS = 5;
|
||||
@@ -77,12 +58,52 @@ inline int menu_row_height(const MenuRow& row) {
|
||||
|
||||
inline int menu_total_height() {
|
||||
int h = 10; // top + bottom padding
|
||||
for (int i = 0; i < MENU_ROW_COUNT; i++)
|
||||
for (int i = 0; i < menu_row_count; i++)
|
||||
if (menu_row_visible(i))
|
||||
h += menu_row_height(menu_rows[i]);
|
||||
return h;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Menu Builder Helpers
|
||||
// ============================================================================
|
||||
|
||||
inline int menu_add_category(const char* label) {
|
||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
||||
MenuRow& r = menu_rows[menu_row_count++];
|
||||
r.is_category = true;
|
||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||
r.app_id = -1;
|
||||
r.external = false;
|
||||
r.binary_path[0] = '\0';
|
||||
r.icon = nullptr;
|
||||
return menu_row_count - 1;
|
||||
}
|
||||
|
||||
inline int menu_add_embedded(const char* label, int app_id, SvgIcon* icon) {
|
||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
||||
MenuRow& r = menu_rows[menu_row_count++];
|
||||
r.is_category = false;
|
||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||
r.app_id = app_id;
|
||||
r.external = false;
|
||||
r.binary_path[0] = '\0';
|
||||
r.icon = icon;
|
||||
return menu_row_count - 1;
|
||||
}
|
||||
|
||||
inline int menu_add_external(const char* label, const char* binary, SvgIcon* icon) {
|
||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
||||
MenuRow& r = menu_rows[menu_row_count++];
|
||||
r.is_category = false;
|
||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||
r.app_id = -1;
|
||||
r.external = true;
|
||||
montauk::strncpy(r.binary_path, binary, sizeof(r.binary_path));
|
||||
r.icon = icon;
|
||||
return menu_row_count - 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Forward Declarations (internal functions)
|
||||
// ============================================================================
|
||||
@@ -95,6 +116,10 @@ gui::CursorStyle cursor_for_edge(gui::ResizeEdge edge);
|
||||
void desktop_draw_app_menu(gui::DesktopState* ds);
|
||||
void desktop_draw_net_popup(gui::DesktopState* ds);
|
||||
|
||||
// main.cpp
|
||||
void desktop_scan_apps(gui::DesktopState* ds);
|
||||
void desktop_build_menu(gui::DesktopState* ds);
|
||||
|
||||
// Month names (shared by panel clock)
|
||||
inline const char* month_names[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
|
||||
@@ -202,7 +202,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
// Walk visible rows to find which one was clicked
|
||||
int iy = menu_y + 5;
|
||||
int cur_cat = -1;
|
||||
for (int i = 0; i < MENU_ROW_COUNT; i++) {
|
||||
for (int i = 0; i < menu_row_count; i++) {
|
||||
const MenuRow& row = menu_rows[i];
|
||||
if (row.is_category) cur_cat++;
|
||||
if (!menu_row_visible(i)) continue;
|
||||
@@ -212,25 +212,25 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
// Toggle category expand/collapse
|
||||
menu_cat_expanded[cur_cat] = !menu_cat_expanded[cur_cat];
|
||||
} else if (!row.is_category) {
|
||||
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 8: open_devexplorer(ds); break;
|
||||
case 9: open_wiki(ds); break;
|
||||
case 10: open_doom(ds); break;
|
||||
case 11: open_settings(ds); break;
|
||||
case 12: open_reboot_dialog(ds); break;
|
||||
case 13: open_weather(ds); break;
|
||||
case 14: open_shutdown_dialog(ds); break;
|
||||
case 15: open_wordprocessor(ds); break;
|
||||
case 16: open_spreadsheet(ds); break;
|
||||
case 17: open_disks(ds); break;
|
||||
if (row.external) {
|
||||
// Launch external app from manifest
|
||||
montauk::spawn(row.binary_path);
|
||||
} else {
|
||||
// Dispatch embedded app
|
||||
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;
|
||||
case 15: open_wordprocessor(ds); break;
|
||||
}
|
||||
}
|
||||
ds->app_menu_open = false;
|
||||
}
|
||||
@@ -534,10 +534,6 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const Montauk::KeyEvent& key
|
||||
open_klog(ds);
|
||||
return;
|
||||
}
|
||||
if (key.ascii == 'd' || key.ascii == 'D') {
|
||||
open_doom(ds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch to focused window
|
||||
|
||||
@@ -6,6 +6,173 @@
|
||||
|
||||
#include "desktop_internal.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// App Manifest Scanning
|
||||
// ============================================================================
|
||||
|
||||
// Extract the basename from a readdir entry.
|
||||
// readdir returns full paths from drive root (e.g. "apps/doom/") —
|
||||
// strip the directory prefix and any trailing slash to get just "doom".
|
||||
static void extract_basename(char* out, int outSz, const char* entry) {
|
||||
// Strip trailing slash
|
||||
int len = montauk::slen(entry);
|
||||
while (len > 0 && entry[len - 1] == '/') len--;
|
||||
|
||||
// Find last slash before that
|
||||
int last_slash = -1;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (entry[i] == '/') last_slash = i;
|
||||
}
|
||||
|
||||
const char* base = (last_slash >= 0) ? entry + last_slash + 1 : entry;
|
||||
int base_len = len - (last_slash >= 0 ? last_slash + 1 : 0);
|
||||
if (base_len >= outSz) base_len = outSz - 1;
|
||||
montauk::memcpy(out, base, base_len);
|
||||
out[base_len] = '\0';
|
||||
}
|
||||
|
||||
void desktop_scan_apps(DesktopState* ds) {
|
||||
ds->external_app_count = 0;
|
||||
|
||||
// Ensure the apps directory exists
|
||||
montauk::fmkdir("0:/apps");
|
||||
|
||||
const char* entries[32];
|
||||
int count = montauk::readdir("0:/apps", entries, 32);
|
||||
|
||||
for (int i = 0; i < count && ds->external_app_count < MAX_EXTERNAL_APPS; i++) {
|
||||
// readdir returns paths like "apps/doom/" — extract just "doom"
|
||||
char dirname[64];
|
||||
extract_basename(dirname, sizeof(dirname), entries[i]);
|
||||
if (dirname[0] == '\0') continue;
|
||||
|
||||
// Try to open the manifest in this app directory
|
||||
char manifest_path[128];
|
||||
snprintf(manifest_path, sizeof(manifest_path), "0:/apps/%s/manifest.toml", dirname);
|
||||
|
||||
int fh = montauk::open(manifest_path);
|
||||
if (fh < 0) continue;
|
||||
|
||||
uint64_t sz = montauk::getsize(fh);
|
||||
if (sz == 0 || sz > 4096) { montauk::close(fh); continue; }
|
||||
|
||||
char* text = (char*)montauk::malloc(sz + 1);
|
||||
montauk::read(fh, (uint8_t*)text, 0, sz);
|
||||
montauk::close(fh);
|
||||
text[sz] = '\0';
|
||||
|
||||
auto doc = montauk::toml::parse(text);
|
||||
montauk::mfree(text);
|
||||
|
||||
ExternalApp* app = &ds->external_apps[ds->external_app_count];
|
||||
|
||||
// Read manifest fields
|
||||
const char* name = doc.get_string("app.name", "Unknown");
|
||||
const char* binary = doc.get_string("app.binary", "");
|
||||
const char* icon_file = doc.get_string("app.icon", "");
|
||||
const char* category = doc.get_string("menu.category", "Applications");
|
||||
bool visible = doc.get_bool("menu.visible", true);
|
||||
|
||||
montauk::strncpy(app->name, name, sizeof(app->name));
|
||||
montauk::strncpy(app->category, category, sizeof(app->category));
|
||||
app->menu_visible = visible;
|
||||
|
||||
// Build full binary path: 0:/apps/<dir>/<binary>
|
||||
snprintf(app->binary_path, sizeof(app->binary_path),
|
||||
"0:/apps/%s/%s", dirname, binary);
|
||||
|
||||
// Load icon from app directory
|
||||
app->icon = {};
|
||||
if (icon_file[0]) {
|
||||
char icon_path[128];
|
||||
snprintf(icon_path, sizeof(icon_path),
|
||||
"0:/apps/%s/%s", dirname, icon_file);
|
||||
Color defColor = colors::ICON_COLOR;
|
||||
app->icon = svg_load(icon_path, 20, 20, defColor);
|
||||
}
|
||||
|
||||
doc.destroy();
|
||||
ds->external_app_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Menu Builder
|
||||
// ============================================================================
|
||||
|
||||
// Category definitions and their ordering
|
||||
static const char* CATEGORY_NAMES[] = { "Applications", "Internet", "System", "Games" };
|
||||
static constexpr int NUM_CATEGORIES = 4;
|
||||
|
||||
// Embedded app definitions: { display_name, app_id, category_index }
|
||||
struct EmbeddedAppDef {
|
||||
const char* name;
|
||||
int app_id;
|
||||
int category; // index into CATEGORY_NAMES
|
||||
};
|
||||
|
||||
static const EmbeddedAppDef embedded_apps[] = {
|
||||
{ "Terminal", 0, 0 },
|
||||
{ "Files", 1, 0 },
|
||||
{ "Text Editor", 4, 0 },
|
||||
{ "Word Processor", 15, 0 },
|
||||
{ "Calculator", 3, 0 },
|
||||
{ "System Info", 2, 2 },
|
||||
{ "Kernel Log", 5, 2 },
|
||||
{ "Processes", 6, 2 },
|
||||
{ "Mandelbrot", 7, 3 },
|
||||
};
|
||||
|
||||
static constexpr int NUM_EMBEDDED = sizeof(embedded_apps) / sizeof(embedded_apps[0]);
|
||||
|
||||
// Resolve embedded app_id to an icon pointer in DesktopState
|
||||
static SvgIcon* icon_for_embedded(DesktopState* ds, int app_id) {
|
||||
switch (app_id) {
|
||||
case 0: return &ds->icon_terminal;
|
||||
case 1: return &ds->icon_filemanager;
|
||||
case 2: return &ds->icon_sysinfo;
|
||||
case 3: return &ds->icon_calculator;
|
||||
case 4: return &ds->icon_texteditor;
|
||||
case 5: return &ds->icon_terminal; // Kernel Log uses terminal icon
|
||||
case 6: return &ds->icon_procmgr;
|
||||
case 7: return &ds->icon_mandelbrot;
|
||||
case 15: return &ds->icon_texteditor; // Word Processor uses text editor icon
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_build_menu(DesktopState* ds) {
|
||||
menu_row_count = 0;
|
||||
|
||||
// Build each category
|
||||
for (int cat = 0; cat < NUM_CATEGORIES; cat++) {
|
||||
menu_add_category(CATEGORY_NAMES[cat]);
|
||||
|
||||
// Add embedded apps in this category
|
||||
for (int e = 0; e < NUM_EMBEDDED; e++) {
|
||||
if (embedded_apps[e].category == cat) {
|
||||
menu_add_embedded(embedded_apps[e].name, embedded_apps[e].app_id,
|
||||
icon_for_embedded(ds, embedded_apps[e].app_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Add external apps in this category
|
||||
for (int x = 0; x < ds->external_app_count; x++) {
|
||||
ExternalApp* app = &ds->external_apps[x];
|
||||
if (!app->menu_visible) continue;
|
||||
if (montauk::streq(app->category, CATEGORY_NAMES[cat])) {
|
||||
menu_add_external(app->name, app->binary_path, &app->icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divider + always-visible entries
|
||||
menu_add_category(""); // divider (cat 4, always expanded)
|
||||
menu_add_embedded("Settings", 11, &ds->icon_settings);
|
||||
menu_add_embedded("Reboot", 12, &ds->icon_reboot);
|
||||
menu_add_embedded("Shutdown", 14, &ds->icon_shutdown);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Initialization
|
||||
// ============================================================================
|
||||
@@ -58,14 +225,12 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
|
||||
ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor);
|
||||
|
||||
ds->icon_weather = svg_load("0:/icons/weather-widget.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_doom = svg_load("0:/icons/doom.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_devexplorer = svg_load("0:/icons/hardware.svg", 20, 20, defColor);
|
||||
ds->icon_disks = svg_load("0:/icons/gparted.svg", 20, 20, defColor); // gparted icon, i.e. disk/partition management
|
||||
ds->icon_spreadsheet = svg_load("0:/icons/spreadsheet.svg", 20, 20, defColor);
|
||||
// Scan 0:/apps/ for external app manifests and build the menu
|
||||
desktop_scan_apps(ds);
|
||||
desktop_build_menu(ds);
|
||||
|
||||
// Settings defaults
|
||||
ds->settings.bg_gradient = true;
|
||||
@@ -236,6 +401,10 @@ void gui::desktop_run(DesktopState* ds) {
|
||||
// Handle mouse events
|
||||
desktop_handle_mouse(ds);
|
||||
|
||||
// Re-poll external windows so that any killed during mouse/key
|
||||
// handling are removed before we touch their pixel buffers.
|
||||
desktop_poll_external_windows(ds);
|
||||
|
||||
// Compose and present
|
||||
desktop_compose(ds);
|
||||
ds->fb.flip();
|
||||
|
||||
@@ -151,34 +151,12 @@ void desktop_draw_app_menu(DesktopState* ds) {
|
||||
fill_rounded_rect(fb, menu_x, menu_y, MENU_W, menu_h, 8, colors::MENU_BG);
|
||||
draw_rect(fb, menu_x, menu_y, MENU_W, menu_h, colors::BORDER);
|
||||
|
||||
// Icon lookup by app_id
|
||||
SvgIcon* icons[18] = {
|
||||
&ds->icon_terminal, // 0
|
||||
&ds->icon_filemanager, // 1
|
||||
&ds->icon_sysinfo, // 2
|
||||
&ds->icon_calculator, // 3
|
||||
&ds->icon_texteditor, // 4
|
||||
&ds->icon_terminal, // 5 (Kernel Log)
|
||||
&ds->icon_procmgr, // 6
|
||||
&ds->icon_mandelbrot, // 7
|
||||
&ds->icon_devexplorer, // 8
|
||||
&ds->icon_wikipedia, // 9
|
||||
&ds->icon_doom, // 10
|
||||
&ds->icon_settings, // 11
|
||||
&ds->icon_reboot, // 12
|
||||
&ds->icon_weather, // 13
|
||||
&ds->icon_shutdown, // 14
|
||||
&ds->icon_texteditor, // 15
|
||||
&ds->icon_spreadsheet, // 16
|
||||
&ds->icon_disks, // 17 (Disks)
|
||||
};
|
||||
|
||||
int mx = ds->mouse.x;
|
||||
int my = ds->mouse.y;
|
||||
int iy = menu_y + 5;
|
||||
int cur_cat = -1;
|
||||
|
||||
for (int i = 0; i < MENU_ROW_COUNT; i++) {
|
||||
for (int i = 0; i < menu_row_count; i++) {
|
||||
const MenuRow& row = menu_rows[i];
|
||||
|
||||
if (row.is_category) cur_cat++;
|
||||
@@ -239,11 +217,8 @@ void desktop_draw_app_menu(DesktopState* ds) {
|
||||
// Icon
|
||||
int icon_x = item_rect.x + 8;
|
||||
int icon_y = item_rect.y + (row_h - 20) / 2;
|
||||
if (row.app_id >= 0 && row.app_id < 18) {
|
||||
SvgIcon* icon = icons[row.app_id];
|
||||
if (icon && icon->pixels) {
|
||||
fb.blit_alpha(icon_x, icon_y, icon->width, icon->height, icon->pixels);
|
||||
}
|
||||
if (row.icon && row.icon->pixels) {
|
||||
fb.blit_alpha(icon_x, icon_y, row.icon->width, row.icon->height, row.icon->pixels);
|
||||
}
|
||||
|
||||
// Label
|
||||
|
||||
@@ -68,14 +68,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/devexplorer.elf
|
||||
TARGET := $(BINDIR)/apps/devexplorer/devexplorer.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/devexplorer
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Devices"
|
||||
binary = "devexplorer.elf"
|
||||
icon = "hardware.svg"
|
||||
|
||||
[menu]
|
||||
category = "System"
|
||||
visible = true
|
||||
@@ -68,14 +68,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/disks.elf
|
||||
TARGET := $(BINDIR)/apps/disks/disks.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/disks
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Disks"
|
||||
binary = "disks.elf"
|
||||
icon = "gparted.svg"
|
||||
|
||||
[menu]
|
||||
category = "System"
|
||||
visible = true
|
||||
@@ -163,14 +163,14 @@ ALL_OBJS := $(DOOM_OBJS) $(LOCAL_OBJS)
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/games/doom.elf
|
||||
TARGET := $(BINDIR)/apps/doom/doom.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(ALL_OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/games
|
||||
mkdir -p $(BINDIR)/apps/doom
|
||||
$(LD) $(CFLAGS) $(LDFLAGS) $(ALL_OBJS) -o $@
|
||||
|
||||
# DOOM source files (from doomgeneric directory)
|
||||
|
||||
@@ -247,7 +247,7 @@ void DG_SetWindowTitle(const char* title) {
|
||||
/* ---- Entry point ---- */
|
||||
|
||||
void _start(void) {
|
||||
char *argv[] = { "doom", "-iwad", "0:/games/doom1.wad", 0 };
|
||||
char *argv[] = { "doom", "-iwad", "0:/apps/doom/doom1.wad", 0 };
|
||||
doomgeneric_Create(3, argv);
|
||||
for (;;) {
|
||||
doomgeneric_Tick();
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "DOOM"
|
||||
binary = "doom.elf"
|
||||
icon = "doom.svg"
|
||||
|
||||
[menu]
|
||||
category = "Games"
|
||||
visible = true
|
||||
@@ -71,14 +71,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/fontpreview.elf
|
||||
TARGET := $(BINDIR)/apps/fontpreview/fontpreview.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/fontpreview
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Font Preview"
|
||||
binary = "fontpreview.elf"
|
||||
icon = "font-viewer.svg"
|
||||
|
||||
[menu]
|
||||
category = "Applications"
|
||||
visible = false
|
||||
@@ -74,14 +74,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/imageviewer.elf
|
||||
TARGET := $(BINDIR)/apps/imageviewer/imageviewer.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/imageviewer
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Image Viewer"
|
||||
binary = "imageviewer.elf"
|
||||
icon = "image-viewer.svg"
|
||||
|
||||
[menu]
|
||||
category = "Applications"
|
||||
visible = false
|
||||
@@ -0,0 +1,86 @@
|
||||
# Makefile for installer (standalone Installer 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 render.cpp actions.cpp stb_truetype_impl.cpp
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/apps/installer/installer.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/apps/installer
|
||||
$(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,420 @@
|
||||
/*
|
||||
* actions.cpp
|
||||
* MontaukOS Installer — install operations
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "installer.h"
|
||||
|
||||
// ============================================================================
|
||||
// EFI System Partition type GUID: C12A7328-F81F-11D2-BA4B-00A0C93EC93B
|
||||
// ============================================================================
|
||||
|
||||
static Montauk::PartGuid esp_type_guid() {
|
||||
Montauk::PartGuid g;
|
||||
g.Data1 = 0xC12A7328;
|
||||
g.Data2 = 0xF81F;
|
||||
g.Data3 = 0x11D2;
|
||||
g.Data4[0] = 0xBA; g.Data4[1] = 0x4B;
|
||||
g.Data4[2] = 0x00; g.Data4[3] = 0xA0;
|
||||
g.Data4[4] = 0xC9; g.Data4[5] = 0x3E;
|
||||
g.Data4[6] = 0xC9; g.Data4[7] = 0x3B;
|
||||
return g;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Refresh disk list
|
||||
// ============================================================================
|
||||
|
||||
void installer_refresh_disks() {
|
||||
auto& st = g_state;
|
||||
st.disk_count = 0;
|
||||
for (int port = 0; port < MAX_DISKS; port++) {
|
||||
Montauk::DiskInfo info;
|
||||
montauk::memset(&info, 0, sizeof(info));
|
||||
int r = montauk::diskinfo(&info, port);
|
||||
if (r == 0 && info.type != 0) {
|
||||
st.disks[st.disk_count++] = info;
|
||||
}
|
||||
}
|
||||
if (st.selected_disk < 0 || st.selected_disk >= st.disk_count)
|
||||
st.selected_disk = st.disk_count > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// String helpers
|
||||
// ============================================================================
|
||||
|
||||
static int slen(const char* s) {
|
||||
int n = 0;
|
||||
while (s[n]) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void path_join(char* out, int outsize, const char* dir, const char* name) {
|
||||
int i = 0;
|
||||
for (int j = 0; dir[j] && i < outsize - 2; j++) out[i++] = dir[j];
|
||||
if (i > 0 && out[i - 1] != '/') out[i++] = '/';
|
||||
for (int j = 0; name[j] && i < outsize - 1; j++) out[i++] = name[j];
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Copy a single file from src_path to dst_path
|
||||
// ============================================================================
|
||||
|
||||
static bool copy_file(const char* src_path, const char* dst_path) {
|
||||
int src = montauk::open(src_path);
|
||||
if (src < 0) return false;
|
||||
|
||||
uint64_t size = montauk::getsize(src);
|
||||
|
||||
// Create destination (returns handle)
|
||||
int dst = montauk::fcreate(dst_path);
|
||||
if (dst < 0) {
|
||||
montauk::close(src);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
// Empty file — just create it
|
||||
montauk::close(dst);
|
||||
montauk::close(src);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Use large buffer for faster copies (256 KB)
|
||||
static constexpr uint64_t CHUNK = 256 * 1024;
|
||||
uint8_t* buf = (uint8_t*)montauk::malloc(CHUNK);
|
||||
if (!buf) {
|
||||
montauk::close(dst);
|
||||
montauk::close(src);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show progress for large files (> 1 MB)
|
||||
bool show_progress = (size > 1024 * 1024);
|
||||
uint64_t last_progress_mb = 0;
|
||||
|
||||
uint64_t offset = 0;
|
||||
bool ok = true;
|
||||
while (offset < size) {
|
||||
uint64_t to_read = size - offset;
|
||||
if (to_read > CHUNK) to_read = CHUNK;
|
||||
|
||||
int rd = montauk::read(src, buf, offset, to_read);
|
||||
if (rd <= 0) { ok = false; break; }
|
||||
|
||||
int wr = montauk::fwrite(dst, buf, offset, rd);
|
||||
if (wr < rd) { ok = false; break; }
|
||||
|
||||
offset += rd;
|
||||
|
||||
if (show_progress) {
|
||||
uint64_t cur_mb = offset / (1024 * 1024);
|
||||
if (cur_mb > last_progress_mb) {
|
||||
last_progress_mb = cur_mb;
|
||||
uint64_t total_mb = size / (1024 * 1024);
|
||||
char prog[64];
|
||||
snprintf(prog, sizeof(prog), " %lu / %lu MB",
|
||||
(unsigned long)cur_mb, (unsigned long)total_mb);
|
||||
set_status(prog);
|
||||
flush_ui();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
montauk::mfree(buf);
|
||||
montauk::close(dst);
|
||||
montauk::close(src);
|
||||
return ok;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Recursively copy directory contents from ramdisk to target drive
|
||||
// ============================================================================
|
||||
|
||||
static int g_files_copied;
|
||||
static int g_dirs_created;
|
||||
|
||||
// Copy all entries from src_dir (on ramdisk) to dst_dir (on target drive).
|
||||
// src_dir: e.g. "0:/" or "0:/os"
|
||||
// dst_dir: e.g. "15:/" or "15:/os"
|
||||
static bool copy_recursive(const char* src_dir, const char* dst_dir) {
|
||||
const char* names[64];
|
||||
int count = montauk::readdir(src_dir, names, 64);
|
||||
if (count < 0) return true; // not a directory or empty, skip
|
||||
|
||||
// Ramdisk readdir returns names relative to the drive root with the
|
||||
// full internal path (e.g., for readdir("0:/os"), names come back as
|
||||
// "os/init.elf", "os/shell.elf", etc.). We need to strip the prefix
|
||||
// that corresponds to the local path portion of src_dir.
|
||||
//
|
||||
// Find the local path after the "N:/" prefix.
|
||||
const char* src_local = src_dir;
|
||||
for (int k = 0; src_local[k]; k++) {
|
||||
if (src_local[k] == ':') {
|
||||
src_local += k + 1;
|
||||
if (src_local[0] == '/') src_local++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int prefix_len = slen(src_local);
|
||||
// If prefix is non-empty, we also skip the trailing '/'
|
||||
if (prefix_len > 0 && src_local[prefix_len - 1] != '/') prefix_len++;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
const char* raw_name = names[i];
|
||||
|
||||
// Strip prefix to get basename
|
||||
const char* basename = raw_name;
|
||||
if (prefix_len > 0 && slen(raw_name) > prefix_len) {
|
||||
basename = raw_name + prefix_len;
|
||||
}
|
||||
|
||||
// Skip "." and ".."
|
||||
if (basename[0] == '.' && (basename[1] == '\0' || basename[1] == '/')) continue;
|
||||
if (basename[0] == '.' && basename[1] == '.' && (basename[2] == '\0' || basename[2] == '/')) continue;
|
||||
|
||||
int blen = slen(basename);
|
||||
|
||||
// Check if this is a directory (trailing '/')
|
||||
bool is_dir = (blen > 0 && basename[blen - 1] == '/');
|
||||
|
||||
if (is_dir) {
|
||||
// Strip trailing '/' for the name
|
||||
char dir_name[256];
|
||||
int j = 0;
|
||||
for (; j < blen - 1 && j < 255; j++) dir_name[j] = basename[j];
|
||||
dir_name[j] = '\0';
|
||||
|
||||
// Create directory on target
|
||||
char target_path[256];
|
||||
path_join(target_path, sizeof(target_path), dst_dir, dir_name);
|
||||
|
||||
montauk::fmkdir(target_path);
|
||||
g_dirs_created++;
|
||||
|
||||
char log_msg[64];
|
||||
snprintf(log_msg, sizeof(log_msg), " mkdir %s", dir_name);
|
||||
add_log(log_msg);
|
||||
flush_ui();
|
||||
|
||||
// Recurse into this directory
|
||||
char src_subdir[256];
|
||||
path_join(src_subdir, sizeof(src_subdir), src_dir, dir_name);
|
||||
|
||||
if (!copy_recursive(src_subdir, target_path))
|
||||
return false;
|
||||
} else {
|
||||
// Skip ramdisk and limine.conf — installed system boots from
|
||||
// disk and gets a fresh config without the ramdisk module.
|
||||
if (strcmp(basename, "ramdisk.tar") == 0) continue;
|
||||
if (strcmp(basename, "limine.conf") == 0) continue;
|
||||
|
||||
// It's a file — copy it
|
||||
char src_path[256];
|
||||
// Build source path: drive prefix + raw_name (which is the full internal path)
|
||||
// src_dir starts with "0:/" so we need "0:/" + raw_name
|
||||
snprintf(src_path, sizeof(src_path), "0:/%s", raw_name);
|
||||
|
||||
char dst_path[256];
|
||||
path_join(dst_path, sizeof(dst_path), dst_dir, basename);
|
||||
|
||||
char log_msg[64];
|
||||
snprintf(log_msg, sizeof(log_msg), " copy %s", basename);
|
||||
add_log(log_msg);
|
||||
flush_ui();
|
||||
|
||||
if (!copy_file(src_path, dst_path))
|
||||
return false;
|
||||
|
||||
g_files_copied++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Install MontaukOS to the selected disk
|
||||
// ============================================================================
|
||||
|
||||
void do_install() {
|
||||
auto& st = g_state;
|
||||
int disk = st.selected_disk;
|
||||
|
||||
if (disk < 0 || disk >= st.disk_count) {
|
||||
st.step = STEP_ERROR;
|
||||
add_log("No disk selected");
|
||||
flush_ui();
|
||||
return;
|
||||
}
|
||||
|
||||
g_files_copied = 0;
|
||||
g_dirs_created = 0;
|
||||
|
||||
// Step 1: Initialize GPT
|
||||
add_log("Initializing GPT...");
|
||||
flush_ui();
|
||||
int r = montauk::gpt_init(disk);
|
||||
if (r < 0) {
|
||||
add_log("ERROR: Failed to initialize GPT");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
add_log(" GPT initialized");
|
||||
flush_ui();
|
||||
|
||||
// Step 2: Create EFI System Partition (entire disk)
|
||||
add_log("Creating EFI System Partition...");
|
||||
flush_ui();
|
||||
Montauk::GptAddParams params;
|
||||
montauk::memset(¶ms, 0, sizeof(params));
|
||||
params.blockDev = disk;
|
||||
params.startLba = 0;
|
||||
params.endLba = 0;
|
||||
params.typeGuid = esp_type_guid();
|
||||
|
||||
const char* pname = "EFI System";
|
||||
int i = 0;
|
||||
for (; pname[i] && i < 71; i++) params.name[i] = pname[i];
|
||||
params.name[i] = '\0';
|
||||
|
||||
r = montauk::gpt_add(¶ms);
|
||||
if (r < 0) {
|
||||
add_log("ERROR: Failed to create partition");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
add_log(" Partition created");
|
||||
flush_ui();
|
||||
|
||||
// Step 3: Format as FAT32
|
||||
add_log("Formatting as FAT32...");
|
||||
flush_ui();
|
||||
|
||||
Montauk::PartInfo parts[MAX_PARTS];
|
||||
int part_count = montauk::partlist(parts, MAX_PARTS);
|
||||
int esp_index = -1;
|
||||
for (int p = 0; p < part_count; p++) {
|
||||
if (parts[p].blockDev == disk) {
|
||||
esp_index = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (esp_index < 0) {
|
||||
add_log("ERROR: Could not find partition");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
Montauk::FsFormatParams fmt;
|
||||
montauk::memset(&fmt, 0, sizeof(fmt));
|
||||
fmt.partIndex = esp_index;
|
||||
fmt.fsType = Montauk::FS_TYPE_FAT32;
|
||||
|
||||
r = montauk::fs_format(&fmt);
|
||||
if (r < 0) {
|
||||
add_log("ERROR: FAT32 format failed");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
add_log(" FAT32 formatted");
|
||||
flush_ui();
|
||||
|
||||
// Step 4: Mount the partition
|
||||
add_log("Mounting partition...");
|
||||
flush_ui();
|
||||
int drive_num = 15;
|
||||
r = montauk::fs_mount(esp_index, drive_num);
|
||||
if (r < 0) {
|
||||
add_log("ERROR: Mount failed");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char drive_root[8];
|
||||
snprintf(drive_root, sizeof(drive_root), "%d:/", drive_num);
|
||||
add_log(" Mounted");
|
||||
flush_ui();
|
||||
|
||||
// Step 5: Create EFI boot directory structure
|
||||
add_log("Creating boot directories...");
|
||||
flush_ui();
|
||||
|
||||
char path_buf[64];
|
||||
snprintf(path_buf, sizeof(path_buf), "%d:/EFI", drive_num);
|
||||
montauk::fmkdir(path_buf);
|
||||
snprintf(path_buf, sizeof(path_buf), "%d:/EFI/BOOT", drive_num);
|
||||
montauk::fmkdir(path_buf);
|
||||
|
||||
// Step 6: Copy entire root filesystem
|
||||
add_log("Copying root filesystem...");
|
||||
flush_ui();
|
||||
|
||||
if (!copy_recursive("0:/", drive_root)) {
|
||||
add_log("ERROR: File copy failed");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
char copy_msg[64];
|
||||
snprintf(copy_msg, sizeof(copy_msg), " %d files, %d directories copied",
|
||||
g_files_copied, g_dirs_created);
|
||||
add_log(copy_msg);
|
||||
flush_ui();
|
||||
|
||||
// Step 7: Write limine.conf without ramdisk module
|
||||
add_log("Writing boot configuration...");
|
||||
flush_ui();
|
||||
snprintf(path_buf, sizeof(path_buf), "%d:/boot/limine/limine.conf", drive_num);
|
||||
{
|
||||
static const char limine_conf[] =
|
||||
"timeout: 0\n"
|
||||
"\n"
|
||||
"/montaukos\n"
|
||||
" protocol: limine\n"
|
||||
" path: boot():/boot/kernel\n";
|
||||
|
||||
int conf_fd = montauk::fcreate(path_buf);
|
||||
if (conf_fd < 0) {
|
||||
add_log("ERROR: Failed to write limine.conf");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
montauk::fwrite(conf_fd, (const uint8_t*)limine_conf, 0, sizeof(limine_conf) - 1);
|
||||
montauk::close(conf_fd);
|
||||
}
|
||||
add_log(" limine.conf written");
|
||||
flush_ui();
|
||||
|
||||
// Step 8: Copy BOOTX64.EFI to EFI/BOOT/
|
||||
// (The recursive copy already put it at boot/limine/BOOTX64.EFI,
|
||||
// but UEFI firmware requires it at /EFI/BOOT/BOOTX64.EFI)
|
||||
add_log("Installing EFI bootloader...");
|
||||
flush_ui();
|
||||
snprintf(path_buf, sizeof(path_buf), "%d:/EFI/BOOT/BOOTX64.EFI", drive_num);
|
||||
if (!copy_file("0:/boot/limine/BOOTX64.EFI", path_buf)) {
|
||||
add_log("ERROR: Failed to install BOOTX64.EFI");
|
||||
flush_ui();
|
||||
st.step = STEP_ERROR;
|
||||
return;
|
||||
}
|
||||
add_log(" BOOTX64.EFI installed");
|
||||
flush_ui();
|
||||
|
||||
// Done
|
||||
add_log("Installation complete!");
|
||||
st.step = STEP_DONE;
|
||||
set_status("MontaukOS installed successfully");
|
||||
flush_ui();
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* installer.h
|
||||
* Shared header for the MontaukOS Installer
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <montauk/syscall.h>
|
||||
#include <montauk/string.h>
|
||||
#include <montauk/heap.h>
|
||||
#include <gui/gui.hpp>
|
||||
#include <gui/truetype.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
}
|
||||
|
||||
using namespace gui;
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int INIT_W = 520;
|
||||
static constexpr int INIT_H = 440;
|
||||
static constexpr int TOOLBAR_H = 36;
|
||||
static constexpr int STEP_BAR_H = 32;
|
||||
static constexpr int CONTENT_TOP = TOOLBAR_H + STEP_BAR_H;
|
||||
static constexpr int MAX_DISKS = 8;
|
||||
static constexpr int MAX_PARTS = 32;
|
||||
static constexpr int STATUS_H = 26;
|
||||
|
||||
// Font sizes — adjusted at runtime by apply_scale()
|
||||
extern int FONT_SIZE;
|
||||
extern int FONT_SM;
|
||||
|
||||
static constexpr int TB_BTN_Y = 5;
|
||||
static constexpr int TB_BTN_H = 26;
|
||||
static constexpr int TB_BTN_RAD = 8;
|
||||
|
||||
static constexpr Color BG_COLOR = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color TOOLBAR_BG = Color::from_rgb(0xF5, 0xF5, 0xF5);
|
||||
static constexpr Color BORDER_COLOR = Color::from_rgb(0xCC, 0xCC, 0xCC);
|
||||
static constexpr Color TEXT_COLOR = Color::from_rgb(0x22, 0x22, 0x22);
|
||||
static constexpr Color DIM_TEXT = Color::from_rgb(0x66, 0x66, 0x66);
|
||||
static constexpr Color FAINT_TEXT = Color::from_rgb(0x88, 0x88, 0x88);
|
||||
static constexpr Color WHITE = Color::from_rgb(0xFF, 0xFF, 0xFF);
|
||||
static constexpr Color ACCENT = Color::from_rgb(0x33, 0x66, 0xCC);
|
||||
static constexpr Color DANGER = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
static constexpr Color SUCCESS_COLOR = Color::from_rgb(0x33, 0x99, 0x33);
|
||||
static constexpr Color DISABLED_BG = Color::from_rgb(0xBB, 0xBB, 0xBB);
|
||||
|
||||
// ============================================================================
|
||||
// Install steps
|
||||
// ============================================================================
|
||||
|
||||
enum InstallStep {
|
||||
STEP_SELECT_DISK,
|
||||
STEP_PARTITION_SCHEME,
|
||||
STEP_CONFIRM,
|
||||
STEP_INSTALLING,
|
||||
STEP_DONE,
|
||||
STEP_ERROR,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Partition schemes
|
||||
// ============================================================================
|
||||
|
||||
enum PartScheme {
|
||||
SCHEME_SINGLE_FAT32 = 0,
|
||||
SCHEME_COUNT,
|
||||
};
|
||||
|
||||
static const char* scheme_names[] = {
|
||||
"Single FAT32 partition (entire disk)",
|
||||
};
|
||||
|
||||
static const char* scheme_descs[] = {
|
||||
"One partition for boot, OS, and data. Simple and compatible.",
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// State
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int LOG_LINES = 16;
|
||||
static constexpr int LOG_LINE_LEN = 64;
|
||||
|
||||
struct InstallerState {
|
||||
Montauk::DiskInfo disks[MAX_DISKS];
|
||||
int disk_count;
|
||||
int selected_disk;
|
||||
int partition_scheme;
|
||||
|
||||
InstallStep step;
|
||||
char log[LOG_LINES][LOG_LINE_LEN];
|
||||
int log_count;
|
||||
|
||||
char status[80];
|
||||
uint64_t status_time;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Global state (extern — defined in main.cpp)
|
||||
// ============================================================================
|
||||
|
||||
extern int g_win_w, g_win_h;
|
||||
extern InstallerState g_state;
|
||||
extern TrueTypeFont* g_font;
|
||||
extern uint32_t* g_pixels;
|
||||
extern int g_win_id;
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations — helpers (main.cpp)
|
||||
// ============================================================================
|
||||
|
||||
void set_status(const char* msg);
|
||||
void add_log(const char* msg);
|
||||
void flush_ui();
|
||||
void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorSize);
|
||||
void apply_scale(int scale);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations — render.cpp
|
||||
// ============================================================================
|
||||
|
||||
void render(uint32_t* pixels);
|
||||
|
||||
// ============================================================================
|
||||
// Function declarations — actions.cpp
|
||||
// ============================================================================
|
||||
|
||||
void installer_refresh_disks();
|
||||
void do_install();
|
||||
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* main.cpp
|
||||
* MontaukOS Installer — entry point, event loop, state
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "installer.h"
|
||||
|
||||
// ============================================================================
|
||||
// Global state definitions
|
||||
// ============================================================================
|
||||
|
||||
int g_win_w = INIT_W;
|
||||
int g_win_h = INIT_H;
|
||||
|
||||
int FONT_SIZE = 18;
|
||||
int FONT_SM = 14;
|
||||
|
||||
InstallerState g_state;
|
||||
TrueTypeFont* g_font = nullptr;
|
||||
uint32_t* g_pixels = nullptr;
|
||||
int g_win_id = -1;
|
||||
|
||||
void apply_scale(int scale) {
|
||||
switch (scale) {
|
||||
case 0: FONT_SIZE = 14; FONT_SM = 11; break;
|
||||
case 2: FONT_SIZE = 22; FONT_SM = 17; break;
|
||||
default: FONT_SIZE = 18; FONT_SM = 14; break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helpers
|
||||
// ============================================================================
|
||||
|
||||
void set_status(const char* msg) {
|
||||
int i = 0;
|
||||
for (; i < 79 && msg[i]; i++) g_state.status[i] = msg[i];
|
||||
g_state.status[i] = '\0';
|
||||
g_state.status_time = montauk::get_milliseconds();
|
||||
}
|
||||
|
||||
void add_log(const char* msg) {
|
||||
if (g_state.log_count >= LOG_LINES) {
|
||||
// Scroll: shift all lines up by one
|
||||
for (int j = 0; j < LOG_LINES - 1; j++)
|
||||
montauk::memcpy(g_state.log[j], g_state.log[j + 1], LOG_LINE_LEN);
|
||||
g_state.log_count = LOG_LINES - 1;
|
||||
}
|
||||
int i = 0;
|
||||
for (; i < LOG_LINE_LEN - 1 && msg[i]; i++)
|
||||
g_state.log[g_state.log_count][i] = msg[i];
|
||||
g_state.log[g_state.log_count][i] = '\0';
|
||||
g_state.log_count++;
|
||||
}
|
||||
|
||||
void flush_ui() {
|
||||
if (g_pixels && g_win_id >= 0) {
|
||||
render(g_pixels);
|
||||
montauk::win_present(g_win_id);
|
||||
}
|
||||
}
|
||||
|
||||
void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorSize) {
|
||||
uint64_t bytes = sectors * sectorSize;
|
||||
uint64_t gb = bytes / (1024ULL * 1024 * 1024);
|
||||
if (gb >= 1024) {
|
||||
uint64_t tb = gb / 1024;
|
||||
uint64_t frac = ((gb % 1024) * 10) / 1024;
|
||||
snprintf(buf, bufsize, "%lu.%lu TB", (unsigned)tb, (unsigned)frac);
|
||||
} else if (gb > 0) {
|
||||
uint64_t frac = ((bytes % (1024ULL * 1024 * 1024)) * 10) / (1024ULL * 1024 * 1024);
|
||||
snprintf(buf, bufsize, "%lu.%lu GB", (unsigned)gb, (unsigned)frac);
|
||||
} else {
|
||||
uint64_t mb = bytes / (1024ULL * 1024);
|
||||
snprintf(buf, bufsize, "%lu MB", (unsigned)mb);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Mouse handling
|
||||
// ============================================================================
|
||||
|
||||
static bool handle_click(int mx, int my) {
|
||||
auto& st = g_state;
|
||||
int fh = g_font ? g_font->get_cache(FONT_SIZE)->ascent - g_font->get_cache(FONT_SIZE)->descent : 16;
|
||||
|
||||
// Common bottom button area
|
||||
int btn_w = 120, btn_h = 34;
|
||||
int btn_y = g_win_h - STATUS_H - btn_h - 12;
|
||||
|
||||
if (st.step == STEP_SELECT_DISK) {
|
||||
// Disk list items
|
||||
int y = CONTENT_TOP + 40 + fh + 12;
|
||||
for (int i = 0; i < st.disk_count; i++) {
|
||||
int item_h = 48;
|
||||
if (mx >= 16 && mx < g_win_w - 16 && my >= y && my < y + item_h) {
|
||||
st.selected_disk = i;
|
||||
return true;
|
||||
}
|
||||
y += item_h + 4;
|
||||
}
|
||||
|
||||
// "Next" button
|
||||
int next_x = g_win_w - btn_w - 16;
|
||||
if (st.selected_disk >= 0 &&
|
||||
mx >= next_x && mx < next_x + btn_w &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
st.step = STEP_PARTITION_SCHEME;
|
||||
return true;
|
||||
}
|
||||
|
||||
// "Refresh" button
|
||||
int ref_w = 80;
|
||||
int ref_x = next_x - ref_w - 8;
|
||||
if (mx >= ref_x && mx < ref_x + ref_w &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
installer_refresh_disks();
|
||||
return true;
|
||||
}
|
||||
} else if (st.step == STEP_PARTITION_SCHEME) {
|
||||
// Scheme radio buttons
|
||||
int y = CONTENT_TOP + 40 + fh + 12;
|
||||
for (int i = 0; i < SCHEME_COUNT; i++) {
|
||||
int item_h = 52;
|
||||
if (mx >= 16 && mx < g_win_w - 16 && my >= y && my < y + item_h) {
|
||||
st.partition_scheme = i;
|
||||
return true;
|
||||
}
|
||||
y += item_h + 4;
|
||||
}
|
||||
|
||||
// "Next" button
|
||||
int next_x = g_win_w - btn_w - 16;
|
||||
if (mx >= next_x && mx < next_x + btn_w &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
st.step = STEP_CONFIRM;
|
||||
return true;
|
||||
}
|
||||
|
||||
// "Back" button
|
||||
int back_x = next_x - btn_w - 8;
|
||||
if (mx >= back_x && mx < back_x + btn_w &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
st.step = STEP_SELECT_DISK;
|
||||
return true;
|
||||
}
|
||||
} else if (st.step == STEP_CONFIRM) {
|
||||
int center_x = g_win_w / 2;
|
||||
int gap = 16;
|
||||
|
||||
// "Install" button
|
||||
int conf_x = center_x - btn_w - gap / 2;
|
||||
if (mx >= conf_x && mx < conf_x + btn_w &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
st.step = STEP_INSTALLING;
|
||||
st.log_count = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// "Back" button
|
||||
int canc_x = center_x + gap / 2;
|
||||
if (mx >= canc_x && mx < canc_x + btn_w &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
st.step = STEP_PARTITION_SCHEME;
|
||||
return true;
|
||||
}
|
||||
} else if (st.step == STEP_DONE || st.step == STEP_ERROR) {
|
||||
int close_x = (g_win_w - 100) / 2;
|
||||
if (mx >= close_x && mx < close_x + 100 &&
|
||||
my >= btn_y && my < btn_y + btn_h) {
|
||||
return false; // signal quit
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Entry point
|
||||
// ============================================================================
|
||||
|
||||
extern "C" void _start() {
|
||||
montauk::memset(&g_state, 0, sizeof(g_state));
|
||||
g_state.selected_disk = -1;
|
||||
g_state.partition_scheme = SCHEME_SINGLE_FAT32;
|
||||
g_state.step = STEP_SELECT_DISK;
|
||||
|
||||
// Load font
|
||||
{
|
||||
TrueTypeFont* f = (TrueTypeFont*)montauk::malloc(sizeof(TrueTypeFont));
|
||||
if (f) {
|
||||
montauk::memset(f, 0, sizeof(TrueTypeFont));
|
||||
if (!f->init("0:/fonts/Roboto-Medium.ttf")) { montauk::mfree(f); f = nullptr; }
|
||||
}
|
||||
g_font = f;
|
||||
}
|
||||
|
||||
apply_scale(montauk::win_getscale());
|
||||
|
||||
installer_refresh_disks();
|
||||
|
||||
// Create window
|
||||
Montauk::WinCreateResult wres;
|
||||
if (montauk::win_create("Install MontaukOS", INIT_W, INIT_H, &wres) < 0 || wres.id < 0)
|
||||
montauk::exit(1);
|
||||
|
||||
int win_id = wres.id;
|
||||
uint32_t* pixels = (uint32_t*)(uintptr_t)wres.pixelVa;
|
||||
g_pixels = pixels;
|
||||
g_win_id = win_id;
|
||||
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
|
||||
bool install_triggered = false;
|
||||
|
||||
while (true) {
|
||||
Montauk::WinEvent ev;
|
||||
bool redraw = false;
|
||||
|
||||
int r = montauk::win_poll(win_id, &ev);
|
||||
if (r < 0) break;
|
||||
|
||||
if (r == 0) {
|
||||
if (g_state.step == STEP_INSTALLING && !install_triggered) {
|
||||
install_triggered = true;
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
do_install();
|
||||
render(pixels);
|
||||
montauk::win_present(win_id);
|
||||
}
|
||||
montauk::sleep_ms(16);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ev.type == 3) break;
|
||||
|
||||
if (ev.type == 4) {
|
||||
apply_scale(ev.scale.scale);
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (ev.type == 2) {
|
||||
g_win_w = ev.resize.w;
|
||||
g_win_h = ev.resize.h;
|
||||
pixels = (uint32_t*)(uintptr_t)montauk::win_resize(win_id, g_win_w, g_win_h);
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (ev.type == 0 && ev.key.pressed) {
|
||||
if (ev.key.scancode == 0x01) break;
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
if (ev.type == 1) {
|
||||
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||
if (clicked) {
|
||||
if (!handle_click(ev.mouse.x, ev.mouse.y))
|
||||
break;
|
||||
redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (redraw) { render(pixels); montauk::win_present(win_id); }
|
||||
}
|
||||
|
||||
montauk::win_destroy(win_id);
|
||||
montauk::exit(0);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Installer"
|
||||
binary = "installer.elf"
|
||||
icon = "text-x-install.svg"
|
||||
|
||||
[menu]
|
||||
category = "System"
|
||||
visible = true
|
||||
@@ -0,0 +1,453 @@
|
||||
/*
|
||||
* render.cpp
|
||||
* MontaukOS Installer — rendering
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "installer.h"
|
||||
|
||||
// ============================================================================
|
||||
// Pixel helpers
|
||||
// ============================================================================
|
||||
|
||||
static void px_fill(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
int x0 = x < 0 ? 0 : x, y0 = y < 0 ? 0 : y;
|
||||
int x1 = x + w > bw ? bw : x + w;
|
||||
int y1 = y + h > bh ? bh : y + h;
|
||||
for (int row = y0; row < y1; row++)
|
||||
for (int col = x0; col < x1; col++)
|
||||
px[row * bw + col] = v;
|
||||
}
|
||||
|
||||
static void px_hline(uint32_t* px, int bw, int bh, int x, int y, int w, Color c) {
|
||||
if (y < 0 || y >= bh) return;
|
||||
uint32_t v = c.to_pixel();
|
||||
int x0 = x < 0 ? 0 : x;
|
||||
int x1 = x + w > bw ? bw : x + w;
|
||||
for (int col = x0; col < x1; col++)
|
||||
px[y * bw + col] = v;
|
||||
}
|
||||
|
||||
static void px_fill_rounded(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, int r, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
for (int row = 0; row < h; row++) {
|
||||
int dy = y + row;
|
||||
if (dy < 0 || dy >= bh) continue;
|
||||
for (int col = 0; col < w; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= bw) continue;
|
||||
bool skip = false;
|
||||
int cx, cy;
|
||||
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); if (cx*cx + cy*cy >= r*r) skip = true; }
|
||||
if (!skip) px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void px_rect(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, Color c) {
|
||||
px_hline(px, bw, bh, x, y, w, c);
|
||||
px_hline(px, bw, bh, x, y + h - 1, w, c);
|
||||
for (int row = y; row < y + h; row++) {
|
||||
if (row < 0 || row >= bh) continue;
|
||||
if (x >= 0 && x < bw) px[row * bw + x] = c.to_pixel();
|
||||
int rx = x + w - 1;
|
||||
if (rx >= 0 && rx < bw) px[row * bw + rx] = c.to_pixel();
|
||||
}
|
||||
}
|
||||
|
||||
static void px_text(uint32_t* px, int bw, int bh,
|
||||
int x, int y, const char* text, Color c, int size = FONT_SIZE) {
|
||||
if (g_font)
|
||||
g_font->draw_to_buffer(px, bw, bh, x, y, text, c, size);
|
||||
}
|
||||
|
||||
static int text_w(const char* text, int size = FONT_SIZE) {
|
||||
return g_font ? g_font->measure_text(text, size) : 0;
|
||||
}
|
||||
|
||||
static int font_h(int size = FONT_SIZE) {
|
||||
if (!g_font) return 16;
|
||||
auto* cache = g_font->get_cache(size);
|
||||
return cache->ascent - cache->descent;
|
||||
}
|
||||
|
||||
static void px_stroke_rounded(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h, int r, Color c) {
|
||||
uint32_t v = c.to_pixel();
|
||||
for (int row = 0; row < h; row++) {
|
||||
int dy = y + row;
|
||||
if (dy < 0 || dy >= bh) continue;
|
||||
for (int col = 0; col < w; col++) {
|
||||
int dx = x + col;
|
||||
if (dx < 0 || dx >= bw) continue;
|
||||
// Check if pixel is on the edge (within 1px of border)
|
||||
bool on_edge = (row == 0 || row == h - 1 || col == 0 || col == w - 1);
|
||||
// For corner regions, check the rounded boundary
|
||||
bool in_shape = true;
|
||||
int cx, cy;
|
||||
if (col < r && row < r) { cx = r - col - 1; cy = r - row - 1; in_shape = cx*cx + cy*cy < r*r; }
|
||||
else if (col >= w - r && row < r) { cx = col - (w - r); cy = r - row - 1; in_shape = cx*cx + cy*cy < r*r; }
|
||||
else if (col < r && row >= h - r) { cx = r - col - 1; cy = row - (h - r); in_shape = cx*cx + cy*cy < r*r; }
|
||||
else if (col >= w - r && row >= h - r) { cx = col - (w - r); cy = row - (h - r); in_shape = cx*cx + cy*cy < r*r; }
|
||||
if (!in_shape) continue;
|
||||
// Check if the pixel one step inward is still in shape
|
||||
bool inner = true;
|
||||
if (on_edge) { inner = false; }
|
||||
else {
|
||||
// Check neighbors — if any neighbor is outside, we're on the edge
|
||||
int offsets[][2] = {{-1,0},{1,0},{0,-1},{0,1}};
|
||||
for (auto& o : offsets) {
|
||||
int nc = col + o[0], nr = row + o[1];
|
||||
if (nc < 0 || nc >= w || nr < 0 || nr >= h) { inner = false; break; }
|
||||
bool nb_in = true;
|
||||
if (nc < r && nr < r) { int a = r-nc-1, b = r-nr-1; nb_in = a*a+b*b < r*r; }
|
||||
else if (nc >= w-r && nr < r) { int a = nc-(w-r), b = r-nr-1; nb_in = a*a+b*b < r*r; }
|
||||
else if (nc < r && nr >= h-r) { int a = r-nc-1, b = nr-(h-r); nb_in = a*a+b*b < r*r; }
|
||||
else if (nc >= w-r && nr >= h-r) { int a = nc-(w-r), b = nr-(h-r); nb_in = a*a+b*b < r*r; }
|
||||
if (!nb_in) { inner = false; break; }
|
||||
}
|
||||
}
|
||||
if (!inner) px[dy * bw + dx] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void px_button(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h,
|
||||
const char* label, Color bg, Color fg, int r) {
|
||||
px_fill_rounded(px, bw, bh, x, y, w, h, r, bg);
|
||||
int tw = text_w(label);
|
||||
int fh = font_h();
|
||||
px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2, label, fg);
|
||||
}
|
||||
|
||||
static void px_button_outline(uint32_t* px, int bw, int bh,
|
||||
int x, int y, int w, int h,
|
||||
const char* label, Color border, Color fg, int r) {
|
||||
px_stroke_rounded(px, bw, bh, x, y, w, h, r, border);
|
||||
int tw = text_w(label);
|
||||
int fh = font_h();
|
||||
px_text(px, bw, bh, x + (w - tw) / 2, y + (h - fh) / 2, label, fg);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render: disk selection step
|
||||
// ============================================================================
|
||||
|
||||
static void render_select_disk(uint32_t* px) {
|
||||
auto& st = g_state;
|
||||
int fh = font_h();
|
||||
|
||||
int y = CONTENT_TOP + 16;
|
||||
px_text(px, g_win_w, g_win_h, 16, y, "Select a target disk", TEXT_COLOR);
|
||||
y += fh + 4;
|
||||
px_text(px, g_win_w, g_win_h, 16, y, "MontaukOS will be installed to this disk.", DIM_TEXT, FONT_SM);
|
||||
y += font_h(FONT_SM) + 12;
|
||||
|
||||
if (st.disk_count == 0) {
|
||||
px_text(px, g_win_w, g_win_h, 16, y, "No disks detected", FAINT_TEXT);
|
||||
}
|
||||
|
||||
for (int i = 0; i < st.disk_count; i++) {
|
||||
int item_h = 48;
|
||||
int item_x = 16;
|
||||
int item_w = g_win_w - 32;
|
||||
bool sel = (i == st.selected_disk);
|
||||
|
||||
Color bg = sel
|
||||
? Color::from_rgb(0xD8, 0xE8, 0xF8)
|
||||
: Color::from_rgb(0xF4, 0xF4, 0xF4);
|
||||
|
||||
px_fill_rounded(px, g_win_w, g_win_h, item_x, y, item_w, item_h, 6, bg);
|
||||
|
||||
if (sel)
|
||||
px_fill_rounded(px, g_win_w, g_win_h, item_x, y, 4, item_h, 2, ACCENT);
|
||||
|
||||
px_text(px, g_win_w, g_win_h, item_x + 12, y + 6, st.disks[i].model, TEXT_COLOR);
|
||||
|
||||
char info[64], sz[24];
|
||||
format_disk_size(sz, sizeof(sz), st.disks[i].sectorCount, st.disks[i].sectorSizeLog);
|
||||
const char* dtype = st.disks[i].rpm == 1 ? "SSD" : "HDD";
|
||||
snprintf(info, sizeof(info), "%s %s (Disk %d)", sz, dtype, i);
|
||||
px_text(px, g_win_w, g_win_h, item_x + 12, y + 6 + fh + 2, info, DIM_TEXT, FONT_SM);
|
||||
|
||||
y += item_h + 4;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
int btn_w = 120, btn_h = 34;
|
||||
int btn_y = g_win_h - STATUS_H - btn_h - 12;
|
||||
int next_x = g_win_w - btn_w - 16;
|
||||
|
||||
Color next_bg = (st.selected_disk >= 0) ? ACCENT : DISABLED_BG;
|
||||
px_button(px, g_win_w, g_win_h, next_x, btn_y, btn_w, btn_h,
|
||||
"Next", next_bg, WHITE, TB_BTN_RAD);
|
||||
|
||||
int ref_w = 80;
|
||||
int ref_x = next_x - ref_w - 8;
|
||||
px_button_outline(px, g_win_w, g_win_h, ref_x, btn_y, ref_w, btn_h,
|
||||
"Refresh", BORDER_COLOR, DIM_TEXT, TB_BTN_RAD);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render: partition scheme step
|
||||
// ============================================================================
|
||||
|
||||
static void render_partition_scheme(uint32_t* px) {
|
||||
auto& st = g_state;
|
||||
int fh = font_h();
|
||||
int fh_sm = font_h(FONT_SM);
|
||||
|
||||
int y = CONTENT_TOP + 16;
|
||||
px_text(px, g_win_w, g_win_h, 16, y, "Partition scheme", TEXT_COLOR);
|
||||
y += fh + 4;
|
||||
px_text(px, g_win_w, g_win_h, 16, y, "How should the disk be partitioned?", DIM_TEXT, FONT_SM);
|
||||
y += fh_sm + 12;
|
||||
|
||||
for (int i = 0; i < SCHEME_COUNT; i++) {
|
||||
int item_h = 52;
|
||||
int item_x = 16;
|
||||
int item_w = g_win_w - 32;
|
||||
|
||||
bool selected = (i == st.partition_scheme);
|
||||
Color bg = selected
|
||||
? Color::from_rgb(0xD8, 0xE8, 0xF8)
|
||||
: Color::from_rgb(0xF4, 0xF4, 0xF4);
|
||||
|
||||
px_fill_rounded(px, g_win_w, g_win_h, item_x, y, item_w, item_h, 6, bg);
|
||||
|
||||
if (selected)
|
||||
px_fill_rounded(px, g_win_w, g_win_h, item_x, y, 4, item_h, 2, ACCENT);
|
||||
|
||||
// Radio indicator
|
||||
int rx = item_x + 14;
|
||||
int ry = y + item_h / 2;
|
||||
px_fill_rounded(px, g_win_w, g_win_h, rx - 6, ry - 6, 12, 12, 6, DIM_TEXT);
|
||||
px_fill_rounded(px, g_win_w, g_win_h, rx - 5, ry - 5, 10, 10, 5, BG_COLOR);
|
||||
if (selected)
|
||||
px_fill_rounded(px, g_win_w, g_win_h, rx - 3, ry - 3, 6, 6, 3, ACCENT);
|
||||
|
||||
px_text(px, g_win_w, g_win_h, item_x + 32, y + 8, scheme_names[i], TEXT_COLOR);
|
||||
px_text(px, g_win_w, g_win_h, item_x + 32, y + 8 + fh + 2, scheme_descs[i], DIM_TEXT, FONT_SM);
|
||||
|
||||
y += item_h + 4;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
int btn_w = 120, btn_h = 34;
|
||||
int btn_y = g_win_h - STATUS_H - btn_h - 12;
|
||||
int next_x = g_win_w - btn_w - 16;
|
||||
|
||||
px_button(px, g_win_w, g_win_h, next_x, btn_y, btn_w, btn_h,
|
||||
"Next", ACCENT, WHITE, TB_BTN_RAD);
|
||||
|
||||
int back_x = next_x - btn_w - 8;
|
||||
px_button_outline(px, g_win_w, g_win_h, back_x, btn_y, btn_w, btn_h,
|
||||
"Back", BORDER_COLOR, DIM_TEXT, TB_BTN_RAD);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render: confirmation step
|
||||
// ============================================================================
|
||||
|
||||
static void render_confirm(uint32_t* px) {
|
||||
auto& st = g_state;
|
||||
int fh = font_h();
|
||||
int y = CONTENT_TOP + 24;
|
||||
|
||||
const char* title = "Confirm Installation";
|
||||
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(title)) / 2, y, title, TEXT_COLOR);
|
||||
y += fh + 16;
|
||||
|
||||
const char* warn1 = "This will ERASE ALL DATA on:";
|
||||
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(warn1)) / 2, y, warn1, DANGER);
|
||||
y += fh + 8;
|
||||
|
||||
char desc[128], sz[24];
|
||||
format_disk_size(sz, sizeof(sz), st.disks[st.selected_disk].sectorCount,
|
||||
st.disks[st.selected_disk].sectorSizeLog);
|
||||
snprintf(desc, sizeof(desc), "Disk %d: %s (%s)",
|
||||
st.selected_disk, st.disks[st.selected_disk].model, sz);
|
||||
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(desc)) / 2, y, desc, TEXT_COLOR);
|
||||
y += fh + 16;
|
||||
|
||||
// Scheme summary
|
||||
const char* scheme_label = scheme_names[st.partition_scheme];
|
||||
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(scheme_label, FONT_SM)) / 2, y,
|
||||
scheme_label, DIM_TEXT, FONT_SM);
|
||||
y += font_h(FONT_SM) + 4;
|
||||
|
||||
const char* warn3 = "The entire root filesystem will be installed.";
|
||||
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(warn3, FONT_SM)) / 2, y, warn3, DIM_TEXT, FONT_SM);
|
||||
|
||||
// Buttons
|
||||
int btn_w = 120, btn_h = 34;
|
||||
int center_x = g_win_w / 2;
|
||||
int btn_y = g_win_h - STATUS_H - btn_h - 12;
|
||||
int gap = 16;
|
||||
|
||||
px_button(px, g_win_w, g_win_h, center_x - btn_w - gap / 2, btn_y, btn_w, btn_h,
|
||||
"Install", DANGER, WHITE, TB_BTN_RAD);
|
||||
px_button_outline(px, g_win_w, g_win_h, center_x + gap / 2, btn_y, btn_w, btn_h,
|
||||
"Back", BORDER_COLOR, DIM_TEXT, TB_BTN_RAD);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render: installing / done / error steps
|
||||
// ============================================================================
|
||||
|
||||
static void render_progress(uint32_t* px) {
|
||||
auto& st = g_state;
|
||||
int fh = font_h();
|
||||
int fh_sm = font_h(FONT_SM);
|
||||
int y = CONTENT_TOP + 16;
|
||||
|
||||
const char* title;
|
||||
Color title_color;
|
||||
if (st.step == STEP_INSTALLING) {
|
||||
title = "Installing...";
|
||||
title_color = TEXT_COLOR;
|
||||
} else if (st.step == STEP_DONE) {
|
||||
title = "Installation Complete";
|
||||
title_color = SUCCESS_COLOR;
|
||||
} else {
|
||||
title = "Installation Failed";
|
||||
title_color = DANGER;
|
||||
}
|
||||
|
||||
px_text(px, g_win_w, g_win_h, (g_win_w - text_w(title)) / 2, y, title, title_color);
|
||||
y += fh + 12;
|
||||
|
||||
// Log area
|
||||
int log_x = 16;
|
||||
int log_w = g_win_w - 32;
|
||||
int log_h = st.log_count * (fh_sm + 2) + 16;
|
||||
int max_log_h = g_win_h - y - STATUS_H - 60;
|
||||
if (log_h < 60) log_h = 60;
|
||||
if (log_h > max_log_h) log_h = max_log_h;
|
||||
|
||||
px_fill_rounded(px, g_win_w, g_win_h, log_x, y, log_w, log_h, 4,
|
||||
Color::from_rgb(0xF4, 0xF4, 0xF4));
|
||||
|
||||
int ly = y + 8;
|
||||
for (int i = 0; i < st.log_count; i++) {
|
||||
if (ly + fh_sm > y + log_h - 4) break;
|
||||
px_text(px, g_win_w, g_win_h, log_x + 8, ly, st.log[i], TEXT_COLOR, FONT_SM);
|
||||
ly += fh_sm + 2;
|
||||
}
|
||||
|
||||
if (st.step == STEP_DONE || st.step == STEP_ERROR) {
|
||||
int btn_w = 100, btn_h = 34;
|
||||
int btn_x = (g_win_w - btn_w) / 2;
|
||||
int btn_y = g_win_h - STATUS_H - btn_h - 12;
|
||||
if (st.step == STEP_DONE)
|
||||
px_button(px, g_win_w, g_win_h, btn_x, btn_y, btn_w, btn_h,
|
||||
"Close", ACCENT, WHITE, TB_BTN_RAD);
|
||||
else
|
||||
px_button_outline(px, g_win_w, g_win_h, btn_x, btn_y, btn_w, btn_h,
|
||||
"Close", BORDER_COLOR, DIM_TEXT, TB_BTN_RAD);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Render: toolbar, step bar, and status bar
|
||||
// ============================================================================
|
||||
|
||||
static void render_toolbar(uint32_t* px) {
|
||||
px_fill(px, g_win_w, g_win_h, 0, 0, g_win_w, TOOLBAR_H, TOOLBAR_BG);
|
||||
px_hline(px, g_win_w, g_win_h, 0, TOOLBAR_H - 1, g_win_w, BORDER_COLOR);
|
||||
|
||||
const char* title = "MontaukOS Installer";
|
||||
int fh = font_h();
|
||||
px_text(px, g_win_w, g_win_h, 12, (TOOLBAR_H - fh) / 2, title, TEXT_COLOR);
|
||||
}
|
||||
|
||||
static void render_step_bar(uint32_t* px) {
|
||||
int bar_y = TOOLBAR_H;
|
||||
px_fill(px, g_win_w, g_win_h, 0, bar_y, g_win_w, STEP_BAR_H,
|
||||
Color::from_rgb(0xFA, 0xFA, 0xFA));
|
||||
px_hline(px, g_win_w, g_win_h, 0, bar_y + STEP_BAR_H - 1, g_win_w, BORDER_COLOR);
|
||||
|
||||
static const char* step_labels[] = { "Disk", "Partition", "Confirm", "Install" };
|
||||
static constexpr int STEP_COUNT = 4;
|
||||
|
||||
// Map current state to step index (0-3)
|
||||
int cur = 0;
|
||||
if (g_state.step == STEP_PARTITION_SCHEME) cur = 1;
|
||||
else if (g_state.step == STEP_CONFIRM) cur = 2;
|
||||
else if (g_state.step >= STEP_INSTALLING) cur = 3;
|
||||
|
||||
int fh = font_h();
|
||||
|
||||
// Measure total width to center the bar
|
||||
int total_w = 0;
|
||||
int label_ws[STEP_COUNT];
|
||||
int arrow_w = text_w(">");
|
||||
for (int i = 0; i < STEP_COUNT; i++) {
|
||||
label_ws[i] = text_w(step_labels[i]);
|
||||
total_w += label_ws[i];
|
||||
if (i < STEP_COUNT - 1) total_w += arrow_w + 16; // padding around arrow
|
||||
}
|
||||
|
||||
int x = (g_win_w - total_w) / 2;
|
||||
int ty = bar_y + (STEP_BAR_H - fh) / 2;
|
||||
|
||||
for (int i = 0; i < STEP_COUNT; i++) {
|
||||
Color col;
|
||||
if (i == cur) col = ACCENT;
|
||||
else if (i < cur) col = Color::from_rgb(0x88, 0xAA, 0xDD);
|
||||
else col = Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
|
||||
px_text(px, g_win_w, g_win_h, x, ty, step_labels[i], col);
|
||||
x += label_ws[i];
|
||||
|
||||
if (i < STEP_COUNT - 1) {
|
||||
x += 8;
|
||||
px_text(px, g_win_w, g_win_h, x, ty, ">", Color::from_rgb(0xCC, 0xCC, 0xCC));
|
||||
x += arrow_w + 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void render_status(uint32_t* px) {
|
||||
auto& st = g_state;
|
||||
int fh = font_h();
|
||||
int sy = g_win_h - STATUS_H;
|
||||
px_fill(px, g_win_w, g_win_h, 0, sy, g_win_w, STATUS_H, Color::from_rgb(0xF0, 0xF0, 0xF0));
|
||||
px_hline(px, g_win_w, g_win_h, 0, sy, g_win_w, BORDER_COLOR);
|
||||
if (st.status[0]) {
|
||||
uint64_t age = montauk::get_milliseconds() - st.status_time;
|
||||
Color sc = (age < 5000)
|
||||
? Color::from_rgb(0x33, 0x33, 0x33)
|
||||
: Color::from_rgb(0xAA, 0xAA, 0xAA);
|
||||
px_text(px, g_win_w, g_win_h, 8, sy + (STATUS_H - fh) / 2, st.status, sc);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Top-level render
|
||||
// ============================================================================
|
||||
|
||||
void render(uint32_t* pixels) {
|
||||
px_fill(pixels, g_win_w, g_win_h, 0, 0, g_win_w, g_win_h, BG_COLOR);
|
||||
render_toolbar(pixels);
|
||||
render_step_bar(pixels);
|
||||
|
||||
switch (g_state.step) {
|
||||
case STEP_SELECT_DISK: render_select_disk(pixels); break;
|
||||
case STEP_PARTITION_SCHEME: render_partition_scheme(pixels); break;
|
||||
case STEP_CONFIRM: render_confirm(pixels); break;
|
||||
case STEP_INSTALLING:
|
||||
case STEP_DONE:
|
||||
case STEP_ERROR: render_progress(pixels); break;
|
||||
}
|
||||
|
||||
render_status(pixels);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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>
|
||||
|
||||
// Override all stb_truetype dependencies before including the implementation
|
||||
|
||||
#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>
|
||||
@@ -68,14 +68,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/pdfviewer.elf
|
||||
TARGET := $(BINDIR)/apps/pdfviewer/pdfviewer.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/pdfviewer
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
HEADERS := pdfviewer.h
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "PDF Viewer"
|
||||
binary = "pdfviewer.elf"
|
||||
icon = "pdf-viewer.svg"
|
||||
|
||||
[menu]
|
||||
category = "Applications"
|
||||
visible = false
|
||||
@@ -68,14 +68,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/spreadsheet.elf
|
||||
TARGET := $(BINDIR)/apps/spreadsheet/spreadsheet.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/spreadsheet
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBDIR)/libc/liblibc.a -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Spreadsheet"
|
||||
binary = "spreadsheet.elf"
|
||||
icon = "spreadsheet.svg"
|
||||
|
||||
[menu]
|
||||
category = "Applications"
|
||||
visible = true
|
||||
@@ -76,14 +76,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/weather.elf
|
||||
TARGET := $(BINDIR)/apps/weather/weather.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/weather
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Weather"
|
||||
binary = "weather.elf"
|
||||
icon = "weather-widget.svg"
|
||||
|
||||
[menu]
|
||||
category = "Internet"
|
||||
visible = true
|
||||
@@ -76,14 +76,14 @@ OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
# ---- Target ----
|
||||
|
||||
TARGET := $(BINDIR)/os/wikipedia.elf
|
||||
TARGET := $(BINDIR)/apps/wikipedia/wikipedia.elf
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) $(LIBS) $(LINK_LD) Makefile
|
||||
mkdir -p $(BINDIR)/os
|
||||
mkdir -p $(BINDIR)/apps/wikipedia
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||
|
||||
$(OBJDIR)/%.o: %.cpp Makefile
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[app]
|
||||
name = "Wikipedia"
|
||||
binary = "wikipedia.elf"
|
||||
icon = "web-browser.svg"
|
||||
|
||||
[menu]
|
||||
category = "Internet"
|
||||
visible = true
|
||||
Reference in New Issue
Block a user