mirror of
https://github.com/transmission/transmission
synced 2025-01-03 05:25:52 +00:00
8e7fc76930
Expose label text as accessible value instead of accessible name, and get accessible name from buddy label as any proper input widget does. Don't expose label tooltip as accessible description unless it's different from its text (which isn't the case when displayed text is truncated). Notify on label text and selection changes. Switch to `SqueezeLabel` for values in statistics dialog which has similar layout to information tab of torrent properties dialog.
725 lines
20 KiB
C++
725 lines
20 KiB
C++
// 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 "Application.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <ctime>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#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
|
|
|
|
#if QT_CONFIG(accessibility)
|
|
#include <QAccessible>
|
|
#endif
|
|
|
|
#include <libtransmission/transmission.h>
|
|
|
|
#include <libtransmission/tr-getopt.h>
|
|
#include <libtransmission/utils.h>
|
|
#include <libtransmission/values.h>
|
|
#include <libtransmission/version.h>
|
|
|
|
#include "AccessibleSqueezeLabel.h"
|
|
#include "AddData.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]";
|
|
}
|
|
|
|
auto constexpr StatsRefreshIntervalMsec = int{ 3000 };
|
|
auto constexpr SessionRefreshIntervalMsec = int{ 3000 };
|
|
auto constexpr ModelRefreshIntervalMsec = int{ 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;
|
|
}
|
|
|
|
[[nodiscard]] auto makeWindowIcon()
|
|
{
|
|
// first, try to load it from the system theme
|
|
if (auto icon = QIcon::fromTheme(QStringLiteral("transmission")); !icon.isNull())
|
|
{
|
|
return icon;
|
|
}
|
|
|
|
// if that fails, use our own as the fallback
|
|
return QIcon{ QStringLiteral(":/icons/transmission.svg") };
|
|
}
|
|
|
|
#if QT_CONFIG(accessibility)
|
|
|
|
QAccessibleInterface* accessibleFactory(QString const& className, QObject* object)
|
|
{
|
|
auto* widget = qobject_cast<QWidget*>(object);
|
|
|
|
if (widget != nullptr)
|
|
{
|
|
if (className == QStringLiteral("SqueezeLabel"))
|
|
{
|
|
return new AccessibleSqueezeLabel(widget);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // QT_CONFIG(accessibility)
|
|
|
|
} // namespace
|
|
|
|
Application::Application(int& argc, char** argv)
|
|
: QApplication{ argc, argv }
|
|
, config_name_{ QStringLiteral("transmission") }
|
|
, display_name_{ QStringLiteral("transmission-qt") }
|
|
{
|
|
setApplicationName(config_name_);
|
|
loadTranslations();
|
|
initUnits();
|
|
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
|
|
if (QIcon::themeName().isEmpty())
|
|
{
|
|
QIcon::setThemeName(QStringLiteral("Faenza"));
|
|
}
|
|
|
|
#endif
|
|
|
|
setWindowIcon(makeWindowIcon());
|
|
|
|
#ifdef __APPLE__
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
#if QT_CONFIG(accessibility)
|
|
QAccessible::installFactory(&accessibleFactory);
|
|
#endif
|
|
|
|
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::addWatchdirTorrent));
|
|
|
|
// 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(ModelRefreshIntervalMsec);
|
|
timer->start();
|
|
|
|
timer = &stats_timer_;
|
|
connect(timer, &QTimer::timeout, session_.get(), &Session::refreshSessionStats);
|
|
timer->setSingleShot(false);
|
|
timer->setInterval(StatsRefreshIntervalMsec);
|
|
timer->start();
|
|
|
|
timer = &session_timer_;
|
|
connect(timer, &QTimer::timeout, session_.get(), &Session::refreshSessionInfo);
|
|
timer->setSingleShot(false);
|
|
timer->setInterval(SessionRefreshIntervalMsec);
|
|
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();
|
|
}
|
|
|
|
// torrent files passed in on the command line
|
|
for (QString const& filename : filenames)
|
|
{
|
|
addTorrent(AddData{ filename });
|
|
}
|
|
|
|
InteropHelper::registerObject(this);
|
|
|
|
#ifdef QT_DBUS_LIB
|
|
if (auto bus = QDBusConnection::sessionBus(); 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()
|
|
{
|
|
auto const qt_qm_dirs = QStringList{} <<
|
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
|
QLibraryInfo::path(QLibraryInfo::TranslationsPath) <<
|
|
#else
|
|
QLibraryInfo::location(QLibraryInfo::TranslationsPath) <<
|
|
#endif
|
|
#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::initUnits()
|
|
{
|
|
using Config = libtransmission::Values::Config;
|
|
|
|
Config::Speed = { Config::Base::Kilo, tr("B/s").toStdString(), tr("kB/s").toStdString(),
|
|
tr("MB/s").toStdString(), tr("GB/s").toStdString(), tr("TB/s").toStdString() };
|
|
|
|
Config::Memory = { Config::Base::Kibi, tr("B").toStdString(), tr("KiB").toStdString(),
|
|
tr("MiB").toStdString(), tr("GiB").toStdString(), tr("TiB").toStdString() };
|
|
|
|
Config::Storage = { Config::Base::Kilo, tr("B").toStdString(), tr("kB").toStdString(),
|
|
tr("MB").toStdString(), tr("GB").toStdString(), tr("TB").toStdString() };
|
|
}
|
|
|
|
void Application::quitLater() const
|
|
{
|
|
QTimer::singleShot(0, this, SLOT(quit()));
|
|
}
|
|
|
|
void Application::onTorrentsEdited(torrent_ids_t const& torrent_ids) const
|
|
{
|
|
// the backend's tr_info has changed, so reload those fields
|
|
session_->initTorrents(torrent_ids);
|
|
}
|
|
|
|
QStringList Application::getNames(torrent_ids_t const& torrent_ids) const
|
|
{
|
|
QStringList names;
|
|
for (auto const& id : torrent_ids)
|
|
{
|
|
names.push_back(model_->getTorrentFromId(id)->name());
|
|
}
|
|
|
|
names.sort();
|
|
return names;
|
|
}
|
|
|
|
void Application::onTorrentsAdded(torrent_ids_t const& torrent_ids) const
|
|
{
|
|
if (!prefs_->getBool(Prefs::SHOW_NOTIFICATION_ON_ADD))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (auto id : torrent_ids)
|
|
{
|
|
notifyTorrentAdded(model_->getTorrentFromId(id));
|
|
}
|
|
}
|
|
|
|
void Application::onTorrentsCompleted(torrent_ids_t const& torrent_ids) const
|
|
{
|
|
if (prefs_->getBool(Prefs::SHOW_NOTIFICATION_ON_COMPLETE))
|
|
{
|
|
auto const title = tr("Torrent(s) Completed", nullptr, static_cast<int>(std::size(torrent_ids)));
|
|
auto const body = getNames(torrent_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& torrent_ids) const
|
|
{
|
|
if (!torrent_ids.empty())
|
|
{
|
|
session_->initTorrents(torrent_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::addWatchdirTorrent(QString const& filename) const
|
|
{
|
|
auto add_data = AddData{ filename };
|
|
auto const disposal = prefs_->getBool(Prefs::TRASH_ORIGINAL) ? AddData::FilenameDisposal::Delete :
|
|
AddData::FilenameDisposal::Rename;
|
|
add_data.setFileDisposal(disposal);
|
|
addTorrent(std::move(add_data));
|
|
}
|
|
|
|
void Application::addTorrent(AddData addme) const
|
|
{
|
|
if (addme.type == addme.NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if there's not already a disposal action set,
|
|
// then honor the `trash original` preference setting
|
|
if (!addme.fileDisposal() && prefs_->getBool(Prefs::TRASH_ORIGINAL))
|
|
{
|
|
addme.setFileDisposal(AddData::FilenameDisposal::Delete);
|
|
}
|
|
|
|
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
|
|
if (auto bus = QDBusConnection::sessionBus(); 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
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
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();
|
|
}
|