Move Qt client's tr_main() into a separate file

Support special (optional) `--` argument to explicitly separate options
from filenames. Support special `---` argument to separate client
arguments from Qt arguments.
This commit is contained in:
Mike Gelfand 2024-06-03 22:43:41 +01:00
parent ec5296c8dc
commit c2dd932afd
No known key found for this signature in database
GPG Key ID: CC4DBBE3299B16F8
4 changed files with 265 additions and 190 deletions

View File

@ -6,9 +6,7 @@
#include "Application.h"
#include <algorithm>
#include <array>
#include <ctime>
#include <memory>
#include <utility>
#include <QIcon>
@ -49,22 +47,7 @@
namespace
{
std::array<tr_option, 8> const Opts = {
tr_option{ 'g', "config-dir", "Where to look for configuration files", "g", true, "<path>" },
{ 'm', "minimized", "Start minimized in system tray", "m", false, nullptr },
{ 'p', "port", "Port to use when connecting to an existing session", "p", true, "<port>" },
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", true, "<host>" },
{ 'u', "username", "Username to use when connecting to an existing session", "u", true, "<username>" },
{ 'v', "version", "Show version number and exit", "v", false, nullptr },
{ 'w', "password", "Password to use when connecting to an existing session", "w", true, "<password>" },
{ 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 +99,17 @@ QAccessibleInterface* accessibleFactory(QString const& className, QObject* objec
} // namespace
Application::Application(int& argc, char** argv)
Application::Application(
std::unique_ptr<Prefs> 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 +128,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<char const**>(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 +139,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<Prefs>(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 +237,8 @@ Application::Application(int& argc, char** argv)
#endif
}
Application::~Application() = default;
void Application::loadTranslations()
{
auto const qt_qm_dirs = QStringList{} <<
@ -425,8 +269,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 +551,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();
}

View File

@ -36,7 +36,14 @@ class Application : public QApplication
TR_DISABLE_COPY_MOVE(Application)
public:
Application(int& argc, char** argv);
Application(
std::unique_ptr<Prefs> 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<QPixmap> 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");

View File

@ -56,6 +56,7 @@ target_sources(${TR_NAME}-qt
InteropObject.h
LicenseDialog.cc
LicenseDialog.h
main.cc
MainWindow.cc
MainWindow.h
MakeDialog.cc

242
qt/main.cc Normal file
View File

@ -0,0 +1,242 @@
#include "Application.h"
#include "InteropHelper.h"
#include "Prefs.h"
#include <QtDebug>
#include <array>
#include <memory>
#include <string_view>
#include <libtransmission/transmission.h>
#include <libtransmission/tr-getopt.h>
#include <libtransmission/utils.h>
#include <libtransmission/version.h>
using namespace std::string_view_literals;
namespace
{
char const* const DisplayName = "transmission-qt";
auto constexpr FileArgsSeparator = "--"sv;
auto constexpr QtArgsSeparator = "---"sv;
std::array<tr_option, 8> const Opts = {
tr_option{ 'g', "config-dir", "Where to look for configuration files", "g", true, "<path>" },
{ 'm', "minimized", "Start minimized in system tray", "m", false, nullptr },
{ 'p', "port", "Port to use when connecting to an existing session", "p", true, "<port>" },
{ 'r', "remote", "Connect to an existing session at the specified hostname", "r", true, "<host>" },
{ 'u', "username", "Username to use when connecting to an existing session", "u", true, "<username>" },
{ 'v', "version", "Show version number and exit", "v", false, nullptr },
{ 'w', "password", "Password to use when connecting to an existing session", "w", true, "<password>" },
{ 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 (QString 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<char const* const*>(argv), Opts.data(), &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':
qInfo() << DisplayName << LONG_VERSION_STRING;
return 0;
case TR_OPT_ERR:
qWarning() << "Invalid option";
tr_getopt_usage(DisplayName, getUsage(), Opts.data());
return 1;
default:
if (optarg == FileArgsSeparator)
{
file_args_start_idx = tr_optind;
break;
}
if (optarg == QtArgsSeparator)
{
qt_args_start_idx = tr_optind;
break;
}
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<Prefs>(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<char*>{ 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<int>(qt_argv.size());
Application const app(std::move(prefs), minimized, config_dir, filenames, qt_argc, qt_argv.data());
return QApplication::exec();
}