Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ VENDORED_LIBS =
endif
CXXFLAGS = -std=c++11 -Os $(SDL_CFLAGS) -DMOD_VERSION=\"v$(MOD_VER)\"
LDLIBS = $(SDL_LIBS)
SOURCES = src/main.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
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
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

Expand Down
15 changes: 9 additions & 6 deletions mod/etc/options_menu/save_manager/launch_save_manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ remaining="${OM_BACK_STACK%;*}"
command_path="${entry%%,*}"
rest="${entry#*,}"
script_path="${rest%%,*}"
title_key="${rest#*,}"
title_rest="${rest#*,}"
title_key="${title_rest%%,*}"
marker=""
[ "$title_rest" != "$title_key" ] && marker="${title_rest#*,}"

usleep 50000
if [ -n "$script_path" ]; then
OM_BACK_STACK="$remaining" "$options_path/options" --commandPath "$command_path" --scriptPath "$script_path" --title "$title_key"
else
OM_BACK_STACK="$remaining" "$options_path/options" --commandPath "$command_path" --title "$title_key"
fi
set -- --commandPath "$command_path"
[ -n "$script_path" ] && set -- "$@" --scriptPath "$script_path"
set -- "$@" --title "$title_key"
[ -n "$marker" ] && set -- "$@" --selectedMarker "$marker"
OM_BACK_STACK="$remaining" "$options_path/options" "$@"
97 changes: 97 additions & 0 deletions src/application_lifecycle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (C) 2026 DefKorns
//
// 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.

#ifndef APPLICATION_LIFECYCLE_H_
#define APPLICATION_LIFECYCLE_H_

#include <csignal>
#include <cstdlib>
#include <string>
#include <utility>
#include <sys/types.h>
#include <unistd.h>

extern char ** environ;

enum class ExitAction { Resume, Leave };

// Resume runs from the destructor, after main()'s locals (SDL) are released.
class ApplicationLifecycle
{
public:
ApplicationLifecycle() = default;
ApplicationLifecycle(const ApplicationLifecycle &) = delete;
ApplicationLifecycle & operator=(const ApplicationLifecycle &) = delete;

~ApplicationLifecycle()
{
if(action == ExitAction::Resume && !resumed && !script.empty())
{
resumed = true;
State().resume = 0;
std::system(("/bin/sh " + script).c_str());
}
}

void SetResumeScript(std::string path)
{
script = std::move(path);
State().argv[1] = script.c_str();
}

void Decide(ExitAction decision)
{
action = decision;
State().resume = decision == ExitAction::Resume ? 1 : 0;
}

// handler may only call async-signal-safe functions; vfork skips the atfork handlers
void InstallSignalHandlers()
{
struct sigaction sa;
sa.sa_handler = OnTerminate;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGTERM, &sa, nullptr);
sigaction(SIGHUP, &sa, nullptr);
}

private:
static void OnTerminate(int sig)
{
SignalState & state = State();
if(!state.fired && state.resume && state.argv[1])
{
state.fired = 1;
if(vfork() == 0)
{
execve("/bin/sh", const_cast<char * const *>(state.argv), environ);
_exit(127);
}
}
_exit(128 + sig);
}

struct SignalState
{
const char * argv[3] = { "sh", nullptr, nullptr };
volatile sig_atomic_t resume = 1;
volatile sig_atomic_t fired = 0;
};

static SignalState & State()
{
static SignalState state;
return state;
}

std::string script;
ExitAction action = ExitAction::Resume;
bool resumed = false;
};

#endif
74 changes: 74 additions & 0 deletions src/back_stack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (C) 2026 DefKorns
//
// 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.

#include "back_stack.h"

#include <vector>

