feat: MontaukOS installer app, toml config, app directory with manifests, and more
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* app_devexplorer.cpp
|
||||
* MontaukOS Desktop - Device Explorer launcher (spawns standalone devexplorer.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_devexplorer(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/devexplorer.elf");
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
/*
|
||||
* app_disks.cpp
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_disks(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/disks.elf");
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* app_doom.cpp
|
||||
* MontaukOS Desktop - DOOM launcher (spawns standalone doom.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_doom(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/games/doom.elf");
|
||||
}
|
||||
@@ -92,7 +92,12 @@ static void filemanager_read_drives(FileManagerState* fm) {
|
||||
fm->at_drives_root = true;
|
||||
fm->current_path[0] = '\0';
|
||||
|
||||
for (int d = 0; d < FM_MAX_DRIVES; d++) {
|
||||
int drives[FM_MAX_DRIVES];
|
||||
int driveCount = montauk::drivelist(drives, FM_MAX_DRIVES);
|
||||
|
||||
for (int di = 0; di < driveCount; di++) {
|
||||
int d = drives[di];
|
||||
int i = fm->entry_count;
|
||||
char probe[8];
|
||||
if (d < 10) {
|
||||
probe[0] = '0' + d;
|
||||
@@ -106,21 +111,16 @@ static void filemanager_read_drives(FileManagerState* fm) {
|
||||
probe[3] = '/';
|
||||
probe[4] = '\0';
|
||||
}
|
||||
const char* tmp[1];
|
||||
int r = montauk::readdir(probe, tmp, 1);
|
||||
if (r >= 0) {
|
||||
int i = fm->entry_count;
|
||||
char label[64];
|
||||
montauk::strcpy(label, "Drive ");
|
||||
str_append(label, probe, 64);
|
||||
montauk::strncpy(fm->entry_names[i], label, 63);
|
||||
fm->entry_types[i] = 3; // drive
|
||||
fm->entry_sizes[i] = 0;
|
||||
fm->is_dir[i] = true;
|
||||
fm->drive_indices[i] = d;
|
||||
fm->entry_count++;
|
||||
if (fm->entry_count >= 64) break;
|
||||
}
|
||||
char label[64];
|
||||
montauk::strcpy(label, "Drive ");
|
||||
str_append(label, probe, 64);
|
||||
montauk::strncpy(fm->entry_names[i], label, 63);
|
||||
fm->entry_types[i] = 3; // drive
|
||||
fm->entry_sizes[i] = 0;
|
||||
fm->is_dir[i] = true;
|
||||
fm->drive_indices[i] = d;
|
||||
fm->entry_count++;
|
||||
if (fm->entry_count >= 64) break;
|
||||
}
|
||||
|
||||
fm->selected = -1;
|
||||
@@ -688,13 +688,13 @@ static void filemanager_on_mouse(Window* win, MouseEvent& ev) {
|
||||
}
|
||||
str_append(fullpath, fm->entry_names[clicked_idx], 512);
|
||||
if (is_image_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/imageviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/imageviewer/imageviewer.elf", fullpath);
|
||||
} else if (is_font_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/fontpreview.elf", fullpath);
|
||||
montauk::spawn("0:/apps/fontpreview/fontpreview.elf", fullpath);
|
||||
} else if (is_pdf_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/pdfviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/pdfviewer/pdfviewer.elf", fullpath);
|
||||
} else if (is_spreadsheet_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/spreadsheet.elf", fullpath);
|
||||
montauk::spawn("0:/apps/spreadsheet/spreadsheet.elf", fullpath);
|
||||
} else if (fm->desktop) {
|
||||
open_texteditor_with_file(fm->desktop, fullpath);
|
||||
}
|
||||
@@ -744,13 +744,13 @@ static void filemanager_on_mouse(Window* win, MouseEvent& ev) {
|
||||
}
|
||||
str_append(fullpath, fm->entry_names[clicked_idx], 512);
|
||||
if (is_image_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/imageviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/imageviewer/imageviewer.elf", fullpath);
|
||||
} else if (is_font_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/fontpreview.elf", fullpath);
|
||||
montauk::spawn("0:/apps/fontpreview/fontpreview.elf", fullpath);
|
||||
} else if (is_pdf_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/pdfviewer.elf", fullpath);
|
||||
montauk::spawn("0:/apps/pdfviewer/pdfviewer.elf", fullpath);
|
||||
} else if (is_spreadsheet_file(fm->entry_names[clicked_idx])) {
|
||||
montauk::spawn("0:/os/spreadsheet.elf", fullpath);
|
||||
montauk::spawn("0:/apps/spreadsheet/spreadsheet.elf", fullpath);
|
||||
} else if (fm->desktop) {
|
||||
open_texteditor_with_file(fm->desktop, fullpath);
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* app_spreadsheet.cpp
|
||||
* MontaukOS Desktop - Spreadsheet launcher (spawns standalone spreadsheet.elf)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_spreadsheet(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/spreadsheet.elf");
|
||||
}
|
||||
@@ -17,12 +17,7 @@ static constexpr int TERM_TAB_GAP = 4;
|
||||
static constexpr int TERM_PLUS_W = 28;
|
||||
static constexpr int TERM_PLUS_PAD = 8;
|
||||
|
||||
// Compute the x-offset for left-aligned tabs within bar_w
|
||||
static int term_tabs_origin(int bar_w, int tab_count) {
|
||||
(void)bar_w;
|
||||
(void)tab_count;
|
||||
return 8;
|
||||
}
|
||||
static constexpr int TERM_TAB_PAD = 8;
|
||||
|
||||
struct TermTabState {
|
||||
TerminalState* tabs[TERM_MAX_TABS];
|
||||
@@ -150,7 +145,7 @@ static void terminal_on_draw(Window* win, Framebuffer& fb) {
|
||||
c.fill_rect(0, 0, cr.w, TERM_TAB_BAR_H, bar_bg);
|
||||
|
||||
int fh = system_font_height();
|
||||
int tab_x = term_tabs_origin(cr.w, tts->tab_count);
|
||||
int tab_x = TERM_TAB_PAD;
|
||||
|
||||
for (int i = 0; i < tts->tab_count; i++) {
|
||||
bool active = (i == tts->active_tab);
|
||||
@@ -241,7 +236,7 @@ static void terminal_on_mouse(Window* win, MouseEvent& ev) {
|
||||
|
||||
if (ly >= TERM_TAB_BAR_H) return; // click in terminal area, ignore
|
||||
|
||||
int tab_x = term_tabs_origin(cr.w, tts->tab_count);
|
||||
int tab_x = TERM_TAB_PAD;
|
||||
for (int i = 0; i < tts->tab_count; i++) {
|
||||
if (lx >= tab_x && lx < tab_x + TERM_TAB_W) {
|
||||
// Check if click is on the close "x" (last 24px of tab)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* app_weather.cpp
|
||||
* MontaukOS Desktop - Weather app launcher
|
||||
* Spawns weather.elf as a standalone Window Server process
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_weather(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/weather.elf");
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* app_wiki.cpp
|
||||
* MontaukOS Desktop - Wikipedia launcher
|
||||
* Spawns wikipedia.elf as a standalone Window Server process
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "apps_common.hpp"
|
||||
|
||||
void open_wiki(DesktopState* ds) {
|
||||
(void)ds;
|
||||
montauk::spawn("0:/os/wikipedia.elf");
|
||||
}
|
||||
@@ -156,7 +156,7 @@ inline void format_size(char* buf, int size) {
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Forward declarations for app launchers
|
||||
// Forward declarations for app launchers (embedded apps only)
|
||||
// ============================================================================
|
||||
|
||||
void open_terminal(DesktopState* ds);
|
||||
@@ -166,16 +166,10 @@ void open_calculator(DesktopState* ds);
|
||||
void open_texteditor(DesktopState* ds);
|
||||
void open_texteditor_with_file(DesktopState* ds, const char* path);
|
||||
void open_klog(DesktopState* ds);
|
||||
void open_wiki(DesktopState* ds);
|
||||
void open_weather(DesktopState* ds);
|
||||
void open_procmgr(DesktopState* ds);
|
||||
void open_mandelbrot(DesktopState* ds);
|
||||
void open_devexplorer(DesktopState* ds);
|
||||
void open_settings(DesktopState* ds);
|
||||
void open_doom(DesktopState* ds);
|
||||
void open_reboot_dialog(DesktopState* ds);
|
||||
void open_wordprocessor(DesktopState* ds);
|
||||
void open_spreadsheet(DesktopState* ds);
|
||||
void open_shutdown_dialog(DesktopState* ds);
|
||||
void open_disks(DesktopState* ds);
|
||||
void desktop_poll_external_windows(DesktopState* ds);
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
#include "apps/apps_common.hpp"
|
||||
#include "wallpaper.hpp"
|
||||
#include <montauk/toml.h>
|
||||
|
||||
// ============================================================================
|
||||
// App Menu Data
|
||||
// App Menu Data (dynamic — built at startup from embedded + external apps)
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int MENU_W = 220;
|
||||
@@ -20,36 +21,16 @@ static constexpr int MENU_DIV_H = 10;
|
||||
|
||||
struct MenuRow {
|
||||
bool is_category;
|
||||
const char* label; // "" for divider-only rows
|
||||
int app_id; // -1 for category headers / dividers
|
||||
char label[48];
|
||||
int app_id; // embedded dispatch ID, or -1 for categories/dividers
|
||||
bool external; // true = spawn binary_path
|
||||
char binary_path[128]; // full VFS path for external apps
|
||||
SvgIcon* icon; // loaded menu icon (null for categories/dividers)
|
||||
};
|
||||
|
||||
static constexpr int MENU_ROW_COUNT = 23;
|
||||
static const MenuRow menu_rows[MENU_ROW_COUNT] = {
|
||||
{ true, "Applications", -1 }, // cat 0
|
||||
{ false, "Terminal", 0 },
|
||||
{ false, "Files", 1 },
|
||||
{ false, "Text Editor", 4 },
|
||||
{ false, "Word Processor", 15 },
|
||||
{ false, "Spreadsheet", 16 },
|
||||
{ false, "Calculator", 3 },
|
||||
{ true, "Internet", -1 }, // cat 1
|
||||
{ false, "Wikipedia", 9 },
|
||||
{ false, "Weather", 13 },
|
||||
{ true, "System", -1 }, // cat 2
|
||||
{ false, "System Info", 2 },
|
||||
{ false, "Kernel Log", 5 },
|
||||
{ false, "Processes", 6 },
|
||||
{ false, "Devices", 8 },
|
||||
{ false, "Disks", 17 },
|
||||
{ true, "Games", -1 }, // cat 3
|
||||
{ false, "Mandelbrot", 7 },
|
||||
{ false, "DOOM", 10 },
|
||||
{ true, "", -1 }, // divider (always visible)
|
||||
{ false, "Settings", 11 },
|
||||
{ false, "Reboot", 12 },
|
||||
{ false, "Shutdown", 14 },
|
||||
};
|
||||
static constexpr int MAX_MENU_ROWS = 48;
|
||||
inline MenuRow menu_rows[MAX_MENU_ROWS];
|
||||
inline int menu_row_count = 0;
|
||||
|
||||
// Collapsible category state (categories 0-3 are toggleable; divider category always expanded)
|
||||
static constexpr int MENU_NUM_CATS = 5;
|
||||
@@ -77,12 +58,52 @@ inline int menu_row_height(const MenuRow& row) {
|
||||
|
||||
inline int menu_total_height() {
|
||||
int h = 10; // top + bottom padding
|
||||
for (int i = 0; i < MENU_ROW_COUNT; i++)
|
||||
for (int i = 0; i < menu_row_count; i++)
|
||||
if (menu_row_visible(i))
|
||||
h += menu_row_height(menu_rows[i]);
|
||||
return h;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Menu Builder Helpers
|
||||
// ============================================================================
|
||||
|
||||
inline int menu_add_category(const char* label) {
|
||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
||||
MenuRow& r = menu_rows[menu_row_count++];
|
||||
r.is_category = true;
|
||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||
r.app_id = -1;
|
||||
r.external = false;
|
||||
r.binary_path[0] = '\0';
|
||||
r.icon = nullptr;
|
||||
return menu_row_count - 1;
|
||||
}
|
||||
|
||||
inline int menu_add_embedded(const char* label, int app_id, SvgIcon* icon) {
|
||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
||||
MenuRow& r = menu_rows[menu_row_count++];
|
||||
r.is_category = false;
|
||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||
r.app_id = app_id;
|
||||
r.external = false;
|
||||
r.binary_path[0] = '\0';
|
||||
r.icon = icon;
|
||||
return menu_row_count - 1;
|
||||
}
|
||||
|
||||
inline int menu_add_external(const char* label, const char* binary, SvgIcon* icon) {
|
||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
||||
MenuRow& r = menu_rows[menu_row_count++];
|
||||
r.is_category = false;
|
||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||
r.app_id = -1;
|
||||
r.external = true;
|
||||
montauk::strncpy(r.binary_path, binary, sizeof(r.binary_path));
|
||||
r.icon = icon;
|
||||
return menu_row_count - 1;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Forward Declarations (internal functions)
|
||||
// ============================================================================
|
||||
@@ -95,6 +116,10 @@ gui::CursorStyle cursor_for_edge(gui::ResizeEdge edge);
|
||||
void desktop_draw_app_menu(gui::DesktopState* ds);
|
||||
void desktop_draw_net_popup(gui::DesktopState* ds);
|
||||
|
||||
// main.cpp
|
||||
void desktop_scan_apps(gui::DesktopState* ds);
|
||||
void desktop_build_menu(gui::DesktopState* ds);
|
||||
|
||||
// Month names (shared by panel clock)
|
||||
inline const char* month_names[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
|
||||
@@ -202,7 +202,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
// Walk visible rows to find which one was clicked
|
||||
int iy = menu_y + 5;
|
||||
int cur_cat = -1;
|
||||
for (int i = 0; i < MENU_ROW_COUNT; i++) {
|
||||
for (int i = 0; i < menu_row_count; i++) {
|
||||
const MenuRow& row = menu_rows[i];
|
||||
if (row.is_category) cur_cat++;
|
||||
if (!menu_row_visible(i)) continue;
|
||||
@@ -212,25 +212,25 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
// Toggle category expand/collapse
|
||||
menu_cat_expanded[cur_cat] = !menu_cat_expanded[cur_cat];
|
||||
} else if (!row.is_category) {
|
||||
switch (row.app_id) {
|
||||
case 0: open_terminal(ds); break;
|
||||
case 1: open_filemanager(ds); break;
|
||||
case 2: open_sysinfo(ds); break;
|
||||
case 3: open_calculator(ds); break;
|
||||
case 4: open_texteditor(ds); break;
|
||||
case 5: open_klog(ds); break;
|
||||
case 6: open_procmgr(ds); break;
|
||||
case 7: open_mandelbrot(ds); break;
|
||||
case 8: open_devexplorer(ds); break;
|
||||
case 9: open_wiki(ds); break;
|
||||
case 10: open_doom(ds); break;
|
||||
case 11: open_settings(ds); break;
|
||||
case 12: open_reboot_dialog(ds); break;
|
||||
case 13: open_weather(ds); break;
|
||||
case 14: open_shutdown_dialog(ds); break;
|
||||
case 15: open_wordprocessor(ds); break;
|
||||
case 16: open_spreadsheet(ds); break;
|
||||
case 17: open_disks(ds); break;
|
||||
if (row.external) {
|
||||
// Launch external app from manifest
|
||||
montauk::spawn(row.binary_path);
|
||||
} else {
|
||||
// Dispatch embedded app
|
||||
switch (row.app_id) {
|
||||
case 0: open_terminal(ds); break;
|
||||
case 1: open_filemanager(ds); break;
|
||||
case 2: open_sysinfo(ds); break;
|
||||
case 3: open_calculator(ds); break;
|
||||
case 4: open_texteditor(ds); break;
|
||||
case 5: open_klog(ds); break;
|
||||
case 6: open_procmgr(ds); break;
|
||||
case 7: open_mandelbrot(ds); break;
|
||||
case 11: open_settings(ds); break;
|
||||
case 12: open_reboot_dialog(ds); break;
|
||||
case 14: open_shutdown_dialog(ds); break;
|
||||
case 15: open_wordprocessor(ds); break;
|
||||
}
|
||||
}
|
||||
ds->app_menu_open = false;
|
||||
}
|
||||
@@ -534,10 +534,6 @@ void gui::desktop_handle_keyboard(DesktopState* ds, const Montauk::KeyEvent& key
|
||||
open_klog(ds);
|
||||
return;
|
||||
}
|
||||
if (key.ascii == 'd' || key.ascii == 'D') {
|
||||
open_doom(ds);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch to focused window
|
||||
|
||||
@@ -6,6 +6,173 @@
|
||||
|
||||
#include "desktop_internal.hpp"
|
||||
|
||||
// ============================================================================
|
||||
// App Manifest Scanning
|
||||
// ============================================================================
|
||||
|
||||
// Extract the basename from a readdir entry.
|
||||
// readdir returns full paths from drive root (e.g. "apps/doom/") —
|
||||
// strip the directory prefix and any trailing slash to get just "doom".
|
||||
static void extract_basename(char* out, int outSz, const char* entry) {
|
||||
// Strip trailing slash
|
||||
int len = montauk::slen(entry);
|
||||
while (len > 0 && entry[len - 1] == '/') len--;
|
||||
|
||||
// Find last slash before that
|
||||
int last_slash = -1;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (entry[i] == '/') last_slash = i;
|
||||
}
|
||||
|
||||
const char* base = (last_slash >= 0) ? entry + last_slash + 1 : entry;
|
||||
int base_len = len - (last_slash >= 0 ? last_slash + 1 : 0);
|
||||
if (base_len >= outSz) base_len = outSz - 1;
|
||||
montauk::memcpy(out, base, base_len);
|
||||
out[base_len] = '\0';
|
||||
}
|
||||
|
||||
void desktop_scan_apps(DesktopState* ds) {
|
||||
ds->external_app_count = 0;
|
||||
|
||||
// Ensure the apps directory exists
|
||||
montauk::fmkdir("0:/apps");
|
||||
|
||||
const char* entries[32];
|
||||
int count = montauk::readdir("0:/apps", entries, 32);
|
||||
|
||||
for (int i = 0; i < count && ds->external_app_count < MAX_EXTERNAL_APPS; i++) {
|
||||
// readdir returns paths like "apps/doom/" — extract just "doom"
|
||||
char dirname[64];
|
||||
extract_basename(dirname, sizeof(dirname), entries[i]);
|
||||
if (dirname[0] == '\0') continue;
|
||||
|
||||
// Try to open the manifest in this app directory
|
||||
char manifest_path[128];
|
||||
snprintf(manifest_path, sizeof(manifest_path), "0:/apps/%s/manifest.toml", dirname);
|
||||
|
||||
int fh = montauk::open(manifest_path);
|
||||
if (fh < 0) continue;
|
||||
|
||||
uint64_t sz = montauk::getsize(fh);
|
||||
if (sz == 0 || sz > 4096) { montauk::close(fh); continue; }
|
||||
|
||||
char* text = (char*)montauk::malloc(sz + 1);
|
||||
montauk::read(fh, (uint8_t*)text, 0, sz);
|
||||
montauk::close(fh);
|
||||
text[sz] = '\0';
|
||||
|
||||
auto doc = montauk::toml::parse(text);
|
||||
montauk::mfree(text);
|
||||
|
||||
ExternalApp* app = &ds->external_apps[ds->external_app_count];
|
||||
|
||||
// Read manifest fields
|
||||
const char* name = doc.get_string("app.name", "Unknown");
|
||||
const char* binary = doc.get_string("app.binary", "");
|
||||
const char* icon_file = doc.get_string("app.icon", "");
|
||||
const char* category = doc.get_string("menu.category", "Applications");
|
||||
bool visible = doc.get_bool("menu.visible", true);
|
||||
|
||||
montauk::strncpy(app->name, name, sizeof(app->name));
|
||||
montauk::strncpy(app->category, category, sizeof(app->category));
|
||||
app->menu_visible = visible;
|
||||
|
||||
// Build full binary path: 0:/apps/<dir>/<binary>
|
||||
snprintf(app->binary_path, sizeof(app->binary_path),
|
||||
"0:/apps/%s/%s", dirname, binary);
|
||||
|
||||
// Load icon from app directory
|
||||
app->icon = {};
|
||||
if (icon_file[0]) {
|
||||
char icon_path[128];
|
||||
snprintf(icon_path, sizeof(icon_path),
|
||||
"0:/apps/%s/%s", dirname, icon_file);
|
||||
Color defColor = colors::ICON_COLOR;
|
||||
app->icon = svg_load(icon_path, 20, 20, defColor);
|
||||
}
|
||||
|
||||
doc.destroy();
|
||||
ds->external_app_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Menu Builder
|
||||
// ============================================================================
|
||||
|
||||
// Category definitions and their ordering
|
||||
static const char* CATEGORY_NAMES[] = { "Applications", "Internet", "System", "Games" };
|
||||
static constexpr int NUM_CATEGORIES = 4;
|
||||
|
||||
// Embedded app definitions: { display_name, app_id, category_index }
|
||||
struct EmbeddedAppDef {
|
||||
const char* name;
|
||||
int app_id;
|
||||
int category; // index into CATEGORY_NAMES
|
||||
};
|
||||
|
||||
static const EmbeddedAppDef embedded_apps[] = {
|
||||
{ "Terminal", 0, 0 },
|
||||
{ "Files", 1, 0 },
|
||||
{ "Text Editor", 4, 0 },
|
||||
{ "Word Processor", 15, 0 },
|
||||
{ "Calculator", 3, 0 },
|
||||
{ "System Info", 2, 2 },
|
||||
{ "Kernel Log", 5, 2 },
|
||||
{ "Processes", 6, 2 },
|
||||
{ "Mandelbrot", 7, 3 },
|
||||
};
|
||||
|
||||
static constexpr int NUM_EMBEDDED = sizeof(embedded_apps) / sizeof(embedded_apps[0]);
|
||||
|
||||
// Resolve embedded app_id to an icon pointer in DesktopState
|
||||
static SvgIcon* icon_for_embedded(DesktopState* ds, int app_id) {
|
||||
switch (app_id) {
|
||||
case 0: return &ds->icon_terminal;
|
||||
case 1: return &ds->icon_filemanager;
|
||||
case 2: return &ds->icon_sysinfo;
|
||||
case 3: return &ds->icon_calculator;
|
||||
case 4: return &ds->icon_texteditor;
|
||||
case 5: return &ds->icon_terminal; // Kernel Log uses terminal icon
|
||||
case 6: return &ds->icon_procmgr;
|
||||
case 7: return &ds->icon_mandelbrot;
|
||||
case 15: return &ds->icon_texteditor; // Word Processor uses text editor icon
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void desktop_build_menu(DesktopState* ds) {
|
||||
menu_row_count = 0;
|
||||
|
||||
// Build each category
|
||||
for (int cat = 0; cat < NUM_CATEGORIES; cat++) {
|
||||
menu_add_category(CATEGORY_NAMES[cat]);
|
||||
|
||||
// Add embedded apps in this category
|
||||
for (int e = 0; e < NUM_EMBEDDED; e++) {
|
||||
if (embedded_apps[e].category == cat) {
|
||||
menu_add_embedded(embedded_apps[e].name, embedded_apps[e].app_id,
|
||||
icon_for_embedded(ds, embedded_apps[e].app_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Add external apps in this category
|
||||
for (int x = 0; x < ds->external_app_count; x++) {
|
||||
ExternalApp* app = &ds->external_apps[x];
|
||||
if (!app->menu_visible) continue;
|
||||
if (montauk::streq(app->category, CATEGORY_NAMES[cat])) {
|
||||
menu_add_external(app->name, app->binary_path, &app->icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Divider + always-visible entries
|
||||
menu_add_category(""); // divider (cat 4, always expanded)
|
||||
menu_add_embedded("Settings", 11, &ds->icon_settings);
|
||||
menu_add_embedded("Reboot", 12, &ds->icon_reboot);
|
||||
menu_add_embedded("Shutdown", 14, &ds->icon_shutdown);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Initialization
|
||||
// ============================================================================
|
||||
@@ -58,14 +225,12 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
ds->icon_reboot = svg_load("0:/icons/system-reboot.svg", 20, 20, defColor);
|
||||
ds->icon_shutdown = svg_load("0:/icons/system-shutdown.svg", 20, 20, defColor);
|
||||
|
||||
ds->icon_weather = svg_load("0:/icons/weather-widget.svg", 20, 20, defColor);
|
||||
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
||||
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
|
||||
|
||||
ds->icon_doom = svg_load("0:/icons/doom.svg", 20, 20, defColor);
|
||||
ds->icon_procmgr = svg_load("0:/icons/system-monitor.svg", 20, 20, defColor);
|
||||
ds->icon_mandelbrot = svg_load("0:/icons/applications-science.svg", 20, 20, defColor);
|
||||
ds->icon_devexplorer = svg_load("0:/icons/hardware.svg", 20, 20, defColor);
|
||||
ds->icon_disks = svg_load("0:/icons/gparted.svg", 20, 20, defColor); // gparted icon, i.e. disk/partition management
|
||||
ds->icon_spreadsheet = svg_load("0:/icons/spreadsheet.svg", 20, 20, defColor);
|
||||
// Scan 0:/apps/ for external app manifests and build the menu
|
||||
desktop_scan_apps(ds);
|
||||
desktop_build_menu(ds);
|
||||
|
||||
// Settings defaults
|
||||
ds->settings.bg_gradient = true;
|
||||
@@ -236,6 +401,10 @@ void gui::desktop_run(DesktopState* ds) {
|
||||
// Handle mouse events
|
||||
desktop_handle_mouse(ds);
|
||||
|
||||
// Re-poll external windows so that any killed during mouse/key
|
||||
// handling are removed before we touch their pixel buffers.
|
||||
desktop_poll_external_windows(ds);
|
||||
|
||||
// Compose and present
|
||||
desktop_compose(ds);
|
||||
ds->fb.flip();
|
||||
|
||||
@@ -151,34 +151,12 @@ void desktop_draw_app_menu(DesktopState* ds) {
|
||||
fill_rounded_rect(fb, menu_x, menu_y, MENU_W, menu_h, 8, colors::MENU_BG);
|
||||
draw_rect(fb, menu_x, menu_y, MENU_W, menu_h, colors::BORDER);
|
||||
|
||||
// Icon lookup by app_id
|
||||
SvgIcon* icons[18] = {
|
||||
&ds->icon_terminal, // 0
|
||||
&ds->icon_filemanager, // 1
|
||||
&ds->icon_sysinfo, // 2
|
||||
&ds->icon_calculator, // 3
|
||||
&ds->icon_texteditor, // 4
|
||||
&ds->icon_terminal, // 5 (Kernel Log)
|
||||
&ds->icon_procmgr, // 6
|
||||
&ds->icon_mandelbrot, // 7
|
||||
&ds->icon_devexplorer, // 8
|
||||
&ds->icon_wikipedia, // 9
|
||||
&ds->icon_doom, // 10
|
||||
&ds->icon_settings, // 11
|
||||
&ds->icon_reboot, // 12
|
||||
&ds->icon_weather, // 13
|
||||
&ds->icon_shutdown, // 14
|
||||
&ds->icon_texteditor, // 15
|
||||
&ds->icon_spreadsheet, // 16
|
||||
&ds->icon_disks, // 17 (Disks)
|
||||
};
|
||||
|
||||
int mx = ds->mouse.x;
|
||||
int my = ds->mouse.y;
|
||||
int iy = menu_y + 5;
|
||||
int cur_cat = -1;
|
||||
|
||||
for (int i = 0; i < MENU_ROW_COUNT; i++) {
|
||||
for (int i = 0; i < menu_row_count; i++) {
|
||||
const MenuRow& row = menu_rows[i];
|
||||
|
||||
if (row.is_category) cur_cat++;
|
||||
@@ -239,11 +217,8 @@ void desktop_draw_app_menu(DesktopState* ds) {
|
||||
// Icon
|
||||
int icon_x = item_rect.x + 8;
|
||||
int icon_y = item_rect.y + (row_h - 20) / 2;
|
||||
if (row.app_id >= 0 && row.app_id < 18) {
|
||||
SvgIcon* icon = icons[row.app_id];
|
||||
if (icon && icon->pixels) {
|
||||
fb.blit_alpha(icon_x, icon_y, icon->width, icon->height, icon->pixels);
|
||||
}
|
||||
if (row.icon && row.icon->pixels) {
|
||||
fb.blit_alpha(icon_x, icon_y, row.icon->width, row.icon->height, row.icon->pixels);
|
||||
}
|
||||
|
||||
// Label
|
||||
|
||||
Reference in New Issue
Block a user