From 78e856f7c9053bad890a194672933b369ae87484 Mon Sep 17 00:00:00 2001 From: teknsl <64030674+teknsl@users.noreply.github.com> Date: Sat, 18 Jul 2026 13:53:18 +0200 Subject: [PATCH] feat: command-line arguments for setting portable directory (#7093) Reviewed-by: Nerixyz Reviewed-by: pajlada --- benchmarks/src/main.cpp | 2 +- mocks/include/mocks/EmptyApplication.hpp | 4 ++-- src/common/Args.cpp | 19 ++++++++++++++++++ src/common/Args.hpp | 3 +++ src/common/Modes.cpp | 8 +++++++- src/common/Modes.hpp | 5 ++++- src/main.cpp | 7 +++---- src/singletons/Paths.cpp | 25 ++++++++++-------------- src/singletons/Paths.hpp | 10 +++------- src/singletons/Updates.cpp | 6 +++--- src/singletons/Updates.hpp | 2 +- src/widgets/dialogs/UpdateDialog.cpp | 7 ++++--- tests/src/main.cpp | 2 +- 13 files changed, 61 insertions(+), 39 deletions(-) diff --git a/benchmarks/src/main.cpp b/benchmarks/src/main.cpp index 5b8dfcc2c..cc12f1a23 100644 --- a/benchmarks/src/main.cpp +++ b/benchmarks/src/main.cpp @@ -27,7 +27,7 @@ int main(int argc, char **argv) // Ensure settings are initialized before any benchmarks are run QTemporaryDir settingsDir; settingsDir.setAutoRemove(false); // we'll remove it manually - chatterino::Settings settings(Modes(), args, settingsDir.path()); + chatterino::Settings settings(Modes(args), args, settingsDir.path()); QTimer::singleShot(0, [&]() { ::benchmark::RunSpecifiedBenchmarks(); diff --git a/mocks/include/mocks/EmptyApplication.hpp b/mocks/include/mocks/EmptyApplication.hpp index 0ccc877c8..a8433a594 100644 --- a/mocks/include/mocks/EmptyApplication.hpp +++ b/mocks/include/mocks/EmptyApplication.hpp @@ -294,9 +294,9 @@ public: } QTemporaryDir settingsDir; - Modes modes_; - Paths paths_ = {modes_}; Args args_; + Modes modes_{args_}; + Paths paths_ = {args_, modes_}; }; } // namespace chatterino::mock diff --git a/src/common/Args.cpp b/src/common/Args.cpp index 77ab03bc4..e5ac92d0e 100644 --- a/src/common/Args.cpp +++ b/src/common/Args.cpp @@ -188,6 +188,12 @@ Args::Args(const QApplication &app) "like you have to use this, please reach out to our issue tracker at " "https://github.com/Chatterino/chatterino2/issues"); + QCommandLineOption portableEnable("portable", "Enable portable mode."); + + QCommandLineOption portableDirectory( + "portable-dir", "Directory to use when portable mode is enabled.", + "directory"); + #ifndef NDEBUG QCommandLineOption useLocalEventsubOption( "use-local-eventsub", @@ -207,6 +213,8 @@ Args::Args(const QApplication &app) channelLayout, activateOption, useOldScalingOption, + portableEnable, + portableDirectory, #ifndef NDEBUG useLocalEventsubOption, #endif @@ -283,6 +291,17 @@ Args::Args(const QApplication &app) this->useOldScaling = true; } + if (parser.isSet(portableEnable)) + { + this->portableEnable = true; + } + + if (parser.isSet(portableDirectory)) + { + this->portableDirectory = + QDir(parser.value(portableDirectory)).absolutePath(); + } + #ifndef NDEBUG if (parser.isSet(useLocalEventsubOption)) { diff --git a/src/common/Args.hpp b/src/common/Args.hpp index 40daf3d8f..ed9b9fc5b 100644 --- a/src/common/Args.hpp +++ b/src/common/Args.hpp @@ -68,6 +68,9 @@ public: bool verbose{}; bool safeMode{}; + bool portableEnable{}; + std::optional portableDirectory; + bool useOldScaling = false; #ifndef NDEBUG diff --git a/src/common/Modes.cpp b/src/common/Modes.cpp index 647b10cfc..e69b96a5c 100644 --- a/src/common/Modes.cpp +++ b/src/common/Modes.cpp @@ -4,14 +4,20 @@ #include "common/Modes.hpp" +#include "common/Args.hpp" #include "util/CombinePath.hpp" #include namespace chatterino { -Modes::Modes() +Modes::Modes(const Args &args) { + if (args.portableEnable) + { + this->isPortable = true; + } + QFile file(combinePath(QCoreApplication::applicationDirPath(), "modes")); if (!file.open(QIODevice::ReadOnly)) { diff --git a/src/common/Modes.hpp b/src/common/Modes.hpp index 8b5324642..19005c7bc 100644 --- a/src/common/Modes.hpp +++ b/src/common/Modes.hpp @@ -6,11 +6,14 @@ namespace chatterino { +class Args; + class Modes { public: - Modes(); + explicit Modes(const Args &args); + /// Marked by the line `portable` or `portableEnable` from `Args` bool isPortable{}; /// Marked by the line `externally-packaged` diff --git a/src/main.cpp b/src/main.cpp index 2e0e6423d..f23c1e617 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,7 +50,8 @@ int main(int argc, char **argv) Version::instance().appUserModelID().c_str()); #endif - const Modes modes; + const Args args(a); + const Modes modes(args); std::unique_ptr paths; // Optional logger override that logs to a file @@ -58,7 +59,7 @@ int main(int argc, char **argv) try { - paths = std::make_unique(modes); + paths = std::make_unique(args, modes); } catch (std::runtime_error &error) { @@ -85,8 +86,6 @@ int main(int argc, char **argv) } ipc::initPaths(paths.get()); - const Args args(a); - #ifdef CHATTERINO_WITH_CRASHPAD const auto crashpadHandler = installCrashHandler(args, *paths); #endif diff --git a/src/singletons/Paths.cpp b/src/singletons/Paths.cpp index b3ddc7edb..ebc0dded5 100644 --- a/src/singletons/Paths.cpp +++ b/src/singletons/Paths.cpp @@ -4,6 +4,7 @@ #include "singletons/Paths.hpp" +#include "common/Args.hpp" #include "common/Modes.hpp" #include "singletons/Settings.hpp" #include "util/CombinePath.hpp" @@ -19,12 +20,11 @@ using namespace std::literals; namespace chatterino { -Paths::Paths(const Modes &modes) +Paths::Paths(const Args &args, const Modes &modes) { this->initAppFilePathHash(); - this->initCheckPortable(); - this->initRootDirectory(modes); + this->initRootDirectory(args, modes); this->initSubDirectories(); } @@ -75,23 +75,18 @@ void Paths::initAppFilePathHash() .replace("/", "x"); } -void Paths::initCheckPortable() +void Paths::initRootDirectory(const Args &args, const Modes &modes) { - this->portable_ = QFileInfo::exists( - combinePath(QCoreApplication::applicationDirPath(), "portable")); -} - -void Paths::initRootDirectory(const Modes &modes) -{ - assert(this->portable_.has_value()); - - // Root path = %APPDATA%/Chatterino or the folder that the executable - // resides in - this->rootAppDataDirectory = [&]() -> QString { // portable if (modes.isPortable) { + // override + if (args.portableDirectory.has_value()) + { + return args.portableDirectory.value(); + } + return QCoreApplication::applicationDirPath(); } diff --git a/src/singletons/Paths.hpp b/src/singletons/Paths.hpp index 50f4ae653..459dbcf34 100644 --- a/src/singletons/Paths.hpp +++ b/src/singletons/Paths.hpp @@ -6,16 +6,15 @@ #include -#include - namespace chatterino { class Modes; +class Args; class Paths { public: - Paths(const Modes &modes); + Paths(const Args &args, const Modes &modes); // Root directory for the configuration files. %APPDATA%/chatterino or // ExecutablePath for portable mode @@ -64,12 +63,9 @@ public: private: void initAppFilePathHash(); - void initCheckPortable(); - void initRootDirectory(const Modes &modes); + void initRootDirectory(const Args &args, const Modes &modes); void initSubDirectories(); - std::optional portable_; - // Directory for cache files. Same as /Misc QString cacheDirectory_; }; diff --git a/src/singletons/Updates.cpp b/src/singletons/Updates.cpp index 310d878c5..daa4734e6 100644 --- a/src/singletons/Updates.cpp +++ b/src/singletons/Updates.cpp @@ -206,7 +206,7 @@ void Updates::installUpdates() file.flush(); file.close(); - auto updaterPath = Updates::portableUpdaterPath(); + auto updaterPath = Updates::portableUpdaterPath(this->paths); if (!QFile::exists(updaterPath)) { this->setStatus_(MissingPortableUpdater); @@ -410,9 +410,9 @@ Updates::Status Updates::getStatus() const return this->status_; } -QString Updates::portableUpdaterPath() +QString Updates::portableUpdaterPath(const Paths &paths) { - return combinePath(QCoreApplication::applicationDirPath(), + return combinePath(paths.rootAppDataDirectory, "updater.1/ChatterinoUpdater.exe"); } diff --git a/src/singletons/Updates.hpp b/src/singletons/Updates.hpp index 8c9b66949..d6784ed08 100644 --- a/src/singletons/Updates.hpp +++ b/src/singletons/Updates.hpp @@ -55,7 +55,7 @@ public: void installUpdates(); Status getStatus() const; - static QString portableUpdaterPath(); + static QString portableUpdaterPath(const Paths &paths); bool shouldShowUpdateButton() const; bool isError() const; diff --git a/src/widgets/dialogs/UpdateDialog.cpp b/src/widgets/dialogs/UpdateDialog.cpp index cdee1b1b7..9796c502b 100644 --- a/src/widgets/dialogs/UpdateDialog.cpp +++ b/src/widgets/dialogs/UpdateDialog.cpp @@ -99,9 +99,10 @@ void UpdateDialog::updateStatusChanged(Updates::Status status) break; case Updates::MissingPortableUpdater: { - this->ui_.label->setText("The portable updater (expected in " % - Updates::portableUpdaterPath() % - ") was not found."); + this->ui_.label->setText( + "The portable updater (expected in " % + Updates::portableUpdaterPath(getApp()->getPaths()) % + ") was not found."); } break; diff --git a/tests/src/main.cpp b/tests/src/main.cpp index 6718e7a29..89b107910 100644 --- a/tests/src/main.cpp +++ b/tests/src/main.cpp @@ -43,7 +43,7 @@ int main(int argc, char **argv) // Ensure settings are initialized before any tests are run QTemporaryDir settingsDir; settingsDir.setAutoRemove(false); // we'll remove it manually - chatterino::Settings settings(Modes(), args, settingsDir.path()); + chatterino::Settings settings(Modes(args), args, settingsDir.path()); QTimer::singleShot(0, [&]() { auto res = RUN_ALL_TESTS();