namespace back_stack
{
std::string MakeEntry(const std::string & commandPath, const std::string & scriptPath,
const std::string & titleKey, const std::string & marker)
{
return commandPath + "," + scriptPath + "," + titleKey + (marker.empty() ? "" : "," + marker);
}

std::string PopIfLast(const std::string & stack, const std::string & entry)
{
const size_t lastSep = stack.find_last_of(';');
const std::string last = stack.empty() ? "" : (lastSep == std::string::npos ? stack : stack.substr(lastSep + 1));
if(last != entry)
return stack;
return lastSep == std::string::npos ? "" : stack.substr(0, lastSep);
}

std::string Push(const std::string & stack, const std::string & entry)
{
return stack.empty() ? entry : stack + ";" + entry;
}

std::string BackCommand(const std::string & stack, const std::string & optionsLocation)
{
if(stack.empty())
return "";

std::vector<std::string> entries;
for(size_t start = 0; start < stack.size();)
{
size_t sep = stack.find(';', start);
if(sep == std::string::npos)
sep = stack.size();
entries.push_back(stack.substr(start, sep - start));
start = sep + 1;
}
const std::string lastEntry = entries.back();
entries.pop_back();
std::string remainingStack;
for(size_t i = 0; i < entries.size(); ++i)
{
if(i)
remainingStack += ";";
remainingStack += entries[i];
}

const size_t c1 = lastEntry.find(',');
const size_t c2 = c1 == std::string::npos ? c1 : lastEntry.find(',', c1 + 1);
if(c2 == std::string::npos)
return "";

const std::string backPath = lastEntry.substr(0, c1);
const std::string backScriptPath = lastEntry.substr(c1 + 1, c2 - c1 - 1);
const size_t c3 = lastEntry.find(',', c2 + 1);
const std::string backTitleKey = lastEntry.substr(c2 + 1, c3 == std::string::npos ? std::string::npos : c3 - c2 - 1);
const std::string backMarker = c3 == std::string::npos ? "" : lastEntry.substr(c3 + 1);

return "usleep 50000 && OM_BACK_STACK=\"" + remainingStack + "\" " + optionsLocation + "options --commandPath " + backPath
+ (backScriptPath.empty() ? "" : " --scriptPath " + backScriptPath)
+ " --title \"" + backTitleKey + "\""
+ (backMarker.empty() ? "" : " --selectedMarker \"" + backMarker + "\"") + " &";
}
}
27 changes: 27 additions & 0 deletions src/back_stack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (C) 2026 DefKorns
//
// 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.

#ifndef BACK_STACK_H_
#define BACK_STACK_H_

#include <string>

// OM_BACK_STACK is ';'-separated entries of "commandPath,scriptPath,titleKey[,marker]".
namespace back_stack
{
std::string MakeEntry(const std::string & commandPath, const std::string & scriptPath,
const std::string & titleKey, const std::string & marker);

std::string PopIfLast(const std::string & stack, const std::string & entry);

std::string Push(const std::string & stack, const std::string & entry);

// empty when the stack is empty or the last entry is malformed
std::string BackCommand(const std::string & stack, const std::string & optionsLocation);
}

#endif
72 changes: 16 additions & 56 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "framework/powerwatch.h"
#include "framework/draw_helpers.h"
#include "framework/utf8.h"
#include "application_lifecycle.h"
#include "back_stack.h"
#include "command.h"
#include "download_screen.h"
#include "localization.h"
Expand Down Expand Up @@ -50,22 +52,14 @@ std::string CollapseSlashes(const std::string & path)
return result;
}

static struct ExitManager
{
std::string exitCommand;
bool runExitCommand = true;
~ExitManager()
{
if(runExitCommand)
system(exitCommand.c_str());
}
} _exitManager;
static ApplicationLifecycle lifecycle;

