From d636339bae2d8a686a65bad13862bab3d7affbbd Mon Sep 17 00:00:00 2001 From: DefKorns Date: Thu, 22 Oct 2026 12:19:19 +0000 Subject: [PATCH 1/6] Keep the menu's open files, /dev/fb0 included, out of the scripts it runs --- Makefile | 2 +- src/command.cpp | 9 +++++++- src/download_screen.cpp | 2 ++ src/framework/close_on_exec.cpp | 41 +++++++++++++++++++++++++++++++++ src/framework/close_on_exec.h | 25 ++++++++++++++++++++ src/main.cpp | 8 +++++++ src/mod_uninstall.cpp | 3 +++ src/save_manager.cpp | 3 +++ 8 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/framework/close_on_exec.cpp create mode 100644 src/framework/close_on_exec.h diff --git a/Makefile b/Makefile index e456cdd..94378d2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ VENDORED_LIBS = endif CXXFLAGS = -std=c++14 -Os $(SDL_CFLAGS) -DMOD_VERSION=\"v$(MOD_VER)\" LDLIBS = $(SDL_LIBS) -SOURCES = src/main.cpp src/back_stack.cpp src/command.cpp src/download_screen.cpp src/localization.cpp src/framework/badge.cpp src/framework/dialog.cpp src/framework/sdl_context.cpp src/framework/texture.cpp src/framework/controller.cpp src/framework/powerwatch.cpp src/framework/draw_helpers.cpp src/framework/utf8.cpp src/framework/font8x8_lookup.cpp src/framework/uitheme.cpp src/framework/list_jump.cpp src/framework/text_layout.cpp src/framework/list_screen.cpp src/framework/panel_dialog.cpp +SOURCES = src/main.cpp src/back_stack.cpp src/command.cpp src/download_screen.cpp src/localization.cpp src/framework/badge.cpp src/framework/dialog.cpp src/framework/sdl_context.cpp src/framework/texture.cpp src/framework/controller.cpp src/framework/powerwatch.cpp src/framework/draw_helpers.cpp src/framework/utf8.cpp src/framework/font8x8_lookup.cpp src/framework/uitheme.cpp src/framework/list_jump.cpp src/framework/text_layout.cpp src/framework/list_screen.cpp src/framework/panel_dialog.cpp src/framework/close_on_exec.cpp OBJECTS = $(SOURCES:.cpp=.o) ALL_OBJECTS = $(OBJECTS) src/daemon.o src/mod_uninstall.o src/save_manager.o src/standby_watchdog.o src/gen_splash.o src/change_combo.o diff --git a/src/command.cpp b/src/command.cpp index 4f9610d..c247165 100644 --- a/src/command.cpp +++ b/src/command.cpp @@ -8,6 +8,7 @@ */ #include "command.h" +#include "framework/close_on_exec.h" #include "framework/controller.h" #include "framework/draw_helpers.h" #include "framework/list_screen.h" @@ -34,6 +35,7 @@ static CommandProcess StartCommand(const std::string & command) int fds[2]; if(pipe(fds) != 0) return { nullptr, -1 }; + MarkFdsCloseOnExec(); const pid_t pid = fork(); if(pid < 0) { @@ -279,7 +281,10 @@ void Command::RunCommand(SDL_Context & sdl_context, Controller * controller, Scr static std::vector ReadLines(const std::string & command) { std::vector lines; - FILE * pipe = command.empty() ? nullptr : popen(command.c_str(), "r"); + if(command.empty()) + return lines; + MarkFdsCloseOnExec(); + FILE * pipe = popen(command.c_str(), "r"); if(!pipe) return lines; std::string line; @@ -342,6 +347,7 @@ void Command::SetChoice(const std::string & value) size_t pos; while((pos = set.find("%value%")) != std::string::npos) set.replace(pos, 7, value); + MarkFdsCloseOnExec(); system(set.c_str()); choiceValue = value; choiceResolved = value.empty() ? choiceAutoResolved : value; @@ -361,6 +367,7 @@ void Command::UpdateState() if(stateCommand.empty()) return; + MarkFdsCloseOnExec(); FILE * pipe = popen(stateCommand.c_str(), "r"); if(!pipe) return; diff --git a/src/download_screen.cpp b/src/download_screen.cpp index f8e5999..b503614 100644 --- a/src/download_screen.cpp +++ b/src/download_screen.cpp @@ -8,6 +8,7 @@ */ #include "download_screen.h" +#include "framework/close_on_exec.h" #include "framework/draw_helpers.h" #include "framework/list_screen.h" #include "framework/text_layout.h" @@ -45,6 +46,7 @@ namespace int fds[2]; if(pipe(fds) != 0) return; + MarkFdsCloseOnExec(); pid_ = fork(); if(pid_ < 0) { diff --git a/src/framework/close_on_exec.cpp b/src/framework/close_on_exec.cpp new file mode 100644 index 0000000..f51b17e --- /dev/null +++ b/src/framework/close_on_exec.cpp @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2026 DefKorns (https://defkorns.github.io/LICENSE) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "close_on_exec.h" + +#include +#include +#include +#include + +void MarkFdsCloseOnExec() +{ + std::unique_ptr dir(opendir("/proc/self/fd"), &closedir); + if(!dir) + return; + const int dirFd = dirfd(dir.get()); + while(const dirent * entry = readdir(dir.get())) + { + char * end = nullptr; + const long fd = std::strtol(entry->d_name, &end, 10); + if(end == entry->d_name || *end != '\0' || fd < 3 || fd == dirFd) + continue; + const int flags = fcntl(static_cast(fd), F_GETFD); + if(flags >= 0 && !(flags & FD_CLOEXEC)) + fcntl(static_cast(fd), F_SETFD, flags | FD_CLOEXEC); + } +} diff --git a/src/framework/close_on_exec.h b/src/framework/close_on_exec.h new file mode 100644 index 0000000..32d32c5 --- /dev/null +++ b/src/framework/close_on_exec.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 DefKorns (https://defkorns.github.io/LICENSE) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef CLOSE_ON_EXEC_H_ +#define CLOSE_ON_EXEC_H_ + +// call right before system()/popen()/fork()+exec: a spawned script holding /dev/fb0 +// gets killed by uistop, which TERMs every process lsof shows on the framebuffer +void MarkFdsCloseOnExec(); + +#endif diff --git a/src/main.cpp b/src/main.cpp index 2974bb8..ce8efd3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ */ #include "framework/badge.h" +#include "framework/close_on_exec.h" #include "framework/dialog.h" #include "framework/sdl_helper.h" #include "framework/controller.h" @@ -142,6 +143,7 @@ int main(int argc, char * argv[]) if(!c.enableIfCommand.empty()) { ExpandTemplates(c.enableIfCommand); + MarkFdsCloseOnExec(); if(system(("source /etc/preinit; script_init; " + c.enableIfCommand).c_str()) != 0) continue; } @@ -188,6 +190,7 @@ int main(int argc, char * argv[]) auto renderer = sdl_context.renderer; Controller controller(1); + MarkFdsCloseOnExec(); system("touch /tmp/options.flag"); const Color bg = UiTheme::Bg; @@ -521,6 +524,7 @@ int main(int argc, char * argv[]) if(!Confirm(Translate(confirmKey.empty() ? "DELETE_CONFIRM_GENERIC" : confirmKey))) return false; lifecycle.Decide(ExitAction::Leave); + MarkFdsCloseOnExec(); system(commands[currentCommandId].deleteCommand.c_str()); return true; }; @@ -608,6 +612,7 @@ int main(int argc, char * argv[]) else if(confirmed) { lifecycle.Decide(commands[currentCommandId].restartUI ? ExitAction::Resume : ExitAction::Leave); + MarkFdsCloseOnExec(); system(commands[currentCommandId].command.c_str()); break; } @@ -637,7 +642,10 @@ int main(int argc, char * argv[]) { lifecycle.Decide(backCommand.empty() ? ExitAction::Resume : ExitAction::Leave); if(!backCommand.empty()) + { + MarkFdsCloseOnExec(); system(backCommand.c_str()); + } break; } bHoldFired = false; diff --git a/src/mod_uninstall.cpp b/src/mod_uninstall.cpp index 9c8e1d4..1479f51 100644 --- a/src/mod_uninstall.cpp +++ b/src/mod_uninstall.cpp @@ -8,6 +8,7 @@ */ #include "framework/badge.h" +#include "framework/close_on_exec.h" #include "framework/sdl_helper.h" #include "framework/controller.h" #include "framework/powerwatch.h" @@ -67,6 +68,7 @@ int main(int argc, char * argv[]) std::vector mods; { char buf[256]; + MarkFdsCloseOnExec(); auto pipe = popen("hakchi pack_list", "r"); if(pipe) { @@ -293,6 +295,7 @@ int main(int argc, char * argv[]) for(const Mod * mod : marked) out << mod->name << " "; out.close(); + MarkFdsCloseOnExec(); system(("/bin/sh " + folderLocation + "/FinishUninstall.sh &").c_str()); return DialogResult::Confirmed; }; diff --git a/src/save_manager.cpp b/src/save_manager.cpp index 505536c..c6376ea 100644 --- a/src/save_manager.cpp +++ b/src/save_manager.cpp @@ -9,6 +9,7 @@ */ #include "framework/badge.h" +#include "framework/close_on_exec.h" #include "framework/sdl_helper.h" #include "framework/controller.h" #include "framework/powerwatch.h" @@ -111,6 +112,7 @@ namespace std::vector ReadLines(const std::string & command) { std::vector lines; + MarkFdsCloseOnExec(); FILE * pipe = popen(command.c_str(), "r"); if(!pipe) return lines; @@ -250,6 +252,7 @@ int main(int, char **) auto RunBusy = [&](const std::function & drawUnder, const std::string & command) -> bool { ShowBusy(sdl_context, drawUnder, Translate("SAVE_MANAGER_WORKING")); + MarkFdsCloseOnExec(); const int status = system(command.c_str()); DropPendingInput(controller); return status != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0; From 5dcfa0cf24e14f6a3d1618e71e28aa82eec2e75c Mon Sep 17 00:00:00 2001 From: DefKorns Date: Thu, 22 Oct 2026 12:19:35 +0000 Subject: [PATCH 2/6] Show Loading while omReloadMenu reloads the home menu --- mod/etc/options_menu/scripts/om_functions | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mod/etc/options_menu/scripts/om_functions b/mod/etc/options_menu/scripts/om_functions index a99891e..5eb64d1 100644 --- a/mod/etc/options_menu/scripts/om_functions +++ b/mod/etc/options_menu/scripts/om_functions @@ -249,7 +249,7 @@ omReloadMenuLater() { mv /var/exec.flag.tmp /var/exec.flag } -# omReloadMenu [command...]: stops the UI, runs the command, then reloads the current folder; +# omReloadMenu [command...]: stops the UI, shows Loading, runs the command, then reloads the current folder; # chmenu refuses the folder already shown unless its state says otherwise omReloadMenu() { local menu_state="/var/saves/$modname/menu" current_menu="000" @@ -257,6 +257,7 @@ omReloadMenu() { uistop uiresume timeout="" waittimeout 1500 '! lsof -n | grep -qF "/dev/fb0"' "framebuffer" + "$om_scripts/gen_splash" --loading 2>/dev/null [ $# -gt 0 ] && "$@" [ -f "$menu_state" ] && current_menu="$(cat "$menu_state")" echo reload >"$menu_state" From b433a72e47f5a71702a893e7d07d6024826a9fdd Mon Sep 17 00:00:00 2001 From: DefKorns Date: Thu, 22 Oct 2026 12:25:27 +0000 Subject: [PATCH 3/6] Show the splash text with a moving bar through omLoading --- mod/etc/options_menu/network/scripts/backup-wifi.sh | 2 +- mod/etc/options_menu/scripts/om_functions | 7 ++++++- src/gen_splash.cpp | 11 ++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mod/etc/options_menu/network/scripts/backup-wifi.sh b/mod/etc/options_menu/network/scripts/backup-wifi.sh index c66102e..1e91aba 100644 --- a/mod/etc/options_menu/network/scripts/backup-wifi.sh +++ b/mod/etc/options_menu/network/scripts/backup-wifi.sh @@ -55,7 +55,7 @@ restoreWifi() { case "$2" in backup) - omSplash WIFI_BACKUP_SPLASH_LINE1 WIFI_BACKUP_SPLASH_LINE2 + omLoading WIFI_BACKUP_SPLASH_LINE1 WIFI_BACKUP_SPLASH_LINE2 backupWifi echo "WIFI config backed up to $network" sleep 1 diff --git a/mod/etc/options_menu/scripts/om_functions b/mod/etc/options_menu/scripts/om_functions index 5eb64d1..409cd7a 100644 --- a/mod/etc/options_menu/scripts/om_functions +++ b/mod/etc/options_menu/scripts/om_functions @@ -217,6 +217,11 @@ omSplash() { "$om_scripts/gen_splash" /tmp/om_splash.png "$@" && decodepng /tmp/om_splash.png >/dev/fb0 } +# omLoading [key...]: omSplash plus a moving bar until the calling script exits; "Loading" without keys +omLoading() { + "$om_scripts/gen_splash" --loading "$@" 2>/dev/null +} + # omFreeKb omFreeKb() { df -Pk "$1" | awk 'NR==2 {print $4}' @@ -257,7 +262,7 @@ omReloadMenu() { uistop uiresume timeout="" waittimeout 1500 '! lsof -n | grep -qF "/dev/fb0"' "framebuffer" - "$om_scripts/gen_splash" --loading 2>/dev/null + omLoading LOADING [ $# -gt 0 ] && "$@" [ -f "$menu_state" ] && current_menu="$(cat "$menu_state")" echo reload >"$menu_state" diff --git a/src/gen_splash.cpp b/src/gen_splash.cpp index 07ec751..16aa031 100644 --- a/src/gen_splash.cpp +++ b/src/gen_splash.cpp @@ -232,12 +232,12 @@ namespace close(devNull); } - int RunLoading() + int RunLoading(const std::vector & keys) { const pid_t caller = getppid(); if(caller <= 1) return 0; - const TextBlock block = RenderText({ "LOADING" }, true); + const TextBlock block = RenderText(keys, true); UiTheme::LoadThemeConfig(OptionsRoot); const int barY = block.bottomY + BarGap; @@ -266,17 +266,18 @@ namespace void PrintUsage(const char * self) { std::cerr << "Usage: " << self << " [line-key...]\n" - << " " << self << " --loading\n"; + << " " << self << " --loading [line-key...]\n"; } } int main(int argc, char * argv[]) { const std::vector args(argv + 1, argv + argc); - if(args.size() == 1 && args[0] == "--loading") + if(!args.empty() && args[0] == "--loading") { LoadLanguageFromConfig(OptionsRoot); - return RunLoading(); + const std::vector keys(args.begin() + 1, args.end()); + return RunLoading(keys.empty() ? std::vector{ "LOADING" } : keys); } if(args.size() < 2) { From 67e86523a4cf7628f2d6206bd02822e5378d481d Mon Sep 17 00:00:00 2001 From: DefKorns Date: Thu, 22 Oct 2026 12:38:24 +0000 Subject: [PATCH 4/6] Keep the caller's splash text while omReloadMenu reloads the home menu --- mod/etc/options_menu/scripts/om_functions | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mod/etc/options_menu/scripts/om_functions b/mod/etc/options_menu/scripts/om_functions index 409cd7a..24bf325 100644 --- a/mod/etc/options_menu/scripts/om_functions +++ b/mod/etc/options_menu/scripts/om_functions @@ -219,6 +219,7 @@ omSplash() { # omLoading [key...]: omSplash plus a moving bar until the calling script exits; "Loading" without keys omLoading() { + om_loading_keys="$*" "$om_scripts/gen_splash" --loading "$@" 2>/dev/null } @@ -250,7 +251,7 @@ omUnpack() { # omReloadMenuLater [command...]: the menu holds /dev/fb0 and uistop kills every holder, # so remote-exec runs omReloadMenu once the caller has exited omReloadMenuLater() { - echo "source $om_scripts/om_functions; omReloadMenu $*" >/var/exec.flag.tmp + echo "source $om_scripts/om_functions; om_loading_keys=\"$om_loading_keys\"; omReloadMenu $*" >/var/exec.flag.tmp mv /var/exec.flag.tmp /var/exec.flag } @@ -262,7 +263,8 @@ omReloadMenu() { uistop uiresume timeout="" waittimeout 1500 '! lsof -n | grep -qF "/dev/fb0"' "framebuffer" - omLoading LOADING + # shellcheck disable=SC2086 + omLoading ${om_loading_keys:-LOADING} [ $# -gt 0 ] && "$@" [ -f "$menu_state" ] && current_menu="$(cat "$menu_state")" echo reload >"$menu_state" From d707d65500d8821a08570631683e0708778ef86a Mon Sep 17 00:00:00 2001 From: DefKorns Date: Thu, 22 Oct 2026 12:56:29 +0000 Subject: [PATCH 5/6] Add the 2.1.0 release notes and document the new add-on features --- README.md | 32 +++++++++++++++++++++++++++----- mod/readme.md | 4 ++-- release-notes/v2.1.0.md | 21 +++++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) create mode 100644 release-notes/v2.1.0.md diff --git a/README.md b/README.md index 7665a02..bfad0bc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This is my continuation of [CompCom's Options Menu](https://github.com/CompCom/O ## Install -**From my Mod Hub (recommended):** in hakchi open **Manage repositories**, add `https://defkorns.github.io/hakchi-repo/` and install **Options Menu** from it. Updates show up there automatically. +**From my Mod Hub (recommended):** in hakchi choose **Modules → Manage mod repositories** and add `https://defkorns.github.io/hakchi-repo/`. Then open **Modules → DefKorns Store** and install **Options Menu** from the *Options Menu* tab. Updates show up there automatically. **By hand:** download `options_menu.hmod` from [Releases](https://github.com/DefKorns/OptionsMenu/releases), put it in hakchi's `user_mods` folder (or drag and drop it onto the hakchi window), then install it from hakchi (**Modules → Install extra modules**). @@ -49,7 +49,7 @@ The footer always shows the buttons for the current screen. | RetroArch › | RetroArch settings, backups and BIOS files. Shown when RetroArch is installed | | Network › | IP address, Wi-Fi scan and Wi-Fi config backups | | Save Manager › | Back up, restore and clean up your saves. Needs a USB/SD drive | -| Language › | Menu language; the menu restarts in the new language | +| Language › | Menu language: *Automatic* follows the console's own language, or pick one. The menu restarts in the new language | | Advanced Options › | Controller, diagnostics and system tools | \*Don't shut down or restart from the menu while RetroArch is running: exit RetroArch first, or you may lose saves. @@ -94,7 +94,7 @@ Restore entries only show up when there's a backup to restore. ### Save Manager -Needs a USB/SD drive with your saves on it. One screen lists the actions on the left; the right panel explains the selected one and shows how many saves are on the console, in the USB backup, left over from removed games and moved aside. Every action asks for confirmation (**Start + Select**). +Needs a USB/SD drive with your saves on it. One screen lists the actions on the left; the right panel explains the selected one and shows how many saves are on the console, in the USB backup, left over from removed games and moved aside. Every action asks for confirmation: **Start + Select** for the ones that overwrite or move saves, **A** for bringing back the saves of removed games, which never overwrites anything. | Action | What it does | | --- | --- | @@ -107,7 +107,7 @@ In the long lists (here and in the Module Uninstaller), **Left**/**Right** page #### Game names -The two "Removed Games" lists show the names of the removed games instead of their codes (`CLV-H-ABCDE`). Every time the console starts, it remembers the names of the games installed at that moment in `game_names.txt` in your saves folder, and a name stays there after its game is removed. The backup keeps the file with your saves, and a restore adds its names without replacing newer ones. Saves without a known name keep showing their code. Getting the names never changes or moves a save. +The two "Removed Games" lists show the names of the removed games instead of their codes (`CLV-H-ABCDE`). Every time the console starts, it remembers the names of the games installed at that moment in `game_names.txt` in your saves folder, and a name stays there after its game is removed. The backup keeps the file with your saves, and a restore adds its names without replacing newer ones. Saves without a known name keep showing their code, two games with the same name show their code too, and empty save folders aren't listed. Getting the names never changes or moves a save. Games removed before this version was installed were never seen on the console, so their names can only come from your Hakchi CE library. The console can't read your PC, so the PC sends them. With the console on and reachable by Hakchi CE (USB or Wi-Fi), run [`pc/push_game_names.ps1`](pc/push_game_names.ps1) from a PowerShell window: @@ -184,7 +184,7 @@ One file per entry, named `cNNNN_Name`; entries are sorted by name. Each line is | Field | Description | | --- | --- | | `COMMAND_NAME` | Label shown, or a translation key | -| `COMMAND_TYPE` | `INTERNAL` shows the command's output in the menu's console; `EXTERNAL` runs it with the menu paused; `DOWNLOAD` runs it on a progress screen with a bar (fed by the downloader's `--progress` lines on stdout) and a B to cancel, closing on its own when the command exits 0 | +| `COMMAND_TYPE` | `INTERNAL` shows the command's output in the menu's console; `EXTERNAL` runs it with the menu paused; `DOWNLOAD` runs it on a progress screen with a bar (fed by the downloader's `--progress` lines on stdout) and a B to cancel, closing on its own when the command exits 0; `CHOICE` turns the entry into a value picker (see the `CHOICE_*` fields) | | `COMMAND_STR` | Single-line command to run. Use a script for anything longer | | `RESTART_UI` | External commands only. `FALSE` leaves the UI paused; resume it yourself with `/bin/sh /etc/options_menu/scripts/ResumeUI.sh` | | `IGNORE_INTERRUPT` | `TRUE`: B doesn't interrupt an internal command while it runs | @@ -197,11 +197,33 @@ One file per entry, named `cNNNN_Name`; entries are sorted by name. Each line is | `DELETE_CONFIRM_KEY` | Translation key for that confirmation; defaults to a generic "Delete this item?" | | `DELETE_BLOCKED_KEY` | Translation key: holding B on the entry shows this message with a Back badge instead of asking to delete it, for entries that can't be removed right now | | `PREVIEW_IMAGE` | PNG shown in the detail panel, scaled to fit | +| `CHOICE_LIST` | `CHOICE` only. Command printing one `valuelabelpreview` line per option; the label can be a translation key and the preview is optional | +| `CHOICE_GET` | `CHOICE` only. Prints the stored value; empty means *Automatic* | +| `CHOICE_SET` | `CHOICE` only. Stores the value picked, with `%value%` replaced by it (empty for *Automatic*) | +| `CHOICE_AUTO` | `CHOICE` only. Translation key of the *Automatic* option, shown when no value is stored | +| `CHOICE_RESOLVED` | `CHOICE` only. Prints the value *Automatic* stands for, so its label and preview can be shown | `COMMAND_STR`, `DELETE_STR` and `STATE_STR` can use `%options_path%` (the folder with the `options` binary) and `%script_dir%` (the current `--scriptPath`). The shared framework also reads a few grid-only fields (`PREVIEW_NEAREST`, `PREVIEW_SQUARE`, `PREVIEW_GRID_COLS`, `PREVIEW_FIT_CONTAIN`, `PREVIEW_HIDE_LABEL`). The menu itself always shows a list; they're for apps built on the shared framework that use a grid layout. `PREVIEW_IMAGE_X/Y/WIDTH/HEIGHT` from the original format are accepted and ignored. +### Script helpers + +Your scripts can `source /etc/options_menu/scripts/om_functions` for the helpers the menu's own scripts use. The main ones: + +| Function | What it does | +| --- | --- | +| `translate ` | Prints the string for the menu's current language | +| `omSplash ` | Draws one translated line per key, full screen | +| `omLoading [key...]` | Same, with a moving bar, until your script exits; "Loading" without keys | +| `omDownload [fallback...]` | Downloads a file, printing progress for a `DOWNLOAD` entry; exit code 3 means no free space | +| `omDownloadAll [keep]` | Downloads a list of files over one connection | +| `omUnpack ` | Unpacks an archive if it fits, then removes it | +| `omReloadMenu` / `omReloadMenuLater` | Stops the UI and reloads the current Home folder, keeping your splash text on screen | +| `enableCmds` / `disableCmds ` | Show or hide entries by renaming `$enable_` / `$disable_` | + +Since 2.1.0, scripts started from the menu no longer hold `/dev/fb0`, so they can call `chmenu` (or anything else that runs `uistop`) without being killed. On 2.0.0 they still hold it: hand such a command to hakchi's `remote-exec` instead, by writing it to `/var/exec.flag`. + ### Translations Strings live in `/etc/options_menu/language/.lang` as `KEY=Text`; the chosen language is in `/etc/options_menu/language.cfg`. `en-US` is always loaded first, so missing keys fall back to English. diff --git a/mod/readme.md b/mod/readme.md index 0233d66..81b02a2 100644 --- a/mod/readme.md +++ b/mod/readme.md @@ -9,8 +9,8 @@ The Options Menu is a custom, easily extendable menu for your console, letting o - **Power:** Hibernate/Standby, Shutdown, Restart - **RetroArch:** back up/restore settings (NAND or USB), restore defaults, transfer BIOS files, clean up overrides/remaps/BIOS, toggle load screens - **Network:** show IP, reconnect, search for SSIDs, back up/restore your wifi config (NAND or USB) -- **Save Manager:** back up/restore saves to USB, and move aside (or bring back) saves of games no longer on the console -- **Language:** English, Français, Deutsch, Español, Italiano, Nederlands, Português, Русский, 日本語 +- **Save Manager:** back up/restore saves to USB, and move aside (or bring back) saves of games no longer on the console, listed by game name +- **Language:** follows the console's own language, or pick one of English, Français, Deutsch, Español, Italiano, Nederlands, Português, Русский, 日本語 - **Advanced:** - Controller: change the menu button combo, autofire (speed, X/Y as turbo A/B), Home menu combo, Start on the 2nd controller - Diagnostics: system info, temperature, top, benchmark, RetroArch debugger, file structure and kernel log dumps to USB diff --git a/release-notes/v2.1.0.md b/release-notes/v2.1.0.md new file mode 100644 index 0000000..c0d7314 --- /dev/null +++ b/release-notes/v2.1.0.md @@ -0,0 +1,21 @@ +## Options Menu v2.1.0 + +- **Menu language follows the console:** Language → Options Menu language now defaults to *Automatic*, which uses the console's own language. Pick a language there to override it. +- **Clearer confirmations:** non-destructive actions confirm with **A**; deleting or overwriting still needs **Start+Select**. Save Manager and Module Uninstaller dialogs share the same look. +- **Save Manager:** saves of games with the same name are tagged with their game code, and empty saves are hidden. +- **Downloads:** add-ons can show a download screen with a progress bar and the free space left, and the file being downloaded is shown by its name only (`2ks_star_wars` instead of `SNES.2ks_star_wars.tar.g`). +- **Waiting screens:** add-ons can show their "please wait" messages with a moving bar, and reloading the Home menu shows it instead of a black screen. +- **More robust:** if the menu is closed by the system (SIGTERM/SIGHUP), the console's UI comes back instead of staying paused. Scripts started from the menu no longer hold the screen, so `chmenu` and folder switches don't kill them halfway. + +## Why 2.1.0 + +This release adds features without breaking anything that already works, so it's a minor version bump (2.0.0 → 2.1.0), not a patch: + +- **New for add-ons:** the `DOWNLOAD` and `CHOICE` command types, the `DELETE_BLOCKED_KEY` dialog, and new `om_functions` helpers (`omSplash`, `omLoading`, `omUnpack`, `omReloadMenu`, `omReloadMenuLater`, `omLangCode`, `omLangList`). Add-ons that use them need 2.1.0 or newer: **Theme Selector 3**, **Console Language** and **Favorites Manager** do. +- **Nothing removed for installed add-ons:** command files and the `om_functions` helpers from 2.0.0 work as before, so add-ons made for 2.0.0 keep working. +- **For C++ add-on developers:** the framework grew `ScreenChrome`, `PanelDialog`, `NoteBox`, list-row and text-layout helpers, and `close_on_exec`. `ModernChrome` is gone (use `ScreenChrome`) and the code builds with `-std=c++14`. Add-ons compile the framework in, so this only matters when you update your `vendor/OptionsMenu`. + +## Which file? + +- `options_menu.hmod`: use this one. +- `options_menu_compat.hmod`: only if another hmod replaces the console's `libstdc++` (e.g. RetroArch 1.8.4 Xtreme SC) and the menu doesn't open with the normal build. From 14b641806fa9c172297a44b9b973f95039eef2ec Mon Sep 17 00:00:00 2001 From: DefKorns Date: Thu, 22 Oct 2026 13:01:01 +0000 Subject: [PATCH 6/6] Add the 2.1.0 release notes and document the new add-on features. --- release-notes/v2.1.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-notes/v2.1.0.md b/release-notes/v2.1.0.md index c0d7314..d751cbb 100644 --- a/release-notes/v2.1.0.md +++ b/release-notes/v2.1.0.md @@ -11,7 +11,7 @@ This release adds features without breaking anything that already works, so it's a minor version bump (2.0.0 → 2.1.0), not a patch: -- **New for add-ons:** the `DOWNLOAD` and `CHOICE` command types, the `DELETE_BLOCKED_KEY` dialog, and new `om_functions` helpers (`omSplash`, `omLoading`, `omUnpack`, `omReloadMenu`, `omReloadMenuLater`, `omLangCode`, `omLangList`). Add-ons that use them need 2.1.0 or newer: **Theme Selector 3**, **Console Language** and **Favorites Manager** do. +- **New for add-ons:** the `DOWNLOAD` and `CHOICE` command types, the `DELETE_BLOCKED_KEY` dialog, and new `om_functions` helpers (`omSplash`, `omLoading`, `omUnpack`, `omReloadMenu`, `omReloadMenuLater`, `omLangCode`, `omLangList`). Add-ons that use them need 2.1.0 or newer. - **Nothing removed for installed add-ons:** command files and the `om_functions` helpers from 2.0.0 work as before, so add-ons made for 2.0.0 keep working. - **For C++ add-on developers:** the framework grew `ScreenChrome`, `PanelDialog`, `NoteBox`, list-row and text-layout helpers, and `close_on_exec`. `ModernChrome` is gone (use `ScreenChrome`) and the code builds with `-std=c++14`. Add-ons compile the framework in, so this only matters when you update your `vendor/OptionsMenu`.