Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ It's licensed under the terms of the GNU General Public License, version 3 or la
* Local Hiscores integration with hi2txt
* And much more!

## Runtime Signals
- Linux/macOS:
- `SIGHUP`: graceful restart (re-read configuration)
- `SIGINT` / `SIGTERM`: graceful shutdown
- `SIGUSR1`: live layout XML hot-reload
- Windows (console mode):
- `Ctrl+C`, `Ctrl+Break`, console close/logoff/shutdown events: graceful shutdown

## System Requirements
* OS
* Windows (10 or higher)
Expand Down
16 changes: 13 additions & 3 deletions RetroFE/Source/Execute/Launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ bool Launcher::run(std::string collection, Item* collectionItem, Page* currentPa
break;
}
case PreWaitMode::UpToMs: {
// IMPORTANT: use simpleLaunch() so job-close doesn�t kill it
// IMPORTANT: use simpleLaunch() so job-close doesn�t kill it
LOG_INFO("Launcher", std::string("Waiting up to ") + std::to_string(preWaitMs) + " ms for pre-hook (non-blocking)...");
if (!preMgr->simpleLaunch(pExe.string(), preArgs, pCwd.string())) {
LOG_ERROR("Launcher", "Pre-hook failed to start: " + pExe.string());
Expand All @@ -381,14 +381,14 @@ bool Launcher::run(std::string collection, Item* collectionItem, Page* currentPa
// ~30fps
std::this_thread::sleep_for(std::chrono::milliseconds(33));
}
// We can�t know exit code in this mode (no handle by design)
// We can�t know exit code in this mode (no handle by design)
LOG_INFO("Launcher", std::string("Pre-hook still running after ")
+ std::to_string(preWaitMs) + " ms; continuing.");
break;
}
case PreWaitMode::NoWait:
default: {
// IMPORTANT: use simpleLaunch() so job-close doesn�t kill it
// IMPORTANT: use simpleLaunch() so job-close doesn�t kill it
if (!preMgr->simpleLaunch(pExe.string(), preArgs, pCwd.string())) {
LOG_ERROR("Launcher", "Pre-hook failed to start: " + pExe.string());
return false;
Expand Down Expand Up @@ -452,6 +452,10 @@ bool Launcher::run(std::string collection, Item* collectionItem, Page* currentPa
int timeout = 30;
config_.getProperty(OPTION_ATTRACTMODELAUNCHRUNTIME, timeout);
auto attractModeInputCheck = [&inputMonitor]() {
// Check for shutdown signal first
if (RetroFE::isShutdownRequested()) {
return true;
}
return inputMonitor.checkInputEvents() != InputDetectionResult::NoInput;
};
WaitResult result = processManager->wait(timeout, attractModeInputCheck, onFrameTick);
Expand Down Expand Up @@ -490,6 +494,12 @@ bool Launcher::run(std::string collection, Item* collectionItem, Page* currentPa
else { // Normal mode
LOG_INFO("Launcher", "Waiting for launched process to complete. Press quit combo to force quit.");
auto quitCheck = [&inputMonitor, launchingNestedRetroFE, quitComboEnabled]() {
// Check for shutdown signal first
if (RetroFE::isShutdownRequested()) {
LOG_INFO("Launcher", "Shutdown signal received. Terminating launched process.");
return true;
}

// Nested RetroFE always owns quit regardless of config
if (launchingNestedRetroFE) return false;

Expand Down
35 changes: 35 additions & 0 deletions RetroFE/Source/RetroFE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@

std::atomic<bool> RetroFE::reloadRequested_{false};
std::atomic<bool> RetroFE::sighupReceived_{false};
std::atomic<bool> RetroFE::shutdownRequested_{false};

void RetroFE::handleSigusr1(int) {
reloadRequested_.store(true);
Expand All @@ -88,6 +89,24 @@ void RetroFE::handleSighup(int) {
sighupReceived_.store(true);
}

void RetroFE::handleSigterm(int) {
shutdownRequested_.store(true);
}

bool RetroFE::isShutdownRequested() {
return shutdownRequested_.load();
}

#ifdef WIN32
BOOL WINAPI RetroFE::consoleCtrlHandler(DWORD dwCtrlType) {
if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT || dwCtrlType == CTRL_CLOSE_EVENT) {
shutdownRequested_.store(true);
return TRUE; // Indicate that the signal was handled
}
return FALSE; // Let the system handle other signals
}
#endif

RetroFE::RetroFE(Configuration& c)
: initialized(false), initializeError(false), initializeThread(NULL), config_(c), db_(NULL), metadb_(NULL),
input_(config_), currentPage_(NULL), keyInputDisable_(0), currentTime_(0), lastLaunchReturnTime_(0),
Expand Down Expand Up @@ -811,6 +830,13 @@ bool RetroFE::run() {
signal(SIGUSR1, RetroFE::handleSigusr1);
// SIGHUP triggers a clean restart (re-reads all configuration)
signal(SIGHUP, RetroFE::handleSighup);
// SIGTERM triggers graceful shutdown
signal(SIGTERM, RetroFE::handleSigterm);
#endif

#ifdef WIN32
// Set console control handler for graceful shutdown on Windows
SetConsoleCtrlHandler(RetroFE::consoleCtrlHandler, TRUE);
#endif

config_.getProperty(OPTION_ATTRACTMODETIME, attractModeTime);
Expand Down Expand Up @@ -978,6 +1004,15 @@ bool RetroFE::run() {
setState(RETROFE_QUIT_REQUEST);
}

// Check for SIGTERM or Windows shutdown signal: trigger graceful shutdown.
// Wait until we are out of splash mode and not already shutting down.
if (!splashMode && shutdownRequested_.exchange(false)
&& state_ != RETROFE_QUIT_REQUEST && state_ != RETROFE_QUIT)
{
LOG_INFO("RetroFE", "Shutdown signal received – exiting gracefully");
setState(RETROFE_QUIT_REQUEST);
}

if (!splashMode && state_accepts_input(state_)) {
RETROFE_STATE inputState = processUserInput(currentPage_);
if (inputState != RETROFE_IDLE) {
Expand Down
11 changes: 9 additions & 2 deletions RetroFE/Source/RetroFE.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ class RetroFE
MetadataDatabase* getMetaDb();
uint64_t freq_ = SDL_GetPerformanceFrequency();

static void handleSigusr1(int sig);
static void handleSighup(int sig);
static void handleSigterm(int sig);
#ifdef WIN32
static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType);
#endif
static bool isShutdownRequested();

private:
#ifdef WIN32
HWND hwnd;
Expand Down Expand Up @@ -190,10 +198,9 @@ class RetroFE
void handleMusicControls(UserInput::KeyCode_E input);
Page *loadPage(const std::string& collectionName);
Page *loadSplashPage( );
static void handleSigusr1(int sig);
static void handleSighup(int sig);
static std::atomic<bool> reloadRequested_;
static std::atomic<bool> sighupReceived_;
static std::atomic<bool> shutdownRequested_;

std::vector<std::string> collectionCycle_;
std::vector<std::string>::iterator collectionCycleIt_;
Expand Down