int main(int argc, char * argv[])
{
std::string optionsLocation(argv[0]);
optionsLocation = CollapseSlashes(optionsLocation.substr(0, optionsLocation.find_last_of('/')+1));
_exitManager.exitCommand = "/bin/sh " + optionsLocation + "/scripts/ResumeUI.sh";
lifecycle.SetResumeScript(optionsLocation + "/scripts/ResumeUI.sh");
lifecycle.InstallSignalHandlers();
std::string commandLocation(optionsLocation + "commands/");
std::string scriptLocation(optionsLocation + "scripts/");

Expand Down Expand Up @@ -113,12 +107,9 @@ int main(int argc, char * argv[])
commandLocation = CollapseSlashes(commandLocation);
scriptLocation = CollapseSlashes(scriptLocation);

std::string myEntry = commandLocation + "," + scriptLocation + "," + titleKey + (selectedMarker.empty() ? "" : "," + selectedMarker);
size_t lastSep = backStack.find_last_of(';');
std::string lastEntry = backStack.empty() ? "" : (lastSep == std::string::npos ? backStack : backStack.substr(lastSep + 1));
if(lastEntry == myEntry)
backStack = (lastSep == std::string::npos) ? "" : backStack.substr(0, lastSep);
setenv("OM_BACK_STACK", (backStack.empty() ? myEntry : backStack + ";" + myEntry).c_str(), 1);
const std::string myEntry = back_stack::MakeEntry(commandLocation, scriptLocation, titleKey, selectedMarker);
backStack = back_stack::PopIfLast(backStack, myEntry);
setenv("OM_BACK_STACK", back_stack::Push(backStack, myEntry).c_str(), 1);

auto ExpandTemplates = [&](std::string & cmd)
{
Expand Down Expand Up @@ -171,51 +162,18 @@ int main(int argc, char * argv[])
else
{
std::cerr << "Cannot open input folder.\n";
lifecycle.Decide(ExitAction::Resume);
exit(1);
}

if(commands.empty())
{
std::cerr << "No usable commands in " << commandLocation << "\n";
lifecycle.Decide(ExitAction::Resume);
exit(1);
}

std::string backCommand;
if(!backStack.empty())
{
std::vector<std::string> entries;
for(size_t start = 0; start < backStack.size();)
{
size_t sep = backStack.find(';', start);
if(sep == std::string::npos) sep = backStack.size();
entries.push_back(backStack.substr(start, sep-start));
start = sep+1;
}
std::string lastEntry = entries.back();
entries.pop_back();
std::string remainingStack;
for(size_t i = 0; i < entries.size(); ++i)
{
if(i) remainingStack += ";";
remainingStack += entries[i];
}

const size_t c1 = lastEntry.find(',');
const size_t c2 = c1 == std::string::npos ? c1 : lastEntry.find(',', c1+1);
if(c2 != std::string::npos)
{
std::string backPath = lastEntry.substr(0, c1);
std::string backScriptPath = lastEntry.substr(c1+1, c2-c1-1);
const size_t c3 = lastEntry.find(',', c2+1);
std::string backTitleKey = lastEntry.substr(c2+1, c3 == std::string::npos ? std::string::npos : c3-c2-1);
std::string backMarker = c3 == std::string::npos ? "" : lastEntry.substr(c3+1);

backCommand = "usleep 50000 && OM_BACK_STACK=\"" + remainingStack + "\" " + optionsLocation + "options --commandPath " + backPath
+ (backScriptPath.empty() ? "" : " --scriptPath " + backScriptPath)
+ " --title \"" + backTitleKey + "\""
+ (backMarker.empty() ? "" : " --selectedMarker \"" + backMarker + "\"") + " &";
}
}
const std::string backCommand = back_stack::BackCommand(backStack, optionsLocation);

int currentCommandId = 0;

Expand Down Expand Up @@ -541,8 +499,8 @@ int main(int argc, char * argv[])
const std::string & confirmKey = commands[currentCommandId].deleteConfirmKey;
if(!Confirm(Translate(confirmKey.empty() ? "DELETE_CONFIRM_GENERIC" : confirmKey)))
return false;
lifecycle.Decide(ExitAction::Leave);
system(commands[currentCommandId].deleteCommand.c_str());
_exitManager.runExitCommand = false;
return true;
};

Expand All @@ -557,12 +515,14 @@ int main(int argc, char * argv[])
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
lifecycle.Decide(ExitAction::Resume);
return 0;
}
}

if(sdl_context.powerwatch->buttonPress())
{
lifecycle.Decide(ExitAction::Resume);
break;
}
if(controller.GetButtonStatus(A) || controller.GetButtonStatus(START))
Expand All @@ -586,8 +546,8 @@ int main(int argc, char * argv[])
}
else if(confirmed)
{
lifecycle.Decide(commands[currentCommandId].restartUI ? ExitAction::Resume : ExitAction::Leave);
system(commands[currentCommandId].command.c_str());
_exitManager.runExitCommand = commands[currentCommandId].restartUI;
break;
}
}
Expand All @@ -614,9 +574,9 @@ int main(int argc, char * argv[])
{
if(bWasHeld && !bHoldFired)
{
lifecycle.Decide(backCommand.empty() ? ExitAction::Resume : ExitAction::Leave);
if(!backCommand.empty())
system(backCommand.c_str());
_exitManager.runExitCommand = backCommand.empty();
break;
}
bHoldFired = false;
Expand Down
Loading