| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953 |
- /*
- * Copyright (C) by Cédric Bellegarde <gnumdk@gmail.com>
- *
- * 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 "accountmanager.h"
- #include "systray.h"
- #include "theme.h"
- #include "config.h"
- #include "common/utility.h"
- #include "tray/svgimageprovider.h"
- #include "tray/usermodel.h"
- #include "wheelhandler.h"
- #include "tray/trayimageprovider.h"
- #include "configfile.h"
- #include "accessmanager.h"
- #include "callstatechecker.h"
- #include <QCursor>
- #include <QGuiApplication>
- #include <QQmlApplicationEngine>
- #include <QQmlContext>
- #include <QQuickWindow>
- #include <QVariantMap>
- #include <QScreen>
- #include <QMenu>
- #include <QGuiApplication>
- #ifdef USE_FDO_NOTIFICATIONS
- #include <QDBusConnection>
- #include <QDBusInterface>
- #include <QDBusMessage>
- #include <QDBusPendingCall>
- #define NOTIFICATIONS_SERVICE "org.freedesktop.Notifications"
- #define NOTIFICATIONS_PATH "/org/freedesktop/Notifications"
- #define NOTIFICATIONS_IFACE "org.freedesktop.Notifications"
- #endif
- namespace OCC {
- Q_LOGGING_CATEGORY(lcSystray, "nextcloud.gui.systray")
- Systray *Systray::_instance = nullptr;
- Systray *Systray::instance()
- {
- if (!_instance) {
- _instance = new Systray();
- }
- return _instance;
- }
- void Systray::setTrayEngine(QQmlApplicationEngine *trayEngine)
- {
- _trayEngine = trayEngine;
- _trayEngine->setNetworkAccessManagerFactory(&_accessManagerFactory);
- _trayEngine->addImportPath("qrc:/qml/theme");
- _trayEngine->addImageProvider("avatars", new ImageProvider);
- _trayEngine->addImageProvider(QLatin1String("svgimage-custom-color"), new OCC::Ui::SvgImageProvider);
- _trayEngine->addImageProvider(QLatin1String("tray-image-provider"), new TrayImageProvider);
- }
- Systray::Systray()
- : QSystemTrayIcon(nullptr)
- {
- #if defined(Q_OS_MACOS) && defined(BUILD_OWNCLOUD_OSX_BUNDLE)
- setUserNotificationCenterDelegate();
- checkNotificationAuth(MacNotificationAuthorizationOptions::Default); // No provisional auth, ask user explicitly first time
- registerNotificationCategories(QString(tr("Download")));
- #elif !defined(Q_OS_MACOS)
- connect(AccountManager::instance(), &AccountManager::accountAdded,
- this, &Systray::setupContextMenu);
- connect(AccountManager::instance(), &AccountManager::accountRemoved,
- this, &Systray::setupContextMenu);
- setupContextMenu();
- #endif
- connect(UserModel::instance(), &UserModel::currentUserChanged,
- this, &Systray::slotCurrentUserChanged);
- connect(UserModel::instance(), &UserModel::addAccount,
- this, &Systray::openAccountWizard);
- #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
- connect(AccountManager::instance(), &AccountManager::accountAdded,
- this, [this]{ showWindow(); });
- #else
- // Since the positioning of the QSystemTrayIcon is borked on non-Windows and non-macOS desktop environments,
- // we hardcode the position of the tray to be in the center when we add a new account from somewhere like
- // the wizard. Otherwise with the conventional method we end up with the tray appearing wherever the cursor
- // is placed
- connect(AccountManager::instance(), &AccountManager::accountAdded,
- this, [this]{ showWindow(WindowPosition::Center); });
- #endif
- }
- void Systray::create()
- {
- if (_trayEngine) {
- if (!AccountManager::instance()->accounts().isEmpty()) {
- _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
- }
- QQmlComponent trayWindowComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"));
- if(trayWindowComponent.isError()) {
- qCWarning(lcSystray) << trayWindowComponent.errorString();
- } else {
- _trayWindow.reset(qobject_cast<QQuickWindow*>(trayWindowComponent.create()));
- }
- }
- hideWindow();
- emit activated(QSystemTrayIcon::ActivationReason::Unknown);
- const auto folderMap = FolderMan::instance()->map();
- for (const auto *folder : folderMap) {
- if (!folder->syncPaused()) {
- _syncIsPaused = false;
- break;
- }
- }
- }
- void Systray::showWindow(WindowPosition position)
- {
- if(isOpen() || !_trayWindow) {
- return;
- }
- if(position == WindowPosition::Center) {
- positionWindowAtScreenCenter(_trayWindow.data());
- } else {
- positionWindowAtTray(_trayWindow.data());
- }
- _trayWindow->show();
- _trayWindow->raise();
- _trayWindow->requestActivate();
- setIsOpen(true);
- UserModel::instance()->fetchCurrentActivityModel();
- }
- void Systray::hideWindow()
- {
- if(!isOpen() || !_trayWindow) {
- return;
- }
- _trayWindow->hide();
- setIsOpen(false);
- }
- void Systray::setupContextMenu()
- {
- const auto oldContextMenu = _contextMenu.data();
- // If we delete the old QMenu before setting the new one the client will crash on GNOME.
- // Let's delete it once this method is over
- if(oldContextMenu) {
- oldContextMenu->deleteLater();
- }
- _contextMenu = new QMenu();
- // NOTE: for reasons unclear, setting the the new menu after adding all the actions
- // will not work on GNOME, as the old menu will not be correctly replaced.
- setContextMenu(_contextMenu);
- if (AccountManager::instance()->accounts().isEmpty()) {
- _contextMenu->addAction(tr("Add account"), this, &Systray::openAccountWizard);
- } else {
- _contextMenu->addAction(tr("Open main dialog"), this, [this]{ showWindow(); });
- }
- auto pauseAction = _contextMenu->addAction(tr("Pause sync"), this, &Systray::slotPauseAllFolders);
- auto resumeAction = _contextMenu->addAction(tr("Resume sync"), this, &Systray::slotUnpauseAllFolders);
- _contextMenu->addAction(tr("Settings"), this, &Systray::openSettings);
- _contextMenu->addAction(tr("Help"), this, &Systray::openHelp);
- _contextMenu->addAction(tr("Exit %1").arg(Theme::instance()->appNameGUI()), this, &Systray::shutdown);
- connect(_contextMenu, &QMenu::aboutToShow, [=] {
- const auto folders = FolderMan::instance()->map();
- const auto allPaused = std::all_of(std::cbegin(folders), std::cend(folders), [](Folder *f) { return f->syncPaused(); });
- const auto pauseText = folders.size() > 1 ? tr("Pause sync for all") : tr("Pause sync");
- pauseAction->setText(pauseText);
- pauseAction->setVisible(!allPaused);
- pauseAction->setEnabled(!allPaused);
- const auto anyPaused = std::any_of(std::cbegin(folders), std::cend(folders), [](Folder *f) { return f->syncPaused(); });
- const auto resumeText = folders.size() > 1 ? tr("Resume sync for all") : tr("Resume sync");
- resumeAction->setText(resumeText);
- resumeAction->setVisible(anyPaused);
- resumeAction->setEnabled(anyPaused);
- });
- }
- void Systray::destroyDialog(QQuickWindow *dialog) const
- {
- dialog->destroy();
- dialog->deleteLater();
- }
- void Systray::createCallDialog(const Activity &callNotification, const AccountStatePtr accountState)
- {
- qCDebug(lcSystray) << "Starting a new call dialog for notification with id: " << callNotification._id << "with text: " << callNotification._subject;
- if (_trayEngine && !_callsAlreadyNotified.contains(callNotification._id)) {
- const QVariantMap talkNotificationData{
- {"conversationToken", callNotification._talkNotificationData.conversationToken},
- {"messageId", callNotification._talkNotificationData.messageId},
- {"messageSent", callNotification._talkNotificationData.messageSent},
- {"userAvatar", callNotification._talkNotificationData.userAvatar},
- };
- QVariantList links;
- for(const auto &link : callNotification._links) {
- links.append(QVariantMap{
- {"imageSource", link._imageSource},
- {"imageSourceHovered", link._imageSourceHovered},
- {"label", link._label},
- {"link", link._link},
- {"primary", link._primary},
- {"verb", link._verb},
- });
- }
- const QVariantMap initialProperties{
- {"accountState", QVariant::fromValue(accountState.data())},
- {"talkNotificationData", talkNotificationData},
- {"links", links},
- {"subject", callNotification._subject},
- {"link", callNotification._link},
- };
- const auto callDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/CallNotificationDialog.qml"));
- if(callDialog->isError()) {
- qCWarning(lcSystray) << callDialog->errorString();
- return;
- }
- // This call dialog gets deallocated on close conditions
- // by a call from the QML side to the destroyDialog slot
- callDialog->createWithInitialProperties(initialProperties);
- _callsAlreadyNotified.insert(callNotification._id);
- }
- }
- void Systray::createEditFileLocallyLoadingDialog(const QString &fileName)
- {
- if (_editFileLocallyLoadingDialog) {
- return;
- }
- qCDebug(lcSystray) << "Opening a file local editing dialog...";
- const auto editFileLocallyLoadingDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/tray/EditFileLocallyLoadingDialog.qml"));
- if (editFileLocallyLoadingDialog->isError()) {
- qCWarning(lcSystray) << editFileLocallyLoadingDialog->errorString();
- return;
- }
- _editFileLocallyLoadingDialog = editFileLocallyLoadingDialog->createWithInitialProperties(QVariantMap{{QStringLiteral("fileName"), fileName}});
- }
- void Systray::destroyEditFileLocallyLoadingDialog()
- {
- if (!_editFileLocallyLoadingDialog) {
- return;
- }
- qCDebug(lcSystray) << "Closing a file local editing dialog...";
- _editFileLocallyLoadingDialog->deleteLater();
- _editFileLocallyLoadingDialog = nullptr;
- }
- void Systray::createResolveConflictsDialog(const OCC::ActivityList &allConflicts)
- {
- const auto conflictsDialog = std::make_unique<QQmlComponent>(_trayEngine, QStringLiteral("qrc:/qml/src/gui/ResolveConflictsDialog.qml"));
- const QVariantMap initialProperties{
- {"allConflicts", QVariant::fromValue(allConflicts)},
- };
- if(conflictsDialog->isError()) {
- qCWarning(lcSystray) << conflictsDialog->errorString();
- return;
- }
- // This call dialog gets deallocated on close conditions
- // by a call from the QML side to the destroyDialog slot
- auto dialog = QScopedPointer(conflictsDialog->createWithInitialProperties(initialProperties));
- if (!dialog) {
- return;
- }
- dialog->setParent(QGuiApplication::instance());
- auto dialogWindow = qobject_cast<QQuickWindow*>(dialog.data());
- if (!dialogWindow) {
- return;
- }
- dialogWindow->show();
- dialogWindow->raise();
- dialogWindow->requestActivate();
- dialog.take();
- }
- bool Systray::raiseDialogs()
- {
- return raiseFileDetailDialogs();
- }
- bool Systray::raiseFileDetailDialogs(const QString &localPath)
- {
- if(_fileDetailDialogs.empty()) {
- return false;
- }
- auto it = _fileDetailDialogs.begin();
- while (it != _fileDetailDialogs.end()) {
- const auto dialog = *it;
- auto nullDialog = dialog == nullptr;
- if (!nullDialog && !dialog->isVisible()) {
- destroyDialog(dialog);
- nullDialog = true;
- }
- if (!nullDialog && (localPath.isEmpty() || dialog->property("localPath").toString() == localPath)) {
- dialog->show();
- dialog->raise();
- dialog->requestActivate();
- ++it;
- continue;
- }
- it = _fileDetailDialogs.erase(it);
- continue;
- }
- // If it is empty then we have raised no dialogs, so return false (and viceversa)
- return !_fileDetailDialogs.empty();
- }
- void Systray::createFileDetailsDialog(const QString &localPath)
- {
- if (raiseFileDetailDialogs(localPath)) {
- qCDebug(lcSystray) << "Reopening an existing file details dialog for " << localPath;
- return;
- }
- qCDebug(lcSystray) << "Opening new file details dialog for " << localPath;
- if (!_trayEngine) {
- qCWarning(lcSystray) << "Could not open file details dialog for" << localPath << "as no tray engine was available";
- return;
- }
- const auto folder = FolderMan::instance()->folderForPath(localPath);
- if (!folder) {
- qCWarning(lcSystray) << "Could not open file details dialog for" << localPath << "no responsible folder found";
- return;
- }
- const QVariantMap initialProperties{
- {"accountState", QVariant::fromValue(folder->accountState())},
- {"localPath", localPath},
- };
- QQmlComponent fileDetailsDialog(_trayEngine, QStringLiteral("qrc:/qml/src/gui/filedetails/FileDetailsWindow.qml"));
- if (!fileDetailsDialog.isError()) {
- const auto createdDialog = fileDetailsDialog.createWithInitialProperties(initialProperties);
- const auto dialog = qobject_cast<QQuickWindow*>(createdDialog);
- if(!dialog) {
- qCWarning(lcSystray) << "File details dialog window resulted in creation of object that was not a window!";
- return;
- }
- _fileDetailDialogs.append(dialog);
- dialog->show();
- dialog->raise();
- dialog->requestActivate();
- } else {
- qCWarning(lcSystray) << fileDetailsDialog.errorString();
- }
- }
- void Systray::createShareDialog(const QString &localPath)
- {
- createFileDetailsDialog(localPath);
- Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Sharing);
- }
- void Systray::createFileActivityDialog(const QString &localPath)
- {
- createFileDetailsDialog(localPath);
- Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Activity);
- }
- void Systray::presentShareViewInTray(const QString &localPath)
- {
- const auto folder = FolderMan::instance()->folderForPath(localPath);
- if (!folder) {
- qCWarning(lcSystray) << "Could not open file details view in tray for" << localPath << "no responsible folder found";
- return;
- }
- qCDebug(lcSystray) << "Opening file details view in tray for " << localPath;
- Q_EMIT showFileDetails(folder->accountState(), localPath, FileDetailsPage::Sharing);
- }
- void Systray::slotCurrentUserChanged()
- {
- if (_trayEngine) {
- // Change ActivityModel
- _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel());
- }
- // Rebuild App list
- UserAppsModel::instance()->buildAppList();
- }
- void Systray::slotUnpauseAllFolders()
- {
- setPauseOnAllFoldersHelper(false);
- }
- void Systray::slotPauseAllFolders()
- {
- setPauseOnAllFoldersHelper(true);
- }
- void Systray::setPauseOnAllFoldersHelper(bool pause)
- {
- // For some reason we get the raw pointer from Folder::accountState()
- // that's why we need a list of raw pointers for the call to contains
- // later on...
- const auto accounts = [=] {
- const auto ptrList = AccountManager::instance()->accounts();
- auto result = QList<AccountState *>();
- result.reserve(ptrList.size());
- std::transform(std::cbegin(ptrList), std::cend(ptrList), std::back_inserter(result), [](const AccountStatePtr &account) {
- return account.data();
- });
- return result;
- }();
- const auto folders = FolderMan::instance()->map();
- for (auto f : folders) {
- if (accounts.contains(f->accountState())) {
- f->setSyncPaused(pause);
- if (pause) {
- f->slotTerminateSync();
- }
- }
- }
- }
- QString Systray::windowTitle() const
- {
- return Theme::instance()->appNameGUI();
- }
- bool Systray::useNormalWindow() const
- {
- if (!isSystemTrayAvailable()) {
- return true;
- }
- ConfigFile cfg;
- return cfg.showMainDialogAsNormalWindow();
- }
- bool Systray::isOpen() const
- {
- return _isOpen;
- }
- bool Systray::enableAddAccount() const
- {
- #if defined ENFORCE_SINGLE_ACCOUNT
- return AccountManager::instance()->accounts().isEmpty();
- #else
- return true;
- #endif
- }
- void Systray::setIsOpen(const bool isOpen)
- {
- _isOpen = isOpen;
- Q_EMIT isOpenChanged();
- }
- void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon)
- {
- #ifdef USE_FDO_NOTIFICATIONS
- if (QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) {
- const QVariantMap hints = {{QStringLiteral("desktop-entry"), LINUX_APPLICATION_ID}};
- QList<QVariant> args = QList<QVariant>() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME
- << title << message << QStringList() << hints << qint32(-1);
- QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify");
- method.setArguments(args);
- QDBusConnection::sessionBus().asyncCall(method);
- } else
- #endif
- #if defined(Q_OS_MACOS) && defined(BUILD_OWNCLOUD_OSX_BUNDLE)
- if (canOsXSendUserNotification()) {
- sendOsXUserNotification(title, message);
- } else
- #endif
- {
- QSystemTrayIcon::showMessage(title, message, icon);
- }
- }
- void Systray::showUpdateMessage(const QString &title, const QString &message, const QUrl &webUrl)
- {
- #if defined(Q_OS_MACOS) && defined(BUILD_OWNCLOUD_OSX_BUNDLE)
- sendOsXUpdateNotification(title, message, webUrl);
- #else // TODO: Implement custom notifications (i.e. actionable) for other OSes
- Q_UNUSED(webUrl);
- showMessage(title, message);
- #endif
- }
- void Systray::showTalkMessage(const QString &title, const QString &message, const QString &token, const QString &replyTo, const AccountStatePtr &accountState)
- {
- #if defined(Q_OS_MACOS) && defined(BUILD_OWNCLOUD_OSX_BUNDLE)
- sendOsXTalkNotification(title, message, token, replyTo, accountState);
- #else // TODO: Implement custom notifications (i.e. actionable) for other OSes
- Q_UNUSED(replyTo)
- Q_UNUSED(token)
- Q_UNUSED(accountState)
- showMessage(title, message);
- #endif
- }
- void Systray::setToolTip(const QString &tip)
- {
- QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
- }
- bool Systray::syncIsPaused() const
- {
- return _syncIsPaused;
- }
- void Systray::setSyncIsPaused(const bool syncIsPaused)
- {
- _syncIsPaused = syncIsPaused;
- if (_syncIsPaused) {
- slotPauseAllFolders();
- } else {
- slotUnpauseAllFolders();
- }
- }
- /********************************************************************************************/
- /* Helper functions for cross-platform tray icon position and taskbar orientation detection */
- /********************************************************************************************/
- void Systray::positionWindowAtTray(QQuickWindow *window) const
- {
- if (!useNormalWindow()) {
- window->setScreen(currentScreen());
- const auto position = computeWindowPosition(window->width(), window->height());
- window->setPosition(position);
- }
- }
- void Systray::positionWindowAtScreenCenter(QQuickWindow *window) const
- {
- if(!useNormalWindow()) {
- window->setScreen(currentScreen());
- const QPoint windowAdjustment(window->geometry().width() / 2, window->geometry().height() / 2);
- const auto position = currentScreen()->virtualGeometry().center() - windowAdjustment;
- window->setPosition(position);
- }
- }
- void Systray::forceWindowInit(QQuickWindow *window) const
- {
- // HACK: At least on Windows, if the systray window is not shown at least once
- // it can prevent session handling to carry on properly, so we show/hide it here
- // this shouldn't flicker
- window->show();
- window->hide();
-
- #ifdef Q_OS_MAC
- // On macOS we need to designate the tray window as visible on all spaces and
- // at the menu bar level, otherwise showing it can cause the current spaces to
- // change, or the window could be obscured by another window that shouldn't
- // normally cover a menu.
- OCC::setTrayWindowLevelAndVisibleOnAllSpaces(window);
- #endif
- }
- void Systray::positionNotificationWindow(QQuickWindow *window) const
- {
- if (!useNormalWindow()) {
- window->setScreen(currentScreen());
- if(geometry().isValid()) {
- // On OSes where the QSystemTrayIcon geometry method isn't borked, we can actually figure out where the system tray is located
- // We can therefore use our normal routines
- const auto position = computeNotificationPosition(window->width(), window->height());
- window->setPosition(position);
- } else if (QProcessEnvironment::systemEnvironment().contains(QStringLiteral("XDG_CURRENT_DESKTOP")) &&
- (QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP")).contains(QStringLiteral("GNOME")))) {
- // We can safely hardcode the top-right position for the notification when running GNOME
- const auto position = computeNotificationPosition(window->width(), window->height(), 0, NotificationPosition::TopRight);
- window->setPosition(position);
- } else {
- // For other DEs we play it safe and place the notification in the centre of the screen
- positionWindowAtScreenCenter(window);
- }
- // TODO: Get actual notification positions for the DEs
- }
- }
- QScreen *Systray::currentScreen() const
- {
- const auto screen = QGuiApplication::screenAt(QCursor::pos());
- if(screen) {
- return screen;
- }
- // Didn't find anything matching the cursor position,
- // falling back to the primary screen
- return QGuiApplication::primaryScreen();
- }
- Systray::TaskBarPosition Systray::taskbarOrientation() const
- {
- // macOS: Always on top
- #if defined(Q_OS_MACOS)
- return TaskBarPosition::Top;
- // Windows: Check registry for actual taskbar orientation
- #elif defined(Q_OS_WIN)
- auto taskbarPositionSubkey = QStringLiteral("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects3");
- if (!Utility::registryKeyExists(HKEY_CURRENT_USER, taskbarPositionSubkey)) {
- // Windows 7
- taskbarPositionSubkey = QStringLiteral("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects2");
- }
- if (!Utility::registryKeyExists(HKEY_CURRENT_USER, taskbarPositionSubkey)) {
- return TaskBarPosition::Bottom;
- }
- auto taskbarPosition = Utility::registryGetKeyValue(HKEY_CURRENT_USER, taskbarPositionSubkey, "Settings");
- switch (taskbarPosition.toInt()) {
- // Mapping windows binary value (0 = left, 1 = top, 2 = right, 3 = bottom) to qml logic (0 = bottom, 1 = left...)
- case 0:
- return TaskBarPosition::Left;
- case 1:
- return TaskBarPosition::Top;
- case 2:
- return TaskBarPosition::Right;
- case 3:
- return TaskBarPosition::Bottom;
- default:
- return TaskBarPosition::Bottom;
- }
- // Probably Linux
- #else
- const auto screenRect = currentScreenRect();
- const auto trayIconCenter = calcTrayIconCenter();
- const auto distBottom = screenRect.bottom() - trayIconCenter.y();
- const auto distRight = screenRect.right() - trayIconCenter.x();
- const auto distLeft = trayIconCenter.x() - screenRect.left();
- const auto distTop = trayIconCenter.y() - screenRect.top();
- const auto minDist = std::min({distRight, distTop, distBottom});
- if (minDist == distBottom) {
- return TaskBarPosition::Bottom;
- } else if (minDist == distLeft) {
- return TaskBarPosition::Left;
- } else if (minDist == distTop) {
- return TaskBarPosition::Top;
- } else {
- return TaskBarPosition::Right;
- }
- #endif
- }
- // TODO: Get real taskbar dimensions Linux as well
- QRect Systray::taskbarGeometry() const
- {
- #if defined(Q_OS_WIN)
- QRect tbRect = Utility::getTaskbarDimensions();
- //QML side expects effective pixels, convert taskbar dimensions if necessary
- auto pixelRatio = currentScreen()->devicePixelRatio();
- if (pixelRatio != 1) {
- tbRect.setHeight(tbRect.height() / pixelRatio);
- tbRect.setWidth(tbRect.width() / pixelRatio);
- }
- return tbRect;
- #elif defined(Q_OS_MACOS)
- const auto screenWidth = currentScreenRect().width();
- const auto statusBarHeight = static_cast<int>(OCC::menuBarThickness());
- return {0, 0, screenWidth, statusBarHeight};
- #else
- if (taskbarOrientation() == TaskBarPosition::Bottom || taskbarOrientation() == TaskBarPosition::Top) {
- auto screenWidth = currentScreenRect().width();
- return {0, 0, screenWidth, 32};
- } else {
- auto screenHeight = currentScreenRect().height();
- return {0, 0, 32, screenHeight};
- }
- #endif
- }
- QRect Systray::currentScreenRect() const
- {
- const auto screen = currentScreen();
- Q_ASSERT(screen);
- return screen->geometry();
- }
- QPoint Systray::computeWindowReferencePoint() const
- {
- constexpr auto spacing = 4;
- const auto trayIconCenter = calcTrayIconCenter();
- const auto taskbarRect = taskbarGeometry();
- const auto taskbarScreenEdge = taskbarOrientation();
- const auto screenRect = currentScreenRect();
- qCDebug(lcSystray) << "screenRect:" << screenRect;
- qCDebug(lcSystray) << "taskbarRect:" << taskbarRect;
- qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
- qCDebug(lcSystray) << "trayIconCenter:" << trayIconCenter;
- switch(taskbarScreenEdge) {
- case TaskBarPosition::Bottom:
- return {
- trayIconCenter.x(),
- screenRect.bottom() - taskbarRect.height() - spacing
- };
- case TaskBarPosition::Left:
- return {
- screenRect.left() + taskbarRect.width() + spacing,
- trayIconCenter.y()
- };
- case TaskBarPosition::Top:
- return {
- trayIconCenter.x(),
- screenRect.top() + taskbarRect.height() + spacing
- };
- case TaskBarPosition::Right:
- return {
- screenRect.right() - taskbarRect.width() - spacing,
- trayIconCenter.y()
- };
- }
- Q_UNREACHABLE();
- }
- QPoint Systray::computeNotificationReferencePoint(int spacing, NotificationPosition position) const
- {
- auto trayIconCenter = calcTrayIconCenter();
- auto taskbarScreenEdge = taskbarOrientation();
- auto taskbarRect = taskbarGeometry();
- const auto screenRect = currentScreenRect();
-
- if(position == NotificationPosition::TopLeft) {
- taskbarScreenEdge = TaskBarPosition::Top;
- trayIconCenter = QPoint(0, 0);
- taskbarRect = QRect(0, 0, screenRect.width(), 32);
- } else if(position == NotificationPosition::TopRight) {
- taskbarScreenEdge = TaskBarPosition::Top;
- trayIconCenter = QPoint(screenRect.width(), 0);
- taskbarRect = QRect(0, 0, screenRect.width(), 32);
- } else if(position == NotificationPosition::BottomLeft) {
- taskbarScreenEdge = TaskBarPosition::Bottom;
- trayIconCenter = QPoint(0, screenRect.height());
- taskbarRect = QRect(0, 0, screenRect.width(), 32);
- } else if(position == NotificationPosition::BottomRight) {
- taskbarScreenEdge = TaskBarPosition::Bottom;
- trayIconCenter = QPoint(screenRect.width(), screenRect.height());
- taskbarRect = QRect(0, 0, screenRect.width(), 32);
- }
- qCDebug(lcSystray) << "screenRect:" << screenRect;
- qCDebug(lcSystray) << "taskbarRect:" << taskbarRect;
- qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
- qCDebug(lcSystray) << "trayIconCenter:" << trayIconCenter;
- switch(taskbarScreenEdge) {
- case TaskBarPosition::Bottom:
- return {
- trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
- screenRect.bottom() - taskbarRect.height() - spacing
- };
- case TaskBarPosition::Left:
- return {
- screenRect.left() + taskbarRect.width() + spacing,
- trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
- };
- case TaskBarPosition::Top:
- return {
- trayIconCenter.x() < screenRect.center().x() ? screenRect.left() + spacing : screenRect.right() - spacing,
- screenRect.top() + taskbarRect.height() + spacing
- };
- case TaskBarPosition::Right:
- return {
- screenRect.right() - taskbarRect.width() - spacing,
- trayIconCenter.y() < screenRect.center().y() ? screenRect.top() + spacing : screenRect.bottom() - spacing
- };
- }
- Q_UNREACHABLE();
- }
- QRect Systray::computeWindowRect(int spacing, const QPoint &topLeft, const QPoint &bottomRight) const
- {
- const auto screenRect = currentScreenRect();
- const auto rect = QRect(topLeft, bottomRight);
- auto offset = QPoint();
- if (rect.left() < screenRect.left()) {
- offset.setX(screenRect.left() - rect.left() + spacing);
- } else if (rect.right() > screenRect.right()) {
- offset.setX(screenRect.right() - rect.right() - spacing);
- }
- if (rect.top() < screenRect.top()) {
- offset.setY(screenRect.top() - rect.top() + spacing);
- } else if (rect.bottom() > screenRect.bottom()) {
- offset.setY(screenRect.bottom() - rect.bottom() - spacing);
- }
- return rect.translated(offset);
- }
- QPoint Systray::computeWindowPosition(int width, int height) const
- {
- constexpr auto spacing = 4;
- const auto referencePoint = computeWindowReferencePoint();
- const auto taskbarScreenEdge = taskbarOrientation();
- const auto screenRect = currentScreenRect();
- const auto topLeft = [=]() {
- switch(taskbarScreenEdge) {
- case TaskBarPosition::Bottom:
- return referencePoint - QPoint(width / 2, height);
- case TaskBarPosition::Left:
- return referencePoint;
- case TaskBarPosition::Top:
- return referencePoint - QPoint(width / 2, 0);
- case TaskBarPosition::Right:
- return referencePoint - QPoint(width, 0);
- }
- Q_UNREACHABLE();
- }();
- const auto bottomRight = topLeft + QPoint(width, height);
- const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);
- qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
- qCDebug(lcSystray) << "screenRect:" << screenRect;
- qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
- qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;
- return windowRect.topLeft();
- }
- QPoint Systray::computeNotificationPosition(int width, int height, int spacing, NotificationPosition position) const
- {
- const auto referencePoint = computeNotificationReferencePoint(spacing, position);
- auto trayIconCenter = calcTrayIconCenter();
- auto taskbarScreenEdge = taskbarOrientation();
- const auto screenRect = currentScreenRect();
-
- if(position == NotificationPosition::TopLeft) {
- taskbarScreenEdge = TaskBarPosition::Top;
- trayIconCenter = QPoint(0, 0);
- } else if(position == NotificationPosition::TopRight) {
- taskbarScreenEdge = TaskBarPosition::Top;
- trayIconCenter = QPoint(screenRect.width(), 0);
- } else if(position == NotificationPosition::BottomLeft) {
- taskbarScreenEdge = TaskBarPosition::Bottom;
- trayIconCenter = QPoint(0, screenRect.height());
- } else if(position == NotificationPosition::BottomRight) {
- taskbarScreenEdge = TaskBarPosition::Bottom;
- trayIconCenter = QPoint(screenRect.width(), screenRect.height());
- }
-
- const auto topLeft = [=]() {
- switch(taskbarScreenEdge) {
- case TaskBarPosition::Bottom:
- return trayIconCenter.x() < screenRect.center().x() ? referencePoint - QPoint(0, height) : referencePoint - QPoint(width, height);
- case TaskBarPosition::Left:
- return trayIconCenter.y() < screenRect.center().y() ? referencePoint : referencePoint - QPoint(0, height);
- case TaskBarPosition::Top:
- return trayIconCenter.x() < screenRect.center().x() ? referencePoint : referencePoint - QPoint(width, 0);
- case TaskBarPosition::Right:
- return trayIconCenter.y() < screenRect.center().y() ? referencePoint - QPoint(width, 0) : QPoint(width, height);
- }
- Q_UNREACHABLE();
- }();
- const auto bottomRight = topLeft + QPoint(width, height);
- const auto windowRect = computeWindowRect(spacing, topLeft, bottomRight);
- qCDebug(lcSystray) << "taskbarScreenEdge:" << taskbarScreenEdge;
- qCDebug(lcSystray) << "screenRect:" << screenRect;
- qCDebug(lcSystray) << "windowRect (reference)" << QRect(topLeft, bottomRight);
- qCDebug(lcSystray) << "windowRect (adjusted)" << windowRect;
- qCDebug(lcSystray) << "referencePoint" << referencePoint;
- return windowRect.topLeft();
- }
- QPoint Systray::calcTrayIconCenter() const
- {
- if(geometry().isValid()) {
- // QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
- // thus we can use this only for Windows and macOS
- auto trayIconCenter = geometry().center();
- return trayIconCenter;
- }
- // On Linux, fall back to mouse position (assuming tray icon is activated by mouse click)
- return QCursor::pos(currentScreen());
- }
- AccessManagerFactory::AccessManagerFactory()
- : QQmlNetworkAccessManagerFactory()
- {
- }
- QNetworkAccessManager* AccessManagerFactory::create(QObject *parent)
- {
- const auto am = new AccessManager(parent);
- const auto diskCache = new QNetworkDiskCache(am);
- diskCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation));
- am->setCache(diskCache);
- return am;
- }
- } // namespace OCC
|