460 lines
15 KiB
C++
460 lines
15 KiB
C++
/*
|
|
* main.cpp
|
|
* MontaukOS Disk Tool — entry point, event loop, state
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "disks.h"
|
|
#include <gui/standalone.hpp>
|
|
|
|
// ============================================================================
|
|
// Global state definitions
|
|
// ============================================================================
|
|
|
|
int g_win_w = INIT_W;
|
|
int g_win_h = INIT_H;
|
|
|
|
DiskToolState g_state;
|
|
TrueTypeFont* g_font = nullptr;
|
|
|
|
const Color part_colors[NUM_PART_COLORS] = {
|
|
Color::from_rgb(0x42, 0x7A, 0xB5),
|
|
Color::from_rgb(0x5C, 0xA0, 0x5C),
|
|
Color::from_rgb(0xCC, 0x88, 0x33),
|
|
Color::from_rgb(0x99, 0x55, 0xBB),
|
|
Color::from_rgb(0xCC, 0x55, 0x55),
|
|
Color::from_rgb(0x44, 0xAA, 0xAA),
|
|
Color::from_rgb(0xBB, 0xBB, 0x44),
|
|
Color::from_rgb(0x88, 0x66, 0x44),
|
|
};
|
|
|
|
// ============================================================================
|
|
// 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();
|
|
}
|
|
|
|
int get_disk_parts(int* indices, int max) {
|
|
int count = 0;
|
|
for (int i = 0; i < g_state.part_count && count < max; i++) {
|
|
if (g_state.parts[i].blockDev == g_state.selected_disk) {
|
|
indices[count++] = i;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// Toolbar hit testing (returns button widths matching render layout)
|
|
// ============================================================================
|
|
|
|
static int disk_btn_width(int idx) {
|
|
char label[8];
|
|
snprintf(label, sizeof(label), "Disk %d", idx);
|
|
int lw = g_font ? g_font->measure_text(label, FONT_SIZE) + 16 : 48;
|
|
return lw;
|
|
}
|
|
|
|
static bool handle_toolbar_click(int mx, int my) {
|
|
if (my >= TOOLBAR_H || my < TB_BTN_Y) return false;
|
|
|
|
auto& dt = g_state;
|
|
|
|
// Disk selector buttons
|
|
int bx = 8;
|
|
for (int i = 0; i < dt.disk_count; i++) {
|
|
int lw = disk_btn_width(i);
|
|
if (mx >= bx && mx < bx + lw && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) {
|
|
dt.selected_disk = i;
|
|
dt.selected_part = -1;
|
|
dt.scroll_y = 0;
|
|
return true;
|
|
}
|
|
bx += lw + 6;
|
|
}
|
|
|
|
// Right-side buttons (must match render layout)
|
|
int rx = g_win_w - 8;
|
|
|
|
int ref_w = 64; rx -= ref_w;
|
|
if (mx >= rx && mx < rx + ref_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) {
|
|
disktool_refresh();
|
|
set_status("Refreshed");
|
|
return true;
|
|
}
|
|
rx -= 6;
|
|
|
|
int mnt_w = 60; rx -= mnt_w;
|
|
if (mx >= rx && mx < rx + mnt_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) {
|
|
do_mount_partition();
|
|
return true;
|
|
}
|
|
rx -= 6;
|
|
|
|
int fmt_w = 64; rx -= fmt_w;
|
|
if (mx >= rx && mx < rx + fmt_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) {
|
|
open_format_dialog();
|
|
return true;
|
|
}
|
|
rx -= 6;
|
|
|
|
int np_w = 74; rx -= np_w;
|
|
if (mx >= rx && mx < rx + np_w && my >= TB_BTN_Y && my < TB_BTN_Y + TB_BTN_H) {
|
|
do_create_partition();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Mouse handling for content area
|
|
// ============================================================================
|
|
|
|
static bool handle_content_click(int mx, int my) {
|
|
auto& dt = g_state;
|
|
if (dt.disk_count == 0 || dt.selected_disk < 0) return false;
|
|
|
|
int fh = g_font ? g_font->get_cache(FONT_SIZE)->ascent - g_font->get_cache(FONT_SIZE)->descent : 16;
|
|
int y = TOOLBAR_H + 8 + fh + 8;
|
|
|
|
// Partition map bar click
|
|
int map_x = MAP_PAD;
|
|
int map_w = g_win_w - MAP_PAD * 2;
|
|
if (my >= y && my < y + MAP_H) {
|
|
Montauk::DiskInfo& disk = dt.disks[dt.selected_disk];
|
|
int part_indices[MAX_PARTS];
|
|
int nparts = get_disk_parts(part_indices, MAX_PARTS);
|
|
uint64_t total = disk.sectorCount;
|
|
if (total > 0) {
|
|
for (int pi = 0; pi < nparts; pi++) {
|
|
Montauk::PartInfo& p = dt.parts[part_indices[pi]];
|
|
int px = map_x + (int)((p.startLba * (uint64_t)map_w) / total);
|
|
int pw = (int)((p.sectorCount * (uint64_t)map_w) / total);
|
|
if (pw < 2) pw = 2;
|
|
if (mx >= px && mx < px + pw) {
|
|
dt.selected_part = pi;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
dt.selected_part = -1;
|
|
return true;
|
|
}
|
|
|
|
y += MAP_H + 8 + HEADER_H;
|
|
|
|
// Partition list click
|
|
int list_bottom = g_win_h - STATUS_H;
|
|
if (my >= y && my < list_bottom) {
|
|
int row = (my - y + dt.scroll_y) / ITEM_H;
|
|
int part_indices[MAX_PARTS];
|
|
int nparts = get_disk_parts(part_indices, MAX_PARTS);
|
|
if (row >= 0 && row < nparts)
|
|
dt.selected_part = row;
|
|
else
|
|
dt.selected_part = -1;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// New partition dialog mouse handling
|
|
// ============================================================================
|
|
|
|
static bool handle_newpart_dialog_click(int mx, int my, bool clicked) {
|
|
auto& dlg = g_state.np_dlg;
|
|
if (!dlg.open) return false;
|
|
|
|
int dw = NP_DLG_W, dh = NP_DLG_H;
|
|
|
|
int btn_w = 90, btn_h = 30;
|
|
int btn_y = dh - btn_h - 16;
|
|
int gap = 16;
|
|
int total_w = btn_w * 2 + gap;
|
|
int bx = (dw - total_w) / 2;
|
|
|
|
dlg.hover_confirm = (mx >= bx && mx < bx + btn_w && my >= btn_y && my < btn_y + btn_h);
|
|
dlg.hover_cancel = (mx >= bx + btn_w + gap && mx < bx + btn_w * 2 + gap && my >= btn_y && my < btn_y + btn_h);
|
|
|
|
if (!clicked) return true;
|
|
|
|
if (dlg.hover_confirm) {
|
|
newpart_dialog_confirm();
|
|
return true;
|
|
}
|
|
|
|
if (dlg.hover_cancel) {
|
|
close_newpart_dialog();
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Format dialog mouse handling
|
|
// ============================================================================
|
|
|
|
static bool handle_format_dialog_click(int mx, int my, bool clicked) {
|
|
auto& dlg = g_state.fmt_dlg;
|
|
if (!dlg.open) return false;
|
|
|
|
int dw = FMT_DLG_W, dh = FMT_DLG_H;
|
|
int fh = g_font ? g_font->get_cache(FONT_SIZE)->ascent - g_font->get_cache(FONT_SIZE)->descent : 16;
|
|
|
|
// Button hit testing
|
|
int btn_w = 90, btn_h = 30;
|
|
int btn_y = dh - btn_h - 16;
|
|
int gap = 16;
|
|
int total_w = btn_w * 2 + gap;
|
|
int bx = (dw - total_w) / 2;
|
|
|
|
dlg.hover_format = (mx >= bx && mx < bx + btn_w && my >= btn_y && my < btn_y + btn_h);
|
|
dlg.hover_cancel = (mx >= bx + btn_w + gap && mx < bx + btn_w * 2 + gap && my >= btn_y && my < btn_y + btn_h);
|
|
|
|
if (!clicked) return true;
|
|
|
|
// FS type selector
|
|
int sel_y = 12 + fh * 2 + 18;
|
|
int opt_y = sel_y + fh + 8;
|
|
for (int i = 0; i < NUM_FS_TYPES; i++) {
|
|
int iy = opt_y + i * 32;
|
|
if (mx >= 24 && mx < dw - 24 && my >= iy && my < iy + 28) {
|
|
dlg.selected_fs = i;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (dlg.hover_format) {
|
|
format_dialog_do_format();
|
|
return true;
|
|
}
|
|
|
|
if (dlg.hover_cancel) {
|
|
close_format_dialog();
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Key handling
|
|
// ============================================================================
|
|
|
|
static bool handle_key(const Montauk::KeyEvent& key) {
|
|
if (!key.pressed) return false;
|
|
|
|
auto& dt = g_state;
|
|
|
|
// New partition dialog keys
|
|
if (dt.np_dlg.open) {
|
|
if (key.scancode == 0x01) { // Escape
|
|
close_newpart_dialog();
|
|
} else if (key.ascii == '\n' || key.ascii == '\r') {
|
|
newpart_dialog_confirm();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Format dialog keys
|
|
if (dt.fmt_dlg.open) {
|
|
if (key.scancode == 0x01) { // Escape
|
|
close_format_dialog();
|
|
} else if (key.scancode == 0x48 && dt.fmt_dlg.selected_fs > 0) {
|
|
dt.fmt_dlg.selected_fs--;
|
|
} else if (key.scancode == 0x50 && dt.fmt_dlg.selected_fs < NUM_FS_TYPES - 1) {
|
|
dt.fmt_dlg.selected_fs++;
|
|
} else if (key.ascii == '\n' || key.ascii == '\r') {
|
|
format_dialog_do_format();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int part_indices[MAX_PARTS];
|
|
int nparts = get_disk_parts(part_indices, MAX_PARTS);
|
|
|
|
if (key.scancode == 0x01) { // Escape
|
|
return false; // signal quit
|
|
} else if (key.scancode == 0x48) { // Up
|
|
if (dt.selected_part > 0) dt.selected_part--;
|
|
else if (nparts > 0) dt.selected_part = 0;
|
|
} else if (key.scancode == 0x50) { // Down
|
|
if (dt.selected_part < nparts - 1) dt.selected_part++;
|
|
} else if (key.scancode == 0x4B) { // Left
|
|
if (dt.selected_disk > 0) { dt.selected_disk--; dt.selected_part = -1; dt.scroll_y = 0; }
|
|
} else if (key.scancode == 0x4D) { // Right
|
|
if (dt.selected_disk < dt.disk_count - 1) { dt.selected_disk++; dt.selected_part = -1; dt.scroll_y = 0; }
|
|
} else if (key.ascii == 'n' || key.ascii == 'N') {
|
|
do_create_partition();
|
|
} else if (key.ascii == 'm' || key.ascii == 'M') {
|
|
do_mount_partition();
|
|
} else if (key.ascii == 'f' || key.ascii == 'F') {
|
|
open_format_dialog();
|
|
} else {
|
|
return true; // consumed but no action
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Entry point
|
|
// ============================================================================
|
|
|
|
extern "C" void _start() {
|
|
montauk::memset(&g_state, 0, sizeof(g_state));
|
|
g_state.selected_disk = 0;
|
|
g_state.selected_part = -1;
|
|
|
|
// 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;
|
|
}
|
|
|
|
disktool_refresh();
|
|
|
|
WsWindow win;
|
|
if (!win.create("Disks", INIT_W, INIT_H))
|
|
montauk::exit(1);
|
|
|
|
uint32_t* pixels = win.pixels;
|
|
|
|
render(pixels);
|
|
win.present();
|
|
|
|
while (true) {
|
|
Montauk::WinEvent ev;
|
|
bool redraw_main = false;
|
|
bool redraw_dlg = false;
|
|
|
|
bool redraw_np = false;
|
|
|
|
// Poll new partition dialog window
|
|
if (g_state.np_dlg.open) {
|
|
int dr = montauk::win_poll(g_state.np_dlg.win_id, &ev);
|
|
if (dr > 0) {
|
|
if (ev.type == 3) { // close
|
|
close_newpart_dialog();
|
|
redraw_main = true;
|
|
} else if (ev.type == 0 && ev.key.pressed) {
|
|
handle_key(ev.key);
|
|
redraw_np = g_state.np_dlg.open;
|
|
redraw_main = true;
|
|
} else if (ev.type == 1) {
|
|
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
|
handle_newpart_dialog_click(ev.mouse.x, ev.mouse.y, clicked);
|
|
redraw_np = g_state.np_dlg.open;
|
|
redraw_main = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Poll format dialog window
|
|
if (g_state.fmt_dlg.open) {
|
|
int dr = montauk::win_poll(g_state.fmt_dlg.win_id, &ev);
|
|
if (dr > 0) {
|
|
if (ev.type == 3) { // close
|
|
close_format_dialog();
|
|
redraw_main = true;
|
|
} else if (ev.type == 0 && ev.key.pressed) {
|
|
handle_key(ev.key);
|
|
redraw_dlg = g_state.fmt_dlg.open;
|
|
redraw_main = true;
|
|
} else if (ev.type == 1) {
|
|
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
|
handle_format_dialog_click(ev.mouse.x, ev.mouse.y, clicked);
|
|
redraw_dlg = g_state.fmt_dlg.open;
|
|
redraw_main = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Poll main window
|
|
int r = win.poll(&ev);
|
|
|
|
if (r < 0) break;
|
|
if (r == 0) {
|
|
// Even with no main event, dialog may have triggered redraws
|
|
if (redraw_main) { render(pixels); win.present(); }
|
|
if (redraw_dlg) { render_format_window(); montauk::win_present(g_state.fmt_dlg.win_id); }
|
|
if (redraw_np) { render_newpart_window(); montauk::win_present(g_state.np_dlg.win_id); }
|
|
if (!redraw_main && !redraw_dlg && !redraw_np) montauk::sleep_ms(16);
|
|
continue;
|
|
}
|
|
|
|
if (ev.type == 3) break; // close
|
|
|
|
// Resize
|
|
if (ev.type == 2) {
|
|
g_win_w = win.width;
|
|
g_win_h = win.height;
|
|
pixels = win.pixels;
|
|
redraw_main = true;
|
|
}
|
|
|
|
// Keyboard
|
|
if (ev.type == 0 && ev.key.pressed) {
|
|
if (!handle_key(ev.key) && ev.key.scancode == 0x01)
|
|
break; // Escape quits when not in dialog
|
|
redraw_main = true;
|
|
}
|
|
|
|
// Mouse
|
|
if (ev.type == 1) {
|
|
int mx = ev.mouse.x;
|
|
int my = ev.mouse.y;
|
|
bool clicked = (ev.mouse.buttons & 1) && !(ev.mouse.prev_buttons & 1);
|
|
|
|
// Scroll
|
|
if (ev.mouse.scroll != 0) {
|
|
g_state.scroll_y -= ev.mouse.scroll * 20;
|
|
if (g_state.scroll_y < 0) g_state.scroll_y = 0;
|
|
redraw_main = true;
|
|
}
|
|
|
|
if (clicked) {
|
|
if (handle_toolbar_click(mx, my) || handle_content_click(mx, my))
|
|
redraw_main = true;
|
|
}
|
|
}
|
|
|
|
if (redraw_main) { render(pixels); win.present(); }
|
|
if (redraw_dlg) { render_format_window(); montauk::win_present(g_state.fmt_dlg.win_id); }
|
|
if (redraw_np) { render_newpart_window(); montauk::win_present(g_state.np_dlg.win_id); }
|
|
}
|
|
|
|
close_newpart_dialog();
|
|
close_format_dialog();
|
|
win.destroy();
|
|
montauk::exit(0);
|
|
}
|