diff --git a/ZAPAROO_FORK.md b/ZAPAROO_FORK.md
new file mode 100644
index 000000000..3a2ae6c59
--- /dev/null
+++ b/ZAPAROO_FORK.md
@@ -0,0 +1,179 @@
+# Zaparoo Fork — Change Map and Cleanup Backlog
+
+This document maps the Zaparoo-specific changes layered on top of MiSTer-devel
+`Main_MiSTer`. It is meant to evolve as the fork iterates, so future
+contributors don't have to re-derive the architecture from `git log`.
+
+**Scope:** commits authored by `wizzomafizzo` (Callan Barrett) and `asturur`
+(Andrea Bogazzi) between commit `a2eb35e` and the tip of
+`feat/zaparoo-fb-toggle`. Upstream merges, upstream-author changes, and
+fully-reverted experiments (F2 toggle, picker/notice commands, perf tweak,
+non-blocking spawn) are intentionally omitted.
+
+> **Living doc:** when you add a Zaparoo-specific behavior, append a row to
+> the table below or revise the inconsistencies section. Stale entries are
+> worse than no entries — please prune as you cleanup.
+
+---
+
+## 1. Change map
+
+| # | Cluster | Purpose | Where it lives |
+|---|---------|---------|----------------|
+| 1 | **External launcher process management** | Spawn `zaparoo/launcher` via `agetty` on tty2; SIGTERM/SIGKILL on shutdown; bounded `waitpid`; respawn timer; 3-strike crash give-up | `support/zaparoo/alt_launcher.cpp` (`spawn`, `kill_launcher`, `alt_launcher_poll`, `return_to_normal_mode`) |
+| 2 | **Process discovery & gating** | `alt_launcher_configured()` = file exists at `zaparoo/launcher` (cached, with sticky escape bit); `alt_launcher_active()` = process running | `support/zaparoo/alt_launcher.cpp:34-43,329-332` |
+| 3 | **Custom menu RBF discovery** | `menu_rbf_name()` / `is_menu_rbf()` so a Zaparoo build can ship its own renamed menu RBF and the file_io / fpga_io / user_io paths still recognize it | `support/zaparoo/menu_rbf.cpp/.h`; consumers in `file_io.cpp`, `fpga_io.cpp`, `user_io.cpp` |
+| 4 | **Forced cfg overrides** | `alt_launcher_cfg_apply()` forces `cfg.fb_terminal = 1; cfg.recents = 1` after INI parse. Original `ALT_LAUNCHER` / `MENU_RBF` INI knobs were dropped in favor of file-existence detection | `cfg.cpp:614`, `support/zaparoo/alt_launcher.cpp:24-30` |
+| 5 | **Polling integration** | `alt_launcher_poll()` driven by main scheduler tick | `scheduler.cpp:36`, `support/zaparoo/alt_launcher.cpp:365` |
+| 6 | **TTY / framebuffer hygiene** | Clear/reset tty2 around launcher lifecycle; toggle `video_fb_enable` and `video_chvt` only on respawn paths; don't touch them on plain shutdown | `support/zaparoo/alt_launcher.cpp` (`clear_launcher_tty`, `reset_launcher_tty`) |
+| 7 | **Joypad routing into launcher** | `alt_launcher_fb_terminal_key()` translates `JOY_L2/R2/OSD` to `KEY_F1/BACKSPACE/MENU`; `joy_digital()` short-circuits to `uinp_send_key` when launcher active | `input.cpp:2475-2484`, `support/zaparoo/alt_launcher.cpp:45-62` |
+| 8 | **Native CRT rendering path** | Launcher running in CRT mode: kernel framebuffer at 320×240 RGBA8888, FPGA scans separate region at `0x3A000000`, `status[9]=1` gates it; pre-spawn blank wipes the prior frame | `support/zaparoo/alt_launcher.cpp` (`enable_native_crt_path`, `disable_native_crt_path`, `blank_native_crt_fb`); paired with `Menu_MiSTer/rtl/native_video_*.sv` |
+| 9 | **CRT mode persistence** | 1-byte `zaparoo_launcher_crt.bin` via `FileSaveConfig` / `FileLoadConfig`; loaded at menu init, applied on spawn | `support/zaparoo/alt_launcher.cpp:76-89,344,499` |
+| 10 | **Native-core auto-init** | `zaparoo_is_native_core()` matches core name `"Zaparoo Launcher"`; `zaparoo_alt_launcher_init_for_core()` auto-spawns when the FPGA loads that core | `support/zaparoo/alt_launcher.cpp:480-495`, `user_io.cpp:1543` |
+| 11 | **In-core "Launcher" OSD entry** | Adds row 31 (`ALT_LAUNCHER_MENUSUB`) to MENU_COMMON1 marked with `reboot_req` when activated | `menu.cpp:2831,2845-2849,3088-3091` |
+| 12 | **OSD/F12 overlay over running launcher** | F12 / `KEY_MENU` reaches the OSD even with launcher running; on menu core opens System Settings directly (skip file picker); F1/F9 disabled when launcher active; `vga_nag` suppressed; auto-open suppressed in CRT mode | `menu.cpp:843-852,1289,1304-1311,1334,1583,1604-1611,6727,6739,6816,6901`, `user_io.cpp:4162-4171` |
+| 13 | **Trimmed System Settings render** | `alt_launcher_render_system_menu()` overrides MENU_SYSTEM1 body for the alt-launcher path; `alt_launcher_translate_system_select()` maps trimmed menusub indices to upstream dispatch slots | `support/zaparoo/alt_launcher_menu.cpp`, `menu.cpp:6739-6745,6816-6821` |
+| 14 | **Right-side Display Centering page** | New menu state hosting H/V offset adjustment + relocated CRT toggle; persisted via 2-byte `zaparoo_video_offsets.bin`; pushed via `user_io_status_set("[13:10]" / "[17:14]")` | `support/zaparoo/display_menu.cpp/.h` *(local rename in progress: `launcher_pages.cpp/.h`)*, `support/zaparoo/alt_launcher.cpp` (offset state + setters), `menu.cpp` `MENU_ZAPAROO_DISPLAY*` cases |
+| 15 | **Escape-to-stock semantics** | Sticky `s_escaped` flag makes `alt_launcher_configured()` return `false` after a clean exit, so the rest of the session reverts to stock OSD; reboot resets it | `support/zaparoo/alt_launcher.cpp:32,38-43,229-241` |
+| 16 | **CI / build infrastructure** | Docker container build; binary named `MiSTer_Zaparoo`; "Z"-suffixed version; release / unstable CI; sync-upstream workflow; deploy script | `docker-build.sh`, `stable-build.sh`, `unstable-build.sh`, `deploy-zaparoo.sh`, `.github/build_*.sh`, `.github/workflows/*.yml` |
+| 17 | **Build-time defaults flipped** | `cfg.recents` and `LOG_FILE_ENTRY` default to enabled in Zaparoo builds | `cfg.cpp` (defaults) |
+
+---
+
+## 2. Inconsistencies and cleanup backlog
+
+These are intentional starting points for follow-up work, ordered roughly from
+"30-minute cleanup" to "needs a design pass."
+
+### 2.1 Mixed namespace prefix in one module
+`support/zaparoo/alt_launcher.h` uses two prefixes for the same conceptual thing:
+
+```c
+alt_launcher_init / _shutdown / _toggle_crt / _native_crt / _active / _configured ...
+zaparoo_is_native_core
+zaparoo_alt_launcher_init_for_core / _for_menu
+```
+
+The `zaparoo_*` wrappers are the only ones called from `user_io.cpp`. Pick one
+prefix (`zap_` is shortest) or split into two headers along that boundary.
+
+### 2.2 Three overlapping predicates
+Gating across the codebase uses `_configured()` / `_active()` / `_native_crt()`
+interchangeably without a documented rule. Current de-facto convention:
+
+| Predicate | Meaning | Used to gate |
+|-----------|---------|--------------|
+| `_configured` | binary file exists, sticky off after escape | **render paths** (System Settings body, MENU_SYSTEM1 entry, vga_nag, file-picker entry, "Launcher" row visibility, right-arrow gate) |
+| `_active` | PID alive | **input handling** (F1/F9 disable, joypad-to-launcher routing, OSD overlay, menu auto-open suppression) |
+| `_native_crt` | PID alive AND CRT mode on | **internal video state machine** (`disable_native_crt_path`, status timer) |
+
+The split is mostly principled but leaks at `MENU_SYSTEM1`, which uses
+`_configured` for both the gate AND the body delegation while `MENU_NONE2`
+auto-open uses `_active`. A user with the launcher binary present but not yet
+running sees different OSD behavior than a user without the binary at all —
+worth either a comment or a unified helper.
+
+### 2.3 Two persistence files, no shared format
+Current state:
+
+```
+zaparoo_launcher_crt.bin 1 byte (CRT mode flag)
+zaparoo_video_offsets.bin 2 bytes (h_offset, v_offset)
+```
+
+Same dir, same `FileSaveConfig` API, but no unified struct. If a third setting
+arrives this becomes a per-feature file pattern. A single `zaparoo_state.bin`
+with a versioned struct would scale better:
+
+```c
+struct zaparoo_state_v1 {
+ uint8_t magic; // 'Z'
+ uint8_t version; // 1
+ uint8_t crt; // 0 / 1
+ int8_t h_offset; // -8..+7
+ int8_t v_offset; // -8..+7
+ uint8_t reserved[3];
+};
+```
+
+Cost: a one-shot migration on existing installs (read legacy `crt.bin` if
+new file is missing; never write the legacy file again).
+
+### 2.4 Hardcoded paths scattered across modules
+- Launcher path: `zaparoo/launcher` in `alt_launcher.cpp:22`
+- Menu RBF name(s): hardcoded in `support/zaparoo/menu_rbf.cpp`
+- Persistence files: hardcoded in `alt_launcher.cpp`
+
+INI knobs were intentionally dropped (commit `72037bc`) in favor of
+file-existence detection, but the resulting literals are now in three places.
+A small `support/zaparoo/paths.h` (or a `paths.cpp` that resolves them at
+startup) would centralize them without bringing INI knobs back.
+
+### 2.5 Status-bit map exists only in code
+`status[9]` (CRT gate), `status[13:10]` (h_offset), `status[17:14]` (v_offset)
+are agreed upon between this fork and `Menu_MiSTer/feat/dual-mode-native-fb`,
+but the agreement is enforced only by lining up `user_io_status_set("[13:10]", …)`
+against the SystemVerilog `status[13:10]`. Neither side has a comment
+referencing the other.
+
+A short `STATUS.md` (or a header in `support/zaparoo/`) documenting the shared
+register layout would prevent accidental conflicts when a future feature
+allocates new bits.
+
+### 2.6 F-key handling comment is archaeology
+F2 toggle was added (`0cea191`), moved to `user_io_kbd` (`6d2690b`), and both
+were reverted (`390c141`, `4220a82`). What remains is a multi-line comment
+block in `user_io.cpp:4162-4171` that reads like commit-history narration.
+
+It can shrink to one line:
+
+```c
+// F12/KEY_MENU bypasses alt_launcher_active() so the user can open
+// the OSD on top of a running launcher.
+```
+
+### 2.7 Trimmed-menu dispatcher has a dead branch
+`alt_launcher_translate_system_select()` returns `-1` to signal "consumed
+inline." That existed for the CRT row, which has now moved to the right page.
+
+The `if (dispatch < 0) { menustate = MENU_SYSTEM1; break; }` branch in
+`MENU_SYSTEM2`'s switch is currently unreachable. Either drop it or note that
+it's reserved for future inline-handled rows.
+
+### 2.8 `alt_launcher.cpp` is doing too many jobs
+~600 lines covering: process lifecycle + video state machine + tty handling
++ status-bit pushers + offset persistence + cfg overrides. Splitting into
+`alt_launcher_proc.cpp`, `alt_launcher_video.cpp`, `alt_launcher_state.cpp`
+would make each concern testable and easier to audit. Not urgent — the file
+is still readable — but the next feature will tip it over.
+
+### 2.9 Hard-override of user INI config is silent
+`alt_launcher_cfg_apply()` quietly forces `fb_terminal=1` and `recents=1`
+after `cfg_parse`. A user who set `fb_terminal=0` in `MiSTer.ini` gets no
+warning that we're overriding them.
+
+At minimum, log it once at startup:
+
+```c
+printf("alt_launcher: forcing fb_terminal=1, recents=1 (Zaparoo build)\n");
+```
+
+### 2.10 Naming drift in the new menu page
+The right-side page was committed as `MENU_ZAPAROO_DISPLAY1/2` with files
+`display_menu.{cpp,h}`. Local in-progress refactor renames the menu states
+to `MENU_ZAPAROO_VIDEO*` / `MENU_ZAPAROO_LAUNCHER*` and the file to
+`launcher_pages.{cpp,h}`. After the rename settles, double-check that the
+file name and the symbols inside agree on what the page is called.
+
+---
+
+## 3. Boundary commit
+
+The "fork divergence" reference point used for this analysis is:
+
+```
+a2eb35eacdd7789abe3411e4d03381e7bf55309f
+```
+
+Use that as the base in `git log a2eb35e..HEAD` if you want to refresh this
+document programmatically.
diff --git a/cfg.cpp b/cfg.cpp
index 112a7f1bd..8a5f13cce 100644
--- a/cfg.cpp
+++ b/cfg.cpp
@@ -139,7 +139,6 @@ static const ini_var_t ini_vars[] =
{ "SCREENSHOT_IMAGE_FORMAT", (void *)(&(cfg.screenshot_image_format)), STRING, 0, sizeof(cfg.screenshot_image_format) - 1 },
{ "XBE2_SHIFT", (void*)(&(cfg.xbe2_shift)), UINT16, 0, 0x22F },
{ "SPD_QUIRK", (void*)(&(cfg.spd_quirk)), UINT8, 0, 3 },
- { "ALT_LAUNCHER", (void*)(&(cfg.alt_launcher)), STRING, 0, sizeof(cfg.alt_launcher) - 1 },
};
static const int nvars = (int)(sizeof(ini_vars) / sizeof(ini_var_t));
@@ -580,7 +579,6 @@ const char* cfg_get_label(uint8_t alt)
void cfg_parse()
{
memset(&cfg, 0, sizeof(cfg));
- alt_launcher_cfg_defaults();
cfg.csync = 1;
cfg.bootscreen = 1;
cfg.fb_terminal = 1;
@@ -613,6 +611,8 @@ void cfg_parse()
ini_parse(altcfg(), video_get_core_mode_name(0));
}
+ alt_launcher_cfg_apply();
+
if (strlen(cfg.vga_mode))
{
if (!strcasecmp(cfg.vga_mode, "rgb")) cfg.vga_mode_int = 0;
diff --git a/cfg.h b/cfg.h
index 6b5213d6c..9a14fe3bc 100644
--- a/cfg.h
+++ b/cfg.h
@@ -106,7 +106,6 @@ typedef struct {
char screenshot_image_format[16];
uint16_t xbe2_shift;
uint8_t spd_quirk;
- char alt_launcher[1024];
} cfg_t;
extern cfg_t cfg;
diff --git a/file_io.cpp b/file_io.cpp
index 58e9aea82..055e2b827 100644
--- a/file_io.cpp
+++ b/file_io.cpp
@@ -33,6 +33,7 @@
#include "scheduler.h"
#include "video.h"
#include "support.h"
+#include "support/zaparoo/menu_rbf.h"
#define MIN(a,b) (((a)<(b)) ? (a) : (b))
@@ -1127,7 +1128,7 @@ void setStorage(int dev)
{
device = 0;
FileSave(CONFIG_DIR"/device.bin", &dev, sizeof(int));
- fpga_load_rbf("menu.rbf");
+ fpga_load_rbf(menu_rbf_name());
}
static int orig_device = 0;
@@ -1693,7 +1694,7 @@ int ScanDirectory(char* path, int mode, const char *extension, int options, cons
// skip hidden files
if (!strncasecmp(de->d_name, ".", 1)) continue;
//skip non-selectable files
- if (!strcasecmp(de->d_name, "menu.rbf")) continue;
+ if (is_menu_rbf(de->d_name)) continue;
if (!strncasecmp(de->d_name, "menu_20", 7)) continue;
if (!strncasecmp(de->d_name, "boot", 4))
{
diff --git a/fpga_io.cpp b/fpga_io.cpp
index 1583883a6..83ff8736a 100644
--- a/fpga_io.cpp
+++ b/fpga_io.cpp
@@ -17,6 +17,7 @@
#include "shmem.h"
#include "offload.h"
#include "support/zaparoo/alt_launcher.h"
+#include "support/zaparoo/menu_rbf.h"
#include "fpga_base_addr_ac5.h"
#include "fpga_manager.h"
@@ -441,7 +442,7 @@ int fpga_load_rbf(const char *name, const char *cfg, const char *xml)
printf("Loading RBF: %s\n", name);
if(name[0] == '/') strcpy(path, name);
- else sprintf(path, "%s/%s", !strcasecmp(name, "menu.rbf") ? getStorageDir(0) : getRootDir(), name);
+ else sprintf(path, "%s/%s", is_menu_rbf(name) ? getStorageDir(0) : getRootDir(), name);
int rbf = open(path, O_RDONLY);
if (rbf < 0)
@@ -504,7 +505,7 @@ int fpga_load_rbf(const char *name, const char *cfg, const char *xml)
}
close(rbf);
- app_restart(!strcasecmp(name, "menu.rbf") ? "menu.rbf" : path, xml);
+ app_restart(is_menu_rbf(name) ? menu_rbf_name() : path, xml);
return ret;
}
diff --git a/input.cpp b/input.cpp
index 28f70a050..d8866fa22 100644
--- a/input.cpp
+++ b/input.cpp
@@ -3564,7 +3564,7 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int
if (osd_event == 2) joy_digital(input[dev].num, 0, 0, 0, BTN_OSD);
}
- if (user_io_osd_is_visible() || video_fb_state())
+ if (user_io_osd_is_visible() || video_fb_state() || alt_launcher_active())
{
if (ev->value <= 1)
{
diff --git a/menu.cpp b/menu.cpp
index d5197652e..6ce4952ce 100644
--- a/menu.cpp
+++ b/menu.cpp
@@ -50,6 +50,9 @@ along with this program. If not, see .
#include "hardware.h"
#include "menu.h"
#include "support/zaparoo/alt_launcher.h"
+#include "support/zaparoo/alt_launcher_menu.h"
+#include "support/zaparoo/launcher_pages.h"
+#include "support/zaparoo/menu_rbf.h"
#include "user_io.h"
#include "debug.h"
#include "fpga_io.h"
@@ -83,6 +86,15 @@ enum MENU
MENU_MISC1,
MENU_MISC2,
+ // Right-side companion to System Settings on the alt-launcher menu core.
+ // Top "Zaparoo Launcher" page lists sub-pages (Video) and an exit row;
+ // the Video sub-page hosts CRT mode + H/V centering offsets and binds
+ // left/right arrows to value adjustment.
+ MENU_ZAPAROO_LAUNCHER1,
+ MENU_ZAPAROO_LAUNCHER2,
+ MENU_ZAPAROO_VIDEO1,
+ MENU_ZAPAROO_VIDEO2,
+
MENU_SELECT_INI1,
MENU_SELECT_INI2,
@@ -411,8 +423,8 @@ void SelectFile(const char* path, const char* pFileExt, int Options, unsigned ch
if (Options & SCANO_CORES)
{
- strcpy(selPath, get_rbf_dir());
- if (strlen(get_rbf_name()))
+ strcpy(selPath, is_menu() ? "" : get_rbf_dir());
+ if (!is_menu() && strlen(get_rbf_name()))
{
if(strlen(selPath)) strcat(selPath, "/");
strcat(selPath, get_rbf_name());
@@ -572,7 +584,7 @@ static uint32_t menu_key_get(void)
else if (CheckTimer(repeat))
{
repeat = GetTimer(REPEATRATE);
- if (GetASCIIKey(c1) || menustate == MENU_FILE_SELECT2 || ((menustate == MENU_COMMON2) && (menusub == 17)) || ((menustate == MENU_SYSTEM2) && (menusub == 5)))
+ if (GetASCIIKey(c1) || menustate == MENU_FILE_SELECT2 || ((menustate == MENU_COMMON2) && (menusub == 17)) || ((menustate == MENU_SYSTEM2) && (menusub == 5)) || ((menustate == MENU_SYSTEM2) && alt_launcher_system_holding_reboot(menusub)))
{
c = c1;
hold_cnt++;
@@ -829,7 +841,11 @@ const char* get_rbf_name_bootcore(char *str)
static void vga_nag()
{
- if (video_fb_state())
+ // With an alt launcher configured the user has fb_terminal on by design
+ // and the CRT is fed directly by the menu core (snow pattern when status[9]=0,
+ // or the launcher's framebuffer when status[9]=1). The "fix MiSTer.ini"
+ // nag is not appropriate — just leave the OSD off without the warning.
+ if (video_fb_state() && !alt_launcher_configured())
{
EnableOsd_on(OSD_VGA);
OsdSetSize(16);
@@ -1270,7 +1286,7 @@ void HandleUI(void)
}
//prevent OSD control while script is executing on framebuffer
- if ((!video_fb_state() || video_chvt(0) != 2) && !select_ini)
+ if (((!video_fb_state() || video_chvt(0) != 2) || alt_launcher_active()) && !select_ini)
{
switch (c)
{
@@ -1285,11 +1301,14 @@ void HandleUI(void)
if (!ignore_osd_release)
menu = true;
ignore_osd_release = false;
- if(video_fb_state()) video_menu_bg(user_io_status_get("[3:1]"));
- video_fb_enable(0);
+ if (!alt_launcher_active())
+ {
+ if(video_fb_state()) video_menu_bg(user_io_status_get("[3:1]"));
+ video_fb_enable(0);
+ }
break;
case KEY_F1:
- if (is_menu() && cfg.fb_terminal)
+ if (!alt_launcher_active() && is_menu() && cfg.fb_terminal)
{
user_io_status_set("[3:1]", user_io_status_get("[3:1]") + 1);
user_io_status_save(user_io_create_config_name());
@@ -1312,7 +1331,7 @@ void HandleUI(void)
break;
case KEY_F9:
- if ((is_menu() || ((get_key_mod() & (LALT | RALT)) && (get_key_mod() & (LCTRL | RCTRL))) || has_fb_terminal) && cfg.fb_terminal)
+ if (!alt_launcher_active() && (is_menu() || ((get_key_mod() & (LALT | RALT)) && (get_key_mod() & (LCTRL | RCTRL))) || has_fb_terminal) && cfg.fb_terminal)
{
video_chvt(1);
video_fb_enable(!video_fb_state());
@@ -1376,7 +1395,6 @@ void HandleUI(void)
break;
}
}
-
if (select_ini)
{
DISKLED_ON;
@@ -1556,7 +1574,13 @@ void HandleUI(void)
menustate = MENU_UNLOCK1;
osd_code_entry[0] = 0;
}
- else if (menu || (is_menu() && !video_fb_state()) || (menustate == MENU_NONE2 && !mgl->done && mgl->state == 1))
+ // On the menu core without an alt launcher, the menu *is* the screen —
+ // keep auto-opening the OSD whenever fb_terminal is off. With an alt
+ // launcher running, that rule would re-open System Settings the moment
+ // the user closes it (in CRT mode video_fb_state is false), so the OSD
+ // could never actually close. Suppress the auto-open in that case;
+ // explicit F12/MENU presses still open and close it normally.
+ else if (menu || (is_menu() && !video_fb_state() && !alt_launcher_active()) || (menustate == MENU_NONE2 && !mgl->done && mgl->state == 1))
{
OsdSetSize(16);
menusub = 0;
@@ -1577,8 +1601,19 @@ void HandleUI(void)
}
else if (is_menu())
{
- menusub = 6;
- SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, MENU_SYSTEM1);
+ if (alt_launcher_configured())
+ {
+ // With an alt launcher, the file picker is not the user's
+ // natural entry point — they want the OSD overlay (System
+ // Settings) directly so they can flip CRT mode etc.
+ menustate = MENU_SYSTEM1;
+ menusub = 0;
+ }
+ else
+ {
+ menusub = 6;
+ SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, MENU_SYSTEM1);
+ }
}
else if (is_minimig())
{
@@ -3107,7 +3142,7 @@ void HandleUI(void)
}
}
- if(!hold_cnt && reboot_req) fpga_load_rbf("menu.rbf");
+ if(!hold_cnt && reboot_req) fpga_load_rbf(menu_rbf_name());
break;
case MENU_VIDEOPROC1:
@@ -6685,7 +6720,11 @@ void HandleUI(void)
/* system menu */
/******************************************************************/
case MENU_SYSTEM1:
- if (video_fb_state())
+ // Without an alt launcher, the wallpaper / fb_terminal flow can't coexist
+ // with this menu — bail out so vga_nag can show the MiSTer.ini warning.
+ // With an alt launcher the OSD overlay is exactly what we want on top of
+ // the running launcher, so let it render through.
+ if (video_fb_state() && !alt_launcher_configured())
{
menustate = MENU_NONE1;
break;
@@ -6695,6 +6734,15 @@ void HandleUI(void)
helptext_idx = 0;
parentstate = menustate;
+ // alt-launcher mode delegates the entire System menu render to a
+ // support helper so this upstream block stays untouched.
+ if (alt_launcher_configured())
+ {
+ cr = alt_launcher_render_system_menu(menusub, &menumask, &reboot_req, &sysinfo_timer);
+ menustate = MENU_SYSTEM2;
+ break;
+ }
+
m = 0;
OsdSetTitle("System Settings", OSD_ARROW_LEFT);
menumask = 0x7F;
@@ -6765,12 +6813,21 @@ void HandleUI(void)
case MENU_SYSTEM2:
if (menu)
{
+ if (alt_launcher_configured())
+ {
+ // F12 toggles: the OSD opened directly into System Settings,
+ // pressing F12 again closes it instead of opening the core picker.
+ menustate = MENU_NONE1;
+ break;
+ }
SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, MENU_SYSTEM1);
break;
}
else if (select)
{
- switch (menusub)
+ int dispatch = alt_launcher_translate_system_select(menusub);
+ if (dispatch < 0) { menustate = MENU_SYSTEM1; break; }
+ switch (dispatch)
{
case 0:
if (getStorage(1) || isUSBMounted()) setStorage(!getStorage(1));
@@ -6841,8 +6898,98 @@ void HandleUI(void)
{
menustate = MENU_MISC1;
}
+ else if (right && alt_launcher_configured())
+ {
+ menustate = MENU_ZAPAROO_LAUNCHER1;
+ menusub = 0;
+ }
+
+ if (!hold_cnt && reboot_req) fpga_load_rbf(menu_rbf_name());
+ break;
- if (!hold_cnt && reboot_req) fpga_load_rbf("menu.rbf");
+ /******************************************************************/
+ /* zaparoo launcher pages (right-side sibling of System) */
+ /******************************************************************/
+ case MENU_ZAPAROO_LAUNCHER1:
+ if (!alt_launcher_configured())
+ {
+ menustate = MENU_NONE1;
+ break;
+ }
+ helptext_idx = 0;
+ parentstate = menustate;
+ launcher_page_render(menusub, &menumask);
+ menustate = MENU_ZAPAROO_LAUNCHER2;
+ break;
+
+ case MENU_ZAPAROO_LAUNCHER2:
+ if (menu)
+ {
+ menustate = MENU_NONE1;
+ break;
+ }
+ if (left)
+ {
+ menustate = MENU_SYSTEM1;
+ menusub = 0;
+ break;
+ }
+ if (right && menusub == 0)
+ {
+ menustate = MENU_ZAPAROO_VIDEO1;
+ menusub = 0;
+ break;
+ }
+ if (select)
+ {
+ int act = launcher_page_handle_select(menusub);
+ if (act == 1)
+ {
+ menustate = MENU_ZAPAROO_VIDEO1;
+ menusub = 0;
+ }
+ else if (act == 0)
+ {
+ menustate = MENU_NONE1;
+ }
+ }
+ break;
+
+ case MENU_ZAPAROO_VIDEO1:
+ if (!alt_launcher_configured())
+ {
+ menustate = MENU_NONE1;
+ break;
+ }
+ helptext_idx = 0;
+ parentstate = menustate;
+ video_page_render(menusub, &menumask);
+ menustate = MENU_ZAPAROO_VIDEO2;
+ break;
+
+ case MENU_ZAPAROO_VIDEO2:
+ if (menu)
+ {
+ menustate = MENU_ZAPAROO_LAUNCHER1;
+ menusub = 0;
+ break;
+ }
+ if (left || right || plus || minus)
+ {
+ video_page_adjust(menusub, (right || plus) ? +1 : -1);
+ menustate = MENU_ZAPAROO_VIDEO1;
+ break;
+ }
+ if (select)
+ {
+ if (!video_page_handle_select(menusub))
+ {
+ menustate = MENU_ZAPAROO_LAUNCHER1;
+ menusub = 0;
+ break;
+ }
+ menustate = MENU_ZAPAROO_VIDEO1;
+ }
break;
case MENU_JOYSYSMAP:
diff --git a/stable-build.sh b/stable-build.sh
index 4e15e0f9d..2cd076cc4 100755
--- a/stable-build.sh
+++ b/stable-build.sh
@@ -54,7 +54,6 @@ fi
STABLE_NAME=$(basename "${STABLE_FILE}")
STABLE_DATE=${STABLE_NAME#MiSTer_}
-FORK_COMMITS=$(git rev-list --reverse --no-merges "${UPSTREAM_REF}..${FORK_HEAD}")
if [ -n "${METADATA_FILE}" ]; then
cat >"${METADATA_FILE}" < Building from upstream ${STABLE_NAME} with Zaparoo ${FORK_SHORT_SHA}"
git worktree add --detach "${TMP_WORKTREE}" "${STABLE_COMMIT}" >/dev/null
-if [ -n "${FORK_COMMITS}" ]; then
- git -C "${TMP_WORKTREE}" cherry-pick --no-commit ${FORK_COMMITS}
+# Apply the cumulative fork-only diff (relative to upstream master) so revert
+# pairs and other intra-fork conflicts cancel out — replaying commit-by-commit
+# would re-expose them. -3 falls back to a 3-way merge when stable's content
+# for a shared file has drifted from upstream master. MiSTer.ini is excluded:
+# the fork's only change is an uncomment of a default-valued line, and stable's
+# example ini drifts often enough to cause spurious conflicts.
+FORK_DIFF=$(git diff --binary "${UPSTREAM_REF}..${FORK_HEAD}" -- . ':(exclude)MiSTer.ini')
+if [ -n "${FORK_DIFF}" ]; then
+ printf '%s\n' "${FORK_DIFF}" | git -C "${TMP_WORKTREE}" apply -3 --index
fi
"${TMP_WORKTREE}/docker-build.sh" "$@"
diff --git a/support/zaparoo/alt_launcher.cpp b/support/zaparoo/alt_launcher.cpp
index 0c5554541..e356dc1ae 100644
--- a/support/zaparoo/alt_launcher.cpp
+++ b/support/zaparoo/alt_launcher.cpp
@@ -9,27 +9,43 @@
#include
#include
#include
+#include
#include
#include "cfg.h"
#include "file_io.h"
#include "hardware.h"
#include "input.h"
+#include "menu.h"
+#include "shmem.h"
#include "user_io.h"
#include "video.h"
-void alt_launcher_cfg_defaults(void)
+static const char s_launcher_path[] = "zaparoo/launcher";
+
+void alt_launcher_cfg_apply(void)
{
+ // Override any user ini values: this fork is single-purpose and the
+ // launcher needs both flags on to render.
+ cfg.fb_terminal = 1;
cfg.recents = 1;
}
+static bool s_escaped = false;
+
bool alt_launcher_configured(void)
{
- return cfg.alt_launcher[0] && cfg.fb_terminal;
+ // After a clean exit / give-up, masquerade as not-configured so the rest
+ // of the OSD reverts to stock menu behavior for the rest of this session.
+ // Reboot re-execs MiSTer and resets this back to the file-existence cache.
+ if (s_escaped) return false;
+ static int cached = -1;
+ if (cached < 0) cached = FileExists(s_launcher_path, 0) ? 1 : 0;
+ return cached != 0;
}
uint16_t alt_launcher_fb_terminal_key(uint32_t mask, bool osd_button)
{
- if (!cfg.alt_launcher[0])
+ if (!alt_launcher_configured())
return 0;
if (osd_button)
@@ -58,6 +74,45 @@ static const int s_vt = 2;
static const char s_tty[] = "tty2";
static const char s_tty_path[] = "/dev/tty2";
static const char s_fb_mode_path[] = "/sys/module/MiSTer_fb/parameters/mode";
+static const char s_crt_state_file[] = "zaparoo_launcher_crt.bin";
+static const char s_offsets_file[] = "zaparoo_video_offsets.bin";
+
+static int8_t s_h_offset = 0;
+static int8_t s_v_offset = 0;
+
+static int8_t clamp_offset(int8_t v)
+{
+ if (v < -8) return -8;
+ if (v > 7) return 7;
+ return v;
+}
+
+static bool load_persisted_native_crt(void)
+{
+ uint8_t v = 0;
+ FileLoadConfig(s_crt_state_file, &v, sizeof(v));
+ return v != 0;
+}
+
+static void save_persisted_native_crt(bool crt)
+{
+ uint8_t v = crt ? 1 : 0;
+ FileSaveConfig(s_crt_state_file, &v, sizeof(v));
+}
+
+static void load_persisted_offsets(void)
+{
+ int8_t buf[2] = { 0, 0 };
+ FileLoadConfig(s_offsets_file, buf, sizeof(buf));
+ s_h_offset = clamp_offset(buf[0]);
+ s_v_offset = clamp_offset(buf[1]);
+}
+
+static void save_persisted_offsets(void)
+{
+ int8_t buf[2] = { s_h_offset, s_v_offset };
+ FileSaveConfig(s_offsets_file, buf, sizeof(buf));
+}
static void set_launcher_fb_mode(int fmt, int rb, int width, int height, int stride, bool log = true)
{
@@ -79,6 +134,27 @@ static void set_native_crt_fb_mode(bool log = true)
set_launcher_fb_mode(8888, 1, 320, 240, 1280, log);
}
+static void blank_native_crt_fb(void)
+{
+ // CRT path doesn't read /dev/fb0 (kernel FB at 0x22000000). The launcher
+ // runs a worker that copies the top-left 320x240 from /dev/fb0 into a
+ // separate FPGA-mapped region at 0x3A000000 (control word + two 320x240
+ // RGBA buffers). The FPGA scans out from that region. Nothing zeros it
+ // across launcher restarts or software reboots, so the previous session's
+ // last frame ghosts in until the launcher's writer thread starts.
+ const uint32_t native_addr = 0x3A000000u;
+ const uint32_t native_size = 0x000A0000u;
+ void *p = shmem_map(native_addr, native_size);
+ if (!p)
+ {
+ printf("alt_launcher: blank native shmem_map(0x%x, %u) failed\n", native_addr, native_size);
+ return;
+ }
+ memset(p, 0, native_size);
+ shmem_unmap(p, native_size);
+ printf("alt_launcher: blanked %u bytes of CRT native video DDR at 0x%x\n", native_size, native_addr);
+}
+
static void clear_launcher_tty(void)
{
int tty_fd = open(s_tty_path, O_WRONLY | O_CLOEXEC);
@@ -148,7 +224,17 @@ static void enable_native_crt_path(void)
{
set_vga_fb(0);
video_fb_enable(0);
+
+ // Double-write with a settle window so the kernel module's 320x240 layout
+ // is live before status[9] flips. Without this, the launcher renders for
+ // up to a second under stale dims (the post-fork retry timer used to be
+ // what eventually fixed the picture).
+ set_native_crt_fb_mode(false);
+ usleep(100000);
set_native_crt_fb_mode();
+
+ blank_native_crt_fb();
+
user_io_status_set("[9]", 1);
s_native_status_timer = GetTimer(500);
if (!s_native_status_timer) s_native_status_timer = 1;
@@ -177,6 +263,7 @@ static void return_to_normal_mode(void)
s_respawn_timer = 0;
s_crash_count = 0;
s_gave_up = true;
+ s_escaped = true;
}
static void reset_launcher_state(void)
@@ -186,6 +273,7 @@ static void reset_launcher_state(void)
s_crash_count = 0;
s_gave_up = false;
s_init_pending = false;
+ s_escaped = false;
}
static void kill_launcher(pid_t pid, int sig)
@@ -197,7 +285,7 @@ static void kill_launcher(pid_t pid, int sig)
static void spawn(void)
{
char path[2100];
- strncpy(path, getFullPath(cfg.alt_launcher), sizeof(path) - 1);
+ strncpy(path, getFullPath(s_launcher_path), sizeof(path) - 1);
path[sizeof(path) - 1] = '\0';
static const char cmd[] =
@@ -262,6 +350,11 @@ static void spawn(void)
input_switch(0);
user_io_status_set("[9]", 1);
}
+
+ // The launcher grabs input as soon as it starts. If the OSD is still
+ // up (e.g. user toggled CRT mode or hit Reboot from System Settings),
+ // it would trap input with no way to dismiss it — drop it now.
+ if (menu_present()) MenuHide();
}
bool alt_launcher_active(void)
@@ -269,9 +362,55 @@ bool alt_launcher_active(void)
return s_pid != 0;
}
+bool alt_launcher_native_crt(void)
+{
+ return s_native_crt && s_pid != 0;
+}
+
+int8_t alt_launcher_h_offset(void)
+{
+ return s_h_offset;
+}
+
+int8_t alt_launcher_v_offset(void)
+{
+ return s_v_offset;
+}
+
+void alt_launcher_set_h_offset(int8_t v)
+{
+ s_h_offset = clamp_offset(v);
+ save_persisted_offsets();
+ // 4-bit two's-complement bit pattern; FPGA reinterprets as signed -8..+7.
+ user_io_status_set("[13:10]", (uint32_t)((uint8_t)s_h_offset & 0xF));
+}
+
+void alt_launcher_set_v_offset(int8_t v)
+{
+ s_v_offset = clamp_offset(v);
+ save_persisted_offsets();
+ user_io_status_set("[17:14]", (uint32_t)((uint8_t)s_v_offset & 0xF));
+}
+
+void alt_launcher_toggle_crt(void)
+{
+ bool current_crt = alt_launcher_native_crt();
+ bool target_crt = !current_crt;
+
+ save_persisted_native_crt(target_crt);
+
+ printf("alt_launcher: toggle CRT path %d -> %d\n", current_crt, target_crt);
+
+ // Shutdown drops status[9], releases the FB mode and restores HPS framebuffer
+ // state regardless of whether the launcher was running. After it returns we
+ // always have a clean slate to spawn the next launcher invocation.
+ alt_launcher_shutdown();
+ alt_launcher_init(target_crt);
+}
+
void alt_launcher_init(bool native_crt)
{
- if (!cfg.alt_launcher[0] || !cfg.fb_terminal || s_pid || s_gave_up)
+ if (!alt_launcher_configured() || s_pid || s_gave_up)
return;
s_crash_count = 0;
s_respawn_timer = 0;
@@ -306,6 +445,18 @@ void alt_launcher_poll(void)
int sig = WIFSIGNALED(status) ? WTERMSIG(status) : 0;
bool escaped = (exited && exit_status == 0) || sig == SIGTERM || sig == SIGINT;
bool crashed = !escaped && (sig != 0 || (exited && exit_status != 0));
+ // Any exit while in CRT mode drops back to HDMI / no launcher
+ // for the rest of this session — respawning into CRT after the
+ // user already left it is a UX trap. The persisted CRT
+ // preference is intentionally untouched (return_to_normal_mode
+ // only clears the in-memory s_native_crt), so the next reboot
+ // honors whatever the user last set in System Settings.
+ if (s_native_crt)
+ {
+ printf("alt_launcher: exited while in CRT mode, dropping to HDMI normal mode\n");
+ return_to_normal_mode();
+ return;
+ }
if (escaped)
{
printf("alt_launcher: exited, returning to normal mode until restart\n");
@@ -326,7 +477,7 @@ void alt_launcher_poll(void)
return;
}
- if (!cfg.alt_launcher[0] || !cfg.fb_terminal)
+ if (!alt_launcher_configured())
return;
if (s_init_pending)
@@ -403,10 +554,22 @@ bool zaparoo_is_native_core(void)
void zaparoo_alt_launcher_init_for_core(void)
{
- if (cfg.alt_launcher[0] && cfg.fb_terminal && zaparoo_is_native_core())
+ if (alt_launcher_configured() && zaparoo_is_native_core())
{
printf("alt_launcher: initializing CRT mode for core '%s' '%s'\n",
user_io_get_core_name(1), user_io_get_core_name(0));
alt_launcher_init(true);
}
}
+
+void zaparoo_alt_launcher_init_for_menu(void)
+{
+ bool crt = load_persisted_native_crt();
+ load_persisted_offsets();
+ printf("alt_launcher: initializing menu launcher (persisted crt=%d, h=%d, v=%d)\n",
+ crt, s_h_offset, s_v_offset);
+ // Push the persisted offsets to the FPGA now that the menu RBF is loaded.
+ user_io_status_set("[13:10]", (uint32_t)((uint8_t)s_h_offset & 0xF));
+ user_io_status_set("[17:14]", (uint32_t)((uint8_t)s_v_offset & 0xF));
+ alt_launcher_init(crt);
+}
diff --git a/support/zaparoo/alt_launcher.h b/support/zaparoo/alt_launcher.h
index 3ce4ba959..0861fb281 100644
--- a/support/zaparoo/alt_launcher.h
+++ b/support/zaparoo/alt_launcher.h
@@ -2,16 +2,27 @@
#include
-#define ALT_LAUNCHER_MENUSUB 31
+#define ALT_LAUNCHER_MENUSUB 31
-void alt_launcher_init(bool native_crt = false);
+void alt_launcher_init(bool native_crt);
void alt_launcher_poll(void);
void alt_launcher_shutdown(void);
+void alt_launcher_toggle_crt(void);
+bool alt_launcher_native_crt(void);
bool alt_launcher_active(void);
bool alt_launcher_configured(void);
-void alt_launcher_cfg_defaults(void);
+// Display centering: signed offsets clamped to -8..+7. Setters update the
+// in-memory cache, persist to the config dir, and push to the FPGA via
+// user_io_status_set so the change takes effect immediately.
+int8_t alt_launcher_h_offset(void);
+int8_t alt_launcher_v_offset(void);
+void alt_launcher_set_h_offset(int8_t v);
+void alt_launcher_set_v_offset(int8_t v);
+
+void alt_launcher_cfg_apply(void);
uint16_t alt_launcher_fb_terminal_key(uint32_t mask, bool osd_button);
bool zaparoo_is_native_core(void);
void zaparoo_alt_launcher_init_for_core(void);
+void zaparoo_alt_launcher_init_for_menu(void);
diff --git a/support/zaparoo/alt_launcher_menu.cpp b/support/zaparoo/alt_launcher_menu.cpp
new file mode 100644
index 000000000..e0bb50dc2
--- /dev/null
+++ b/support/zaparoo/alt_launcher_menu.cpp
@@ -0,0 +1,94 @@
+#include "alt_launcher_menu.h"
+#include "alt_launcher.h"
+#include "file_io.h"
+#include "osd.h"
+
+#include
+#include
+#include
+
+extern const char *version;
+
+// The OSD column used for the Exit row label, sized to match
+// menu.cpp's STD_EXIT define (a local #define there, kept in sync
+// here rather than re-exported to avoid a header touch).
+#define ALT_STD_EXIT " exit"
+
+int alt_launcher_render_system_menu(int menusub, uint64_t *menumask,
+ int *reboot_req,
+ long *sysinfo_timer)
+{
+ if (!alt_launcher_configured()) return 0;
+
+ char s[256];
+ int m = 0;
+
+ // Right arrow indicates a sibling page (Zaparoo Launcher) accessible
+ // via the right-arrow key — see MENU_ZAPAROO_LAUNCHER1 in menu.cpp.
+ OsdSetTitle("System Settings", OSD_ARROW_LEFT | OSD_ARROW_RIGHT);
+ *menumask = 0x1F;
+
+ OsdWrite(m++);
+ sprintf(s, " MiSTer v%s", version + 5);
+ {
+ char str[8] = {};
+ FILE *f = fopen("/MiSTer.version", "r");
+ if (f)
+ {
+ if (fread(str, 6, 1, f)) sprintf(s, " MiSTer v%s, OS v%s", version + 5, str);
+ fclose(f);
+ }
+ }
+ OsdWrite(m++, s);
+
+ {
+ uint64_t avail = 0;
+ struct statvfs buf;
+ memset(&buf, 0, sizeof(buf));
+ if (!statvfs(getRootDir(), &buf)) avail = buf.f_bsize * buf.f_bavail;
+ if (avail < (10ull * 1024 * 1024 * 1024))
+ sprintf(s, " Available space: %llumb", (unsigned long long)(avail / (1024 * 1024)));
+ else
+ sprintf(s, " Available space: %llugb", (unsigned long long)(avail / (1024 * 1024 * 1024)));
+ OsdWrite(m + 2, s, 0, 0);
+ }
+
+ OsdWrite(m++, "");
+ OsdWrite(m++, "");
+ m++;
+ OsdWrite(m++, "");
+ OsdWrite(m++, "");
+
+ OsdWrite(m++, " Remap keyboard \x16", menusub == 0);
+ OsdWrite(m++, " Define joystick buttons \x16", menusub == 1);
+ OsdWrite(m++, " Scripts \x16", menusub == 2);
+
+ OsdWrite(m++, "");
+ int cr = m;
+ OsdWrite(m++, " Reboot (hold \x16 cold reboot)", menusub == 3);
+ *sysinfo_timer = 0;
+ *reboot_req = 0;
+
+ while (m < OsdGetSize() - 1) OsdWrite(m++, "");
+ OsdWrite(15, ALT_STD_EXIT, menusub == 4);
+
+ return cr;
+}
+
+int alt_launcher_translate_system_select(int menusub)
+{
+ if (!alt_launcher_configured()) return menusub;
+
+ // Maps trimmed-menu menusub to upstream MENU_SYSTEM2 dispatch index:
+ // 0 Remap -> 1, 1 Define joy -> 2, 2 Scripts -> 3, 3 Reboot -> 5,
+ // 4 Exit -> 6. CRT mode lives on the Zaparoo Launcher's Video
+ // sub-page now, not here.
+ static const int map[] = { 1, 2, 3, 5, 6 };
+ if (menusub < 0 || menusub >= (int)(sizeof(map) / sizeof(map[0]))) return -1;
+ return map[menusub];
+}
+
+bool alt_launcher_system_holding_reboot(int menusub)
+{
+ return alt_launcher_configured() && menusub == 3;
+}
diff --git a/support/zaparoo/alt_launcher_menu.h b/support/zaparoo/alt_launcher_menu.h
new file mode 100644
index 000000000..797a9e194
--- /dev/null
+++ b/support/zaparoo/alt_launcher_menu.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include
+#include
+
+// Trimmed System Settings menu for alt-launcher mode. The launcher is
+// the only "core" running on top of menu.rbf, so storage toggle and
+// the bundled help PDF are irrelevant — this layout hides them and
+// exposes Remap(0), Define joy(1), Scripts(2), Reboot(3), Exit(4).
+// CRT mode lives on the Zaparoo Launcher's Video sub-page.
+//
+// All three helpers are no-ops / return safe defaults when
+// alt_launcher_configured() is false, so menu.cpp's hook sites can
+// be unconditional. Caller must have already set OsdSetSize(16);
+// returns the row of the Reboot line so the cold-reboot animation
+// in MENU_SYSTEM2 can target the right OSD slot.
+int alt_launcher_render_system_menu(int menusub, uint64_t *menumask,
+ int *reboot_req,
+ long *sysinfo_timer);
+
+// Translate a select press on the trimmed menu to its upstream
+// MENU_SYSTEM2 switch case index. Returns -1 when the press is
+// consumed inline (CRT toggle); the caller should re-render
+// MENU_SYSTEM1 and skip the dispatch.
+int alt_launcher_translate_system_select(int menusub);
+
+// menu.cpp's hold-to-cold-reboot key-repeat gate hard-codes
+// menusub==5 (the upstream Reboot row). The trimmed menu places
+// Reboot at menusub 3, so menu.cpp ORs in this helper.
+bool alt_launcher_system_holding_reboot(int menusub);
diff --git a/support/zaparoo/launcher_pages.cpp b/support/zaparoo/launcher_pages.cpp
new file mode 100644
index 000000000..9bf2a31e5
--- /dev/null
+++ b/support/zaparoo/launcher_pages.cpp
@@ -0,0 +1,101 @@
+#include "launcher_pages.h"
+#include "alt_launcher.h"
+#include "osd.h"
+
+#include
+
+// Mirrors menu.cpp's STD_EXIT (a local #define there, kept in sync
+// here rather than re-exporting it via a header touch).
+#define LAUNCHER_STD_EXIT " exit"
+
+void launcher_page_render(int menusub, uint64_t *menumask)
+{
+ OsdSetSize(16);
+ OsdSetTitle("Zaparoo Launcher", OSD_ARROW_LEFT);
+ *menumask = 0x3; // Video, Exit
+
+ int m = 0;
+ OsdWrite(m++);
+ OsdWrite(m++, "");
+ OsdWrite(m++, "");
+ OsdWrite(m++, "");
+ OsdWrite(m++, "");
+
+ OsdWrite(m++, " Video \x16", menusub == 0);
+
+ while (m < OsdGetSize() - 1) OsdWrite(m++, "");
+ OsdWrite(15, LAUNCHER_STD_EXIT, menusub == 1);
+}
+
+int launcher_page_handle_select(int menusub)
+{
+ switch (menusub)
+ {
+ case 0: return 1;
+ case 1: return 0;
+ default: return -1;
+ }
+}
+
+void video_page_render(int menusub, uint64_t *menumask)
+{
+ OsdSetSize(16);
+ // No arrow flags: left/right are bound to value adjustment on this
+ // page, not sibling navigation.
+ OsdSetTitle("Video", 0);
+ *menumask = 0xF; // CRT, H Offset, V Offset, Exit
+
+ char s[64];
+ int m = 0;
+
+ OsdWrite(m++);
+ OsdWrite(m++, "");
+ OsdWrite(m++, "");
+
+ sprintf(s, " CRT mode: %-15s", alt_launcher_native_crt() ? "On" : "Off");
+ OsdWrite(m++, s, menusub == 0);
+
+ OsdWrite(m++, "");
+ sprintf(s, " H Offset: %+3d", alt_launcher_h_offset());
+ OsdWrite(m++, s, menusub == 1);
+
+ sprintf(s, " V Offset: %+3d", alt_launcher_v_offset());
+ OsdWrite(m++, s, menusub == 2);
+
+ while (m < OsdGetSize() - 1) OsdWrite(m++, "");
+ OsdWrite(15, LAUNCHER_STD_EXIT, menusub == 3);
+}
+
+bool video_page_handle_select(int menusub)
+{
+ switch (menusub)
+ {
+ case 0:
+ alt_launcher_toggle_crt();
+ return true;
+ case 3:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void video_page_adjust(int menusub, int dir)
+{
+ if (dir == 0) return;
+ int8_t step = (int8_t)(dir > 0 ? 1 : -1);
+ switch (menusub)
+ {
+ case 0:
+ alt_launcher_toggle_crt();
+ break;
+ case 1:
+ alt_launcher_set_h_offset((int8_t)(alt_launcher_h_offset() + step));
+ break;
+ case 2:
+ alt_launcher_set_v_offset((int8_t)(alt_launcher_v_offset() + step));
+ break;
+ default:
+ break;
+ }
+}
diff --git a/support/zaparoo/launcher_pages.h b/support/zaparoo/launcher_pages.h
new file mode 100644
index 000000000..0f7b1afdd
--- /dev/null
+++ b/support/zaparoo/launcher_pages.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+#include
+
+// Right-side companion to the trimmed System Settings menu, reachable
+// via the right arrow when alt_launcher_configured() is true. This file
+// hosts both pages of that companion: the top-level "Zaparoo Launcher"
+// page and the nested "Video" sub-page that owns the CRT mode toggle
+// plus the H/V centering offsets.
+//
+// The Video page binds left/right arrows to value adjustment instead of
+// sibling navigation, which is why it lives behind a sub-page rather
+// than directly under System Settings.
+
+// Top "Zaparoo Launcher" page. menusub layout: 0 = Video, 1 = Exit.
+void launcher_page_render(int menusub, uint64_t *menumask);
+
+// Translates a select press on the Launcher page.
+// 1 -> entered Video sub-page
+// 0 -> Exit pressed (close OSD)
+// -1 -> no-op
+int launcher_page_handle_select(int menusub);
+
+// Video sub-page. menusub layout: 0 = CRT mode, 1 = H Offset,
+// 2 = V Offset, 3 = Exit.
+//
+// The OSD is closed automatically by the launcher spawn path (see
+// MenuHide() in spawn() in alt_launcher.cpp) when CRT toggling
+// triggers a respawn — these helpers don't need to signal that.
+void video_page_render(int menusub, uint64_t *menumask);
+
+// Returns true if the press was consumed (re-render only); false when
+// Exit was selected (caller pops back to the Launcher page).
+bool video_page_handle_select(int menusub);
+
+// Adjust the highlighted row by `dir` (-1 / +1). Toggles CRT mode on
+// the CRT row regardless of sign; ±1 on the H/V offset rows; no-op
+// elsewhere.
+void video_page_adjust(int menusub, int dir);
diff --git a/support/zaparoo/menu_rbf.cpp b/support/zaparoo/menu_rbf.cpp
new file mode 100644
index 000000000..80db5af2a
--- /dev/null
+++ b/support/zaparoo/menu_rbf.cpp
@@ -0,0 +1,21 @@
+#include "menu_rbf.h"
+#include
+#include
+
+static const char s_menu_rbf_path[] = "zaparoo/menu_zaparoo.rbf";
+
+const char *menu_rbf_name(void)
+{
+ return s_menu_rbf_path;
+}
+
+bool is_menu_rbf(const char *name)
+{
+ if (!name || !name[0]) return false;
+ if (!strcasecmp(name, "menu.rbf")) return true;
+ if (!strcasecmp(name, s_menu_rbf_path)) return true;
+ const char *base = strrchr(s_menu_rbf_path, '/');
+ base = base ? base + 1 : s_menu_rbf_path;
+ if (base[0] && !strcasecmp(name, base)) return true;
+ return false;
+}
diff --git a/support/zaparoo/menu_rbf.h b/support/zaparoo/menu_rbf.h
new file mode 100644
index 000000000..72da01dba
--- /dev/null
+++ b/support/zaparoo/menu_rbf.h
@@ -0,0 +1,4 @@
+#pragma once
+
+const char *menu_rbf_name(void);
+bool is_menu_rbf(const char *name);
diff --git a/user_io.cpp b/user_io.cpp
index 3c136bf5f..76169251b 100644
--- a/user_io.cpp
+++ b/user_io.cpp
@@ -41,6 +41,7 @@
#include "scaler.h"
#include "support.h"
#include "support/zaparoo/alt_launcher.h"
+#include "support/zaparoo/menu_rbf.h"
static char core_path[1024] = {};
static char rbf_path[1024] = {};
@@ -1454,6 +1455,10 @@ void user_io_init(const char *path, const char *xml)
app_restart(path, xml, main);
}
+ // Zaparoo: u-boot/stock binary may have loaded the system menu.rbf before we got here.
+ // Force a reload of our hardcoded menu RBF if we booted without an explicit RBF path.
+ if (is_menu() && !rbf_path[0]) fpga_load_rbf(menu_rbf_name());
+
uint8_t hotswap[4] = {};
ide_reset(hotswap);
@@ -1528,7 +1533,7 @@ void user_io_init(const char *path, const char *xml)
else if (is_menu())
{
user_io_status_set("[4]", (cfg.menu_pal) ? 1 : 0);
- if (cfg.alt_launcher[0] && cfg.fb_terminal) alt_launcher_init();
+ if (alt_launcher_configured()) zaparoo_alt_launcher_init_for_menu();
else
if (cfg.fb_terminal) video_menu_bg(user_io_status_get("[3:1]"));
else user_io_status_set("[3:1]", 0);
@@ -3032,7 +3037,7 @@ void user_io_set_ini(int ini_num)
if (!name[0])
{
- name = "menu.rbf";
+ name = menu_rbf_name();
xml = NULL;
}
@@ -3709,7 +3714,7 @@ void user_io_poll()
if (!coldreset_req && prev_coldreset_req)
{
- fpga_load_rbf("menu.rbf");
+ fpga_load_rbf(menu_rbf_name());
}
prev_coldreset_req = coldreset_req;
@@ -4157,8 +4162,12 @@ void user_io_kbd(uint16_t key, int press)
if (key)
{
uint32_t code = get_ps2_code(key);
- if (alt_launcher_active() && (key == KEY_MENU || key == KEY_F12))
- return;
+ // Both F12 and KEY_MENU now reach the normal menu/OSD flow even
+ // while the alt launcher is running, so the user can open the OSD
+ // overlay (System Settings) on top of their launcher app from
+ // either keyboard or joypad MENU button. Input grabbing flips
+ // automatically when the OSD opens (user_io_osd_key_enable ->
+ // input_switch -> EVIOCGRAB).
bool is_menu_event = ((has_menu() || osd_is_visible || (get_key_mod() & (LALT | RALT | RGUI | LGUI))) && (((key == KEY_F12) && (!is_f12_mod_needed() || (get_key_mod() & (RGUI | LGUI)))) || key == KEY_MENU));
if (!press)
{