diff --git a/src/app/application.cpp b/src/app/application.cpp index 26669d8e9..3f42b13e6 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -226,6 +226,7 @@ namespace Application::Application(int &argc, char **argv) : BaseApplication(argc, argv) , m_commandLineArgs(parseCommandLine(Application::arguments())) + , m_storeInstanceName(SETTINGS_KEY(u"InstanceName"_s)) , m_storeFileLoggerEnabled(FILELOGGER_SETTINGS_KEY(u"Enabled"_s)) , m_storeFileLoggerBackup(FILELOGGER_SETTINGS_KEY(u"Backup"_s)) , m_storeFileLoggerDeleteOld(FILELOGGER_SETTINGS_KEY(u"DeleteOld"_s)) @@ -360,6 +361,23 @@ const QBtCommandLineParameters &Application::commandLineArgs() const return m_commandLineArgs; } +QString Application::instanceName() const +{ + return m_storeInstanceName; +} + +void Application::setInstanceName(const QString &name) +{ + if (name == instanceName()) + return; + + m_storeInstanceName = name; +#ifndef DISABLE_GUI + if (MainWindow *mw = mainWindow()) + mw->setTitleSuffix(name); +#endif +} + int Application::memoryWorkingSetLimit() const { return m_storeMemoryWorkingSetLimit.get(512); @@ -880,7 +898,8 @@ int Application::exec() const WindowState windowState = (m_startupProgressDialog->windowState() & Qt::WindowMinimized) ? WindowState::Minimized : WindowState::Normal; #endif - m_window = new MainWindow(this, windowState); + m_window = new MainWindow(this, windowState, instanceName()); + delete m_startupProgressDialog; #endif // DISABLE_GUI diff --git a/src/app/application.h b/src/app/application.h index 678325949..3218f3d71 100644 --- a/src/app/application.h +++ b/src/app/application.h @@ -105,6 +105,9 @@ public: bool callMainInstance(); const QBtCommandLineParameters &commandLineArgs() const; + QString instanceName() const override; + void setInstanceName(const QString &name) override; + // FileLogger properties bool isFileLoggerEnabled() const override; void setFileLoggerEnabled(bool value) override; @@ -194,6 +197,7 @@ private: QList m_paramsQueue; + SettingValue m_storeInstanceName; SettingValue m_storeFileLoggerEnabled; SettingValue m_storeFileLoggerBackup; SettingValue m_storeFileLoggerDeleteOld; diff --git a/src/base/interfaces/iapplication.h b/src/base/interfaces/iapplication.h index 5f8eb18ea..211a9ea20 100644 --- a/src/base/interfaces/iapplication.h +++ b/src/base/interfaces/iapplication.h @@ -61,6 +61,9 @@ class IApplication public: virtual ~IApplication() = default; + virtual QString instanceName() const = 0; + virtual void setInstanceName(const QString &name) = 0; + // FileLogger properties virtual bool isFileLoggerEnabled() const = 0; virtual void setFileLoggerEnabled(bool value) = 0; diff --git a/src/gui/advancedsettings.cpp b/src/gui/advancedsettings.cpp index 187194d02..dac681628 100644 --- a/src/gui/advancedsettings.cpp +++ b/src/gui/advancedsettings.cpp @@ -79,6 +79,7 @@ namespace CONFIRM_RECHECK_TORRENT, RECHECK_COMPLETED, // UI related + APP_INSTANCE_NAME, LIST_REFRESH, RESOLVE_HOSTS, RESOLVE_COUNTRIES, @@ -280,6 +281,8 @@ void AdvancedSettings::saveAdvancedSettings() const session->setBlockPeersOnPrivilegedPorts(m_checkBoxBlockPeersOnPrivilegedPorts.isChecked()); // Recheck torrents on completion pref->recheckTorrentsOnCompletion(m_checkBoxRecheckCompleted.isChecked()); + // Customize application instance name + app()->setInstanceName(m_lineEditAppInstanceName.text()); // Transfer list refresh interval session->setRefreshInterval(m_spinBoxListRefresh.value()); // Peer resolution @@ -723,6 +726,10 @@ void AdvancedSettings::loadAdvancedSettings() // Recheck completed torrents m_checkBoxRecheckCompleted.setChecked(pref->recheckTorrentsOnCompletion()); addRow(RECHECK_COMPLETED, tr("Recheck torrents on completion"), &m_checkBoxRecheckCompleted); + // Customize application instance name + m_lineEditAppInstanceName.setText(app()->instanceName()); + m_lineEditAppInstanceName.setToolTip(tr("It appends the text to the window title to help distinguish qBittorent instances")); + addRow(APP_INSTANCE_NAME, tr("Customize application instance name"), &m_lineEditAppInstanceName); // Refresh interval m_spinBoxListRefresh.setMinimum(30); m_spinBoxListRefresh.setMaximum(99999); diff --git a/src/gui/advancedsettings.h b/src/gui/advancedsettings.h index 8dbc39fd7..91473c8e2 100644 --- a/src/gui/advancedsettings.h +++ b/src/gui/advancedsettings.h @@ -82,7 +82,7 @@ private: m_checkBoxSuggestMode, m_checkBoxSpeedWidgetEnabled, m_checkBoxIDNSupport, m_checkBoxConfirmRemoveTrackerFromAllTorrents; QComboBox m_comboBoxInterface, m_comboBoxInterfaceAddress, m_comboBoxDiskIOReadMode, m_comboBoxDiskIOWriteMode, m_comboBoxUtpMixedMode, m_comboBoxChokingAlgorithm, m_comboBoxSeedChokingAlgorithm, m_comboBoxResumeDataStorage; - QLineEdit m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes; + QLineEdit m_lineEditAppInstanceName, m_pythonExecutablePath, m_lineEditAnnounceIP, m_lineEditDHTBootstrapNodes; #ifndef QBT_USES_LIBTORRENT2 QSpinBox m_spinBoxCache, m_spinBoxCacheTTL; diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index edd8004b9..e1d6cf28c 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -122,7 +122,7 @@ namespace } } -MainWindow::MainWindow(IGUIApplication *app, WindowState initialState) +MainWindow::MainWindow(IGUIApplication *app, const WindowState initialState, const QString &titleSuffix) : GUIApplicationComponent(app) , m_ui(new Ui::MainWindow) , m_storeExecutionLogEnabled(EXECUTIONLOG_SETTINGS_KEY(u"Enabled"_s)) @@ -134,9 +134,10 @@ MainWindow::MainWindow(IGUIApplication *app, WindowState initialState) { m_ui->setupUi(this); + setTitleSuffix(titleSuffix); + Preferences *const pref = Preferences::instance(); m_uiLocked = pref->isUILocked(); - setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION)); m_displaySpeedInTitle = pref->speedInTitleBar(); // Setting icons #ifndef Q_OS_MACOS @@ -524,6 +525,16 @@ void MainWindow::setDownloadTrackerFavicon(const bool value) m_storeDownloadTrackerFavicon = value; } +void MainWindow::setTitleSuffix(const QString &suffix) +{ + const auto emDash = QChar(0x2014); + const QString separator = u' ' + emDash + u' '; + m_windowTitle = QStringLiteral("qBittorrent " QBT_VERSION) + + (!suffix.isEmpty() ? (separator + suffix) : QString()); + + setWindowTitle(m_windowTitle); +} + void MainWindow::addToolbarContextMenu() { const Preferences *const pref = Preferences::instance(); @@ -1485,23 +1496,24 @@ void MainWindow::loadPreferences() void MainWindow::reloadSessionStats() { const BitTorrent::SessionStatus &status = BitTorrent::Session::instance()->status(); + const QString downloadRate = Utils::Misc::friendlyUnit(status.payloadDownloadRate, true); + const QString uploadRate = Utils::Misc::friendlyUnit(status.payloadUploadRate, true); // update global information #ifdef Q_OS_MACOS m_badger->updateSpeed(status.payloadDownloadRate, status.payloadUploadRate); #else const auto toolTip = u"%1\n%2"_s.arg( - tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true)) - , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(Utils::Misc::friendlyUnit(status.payloadUploadRate, true))); + tr("DL speed: %1", "e.g: Download speed: 10 KiB/s").arg(downloadRate) + , tr("UP speed: %1", "e.g: Upload speed: 10 KiB/s").arg(uploadRate)); app()->desktopIntegration()->setToolTip(toolTip); // tray icon #endif // Q_OS_MACOS if (m_displaySpeedInTitle) { - setWindowTitle(tr("[D: %1, U: %2] qBittorrent %3", "D = Download; U = Upload; %3 is qBittorrent version") - .arg(Utils::Misc::friendlyUnit(status.payloadDownloadRate, true) - , Utils::Misc::friendlyUnit(status.payloadUploadRate, true) - , QStringLiteral(QBT_VERSION))); + const QString title = tr("[D: %1, U: %2] %3", "D = Download; U = Upload; %3 is the rest of the window title") + .arg(downloadRate, uploadRate, m_windowTitle); + setWindowTitle(title); } } @@ -1621,7 +1633,7 @@ void MainWindow::on_actionSpeedInTitleBar_triggered() if (m_displaySpeedInTitle) reloadSessionStats(); else - setWindowTitle(QStringLiteral("qBittorrent " QBT_VERSION)); + setWindowTitle(m_windowTitle); } void MainWindow::on_actionRSSReader_triggered() diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index 4fc08a253..d122cdbb3 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -84,7 +84,7 @@ class MainWindow final : public GUIApplicationComponent Q_DISABLE_COPY_MOVE(MainWindow) public: - explicit MainWindow(IGUIApplication *app, WindowState initialState = WindowState::Normal); + explicit MainWindow(IGUIApplication *app, WindowState initialState = WindowState::Normal, const QString &titleSuffix = {}); ~MainWindow() override; QWidget *currentTabWidget() const; @@ -97,12 +97,12 @@ public: Log::MsgTypes executionLogMsgTypes() const; void setExecutionLogMsgTypes(Log::MsgTypes value); - // Notifications properties - // Misc properties bool isDownloadTrackerFavicon() const; void setDownloadTrackerFavicon(bool value); + void setTitleSuffix(const QString &suffix); + void activate(); void cleanup(); @@ -207,6 +207,7 @@ private: QFileSystemWatcher *m_executableWatcher = nullptr; // GUI related + QString m_windowTitle; bool m_posInitialized = false; bool m_neverShown = true; QPointer m_tabs; diff --git a/src/webui/api/appcontroller.cpp b/src/webui/api/appcontroller.cpp index 8ea29d568..073ada234 100644 --- a/src/webui/api/appcontroller.cpp +++ b/src/webui/api/appcontroller.cpp @@ -361,6 +361,8 @@ void AppController::preferencesAction() data[u"torrent_file_size_limit"_s] = pref->getTorrentFileSizeLimit(); // Recheck completed torrents data[u"recheck_completed_torrents"_s] = pref->recheckTorrentsOnCompletion(); + // Customize application instance name + data[u"app_instance_name"_s] = app()->instanceName(); // Refresh interval data[u"refresh_interval"_s] = session->refreshInterval(); // Resolve peer countries @@ -943,6 +945,9 @@ void AppController::setPreferencesAction() // Recheck completed torrents if (hasKey(u"recheck_completed_torrents"_s)) pref->recheckTorrentsOnCompletion(it.value().toBool()); + // Customize application instance name + if (hasKey(u"app_instance_name"_s)) + app()->setInstanceName(it.value().toString()); // Refresh interval if (hasKey(u"refresh_interval"_s)) session->setRefreshInterval(it.value().toInt()); diff --git a/src/webui/webapplication.h b/src/webui/webapplication.h index a6f2ede6a..aeee95863 100644 --- a/src/webui/webapplication.h +++ b/src/webui/webapplication.h @@ -53,7 +53,7 @@ #include "base/utils/version.h" #include "api/isessionmanager.h" -inline const Utils::Version<3, 2> API_VERSION {2, 10, 3}; +inline const Utils::Version<3, 2> API_VERSION {2, 10, 4}; class QTimer; diff --git a/src/webui/www/private/css/style.css b/src/webui/www/private/css/style.css index f05ae3e78..4387c43a2 100644 --- a/src/webui/www/private/css/style.css +++ b/src/webui/www/private/css/style.css @@ -241,7 +241,7 @@ a.propButton img { .contextMenu li a { color: var(--color-text-default); display: block; - font-family: tahoma, arial, sans-serif; + font-family: Tahoma, Arial, sans-serif; font-size: 12px; padding: 5px 20px 5px 5px; text-decoration: none; diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index 65bd2388a..d6f108678 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -36,7 +36,8 @@ window.qBittorrent.Client = (() => { genHash: genHash, getSyncMainDataInterval: getSyncMainDataInterval, isStopped: isStopped, - stop: stop + stop: stop, + mainTitle: mainTitle }; }; @@ -67,6 +68,15 @@ window.qBittorrent.Client = (() => { stopped = true; }; + const mainTitle = () => { + const emDash = '\u2014'; + const qbtVersion = window.qBittorrent.Cache.qbtVersion.get(); + const suffix = window.qBittorrent.Cache.preferences.get()['app_instance_name'] || ''; + const title = `qBittorrent ${qbtVersion} QBT_TR(WebUI)QBT_TR[CONTEXT=OptionsDialog]` + + ((suffix.length > 0) ? ` ${emDash} ${suffix}` : ''); + return title; + }; + return exports(); })(); Object.freeze(window.qBittorrent.Client); @@ -275,7 +285,7 @@ window.addEventListener("DOMContentLoaded", function() { initializeWindows(); // Show Top Toolbar is enabled by default - let showTopToolbar = LocalPreferences.get('show_top_toolbar', 'true') == "true"; + let showTopToolbar = LocalPreferences.get('show_top_toolbar', 'true') === "true"; if (!showTopToolbar) { $('showTopToolbarLink').firstChild.style.opacity = '0'; $('mochaToolbar').addClass('invisible'); @@ -296,7 +306,7 @@ window.addEventListener("DOMContentLoaded", function() { $('filtersColumn_handle').addClass('invisible'); } - let speedInTitle = LocalPreferences.get('speed_in_browser_title_bar') == "true"; + let speedInTitle = LocalPreferences.get('speed_in_browser_title_bar') === "true"; if (!speedInTitle) $('speedInBrowserTitleBarLink').firstChild.style.opacity = '0'; @@ -873,14 +883,13 @@ window.addEventListener("DOMContentLoaded", function() { transfer_info += " (" + window.qBittorrent.Misc.friendlyUnit(serverState.up_info_data, false) + ")"; $("UpInfos").set('html', transfer_info); - const qbtVersion = window.qBittorrent.Cache.qbtVersion.get(); + document.title = (speedInTitle + ? (`QBT_TR([D: %1, U: %2])QBT_TR[CONTEXT=MainWindow] ` + .replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_speed, true)) + .replace("%2", window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true))) + : '') + + window.qBittorrent.Client.mainTitle(); - if (speedInTitle) { - document.title = "QBT_TR([D: %1, U: %2] qBittorrent %3)QBT_TR[CONTEXT=MainWindow]".replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.dl_info_speed, true)).replace("%2", window.qBittorrent.Misc.friendlyUnit(serverState.up_info_speed, true)).replace("%3", qbtVersion); - document.title += " QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]"; - } - else - document.title = ("qBittorrent " + qbtVersion + " QBT_TR(Web UI)QBT_TR[CONTEXT=OptionsDialog]"); $('freeSpaceOnDisk').set('html', 'QBT_TR(Free space: %1)QBT_TR[CONTEXT=HttpServer]'.replace("%1", window.qBittorrent.Misc.friendlyUnit(serverState.free_space_on_disk))); $('DHTNodes').set('html', 'QBT_TR(DHT: %1 nodes)QBT_TR[CONTEXT=StatusBar]'.replace("%1", serverState.dht_nodes)); diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index 613f0fe15..d46ff1664 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -1157,7 +1157,8 @@ const initializeWindows = function() { url: 'api/v2/app/shutdown', method: 'post', onSuccess: function() { - document.write(' QBT_TR(qBittorrent has been shutdown)QBT_TR[CONTEXT=HttpServer]

QBT_TR(qBittorrent has been shutdown)QBT_TR[CONTEXT=HttpServer]

'); + const shutdownMessage = 'QBT_TR(%1 has been shutdown)QBT_TR[CONTEXT=HttpServer]'.replace("%1", window.qBittorrent.Client.mainTitle()); + document.write(` ${shutdownMessage}

${shutdownMessage}

`); document.close(); window.stop(); window.qBittorrent.Client.stop(); diff --git a/src/webui/www/private/views/preferences.html b/src/webui/www/private/views/preferences.html index ab62b96ec..624560149 100644 --- a/src/webui/www/private/views/preferences.html +++ b/src/webui/www/private/views/preferences.html @@ -1043,6 +1043,14 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD + + + + + + + + @@ -2301,6 +2309,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD $('saveResumeDataInterval').setProperty('value', pref.save_resume_data_interval); $('torrentFileSizeLimit').setProperty('value', (pref.torrent_file_size_limit / 1024 / 1024)); $('recheckTorrentsOnCompletion').setProperty('checked', pref.recheck_completed_torrents); + $('appInstanceName').setProperty('value', pref.app_instance_name); $('refreshInterval').setProperty('value', pref.refresh_interval); $('resolvePeerCountries').setProperty('checked', pref.resolve_peer_countries); $('reannounceWhenAddressChanged').setProperty('checked', pref.reannounce_when_address_changed); @@ -2746,6 +2755,7 @@ Use ';' to split multiple entries. Can use wildcard '*'.)QBT_TR[CONTEXT=OptionsD settings['save_resume_data_interval'] = Number($('saveResumeDataInterval').getProperty('value')); settings['torrent_file_size_limit'] = ($('torrentFileSizeLimit').getProperty('value') * 1024 * 1024); settings['recheck_completed_torrents'] = $('recheckTorrentsOnCompletion').getProperty('checked'); + settings['app_instance_name'] = $('appInstanceName').getProperty('value'); settings['refresh_interval'] = Number($('refreshInterval').getProperty('value')); settings['resolve_peer_countries'] = $('resolvePeerCountries').getProperty('checked'); settings['reannounce_when_address_changed'] = $('reannounceWhenAddressChanged').getProperty('checked');