mirror of
https://github.com/transmission/transmission
synced 2024-12-28 10:38:45 +00:00
be74cb6356
* Bump minimum Qt version to 5.6 * Switch from QRegExp to QRegularExpression While still available, QRegExp has been moved to Qt6::Core5Compat module and is not part of Qt6::Core. * Use qIsEffectiveTLD instead of QUrl::topLevelDomain The latter is not part of Qt6::Core. The former is a private utility in Qt6::Network; using it for now, until (and if) we switch to something non-Qt-specific. * Use QStyle::State_Horizontal state when drawing progress bars Although available for a long time, this state either didn't apply to progress bars before Qt 6, or was deduced based on bar size. With Qt 6, failing to specify it results in bad rendering. * Don't use QStringRef (and associated methods) While still available, QStringRef has been moved to Qt6::Core5Compat module and is not part of Qt6::Core. Related method (e.g. QString::midRef) have been removed in Qt 6. * Use Qt::ItemIsAutoTristate instead of Qt::ItemIsTristate The latter was deprecated and replaced with the former in Qt 5.6. * Don't use QApplication::globalStrut This property has been deprecated in Qt 5.15 and removed in Qt 6. * Use QImage::fromHICON instead of QtWin::fromHICON WinExtras module (providind the latter helper) has been removed in Qt 6. * Use QStringDecoder instead of QTextCodec While still available, QTextCodec has been moved to Qt6::Core5Compat module and is not part of Qt6::Core. * Don't forward-declare QStringList Instead of being a standalone class, its definition has changed to QList<QString> template specialization in Qt 6. * Use explicit (since Qt 6) QFileInfo constructor * Use QDateTime's {to,from}SecsSinceEpoch instead of {to,from}Time_t The latter was deprecated in Qt 5.8 and removed in Qt 6. * Don't use QFuture<>'s operator== It has been removed in Qt 6. Since the original issue this code was solving was caused by future reuse, just don't reuse futures and create new finished ones when necessary. * Use std::vector<> instead of QVector<> The latter has been changed to a typedef for QList<>, which might not be what one wants, and which also changed behavior a bit leading to compilation errors. * Don't use + for flags, cast to int explicitly Operator+ for enum values has been deleted in Qt 6, so using operator| instead. Then, there's no conversion from QFlags<> to QVariant, so need to cast to int. * Support Qt 6 in CMake and for MSI packaging * Remove extra (empty) CMake variable use when constructing Qt target names * Simplify logic in tr_qt_add_translation CMake helper Co-authored-by: Charles Kerr <charles@charleskerr.com>
675 lines
18 KiB
C++
675 lines
18 KiB
C++
/*
|
|
* This file Copyright (C) 2009-2015 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
*/
|
|
|
|
#include "Application.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <ctime>
|
|
|
|
#include <QIcon>
|
|
#include <QLibraryInfo>
|
|
#include <QMessageBox>
|
|
#include <QProcess>
|
|
#include <QRect>
|
|
#include <QSystemTrayIcon>
|
|
#include <QtDebug>
|
|
|
|
#ifdef QT_DBUS_LIB
|
|
#include <QDBusConnection>
|
|
#include <QDBusMessage>
|
|
#include <QDBusReply>
|
|
#endif
|
|
|
|
#include <libtransmission/tr-getopt.h>
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/utils.h>
|
|
#include <libtransmission/version.h>
|
|
|
|
#include "AddData.h"
|
|
#include "Formatter.h"
|
|
#include "InteropHelper.h"
|
|
#include "MainWindow.h"
|
|
#include "OptionsDialog.h"
|
|
#include "Prefs.h"
|
|
#include "Session.h"
|
|
#include "TorrentModel.h"
|
|
#include "WatchDir.h"
|
|
|
|
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]";
|
|
}
|
|
|
|
enum
|
|
{
|
|
STATS_REFRESH_INTERVAL_MSEC = 3000,
|
|
SESSION_REFRESH_INTERVAL_MSEC = 3000,
|
|
MODEL_REFRESH_INTERVAL_MSEC = 3000
|
|
};
|
|
|
|
bool loadTranslation(QTranslator& translator, QString const& name, QLocale const& locale, QStringList const& search_directories)
|
|
{
|
|
for (QString const& directory : search_directories)
|
|
{
|
|
if (translator.load(locale, name, QStringLiteral("_"), directory))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Application::Application(int& argc, char** argv)
|
|
: QApplication(argc, argv)
|
|
, config_name_{ QStringLiteral("transmission") }
|
|
, display_name_{ QStringLiteral("transmission-qt") }
|
|
, start_now_regex_{ QRegularExpression(QStringLiteral(R"rgx(start-now\((\d+)\))rgx")) }
|
|
{
|
|
setApplicationName(config_name_);
|
|
loadTranslations();
|
|
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
|
|
if (QIcon::themeName().isEmpty())
|
|
{
|
|
QIcon::setThemeName(QStringLiteral("Faenza"));
|
|
}
|
|
|
|
#endif
|
|
|
|
// set the default icon
|
|
QIcon icon = QIcon::fromTheme(QStringLiteral("transmission"));
|
|
|
|
if (icon.isNull())
|
|
{
|
|
static std::array<int, 11> constexpr Sizes = { 16, 22, 24, 32, 48, 64, 72, 96, 128, 192, 256 };
|
|
for (auto const size : Sizes)
|
|
{
|
|
icon.addPixmap(QPixmap(QStringLiteral(":/icons/transmission-%1.png").arg(size)));
|
|
}
|
|
}
|
|
|
|
setWindowIcon(icon);
|
|
|
|
#ifdef __APPLE__
|
|
setAttribute(Qt::AA_DontShowIconsInMenus);
|
|
#endif
|
|
|
|
// parse the command-line arguments
|
|
int c;
|
|
bool minimized = false;
|
|
char const* optarg;
|
|
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 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::fromUtf8(tr_getDefaultConfigDir("transmission"));
|
|
}
|
|
|
|
// ensure our config directory exists
|
|
QDir dir(config_dir);
|
|
|
|
if (!dir.exists())
|
|
{
|
|
dir.mkpath(config_dir);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
session_ = std::make_unique<Session>(config_dir, *prefs_);
|
|
model_ = std::make_unique<TorrentModel>(*prefs_);
|
|
window_ = std::make_unique<MainWindow>(*session_, *prefs_, *model_, minimized);
|
|
watch_dir_ = std::make_unique<WatchDir>(*model_);
|
|
|
|
connect(this, &QCoreApplication::aboutToQuit, this, &Application::saveGeometry);
|
|
connect(model_.get(), &TorrentModel::torrentsAdded, this, &Application::onTorrentsAdded);
|
|
connect(model_.get(), &TorrentModel::torrentsCompleted, this, &Application::onTorrentsCompleted);
|
|
connect(model_.get(), &TorrentModel::torrentsEdited, this, &Application::onTorrentsEdited);
|
|
connect(model_.get(), &TorrentModel::torrentsNeedInfo, this, &Application::onTorrentsNeedInfo);
|
|
connect(prefs_.get(), &Prefs::changed, this, &Application::refreshPref);
|
|
connect(session_.get(), &Session::sourceChanged, this, &Application::onSessionSourceChanged);
|
|
connect(session_.get(), &Session::torrentsRemoved, model_.get(), &TorrentModel::removeTorrents);
|
|
connect(session_.get(), &Session::torrentsUpdated, model_.get(), &TorrentModel::updateTorrents);
|
|
connect(watch_dir_.get(), &WatchDir::torrentFileAdded, this, qOverload<QString const&>(&Application::addTorrent));
|
|
|
|
// init from preferences
|
|
for (auto const key : { Prefs::DIR_WATCH })
|
|
{
|
|
refreshPref(key);
|
|
}
|
|
|
|
QTimer* timer = &model_timer_;
|
|
connect(timer, &QTimer::timeout, this, &Application::refreshTorrents);
|
|
timer->setSingleShot(false);
|
|
timer->setInterval(MODEL_REFRESH_INTERVAL_MSEC);
|
|
timer->start();
|
|
|
|
timer = &stats_timer_;
|
|
connect(timer, &QTimer::timeout, session_.get(), &Session::refreshSessionStats);
|
|
timer->setSingleShot(false);
|
|
timer->setInterval(STATS_REFRESH_INTERVAL_MSEC);
|
|
timer->start();
|
|
|
|
timer = &session_timer_;
|
|
connect(timer, &QTimer::timeout, session_.get(), &Session::refreshSessionInfo);
|
|
timer->setSingleShot(false);
|
|
timer->setInterval(SESSION_REFRESH_INTERVAL_MSEC);
|
|
timer->start();
|
|
|
|
maybeUpdateBlocklist();
|
|
|
|
if (!first_time)
|
|
{
|
|
session_->restart();
|
|
}
|
|
else
|
|
{
|
|
window_->openSession();
|
|
}
|
|
|
|
if (!prefs_->getBool(Prefs::USER_HAS_GIVEN_INFORMED_CONSENT))
|
|
{
|
|
auto* dialog = new QMessageBox(
|
|
QMessageBox::Information,
|
|
QString(),
|
|
tr("<b>Transmission is a file sharing program.</b>"),
|
|
QMessageBox::Ok | QMessageBox::Cancel,
|
|
window_.get());
|
|
dialog->setInformativeText(
|
|
tr("When you run a torrent, its data will be made available to others by means of upload. "
|
|
"Any content you share is your sole responsibility."));
|
|
dialog->button(QMessageBox::Ok)->setText(tr("I &Agree"));
|
|
dialog->setDefaultButton(QMessageBox::Ok);
|
|
dialog->setModal(true);
|
|
|
|
connect(dialog, &QDialog::finished, this, &Application::consentGiven);
|
|
|
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
dialog->show();
|
|
}
|
|
|
|
for (QString const& filename : filenames)
|
|
{
|
|
addTorrent(filename);
|
|
}
|
|
|
|
InteropHelper::registerObject(this);
|
|
|
|
#ifdef QT_DBUS_LIB
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
if (bus.isConnected())
|
|
{
|
|
bus.connect(
|
|
fdo_notifications_service_name_,
|
|
fdo_notifications_path_,
|
|
fdo_notifications_interface_name_,
|
|
QLatin1String("ActionInvoked"),
|
|
this,
|
|
SLOT(onNotificationActionInvoked(quint32, QString)));
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
void Application::loadTranslations()
|
|
{
|
|
QStringList const qt_qm_dirs = QStringList() << QLibraryInfo::location(QLibraryInfo::TranslationsPath) <<
|
|
#ifdef TRANSLATIONS_DIR
|
|
QStringLiteral(TRANSLATIONS_DIR) <<
|
|
#endif
|
|
(applicationDirPath() + QStringLiteral("/translations"));
|
|
|
|
QStringList const app_qm_dirs = QStringList() <<
|
|
#ifdef TRANSLATIONS_DIR
|
|
QStringLiteral(TRANSLATIONS_DIR) <<
|
|
#endif
|
|
(applicationDirPath() + QStringLiteral("/translations"));
|
|
|
|
auto const qt_file_name = QStringLiteral("qtbase");
|
|
|
|
QLocale const locale;
|
|
QLocale const english_locale(QLocale::English, QLocale::UnitedStates);
|
|
|
|
if (loadTranslation(qt_translator_, qt_file_name, locale, qt_qm_dirs) ||
|
|
loadTranslation(qt_translator_, qt_file_name, english_locale, qt_qm_dirs))
|
|
{
|
|
installTranslator(&qt_translator_);
|
|
}
|
|
|
|
if (loadTranslation(app_translator_, config_name_, locale, app_qm_dirs) ||
|
|
loadTranslation(app_translator_, config_name_, english_locale, app_qm_dirs))
|
|
{
|
|
installTranslator(&app_translator_);
|
|
}
|
|
}
|
|
|
|
void Application::quitLater() const
|
|
{
|
|
QTimer::singleShot(0, this, SLOT(quit()));
|
|
}
|
|
|
|
void Application::onTorrentsEdited(torrent_ids_t const& ids) const
|
|
{
|
|
// the backend's tr_info has changed, so reload those fields
|
|
session_->initTorrents(ids);
|
|
}
|
|
|
|
QStringList Application::getNames(torrent_ids_t const& ids) const
|
|
{
|
|
QStringList names;
|
|
for (auto const& id : ids)
|
|
{
|
|
names.push_back(model_->getTorrentFromId(id)->name());
|
|
}
|
|
|
|
names.sort();
|
|
return names;
|
|
}
|
|
|
|
void Application::onTorrentsAdded(torrent_ids_t const& ids) const
|
|
{
|
|
if (prefs_->getBool(Prefs::SHOW_NOTIFICATION_ON_ADD))
|
|
{
|
|
for (auto id : ids)
|
|
{
|
|
notifyTorrentAdded(model_->getTorrentFromId(id));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Application::onTorrentsCompleted(torrent_ids_t const& ids) const
|
|
{
|
|
if (prefs_->getBool(Prefs::SHOW_NOTIFICATION_ON_COMPLETE))
|
|
{
|
|
auto const title = tr("Torrent Completed", nullptr, static_cast<int>(ids.size()));
|
|
auto const body = getNames(ids).join(QStringLiteral("\n"));
|
|
notifyApp(title, body);
|
|
}
|
|
|
|
if (prefs_->getBool(Prefs::COMPLETE_SOUND_ENABLED))
|
|
{
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
|
beep();
|
|
#else
|
|
auto args = prefs_->get<QStringList>(Prefs::COMPLETE_SOUND_COMMAND);
|
|
auto const command = args.takeFirst();
|
|
QProcess::execute(command, args);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void Application::onTorrentsNeedInfo(torrent_ids_t const& ids) const
|
|
{
|
|
if (!ids.empty())
|
|
{
|
|
session_->initTorrents(ids);
|
|
}
|
|
}
|
|
|
|
void Application::notifyTorrentAdded(Torrent const* tor) const
|
|
{
|
|
QStringList actions;
|
|
actions << QString(QLatin1String("start-now(%1)")).arg(tor->id()) << QObject::tr("Start Now");
|
|
notifyApp(tr("Torrent Added"), tor->name(), actions);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void Application::consentGiven(int result) const
|
|
{
|
|
if (result == QMessageBox::Ok)
|
|
{
|
|
prefs_->set<bool>(Prefs::USER_HAS_GIVEN_INFORMED_CONSENT, true);
|
|
}
|
|
else
|
|
{
|
|
quit();
|
|
}
|
|
}
|
|
|
|
void Application::saveGeometry() const
|
|
{
|
|
if (prefs_ != nullptr && window_ != nullptr)
|
|
{
|
|
auto const geometry = window_->geometry();
|
|
prefs_->set(Prefs::MAIN_WINDOW_HEIGHT, std::max(100, geometry.height()));
|
|
prefs_->set(Prefs::MAIN_WINDOW_WIDTH, std::max(100, geometry.width()));
|
|
prefs_->set(Prefs::MAIN_WINDOW_X, geometry.x());
|
|
prefs_->set(Prefs::MAIN_WINDOW_Y, geometry.y());
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void Application::refreshPref(int key) const
|
|
{
|
|
switch (key)
|
|
{
|
|
case Prefs::BLOCKLIST_UPDATES_ENABLED:
|
|
maybeUpdateBlocklist();
|
|
break;
|
|
|
|
case Prefs::DIR_WATCH:
|
|
case Prefs::DIR_WATCH_ENABLED:
|
|
watch_dir_->setPath(prefs_->getString(Prefs::DIR_WATCH), prefs_->getBool(Prefs::DIR_WATCH_ENABLED));
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Application::maybeUpdateBlocklist() const
|
|
{
|
|
if (!prefs_->getBool(Prefs::BLOCKLIST_UPDATES_ENABLED))
|
|
{
|
|
return;
|
|
}
|
|
|
|
QDateTime const last_updated_at = prefs_->getDateTime(Prefs::BLOCKLIST_DATE);
|
|
QDateTime const next_update_at = last_updated_at.addDays(7);
|
|
QDateTime const now = QDateTime::currentDateTime();
|
|
|
|
if (now < next_update_at)
|
|
{
|
|
session_->updateBlocklist();
|
|
prefs_->set(Prefs::BLOCKLIST_DATE, now);
|
|
}
|
|
}
|
|
|
|
void Application::onSessionSourceChanged() const
|
|
{
|
|
session_->initTorrents();
|
|
session_->refreshSessionStats();
|
|
session_->refreshSessionInfo();
|
|
}
|
|
|
|
void Application::refreshTorrents()
|
|
{
|
|
// usually we just poll the torrents that have shown recent activity,
|
|
// but we also periodically ask for updates on the others to ensure
|
|
// nothing's falling through the cracks.
|
|
time_t const now = time(nullptr);
|
|
|
|
if (last_full_update_time_ + 60 >= now)
|
|
{
|
|
session_->refreshActiveTorrents();
|
|
}
|
|
else
|
|
{
|
|
last_full_update_time_ = now;
|
|
session_->refreshAllTorrents();
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void Application::addTorrent(QString const& addme) const
|
|
{
|
|
addTorrent(AddData(addme));
|
|
}
|
|
|
|
void Application::addTorrent(AddData const& addme) const
|
|
{
|
|
if (addme.type == addme.NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!prefs_->getBool(Prefs::OPTIONS_PROMPT))
|
|
{
|
|
session_->addTorrent(addme);
|
|
}
|
|
else
|
|
{
|
|
auto* o = new OptionsDialog(*session_, *prefs_, addme, window_.get());
|
|
o->show();
|
|
}
|
|
|
|
raise();
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void Application::raise() const
|
|
{
|
|
alert(window_.get());
|
|
}
|
|
|
|
bool Application::notifyApp(QString const& title, QString const& body, QStringList const& actions) const
|
|
{
|
|
#ifdef QT_DBUS_LIB
|
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
|
|
|
if (bus.isConnected())
|
|
{
|
|
QDBusMessage m = QDBusMessage::createMethodCall(
|
|
fdo_notifications_service_name_,
|
|
fdo_notifications_path_,
|
|
fdo_notifications_interface_name_,
|
|
QStringLiteral("Notify"));
|
|
QVariantList args;
|
|
args.append(QStringLiteral("Transmission")); // app_name
|
|
args.append(0U); // replaces_id
|
|
args.append(QStringLiteral("transmission")); // icon
|
|
args.append(title); // summary
|
|
args.append(body); // body
|
|
args.append(actions);
|
|
args.append(QVariantMap({
|
|
std::make_pair(QStringLiteral("category"), QVariant(QStringLiteral("transfer.complete"))),
|
|
})); // hints
|
|
args.append(static_cast<int32_t>(-1)); // use the default timeout period
|
|
m.setArguments(args);
|
|
QDBusReply<quint32> const reply_msg = bus.call(m);
|
|
|
|
if (reply_msg.isValid() && reply_msg.value() > 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
window_->trayIcon().showMessage(title, body);
|
|
return true;
|
|
}
|
|
|
|
#ifdef QT_DBUS_LIB
|
|
void Application::onNotificationActionInvoked(quint32 /* notification_id */, QString action_key)
|
|
{
|
|
auto const match = start_now_regex_.match(action_key);
|
|
if (match.hasMatch())
|
|
{
|
|
int const torrent_id = match.captured(1).toInt();
|
|
session_->startTorrentsNow({ torrent_id });
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FaviconCache& Application::faviconCache()
|
|
{
|
|
return favicons_;
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
int tr_main(int argc, char** argv)
|
|
{
|
|
InteropHelper::initialize();
|
|
|
|
Application::setAttribute(Qt::AA_EnableHighDpiScaling);
|
|
Application::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
|
|
|
Application app(argc, argv);
|
|
return QApplication::exec();
|
|
}
|