#include "./trayicon.h" #include "./traywidget.h" #include #include #include #include #include #include #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #include #endif #include #include #include #include #include #include #ifdef LIB_SYNCTHING_CONNECTOR_SUPPORT_SYSTEMD #include #endif using namespace std; using namespace QtUtilities; using namespace Data; namespace QtGui { /*! * \brief Instantiates a new tray icon. */ TrayIcon::TrayIcon(const QString &connectionConfig, QObject *parent) : QSystemTrayIcon(parent) , m_trayMenu(new TrayMenu(this, &m_parentWidget)) #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS , m_dbusNotificationsEnabled(Settings::values().dbusNotifications) #endif , m_notifyOnSyncthingErrors(Settings::values().notifyOn.syncthingErrors) , m_messageClickedAction(TrayIconMessageClickedAction::None) { // get widget, connection and notifier const auto &widget(trayMenu().widget()); const auto &connection(widget.connection()); const auto ¬ifier(widget.notifier()); // set context menu #ifndef SYNCTHINGTRAY_UNIFY_TRAY_MENUS connect(m_contextMenu.addAction(QIcon(QStringLiteral(":/icons/hicolor/scalable/status/syncthing-default.svg")), tr("Open Syncthing")), &QAction::triggered, &widget, &TrayWidget::showWebUi); connect(m_contextMenu.addAction( QIcon::fromTheme(QStringLiteral("preferences-other"), QIcon(QStringLiteral(":/icons/hicolor/scalable/apps/preferences-other.svg"))), tr("Settings")), &QAction::triggered, &widget, &TrayWidget::showSettingsDialog); connect(m_contextMenu.addAction( QIcon::fromTheme(QStringLiteral("folder-sync"), QIcon(QStringLiteral(":/icons/hicolor/scalable/actions/folder-sync.svg"))), tr("Rescan all")), &QAction::triggered, &widget.connection(), &SyncthingConnection::rescanAllDirs); connect(m_contextMenu.addAction( QIcon::fromTheme(QStringLiteral("text-x-generic"), QIcon(QStringLiteral(":/icons/hicolor/scalable/mimetypes/text-x-generic.svg"))), tr("Log")), &QAction::triggered, &widget, &TrayWidget::showLog); m_errorsAction = m_contextMenu.addAction( QIcon::fromTheme(QStringLiteral("emblem-error"), QIcon(QStringLiteral(":/icons/hicolor/scalable/emblems/8/emblem-error.svg"))), tr("Show internal errors")); m_errorsAction->setVisible(false); connect(m_errorsAction, &QAction::triggered, this, &TrayIcon::showInternalErrorsDialog); m_contextMenu.addMenu(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, &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, this, &TrayIcon::deleteLater); setContextMenu(&m_contextMenu); #endif // connect signals and slots connect(this, &TrayIcon::activated, this, &TrayIcon::handleActivated); connect(this, &TrayIcon::messageClicked, this, &TrayIcon::handleMessageClicked); connect(&connection, &SyncthingConnection::error, this, &TrayIcon::showInternalError); connect(&connection, &SyncthingConnection::newNotification, this, &TrayIcon::showSyncthingNotification); connect(¬ifier, &SyncthingNotifier::syncthingProcessError, this, &TrayIcon::showLauncherError); connect(¬ifier, &SyncthingNotifier::disconnected, this, &TrayIcon::showDisconnected); connect(¬ifier, &SyncthingNotifier::syncComplete, this, &TrayIcon::showSyncComplete); connect(¬ifier, &SyncthingNotifier::newDevice, this, &TrayIcon::showNewDev); connect(¬ifier, &SyncthingNotifier::newDir, this, &TrayIcon::showNewDir); connect(&connection, &SyncthingConnection::statusChanged, this, &TrayIcon::updateStatusIconAndText); connect(&connection, &SyncthingConnection::newDevices, this, &TrayIcon::updateStatusIconAndText); connect(&connection, &SyncthingConnection::devStatusChanged, this, &TrayIcon::updateStatusIconAndText); connect(&IconManager::instance(), &IconManager::statusIconsChanged, this, &TrayIcon::updateStatusIconAndText); #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS connect(&m_dbusNotifier, &DBusStatusNotifier::connectRequested, &connection, static_cast(&SyncthingConnection::connect)); connect(&m_dbusNotifier, &DBusStatusNotifier::dismissNotificationsRequested, &widget, &TrayWidget::dismissNotifications); connect(&m_dbusNotifier, &DBusStatusNotifier::showNotificationsRequested, &widget, &TrayWidget::showNotifications); connect(&m_dbusNotifier, &DBusStatusNotifier::errorDetailsRequested, this, &TrayIcon::showInternalErrorsDialog); connect(&m_dbusNotifier, &DBusStatusNotifier::webUiRequested, &widget, &TrayWidget::showWebUi); connect(¬ifier, &SyncthingNotifier::connected, &m_dbusNotifier, &DBusStatusNotifier::hideDisconnect); #endif // apply settings, this also establishes the connection to Syncthing (according to settings) // note: It is important to apply settings only after all Signals & Slots have been connected (e.g. to handle SyncthingConnection::error()). // note: This weirdly calls updateStatusIconAndText(). So there is not need to call it again within this constructor. trayMenu().widget().applySettings(connectionConfig); } /*! * \brief Moves the specified \a point in the specified \a rect. */ void moveInside(QPoint &point, const QRect &rect) { if (point.y() < rect.top()) { point.setY(rect.top()); } else if (point.y() > rect.bottom()) { point.setY(rect.bottom()); } if (point.x() < rect.left()) { point.setX(rect.left()); } else if (point.x() > rect.right()) { point.setX(rect.right()); } } void TrayIcon::handleActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { case QSystemTrayIcon::Context: // can't catch that event on Plasma 5 anyways break; case QSystemTrayIcon::MiddleClick: trayMenu().widget().showWebUi(); break; case QSystemTrayIcon::Trigger: { trayMenu().showUsingPositioningSettings(); break; } default:; } } void TrayIcon::handleMessageClicked() { switch (m_messageClickedAction) { case TrayIconMessageClickedAction::None: return; case TrayIconMessageClickedAction::DismissNotification: trayMenu().widget().dismissNotifications(); break; case TrayIconMessageClickedAction::ShowInternalErrors: showInternalErrorsDialog(); break; case TrayIconMessageClickedAction::ShowWebUi: trayMenu().widget().showWebUi(); break; } } void TrayIcon::showDisconnected() { #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showDisconnect(); } else #endif { m_messageClickedAction = TrayIconMessageClickedAction::None; showMessage(QCoreApplication::applicationName(), tr("Disconnected from Syncthing"), QSystemTrayIcon::Warning); } } void TrayIcon::showSyncComplete(const QString &message) { #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showSyncComplete(message); } else #endif { m_messageClickedAction = TrayIconMessageClickedAction::None; showMessage(QCoreApplication::applicationName(), message, QSystemTrayIcon::Information); } } void TrayIcon::handleErrorsCleared() { #ifndef SYNCTHINGTRAY_UNIFY_TRAY_MENUS m_errorsAction->setVisible(false); #endif } void TrayIcon::showInternalError( const QString &errorMessage, SyncthingErrorCategory category, int networkError, const QNetworkRequest &request, const QByteArray &response) { if (!InternalError::isRelevant(trayMenu().widget().connection(), category, networkError)) { return; } InternalError error(errorMessage, request.url(), response); #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showInternalError(error); } else #endif { m_messageClickedAction = TrayIconMessageClickedAction::ShowInternalErrors; showMessage(tr("Error"), errorMessage, QSystemTrayIcon::Critical); } InternalErrorsDialog::addError(move(error)); #ifdef SYNCTHINGTRAY_UNIFY_TRAY_MENUS trayMenu().widget().showInternalErrorsButton(); #else m_errorsAction->setVisible(true); #endif } void TrayIcon::showLauncherError(const QString &errorMessage, const QString &additionalInfo) { #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showLauncherError(errorMessage, additionalInfo); } else #endif { m_messageClickedAction = TrayIconMessageClickedAction::None; showMessage(tr("Launcher error"), QStringList({ errorMessage, additionalInfo }).join(QChar('\n')), QSystemTrayIcon::Critical); } } void TrayIcon::showSyncthingNotification(CppUtilities::DateTime when, const QString &message) { if (m_notifyOnSyncthingErrors) { #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showSyncthingNotification(when, message); } else #else Q_UNUSED(when) #endif { m_messageClickedAction = TrayIconMessageClickedAction::DismissNotification; showMessage(tr("Syncthing notification - click to dismiss"), message, QSystemTrayIcon::Warning); } } updateStatusIconAndText(); } void TrayIcon::updateStatusIconAndText() { auto &trayWidget = trayMenu().widget(); const auto statusInfo = StatusInfo(trayMenu().widget().connection(), TrayWidget::instances().size() > 1 && trayWidget.selectedConnection() ? trayWidget.selectedConnection()->label : QString()); if (statusInfo.additionalStatusText().isEmpty()) { setToolTip(statusInfo.statusText()); } else { setToolTip(statusInfo.statusText() % QChar('\n') % statusInfo.additionalStatusText()); } setIcon(statusInfo.statusIcon()); } void TrayIcon::showNewDev(const QString &devId, const QString &message) { #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showNewDev(devId, message); } else #else Q_UNUSED(devId) #endif { m_messageClickedAction = TrayIconMessageClickedAction::ShowWebUi; showMessage(tr("Syncthing device wants to connect - click for web UI"), message, QSystemTrayIcon::Information); } } void TrayIcon::showNewDir(const QString &devId, const QString &dirId, const QString &message) { #ifdef QT_UTILITIES_SUPPORT_DBUS_NOTIFICATIONS if (m_dbusNotificationsEnabled) { m_dbusNotifier.showNewDir(devId, dirId, message); } else #else Q_UNUSED(devId) Q_UNUSED(dirId) #endif { m_messageClickedAction = TrayIconMessageClickedAction::ShowWebUi; showMessage(tr("New Syncthing directory - click for web UI"), message, QSystemTrayIcon::Information); } } void TrayIcon::showInternalErrorsDialog() { auto *const errorViewDlg = InternalErrorsDialog::instance(); connect(errorViewDlg, &InternalErrorsDialog::errorsCleared, this, &TrayIcon::handleErrorsCleared); centerWidget(errorViewDlg); errorViewDlg->show(); } } // namespace QtGui