fix: fix desktop app loading cap
This commit is contained in:
@@ -17,8 +17,6 @@ namespace gui {
|
|||||||
|
|
||||||
static constexpr int MAX_WINDOWS = 8;
|
static constexpr int MAX_WINDOWS = 8;
|
||||||
static constexpr int PANEL_HEIGHT = 32;
|
static constexpr int PANEL_HEIGHT = 32;
|
||||||
static constexpr int MAX_EXTERNAL_APPS = 32;
|
|
||||||
static constexpr int MAX_LAUNCHER_ITEMS = 64;
|
|
||||||
|
|
||||||
// External app discovered from 0:/apps/ manifest
|
// External app discovered from 0:/apps/ manifest
|
||||||
struct ExternalApp {
|
struct ExternalApp {
|
||||||
@@ -95,9 +93,10 @@ struct DesktopState {
|
|||||||
bool launcher_open;
|
bool launcher_open;
|
||||||
char launcher_query[64];
|
char launcher_query[64];
|
||||||
int launcher_query_len;
|
int launcher_query_len;
|
||||||
LauncherItem launcher_items[MAX_LAUNCHER_ITEMS];
|
LauncherItem* launcher_items; // dynamically grown
|
||||||
int launcher_item_count;
|
int launcher_item_count;
|
||||||
int launcher_results[MAX_LAUNCHER_ITEMS];
|
int launcher_item_capacity;
|
||||||
|
int* launcher_results; // indices into launcher_items, same capacity
|
||||||
int launcher_result_count;
|
int launcher_result_count;
|
||||||
int launcher_selected;
|
int launcher_selected;
|
||||||
int launcher_scroll;
|
int launcher_scroll;
|
||||||
@@ -151,9 +150,10 @@ struct DesktopState {
|
|||||||
|
|
||||||
SvgIcon icon_volume;
|
SvgIcon icon_volume;
|
||||||
|
|
||||||
// External apps discovered from 0:/apps/ manifests
|
// External apps discovered from 0:/apps/ manifests (dynamically grown)
|
||||||
ExternalApp external_apps[MAX_EXTERNAL_APPS];
|
ExternalApp* external_apps;
|
||||||
int external_app_count;
|
int external_app_count;
|
||||||
|
int external_app_capacity;
|
||||||
|
|
||||||
bool ctx_menu_open;
|
bool ctx_menu_open;
|
||||||
int ctx_menu_x, ctx_menu_y;
|
int ctx_menu_x, ctx_menu_y;
|
||||||
|
|||||||
@@ -29,9 +29,23 @@ struct MenuRow {
|
|||||||
SvgIcon* icon; // loaded menu icon (null for categories/dividers)
|
SvgIcon* icon; // loaded menu icon (null for categories/dividers)
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr int MAX_MENU_ROWS = 48;
|
inline MenuRow* menu_rows = nullptr;
|
||||||
inline MenuRow menu_rows[MAX_MENU_ROWS];
|
|
||||||
inline int menu_row_count = 0;
|
inline int menu_row_count = 0;
|
||||||
|
inline int menu_row_capacity = 0;
|
||||||
|
|
||||||
|
// Return the next menu row, doubling the array as needed.
|
||||||
|
// Returns nullptr if allocation failed.
|
||||||
|
inline MenuRow* menu_alloc_row() {
|
||||||
|
if (menu_row_count >= menu_row_capacity) {
|
||||||
|
int new_cap = menu_row_capacity ? menu_row_capacity * 2 : 64;
|
||||||
|
auto* grown = (MenuRow*)montauk::realloc(
|
||||||
|
menu_rows, (uint64_t)new_cap * sizeof(MenuRow));
|
||||||
|
if (!grown) return nullptr;
|
||||||
|
menu_rows = grown;
|
||||||
|
menu_row_capacity = new_cap;
|
||||||
|
}
|
||||||
|
return &menu_rows[menu_row_count++];
|
||||||
|
}
|
||||||
|
|
||||||
// Collapsible category state (categories 0-3 are toggleable; divider category always expanded)
|
// Collapsible category state (categories 0-3 are toggleable; divider category always expanded)
|
||||||
static constexpr int MENU_NUM_CATS = 5;
|
static constexpr int MENU_NUM_CATS = 5;
|
||||||
@@ -81,8 +95,9 @@ inline int menu_total_height() {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
inline int menu_add_category(const char* label) {
|
inline int menu_add_category(const char* label) {
|
||||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
MenuRow* rp = menu_alloc_row();
|
||||||
MenuRow& r = menu_rows[menu_row_count++];
|
if (!rp) return -1;
|
||||||
|
MenuRow& r = *rp;
|
||||||
r.is_category = true;
|
r.is_category = true;
|
||||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||||
r.app_id = DESKTOP_BUILTIN_NONE;
|
r.app_id = DESKTOP_BUILTIN_NONE;
|
||||||
@@ -94,8 +109,9 @@ inline int menu_add_category(const char* label) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline int menu_add_embedded(const char* label, DesktopBuiltinId app_id, SvgIcon* icon) {
|
inline int menu_add_embedded(const char* label, DesktopBuiltinId app_id, SvgIcon* icon) {
|
||||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
MenuRow* rp = menu_alloc_row();
|
||||||
MenuRow& r = menu_rows[menu_row_count++];
|
if (!rp) return -1;
|
||||||
|
MenuRow& r = *rp;
|
||||||
r.is_category = false;
|
r.is_category = false;
|
||||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||||
r.app_id = app_id;
|
r.app_id = app_id;
|
||||||
@@ -107,8 +123,9 @@ inline int menu_add_embedded(const char* label, DesktopBuiltinId app_id, SvgIcon
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline int menu_add_external(const char* label, const char* binary, SvgIcon* icon, bool launch_with_home) {
|
inline int menu_add_external(const char* label, const char* binary, SvgIcon* icon, bool launch_with_home) {
|
||||||
if (menu_row_count >= MAX_MENU_ROWS) return -1;
|
MenuRow* rp = menu_alloc_row();
|
||||||
MenuRow& r = menu_rows[menu_row_count++];
|
if (!rp) return -1;
|
||||||
|
MenuRow& r = *rp;
|
||||||
r.is_category = false;
|
r.is_category = false;
|
||||||
montauk::strncpy(r.label, label, sizeof(r.label));
|
montauk::strncpy(r.label, label, sizeof(r.label));
|
||||||
r.app_id = DESKTOP_BUILTIN_NONE;
|
r.app_id = DESKTOP_BUILTIN_NONE;
|
||||||
|
|||||||
@@ -174,7 +174,18 @@ static void launcher_add_item(DesktopState* ds, LauncherItemKind kind,
|
|||||||
SvgIcon* icon, int ref_index,
|
SvgIcon* icon, int ref_index,
|
||||||
const char* path = nullptr,
|
const char* path = nullptr,
|
||||||
const char* keywords = nullptr) {
|
const char* keywords = nullptr) {
|
||||||
if (ds->launcher_item_count >= MAX_LAUNCHER_ITEMS) return;
|
if (ds->launcher_item_count >= ds->launcher_item_capacity) {
|
||||||
|
int new_cap = ds->launcher_item_capacity ? ds->launcher_item_capacity * 2 : 64;
|
||||||
|
auto* items = (LauncherItem*)montauk::realloc(
|
||||||
|
ds->launcher_items, (uint64_t)new_cap * sizeof(LauncherItem));
|
||||||
|
if (!items) return;
|
||||||
|
ds->launcher_items = items;
|
||||||
|
auto* results = (int*)montauk::realloc(
|
||||||
|
ds->launcher_results, (uint64_t)new_cap * sizeof(int));
|
||||||
|
if (!results) return;
|
||||||
|
ds->launcher_results = results;
|
||||||
|
ds->launcher_item_capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
LauncherItem* item = &ds->launcher_items[ds->launcher_item_count++];
|
LauncherItem* item = &ds->launcher_items[ds->launcher_item_count++];
|
||||||
montauk::memset(item, 0, sizeof(LauncherItem));
|
montauk::memset(item, 0, sizeof(LauncherItem));
|
||||||
|
|||||||
@@ -35,79 +35,104 @@ static void extract_basename(char* out, int outSz, const char* entry) {
|
|||||||
out[base_len] = '\0';
|
out[base_len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a slot for the next external app, doubling the array as needed.
|
||||||
|
// Returns nullptr if allocation failed.
|
||||||
|
static ExternalApp* next_external_app_slot(DesktopState* ds) {
|
||||||
|
if (ds->external_app_count >= ds->external_app_capacity) {
|
||||||
|
int new_cap = ds->external_app_capacity ? ds->external_app_capacity * 2 : 32;
|
||||||
|
auto* grown = (ExternalApp*)montauk::realloc(
|
||||||
|
ds->external_apps, (uint64_t)new_cap * sizeof(ExternalApp));
|
||||||
|
if (!grown) return nullptr;
|
||||||
|
ds->external_apps = grown;
|
||||||
|
ds->external_app_capacity = new_cap;
|
||||||
|
}
|
||||||
|
return &ds->external_apps[ds->external_app_count];
|
||||||
|
}
|
||||||
|
|
||||||
void desktop_scan_apps(DesktopState* ds) {
|
void desktop_scan_apps(DesktopState* ds) {
|
||||||
ds->external_app_count = 0;
|
ds->external_app_count = 0;
|
||||||
|
|
||||||
// Ensure the apps directory exists
|
// Ensure the apps directory exists
|
||||||
montauk::fmkdir("0:/apps");
|
montauk::fmkdir("0:/apps");
|
||||||
|
|
||||||
const char* entries[32];
|
// Page through the directory with readdir_at; a single readdir call
|
||||||
int count = montauk::readdir("0:/apps", entries, 32);
|
// returns at most one batch and would silently drop apps past it.
|
||||||
|
constexpr int BATCH = 128;
|
||||||
|
const char* entries[BATCH];
|
||||||
|
int scanned = 0;
|
||||||
|
int got;
|
||||||
|
while ((got = montauk::readdir_at("0:/apps", entries, BATCH, scanned)) > 0) {
|
||||||
|
scanned += got;
|
||||||
|
|
||||||
for (int i = 0; i < count && ds->external_app_count < MAX_EXTERNAL_APPS; i++) {
|
for (int i = 0; i < got; i++) {
|
||||||
// readdir returns paths like "apps/doom/" — extract just "doom"
|
// readdir returns paths like "apps/doom/" — extract just "doom"
|
||||||
char dirname[64];
|
char dirname[64];
|
||||||
extract_basename(dirname, sizeof(dirname), entries[i]);
|
extract_basename(dirname, sizeof(dirname), entries[i]);
|
||||||
if (dirname[0] == '\0') continue;
|
if (dirname[0] == '\0') continue;
|
||||||
|
|
||||||
// Try to open the manifest in this app directory
|
// Try to open the manifest in this app directory
|
||||||
char manifest_path[128];
|
char manifest_path[128];
|
||||||
snprintf(manifest_path, sizeof(manifest_path), "0:/apps/%s/manifest.toml", dirname);
|
snprintf(manifest_path, sizeof(manifest_path), "0:/apps/%s/manifest.toml", dirname);
|
||||||
|
|
||||||
int fh = montauk::open(manifest_path);
|
int fh = montauk::open(manifest_path);
|
||||||
if (fh < 0) continue;
|
if (fh < 0) continue;
|
||||||
|
|
||||||
uint64_t sz = montauk::getsize(fh);
|
uint64_t sz = montauk::getsize(fh);
|
||||||
if (sz == 0 || sz > 4096) { montauk::close(fh); continue; }
|
if (sz == 0 || sz > 4096) { montauk::close(fh); continue; }
|
||||||
|
|
||||||
char* text = (char*)montauk::malloc(sz + 1);
|
char* text = (char*)montauk::malloc(sz + 1);
|
||||||
if (!text) { montauk::close(fh); continue; }
|
if (!text) { montauk::close(fh); continue; }
|
||||||
int bytes_read = montauk::read(fh, (uint8_t*)text, 0, sz);
|
int bytes_read = montauk::read(fh, (uint8_t*)text, 0, sz);
|
||||||
montauk::close(fh);
|
montauk::close(fh);
|
||||||
if (bytes_read != (int)sz) {
|
if (bytes_read != (int)sz) {
|
||||||
|
montauk::mfree(text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
text[sz] = '\0';
|
||||||
|
|
||||||
|
auto doc = montauk::toml::parse(text);
|
||||||
montauk::mfree(text);
|
montauk::mfree(text);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
text[sz] = '\0';
|
|
||||||
|
|
||||||
auto doc = montauk::toml::parse(text);
|
// Read manifest fields
|
||||||
montauk::mfree(text);
|
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);
|
||||||
|
bool launch_with_home = doc.get_bool("launch.pass_home_dir", true);
|
||||||
|
if (binary[0] == '\0') {
|
||||||
|
doc.destroy();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ExternalApp* app = &ds->external_apps[ds->external_app_count];
|
ExternalApp* app = next_external_app_slot(ds);
|
||||||
|
if (!app) {
|
||||||
|
doc.destroy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
montauk::strncpy(app->name, name, sizeof(app->name));
|
||||||
|
montauk::strncpy(app->category, category, sizeof(app->category));
|
||||||
|
app->menu_visible = visible;
|
||||||
|
app->launch_with_home = launch_with_home;
|
||||||
|
|
||||||
|
// 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 = {};
|
||||||
|
app->icon_path[0] = '\0';
|
||||||
|
if (icon_file[0]) {
|
||||||
|
snprintf(app->icon_path, sizeof(app->icon_path),
|
||||||
|
"0:/apps/%s/%s", dirname, icon_file);
|
||||||
|
Color defColor = colors::ICON_COLOR;
|
||||||
|
app->icon = svg_load(app->icon_path, 20, 20, defColor);
|
||||||
|
}
|
||||||
|
|
||||||
// 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);
|
|
||||||
bool launch_with_home = doc.get_bool("launch.pass_home_dir", true);
|
|
||||||
if (binary[0] == '\0') {
|
|
||||||
doc.destroy();
|
doc.destroy();
|
||||||
continue;
|
ds->external_app_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
montauk::strncpy(app->name, name, sizeof(app->name));
|
|
||||||
montauk::strncpy(app->category, category, sizeof(app->category));
|
|
||||||
app->menu_visible = visible;
|
|
||||||
app->launch_with_home = launch_with_home;
|
|
||||||
|
|
||||||
// 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 = {};
|
|
||||||
app->icon_path[0] = '\0';
|
|
||||||
if (icon_file[0]) {
|
|
||||||
snprintf(app->icon_path, sizeof(app->icon_path),
|
|
||||||
"0:/apps/%s/%s", dirname, icon_file);
|
|
||||||
Color defColor = colors::ICON_COLOR;
|
|
||||||
app->icon = svg_load(app->icon_path, 20, 20, defColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.destroy();
|
|
||||||
ds->external_app_count++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user