From eefefe187a58c0c695d1237bc97942647d418211 Mon Sep 17 00:00:00 2001 From: Nicholas Morris <129607660+niwamo@users.noreply.github.com> Date: Sat, 31 May 2025 12:39:02 -0500 Subject: [PATCH] Adds Support for Command-Line on Windows (#3699) * + cli support on windows - removes the preprocessor macro that prevented arg parsing on Windows - adds windows-cli.cpp as src for a wrapper exe - adds flameshot-cli target into cmake when building on Windows - updates README * updates PR + support for unicode characters in cli args + additional clarification in README re: flameshot-cli + new workaround for spaces in _popen path; works with relative output paths * fix EOL * updated flameshot.exe path construction avoids using the hard-coded length of flameshot-cli.exe * fix whitespace (clang) --- README.md | 8 ++++++ src/CMakeLists.txt | 11 ++++++++ src/main.cpp | 2 -- src/windows-cli.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/windows-cli.cpp diff --git a/README.md b/README.md index 1e058470..6a9896ec 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,14 @@ A systray icon will be in your system's panel while Flameshot is running. Do a right click on the tray icon and you'll see some menu items to open the configuration window and the information window. Check out the About window to see all available shortcuts in the graphical capture mode. +### Usage on Windows + +On Windows, `flameshot.exe` will behave as expected for all supported command-line arguments, +but it will not output any text to the console. This is problematic if, for example, you are +running `flameshot.exe -h`. + +If you require console output, run `flameshot-cli.exe` instead. `flameshot-cli.exe` is a minimal wrapper around `flameshot.exe` that ensures all stdout is captured and output to the console. + ### CLI configuration You can use the graphical menu to configure Flameshot, but alternatively you can use your terminal or scripts to do so. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b3e6358..dde2fb92 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -73,6 +73,13 @@ if(FLAMESHOT_ICON) endif() if (WIN32) + add_executable(flameshot-cli) + target_sources( + flameshot-cli + PRIVATE + windows-cli.cpp) + set_target_properties(flameshot-cli PROPERTIES OUTPUT_NAME "flameshot-cli") + target_link_options(flameshot-cli PRIVATE /SUBSYSTEM:CONSOLE) set_property(TARGET flameshot PROPERTY WIN32_EXECUTABLE true) if (MSVC) target_compile_options(flameshot PRIVATE /source-charset:utf-8) @@ -322,6 +329,10 @@ install(TARGETS flameshot EXPORT flameshot-targets BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +if (WIN32) + install(TARGETS flameshot-cli + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif () if (UNIX) # Install desktop files, completion and dbus files diff --git a/src/main.cpp b/src/main.cpp index 69ca35db..7a3dfbf8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -184,7 +184,6 @@ int main(int argc, char* argv[]) return qApp->exec(); } -#if !defined(Q_OS_WIN) /*--------------| * CLI parsing | * ------------*/ @@ -609,6 +608,5 @@ int main(int argc, char* argv[]) } finish: -#endif return 0; } diff --git a/src/windows-cli.cpp b/src/windows-cli.cpp new file mode 100644 index 00000000..60773b63 --- /dev/null +++ b/src/windows-cli.cpp @@ -0,0 +1,69 @@ +#include +#include + +std::wstring joinArgs(int argc, wchar_t* argv[]) +{ + std::wstring result; + for (int i = 1; i < argc; ++i) { + if (i > 1) { + result += L" "; + } + result += argv[i]; + } + return result; +} + +void CallFlameshot(const std::wstring args, bool wait) +{ + // generate full path for flameshot executable + wchar_t path[MAX_PATH]; + int pathLength = GetModuleFileNameW(NULL, path, MAX_PATH); + std::wstring pathstring(path); + + // Find the last backslash to isolate the filename + size_t lastBackslash = pathstring.find_last_of(L'\\'); + std::wstring directory = (lastBackslash != std::wstring::npos) + ? pathstring.substr(0, lastBackslash + 1) + : L""; + + // generate command string + // note: binary path placed within quotes in case of spaces in path + int cmdSize = 32 + sizeof(directory) + sizeof(args); + wchar_t* cmd = (wchar_t*)malloc(sizeof(wchar_t) * cmdSize); + swprintf(cmd, + cmdSize, + L"\"%s\\flameshot.exe\" %s", + directory.c_str(), + args.c_str()); + // call subprocess + FILE* stream = _wpopen(cmd, L"r"); + free(cmd); + if (wait) { + if (stream) { + const int MAX_BUFFER = 2048; + char buffer[MAX_BUFFER]; + while (!feof(stream)) { + if (fgets(buffer, MAX_BUFFER, stream) != NULL) { + std::cout << buffer; + } + } + } + _pclose(stream); + } + return; +} + +// Console 'wrapper' for flameshot on windows +int wmain(int argc, wchar_t* argv[]) +{ + // if no args, do not wait for stdout + if (argc == 1) { + std::cout << "Starting flameshot in daemon mode" << std::endl; + CallFlameshot(L"", false); + } else { + std::wstring argString = joinArgs(argc, argv); + CallFlameshot(argString, true); + } + std::cout.flush(); + return 0; +}