feat: redesign installer on MTK toolkit with component selection
Rewrite the Installer app on the Montauk Toolkit (Canvas + gui/mtk widgets, theme, hover states, scrollbar) replacing the hand-rolled px_* renderer and bespoke TrueType usage. Add a new "Software" step: an expandable checkbox tree for optional components (Montauk SDK with gcc/g++, binutils, tcc, lua sub-items; Games; Office; Internet apps; Printing; experimental httpd and SDR). Unchecked components' paths are excluded from the install copy, with a longest-path ownership rule so e.g. sdk/tcc installs even when the rest of the SDK is deselected. The update flow refreshes only the components the target already has. Add tri-state checkbox and disclosure-arrow widgets to the MTK toolkit (anti-aliased, supersampled). Merge tcc and lua into 0:/sdk (0:/sdk/tcc, 0:/sdk/lua) and drop 0:/lib: update tcc config.h, lua luaconf.h, both Makefiles, the devkit clean scope, man pages and montaukos.org notices. Make printing optional: init skips printd when 0:/os/printd.elf is absent. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#define MONTAUK_BUILD_NUMBER 29
|
#define MONTAUK_BUILD_NUMBER 31
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ exception, is included on the MontaukOS ISO at 0:/os/licenses/AGPL-3.0-C059.txt.
|
|||||||
10. Tiny C Compiler (TCC)
|
10. Tiny C Compiler (TCC)
|
||||||
================================================================================
|
================================================================================
|
||||||
Shipped as the bundled C compiler (tcc.elf) with its runtime libraries and
|
Shipped as the bundled C compiler (tcc.elf) with its runtime libraries and
|
||||||
headers on the ISO (0:/lib/tcc/). MontaukOS ships a modified version of TCC
|
headers on the ISO (0:/sdk/tcc/). MontaukOS ships a modified version of TCC
|
||||||
0.9.28rc, ported to target MontaukOS (added platform startup/compatibility
|
0.9.28rc, ported to target MontaukOS (added platform startup/compatibility
|
||||||
code and a MontaukOS sysroot).
|
code and a MontaukOS sysroot).
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ License: GNU Lesser General Public License, version 2.1 (LGPL-2.1)
|
|||||||
11. Lua
|
11. Lua
|
||||||
================================================================================
|
================================================================================
|
||||||
Shipped as the bundled Lua interpreter (lua.elf), Lua 5.4.8, with its static
|
Shipped as the bundled Lua interpreter (lua.elf), Lua 5.4.8, with its static
|
||||||
library and headers on the ISO (0:/lib/lua/).
|
library and headers on the ISO (0:/sdk/lua/).
|
||||||
|
|
||||||
License: MIT License
|
License: MIT License
|
||||||
|
|
||||||
|
|||||||
@@ -153,9 +153,9 @@
|
|||||||
* Roboto font, Copyright The Roboto Project Authors - OFL-1.1
|
* Roboto font, Copyright The Roboto Project Authors - OFL-1.1
|
||||||
* C059 font (URW Base 35), Copyright (C) (URW)++ Design and Development
|
* C059 font (URW Base 35), Copyright (C) (URW)++ Design and Development
|
||||||
GmbH - AGPLv3 with font-embedding exception
|
GmbH - AGPLv3 with font-embedding exception
|
||||||
* Tiny C Compiler (tcc.elf, 0:/lib/tcc), Copyright (c) Fabrice Bellard and
|
* Tiny C Compiler (tcc.elf, 0:/sdk/tcc), Copyright (c) Fabrice Bellard and
|
||||||
contributors - LGPL-2.1
|
contributors - LGPL-2.1
|
||||||
* Lua (lua.elf, 0:/lib/lua), Copyright (C) Lua.org, PUC-Rio - MIT
|
* Lua (lua.elf, 0:/sdk/lua), Copyright (C) Lua.org, PUC-Rio - MIT
|
||||||
* Mozilla CA certificate bundle (0:/os/certs), Mozilla CA Certificate
|
* Mozilla CA certificate bundle (0:/os/certs), Mozilla CA Certificate
|
||||||
Program - MPL-2.0
|
Program - MPL-2.0
|
||||||
* Intel Bluetooth firmware (0:/os/firmware/intel), Copyright (c) Intel
|
* Intel Bluetooth firmware (0:/os/firmware/intel), Copyright (c) Intel
|
||||||
|
|||||||
@@ -295,11 +295,11 @@ gen-syscalls:
|
|||||||
# check-syscalls guards against shipping a stale C API header into the ramdisk.
|
# check-syscalls guards against shipping a stale C API header into the ramdisk.
|
||||||
tcc: libc check-syscalls
|
tcc: libc check-syscalls
|
||||||
$(MAKE) -C src/tcc
|
$(MAKE) -C src/tcc
|
||||||
mkdir -p $(BINDIR)/lib/tcc/include
|
mkdir -p $(BINDIR)/sdk/tcc/include
|
||||||
cp -r src/tcc/tcc_include/* $(BINDIR)/lib/tcc/include/
|
cp -r src/tcc/tcc_include/* $(BINDIR)/sdk/tcc/include/
|
||||||
cp include/libc/*.h $(BINDIR)/lib/tcc/include/
|
cp include/libc/*.h $(BINDIR)/sdk/tcc/include/
|
||||||
mkdir -p $(BINDIR)/lib/tcc/include/sys
|
mkdir -p $(BINDIR)/sdk/tcc/include/sys
|
||||||
cp include/libc/sys/*.h $(BINDIR)/lib/tcc/include/sys/
|
cp include/libc/sys/*.h $(BINDIR)/sdk/tcc/include/sys/
|
||||||
|
|
||||||
# Build Lua interpreter and static library (depends on libc).
|
# Build Lua interpreter and static library (depends on libc).
|
||||||
lua: libc
|
lua: libc
|
||||||
@@ -388,7 +388,10 @@ DEVKIT_TOOLS := as ld ar nm objcopy objdump readelf ranlib strip size strings ad
|
|||||||
.PHONY: devkit
|
.PHONY: devkit
|
||||||
devkit: libc
|
devkit: libc
|
||||||
ifneq ($(wildcard $(NATIVE_BIN)/as),)
|
ifneq ($(wildcard $(NATIVE_BIN)/as),)
|
||||||
rm -rf $(BINDIR)/sdk
|
# Clean only the devkit-owned subtrees; sdk/tcc and sdk/lua are
|
||||||
|
# installed into bin/sdk/ by their own targets.
|
||||||
|
rm -rf $(BINDIR)/sdk/bin $(BINDIR)/sdk/lib $(BINDIR)/sdk/include \
|
||||||
|
$(BINDIR)/sdk/libexec $(BINDIR)/sdk/x86_64-montauk
|
||||||
mkdir -p $(BINDIR)/sdk/bin $(BINDIR)/sdk/lib $(BINDIR)/sdk/include
|
mkdir -p $(BINDIR)/sdk/bin $(BINDIR)/sdk/lib $(BINDIR)/sdk/include
|
||||||
for t in $(DEVKIT_TOOLS); do \
|
for t in $(DEVKIT_TOOLS); do \
|
||||||
cp $(NATIVE_BIN)/$$t $(BINDIR)/sdk/bin/$$t.elf || exit 1; \
|
cp $(NATIVE_BIN)/$$t $(BINDIR)/sdk/bin/$$t.elf || exit 1; \
|
||||||
|
|||||||
@@ -942,6 +942,217 @@ inline void draw_radio(Canvas& c,
|
|||||||
label, theme.text);
|
label, theme.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Checkboxes and disclosure arrows
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
enum CheckState : uint8_t {
|
||||||
|
CHECK_OFF = 0,
|
||||||
|
CHECK_ON,
|
||||||
|
CHECK_MIXED,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline CheckState check_state(bool checked) {
|
||||||
|
return checked ? CHECK_ON : CHECK_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect checkbox_indicator_rect(const Rect& option, int size = 16) {
|
||||||
|
return {option.x, option.y + (option.h - size) / 2, size, size};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coverage-based pixel blend used by the anti-aliased checkbox drawing.
|
||||||
|
// `cov` is 0..255; the destination is treated as opaque.
|
||||||
|
inline void checkbox_blend_px(Canvas& c, int x, int y, Color color, int cov) {
|
||||||
|
if (x < 0 || x >= c.w || y < 0 || y >= c.h || cov <= 0) return;
|
||||||
|
if (cov > 255) cov = 255;
|
||||||
|
uint32_t dst = c.pixels[y * c.w + x];
|
||||||
|
uint32_t dr = (dst >> 16) & 0xFF, dg = (dst >> 8) & 0xFF, db = dst & 0xFF;
|
||||||
|
uint32_t a = (uint32_t)cov, ia = 255 - a;
|
||||||
|
uint32_t nr = (color.r * a + dr * ia + 127) / 255;
|
||||||
|
uint32_t ng = (color.g * a + dg * ia + 127) / 255;
|
||||||
|
uint32_t nb = (color.b * a + db * ia + 127) / 255;
|
||||||
|
c.pixels[y * c.w + x] = 0xFF000000u | (nr << 16) | (ng << 8) | nb;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point (px, py) inside a rounded rect of size w x h with corner radius r,
|
||||||
|
// evaluated in the rect's local coordinate space.
|
||||||
|
inline bool round_rect_contains(double px, double py, double w, double h, double r) {
|
||||||
|
if (px < 0 || py < 0 || px > w || py > h) return false;
|
||||||
|
double rx = 0, ry = 0;
|
||||||
|
bool corner = false;
|
||||||
|
if (px < r && py < r) { rx = r - px; ry = r - py; corner = true; }
|
||||||
|
else if (px > w - r && py < r) { rx = px - (w - r); ry = r - py; corner = true; }
|
||||||
|
else if (px < r && py > h - r) { rx = r - px; ry = py - (h - r); corner = true; }
|
||||||
|
else if (px > w - r && py > h - r) { rx = px - (w - r); ry = py - (h - r); corner = true; }
|
||||||
|
if (!corner) return true;
|
||||||
|
return rx * rx + ry * ry <= r * r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anti-aliased rounded-rect fill via 4x4 supersampling. Kept local to the
|
||||||
|
// checkbox so the rest of the toolkit's crisp fills are unchanged.
|
||||||
|
inline void checkbox_fill_round_aa(Canvas& c, const Rect& box, double radius, Color color) {
|
||||||
|
if (box.w <= 0 || box.h <= 0) return;
|
||||||
|
if (radius < 0) radius = 0;
|
||||||
|
const int S = 4;
|
||||||
|
for (int row = -1; row <= box.h; row++) {
|
||||||
|
int py = box.y + row;
|
||||||
|
if (py < 0 || py >= c.h) continue;
|
||||||
|
for (int col = -1; col <= box.w; col++) {
|
||||||
|
int px = box.x + col;
|
||||||
|
if (px < 0 || px >= c.w) continue;
|
||||||
|
int hits = 0;
|
||||||
|
for (int sy = 0; sy < S; sy++)
|
||||||
|
for (int sx = 0; sx < S; sx++) {
|
||||||
|
double fx = col + (sx + 0.5) / S;
|
||||||
|
double fy = row + (sy + 0.5) / S;
|
||||||
|
if (round_rect_contains(fx, fy, box.w, box.h, radius)) hits++;
|
||||||
|
}
|
||||||
|
if (hits) checkbox_blend_px(c, px, py, color, hits * 255 / (S * S));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Squared distance from a point to a line segment (no sqrt; used for the
|
||||||
|
// thickness test of the checkmark strokes).
|
||||||
|
inline double checkbox_seg_dist2(double px, double py,
|
||||||
|
double ax, double ay, double bx, double by) {
|
||||||
|
double dx = bx - ax, dy = by - ay;
|
||||||
|
double len2 = dx * dx + dy * dy;
|
||||||
|
double t = len2 > 0 ? ((px - ax) * dx + (py - ay) * dy) / len2 : 0.0;
|
||||||
|
if (t < 0) t = 0; else if (t > 1) t = 1;
|
||||||
|
double ex = px - (ax + t * dx), ey = py - (ay + t * dy);
|
||||||
|
return ex * ex + ey * ey;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anti-aliased two-stroke checkmark via 4x4 supersampling of a fixed-width
|
||||||
|
// polyline.
|
||||||
|
inline void draw_check_mark(Canvas& c, const Rect& box, Color color) {
|
||||||
|
double ax = box.x + box.w * 0.24, ay = box.y + box.h * 0.50;
|
||||||
|
double bx = box.x + box.w * 0.42, by = box.y + box.h * 0.68;
|
||||||
|
double cx = box.x + box.w * 0.74, cy = box.y + box.h * 0.26;
|
||||||
|
double hw = box.w * 0.085 + 0.35; // half stroke width
|
||||||
|
double hw2 = hw * hw;
|
||||||
|
const int S = 4;
|
||||||
|
for (int py = box.y - 1; py <= box.y + box.h + 1; py++) {
|
||||||
|
if (py < 0 || py >= c.h) continue;
|
||||||
|
for (int px = box.x - 1; px <= box.x + box.w + 1; px++) {
|
||||||
|
if (px < 0 || px >= c.w) continue;
|
||||||
|
int hits = 0;
|
||||||
|
for (int sy = 0; sy < S; sy++)
|
||||||
|
for (int sx = 0; sx < S; sx++) {
|
||||||
|
double fx = px + (sx + 0.5) / S;
|
||||||
|
double fy = py + (sy + 0.5) / S;
|
||||||
|
double d1 = checkbox_seg_dist2(fx, fy, ax, ay, bx, by);
|
||||||
|
double d2 = checkbox_seg_dist2(fx, fy, bx, by, cx, cy);
|
||||||
|
if ((d1 < d2 ? d1 : d2) <= hw2) hits++;
|
||||||
|
}
|
||||||
|
if (hits) checkbox_blend_px(c, px, py, color, hits * 255 / (S * S));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void draw_checkbox(Canvas& c,
|
||||||
|
const Rect& option,
|
||||||
|
const char* label,
|
||||||
|
CheckState state,
|
||||||
|
const Theme& theme,
|
||||||
|
bool enabled = true,
|
||||||
|
bool hovered = false) {
|
||||||
|
Rect box = checkbox_indicator_rect(option);
|
||||||
|
double radius = box.w * 0.22;
|
||||||
|
|
||||||
|
Color fill, border, mark;
|
||||||
|
if (!enabled) {
|
||||||
|
fill = theme.disabled_bg;
|
||||||
|
border = theme.disabled_bg;
|
||||||
|
mark = theme.disabled_fg;
|
||||||
|
} else if (state == CHECK_OFF) {
|
||||||
|
fill = colors::WHITE;
|
||||||
|
border = hovered ? theme.accent : theme.border;
|
||||||
|
mark = theme.accent_fg;
|
||||||
|
} else {
|
||||||
|
fill = hovered ? theme.accent_hover : theme.accent;
|
||||||
|
border = fill;
|
||||||
|
mark = theme.accent_fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == CHECK_OFF) {
|
||||||
|
// Colored ring with a white interior.
|
||||||
|
checkbox_fill_round_aa(c, box, radius, border);
|
||||||
|
Rect inner = {box.x + 1, box.y + 1, box.w - 2, box.h - 2};
|
||||||
|
checkbox_fill_round_aa(c, inner, radius - 1, fill);
|
||||||
|
} else {
|
||||||
|
checkbox_fill_round_aa(c, box, radius, fill);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == CHECK_ON) {
|
||||||
|
draw_check_mark(c, box, mark);
|
||||||
|
} else if (state == CHECK_MIXED) {
|
||||||
|
Rect dash = {box.x + 4, box.y + box.h / 2 - 1, box.w - 8, 2};
|
||||||
|
checkbox_fill_round_aa(c, dash, 1, mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label && label[0]) {
|
||||||
|
int fh = system_font_height();
|
||||||
|
c.text(box.x + box.w + theme.gap_sm,
|
||||||
|
option.y + (option.h - fh) / 2,
|
||||||
|
label, enabled ? theme.text : theme.text_muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect disclosure_rect(const Rect& option, int size = 16) {
|
||||||
|
return {option.x, option.y + (option.h - size) / 2, size, size};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void draw_disclosure(Canvas& c,
|
||||||
|
const Rect& box,
|
||||||
|
bool expanded,
|
||||||
|
const Theme& theme,
|
||||||
|
bool hovered = false) {
|
||||||
|
Color color = hovered ? theme.text : theme.text_subtle;
|
||||||
|
double cx = box.x + box.w / 2.0;
|
||||||
|
double cy = box.y + box.h / 2.0;
|
||||||
|
double s = box.w * 0.26;
|
||||||
|
if (s < 3) s = 3;
|
||||||
|
|
||||||
|
// Triangle vertices (equilateral-ish), rotated per state.
|
||||||
|
double vx[3], vy[3];
|
||||||
|
if (expanded) { // pointing down
|
||||||
|
vx[0] = cx - s; vy[0] = cy - s * 0.6;
|
||||||
|
vx[1] = cx + s; vy[1] = cy - s * 0.6;
|
||||||
|
vx[2] = cx; vy[2] = cy + s * 0.75;
|
||||||
|
} else { // pointing right
|
||||||
|
vx[0] = cx - s * 0.6; vy[0] = cy - s;
|
||||||
|
vx[1] = cx - s * 0.6; vy[1] = cy + s;
|
||||||
|
vx[2] = cx + s * 0.75; vy[2] = cy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anti-aliased fill via 4x4 supersampling of the triangle's half-plane test.
|
||||||
|
auto edge = [](double ax, double ay, double bx, double by, double px, double py) {
|
||||||
|
return (px - ax) * (by - ay) - (py - ay) * (bx - ax);
|
||||||
|
};
|
||||||
|
const int S = 4;
|
||||||
|
for (int py = box.y; py < box.y + box.h; py++) {
|
||||||
|
if (py < 0 || py >= c.h) continue;
|
||||||
|
for (int px = box.x; px < box.x + box.w; px++) {
|
||||||
|
if (px < 0 || px >= c.w) continue;
|
||||||
|
int hits = 0;
|
||||||
|
for (int sy = 0; sy < S; sy++)
|
||||||
|
for (int sx = 0; sx < S; sx++) {
|
||||||
|
double fx = px + (sx + 0.5) / S;
|
||||||
|
double fy = py + (sy + 0.5) / S;
|
||||||
|
double e0 = edge(vx[0], vy[0], vx[1], vy[1], fx, fy);
|
||||||
|
double e1 = edge(vx[1], vy[1], vx[2], vy[2], fx, fy);
|
||||||
|
double e2 = edge(vx[2], vy[2], vx[0], vy[0], fx, fy);
|
||||||
|
bool inside = (e0 >= 0 && e1 >= 0 && e2 >= 0) ||
|
||||||
|
(e0 <= 0 && e1 <= 0 && e2 <= 0);
|
||||||
|
if (inside) hits++;
|
||||||
|
}
|
||||||
|
if (hits) checkbox_blend_px(c, px, py, color, hits * 255 / (S * S));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline Rect swatch_rect(int row_x, int row_y, int index, int size = 24, int gap = 6) {
|
inline Rect swatch_rect(int row_x, int row_y, int index, int size = 24, int gap = 6) {
|
||||||
return {row_x + index * (size + gap), row_y, size, size};
|
return {row_x + index * (size + gap), row_y, size, size};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,13 +52,14 @@
|
|||||||
0:/icons/ Shared icons
|
0:/icons/ Shared icons
|
||||||
0:/man/ Manual pages
|
0:/man/ Manual pages
|
||||||
0:/www/ Web server content
|
0:/www/ Web server content
|
||||||
0:/lib/ Lua and TinyCC toolchain payloads
|
0:/sdk/ Montauk SDK (native gcc/binutils, TinyCC,
|
||||||
|
Lua, headers and libraries), when installed
|
||||||
0:/boot/ Kernel, bootloader, ramdisk image
|
0:/boot/ Kernel, bootloader, ramdisk image
|
||||||
|
|
||||||
There is no 0:/games/, 0:/common/, 0:/home/, or 0:/etc/ --
|
There is no 0:/games/, 0:/common/, 0:/home/, or 0:/etc/ --
|
||||||
these were used by earlier single-user releases and no longer
|
these were used by earlier single-user releases and no longer
|
||||||
exist. Games and other GUI programs (including doom) ship as
|
exist. 0:/lib/ was merged into 0:/sdk/. Games and other GUI
|
||||||
bundles under 0:/apps/.
|
programs (including doom) ship as bundles under 0:/apps/.
|
||||||
|
|
||||||
.SH SHELL
|
.SH SHELL
|
||||||
The interactive shell is the primary way to interact with
|
The interactive shell is the primary way to interact with
|
||||||
|
|||||||
@@ -46,9 +46,9 @@
|
|||||||
* Roboto font, Copyright The Roboto Project Authors - OFL-1.1
|
* Roboto font, Copyright The Roboto Project Authors - OFL-1.1
|
||||||
* C059 font (URW Base 35), Copyright (C) (URW)++ Design and Development
|
* C059 font (URW Base 35), Copyright (C) (URW)++ Design and Development
|
||||||
GmbH - AGPLv3 with font-embedding exception
|
GmbH - AGPLv3 with font-embedding exception
|
||||||
* Tiny C Compiler (tcc.elf, 0:/lib/tcc), Copyright (c) Fabrice Bellard and
|
* Tiny C Compiler (tcc.elf, 0:/sdk/tcc), Copyright (c) Fabrice Bellard and
|
||||||
contributors - LGPL-2.1
|
contributors - LGPL-2.1
|
||||||
* Lua (lua.elf, 0:/lib/lua), Copyright (C) Lua.org, PUC-Rio - MIT
|
* Lua (lua.elf, 0:/sdk/lua), Copyright (C) Lua.org, PUC-Rio - MIT
|
||||||
* Mozilla CA certificate bundle (0:/os/certs), Mozilla CA Certificate
|
* Mozilla CA certificate bundle (0:/os/certs), Mozilla CA Certificate
|
||||||
Program - MPL-2.0
|
Program - MPL-2.0
|
||||||
* Intel Bluetooth firmware (0:/os/firmware/intel), Copyright (c) Intel
|
* Intel Bluetooth firmware (0:/os/firmware/intel), Copyright (c) Intel
|
||||||
|
|||||||
@@ -124,6 +124,13 @@ static void log_err(const char* msg) { log(LOG_ERR, msg); }
|
|||||||
|
|
||||||
// ---- Service runner ----
|
// ---- Service runner ----
|
||||||
|
|
||||||
|
static bool service_installed(const char* path) {
|
||||||
|
int fd = montauk::open(path);
|
||||||
|
if (fd < 0) return false;
|
||||||
|
montauk::close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool run_service(const char* path, const char* name, bool wait = true) {
|
static bool run_service(const char* path, const char* name, bool wait = true) {
|
||||||
char msg[128];
|
char msg[128];
|
||||||
|
|
||||||
@@ -156,7 +163,12 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
// ---- Stage 1: Network configuration (non-blocking) ----
|
// ---- Stage 1: Network configuration (non-blocking) ----
|
||||||
run_service("0:/os/dhcp.elf", "dhcp", false);
|
run_service("0:/os/dhcp.elf", "dhcp", false);
|
||||||
|
|
||||||
|
// Printing is an optional install component; skip quietly when absent.
|
||||||
|
if (service_installed("0:/os/printd.elf"))
|
||||||
run_service("0:/os/printd.elf", "print spooler", false);
|
run_service("0:/os/printd.elf", "print spooler", false);
|
||||||
|
else
|
||||||
|
log_info("Print spooler not installed, skipping");
|
||||||
|
|
||||||
// ---- Stage 2: Login screen -> desktop (falls back to desktop, then shell) ----
|
// ---- Stage 2: Login screen -> desktop (falls back to desktop, then shell) ----
|
||||||
if (!run_service("0:/os/login.elf", "login")) {
|
if (!run_service("0:/os/login.elf", "login")) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ LDFLAGS := \
|
|||||||
|
|
||||||
# ---- Source files ----
|
# ---- Source files ----
|
||||||
|
|
||||||
SRCS := main.cpp render.cpp actions.cpp stb_truetype_impl.cpp
|
SRCS := main.cpp render.cpp actions.cpp components.cpp stb_truetype_impl.cpp
|
||||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||||
|
|
||||||
# ---- Target ----
|
# ---- Target ----
|
||||||
|
|||||||
@@ -81,6 +81,96 @@ static void path_join(char* out, int outsize, const char* dir, const char* name)
|
|||||||
out[i] = '\0';
|
out[i] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* rel_basename(const char* rel) {
|
||||||
|
const char* base = rel;
|
||||||
|
for (int i = 0; rel[i]; i++)
|
||||||
|
if (rel[i] == '/') base = rel + i + 1;
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Filesystem probes and heap-backed directory listing
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
bool installer_path_exists(const char* abs_path) {
|
||||||
|
int fd = montauk::open(abs_path);
|
||||||
|
if (fd >= 0) {
|
||||||
|
montauk::close(fd);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const char* names[4];
|
||||||
|
return montauk::readdir_at(abs_path, names, 4, 0) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void installer_free_dir_list(DirList* list) {
|
||||||
|
if (!list) return;
|
||||||
|
for (int i = 0; i < list->count; i++)
|
||||||
|
montauk::mfree(list->entries[i].rel);
|
||||||
|
montauk::mfree(list->entries);
|
||||||
|
list->entries = nullptr;
|
||||||
|
list->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The kernel serves readdir strings from a small ring of scratch pages, so
|
||||||
|
// nested listings invalidate earlier results. Copy each batch to the heap
|
||||||
|
// and page with readdir_at so directories of any size enumerate fully.
|
||||||
|
bool installer_list_dir(const char* abs_dir, DirList* out) {
|
||||||
|
out->entries = nullptr;
|
||||||
|
out->count = 0;
|
||||||
|
|
||||||
|
static constexpr int BATCH = 128;
|
||||||
|
const char* batch[BATCH];
|
||||||
|
|
||||||
|
int scanned = 0;
|
||||||
|
int capacity = 0;
|
||||||
|
|
||||||
|
int got = montauk::readdir_at(abs_dir, batch, BATCH, 0);
|
||||||
|
if (got < 0) return false;
|
||||||
|
|
||||||
|
while (got > 0) {
|
||||||
|
for (int i = 0; i < got; i++) {
|
||||||
|
const char* raw = batch[i];
|
||||||
|
int len = slen(raw);
|
||||||
|
|
||||||
|
bool is_dir = (len > 0 && raw[len - 1] == '/');
|
||||||
|
if (is_dir) len--;
|
||||||
|
if (len <= 0) continue;
|
||||||
|
|
||||||
|
// Skip "." and ".."
|
||||||
|
const char* base = raw;
|
||||||
|
for (int k = 0; k < len; k++)
|
||||||
|
if (raw[k] == '/') base = raw + k + 1;
|
||||||
|
int base_len = (int)(len - (base - raw));
|
||||||
|
if (base_len == 1 && base[0] == '.') continue;
|
||||||
|
if (base_len == 2 && base[0] == '.' && base[1] == '.') continue;
|
||||||
|
|
||||||
|
if (out->count >= capacity) {
|
||||||
|
int new_cap = capacity ? capacity * 2 : 64;
|
||||||
|
DirEntry* grown = (DirEntry*)montauk::malloc(new_cap * sizeof(DirEntry));
|
||||||
|
if (!grown) { installer_free_dir_list(out); return false; }
|
||||||
|
for (int k = 0; k < out->count; k++) grown[k] = out->entries[k];
|
||||||
|
montauk::mfree(out->entries);
|
||||||
|
out->entries = grown;
|
||||||
|
capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* rel = (char*)montauk::malloc(len + 1);
|
||||||
|
if (!rel) { installer_free_dir_list(out); return false; }
|
||||||
|
montauk::memcpy(rel, raw, len);
|
||||||
|
rel[len] = '\0';
|
||||||
|
|
||||||
|
out->entries[out->count].rel = rel;
|
||||||
|
out->entries[out->count].is_dir = is_dir;
|
||||||
|
out->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
scanned += got;
|
||||||
|
got = montauk::readdir_at(abs_dir, batch, BATCH, scanned);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Copy a single file from src_path to dst_path
|
// Copy a single file from src_path to dst_path
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -159,113 +249,78 @@ static bool copy_file(const char* src_path, const char* dst_path) {
|
|||||||
static int g_files_copied;
|
static int g_files_copied;
|
||||||
static int g_dirs_created;
|
static int g_dirs_created;
|
||||||
|
|
||||||
// Copy all entries from src_dir (on ramdisk) to dst_dir (on target drive).
|
// Copy all entries from src_dir (on ramdisk) to dst_dir (on target drive),
|
||||||
|
// honoring the component selection: paths owned by an unchecked component
|
||||||
|
// are skipped, and directories are still entered when a checked component
|
||||||
|
// lives deeper inside (e.g. sdk/tcc with the rest of sdk deselected).
|
||||||
// If skip_toplevel is non-null, skip that directory name at the top level.
|
// If skip_toplevel is non-null, skip that directory name at the top level.
|
||||||
static bool copy_recursive(const char* src_dir, const char* dst_dir,
|
static bool copy_recursive(const char* src_dir, const char* dst_dir,
|
||||||
const char* skip_toplevel = nullptr) {
|
const char* skip_toplevel = nullptr) {
|
||||||
const char* names[256];
|
DirList list;
|
||||||
int count = montauk::readdir(src_dir, names, 256);
|
if (!installer_list_dir(src_dir, &list))
|
||||||
if (count < 0) return true; // not a directory or empty, skip
|
return true; // not a directory, skip
|
||||||
|
|
||||||
// Ramdisk readdir returns names relative to the drive root with the
|
bool ok = true;
|
||||||
// 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++) {
|
for (int i = 0; i < list.count && ok; i++) {
|
||||||
const char* raw_name = names[i];
|
const char* rel = list.entries[i].rel;
|
||||||
|
const char* base = rel_basename(rel);
|
||||||
|
|
||||||
// Strip prefix to get basename
|
char src_path[256];
|
||||||
const char* basename = raw_name;
|
snprintf(src_path, sizeof(src_path), "0:/%s", rel);
|
||||||
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';
|
|
||||||
|
|
||||||
|
if (list.entries[i].is_dir) {
|
||||||
// Skip the installer app — no need on the installed system
|
// Skip the installer app — no need on the installed system
|
||||||
if (strcmp(dir_name, "installer") == 0 &&
|
if (strcmp(rel, "apps/installer") == 0) continue;
|
||||||
strcmp(src_local, "apps") == 0) continue;
|
|
||||||
|
|
||||||
// Skip requested top-level directory
|
// Skip requested top-level directory
|
||||||
if (skip_toplevel && strcmp(dir_name, skip_toplevel) == 0) continue;
|
if (skip_toplevel && strcmp(base, skip_toplevel) == 0) continue;
|
||||||
|
|
||||||
|
bool allowed = component_allows(rel);
|
||||||
|
if (!allowed && !component_subtree_needed(rel)) continue;
|
||||||
|
|
||||||
// Create directory on target
|
|
||||||
char target_path[256];
|
char target_path[256];
|
||||||
path_join(target_path, sizeof(target_path), dst_dir, dir_name);
|
path_join(target_path, sizeof(target_path), dst_dir, base);
|
||||||
|
|
||||||
montauk::fmkdir(target_path);
|
montauk::fmkdir(target_path);
|
||||||
g_dirs_created++;
|
g_dirs_created++;
|
||||||
|
|
||||||
char log_msg[64];
|
char log_msg[64];
|
||||||
snprintf(log_msg, sizeof(log_msg), " mkdir %s", dir_name);
|
snprintf(log_msg, sizeof(log_msg), " mkdir %s", base);
|
||||||
add_log(log_msg);
|
add_log(log_msg);
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
// Recurse into this directory
|
ok = copy_recursive(src_path, target_path);
|
||||||
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 {
|
} else {
|
||||||
// Skip ramdisk and limine.conf — installed system boots from
|
// Skip ramdisk and limine.conf — installed system boots from
|
||||||
// disk and gets a fresh config without the ramdisk module.
|
// disk and gets a fresh config without the ramdisk module.
|
||||||
// Skip setup.toml — live/setup environment config that should
|
// Skip setup.toml — live/setup environment config that should
|
||||||
// not be present on the installed system.
|
// not be present on the installed system.
|
||||||
if (strcmp(basename, "ramdisk.tar") == 0) continue;
|
if (strcmp(base, "ramdisk.tar") == 0) continue;
|
||||||
if (strcmp(basename, "limine.conf") == 0) continue;
|
if (strcmp(base, "limine.conf") == 0) continue;
|
||||||
if (strcmp(basename, "setup.toml") == 0) continue;
|
if (strcmp(base, "setup.toml") == 0) continue;
|
||||||
|
|
||||||
// It's a file — copy it
|
if (!component_allows(rel)) continue;
|
||||||
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];
|
char dst_path[256];
|
||||||
path_join(dst_path, sizeof(dst_path), dst_dir, basename);
|
path_join(dst_path, sizeof(dst_path), dst_dir, base);
|
||||||
|
|
||||||
char log_msg[64];
|
char log_msg[64];
|
||||||
snprintf(log_msg, sizeof(log_msg), " copy %s", basename);
|
snprintf(log_msg, sizeof(log_msg), " copy %s", base);
|
||||||
add_log(log_msg);
|
add_log(log_msg);
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
if (!copy_file(src_path, dst_path))
|
if (!copy_file(src_path, dst_path)) {
|
||||||
return false;
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
g_files_copied++;
|
g_files_copied++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
installer_free_dir_list(&list);
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -412,8 +467,7 @@ static void install_efi_ext2(int disk) {
|
|||||||
add_log(" Mounted");
|
add_log(" Mounted");
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
char efi_root[8], ext2_root[8];
|
char ext2_root[8];
|
||||||
snprintf(efi_root, sizeof(efi_root), "%d:/", efi_drive);
|
|
||||||
snprintf(ext2_root, sizeof(ext2_root), "%d:/", root_drive);
|
snprintf(ext2_root, sizeof(ext2_root), "%d:/", root_drive);
|
||||||
|
|
||||||
// Step 8: Create EFI boot directory structure
|
// Step 8: Create EFI boot directory structure
|
||||||
@@ -705,13 +759,14 @@ void do_update() {
|
|||||||
add_log("ERROR: Mount failed");
|
add_log("ERROR: Mount failed");
|
||||||
flush_ui(); st.step = STEP_ERROR; return;
|
flush_ui(); st.step = STEP_ERROR; return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char drive_root[8];
|
|
||||||
snprintf(drive_root, sizeof(drive_root), "%d:/", drive_num);
|
|
||||||
add_log(" Mounted");
|
add_log(" Mounted");
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
// Step 2: Update os/ — remove old and copy fresh
|
// Step 2: Detect which optional components the target has, so the
|
||||||
|
// update refreshes those without re-adding deselected ones.
|
||||||
|
components_set_from_target(drive_num);
|
||||||
|
|
||||||
|
// Step 3: Update os/ — overwrite existing and add new
|
||||||
add_log("Updating os/...");
|
add_log("Updating os/...");
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
@@ -724,7 +779,7 @@ void do_update() {
|
|||||||
flush_ui(); st.step = STEP_ERROR; return;
|
flush_ui(); st.step = STEP_ERROR; return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 3: Update apps/ — copy all, overwriting existing and adding new
|
// Step 4: Update apps/ — copy all, overwriting existing and adding new
|
||||||
add_log("Updating apps/...");
|
add_log("Updating apps/...");
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
@@ -736,17 +791,19 @@ void do_update() {
|
|||||||
flush_ui(); st.step = STEP_ERROR; return;
|
flush_ui(); st.step = STEP_ERROR; return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: Update lib/ — compiler runtime, headers, libc
|
// Step 5: Update sdk/ — dev tools, headers, tcc, lua
|
||||||
add_log("Updating lib/...");
|
if (component_allows("sdk") || component_subtree_needed("sdk")) {
|
||||||
|
add_log("Updating sdk/...");
|
||||||
flush_ui();
|
flush_ui();
|
||||||
|
|
||||||
snprintf(path_buf, sizeof(path_buf), "%d:/lib", drive_num);
|
snprintf(path_buf, sizeof(path_buf), "%d:/sdk", drive_num);
|
||||||
montauk::fmkdir(path_buf);
|
montauk::fmkdir(path_buf);
|
||||||
|
|
||||||
if (!copy_recursive("0:/lib", path_buf)) {
|
if (!copy_recursive("0:/sdk", path_buf)) {
|
||||||
add_log("ERROR: Failed to update lib/");
|
add_log("ERROR: Failed to update sdk/");
|
||||||
flush_ui(); st.step = STEP_ERROR; return;
|
flush_ui(); st.step = STEP_ERROR; return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
char copy_msg[64];
|
char copy_msg[64];
|
||||||
snprintf(copy_msg, sizeof(copy_msg), " %d files, %d directories updated",
|
snprintf(copy_msg, sizeof(copy_msg), " %d files, %d directories updated",
|
||||||
|
|||||||
@@ -0,0 +1,439 @@
|
|||||||
|
/*
|
||||||
|
* components.cpp
|
||||||
|
* MontaukOS Installer — optional component selection
|
||||||
|
* Copyright (c) 2026 Daniel Hammer
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "installer.h"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Component table
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
enum ComponentIndex {
|
||||||
|
CI_SDK = 0,
|
||||||
|
CI_GCC,
|
||||||
|
CI_BINUTILS,
|
||||||
|
CI_TCC,
|
||||||
|
CI_LUA,
|
||||||
|
CI_GAMES,
|
||||||
|
CI_DOOM,
|
||||||
|
CI_2048,
|
||||||
|
CI_RPGDEMO,
|
||||||
|
CI_MANDELBROT,
|
||||||
|
CI_OFFICE,
|
||||||
|
CI_WORDPROCESSOR,
|
||||||
|
CI_SPREADSHEET,
|
||||||
|
CI_PDFVIEWER,
|
||||||
|
CI_INTERNET,
|
||||||
|
CI_WIKIPEDIA,
|
||||||
|
CI_WEATHER,
|
||||||
|
CI_IRC,
|
||||||
|
CI_PRINTING,
|
||||||
|
CI_HTTPD,
|
||||||
|
CI_SDR,
|
||||||
|
CI_COUNT,
|
||||||
|
};
|
||||||
|
|
||||||
|
Component g_components[] = {
|
||||||
|
{ "sdk", "Montauk SDK",
|
||||||
|
"Native development kit under 0:/sdk: headers, libc, libstdc++ and tools",
|
||||||
|
-1, "sdk/include", {"sdk", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "gcc", "GNU C/C++ compiler (gcc, g++)",
|
||||||
|
"Native GCC 14 with C and C++ support (needs binutils)",
|
||||||
|
CI_SDK, "sdk/bin/gcc.elf",
|
||||||
|
{"sdk/bin/gcc.elf", "sdk/bin/g++.elf", "sdk/bin/cpp.elf",
|
||||||
|
"sdk/libexec", "sdk/lib/gcc", nullptr},
|
||||||
|
true, CI_BINUTILS,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "binutils", "GNU binutils (as, ld, ar, ...)",
|
||||||
|
"Assembler, linker and object file tools",
|
||||||
|
CI_SDK, "sdk/bin/as.elf",
|
||||||
|
{"sdk/bin/as.elf", "sdk/bin/ld.elf", "sdk/bin/ar.elf", "sdk/bin/nm.elf",
|
||||||
|
"sdk/bin/objcopy.elf", "sdk/bin/objdump.elf", "sdk/bin/readelf.elf",
|
||||||
|
"sdk/bin/ranlib.elf", "sdk/bin/strip.elf", "sdk/bin/size.elf",
|
||||||
|
"sdk/bin/strings.elf", "sdk/bin/addr2line.elf", "sdk/bin/c++filt.elf",
|
||||||
|
"sdk/bin/elfedit.elf", nullptr},
|
||||||
|
true, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "tcc", "Tiny C Compiler",
|
||||||
|
"Lightweight single-pass C compiler (tcc) with its own headers",
|
||||||
|
CI_SDK, "sdk/tcc", {"sdk/tcc", "os/tcc.elf", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "lua", "Lua 5.4",
|
||||||
|
"Lua interpreter (lua) with static library and headers",
|
||||||
|
CI_SDK, "sdk/lua", {"sdk/lua", "os/lua.elf", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "games", "Games & demos",
|
||||||
|
"Games and graphics demos",
|
||||||
|
-1, nullptr, {nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "doom", "DOOM",
|
||||||
|
"id Software's DOOM on the doomgeneric engine",
|
||||||
|
CI_GAMES, "apps/doom", {"apps/doom", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "2048", "2048",
|
||||||
|
"Sliding tile puzzle game",
|
||||||
|
CI_GAMES, "apps/2048", {"apps/2048", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "rpgdemo", "RPG demo",
|
||||||
|
"Top-down RPG game engine demo",
|
||||||
|
CI_GAMES, "apps/rpgdemo", {"apps/rpgdemo", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "mandelbrot", "Mandelbrot",
|
||||||
|
"Fractal explorer",
|
||||||
|
CI_GAMES, "apps/mandelbrot", {"apps/mandelbrot", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "office", "Office & productivity",
|
||||||
|
"Document, spreadsheet and PDF applications",
|
||||||
|
-1, nullptr, {nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "wordprocessor", "Word Processor",
|
||||||
|
"Rich text documents with printing support",
|
||||||
|
CI_OFFICE, "apps/wordprocessor", {"apps/wordprocessor", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "spreadsheet", "Spreadsheet",
|
||||||
|
"Formula-based spreadsheet",
|
||||||
|
CI_OFFICE, "apps/spreadsheet", {"apps/spreadsheet", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "pdfviewer", "PDF Viewer",
|
||||||
|
"PDF document viewer",
|
||||||
|
CI_OFFICE, "apps/pdfviewer", {"apps/pdfviewer", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "internet", "Internet apps",
|
||||||
|
"Optional network applications",
|
||||||
|
-1, nullptr, {nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "wikipedia", "Wikipedia",
|
||||||
|
"Wikipedia reader (GUI app and wiki CLI)",
|
||||||
|
CI_INTERNET, "apps/wikipedia", {"apps/wikipedia", "os/wiki.elf", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "weather", "Weather",
|
||||||
|
"Weather forecast app",
|
||||||
|
CI_INTERNET, "apps/weather", {"apps/weather", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "irc", "IRC client",
|
||||||
|
"Command-line IRC client",
|
||||||
|
CI_INTERNET, "os/irc.elf", {"os/irc.elf", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "printing", "Printing support",
|
||||||
|
"Print spooler daemon (printd), printer setup app and print tools",
|
||||||
|
-1, "os/printd.elf",
|
||||||
|
{"os/printd.elf", "os/printctl.elf", "os/printersapplet.lib",
|
||||||
|
"apps/printers", nullptr},
|
||||||
|
false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "httpd", "HTTP server (experimental)",
|
||||||
|
"Experimental web server (httpd) with 0:/www content",
|
||||||
|
-1, "os/httpd.elf", {"os/httpd.elf", "www", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
|
||||||
|
{ "sdr", "SDR tools (experimental)",
|
||||||
|
"RTL-SDR software defined radio demo (sdr CLI)",
|
||||||
|
-1, "os/sdr.elf", {"os/sdr.elf", nullptr}, false, -1,
|
||||||
|
false, false, false, false, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
const int g_component_count = CI_COUNT;
|
||||||
|
|
||||||
|
static bool g_sizes_ready = false;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static bool comp_is_group(int idx) {
|
||||||
|
return g_components[idx].paths[0] == nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool component_has_children(int idx) {
|
||||||
|
for (int i = 0; i < g_component_count; i++)
|
||||||
|
if (g_components[i].parent == idx) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// True when rel equals prefix or lies inside the prefix directory.
|
||||||
|
static bool path_under(const char* prefix, const char* rel) {
|
||||||
|
int pl = 0;
|
||||||
|
while (prefix[pl]) {
|
||||||
|
if (rel[pl] != prefix[pl]) return false;
|
||||||
|
pl++;
|
||||||
|
}
|
||||||
|
return rel[pl] == '\0' || rel[pl] == '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
static int str_len(const char* s) {
|
||||||
|
int n = 0;
|
||||||
|
while (s[n]) n++;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Component owning rel, resolved by the longest matching owned path. This is
|
||||||
|
// what lets "sdk/tcc" stay installable while the rest of "sdk" is skipped.
|
||||||
|
static int component_owning(const char* rel) {
|
||||||
|
int best = -1, best_len = -1;
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
for (int p = 0; g_components[i].paths[p]; p++) {
|
||||||
|
const char* path = g_components[i].paths[p];
|
||||||
|
if (!path_under(path, rel)) continue;
|
||||||
|
int len = str_len(path);
|
||||||
|
if (len > best_len) { best_len = len; best = i; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool component_allows(const char* rel) {
|
||||||
|
int owner = component_owning(rel);
|
||||||
|
return owner < 0 || g_components[owner].checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool component_subtree_needed(const char* rel) {
|
||||||
|
int rel_len = str_len(rel);
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (!g_components[i].checked) continue;
|
||||||
|
for (int p = 0; g_components[i].paths[p]; p++) {
|
||||||
|
const char* path = g_components[i].paths[p];
|
||||||
|
if (str_len(path) > rel_len && path_under(rel, path)) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Presence detection and defaults
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static bool rel_exists(const char* rel) {
|
||||||
|
char abs[160];
|
||||||
|
snprintf(abs, sizeof(abs), "0:/%s", rel);
|
||||||
|
return installer_path_exists(abs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void components_init() {
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
Component& c = g_components[i];
|
||||||
|
c.expanded = false;
|
||||||
|
c.bytes = 0;
|
||||||
|
c.base_present = c.probe ? rel_exists(c.probe) : false;
|
||||||
|
c.present = c.base_present;
|
||||||
|
c.checked = c.base_present;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A parent counts as present when its own payload or any child exists.
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
int p = g_components[i].parent;
|
||||||
|
if (p >= 0 && g_components[i].present)
|
||||||
|
g_components[p].present = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_sizes_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Check state and toggling
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
mtk::CheckState component_state(int idx) {
|
||||||
|
const Component& c = g_components[idx];
|
||||||
|
if (!component_has_children(idx))
|
||||||
|
return mtk::check_state(c.checked);
|
||||||
|
|
||||||
|
int total = 0, on = 0;
|
||||||
|
if (c.base_present) { total++; if (c.checked) on++; }
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (g_components[i].parent != idx || !g_components[i].present) continue;
|
||||||
|
total++;
|
||||||
|
if (g_components[i].checked) on++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total == 0 || on == 0) return mtk::CHECK_OFF;
|
||||||
|
if (on == total) return mtk::CHECK_ON;
|
||||||
|
return mtk::CHECK_MIXED;
|
||||||
|
}
|
||||||
|
|
||||||
|
void component_toggle(int idx) {
|
||||||
|
Component& c = g_components[idx];
|
||||||
|
if (!c.present) return;
|
||||||
|
|
||||||
|
if (component_has_children(idx)) {
|
||||||
|
bool on = component_state(idx) != mtk::CHECK_ON;
|
||||||
|
c.checked = on && c.base_present;
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (g_components[i].parent == idx && g_components[i].present)
|
||||||
|
g_components[i].checked = on;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c.checked = !c.checked;
|
||||||
|
|
||||||
|
if (c.checked) {
|
||||||
|
if (c.depends_on >= 0 && g_components[c.depends_on].present)
|
||||||
|
g_components[c.depends_on].checked = true;
|
||||||
|
if (c.requires_parent && c.parent >= 0 &&
|
||||||
|
g_components[c.parent].base_present)
|
||||||
|
g_components[c.parent].checked = true;
|
||||||
|
} else {
|
||||||
|
// Drop anything that declared a dependency on this component.
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (g_components[i].depends_on == idx)
|
||||||
|
g_components[i].checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int component_visible_rows(int* out, int max) {
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < g_component_count && count < max; i++) {
|
||||||
|
int p = g_components[i].parent;
|
||||||
|
if (p >= 0 && !g_components[p].expanded) continue;
|
||||||
|
out[count++] = i;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Size measurement
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static uint64_t file_size_abs(const char* abs) {
|
||||||
|
int fd = montauk::open(abs);
|
||||||
|
if (fd < 0) return 0;
|
||||||
|
uint64_t size = montauk::getsize(fd);
|
||||||
|
montauk::close(fd);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive size of rel, counting only entries owned by component `owner`
|
||||||
|
// (entries claimed by a more specific component are counted there instead).
|
||||||
|
static uint64_t du_walk(const char* rel, int owner) {
|
||||||
|
if (component_owning(rel) != owner) return 0;
|
||||||
|
|
||||||
|
char abs[192];
|
||||||
|
snprintf(abs, sizeof(abs), "0:/%s", rel);
|
||||||
|
|
||||||
|
DirList list;
|
||||||
|
if (installer_list_dir(abs, &list)) {
|
||||||
|
uint64_t sum = 0;
|
||||||
|
for (int i = 0; i < list.count; i++)
|
||||||
|
sum += du_walk(list.entries[i].rel, owner);
|
||||||
|
installer_free_dir_list(&list);
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_size_abs(abs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void components_compute_sizes() {
|
||||||
|
if (g_sizes_ready) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
Component& c = g_components[i];
|
||||||
|
c.bytes = 0;
|
||||||
|
if (!c.present || comp_is_group(i)) continue;
|
||||||
|
for (int p = 0; c.paths[p]; p++)
|
||||||
|
c.bytes += du_walk(c.paths[p], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pure groups display the sum of their children.
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (!comp_is_group(i)) continue;
|
||||||
|
for (int j = 0; j < g_component_count; j++)
|
||||||
|
if (g_components[j].parent == i)
|
||||||
|
g_components[i].bytes += g_components[j].bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_sizes_ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool components_sizes_ready() {
|
||||||
|
return g_sizes_ready;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t components_selected_bytes() {
|
||||||
|
uint64_t sum = 0;
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (comp_is_group(i)) continue;
|
||||||
|
if (g_components[i].checked) sum += g_components[i].bytes;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t components_unselected_bytes() {
|
||||||
|
uint64_t sum = 0;
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (comp_is_group(i)) continue;
|
||||||
|
if (g_components[i].present && !g_components[i].checked)
|
||||||
|
sum += g_components[i].bytes;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Summaries and the update flow
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
void components_excluded_summary(char* buf, int bufsize) {
|
||||||
|
int excluded = 0;
|
||||||
|
int named = 0;
|
||||||
|
int pos = 0;
|
||||||
|
buf[0] = '\0';
|
||||||
|
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
if (comp_is_group(i)) continue;
|
||||||
|
if (!g_components[i].present || g_components[i].checked) continue;
|
||||||
|
excluded++;
|
||||||
|
if (named < 3) {
|
||||||
|
pos += snprintf(buf + pos, bufsize - pos, "%s%s",
|
||||||
|
named ? ", " : "Skipping: ",
|
||||||
|
g_components[i].label);
|
||||||
|
named++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excluded == 0) {
|
||||||
|
snprintf(buf, bufsize, "All optional components selected");
|
||||||
|
} else if (excluded > named) {
|
||||||
|
snprintf(buf + pos, bufsize - pos, " (+%d more)", excluded - named);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the update flow: refresh only the optional components the target
|
||||||
|
// already has; never re-add ones that were deselected at install time.
|
||||||
|
void components_set_from_target(int drive) {
|
||||||
|
for (int i = 0; i < g_component_count; i++) {
|
||||||
|
Component& c = g_components[i];
|
||||||
|
if (!c.probe) continue;
|
||||||
|
char abs[160];
|
||||||
|
snprintf(abs, sizeof(abs), "%d:/%s", drive, c.probe);
|
||||||
|
c.checked = c.present && installer_path_exists(abs);
|
||||||
|
|
||||||
|
// Migration: pre-sdk installs kept tcc/lua under 0:/lib.
|
||||||
|
if (!c.checked && c.present &&
|
||||||
|
(strcmp(c.id, "tcc") == 0 || strcmp(c.id, "lua") == 0)) {
|
||||||
|
snprintf(abs, sizeof(abs), "%d:/lib/%s", drive, c.id);
|
||||||
|
c.checked = installer_path_exists(abs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
#include <montauk/string.h>
|
#include <montauk/string.h>
|
||||||
#include <montauk/heap.h>
|
#include <montauk/heap.h>
|
||||||
#include <gui/gui.hpp>
|
#include <gui/gui.hpp>
|
||||||
#include <gui/truetype.hpp>
|
#include <gui/font.hpp>
|
||||||
|
#include <gui/mtk.hpp>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -23,34 +24,20 @@ using namespace gui;
|
|||||||
// Constants
|
// Constants
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
static constexpr int INIT_W = 520;
|
static constexpr int INIT_W = 560;
|
||||||
static constexpr int INIT_H = 440;
|
static constexpr int INIT_H = 500;
|
||||||
static constexpr int TOOLBAR_H = 36;
|
static constexpr int HEADER_H = 44;
|
||||||
static constexpr int STEP_BAR_H = 32;
|
static constexpr int STEP_BAR_H = 30;
|
||||||
static constexpr int CONTENT_TOP = TOOLBAR_H + STEP_BAR_H;
|
static constexpr int CONTENT_TOP = HEADER_H + STEP_BAR_H;
|
||||||
|
static constexpr int BOTTOM_H = 48;
|
||||||
|
static constexpr int PAD = 16;
|
||||||
|
static constexpr int BTN_H = 30;
|
||||||
static constexpr int MAX_DISKS = 32;
|
static constexpr int MAX_DISKS = 32;
|
||||||
static constexpr int MAX_PARTS = 32;
|
static constexpr int MAX_PARTS = 32;
|
||||||
static constexpr int STATUS_H = 26;
|
|
||||||
|
|
||||||
// Font sizes — adjusted at runtime by apply_scale()
|
static constexpr int COMP_ROW_H = 28;
|
||||||
extern int FONT_SIZE;
|
static constexpr int COMP_INDENT = 26;
|
||||||
extern int FONT_SM;
|
static constexpr int COMP_TOGGLE_W = 18;
|
||||||
|
|
||||||
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
|
// Install steps
|
||||||
@@ -65,6 +52,7 @@ enum InstallStep {
|
|||||||
STEP_MODE_SELECT,
|
STEP_MODE_SELECT,
|
||||||
STEP_SELECT_DISK,
|
STEP_SELECT_DISK,
|
||||||
STEP_PARTITION_SCHEME,
|
STEP_PARTITION_SCHEME,
|
||||||
|
STEP_COMPONENTS,
|
||||||
STEP_CONFIRM,
|
STEP_CONFIRM,
|
||||||
STEP_INSTALLING,
|
STEP_INSTALLING,
|
||||||
STEP_DONE,
|
STEP_DONE,
|
||||||
@@ -95,6 +83,55 @@ static const char* scheme_descs[] = {
|
|||||||
"One partition for boot, OS, and data. Simple and compatible.",
|
"One partition for boot, OS, and data. Simple and compatible.",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Optional components
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static constexpr int MAX_COMPONENT_PATHS = 16;
|
||||||
|
|
||||||
|
struct Component {
|
||||||
|
const char* id;
|
||||||
|
const char* label;
|
||||||
|
const char* desc; // shown in the status bar on hover
|
||||||
|
int parent; // index of parent component, or -1 for top level
|
||||||
|
// Path that identifies this component in an image / on a target drive.
|
||||||
|
const char* probe;
|
||||||
|
// Files or directories (relative to the drive root, no "0:/" prefix)
|
||||||
|
// owned by this component; nullptr-terminated. Empty for pure groups.
|
||||||
|
const char* paths[MAX_COMPONENT_PATHS];
|
||||||
|
// A child that needs its parent's base payload (e.g. gcc needs the
|
||||||
|
// SDK sysroot). Checking it re-checks the parent.
|
||||||
|
bool requires_parent;
|
||||||
|
// Another component this one cannot work without (e.g. gcc drives the
|
||||||
|
// binutils assembler/linker), or -1.
|
||||||
|
int depends_on;
|
||||||
|
|
||||||
|
// Runtime state
|
||||||
|
bool checked;
|
||||||
|
bool expanded; // parents only
|
||||||
|
bool present; // this component (or a child) exists in the image
|
||||||
|
bool base_present; // own probe exists (parents with own payload)
|
||||||
|
uint64_t bytes; // exclusive payload size in the source image
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Component g_components[];
|
||||||
|
extern const int g_component_count;
|
||||||
|
|
||||||
|
// components.cpp
|
||||||
|
void components_init();
|
||||||
|
void components_compute_sizes();
|
||||||
|
bool component_has_children(int idx);
|
||||||
|
mtk::CheckState component_state(int idx);
|
||||||
|
void component_toggle(int idx);
|
||||||
|
int component_visible_rows(int* out, int max);
|
||||||
|
bool component_allows(const char* rel);
|
||||||
|
bool component_subtree_needed(const char* rel);
|
||||||
|
void components_set_from_target(int drive);
|
||||||
|
uint64_t components_selected_bytes();
|
||||||
|
uint64_t components_unselected_bytes();
|
||||||
|
void components_excluded_summary(char* buf, int bufsize);
|
||||||
|
bool components_sizes_ready();
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// State
|
// State
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -120,8 +157,12 @@ struct InstallerState {
|
|||||||
char log[LOG_LINES][LOG_LINE_LEN];
|
char log[LOG_LINES][LOG_LINE_LEN];
|
||||||
int log_count;
|
int log_count;
|
||||||
|
|
||||||
char status[80];
|
char status[96];
|
||||||
uint64_t status_time;
|
uint64_t status_time;
|
||||||
|
|
||||||
|
int mouse_x;
|
||||||
|
int mouse_y;
|
||||||
|
int comp_scroll;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -130,10 +171,68 @@ struct InstallerState {
|
|||||||
|
|
||||||
extern int g_win_w, g_win_h;
|
extern int g_win_w, g_win_h;
|
||||||
extern InstallerState g_state;
|
extern InstallerState g_state;
|
||||||
extern TrueTypeFont* g_font;
|
extern Color g_accent;
|
||||||
extern uint32_t* g_pixels;
|
|
||||||
extern uint32_t* g_backbuf;
|
// ============================================================================
|
||||||
extern int g_win_id;
|
// Shared layout helpers
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
inline int content_x() { return PAD; }
|
||||||
|
inline int content_w() { return g_win_w - PAD * 2; }
|
||||||
|
inline int content_bottom() { return g_win_h - BOTTOM_H; }
|
||||||
|
|
||||||
|
// Heading block: title line + smaller subtitle line
|
||||||
|
inline int heading_bottom() {
|
||||||
|
return CONTENT_TOP + 14 + system_font_height() * 2 + 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect bottom_bar_rect() {
|
||||||
|
return {0, g_win_h - BOTTOM_H, g_win_w, BOTTOM_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect primary_btn_rect() {
|
||||||
|
int y = g_win_h - BOTTOM_H + (BOTTOM_H - BTN_H) / 2;
|
||||||
|
return {g_win_w - PAD - 108, y, 108, BTN_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect secondary_btn_rect() {
|
||||||
|
Rect p = primary_btn_rect();
|
||||||
|
return {p.x - 8 - 96, p.y, 96, BTN_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect tertiary_btn_rect() {
|
||||||
|
Rect s = secondary_btn_rect();
|
||||||
|
return {s.x - 8 - 84, s.y, 84, BTN_H};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode select cards
|
||||||
|
inline Rect mode_card_rect(int index) {
|
||||||
|
int card_h = 56;
|
||||||
|
int y = heading_bottom() + index * (card_h + 10);
|
||||||
|
return {content_x(), y, content_w(), card_h};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disk / partition / scheme list rows
|
||||||
|
inline Rect list_row_rect(int index, int row_h) {
|
||||||
|
int y = heading_bottom() + index * (row_h + 6);
|
||||||
|
return {content_x(), y, content_w(), row_h};
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr int DISK_ROW_H = 48;
|
||||||
|
static constexpr int SCHEME_ROW_H = 50;
|
||||||
|
static constexpr int PART_ROW_H = 48;
|
||||||
|
|
||||||
|
// Component tree viewport
|
||||||
|
inline Rect components_list_rect() {
|
||||||
|
int top = heading_bottom();
|
||||||
|
return {content_x(), top, content_w(), content_bottom() - top - 8};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Rect component_row_rect(int visible_index) {
|
||||||
|
Rect list = components_list_rect();
|
||||||
|
return {list.x, list.y + visible_index * COMP_ROW_H - g_state.comp_scroll,
|
||||||
|
list.w, COMP_ROW_H};
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Function declarations — helpers (main.cpp)
|
// Function declarations — helpers (main.cpp)
|
||||||
@@ -143,7 +242,7 @@ void set_status(const char* msg);
|
|||||||
void add_log(const char* msg);
|
void add_log(const char* msg);
|
||||||
void flush_ui();
|
void flush_ui();
|
||||||
void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorSize);
|
void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorSize);
|
||||||
void apply_scale(int scale);
|
void format_bytes(char* buf, int bufsize, uint64_t bytes);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Function declarations — render.cpp
|
// Function declarations — render.cpp
|
||||||
@@ -159,3 +258,21 @@ void installer_refresh_disks();
|
|||||||
void installer_refresh_parts();
|
void installer_refresh_parts();
|
||||||
void do_install();
|
void do_install();
|
||||||
void do_update();
|
void do_update();
|
||||||
|
bool installer_path_exists(const char* abs_path);
|
||||||
|
|
||||||
|
// Heap-backed directory listing. Entries are full paths relative to the
|
||||||
|
// drive root (e.g. "apps/doom"), with is_dir derived from the trailing '/'
|
||||||
|
// the VFS appends to directory names. Survives nested listings, unlike the
|
||||||
|
// kernel's rotating readdir scratch pages.
|
||||||
|
struct DirEntry {
|
||||||
|
char* rel; // path relative to drive root, no trailing '/'
|
||||||
|
bool is_dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DirList {
|
||||||
|
DirEntry* entries;
|
||||||
|
int count;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool installer_list_dir(const char* abs_dir, DirList* out);
|
||||||
|
void installer_free_dir_list(DirList* list);
|
||||||
|
|||||||
+209
-166
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "installer.h"
|
#include "installer.h"
|
||||||
|
#include <gui/mtk/settings.hpp>
|
||||||
#include <gui/standalone.hpp>
|
#include <gui/standalone.hpp>
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -14,23 +15,10 @@
|
|||||||
int g_win_w = INIT_W;
|
int g_win_w = INIT_W;
|
||||||
int g_win_h = INIT_H;
|
int g_win_h = INIT_H;
|
||||||
|
|
||||||
int FONT_SIZE = 18;
|
|
||||||
int FONT_SM = 14;
|
|
||||||
|
|
||||||
InstallerState g_state;
|
InstallerState g_state;
|
||||||
TrueTypeFont* g_font = nullptr;
|
Color g_accent = colors::ACCENT;
|
||||||
uint32_t* g_pixels = nullptr;
|
|
||||||
uint32_t* g_backbuf = nullptr;
|
|
||||||
int g_win_id = -1;
|
|
||||||
static WsWindow g_window;
|
|
||||||
|
|
||||||
void apply_scale(int scale) {
|
static WsWindow g_window;
|
||||||
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
|
// Helpers
|
||||||
@@ -38,7 +26,8 @@ void apply_scale(int scale) {
|
|||||||
|
|
||||||
void set_status(const char* msg) {
|
void set_status(const char* msg) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (; i < 79 && msg[i]; i++) g_state.status[i] = msg[i];
|
for (; i < (int)sizeof(g_state.status) - 1 && msg[i]; i++)
|
||||||
|
g_state.status[i] = msg[i];
|
||||||
g_state.status[i] = '\0';
|
g_state.status[i] = '\0';
|
||||||
g_state.status_time = montauk::get_milliseconds();
|
g_state.status_time = montauk::get_milliseconds();
|
||||||
}
|
}
|
||||||
@@ -58,9 +47,8 @@ void add_log(const char* msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void flush_ui() {
|
void flush_ui() {
|
||||||
if (g_pixels && g_backbuf && g_window.id >= 0) {
|
if (g_window.pixels && g_window.id >= 0) {
|
||||||
render(g_backbuf);
|
render(g_window.pixels);
|
||||||
montauk::memcpy(g_pixels, g_backbuf, g_win_w * g_win_h * 4);
|
|
||||||
g_window.present();
|
g_window.present();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,13 +59,95 @@ void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorS
|
|||||||
if (gb >= 1024) {
|
if (gb >= 1024) {
|
||||||
uint64_t tb = gb / 1024;
|
uint64_t tb = gb / 1024;
|
||||||
uint64_t frac = ((gb % 1024) * 10) / 1024;
|
uint64_t frac = ((gb % 1024) * 10) / 1024;
|
||||||
snprintf(buf, bufsize, "%lu.%lu TB", (unsigned)tb, (unsigned)frac);
|
snprintf(buf, bufsize, "%lu.%lu TB", (unsigned long)tb, (unsigned long)frac);
|
||||||
} else if (gb > 0) {
|
} else if (gb > 0) {
|
||||||
uint64_t frac = ((bytes % (1024ULL * 1024 * 1024)) * 10) / (1024ULL * 1024 * 1024);
|
uint64_t frac = ((bytes % (1024ULL * 1024 * 1024)) * 10) / (1024ULL * 1024 * 1024);
|
||||||
snprintf(buf, bufsize, "%lu.%lu GB", (unsigned)gb, (unsigned)frac);
|
snprintf(buf, bufsize, "%lu.%lu GB", (unsigned long)gb, (unsigned long)frac);
|
||||||
} else {
|
} else {
|
||||||
uint64_t mb = bytes / (1024ULL * 1024);
|
uint64_t mb = bytes / (1024ULL * 1024);
|
||||||
snprintf(buf, bufsize, "%lu MB", (unsigned)mb);
|
snprintf(buf, bufsize, "%lu MB", (unsigned long)mb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void format_bytes(char* buf, int bufsize, uint64_t bytes) {
|
||||||
|
if (bytes >= 1024ULL * 1024 * 1024) {
|
||||||
|
uint64_t gb = bytes / (1024ULL * 1024 * 1024);
|
||||||
|
uint64_t frac = ((bytes % (1024ULL * 1024 * 1024)) * 10) / (1024ULL * 1024 * 1024);
|
||||||
|
snprintf(buf, bufsize, "%lu.%lu GB", (unsigned long)gb, (unsigned long)frac);
|
||||||
|
} else if (bytes >= 1024 * 1024) {
|
||||||
|
snprintf(buf, bufsize, "%lu MB", (unsigned long)(bytes / (1024 * 1024)));
|
||||||
|
} else if (bytes >= 1024) {
|
||||||
|
snprintf(buf, bufsize, "%lu KB", (unsigned long)(bytes / 1024));
|
||||||
|
} else {
|
||||||
|
snprintf(buf, bufsize, "%lu B", (unsigned long)bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Step transitions
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
static void clamp_comp_scroll() {
|
||||||
|
int vis[32];
|
||||||
|
int n = component_visible_rows(vis, 32);
|
||||||
|
Rect list = components_list_rect();
|
||||||
|
int max_scroll = n * COMP_ROW_H - list.h;
|
||||||
|
if (max_scroll < 0) max_scroll = 0;
|
||||||
|
// Round up to a row boundary so the top visible row stays aligned.
|
||||||
|
max_scroll = ((max_scroll + COMP_ROW_H - 1) / COMP_ROW_H) * COMP_ROW_H;
|
||||||
|
if (g_state.comp_scroll > max_scroll) g_state.comp_scroll = max_scroll;
|
||||||
|
if (g_state.comp_scroll < 0) g_state.comp_scroll = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void goto_components() {
|
||||||
|
g_state.step = STEP_COMPONENTS;
|
||||||
|
g_state.comp_scroll = 0;
|
||||||
|
if (!components_sizes_ready()) {
|
||||||
|
set_status("Measuring component sizes...");
|
||||||
|
flush_ui();
|
||||||
|
components_compute_sizes();
|
||||||
|
set_status("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void step_advance() {
|
||||||
|
auto& st = g_state;
|
||||||
|
switch (st.step) {
|
||||||
|
case STEP_SELECT_DISK:
|
||||||
|
if (st.selected_disk >= 0) st.step = STEP_PARTITION_SCHEME;
|
||||||
|
break;
|
||||||
|
case STEP_PARTITION_SCHEME:
|
||||||
|
goto_components();
|
||||||
|
break;
|
||||||
|
case STEP_COMPONENTS:
|
||||||
|
st.step = STEP_CONFIRM;
|
||||||
|
break;
|
||||||
|
case STEP_CONFIRM:
|
||||||
|
st.step = STEP_INSTALLING;
|
||||||
|
st.log_count = 0;
|
||||||
|
break;
|
||||||
|
case STEP_UPDATE_SELECT_PART:
|
||||||
|
if (st.selected_part >= 0) st.step = STEP_UPDATE_CONFIRM;
|
||||||
|
break;
|
||||||
|
case STEP_UPDATE_CONFIRM:
|
||||||
|
st.step = STEP_UPDATING;
|
||||||
|
st.log_count = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void step_back() {
|
||||||
|
auto& st = g_state;
|
||||||
|
switch (st.step) {
|
||||||
|
case STEP_SELECT_DISK: st.step = STEP_MODE_SELECT; break;
|
||||||
|
case STEP_PARTITION_SCHEME: st.step = STEP_SELECT_DISK; break;
|
||||||
|
case STEP_COMPONENTS: st.step = STEP_PARTITION_SCHEME; break;
|
||||||
|
case STEP_CONFIRM: st.step = STEP_COMPONENTS; break;
|
||||||
|
case STEP_UPDATE_SELECT_PART: st.step = STEP_MODE_SELECT; break;
|
||||||
|
case STEP_UPDATE_CONFIRM: st.step = STEP_UPDATE_SELECT_PART; break;
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,166 +155,141 @@ void format_disk_size(char* buf, int bufsize, uint64_t sectors, uint16_t sectorS
|
|||||||
// Mouse handling
|
// Mouse handling
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
// Returns false to signal quit.
|
||||||
static bool handle_click(int mx, int my) {
|
static bool handle_click(int mx, int my) {
|
||||||
auto& st = g_state;
|
auto& st = g_state;
|
||||||
int fh = g_font ? g_font->get_cache(FONT_SIZE)->ascent - g_font->get_cache(FONT_SIZE)->descent : 16;
|
|
||||||
int fh_sm = g_font ? g_font->get_cache(FONT_SM)->ascent - g_font->get_cache(FONT_SM)->descent : 12;
|
|
||||||
|
|
||||||
// Common bottom button area
|
Rect primary = primary_btn_rect();
|
||||||
int btn_w = 120, btn_h = 34;
|
Rect secondary = secondary_btn_rect();
|
||||||
int btn_y = g_win_h - STATUS_H - btn_h - 12;
|
Rect tertiary = tertiary_btn_rect();
|
||||||
|
|
||||||
if (st.step == STEP_MODE_SELECT) {
|
switch (st.step) {
|
||||||
// Two mode cards
|
case STEP_MODE_SELECT:
|
||||||
int y = CONTENT_TOP + 16 + fh + 4 + fh_sm + 16;
|
if (mode_card_rect(0).contains(mx, my)) {
|
||||||
int card_h = 52, card_x = 16, card_w = g_win_w - 32;
|
|
||||||
|
|
||||||
// Install card
|
|
||||||
if (mx >= card_x && mx < card_x + card_w && my >= y && my < y + card_h) {
|
|
||||||
st.mode = MODE_INSTALL;
|
st.mode = MODE_INSTALL;
|
||||||
installer_refresh_disks();
|
installer_refresh_disks();
|
||||||
st.step = STEP_SELECT_DISK;
|
st.step = STEP_SELECT_DISK;
|
||||||
return true;
|
} else if (mode_card_rect(1).contains(mx, my)) {
|
||||||
}
|
|
||||||
y += card_h + 8;
|
|
||||||
|
|
||||||
// Update card
|
|
||||||
if (mx >= card_x && mx < card_x + card_w && my >= y && my < y + card_h) {
|
|
||||||
st.mode = MODE_UPDATE;
|
st.mode = MODE_UPDATE;
|
||||||
installer_refresh_parts();
|
installer_refresh_parts();
|
||||||
st.step = STEP_UPDATE_SELECT_PART;
|
st.step = STEP_UPDATE_SELECT_PART;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
} else if (st.step == STEP_SELECT_DISK) {
|
break;
|
||||||
// Disk list items
|
|
||||||
int y = CONTENT_TOP + 40 + fh + 12;
|
case STEP_SELECT_DISK:
|
||||||
for (int i = 0; i < st.disk_count; i++) {
|
for (int i = 0; i < st.disk_count; i++) {
|
||||||
int item_h = 48;
|
if (list_row_rect(i, DISK_ROW_H).contains(mx, my)) {
|
||||||
if (mx >= 16 && mx < g_win_w - 16 && my >= y && my < y + item_h) {
|
|
||||||
st.selected_disk = i;
|
st.selected_disk = i;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
y += item_h + 4;
|
|
||||||
}
|
}
|
||||||
|
if (primary.contains(mx, my)) step_advance();
|
||||||
// "Next" button
|
else if (secondary.contains(mx, my)) step_back();
|
||||||
int next_x = g_win_w - btn_w - 16;
|
else if (tertiary.contains(mx, my)) {
|
||||||
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();
|
installer_refresh_disks();
|
||||||
return true;
|
set_status("Disk list refreshed");
|
||||||
}
|
}
|
||||||
} else if (st.step == STEP_PARTITION_SCHEME) {
|
break;
|
||||||
// Scheme radio buttons
|
|
||||||
int y = CONTENT_TOP + 40 + fh + 12;
|
case STEP_PARTITION_SCHEME:
|
||||||
for (int i = 0; i < SCHEME_COUNT; i++) {
|
for (int i = 0; i < SCHEME_COUNT; i++) {
|
||||||
int item_h = 52;
|
if (list_row_rect(i, SCHEME_ROW_H).contains(mx, my)) {
|
||||||
if (mx >= 16 && mx < g_win_w - 16 && my >= y && my < y + item_h) {
|
|
||||||
st.partition_scheme = i;
|
st.partition_scheme = i;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
y += item_h + 4;
|
|
||||||
}
|
}
|
||||||
|
if (primary.contains(mx, my)) step_advance();
|
||||||
|
else if (secondary.contains(mx, my)) step_back();
|
||||||
|
break;
|
||||||
|
|
||||||
// "Next" button
|
case STEP_COMPONENTS: {
|
||||||
int next_x = g_win_w - btn_w - 16;
|
int vis[32];
|
||||||
if (mx >= next_x && mx < next_x + btn_w &&
|
int n = component_visible_rows(vis, 32);
|
||||||
my >= btn_y && my < btn_y + btn_h) {
|
Rect list = components_list_rect();
|
||||||
st.step = STEP_CONFIRM;
|
for (int k = 0; k < n; k++) {
|
||||||
|
Rect row = component_row_rect(k);
|
||||||
|
if (row.y + row.h <= list.y || row.y >= list.y + list.h) continue;
|
||||||
|
if (!row.contains(mx, my)) continue;
|
||||||
|
|
||||||
|
int idx = vis[k];
|
||||||
|
bool child = g_components[idx].parent >= 0;
|
||||||
|
int x = row.x + 4 + (child ? COMP_INDENT : 0);
|
||||||
|
|
||||||
|
if (component_has_children(idx) && mx < x + COMP_TOGGLE_W + 4) {
|
||||||
|
g_components[idx].expanded = !g_components[idx].expanded;
|
||||||
|
clamp_comp_scroll();
|
||||||
|
} else {
|
||||||
|
component_toggle(idx);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (primary.contains(mx, my)) step_advance();
|
||||||
// "Back" button
|
else if (secondary.contains(mx, my)) step_back();
|
||||||
int back_x = next_x - btn_w - 8;
|
break;
|
||||||
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
|
case STEP_CONFIRM:
|
||||||
int canc_x = center_x + gap / 2;
|
case STEP_UPDATE_CONFIRM:
|
||||||
if (mx >= canc_x && mx < canc_x + btn_w &&
|
if (primary.contains(mx, my)) step_advance();
|
||||||
my >= btn_y && my < btn_y + btn_h) {
|
else if (secondary.contains(mx, my)) step_back();
|
||||||
st.step = STEP_PARTITION_SCHEME;
|
break;
|
||||||
return true;
|
|
||||||
}
|
case STEP_UPDATE_SELECT_PART:
|
||||||
} 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
|
|
||||||
}
|
|
||||||
} else if (st.step == STEP_UPDATE_SELECT_PART) {
|
|
||||||
// Partition list items
|
|
||||||
int y = CONTENT_TOP + 16 + fh + 4 + fh_sm + 12;
|
|
||||||
for (int i = 0; i < st.part_count; i++) {
|
for (int i = 0; i < st.part_count; i++) {
|
||||||
int item_h = 48;
|
if (list_row_rect(i, PART_ROW_H).contains(mx, my)) {
|
||||||
if (mx >= 16 && mx < g_win_w - 16 && my >= y && my < y + item_h) {
|
|
||||||
st.selected_part = i;
|
st.selected_part = i;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
y += item_h + 4;
|
}
|
||||||
|
if (primary.contains(mx, my)) step_advance();
|
||||||
|
else if (secondary.contains(mx, my)) step_back();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case STEP_DONE:
|
||||||
|
case STEP_ERROR:
|
||||||
|
if (primary.contains(mx, my)) return false;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Next" button
|
|
||||||
int next_x = g_win_w - btn_w - 16;
|
|
||||||
if (st.selected_part >= 0 &&
|
|
||||||
mx >= next_x && mx < next_x + btn_w &&
|
|
||||||
my >= btn_y && my < btn_y + btn_h) {
|
|
||||||
st.step = STEP_UPDATE_CONFIRM;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Back" button
|
// ============================================================================
|
||||||
int back_x = next_x - btn_w - 8;
|
// Key handling — returns false to signal quit
|
||||||
if (mx >= back_x && mx < back_x + btn_w &&
|
// ============================================================================
|
||||||
my >= btn_y && my < btn_y + btn_h) {
|
|
||||||
st.step = STEP_MODE_SELECT;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (st.step == STEP_UPDATE_CONFIRM) {
|
|
||||||
int center_x = g_win_w / 2;
|
|
||||||
int gap = 16;
|
|
||||||
|
|
||||||
// "Update" button
|
static bool handle_key(const montauk::abi::KeyEvent& key) {
|
||||||
int upd_x = center_x - btn_w - gap / 2;
|
auto& st = g_state;
|
||||||
if (mx >= upd_x && mx < upd_x + btn_w &&
|
|
||||||
my >= btn_y && my < btn_y + btn_h) {
|
if (key.scancode == 0x01) { // Escape
|
||||||
st.step = STEP_UPDATING;
|
if (st.step == STEP_INSTALLING || st.step == STEP_UPDATING)
|
||||||
st.log_count = 0;
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
||||||
|
if (st.step == STEP_DONE || st.step == STEP_ERROR) return false;
|
||||||
|
step_advance();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Back" button
|
if (key.scancode == 0x48) { // Up
|
||||||
int back_x = center_x + gap / 2;
|
if (st.step == STEP_SELECT_DISK && st.selected_disk > 0)
|
||||||
if (mx >= back_x && mx < back_x + btn_w &&
|
st.selected_disk--;
|
||||||
my >= btn_y && my < btn_y + btn_h) {
|
else if (st.step == STEP_UPDATE_SELECT_PART && st.selected_part > 0)
|
||||||
st.step = STEP_UPDATE_SELECT_PART;
|
st.selected_part--;
|
||||||
return true;
|
else if (st.step == STEP_PARTITION_SCHEME && st.partition_scheme > 0)
|
||||||
}
|
st.partition_scheme--;
|
||||||
|
} else if (key.scancode == 0x50) { // Down
|
||||||
|
if (st.step == STEP_SELECT_DISK && st.selected_disk < st.disk_count - 1)
|
||||||
|
st.selected_disk++;
|
||||||
|
else if (st.step == STEP_UPDATE_SELECT_PART && st.selected_part < st.part_count - 1)
|
||||||
|
st.selected_part++;
|
||||||
|
else if (st.step == STEP_PARTITION_SCHEME && st.partition_scheme < SCHEME_COUNT - 1)
|
||||||
|
st.partition_scheme++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -260,31 +305,21 @@ extern "C" void _start() {
|
|||||||
g_state.selected_part = -1;
|
g_state.selected_part = -1;
|
||||||
g_state.partition_scheme = SCHEME_EFI_EXT2;
|
g_state.partition_scheme = SCHEME_EFI_EXT2;
|
||||||
g_state.step = STEP_MODE_SELECT;
|
g_state.step = STEP_MODE_SELECT;
|
||||||
|
g_state.mouse_x = -1;
|
||||||
|
g_state.mouse_y = -1;
|
||||||
|
|
||||||
// Load font
|
if (!fonts::init())
|
||||||
{
|
montauk::exit(1);
|
||||||
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());
|
g_accent = mtk::load_system_accent();
|
||||||
|
|
||||||
|
components_init();
|
||||||
installer_refresh_disks();
|
installer_refresh_disks();
|
||||||
|
|
||||||
if (!g_window.create("Install MontaukOS", INIT_W, INIT_H))
|
if (!g_window.create("Install MontaukOS", INIT_W, INIT_H))
|
||||||
montauk::exit(1);
|
montauk::exit(1);
|
||||||
|
|
||||||
g_pixels = g_window.pixels;
|
flush_ui();
|
||||||
g_win_id = g_window.id;
|
|
||||||
g_backbuf = (uint32_t*)montauk::malloc(g_win_w * g_win_h * 4);
|
|
||||||
|
|
||||||
render(g_backbuf);
|
|
||||||
montauk::memcpy(g_pixels, g_backbuf, g_win_w * g_win_h * 4);
|
|
||||||
g_window.present();
|
|
||||||
|
|
||||||
bool install_triggered = false;
|
bool install_triggered = false;
|
||||||
|
|
||||||
@@ -314,32 +349,40 @@ extern "C" void _start() {
|
|||||||
|
|
||||||
if (ev.type == 3) break;
|
if (ev.type == 3) break;
|
||||||
|
|
||||||
if (ev.type == 4) {
|
if (ev.type == 4) redraw = true;
|
||||||
apply_scale(g_window.scale_factor);
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ev.type == 2) {
|
if (ev.type == 2) {
|
||||||
g_win_w = g_window.width;
|
g_win_w = g_window.width;
|
||||||
g_win_h = g_window.height;
|
g_win_h = g_window.height;
|
||||||
g_pixels = g_window.pixels;
|
if (g_state.step == STEP_COMPONENTS) clamp_comp_scroll();
|
||||||
montauk::mfree(g_backbuf);
|
|
||||||
g_backbuf = (uint32_t*)montauk::malloc(g_win_w * g_win_h * 4);
|
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.type == 0 && ev.key.pressed) {
|
if (ev.type == 0 && ev.key.pressed) {
|
||||||
if (ev.key.scancode == 0x01) break;
|
if (!handle_key(ev.key)) break;
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ev.type == 1) {
|
if (ev.type == 1) {
|
||||||
|
bool moved = (ev.mouse.x != g_state.mouse_x ||
|
||||||
|
ev.mouse.y != g_state.mouse_y);
|
||||||
|
g_state.mouse_x = ev.mouse.x;
|
||||||
|
g_state.mouse_y = ev.mouse.y;
|
||||||
|
|
||||||
|
if (ev.mouse.scroll != 0 && g_state.step == STEP_COMPONENTS) {
|
||||||
|
g_state.comp_scroll -= ev.mouse.scroll * COMP_ROW_H;
|
||||||
|
clamp_comp_scroll();
|
||||||
|
redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
||||||
if (clicked) {
|
if (clicked) {
|
||||||
if (!handle_click(ev.mouse.x, ev.mouse.y))
|
if (!handle_click(ev.mouse.x, ev.mouse.y))
|
||||||
break;
|
break;
|
||||||
redraw = true;
|
redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
redraw |= moved;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redraw) flush_ui();
|
if (redraw) flush_ui();
|
||||||
|
|||||||
+427
-476
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ SRCDIR := src
|
|||||||
OBJDIR := obj
|
OBJDIR := obj
|
||||||
LIBC_DIR := ../../lib/libc
|
LIBC_DIR := ../../lib/libc
|
||||||
BINDIR := ../../bin/os
|
BINDIR := ../../bin/os
|
||||||
LIBDIR := ../../bin/lib/lua
|
LIBDIR := ../../bin/sdk/lua
|
||||||
|
|
||||||
CFLAGS := \
|
CFLAGS := \
|
||||||
-std=gnu11 \
|
-std=gnu11 \
|
||||||
|
|||||||
@@ -223,8 +223,8 @@
|
|||||||
|
|
||||||
#elif defined(__MONTAUKOS__) /* }{ */
|
#elif defined(__MONTAUKOS__) /* }{ */
|
||||||
|
|
||||||
#define LUA_LDIR "0:/lib/lua/"
|
#define LUA_LDIR "0:/sdk/lua/"
|
||||||
#define LUA_CDIR "0:/lib/lua/"
|
#define LUA_CDIR "0:/sdk/lua/"
|
||||||
|
|
||||||
#if !defined(LUA_PATH_DEFAULT)
|
#if !defined(LUA_PATH_DEFAULT)
|
||||||
#define LUA_PATH_DEFAULT \
|
#define LUA_PATH_DEFAULT \
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ TARGET := $(BINDIR)/tcc.elf
|
|||||||
# ---- CRT objects (staged from shared libc) ----
|
# ---- CRT objects (staged from shared libc) ----
|
||||||
|
|
||||||
SHARED_CRT_DIR := $(LIBDIR)/libc
|
SHARED_CRT_DIR := $(LIBDIR)/libc
|
||||||
CRT_DIR := ../../bin/lib/tcc/lib
|
CRT_DIR := ../../bin/sdk/tcc/lib
|
||||||
CRT_OBJS := $(CRT_DIR)/crt1.o $(CRT_DIR)/crti.o $(CRT_DIR)/crtn.o
|
CRT_OBJS := $(CRT_DIR)/crt1.o $(CRT_DIR)/crti.o $(CRT_DIR)/crtn.o
|
||||||
|
|
||||||
# ---- Rules ----
|
# ---- Rules ----
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
#undef CONFIG_TCC_BCHECK
|
#undef CONFIG_TCC_BCHECK
|
||||||
|
|
||||||
/* Paths on MontaukOS ramdisk */
|
/* Paths on MontaukOS ramdisk */
|
||||||
#define CONFIG_TCCDIR "0:/lib/tcc"
|
#define CONFIG_TCCDIR "0:/sdk/tcc"
|
||||||
#define CONFIG_TCC_SYSINCLUDEPATHS "0:/lib/tcc/include"
|
#define CONFIG_TCC_SYSINCLUDEPATHS "0:/sdk/tcc/include"
|
||||||
#define CONFIG_TCC_LIBPATHS "0:/lib/tcc/lib"
|
#define CONFIG_TCC_LIBPATHS "0:/sdk/tcc/lib"
|
||||||
#define CONFIG_TCC_CRTPREFIX "0:/lib/tcc/lib"
|
#define CONFIG_TCC_CRTPREFIX "0:/sdk/tcc/lib"
|
||||||
#define CONFIG_TCC_ELFINTERP ""
|
#define CONFIG_TCC_ELFINTERP ""
|
||||||
|
|
||||||
/* We are not native -- no -run support */
|
/* We are not native -- no -run support */
|
||||||
|
|||||||
Reference in New Issue
Block a user