diff --git a/CMakeLists.txt b/CMakeLists.txt index 38f4ec9..34d8df7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ set(SRC_FILES set(WIDGETS_HEADER_FILES application/settings.h + application/singleinstance.h gui/trayicon.h gui/traywidget.h gui/traymenu.h @@ -54,6 +55,7 @@ set(WIDGETS_HEADER_FILES set(WIDGETS_SRC_FILES application/main.cpp application/settings.cpp + application/singleinstance.cpp gui/trayicon.cpp gui/traywidget.cpp gui/traymenu.cpp diff --git a/README.md b/README.md index 78f5137..7e5ec69 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ support * Openbox/qt5ct/Tint2 * Cinnamon * Windows 10 - * Can be shown as regular window if tray icon support is not available +* Can be shown as regular window if tray icon support is not available ## Features * Provides quick access to most frequently used features but does not intend to replace the official web UI @@ -54,6 +54,12 @@ The tray is still under development; the following features are planned: ### Web view (dark color theme) ![Web view](/resources/screenshots/webview.png?raw=true) +## Hotkey for Web UI +To create a hotkey for the web UI, you can use the same approach as for any other +application. Just add `--webui` to the arguments to trigger the web UI. +Syncthing Tray ensures that no second instance will be spawned if it is already +running. + ## Download / binary repository I currently provide packages for Arch Linux and Windows. Sources for those packages can be found in a separate [repository](https://github.com/Martchus/PKGBUILDs). For binaries checkout my diff --git a/application/main.cpp b/application/main.cpp index 9260e2f..c169478 100644 --- a/application/main.cpp +++ b/application/main.cpp @@ -1,6 +1,9 @@ #include "./settings.h" +#include "./singleinstance.h" + #include "../gui/trayicon.h" #include "../gui/traywidget.h" + #include "../data/syncthingprocess.h" #include "resources/config.h" @@ -24,69 +27,101 @@ using namespace ApplicationUtilities; using namespace QtGui; using namespace Data; -int main(int argc, char *argv[]) +int initSyncthingTray(bool windowed, bool waitForTray) { - SET_APPLICATION_INFO; + if(windowed) { + if(Settings::launchSynchting()) { + syncthingProcess().startSyncthing(); + } + auto *trayWidget = new TrayWidget; + trayWidget->setAttribute(Qt::WA_DeleteOnClose); + trayWidget->show(); + } else { +#ifndef QT_NO_SYSTEMTRAYICON + if(QSystemTrayIcon::isSystemTrayAvailable() || waitForTray) { + if(Settings::launchSynchting()) { + syncthingProcess().startSyncthing(); + } + auto *trayIcon = new TrayIcon; + trayIcon->show(); + if(Settings::firstLaunch()) { + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Information); + msgBox.setText(QCoreApplication::translate("main", "You must configure how to connect to Syncthing when using Syncthing Tray the first time.")); + msgBox.setInformativeText(QCoreApplication::translate("main", "Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration.")); + msgBox.exec(); + trayIcon->trayMenu().widget()->showSettingsDialog(); + } + } else { + QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though.")); + return -1; + } +#else + QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though.")); + return -2; +#endif + } + return 0; +} + +int runApplication(int argc, const char *const *argv) +{ + static bool firstRun = true; + // setup argument parser + SET_APPLICATION_INFO; ArgumentParser parser; HelpArgument helpArg(parser); // Qt configuration arguments QT_CONFIG_ARGUMENTS qtConfigArgs; Argument windowedArg("windowed", 'w', "opens the tray menu as a regular window"); windowedArg.setCombinable(true); + Argument showWebUi("webui", '\0', "instantly shows the web UI - meant for creating shortcut to web UI"); + showWebUi.setCombinable(true); Argument waitForTrayArg("wait", '\0', "wait until the system tray becomes available instead of showing an error message if the system tray is not available on start-up"); waitForTrayArg.setCombinable(true); qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&windowedArg); + qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&showWebUi); qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&waitForTrayArg); parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &helpArg}); try { parser.parseArgs(argc, argv); if(qtConfigArgs.qtWidgetsGuiArg().isPresent()) { - SET_QT_APPLICATION_INFO; - QApplication application(argc, argv); - Settings::restore(); - Settings::qtSettings().apply(); - qtConfigArgs.applySettings(true); - LOAD_QT_TRANSLATIONS; - QtUtilitiesResources::init(); - int res; - if(windowedArg.isPresent()) { - if(Settings::launchSynchting()) { - syncthingProcess().startSyncthing(); - } - TrayWidget trayWidget; - trayWidget.show(); - res = application.exec(); - } else { -#ifndef QT_NO_SYSTEMTRAYICON - if(QSystemTrayIcon::isSystemTrayAvailable() || waitForTrayArg.isPresent()) { - if(Settings::launchSynchting()) { - syncthingProcess().startSyncthing(); - } - application.setQuitOnLastWindowClosed(false); - TrayIcon trayIcon; - trayIcon.show(); - if(Settings::firstLaunch()) { - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Information); - msgBox.setText(QCoreApplication::translate("main", "You must configure how to connect to Syncthing when using Syncthing Tray the first time.")); - msgBox.setInformativeText(QCoreApplication::translate("main", "Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration.")); - msgBox.exec(); - trayIcon.trayMenu().widget()->showSettingsDialog(); + if(firstRun) { + firstRun = false; + + SET_QT_APPLICATION_INFO; + QApplication application(argc, const_cast(argv)); + QGuiApplication::setQuitOnLastWindowClosed(false); + SingleInstance singleInstance(argc, argv); + QObject::connect(&singleInstance, &SingleInstance::newInstance, &runApplication); + + Settings::restore(); + Settings::qtSettings().apply(); + qtConfigArgs.applySettings(true); + + LOAD_QT_TRANSLATIONS; + QtUtilitiesResources::init(); + + int res = initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent()); + if(!res) { + if(!TrayWidget::instances().empty() && showWebUi.isPresent()) { + TrayWidget::instances().front()->showWebUi(); } res = application.exec(); - } else { - QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though.")); - res = -1; } -#else - QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though.")); - res = -2; -#endif + + Settings::save(); + QtUtilitiesResources::cleanup(); + return res; + } else { + if(!TrayWidget::instances().empty() && showWebUi.isPresent()) { + // if --webui is present don't create a new tray icon, just show the web UI one of the present ones + TrayWidget::instances().front()->showWebUi(); + } else { + return initSyncthingTray(windowedArg.isPresent(), waitForTrayArg.isPresent()); + } } - Settings::save(); - QtUtilitiesResources::cleanup(); - return res; } } catch(const Failure &ex) { CMD_UTILS_START_CONSOLE; @@ -96,3 +131,8 @@ int main(int argc, char *argv[]) return 0; } +int main(int argc, char *argv[]) +{ + return runApplication(argc, argv); +} + diff --git a/application/singleinstance.cpp b/application/singleinstance.cpp new file mode 100644 index 0000000..4375853 --- /dev/null +++ b/application/singleinstance.cpp @@ -0,0 +1,97 @@ +#include "./singleinstance.h" + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std; +using namespace ConversionUtilities; + +namespace QtGui { + +SingleInstance::SingleInstance(int argc, const char *const *argv, QObject *parent) : + QObject(parent), + m_server(nullptr) +{ + const QString appId(QCoreApplication::applicationName() % QStringLiteral(" by ") % QCoreApplication::organizationName()); + + // check for previous instance + QLocalSocket socket; + socket.connectToServer(appId, QLocalSocket::ReadWrite); + if(socket.waitForConnected(1000)) { + cerr << "Info: Application already running, sending args to previous instance" << endl; + if(argc >= 0 && argc <= 0xFFFF) { + char buffer[2]; + BE::getBytes(static_cast(argc), buffer); + socket.write(buffer, 2); + *buffer = '\0'; + for(const char *const *end = argv + argc; argv != end; ++argv) { + socket.write(*argv); + socket.write(buffer, 1); + } + } else { + cerr << "Error: Unable to pass the specified number of arguments" << endl; + } + socket.flush(); + socket.close(); + exit(0); + } + + // no previous instance running + // -> however, previous server instance might not have been cleaned up dute to crash + QLocalServer::removeServer(appId); + // -> start server + m_server = new QLocalServer(this); + connect(m_server, &QLocalServer::newConnection, this, &SingleInstance::handleNewConnection); + if(!m_server->listen(appId)) { + cerr << "Error: Unable to launch as single instance application" << endl; + } +} + +void SingleInstance::handleNewConnection() +{ + QLocalSocket *socket = m_server->nextPendingConnection(); + connect(socket, &QLocalSocket::readChannelFinished, this, &SingleInstance::readArgs); +} + +void SingleInstance::readArgs() +{ + auto *socket = static_cast(sender()); + + // check arg data size + const auto argDataSize = socket->bytesAvailable(); + if(argDataSize < 2 && argDataSize > (1024 * 1024)) { + cerr << "Error: Another application instance sent invalid argument data." << endl; + return; + } + + // read arg data + auto argData = make_unique(static_cast(argDataSize)); + socket->read(argData.get(), argDataSize); + socket->close(); + socket->deleteLater(); + + // reconstruct argc argv array + uint16 argc = BE::toUInt16(argData.get()); + vector args; + args.reserve(argc + 1); + for(const char *argv = argData.get() + 2, *end = argData.get() + argDataSize, *i = argv; i != end && *argv;) { + if(!*i) { + args.push_back(argv); + argv = ++i; + } else { + ++i; + } + } + args.push_back(nullptr); + + emit newInstance(static_cast(argc), args.data()); +} + +} diff --git a/application/singleinstance.h b/application/singleinstance.h new file mode 100644 index 0000000..0639929 --- /dev/null +++ b/application/singleinstance.h @@ -0,0 +1,34 @@ +#ifndef SINGLEINSTANCE_H +#define SINGLEINSTANCE_H + +#include + +QT_FORWARD_DECLARE_CLASS(QLocalServer) + +namespace Data { +struct SyncthingDir; +} + +namespace QtGui { + +class SingleInstance : public QObject +{ + Q_OBJECT +public: + SingleInstance(int argc, const char *const *argv, QObject *parent = nullptr); + +Q_SIGNALS: + void newInstance(int argc, const char *const *argv); + +private Q_SLOTS: + void handleNewConnection(); + void readArgs(); + +private: + QLocalServer *m_server; + +}; + +} + +#endif // SINGLEINSTANCE_H diff --git a/data/syncthingprocess.cpp b/data/syncthingprocess.cpp index 061429d..99f354b 100644 --- a/data/syncthingprocess.cpp +++ b/data/syncthingprocess.cpp @@ -29,7 +29,9 @@ void SyncthingProcess::restartSyncthing() void SyncthingProcess::startSyncthing() { - start(Settings::syncthingPath() % QChar(' ') % Settings::syncthingArgs(), QProcess::ReadOnly); + if(state() == QProcess::NotRunning) { + start(Settings::syncthingPath() % QChar(' ') % Settings::syncthingArgs(), QProcess::ReadOnly); + } } void SyncthingProcess::handleFinished(int exitCode, QProcess::ExitStatus exitStatus) diff --git a/gui/trayicon.cpp b/gui/trayicon.cpp index 40ef3fd..9125852 100644 --- a/gui/trayicon.cpp +++ b/gui/trayicon.cpp @@ -6,7 +6,7 @@ #include -#include +#include #include #include #include @@ -28,6 +28,7 @@ TrayIcon::TrayIcon(QObject *parent) : m_statusIconNotify(QIcon(renderSvgImage(QStringLiteral(":/icons/hicolor/scalable/status/syncthing-notify.svg")))), m_statusIconPause(QIcon(renderSvgImage(QStringLiteral(":/icons/hicolor/scalable/status/syncthing-pause.svg")))), m_statusIconSync(QIcon(renderSvgImage(QStringLiteral(":/icons/hicolor/scalable/status/syncthing-sync.svg")))), + m_trayMenu(this), m_status(SyncthingStatus::Disconnected) { // set context menu @@ -37,7 +38,7 @@ TrayIcon::TrayIcon(QObject *parent) : m_contextMenu.addMenu(m_trayMenu.widget()->connectionsMenu()); connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("help-about"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/help-about.svg"))), tr("About")), &QAction::triggered, m_trayMenu.widget(), &TrayWidget::showAboutDialog); m_contextMenu.addSeparator(); - connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("window-close"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/window-close.svg"))), tr("Close")), &QAction::triggered, &QCoreApplication::quit); + connect(m_contextMenu.addAction(QIcon::fromTheme(QStringLiteral("window-close"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/window-close.svg"))), tr("Close")), &QAction::triggered, this, &TrayIcon::deleteLater); setContextMenu(&m_contextMenu); // set initial status diff --git a/gui/traymenu.cpp b/gui/traymenu.cpp index 474d196..bbb4019 100644 --- a/gui/traymenu.cpp +++ b/gui/traymenu.cpp @@ -1,5 +1,6 @@ #include "./traymenu.h" #include "./traywidget.h" +#include "./trayicon.h" #include "../application/settings.h" @@ -7,8 +8,15 @@ namespace QtGui { +TrayMenu::TrayMenu(TrayIcon *trayIcon, QWidget *parent) : + TrayMenu(parent) +{ + m_trayIcon = trayIcon; +} + TrayMenu::TrayMenu(QWidget *parent) : - QMenu(parent) + QMenu(parent), + m_trayIcon(nullptr) { auto *menuLayout = new QHBoxLayout; menuLayout->setMargin(0), menuLayout->setSpacing(0); @@ -17,6 +25,11 @@ TrayMenu::TrayMenu(QWidget *parent) : setPlatformMenu(nullptr); } +TrayMenu::~TrayMenu() +{ + +} + QSize TrayMenu::sizeHint() const { return Settings::trayMenuSize(); diff --git a/gui/traymenu.h b/gui/traymenu.h index c1e58f6..8f126d3 100644 --- a/gui/traymenu.h +++ b/gui/traymenu.h @@ -5,6 +5,7 @@ namespace QtGui { +class TrayIcon; class TrayWidget; class TrayMenu : public QMenu @@ -12,13 +13,17 @@ class TrayMenu : public QMenu Q_OBJECT public: + TrayMenu(TrayIcon *trayIcon, QWidget *parent = nullptr); TrayMenu(QWidget *parent = nullptr); + ~TrayMenu(); QSize sizeHint() const; TrayWidget *widget(); + TrayIcon *icon(); private: TrayWidget *m_trayWidget; + TrayIcon *m_trayIcon; }; inline TrayWidget *TrayMenu::widget() @@ -26,6 +31,11 @@ inline TrayWidget *TrayMenu::widget() return m_trayWidget; } +inline TrayIcon *TrayMenu::icon() +{ + return m_trayIcon; +} + } #endif // TRAY_MENU_H diff --git a/gui/traywidget.cpp b/gui/traywidget.cpp index 12f9fea..60a701e 100644 --- a/gui/traywidget.cpp +++ b/gui/traywidget.cpp @@ -1,5 +1,6 @@ #include "./traywidget.h" #include "./traymenu.h" +#include "./trayicon.h" #include "./settingsdialog.h" #include "./webviewdialog.h" #include "./textviewdialog.h" @@ -28,6 +29,7 @@ #include #include +#include using namespace ApplicationUtilities; using namespace ConversionUtilities; @@ -38,6 +40,8 @@ using namespace std; namespace QtGui { +vector TrayWidget::m_instances; + /*! * \brief Instantiates a new tray widget. */ @@ -55,6 +59,8 @@ TrayWidget::TrayWidget(TrayMenu *parent) : m_dlModel(m_connection), m_selectedConnection(nullptr) { + m_instances.push_back(this); + m_ui->setupUi(this); // setup model and view @@ -105,7 +111,7 @@ TrayWidget::TrayWidget(TrayMenu *parent) : // connect signals and slots connect(m_ui->statusPushButton, &QPushButton::clicked, this, &TrayWidget::changeStatus); - connect(m_ui->closePushButton, &QPushButton::clicked, &QCoreApplication::quit); + connect(m_ui->closePushButton, &QPushButton::clicked, this, &TrayWidget::quitTray); connect(m_ui->aboutPushButton, &QPushButton::clicked, this, &TrayWidget::showAboutDialog); connect(m_ui->webUiPushButton, &QPushButton::clicked, this, &TrayWidget::showWebUi); connect(m_ui->settingsPushButton, &QPushButton::clicked, this, &TrayWidget::showSettingsDialog); @@ -126,7 +132,15 @@ TrayWidget::TrayWidget(TrayMenu *parent) : } TrayWidget::~TrayWidget() -{} +{ + auto i = std::find(m_instances.begin(), m_instances.end(), this); + if(i != m_instances.end()) { + m_instances.erase(i); + } + if(m_instances.empty()) { + QCoreApplication::quit(); + } +} void TrayWidget::showSettingsDialog() { @@ -228,6 +242,21 @@ void TrayWidget::showNotifications() m_ui->notificationsPushButton->setHidden(true); } +void TrayWidget::quitTray() +{ + QObject *parent; + if(m_menu) { + if(m_menu->icon()) { + parent = m_menu->icon(); + } else { + parent = m_menu; + } + } else { + parent = this; + } + parent->deleteLater(); +} + void TrayWidget::handleStatusChanged(SyncthingStatus status) { switch(status) { diff --git a/gui/traywidget.h b/gui/traywidget.h index 8be0e19..3175091 100644 --- a/gui/traywidget.h +++ b/gui/traywidget.h @@ -46,6 +46,7 @@ public: Data::SyncthingConnection &connection(); QMenu *connectionsMenu(); + static const std::vector &instances(); public slots: void showSettingsDialog(); @@ -54,6 +55,7 @@ public slots: void showOwnDeviceId(); void showLog(); void showNotifications(); + void quitTray(); private slots: void handleStatusChanged(Data::SyncthingStatus status); @@ -88,6 +90,7 @@ private: QActionGroup *m_connectionsActionGroup; Settings::ConnectionSettings *m_selectedConnection; std::vector m_notifications; + static std::vector m_instances; }; inline Data::SyncthingConnection &TrayWidget::connection() @@ -100,6 +103,11 @@ inline QMenu *TrayWidget::connectionsMenu() return m_connectionsMenu; } +inline const std::vector &TrayWidget::instances() +{ + return m_instances; +} + } #endif // TRAY_WIDGET_H diff --git a/translations/syncthingtray_de_DE.ts b/translations/syncthingtray_de_DE.ts index d870716..72cd214 100644 --- a/translations/syncthingtray_de_DE.ts +++ b/translations/syncthingtray_de_DE.ts @@ -4,145 +4,145 @@ Data::SyncthingConnection - + disconnected - + reconnecting - + connected - + connected, notifications available - + connected, paused - + connected, synchronizing - + unknown - - + + Connection configuration is insufficient. - + Unable to parse Syncthing log: - + Unable to request system log: - + Unable to locate certificate used by Syncthing GUI. - + Unable to load certificate used by Syncthing GUI. - - + + Unable to parse Syncthing config: - - + + Unable to request Syncthing config: - + Unable to parse connections: - + Unable to request connections: - + Unable to parse directory statistics: - + Unable to request directory statistics: - + Unable to parse device statistics: - + Unable to request device statistics: - + Unable to parse errors: - + Unable to request errors: - + Unable to parse Syncthing events: - + Unable to request Syncthing events: - + Unable to request rescan: - + Unable to request pause/resume: - + Unable to request restart: - + Unable to request QR-Code: @@ -769,82 +769,82 @@ QtGui::TrayIcon - + Web UI - + Settings - + Rescan all - + About - + Close - + Error - + Syncthing notification - click to dismiss - + Not connected to Syncthing - + Disconnected from Syncthing - + Reconnecting ... - + Syncthing is idling - + Syncthing is scanning - + Notifications available - + At least one device is paused - + Synchronization is ongoing - + Synchronization complete @@ -858,7 +858,7 @@ - + Connect @@ -896,7 +896,7 @@ For <i>all</i> notifications, checkout the log - + unknown @@ -922,7 +922,7 @@ For <i>all</i> notifications, checkout the log - + About @@ -937,88 +937,88 @@ For <i>all</i> notifications, checkout the log - + View own device ID - + Rescan all directories - + Show Syncthing log - + Restart Syncthing - + Connection - + device ID is unknown - + Copy to clipboard - + New notifications - + Not connected to Syncthing, click to connect - + Pause - + Syncthing is running, click to pause all devices - + At least one device is paused, click to resume - + The directory <i>%1</i> does not exist on the local machine. - + The file <i>%1</i> does not exist on the local machine. - + Continue - + Own device ID - + Log @@ -1082,22 +1082,22 @@ The Web UI will be opened in the default web browser instead. main - + You must configure how to connect to Syncthing when using Syncthing Tray the first time. - + Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration. - + The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though. - + The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though. diff --git a/translations/syncthingtray_en_US.ts b/translations/syncthingtray_en_US.ts index 270c4c5..ee5ef36 100644 --- a/translations/syncthingtray_en_US.ts +++ b/translations/syncthingtray_en_US.ts @@ -4,145 +4,145 @@ Data::SyncthingConnection - + disconnected - + reconnecting - + connected - + connected, notifications available - + connected, paused - + connected, synchronizing - + unknown - - + + Connection configuration is insufficient. - + Unable to parse Syncthing log: - + Unable to request system log: - + Unable to locate certificate used by Syncthing GUI. - + Unable to load certificate used by Syncthing GUI. - - + + Unable to parse Syncthing config: - - + + Unable to request Syncthing config: - + Unable to parse connections: - + Unable to request connections: - + Unable to parse directory statistics: - + Unable to request directory statistics: - + Unable to parse device statistics: - + Unable to request device statistics: - + Unable to parse errors: - + Unable to request errors: - + Unable to parse Syncthing events: - + Unable to request Syncthing events: - + Unable to request rescan: - + Unable to request pause/resume: - + Unable to request restart: - + Unable to request QR-Code: @@ -769,82 +769,82 @@ QtGui::TrayIcon - + Web UI - + Settings - + Rescan all - + About - + Close - + Error - + Syncthing notification - click to dismiss - + Not connected to Syncthing - + Disconnected from Syncthing - + Reconnecting ... - + Syncthing is idling - + Syncthing is scanning - + Notifications available - + At least one device is paused - + Synchronization is ongoing - + Synchronization complete @@ -858,7 +858,7 @@ - + Connect @@ -896,7 +896,7 @@ For <i>all</i> notifications, checkout the log - + unknown @@ -922,7 +922,7 @@ For <i>all</i> notifications, checkout the log - + About @@ -937,88 +937,88 @@ For <i>all</i> notifications, checkout the log - + View own device ID - + Rescan all directories - + Show Syncthing log - + Restart Syncthing - + Connection - + device ID is unknown - + Copy to clipboard - + New notifications - + Not connected to Syncthing, click to connect - + Pause - + Syncthing is running, click to pause all devices - + At least one device is paused, click to resume - + The directory <i>%1</i> does not exist on the local machine. - + The file <i>%1</i> does not exist on the local machine. - + Continue - + Own device ID - + Log @@ -1082,22 +1082,22 @@ The Web UI will be opened in the default web browser instead. main - + You must configure how to connect to Syncthing when using Syncthing Tray the first time. - + Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration. - + The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though. - + The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though.