Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use CLI11 to parse command line options
This has a few advantages over the previous manual approach: - The option parsing is handled by the library, so it doesn't need to be done manually. - More detailed help usage is now shown when passing the -h/--help flag. Information about each of the available options is shown. - Options can be marked as mutually exclusive. This is done with the --remove_as_default and --set_as_default flags, for example. - Options can be passed in in windows style (e.g. /help). - Although not used at the moment, the library also offers other features, such as the ability to validate and transform input values.
- Loading branch information
Showing
13 changed files
with
5,057 additions
and
238 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
// Copyright (C) Explorer++ Project | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
// See LICENSE in the top level directory | ||
|
||
#include "stdafx.h" | ||
#include "CommandLine.h" | ||
#include "Explorer++_internal.h" | ||
#include "MainResource.h" | ||
#include "../Helper/Macros.h" | ||
#include "../Helper/ProcessHelper.h" | ||
#include "../Helper/SetDefaultFileManager.h" | ||
#include "../Helper/ShellHelper.h" | ||
#include "../Helper/StringHelper.h" | ||
#include "../ThirdParty/CLI11/CLI11.hpp" | ||
#include <boost/filesystem.hpp> | ||
#include <boost/log/core.hpp> | ||
|
||
extern std::vector<std::wstring> g_TabDirs; | ||
|
||
struct CommandLineSettings | ||
{ | ||
bool clearRegistrySettings; | ||
bool enableLogging; | ||
bool enablePlugins; | ||
bool removeAsDefault; | ||
bool setAsDefault; | ||
std::string language; | ||
bool jumplistNewTab; | ||
std::vector<std::string> directories; | ||
}; | ||
|
||
boost::optional<CommandLine::ExitInfo> ProcessCommandLineSettings(const CommandLineSettings& commandLineSettings); | ||
void OnClearRegistrySettings(); | ||
void OnRemoveAsDefault(); | ||
void OnSetAsDefault(); | ||
void OnJumplistNewTab(); | ||
|
||
boost::optional<CommandLine::ExitInfo> CommandLine::ProcessCommandLine() | ||
{ | ||
CLI::App app("Explorer++"); | ||
|
||
CommandLineSettings commandLineSettings; | ||
|
||
commandLineSettings.clearRegistrySettings = false; | ||
app.add_flag("--clear-registry-settings", commandLineSettings.clearRegistrySettings, "Clear existing registry settings"); | ||
|
||
commandLineSettings.enableLogging = false; | ||
app.add_flag("--enable-logging", commandLineSettings.enableLogging, "Enable logging"); | ||
|
||
commandLineSettings.enablePlugins = false; | ||
app.add_flag("--enable-plugins", commandLineSettings.enablePlugins, "Enable the Lua plugin system"); | ||
|
||
commandLineSettings.removeAsDefault = false; | ||
auto removeAsDefaultOption = app.add_flag("--remove-as-default", commandLineSettings.removeAsDefault, "Remove Explorer++ as the default file manager (requires administrator privileges)"); | ||
|
||
commandLineSettings.setAsDefault = false; | ||
auto setAsDefaultOption = app.add_flag("--set-as-default", commandLineSettings.setAsDefault, "Set Explorer++ as the default file manager (requires administrator privileges)"); | ||
|
||
removeAsDefaultOption->excludes(setAsDefaultOption); | ||
setAsDefaultOption->excludes(removeAsDefaultOption); | ||
|
||
app.add_option("--language", commandLineSettings.language, "Allows you to select your desired language. Should be a two-letter language code (e.g. FR, RU, etc)."); | ||
|
||
app.add_option("directories", commandLineSettings.directories, "Directories to open"); | ||
|
||
// The options in this group are only used internally by the | ||
// application. They're not directly exposed to users. | ||
CLI::App *privateCommands = app.add_subcommand("private"); | ||
privateCommands->group(""); | ||
|
||
privateCommands->add_flag(wstrToStr(NExplorerplusplus::JUMPLIST_TASK_NEWTAB_ARGUMENT), commandLineSettings.jumplistNewTab); | ||
|
||
try | ||
{ | ||
app.parse(__argc, __argv); | ||
} | ||
catch (const CLI::ParseError & e) | ||
{ | ||
return ExitInfo{ app.exit(e) }; | ||
} | ||
|
||
return ProcessCommandLineSettings(commandLineSettings); | ||
} | ||
|
||
boost::optional<CommandLine::ExitInfo> ProcessCommandLineSettings(const CommandLineSettings& commandLineSettings) | ||
{ | ||
if (commandLineSettings.jumplistNewTab) | ||
{ | ||
OnJumplistNewTab(); | ||
return CommandLine::ExitInfo{ EXIT_SUCCESS }; | ||
} | ||
|
||
if (commandLineSettings.clearRegistrySettings) | ||
{ | ||
OnClearRegistrySettings(); | ||
} | ||
|
||
if (commandLineSettings.enableLogging) | ||
{ | ||
boost::log::core::get()->set_logging_enabled(true); | ||
} | ||
|
||
if (commandLineSettings.enablePlugins) | ||
{ | ||
g_enablePlugins = true; | ||
} | ||
|
||
if (commandLineSettings.removeAsDefault) | ||
{ | ||
OnRemoveAsDefault(); | ||
} | ||
else if (commandLineSettings.setAsDefault) | ||
{ | ||
OnSetAsDefault(); | ||
} | ||
|
||
if (!commandLineSettings.language.empty()) | ||
{ | ||
g_bForceLanguageLoad = TRUE; | ||
|
||
StringCchCopy(g_szLang, SIZEOF_ARRAY(g_szLang), strToWstr(commandLineSettings.language).c_str()); | ||
} | ||
|
||
TCHAR processImageName[MAX_PATH]; | ||
GetProcessImageName(GetCurrentProcessId(), processImageName, SIZEOF_ARRAY(processImageName)); | ||
|
||
boost::filesystem::path processDirectoryPath(processImageName); | ||
processDirectoryPath.remove_filename(); | ||
|
||
for (const std::string& directory : commandLineSettings.directories) | ||
{ | ||
TCHAR szParsingPath[MAX_PATH]; | ||
DecodePath(strToWstr(directory).c_str(), processDirectoryPath.wstring().c_str(), szParsingPath, SIZEOF_ARRAY(szParsingPath)); | ||
|
||
g_TabDirs.push_back(szParsingPath); | ||
} | ||
|
||
return boost::none; | ||
} | ||
|
||
void OnClearRegistrySettings() | ||
{ | ||
LSTATUS lStatus; | ||
|
||
lStatus = SHDeleteKey(HKEY_CURRENT_USER, NExplorerplusplus::REG_MAIN_KEY); | ||
|
||
if (lStatus == ERROR_SUCCESS) | ||
MessageBox(NULL, _T("Settings cleared successfully."), NExplorerplusplus::APP_NAME, MB_OK); | ||
else | ||
MessageBox(NULL, _T("Settings could not be cleared."), NExplorerplusplus::APP_NAME, MB_ICONWARNING); | ||
} | ||
|
||
void OnRemoveAsDefault() | ||
{ | ||
BOOL bSuccess = NDefaultFileManager::RemoveAsDefaultFileManagerFileSystem(SHELL_DEFAULT_INTERNAL_COMMAND_NAME); | ||
|
||
/* Language hasn't been fully specified at this point, so | ||
can't load success/error message from language dll. Simply show | ||
a hardcoded success/error message. */ | ||
if (bSuccess) | ||
{ | ||
MessageBox(NULL, _T("Explorer++ successfully removed as default file manager."), | ||
NExplorerplusplus::APP_NAME, MB_OK); | ||
} | ||
else | ||
{ | ||
MessageBox(NULL, _T("Could not remove Explorer++ as default file manager. Please \ | ||
ensure you have administrator privileges."), NExplorerplusplus::APP_NAME, MB_ICONWARNING | MB_OK); | ||
} | ||
} | ||
|
||
void OnSetAsDefault() | ||
{ | ||
TCHAR menuText[256]; | ||
LoadString(GetModuleHandle(0), IDS_OPEN_IN_EXPLORERPLUSPLUS, | ||
menuText, SIZEOF_ARRAY(menuText)); | ||
|
||
BOOL bSuccess = NDefaultFileManager::SetAsDefaultFileManagerFileSystem( | ||
SHELL_DEFAULT_INTERNAL_COMMAND_NAME, menuText); | ||
|
||
if (bSuccess) | ||
{ | ||
MessageBox(NULL, _T("Explorer++ successfully set as default file manager."), | ||
NExplorerplusplus::APP_NAME, MB_OK); | ||
} | ||
else | ||
{ | ||
MessageBox(NULL, _T("Could not set Explorer++ as default file manager. Please \ | ||
ensure you have administrator privileges."), NExplorerplusplus::APP_NAME, MB_ICONWARNING | MB_OK); | ||
} | ||
} | ||
|
||
void OnJumplistNewTab() | ||
{ | ||
/* This will be called when the user clicks the | ||
'New Tab' item on the tasks menu in Windows 7 and above. | ||
Find the already opened version of Explorer++, | ||
and tell it to open a new tab. */ | ||
HANDLE hMutex; | ||
|
||
hMutex = CreateMutex(NULL, TRUE, _T("Explorer++")); | ||
|
||
if (GetLastError() == ERROR_ALREADY_EXISTS) | ||
{ | ||
HWND hPrev; | ||
|
||
hPrev = FindWindow(NExplorerplusplus::CLASS_NAME, NULL); | ||
|
||
if (hPrev != NULL) | ||
{ | ||
COPYDATASTRUCT cds; | ||
|
||
cds.cbData = 0; | ||
cds.lpData = NULL; | ||
SendMessage(hPrev, WM_COPYDATA, NULL, (LPARAM)& cds); | ||
|
||
SetForegroundWindow(hPrev); | ||
ShowWindow(hPrev, SW_SHOW); | ||
} | ||
} | ||
|
||
if (hMutex != NULL) | ||
{ | ||
CloseHandle(hMutex); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// Copyright (C) Explorer++ Project | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
// See LICENSE in the top level directory | ||
|
||
#pragma once | ||
|
||
#include <boost/optional.hpp> | ||
|
||
namespace CommandLine | ||
{ | ||
struct ExitInfo | ||
{ | ||
int exitCode; | ||
}; | ||
|
||
boost::optional<ExitInfo> ProcessCommandLine(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// Copyright (C) Explorer++ Project | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
// See LICENSE in the top level directory | ||
|
||
#include "stdafx.h" | ||
#include "Console.h" | ||
#include <iostream> | ||
|
||
bool RedirectConsoleIO(); | ||
|
||
// The code in this file has been sourced from the following Stack Overflow | ||
// answer: | ||
// https://stackoverflow.com/a/55875595 | ||
// It doesn't make much sense to rewrite it, given that it's relatively simple | ||
// and performs a specific function (so any rewritten code would look about the | ||
// same). | ||
|
||
bool Console::AttachParentConsole() | ||
{ | ||
bool result = false; | ||
|
||
// Release any current console and redirect IO to NUL | ||
ReleaseConsole(); | ||
|
||
// Attempt to attach to parent process's console | ||
if (AttachConsole(ATTACH_PARENT_PROCESS)) | ||
{ | ||
result = RedirectConsoleIO(); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
bool Console::ReleaseConsole() | ||
{ | ||
bool result = true; | ||
FILE* fp; | ||
|
||
// Just to be safe, redirect standard IO to NUL before releasing. | ||
|
||
// Redirect STDIN to NUL | ||
if (freopen_s(&fp, "NUL:", "r", stdin) != 0) | ||
result = false; | ||
else | ||
setvbuf(stdin, NULL, _IONBF, 0); | ||
|
||
// Redirect STDOUT to NUL | ||
if (freopen_s(&fp, "NUL:", "w", stdout) != 0) | ||
result = false; | ||
else | ||
setvbuf(stdout, NULL, _IONBF, 0); | ||
|
||
// Redirect STDERR to NUL | ||
if (freopen_s(&fp, "NUL:", "w", stderr) != 0) | ||
result = false; | ||
else | ||
setvbuf(stderr, NULL, _IONBF, 0); | ||
|
||
// Detach from console | ||
if (!FreeConsole() || !result) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
bool RedirectConsoleIO() | ||
{ | ||
bool result = true; | ||
FILE* fp; | ||
|
||
// Redirect STDIN if the console has an input handle | ||
if (GetStdHandle(STD_INPUT_HANDLE) != INVALID_HANDLE_VALUE) | ||
if (freopen_s(&fp, "CONIN$", "r", stdin) != 0) | ||
result = false; | ||
else | ||
setvbuf(stdin, NULL, _IONBF, 0); | ||
|
||
// Redirect STDOUT if the console has an output handle | ||
if (GetStdHandle(STD_OUTPUT_HANDLE) != INVALID_HANDLE_VALUE) | ||
if (freopen_s(&fp, "CONOUT$", "w", stdout) != 0) | ||
result = false; | ||
else | ||
setvbuf(stdout, NULL, _IONBF, 0); | ||
|
||
// Redirect STDERR if the console has an error handle | ||
if (GetStdHandle(STD_ERROR_HANDLE) != INVALID_HANDLE_VALUE) | ||
if (freopen_s(&fp, "CONOUT$", "w", stderr) != 0) | ||
result = false; | ||
else | ||
setvbuf(stderr, NULL, _IONBF, 0); | ||
|
||
// Make C++ standard streams point to console as well. | ||
std::ios::sync_with_stdio(true); | ||
|
||
// Clear the error state for each of the C++ standard streams. | ||
std::wcout.clear(); | ||
std::cout.clear(); | ||
std::wcerr.clear(); | ||
std::cerr.clear(); | ||
std::wcin.clear(); | ||
std::cin.clear(); | ||
|
||
return result; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (C) Explorer++ Project | ||
// SPDX-License-Identifier: GPL-3.0-only | ||
// See LICENSE in the top level directory | ||
|
||
#pragma once | ||
|
||
namespace Console | ||
{ | ||
bool AttachParentConsole(); | ||
bool ReleaseConsole(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.