/* * Copyright (C) by Cédric Bellegarde * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "systray.h" #include "common/restartmanager.h" #include "gui/accountmanager.h" #include "gui/application.h" #include "gui/folderman.h" #include "gui/networkinformation.h" #include "libsync/theme.h" #include #include #include #include using namespace Qt::Literals::StringLiterals; namespace OCC { namespace { void showSettingsAfterTrayEvent() { auto *app = ocApp(); QMetaObject::invokeMethod(app, [app] { app->showSettings(); }, Qt::QueuedConnection); } } Systray::Systray(QObject *parent) : QSystemTrayIcon(parent) { connect(this, &QSystemTrayIcon::activated, this, [](QSystemTrayIcon::ActivationReason reason) { // Left click if (reason == QSystemTrayIcon::Trigger) { showSettingsAfterTrayEvent(); } }); connect(AccountManager::instance(), &AccountManager::accountAdded, this, [this](AccountStatePtr accountState) { connect(accountState.data(), &AccountState::stateChanged, this, &Systray::slotComputeOverallSyncStatus); }); connect(FolderMan::instance(), &FolderMan::folderSyncStateChange, this, &Systray::slotComputeOverallSyncStatus); // init systray slotComputeOverallSyncStatus(); computeContextMenu(); show(); } void Systray::setToolTip(const QString &tip) { #ifdef Q_OS_WIN QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip)); #else QSystemTrayIcon::setToolTip(u"%1\n%2"_s.arg(Theme::instance()->appNameGUI(), tip)); #endif } void Systray::slotComputeOverallSyncStatus() { auto getIcon = [](const SyncResult &result) { return Theme::instance()->themeTrayIcon(result); }; auto getIconFromStatus = [getIcon](const SyncResult::Status &status) { return getIcon(SyncResult{status}); }; bool allSignedOut = true; bool allPaused = true; QVector problemAccounts; for (const auto &a : AccountManager::instance()->accounts()) { if (!a->isSignedOut()) { allSignedOut = false; } if (!a->isConnected()) { problemAccounts.append(a); } } const auto &map = FolderMan::instance()->folders(); for (auto *f : map) { if (!f->isSyncPaused()) { allPaused = false; } } if (!problemAccounts.empty()) { setIcon(getIconFromStatus(SyncResult::Status::Offline)); #ifdef Q_OS_WIN // Windows has a 128-char tray tooltip length limit. QStringList accountNames; for (const auto &a : std::as_const(problemAccounts)) { accountNames.append(a->account()->displayNameWithHost()); } setToolTip(tr("Disconnected from %1").arg(accountNames.join(QLatin1String(", ")))); #else QStringList messages; messages.append(tr("Disconnected from accounts:")); for (const auto &a : std::as_const(problemAccounts)) { QString message = tr("Account %1").arg(a->account()->displayNameWithHost()); if (!a->connectionErrors().empty()) { message += QLatin1String("\n") + a->connectionErrors().join(QLatin1String("\n")); } messages.append(message); } setToolTip(messages.join(QLatin1String("\n\n"))); #endif return; } if (allSignedOut) { setIcon(getIconFromStatus(SyncResult::Status::Offline)); setToolTip(tr("Please sign in")); return; } else if (allPaused) { setIcon(getIconFromStatus(SyncResult::Paused)); setToolTip(tr("Account synchronization is disabled")); return; } // display the info of the least successful sync (eg. do not just display the result of the latest sync) QString trayMessage; auto trayOverallStatusResult = FolderMan::trayOverallStatus(map); const QIcon statusIcon = getIcon(trayOverallStatusResult.overallStatus()); setIcon(statusIcon); // create the tray blob message, check if we have an defined state #ifdef Q_OS_WIN // Windows has a 128-char tray tooltip length limit. trayMessage = FolderMan::instance()->trayTooltipStatusString(trayOverallStatusResult.overallStatus(), false); #else QStringList allStatusStrings; for (auto *folder : map) { QString folderMessage = FolderMan::trayTooltipStatusString(folder->syncResult(), folder->isSyncPaused()); allStatusStrings += tr("Space »%1«: %2").arg(folder->shortGuiLocalPath(), folderMessage); } trayMessage = allStatusStrings.join(QLatin1String("\n")); #endif setToolTip(trayMessage); } void Systray::computeContextMenu() { Q_ASSERT(!contextMenu()); auto *menu = new QMenu(Theme::instance()->appNameGUI()); auto *showAction = menu->addAction(Theme::instance()->applicationIcon(), tr("Show %1").arg(Theme::instance()->appNameGUI())); connect(showAction, &QAction::triggered, this, [] { showSettingsAfterTrayEvent(); }); auto *pauseResume = new QAction(menu); auto updatePauseResumeAction = [pauseResume] { pauseResume->setText(FolderMan::instance()->scheduler()->isRunning() ? tr("Pause synchronizations") : tr("Resume synchronizations")); }; connect(pauseResume, &QAction::triggered, FolderMan::instance()->scheduler(), [] { if (FolderMan::instance()->scheduler()->isRunning()) { FolderMan::instance()->scheduler()->stop(); FolderMan::instance()->scheduler()->terminateCurrentSync(tr("Synchronization paused")); } else { FolderMan::instance()->scheduler()->start(); } }); connect(FolderMan::instance()->scheduler(), &SyncScheduler::isRunningChanged, pauseResume, updatePauseResumeAction); menu->addAction(pauseResume); updatePauseResumeAction(); if (ocApp()->debugMode()) { menu->addSeparator(); auto *debugMenu = menu->addMenu(QStringLiteral("Debug actions")); debugMenu->addAction(QStringLiteral("Crash if asserts enabled - OC_ENSURE"), ocApp(), [] { if (OC_ENSURE(false)) { Q_UNREACHABLE(); } }); debugMenu->addAction(QStringLiteral("Crash if asserts enabled - Q_ASSERT"), ocApp(), [] { Q_ASSERT(false); }); debugMenu->addAction(QStringLiteral("Crash now - Utility::crash()"), ocApp(), [] { Utility::crash(); }); debugMenu->addAction(QStringLiteral("Crash now - OC_ENFORCE()"), ocApp(), [] { OC_ENFORCE(false); }); debugMenu->addAction(QStringLiteral("Crash now - qFatal"), ocApp(), [] { qFatal("la Qt fatale"); }); debugMenu->addAction(QStringLiteral("Restart now"), ocApp(), [] { RestartManager::requestRestart(); }); debugMenu->addSeparator(); auto captivePortalCheckbox = debugMenu->addAction(QStringLiteral("Behind Captive Portal")); captivePortalCheckbox->setCheckable(true); captivePortalCheckbox->setChecked(NetworkInformation::instance()->isForcedCaptivePortal()); connect(captivePortalCheckbox, &QAction::triggered, this, [](bool checked) { NetworkInformation::instance()->setForcedCaptivePortal(checked); }); connect(NetworkInformation::instance(), &NetworkInformation::isBehindCaptivePortalChanged, captivePortalCheckbox, [captivePortalCheckbox] { captivePortalCheckbox->setChecked(NetworkInformation::instance()->isForcedCaptivePortal()); }); } menu->addSeparator(); if (!Theme::instance()->helpUrl().isEmpty()) { menu->addAction(tr("Help"), this, [] { QDesktopServices::openUrl(Theme::instance()->helpUrl()); }); } menu->addAction(tr("About"), ocApp(), &Application::showAbout); // this action will be hidden on mac and be part of the application menu menu->addAction(tr("About Qt"), qApp, &QApplication::aboutQt)->setMenuRole(QAction::AboutQtRole); menu->addAction(tr("Quit"), ocApp(), &QApplication::quit); setContextMenu(menu); } } // namespace OCC