From d8413493d065d64838e967dcf2a0ef6ff767958f Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Sat, 24 Aug 2024 18:15:19 +0100 Subject: [PATCH] Move Qt client's tr_main() into a separate file (#7076) Support special (optional) `--` argument to explicitly separate options from filenames. Support special `---` argument to separate client arguments from Qt arguments. --- qt/Application.cc | 205 +++----------------------------------- qt/Application.h | 12 ++- qt/CMakeLists.txt | 1 + qt/main.cc | 247 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 270 insertions(+), 195 deletions(-) create mode 100644 qt/main.cc diff --git a/qt/Application.cc b/qt/Application.cc index 8f0dceb13..b5ec84280 100644 --- a/qt/Application.cc +++ b/qt/Application.cc @@ -6,9 +6,6 @@ #include "Application.h" #include -#include -#include -#include #include #include @@ -17,7 +14,6 @@ #include #include #include -#include #ifdef QT_DBUS_LIB #include @@ -31,10 +27,7 @@ #include -#include -#include #include -#include #include "AccessibleSqueezeLabel.h" #include "AddData.h" @@ -49,22 +42,7 @@ namespace { -std::array const Opts = { - tr_option{ 'g', "config-dir", "Where to look for configuration files", "g", true, "" }, - { 'm', "minimized", "Start minimized in system tray", "m", false, nullptr }, - { 'p', "port", "Port to use when connecting to an existing session", "p", true, "" }, - { 'r', "remote", "Connect to an existing session at the specified hostname", "r", true, "" }, - { 'u', "username", "Username to use when connecting to an existing session", "u", true, "" }, - { 'v', "version", "Show version number and exit", "v", false, nullptr }, - { 'w', "password", "Password to use when connecting to an existing session", "w", true, "" }, - { 0, nullptr, nullptr, nullptr, false, nullptr } -}; - -char const* getUsage() -{ - return "Usage:\n" - " transmission [OPTIONS...] [torrent files]"; -} +auto const ConfigName = QLatin1String("transmission"); auto constexpr StatsRefreshIntervalMsec = 3000; auto constexpr SessionRefreshIntervalMsec = 3000; @@ -116,12 +94,17 @@ QAccessibleInterface* accessibleFactory(QString const& className, QObject* objec } // namespace -Application::Application(int& argc, char** argv) +Application::Application( + std::unique_ptr prefs, + bool minimized, + QString const& config_dir, + QStringList const& filenames, + int& argc, + char** argv) : QApplication{ argc, argv } - , config_name_{ QStringLiteral("transmission") } - , display_name_{ QStringLiteral("transmission-qt") } + , prefs_(std::move(prefs)) { - setApplicationName(config_name_); + setApplicationName(ConfigName); loadTranslations(); initUnits(); @@ -140,113 +123,6 @@ Application::Application(int& argc, char** argv) setAttribute(Qt::AA_DontShowIconsInMenus); #endif - // parse the command-line arguments - int c = 0; - bool minimized = false; - char const* optarg = nullptr; - QString host; - QString port; - QString username; - QString password; - QString config_dir; - QStringList filenames; - - while ((c = tr_getopt(getUsage(), argc, const_cast(argv), Opts.data(), &optarg)) != TR_OPT_DONE) - { - switch (c) - { - case 'g': - config_dir = QString::fromUtf8(optarg); - break; - - case 'p': - port = QString::fromUtf8(optarg); - break; - - case 'r': - host = QString::fromUtf8(optarg); - break; - - case 'u': - username = QString::fromUtf8(optarg); - break; - - case 'w': - password = QString::fromUtf8(optarg); - break; - - case 'm': - minimized = true; - break; - - case 'v': - qInfo() << qPrintable(display_name_) << LONG_VERSION_STRING; - quitLater(); - return; - - case TR_OPT_ERR: - qWarning() << qPrintable(QObject::tr("Invalid option")); - tr_getopt_usage(qPrintable(display_name_), getUsage(), Opts.data()); - quitLater(); - return; - - default: - filenames.append(QString::fromUtf8(optarg)); - break; - } - } - - // try to delegate the work to an existing copy of Transmission - // before starting ourselves... - InteropHelper const interop_client; - - if (interop_client.isConnected()) - { - bool delegated = false; - - for (QString const& filename : filenames) - { - auto const a = AddData(filename); - QString metainfo; - - switch (a.type) - { - case AddData::URL: - metainfo = a.url.toString(); - break; - - case AddData::MAGNET: - metainfo = a.magnet; - break; - - case AddData::FILENAME: - case AddData::METAINFO: - metainfo = QString::fromUtf8(a.toBase64()); - break; - - default: - break; - } - - if (!metainfo.isEmpty() && interop_client.addMetainfo(metainfo)) - { - delegated = true; - } - } - - if (delegated) - { - quitLater(); - return; - } - } - - // set the fallback config dir - if (config_dir.isNull()) - { - config_dir = QString::fromStdString(tr_getDefaultConfigDir("transmission")); - } - // ensure our config directory exists QDir const dir(config_dir); @@ -258,45 +134,6 @@ Application::Application(int& argc, char** argv) // is this the first time we've run transmission? bool const first_time = !dir.exists(QStringLiteral("settings.json")); - // initialize the prefs - prefs_ = std::make_unique(config_dir); - - if (!host.isNull()) - { - prefs_->set(Prefs::SESSION_REMOTE_HOST, host); - } - - if (!port.isNull()) - { - prefs_->set(Prefs::SESSION_REMOTE_PORT, port.toUInt()); - } - - if (!username.isNull()) - { - prefs_->set(Prefs::SESSION_REMOTE_USERNAME, username); - } - - if (!password.isNull()) - { - prefs_->set(Prefs::SESSION_REMOTE_PASSWORD, password); - } - - if (!host.isNull() || !port.isNull() || !username.isNull() || !password.isNull()) - { - prefs_->set(Prefs::SESSION_IS_REMOTE, true); - } - - if (prefs_->getBool(Prefs::START_MINIMIZED)) - { - minimized = true; - } - - // start as minimized only if the system tray present - if (!prefs_->getBool(Prefs::SHOW_TRAY_ICON)) - { - minimized = false; - } - #if QT_CONFIG(accessibility) QAccessible::installFactory(&accessibleFactory); #endif @@ -395,6 +232,8 @@ Application::Application(int& argc, char** argv) #endif } +Application::~Application() = default; + void Application::loadTranslations() { auto const qt_qm_dirs = QStringList{} << @@ -425,8 +264,8 @@ void Application::loadTranslations() installTranslator(&qt_translator_); } - if (loadTranslation(app_translator_, config_name_, locale, app_qm_dirs) || - loadTranslation(app_translator_, config_name_, english_locale, app_qm_dirs)) + if (loadTranslation(app_translator_, ConfigName, locale, app_qm_dirs) || + loadTranslation(app_translator_, ConfigName, english_locale, app_qm_dirs)) { installTranslator(&app_translator_); } @@ -707,19 +546,3 @@ void Application::onNotificationActionInvoked(quint32 /* notification_id */, QSt } } #endif - -/*** -**** -***/ - -int tr_main(int argc, char** argv) -{ - auto const init_mgr = tr_lib_init(); - - tr_locale_set_global(""); - - InteropHelper::initialize(); - - Application const app(argc, argv); - return QApplication::exec(); -} diff --git a/qt/Application.h b/qt/Application.h index 043b4487d..f53f21f2a 100644 --- a/qt/Application.h +++ b/qt/Application.h @@ -36,7 +36,14 @@ class Application : public QApplication TR_DISABLE_COPY_MOVE(Application) public: - Application(int& argc, char** argv); + Application( + std::unique_ptr prefs, + bool minimized, + QString const& config_dir, + QStringList const& filenames, + int& argc, + char** argv); + ~Application() override; void raise() const; bool notifyApp(QString const& title, QString const& body, QStringList const& actions = {}) const; @@ -113,9 +120,6 @@ private: FaviconCache favicon_cache_; - QString const config_name_ = QStringLiteral("transmission"); - QString const display_name_ = QStringLiteral("transmission-qt"); - #ifdef QT_DBUS_LIB QString const fdo_notifications_service_name_ = QStringLiteral("org.freedesktop.Notifications"); QString const fdo_notifications_path_ = QStringLiteral("/org/freedesktop/Notifications"); diff --git a/qt/CMakeLists.txt b/qt/CMakeLists.txt index 475681a11..b93c4ad58 100644 --- a/qt/CMakeLists.txt +++ b/qt/CMakeLists.txt @@ -56,6 +56,7 @@ target_sources(${TR_NAME}-qt InteropObject.h LicenseDialog.cc LicenseDialog.h + main.cc MainWindow.cc MainWindow.h MakeDialog.cc diff --git a/qt/main.cc b/qt/main.cc new file mode 100644 index 000000000..8801801f3 --- /dev/null +++ b/qt/main.cc @@ -0,0 +1,247 @@ +// This file Copyright © Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "Application.h" +#include "InteropHelper.h" +#include "Prefs.h" + +using namespace std::string_view_literals; + +namespace +{ + +char const* const DisplayName = "transmission-qt"; + +auto constexpr FileArgsSeparator = "--"sv; +auto constexpr QtArgsSeparator = "---"sv; + +std::array const Opts = { + tr_option{ 'g', "config-dir", "Where to look for configuration files", "g", true, "" }, + { 'm', "minimized", "Start minimized in system tray", "m", false, nullptr }, + { 'p', "port", "Port to use when connecting to an existing session", "p", true, "" }, + { 'r', "remote", "Connect to an existing session at the specified hostname", "r", true, "" }, + { 'u', "username", "Username to use when connecting to an existing session", "u", true, "" }, + { 'v', "version", "Show version number and exit", "v", false, nullptr }, + { 'w', "password", "Password to use when connecting to an existing session", "w", true, "" }, + { 0, nullptr, nullptr, nullptr, false, nullptr } +}; + +char const* getUsage() +{ + return "Usage:\n" + " transmission-qt [options...] [[--] torrent files...] [--- Qt options...]"; +} + +bool tryDelegate(QStringList const& filenames) +{ + InteropHelper const interop_client; + if (!interop_client.isConnected()) + { + return false; + } + + bool delegated = false; + + for (auto const& filename : filenames) + { + auto const add_data = AddData(filename); + QString metainfo; + + switch (add_data.type) + { + case AddData::URL: + metainfo = add_data.url.toString(); + break; + + case AddData::MAGNET: + metainfo = add_data.magnet; + break; + + case AddData::FILENAME: + case AddData::METAINFO: + metainfo = QString::fromUtf8(add_data.toBase64()); + break; + + default: + break; + } + + if (!metainfo.isEmpty() && interop_client.addMetainfo(metainfo)) + { + delegated = true; + } + } + + return delegated; +} + +} // namespace + +int tr_main(int argc, char** argv) +{ + auto const init_mgr = tr_lib_init(); + + tr_locale_set_global(""); + + // parse the command-line arguments + bool minimized = false; + QString host; + QString port; + QString username; + QString password; + QString config_dir; + QStringList filenames; + + int opt = 0; + char const* optarg = nullptr; + int file_args_start_idx = -1; + int qt_args_start_idx = -1; + while (file_args_start_idx < 0 && qt_args_start_idx < 0 && + (opt = tr_getopt(getUsage(), argc, static_cast(argv), std::data(Opts), &optarg)) != TR_OPT_DONE) + { + switch (opt) + { + case 'g': + config_dir = QString::fromUtf8(optarg); + break; + + case 'p': + port = QString::fromUtf8(optarg); + break; + + case 'r': + host = QString::fromUtf8(optarg); + break; + + case 'u': + username = QString::fromUtf8(optarg); + break; + + case 'w': + password = QString::fromUtf8(optarg); + break; + + case 'm': + minimized = true; + break; + + case 'v': + fmt::print("{:s} {:s}\n", DisplayName, LONG_VERSION_STRING); + return 0; + + case TR_OPT_ERR: + fmt::print(stderr, "Invalid option\n"); + tr_getopt_usage(DisplayName, getUsage(), std::data(Opts)); + return 1; + + default: + if (optarg == FileArgsSeparator) + { + file_args_start_idx = tr_optind; + } + else if (optarg == QtArgsSeparator) + { + qt_args_start_idx = tr_optind; + } + else + { + filenames.append(QString::fromUtf8(optarg)); + } + + break; + } + } + + if (file_args_start_idx >= 0) + { + for (int i = file_args_start_idx; i < argc; ++i) + { + if (argv[i] == QtArgsSeparator) + { + qt_args_start_idx = i + 1; + break; + } + + filenames.push_back(QString::fromUtf8(argv[i])); + } + } + + InteropHelper::initialize(); + + // try to delegate the work to an existing copy of Transmission + // before starting ourselves... + if (tryDelegate(filenames)) + { + return 0; + } + + // set the fallback config dir + if (config_dir.isNull()) + { + config_dir = QString::fromStdString(tr_getDefaultConfigDir("transmission")); + } + + // initialize the prefs + auto prefs = std::make_unique(config_dir); + + if (!host.isNull()) + { + prefs->set(Prefs::SESSION_REMOTE_HOST, host); + } + + if (!port.isNull()) + { + prefs->set(Prefs::SESSION_REMOTE_PORT, port.toUInt()); + } + + if (!username.isNull()) + { + prefs->set(Prefs::SESSION_REMOTE_USERNAME, username); + } + + if (!password.isNull()) + { + prefs->set(Prefs::SESSION_REMOTE_PASSWORD, password); + } + + if (!host.isNull() || !port.isNull() || !username.isNull() || !password.isNull()) + { + prefs->set(Prefs::SESSION_IS_REMOTE, true); + } + + if (prefs->getBool(Prefs::START_MINIMIZED)) + { + minimized = true; + } + + // start as minimized only if the system tray present + if (!prefs->getBool(Prefs::SHOW_TRAY_ICON)) + { + minimized = false; + } + + auto qt_argv = std::vector{ argv[0] }; + if (qt_args_start_idx >= 0) + { + qt_argv.insert(qt_argv.end(), &argv[qt_args_start_idx], &argv[argc]); + } + + auto qt_argc = static_cast(std::size(qt_argv)); + + Application const app(std::move(prefs), minimized, config_dir, filenames, qt_argc, std::data(qt_argv)); + return QApplication::exec(); +}