Refactored command line parsing to use a container instead of C-style functions

Also simplified the Windows console stream redirection, and only redirect if started from the command line
This commit is contained in:
Leon Styhre 2023-08-11 20:22:48 +02:00
parent 7ab8b82bcc
commit 461bbe0e52

View file

@ -88,45 +88,36 @@ namespace
} // namespace } // namespace
#if defined(_WIN64) #if defined(_WIN64)
// Console output for Windows. The handling of consoles is a mess on this operating system, // As we link using the WINDOWS subsystem there is no console allocated on application startup.
// and this is the best solution I could find. EmulationStation is built using the WINDOWS // As such we'll attempt to attach to a parent console, and if this fails it probably means we've
// subsystem (using the -mwindows compiler flag). The idea is to attach to or allocate a new // not been started from the command line. In this case there is no need to redirect anything.
// console as needed. However some console types such as the 'Git Bash' shell simply doesn't // Note that some console types such as the "Git Bash" shell simply don't work properly. Windows
// work properly. Windows thinks it's attaching to a console but is unable to redirect the // thinks it's attaching to a console but is unable to redirect the standard input and output.
// standard input and output. Output also can't be redirected or piped by the user for any // Output also can't be redirected or piped by the user for any console type, and PowerShell
// console type and PowerShell behaves quite strange. Still, it works well enough to be // behaves quite strange. Still it works well enough to be somewhat usable.
// somewhat usable, at least for the moment. If the allocConsole argument is set to true void outputToConsole()
// and there is no console available, a new console window will be spawned.
win64ConsoleType outputToConsole(bool allocConsole)
{ {
HANDLE outputHandle {nullptr}; HANDLE outputHandle {nullptr};
HWND consoleWindow {nullptr}; HWND consoleWindow {nullptr};
win64ConsoleType consoleType {NO_CONSOLE};
// Try to attach to a parent console process. // Try to attach to a parent console process.
if (AttachConsole(ATTACH_PARENT_PROCESS)) if (AttachConsole(ATTACH_PARENT_PROCESS))
outputHandle = GetStdHandle(STD_OUTPUT_HANDLE); outputHandle = GetStdHandle(STD_OUTPUT_HANDLE);
// If there is a parent console process, then attempt to retrieve its handle. // If there is a parent console process, then attempt to retrieve its handle.
if (outputHandle != INVALID_HANDLE_VALUE && outputHandle != nullptr) { if (outputHandle != INVALID_HANDLE_VALUE && outputHandle != nullptr)
consoleWindow = GetConsoleWindow(); consoleWindow = GetConsoleWindow();
consoleType = PARENT_CONSOLE;
}
// If we couldn't retrieve the handle, it means we need to allocate a new console window. // If we couldn't retrieve the handle, then we're probably not running from a console.
if (!consoleWindow && allocConsole) { if (!consoleWindow)
AllocConsole(); return;
consoleType = ALLOCATED_CONSOLE;
}
// If we are attached to the parent console or we have opened a new console window, // Redirect stdin, stdout and stderr to the console window.
// then redirect stdin, stdout and stderr accordingly.
if (consoleType == PARENT_CONSOLE || consoleType == ALLOCATED_CONSOLE) {
FILE* fp {nullptr}; FILE* fp {nullptr};
freopen_s(&fp, "CONIN$", "rb", stdin); freopen_s(&fp, "CONIN$", "r", stdin);
freopen_s(&fp, "CONOUT$", "wb", stdout); freopen_s(&fp, "CONOUT$", "w", stdout);
setvbuf(stdout, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0);
freopen_s(&fp, "CONOUT$", "wb", stderr); freopen_s(&fp, "CONOUT$", "w", stderr);
setvbuf(stderr, 0, _IONBF, 0); setvbuf(stderr, 0, _IONBF, 0);
// Point the standard streams to the console. // Point the standard streams to the console.
@ -139,25 +130,13 @@ win64ConsoleType outputToConsole(bool allocConsole)
std::cerr.clear(); std::cerr.clear();
std::wcin.clear(); std::wcin.clear();
std::cin.clear(); std::cin.clear();
std::cout << "\n";
}
return consoleType;
} }
#endif #endif
bool parseArgs(int argc, char* argv[]) bool parseArguments(const std::vector<std::string>& arguments)
{ {
Utils::FileSystem::setExePath(argv[0]); Utils::FileSystem::setExePath(arguments[0]);
const std::string portableFilePath {Utils::FileSystem::getExePath() + "/portable.txt"};
#if defined(_WIN64)
// Print any command line output to the console.
if (argc > 1)
outputToConsole(false);
#endif
std::string portableFilePath {Utils::FileSystem::getExePath() + "/portable.txt"};
// This is primarily intended for portable ES-DE installations on Windows (for example // This is primarily intended for portable ES-DE installations on Windows (for example
// placed on a USB memory stick) but it may be usable for other operating systems too. // placed on a USB memory stick) but it may be usable for other operating systems too.
@ -190,7 +169,6 @@ bool parseArgs(int argc, char* argv[])
else if (homePath.size() == 2 && Utils::FileSystem::driveExists(homePath)) else if (homePath.size() == 2 && Utils::FileSystem::driveExists(homePath))
homeExists = true; homeExists = true;
#endif #endif
if (!homeExists) { if (!homeExists) {
std::cerr << "Error: Defined home path \"" << homePath << "\" does not exist\n"; std::cerr << "Error: Defined home path \"" << homePath << "\" does not exist\n";
} }
@ -206,64 +184,65 @@ bool parseArgs(int argc, char* argv[])
portableFile.close(); portableFile.close();
} }
// We need to process --home before any call to Settings::getInstance(), // We need to process --home before any call to Settings::getInstance(), because
// because settings are loaded from the home path. // settings are loaded from the home path.
for (int i {1}; i < argc; ++i) { for (size_t i {1}; i < arguments.size(); ++i) {
if (strcmp(argv[i], "--home") == 0) { if (arguments[i] == "--home") {
if (i >= argc - 1) { if (i >= arguments.size() - 1) {
std::cerr << "Error: No home path supplied with \'--home'\n"; std::cerr << "Error: No home path supplied with \'--home'\n";
return false; return false;
} }
#if defined(_WIN64) #if defined(_WIN64)
if (!Utils::FileSystem::exists(argv[i + 1]) && if (!Utils::FileSystem::exists(arguments[i + 1]) &&
(!Utils::FileSystem::driveExists(argv[i + 1]))) { (!Utils::FileSystem::driveExists(arguments[i + 1]))) {
#else #else
if (!Utils::FileSystem::exists(argv[i + 1])) { if (!Utils::FileSystem::exists(arguments[i + 1])) {
#endif #endif
std::cerr << "Error: Home path \'" << argv[i + 1] << "\' does not exist\n"; std::cerr << "Error: Home path \'" << arguments[i + 1] << "\' does not exist\n";
return false; return false;
} }
if (Utils::FileSystem::isRegularFile(argv[i + 1])) { if (Utils::FileSystem::isRegularFile(arguments[i + 1])) {
std::cerr << "Error: Home path \'" << argv[i + 1] std::cerr << "Error: Home path \'" << arguments[i + 1]
<< "\' is a file and not a directory\n"; << "\' is a file and not a directory\n";
return false; return false;
} }
Utils::FileSystem::setHomePath(argv[i + 1]); Utils::FileSystem::setHomePath(arguments[i + 1]);
portableMode = false; portableMode = false;
break; break;
} }
} }
for (int i {1}; i < argc; ++i) { for (size_t i {1}; i < arguments.size(); ++i) {
// Skip past --home flag as we already processed it. // Skip past --home flag as we already processed it.
if (strcmp(argv[i], "--home") == 0) { if (arguments[i] == "--home") {
++i; // Skip the argument value. ++i; // Skip the argument value.
continue; continue;
} }
if (strcmp(argv[i], "--display") == 0) { if (arguments[i] == "--display") {
if (i >= argc - 1 || atoi(argv[i + 1]) < 1 || atoi(argv[i + 1]) > 4) { if (i >= arguments.size() - 1 || stoi(arguments[i + 1]) < 1 ||
stoi(arguments[i + 1]) > 4) {
std::cerr << "Error: Invalid display index supplied\n"; std::cerr << "Error: Invalid display index supplied\n";
return false; return false;
} }
int DisplayIndex {atoi(argv[i + 1])}; const int displayIndex {stoi(arguments[i + 1])};
Settings::getInstance()->setInt("DisplayIndex", DisplayIndex); Settings::getInstance()->setInt("DisplayIndex", displayIndex);
settingsNeedSaving = true; settingsNeedSaving = true;
++i; ++i;
} }
else if (strcmp(argv[i], "--resolution") == 0) { else if (arguments[i] == "--resolution") {
if (i >= argc - 2) { if (i >= arguments.size() - 2) {
std::cerr << "Error: Invalid resolution values supplied\n"; std::cerr << "Error: Invalid resolution values supplied\n";
return false; return false;
} }
std::string widthArg {argv[i + 1]}; const std::string widthArg {arguments[i + 1]};
std::string heightArg {argv[i + 2]}; const std::string heightArg {arguments[i + 2]};
if (widthArg.find_first_not_of("0123456789") != std::string::npos || if (widthArg.find_first_not_of("0123456789") != std::string::npos ||
heightArg.find_first_not_of("0123456789") != std::string::npos) { heightArg.find_first_not_of("0123456789") != std::string::npos) {
std::cerr << "Error: Invalid resolution values supplied\n"; std::cerr << "Error: Invalid resolution values supplied\n";
return false; return false;
} }
int width {atoi(argv[i + 1])}; const int width {stoi(arguments[i + 1])};
int height {atoi(argv[i + 2])}; const int height {stoi(arguments[i + 2])};
if (width < 224 || height < 224 || width > 7680 || height > 7680 || if (width < 224 || height < 224 || width > 7680 || height > 7680 ||
height < width / 4 || width < height / 2) { height < width / 4 || width < height / 2) {
std::cerr << "Error: Unsupported resolution " << width << "x" << height std::cerr << "Error: Unsupported resolution " << width << "x" << height
@ -274,38 +253,38 @@ bool parseArgs(int argc, char* argv[])
Settings::getInstance()->setInt("ScreenHeight", height); Settings::getInstance()->setInt("ScreenHeight", height);
i += 2; i += 2;
} }
else if (strcmp(argv[i], "--screenoffset") == 0) { else if (arguments[i] == "--screenoffset") {
if (i >= argc - 2) { if (i >= arguments.size() - 2) {
std::cerr << "Error: Invalid screenoffset values supplied\n"; std::cerr << "Error: Invalid screenoffset values supplied\n";
return false; return false;
} }
int x {atoi(argv[i + 1])}; const int x {stoi(arguments[i + 1])};
int y {atoi(argv[i + 2])}; const int y {stoi(arguments[i + 2])};
Settings::getInstance()->setInt("ScreenOffsetX", x); Settings::getInstance()->setInt("ScreenOffsetX", x);
Settings::getInstance()->setInt("ScreenOffsetY", y); Settings::getInstance()->setInt("ScreenOffsetY", y);
i += 2; i += 2;
} }
else if (strcmp(argv[i], "--screenrotate") == 0) { else if (arguments[i] == "--screenrotate") {
if (i >= argc - 1) { if (i >= arguments.size() - 1) {
std::cerr << "Error: No screenrotate value supplied\n"; std::cerr << "Error: No screenrotate value supplied\n";
return false; return false;
} }
const std::string rotateValue {argv[i + 1]}; const std::string rotateValue {arguments[i + 1]};
if (rotateValue != "0" && rotateValue != "90" && rotateValue != "180" && if (rotateValue != "0" && rotateValue != "90" && rotateValue != "180" &&
rotateValue != "270") { rotateValue != "270") {
std::cerr << "Error: Invalid screenrotate value supplied\n"; std::cerr << "Error: Invalid screenrotate value supplied\n";
return false; return false;
} }
Settings::getInstance()->setInt("ScreenRotate", atoi(argv[i + 1])); Settings::getInstance()->setInt("ScreenRotate", stoi(arguments[i + 1]));
settingsNeedSaving = true; settingsNeedSaving = true;
++i; ++i;
} }
else if (strcmp(argv[i], "--fullscreen-padding") == 0) { else if (arguments[i] == "--fullscreen-padding") {
if (i >= argc - 1) { if (i >= arguments.size() - 1) {
std::cerr << "Error: No fullscreen-padding value supplied\n"; std::cerr << "Error: No fullscreen-padding value supplied\n";
return false; return false;
} }
std::string fullscreenPaddingValue {argv[i + 1]}; std::string fullscreenPaddingValue {arguments[i + 1]};
if (fullscreenPaddingValue != "on" && fullscreenPaddingValue != "off" && if (fullscreenPaddingValue != "on" && fullscreenPaddingValue != "off" &&
fullscreenPaddingValue != "1" && fullscreenPaddingValue != "0") { fullscreenPaddingValue != "1" && fullscreenPaddingValue != "0") {
std::cerr << "Error: Invalid fullscreen-padding value supplied\n"; std::cerr << "Error: Invalid fullscreen-padding value supplied\n";
@ -316,12 +295,12 @@ bool parseArgs(int argc, char* argv[])
Settings::getInstance()->setBool("FullscreenPadding", fullscreenPadding); Settings::getInstance()->setBool("FullscreenPadding", fullscreenPadding);
++i; ++i;
} }
else if (strcmp(argv[i], "--vsync") == 0) { else if (arguments[i] == "--vsync") {
if (i >= argc - 1) { if (i >= arguments.size() - 1) {
std::cerr << "Error: No VSync value supplied\n"; std::cerr << "Error: No VSync value supplied\n";
return false; return false;
} }
std::string vSyncValue {argv[i + 1]}; std::string vSyncValue {arguments[i + 1]};
if (vSyncValue != "on" && vSyncValue != "off" && vSyncValue != "1" && if (vSyncValue != "on" && vSyncValue != "off" && vSyncValue != "1" &&
vSyncValue != "0") { vSyncValue != "0") {
std::cerr << "Error: Invalid VSync value supplied\n"; std::cerr << "Error: Invalid VSync value supplied\n";
@ -331,25 +310,25 @@ bool parseArgs(int argc, char* argv[])
Settings::getInstance()->setBool("VSync", vSync); Settings::getInstance()->setBool("VSync", vSync);
++i; ++i;
} }
else if (strcmp(argv[i], "--max-vram") == 0) { else if (arguments[i] == "--max-vram") {
if (i >= argc - 1) { if (i >= arguments.size() - 1) {
std::cerr << "Error: Invalid VRAM value supplied\n"; std::cerr << "Error: Invalid VRAM value supplied\n";
return false; return false;
} }
const int maxVRAM {atoi(argv[i + 1])}; const int maxVRAM {stoi(arguments[i + 1])};
Settings::getInstance()->setInt("MaxVRAM", maxVRAM); Settings::getInstance()->setInt("MaxVRAM", maxVRAM);
settingsNeedSaving = true; settingsNeedSaving = true;
++i; ++i;
} }
#if !defined(USE_OPENGLES) #if !defined(USE_OPENGLES)
else if (strcmp(argv[i], "--anti-aliasing") == 0) { else if (arguments[i] == "--anti-aliasing") {
bool invalidValue {false}; bool invalidValue {false};
int antiAlias {0}; int antiAlias {0};
if (i >= argc - 1) { if (i >= arguments.size() - 1) {
invalidValue = true; invalidValue = true;
} }
else { else {
antiAlias = atoi(argv[i + 1]); antiAlias = stoi(arguments[i + 1]);
if (antiAlias != 0 && antiAlias != 2 && antiAlias != 4) if (antiAlias != 0 && antiAlias != 2 && antiAlias != 4)
invalidValue = true; invalidValue = true;
} }
@ -362,55 +341,55 @@ bool parseArgs(int argc, char* argv[])
++i; ++i;
} }
#endif #endif
else if (strcmp(argv[i], "--no-splash") == 0) { else if (arguments[i] == "--no-splash") {
Settings::getInstance()->setBool("SplashScreen", false); Settings::getInstance()->setBool("SplashScreen", false);
} }
#if defined(APPLICATION_UPDATER) #if defined(APPLICATION_UPDATER)
else if (strcmp(argv[i], "--no-update-check") == 0) { else if (arguments[i] == "--no-update-check") {
noUpdateCheck = true; noUpdateCheck = true;
} }
#endif #endif
else if (strcmp(argv[i], "--gamelist-only") == 0) { else if (arguments[i] == "--gamelist-only") {
Settings::getInstance()->setBool("ParseGamelistOnly", true); Settings::getInstance()->setBool("ParseGamelistOnly", true);
settingsNeedSaving = true; settingsNeedSaving = true;
} }
else if (strcmp(argv[i], "--ignore-gamelist") == 0) { else if (arguments[i] == "--ignore-gamelist") {
Settings::getInstance()->setBool("IgnoreGamelist", true); Settings::getInstance()->setBool("IgnoreGamelist", true);
} }
else if (strcmp(argv[i], "--show-hidden-files") == 0) { else if (arguments[i] == "--show-hidden-files") {
Settings::getInstance()->setBool("ShowHiddenFiles", true); Settings::getInstance()->setBool("ShowHiddenFiles", true);
settingsNeedSaving = true; settingsNeedSaving = true;
} }
else if (strcmp(argv[i], "--show-hidden-games") == 0) { else if (arguments[i] == "--show-hidden-games") {
Settings::getInstance()->setBool("ShowHiddenGames", true); Settings::getInstance()->setBool("ShowHiddenGames", true);
settingsNeedSaving = true; settingsNeedSaving = true;
} }
else if (strcmp(argv[i], "--force-full") == 0) { else if (arguments[i] == "--force-full") {
Settings::getInstance()->setString("UIMode", "full"); Settings::getInstance()->setString("UIMode", "full");
Settings::getInstance()->setBool("ForceFull", true); Settings::getInstance()->setBool("ForceFull", true);
} }
else if (strcmp(argv[i], "--force-kiosk") == 0) { else if (arguments[i] == "--force-kiosk") {
Settings::getInstance()->setBool("ForceKiosk", true); Settings::getInstance()->setBool("ForceKiosk", true);
} }
else if (strcmp(argv[i], "--force-kid") == 0) { else if (arguments[i] == "--force-kid") {
Settings::getInstance()->setBool("ForceKid", true); Settings::getInstance()->setBool("ForceKid", true);
} }
else if (strcmp(argv[i], "--force-input-config") == 0) { else if (arguments[i] == "--force-input-config") {
forceInputConfig = true; forceInputConfig = true;
} }
else if (strcmp(argv[i], "--create-system-dirs") == 0) { else if (arguments[i] == "--create-system-dirs") {
createSystemDirectories = true; createSystemDirectories = true;
} }
else if (strcmp(argv[i], "--debug") == 0) { else if (arguments[i] == "--debug") {
Settings::getInstance()->setBool("Debug", true); Settings::getInstance()->setBool("Debug", true);
Log::setReportingLevel(LogDebug); Log::setReportingLevel(LogDebug);
} }
else if (strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-v") == 0) { else if (arguments[i] == "--version" || arguments[i] == "-v") {
std::cout << "EmulationStation Desktop Edition v" << PROGRAM_VERSION_STRING << " (r" std::cout << "EmulationStation Desktop Edition v" << PROGRAM_VERSION_STRING << " (r"
<< PROGRAM_RELEASE_NUMBER << ")\n"; << PROGRAM_RELEASE_NUMBER << ")\n";
return false; return false;
} }
else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { else if (arguments[i] == "--help" || arguments[i] == "-h") {
std::cout << std::cout <<
// clang-format off // clang-format off
"Usage: emulationstation [options]\n" "Usage: emulationstation [options]\n"
@ -444,13 +423,13 @@ bool parseArgs(int argc, char* argv[])
" --version, -v Display version information\n" " --version, -v Display version information\n"
" --help, -h Summon a sentient, angry tuba\n"; " --help, -h Summon a sentient, angry tuba\n";
// clang-format on // clang-format on
return false; // Exit after printing help. return false;
} }
else { else {
std::string argv_unknown = argv[i]; const std::string argUnknown {arguments[i]};
std::cout << "Unknown option '" << argv_unknown << "'.\n"; std::cout << "Unknown option '" << argUnknown << "'.\n";
std::cout << "Try 'emulationstation --help' for more information.\n"; std::cout << "Try 'emulationstation --help' for more information.\n";
return false; // Exit after printing message. return false;
} }
} }
@ -477,7 +456,7 @@ bool checkApplicationHomeDirectory()
#endif #endif
Utils::FileSystem::createDirectory(applicationHome); Utils::FileSystem::createDirectory(applicationHome);
if (!Utils::FileSystem::exists(applicationHome)) { if (!Utils::FileSystem::exists(applicationHome)) {
std::cerr << "Fatal error: Couldn't create directory, permission problems?\n"; std::cerr << "Error: Couldn't create directory, permission problems?\n";
return false; return false;
} }
} }
@ -570,18 +549,26 @@ int main(int argc, char* argv[])
system(chmodCommand.c_str()); system(chmodCommand.c_str());
#endif #endif
if (!parseArgs(argc, argv)) {
#if defined(_WIN64) #if defined(_WIN64)
FreeConsole(); // If we've been started from a console then redirect the standard streams there.
outputToConsole();
#endif #endif
{
std::vector<std::string> arguments;
for (int i {0}; i < argc; ++i)
arguments.emplace_back(argv[i]);
#if defined(_WIN64)
if (!parseArguments(arguments)) {
FreeConsole();
return 0;
}
#else
if (!parseArguments(arguments)) {
return 0; return 0;
} }
#if defined(_WIN64)
// Send debug output to the console..
if (Settings::getInstance()->getBool("Debug"))
outputToConsole(true);
#endif #endif
}
#if defined(FREEIMAGE_LIB) #if defined(FREEIMAGE_LIB)
// Call this ONLY when linking with FreeImage as a static library. // Call this ONLY when linking with FreeImage as a static library.