/* * main.c -- OreBolt OS master launcher * * SPDX-License-Identifier: GPL-2.0-or-later * * Boots the LVGL UI, displays the module menu, and fork+execs the * selected module binary from /apps/.mod. * * Each module is a standalone binary with its own LVGL init and * event loop. The retro_input_mapper is a standalone uinput daemon * (no LVGL dependency) integrated into the core build with hardened * flags. */ #include #include #include #include #include #include #include #include #include #include "h2_ui.h" #include "log_manager.h" #define MAX_MODULES 32 #define APPS_DIR "/apps" #define MAX_LABEL_LEN 48 /* Module metadata table -- drives the menu */ static const h2_module_t modules[] = { { H2_MOD_VAULT, "vault", "1.7", "GPL-2.0-or-later", "vault.mod" }, { H2_MOD_NETTAP, "nettaps", "1.7", "GPL-2.0-or-later", "nettaps.mod" }, { H2_MOD_SCALPEL, "scalpel", "1.7", "GPL-2.0-or-later", "scalpel.mod" }, { H2_MOD_DEPLOY, "deploy", "1.7", "GPL-2.0-or-later", "deploy.mod" }, { H2_MOD_STUDIO, "studio", "1.7", "GPL-2.0-or-later", "studio.mod" }, { H2_MOD_PROBE, "probe", "1.7", "GPL-2.0-or-later", "probe.mod" }, { H2_MOD_VTERM, "vterm", "1.7", "GPL-2.0-or-later", "vterm.mod" }, { H2_MOD_PAUTO, "pauto", "1.7", "GPL-2.0-or-later", "pauto.mod" }, { H2_MOD_EXTRACT, "extract", "1.7", "GPL-2.0-or-later", "extract.mod" }, { H2_MOD_NOISE, "noise", "1.7", "GPL-2.0-or-later", "noise.mod" }, { H2_MOD_RESET, "system", "1.7", "GPL-2.0-or-later", "reset.mod" }, { H2_MOD_RETRO, "retro", "1.7", "GPL-2.0-or-later", "retro.mod" }, { H2_MOD_RFID, "rfid", "1.7", "GPL-2.0-or-later", "rfid.mod" }, { H2_MOD_GLITCH, "glitch", "1.7", "GPL-2.0-or-later", "glitch.mod" }, { H2_MOD_PWDB, "pwdb", "1.7", "GPL-2.0-or-later", "pwdb.mod" }, { H2_MOD_PROXALARM, "proxalarm", "1.7", "GPL-2.0-or-later", "proxalarm.mod" }, { H2_MOD_BITCHAT, "bitchat", "1.7", "AGPL-3.0-only", "bitchat.mod" }, }; #define NUM_MODULES (sizeof(modules) / sizeof(modules[0])) static volatile sig_atomic_t running = 1; static void on_signal(int sig) { (void)sig; running = 0; } /* Menu UI objects */ static lv_obj_t *menu_list = NULL; static lv_obj_t *info_label = NULL; static int selection = 0; static int total_modules = 0; /* Track which .mod files actually exist on disk */ static h2_module_t available[MAX_MODULES]; static int available_count = 0; static void scan_apps(void) { available_count = 0; for (size_t i = 0; i < NUM_MODULES && available_count < MAX_MODULES; i++) { char path[128]; snprintf(path, sizeof(path), "%s/%s", APPS_DIR, modules[i].mod_binary); if (access(path, X_OK) == 0) { available[available_count++] = modules[i]; } } total_modules = available_count; } static void build_menu(void) { if (menu_list) lv_obj_clean(menu_list); for (int i = 0; i < total_modules; i++) { lv_obj_t *btn = lv_list_add_btn(menu_list, LV_SYMBOL_LIST, available[i].name); /* Color AGPL modules differently */ if (strcmp(available[i].license, "AGPL-3.0-only") == 0) { lv_obj_set_style_text_color(btn, lv_color_make(255, 180, 50), LV_PART_MAIN); } } char buf[64]; snprintf(buf, sizeof(buf), "%d modules available", total_modules); lv_label_set_text(info_label, buf); } static void scroll_to_selection(int delta) { selection += delta; if (selection < 0) selection = 0; if (selection >= total_modules) selection = total_modules - 1; /* Get the button list children and scroll to selection */ uint32_t child_cnt = lv_obj_get_child_cnt(menu_list); if (selection < (int)child_cnt) { lv_obj_t *child = lv_obj_get_child(menu_list, (uint32_t)selection); if (child) lv_obj_scroll_to_view(child, LV_ANIM_ON); } } static void launch_selected(void) { if (selection < 0 || selection >= total_modules) return; const char *binary = available[selection].mod_binary; char path[128]; snprintf(path, sizeof(path), "%s/%s", APPS_DIR, binary); LOG_INF("launching module: %s (%s)", available[selection].name, binary); pid_t pid = fork(); if (pid == 0) { /* Child: exec the module binary */ execl(path, binary, NULL); _exit(127); } if (pid < 0) { LOG_ERR("fork failed for %s", binary); return; } /* Parent: wait for child to exit */ int wstatus; waitpid(pid, &wstatus, 0); LOG_INF("module exited: %s", available[selection].name); /* Re-initialize LVGL display after child returns (framebuffer may be dirty) */ init_h2_graphics_runtime("OREBOLT OS v1.7 LAUNCHER"); } int main(int argc, char **argv) { (void)argc; (void)argv; signal(SIGINT, on_signal); signal(SIGTERM, on_signal); if (log_init("/data/vault/syslog.log") < 0) { fprintf(stderr, "[-] log_init failed\n"); return 1; } LOG_INF("OreBolt OS v1.7 starting (target: Ingenic X1000E)"); /* Initialize graphics */ init_h2_graphics_runtime("OREBOLT OS v1.7 LAUNCHER"); lv_obj_t *scr = lv_scr_act(); /* Title */ lv_obj_t *title = lv_label_create(scr); lv_label_set_text(title, "OREBOLT OS v1.7"); lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 8); lv_obj_set_style_text_color(title, COLOR_PRIMARY, LV_PART_MAIN); lv_obj_set_style_text_font(title, &lv_font_montserrat_14, LV_PART_MAIN); /* Subtitle */ lv_obj_t *sub = lv_label_create(scr); lv_label_set_text(sub, "HiFiWalker H2 | X1000E | MIPS32r2"); lv_obj_align(sub, LV_ALIGN_TOP_MID, 0, 26); lv_obj_set_style_text_color(sub, COLOR_MUTED, LV_PART_MAIN); lv_obj_set_style_text_font(sub, &lv_font_montserrat_10, LV_PART_MAIN); /* Module list */ menu_list = lv_list_create(scr); lv_obj_set_size(menu_list, 296, 170); lv_obj_align(menu_list, LV_ALIGN_TOP_MID, 0, 42); lv_obj_set_style_bg_color(menu_list, lv_color_make(18, 22, 32), LV_PART_MAIN); lv_obj_set_style_border_width(menu_list, 1, LV_PART_MAIN); lv_obj_set_style_border_color(menu_list, lv_color_make(50, 55, 70), LV_PART_MAIN); /* Info bar */ info_label = lv_label_create(scr); lv_obj_align(info_label, LV_ALIGN_BOTTOM_LEFT, 8, -16); lv_obj_set_style_text_color(info_label, COLOR_MUTED, LV_PART_MAIN); lv_obj_set_style_text_font(info_label, &lv_font_montserrat_10, LV_PART_MAIN); /* Status bar */ lv_obj_t *status = lv_label_create(scr); lv_label_set_text(status, "ROTATE=scroll PLAY=launch BACK=exit"); lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4); lv_obj_set_style_text_color(status, lv_color_make(80, 85, 100), LV_PART_MAIN); lv_obj_set_style_text_font(status, &lv_font_montserrat_10, LV_PART_MAIN); /* Scan for available modules and build menu */ scan_apps(); build_menu(); LOG_INF("%d modules available out of %zu registered", total_modules, NUM_MODULES); /* Input loop */ int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK); struct input_event ev; while (running) { lv_timer_handler(); if (input_fd >= 0 && read(input_fd, &ev, sizeof(struct input_event)) > 0) { if (ev.type == EV_REL && ev.code == H2_ROTARY_REL && ev.value != 0) { scroll_to_selection(ev.value > 0 ? 1 : -1); } if (ev.type == EV_KEY && ev.value == 1) { if (ev.code == H2_KEY_PLAY && total_modules > 0) { launch_selected(); /* Rebuild menu after module returns */ scan_apps(); build_menu(); if (selection >= total_modules) selection = (total_modules > 0) ? total_modules - 1 : 0; } if (ev.code == H2_KEY_BACK) { break; } } } usleep(5000); } if (input_fd >= 0) close(input_fd); log_close(); LOG_INF("OreBolt OS v1.7 shutting down"); return 0; }