Major multi monitor improvements and rewrite of tray window positioning
authorDominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
Fri, 24 Apr 2020 16:34:11 +0000 (18:34 +0200)
committerKevin Ottens (Rebase PR Action) <er-vin@users.noreply.github.com>
Mon, 15 Jun 2020 12:32:25 +0000 (12:32 +0000)
Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
src/3rdparty/qtmacgoodies [new submodule]
src/common/utility.h
src/common/utility_win.cpp
src/gui/systray.cpp
src/gui/systray.h
src/gui/tray/Window.qml

diff --git a/src/3rdparty/qtmacgoodies b/src/3rdparty/qtmacgoodies
new file mode 160000 (submodule)
index 0000000..b59d091
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit b59d091b3e6b98e7219cf636f7d21fb267242c27
index e8a2435466ce278b90b7edc9322a818b55d097ff..1aa17928c48f7ec795499b0f6391c16082be7c7a 100644 (file)
@@ -229,6 +229,7 @@ namespace Utility {
     OCSYNC_EXPORT bool registryDeleteKeyTree(HKEY hRootKey, const QString &subKey);
     OCSYNC_EXPORT bool registryDeleteKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName);
     OCSYNC_EXPORT bool registryWalkSubKeys(HKEY hRootKey, const QString &subKey, const std::function<void(HKEY, const QString &)> &callback);
+    OCSYNC_EXPORT QRect getTaskbarDimensions();
 #endif
 }
 /** @} */ // \addtogroup
index f8157a68aeb97af6648d1b2edbc321bc93a9a69a..9df84e0c5c866bc94b6ba0fba8aabf1c15dbe920 100644 (file)
@@ -100,6 +100,20 @@ static inline bool hasDarkSystray_private()
     }
 }
 
+QRect Utility::getTaskbarDimensions()
+{
+    APPBARDATA barData;
+    barData.cbSize = sizeof(APPBARDATA);
+
+    BOOL fResult = (BOOL)SHAppBarMessage(ABM_GETTASKBARPOS, &barData);
+    if (!fResult) {
+        return QRect();
+    }
+
+    RECT barRect = barData.rc;
+    return QRect(barRect.left, barRect.top, (barRect.right - barRect.left), (barRect.bottom - barRect.top));
+}
+
 QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, const QString &valueName)
 {
     QVariant value;
@@ -142,6 +156,15 @@ QVariant Utility::registryGetKeyValue(HKEY hRootKey, const QString &subKey, cons
             }
             break;
         }
+        case REG_BINARY: {
+            QByteArray buffer;
+            buffer.resize(sizeInBytes);
+            result = RegQueryValueEx(hKey, reinterpret_cast<LPCWSTR>(valueName.utf16()), 0, &type, reinterpret_cast<LPBYTE>(buffer.data()), &sizeInBytes);
+            if (result == ERROR_SUCCESS) {
+                value = buffer.at(12);
+            }
+            break;
+        }
         default:
             Q_UNREACHABLE();
         }
index 66ac956f2c7a5c4db5e00c2cd114627ac44884de..98d55c1f968ba50e4d57898670060b50c9c11a42 100644 (file)
@@ -16,6 +16,7 @@
 #include "systray.h"
 #include "theme.h"
 #include "config.h"
+#include "common/utility.h"
 #include "tray/UserModel.h"
 
 #include <QCursor>
@@ -60,6 +61,7 @@ Systray::Systray()
 
     connect(AccountManager::instance(), &AccountManager::accountAdded,
         this, &Systray::showWindow);
+
 }
 
 void Systray::create()
@@ -69,6 +71,7 @@ void Systray::create()
     }
     _trayEngine->load(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"));
     hideWindow();
+    emit activated(QSystemTrayIcon::ActivationReason::Unknown);
 }
 
 void Systray::slotNewUserSelected()
@@ -85,17 +88,6 @@ bool Systray::isOpen()
     return _isOpen;
 }
 
-Q_INVOKABLE int Systray::screenIndex()
-{
-    auto qPos = QCursor::pos();
-    for (int i = 0; i < QGuiApplication::screens().count(); i++) {
-        if (QGuiApplication::screens().at(i)->geometry().contains(qPos)) {
-            return i;
-        }
-    }
-    return 0;
-}
-
 Q_INVOKABLE void Systray::setOpened()
 {
     _isOpen = true;
@@ -133,123 +125,87 @@ void Systray::setToolTip(const QString &tip)
     QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
 }
 
-int Systray::calcTrayWindowX()
+bool Systray::syncIsPaused()
 {
-#ifdef Q_OS_OSX
-    // macOS handles DPI awareness differently
-    // and menu bar is always at the top, icons starting from the right
-
-    QPoint topLeft = this->geometry().topLeft();
-    QPoint topRight = this->geometry().topRight();
-    int trayIconTopCenterX = (topRight - ((topRight - topLeft) * 0.5)).x();
-    return trayIconTopCenterX - (400 * 0.5);
-#else
-    QScreen* trayScreen = nullptr;
-    if (QGuiApplication::screens().count() > 1) {
-        trayScreen = QGuiApplication::screens().at(screenIndex());
-    } else {
-        trayScreen = QGuiApplication::primaryScreen();
-    }
+    return _syncIsPaused;
+}
 
-    int screenWidth = trayScreen->geometry().width();
-    int screenHeight = trayScreen->geometry().height();
-    int availableWidth = trayScreen->availableGeometry().width();
-    int availableHeight = trayScreen->availableGeometry().height();
-
-    QPoint topRightDpiAware = QPoint();
-    QPoint topLeftDpiAware = QPoint();
-    if (this->geometry().left() == 0 || this->geometry().top() == 0) {
-        // tray geometry is invalid - QT bug on some linux desktop environments
-        // Use mouse position instead. Cringy, but should work for now
-        topRightDpiAware = QCursor::pos() / trayScreen->devicePixelRatio();
-        topLeftDpiAware = QCursor::pos() / trayScreen->devicePixelRatio();
+void Systray::pauseResumeSync()
+{
+    if (_syncIsPaused) {
+        _syncIsPaused = false;
+        emit resumeSync();
     } else {
-        topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
-        topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
+        _syncIsPaused = true;
+        emit pauseSync();
     }
+}
 
-    // get x coordinate from top center point of tray icon
-    int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x();
+/********************************************************************************************/
+/* Helper functions for cross-platform tray icon position and taskbar orientation detection */
+/********************************************************************************************/
 
-    if (availableHeight < screenHeight) {
-        // taskbar is on top or bottom
-        if (trayIconTopCenterX + (400 * 0.5) > availableWidth) {
-            return availableWidth - 400 - 12;
-        } else {
-            return trayIconTopCenterX - (400 * 0.5);
-        }
-    } else {
-        if (trayScreen->availableGeometry().x() > trayScreen->geometry().x()) {
-            // on the left
-            return (screenWidth - availableWidth) + 6;
-        } else {
-            // on the right
-            return screenWidth - 400 - (screenWidth - availableWidth) - 6;
+/// Return the current screen index based on cursor position
+int Systray::screenIndex()
+{
+    auto qPos = QCursor::pos();
+    for (int i = 0; i < QGuiApplication::screens().count(); i++) {
+        if (QGuiApplication::screens().at(i)->geometry().contains(qPos)) {
+            return i;
         }
     }
-#endif
+    return 0;
 }
-int Systray::calcTrayWindowY()
-{
-#ifdef Q_OS_OSX
-    // macOS menu bar is always 22 (effective) pixels
-    // don't use availableGeometry() here, because this also excludes the dock
-    return 22+6;
-#else
-    QScreen* trayScreen = nullptr;
-    if (QGuiApplication::screens().count() > 1) {
-        trayScreen = QGuiApplication::screens().at(screenIndex());
-    } else {
-        trayScreen = QGuiApplication::primaryScreen();
-    }
-
-    int screenHeight = trayScreen->geometry().height();
-    int availableHeight = trayScreen->availableGeometry().height();
 
-    QPoint topRightDpiAware = QPoint();
-    QPoint topLeftDpiAware = QPoint();
-    if (this->geometry().left() == 0 || this->geometry().top() == 0) {
-        // tray geometry is invalid - QT bug on some linux desktop environments
-        // Use mouse position instead. Cringy, but should work for now
-        topRightDpiAware = QCursor::pos() / trayScreen->devicePixelRatio();
-        topLeftDpiAware = QCursor::pos() / trayScreen->devicePixelRatio();
-    } else {
-        topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
-        topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
-    }
-    // get y coordinate from top center point of tray icon
-    int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y();
-
-    if (availableHeight < screenHeight) {
-        // taskbar is on top or bottom
-        if (trayScreen->availableGeometry().y() > trayScreen->geometry().y()) {
-            // on top
-            return (screenHeight - availableHeight) + 6;
-        } else {
-            // on bottom
-            return screenHeight - 510 - (screenHeight - availableHeight) - 6;
-        }
-    } else {
-        // on the left or right
-        return (trayIconTopCenterY - 510 + 12);
+int Systray::taskbarOrientation()
+{
+// 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 taskbarPosition = Utility::registryGetKeyValue(HKEY_CURRENT_USER,
+        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StuckRects3",
+        "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;
     }
+// Else (generally linux DEs): fallback to cursor position nearest edge logic
+#else
+    return 0;
 #endif
 }
 
-bool Systray::syncIsPaused()
+QRect Systray::taskbarRect()
 {
-    return _syncIsPaused;
+#if defined(Q_OS_WIN)
+    return Utility::getTaskbarDimensions();
+#else
+    return QRect(0, 0, 0, 32);
+#endif
 }
 
-void Systray::pauseResumeSync()
+QPoint Systray::calcTrayIconCenter()
 {
-    if (_syncIsPaused) {
-        _syncIsPaused = false;
-        emit resumeSync();
-    } else {
-        _syncIsPaused = true;
-        emit pauseSync();
-    }
+// QSystemTrayIcon::geometry() is broken for ages on most Linux DEs (invalid geometry returned)
+// thus we can use this only for Windows and macOS
+#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
+    auto trayIconCenter = geometry().center();
+    return trayIconCenter;
+#else
+// On Linux, fall back to mouse position (assuming tray icon is activated by mouse click)
+    return QCursor::pos();
+#endif
 }
 
 } // namespace OCC
index 0152a75ffbabfd66b2111012697665b7cbb10be2..ad92c7a54c66864bcc5356a3a193bc9395b7e6d1 100644 (file)
@@ -33,6 +33,13 @@ namespace Ui {
     class Systray;
 }
 
+enum TaskBarPosition {
+    Bottom = 0,
+    Left,
+    Top,
+    Right
+};
+
 /**
  * @brief The Systray class
  * @ingroup gui
@@ -51,12 +58,13 @@ public:
     bool isOpen();
 
     Q_INVOKABLE void pauseResumeSync();
-    Q_INVOKABLE int calcTrayWindowX();
-    Q_INVOKABLE int calcTrayWindowY();
     Q_INVOKABLE bool syncIsPaused();
     Q_INVOKABLE void setOpened();
     Q_INVOKABLE void setClosed();
     Q_INVOKABLE int screenIndex();
+    Q_INVOKABLE QPoint calcTrayIconCenter();
+    Q_INVOKABLE int taskbarOrientation();
+    Q_INVOKABLE QRect taskbarRect();
 
 signals:
     void currentUserChanged();
index ce1cda2c47e32c53011b30c32150d6f8768b5ca4..1ae7973dbf7d4ca6a556bd47b065e9ff884147a9 100644 (file)
@@ -10,6 +10,125 @@ import QtGraphicalEffects 1.0
 import Style 1.0\r
 \r
 Window {\r
+\r
+    function setTrayWindowPosition()\r
+    {\r
+        var trayIconCenter = systrayBackend.calcTrayIconCenter();\r
+        console.debug("Calculated tray icon center:",trayIconCenter);\r
+        var currentScreen = systrayBackend.screenIndex();\r
+        console.debug("Tray menu about to show on screen",currentScreen,".");\r
+        trayWindow.screen = Qt.application.screens[currentScreen];\r
+        trayWindow.show();\r
+        trayWindow.raise();\r
+        trayWindow.requestActivate();\r
+        var trayWindowX;\r
+        var trayWindowY;\r
+        var taskbarHeight;\r
+        var taskbarWidth;\r
+        var tbOrientation;\r
+        if (Qt.platform.os === "linux") {\r
+            var distBottom = Screen.height - (trayIconCenter.y - Screen.virtualY);\r
+            var distRight = Screen.width - (trayIconCenter.x - Screen.virtualX);\r
+            var distLeft = trayIconCenter.x - Screen.virtualX;\r
+            var distTop = trayIconCenter.y - Screen.virtualY;\r
+            if (distBottom < distRight && distBottom < distTop && distBottom < distLeft) {\r
+                tbOrientation = 0;\r
+            } else if (distLeft < distTop && distLeft < distRight && distLeft < distBottom) {\r
+                tbOrientation = 1;\r
+            } else if (distTop < distRight && distTop < distBottom && distTop < distLeft) {\r
+                tbOrientation = 2;\r
+            } else {\r
+                tbOrientation = 3;\r
+            }\r
+        } else {\r
+            tbOrientation = systrayBackend.taskbarOrientation();\r
+        }\r
+        if (Qt.platform.os === "osx") {\r
+            taskbarHeight = 22;\r
+            taskbarWidth = Screen.width;\r
+        } else if (Qt.platform.os === "linux") {\r
+            taskbarHeight = (tbOrientation === 0 || tbOrientation === 2) ? 32 : Screen.height;\r
+            taskbarWidth = (tbOrientation === 0 || tbOrientation === 2) ? Screen.width : 32;\r
+        } else {\r
+            taskbarHeight = systrayBackend.taskbarRect().height;\r
+            taskbarWidth = systrayBackend.taskbarRect().width;\r
+        }\r
+\r
+        switch(tbOrientation) {\r
+            // Platform separation here: Windows and macOS draw coordinates have to be given in screen-coordinates\r
+            // KDE and most xorg based DEs expect them as virtual coordinates\r
+            case 0:\r
+                console.debug("Taskbar is on the bottom.");\r
+                trayWindowX = trayIconCenter.x - trayWindow.width / 2;\r
+                trayWindowY = (Qt.platform.os !== "linux") ? (Screen.height - taskbarHeight - trayWindow.height - 4)\r
+                                                           : (Screen.height + Screen.virtualY - taskbarHeight - trayWindow.height - 4);\r
+                break;\r
+            case 1:\r
+                console.debug("Taskbar is on the left.");\r
+                trayWindowX = (Qt.platform.os !== "linux") ? (taskbarWidth + 4)\r
+                                                           : (Screen.virtualX + taskbarWidth + 4);\r
+                trayWindowY = trayIconCenter.y;\r
+                break;\r
+            case 2:\r
+                console.debug("Taskbar is on the top.");\r
+                trayWindowX = trayIconCenter.x - trayWindow.width / 2;\r
+                trayWindowY = Screen.virtualY + taskbarHeight + 4;\r
+                break;\r
+            case 3:\r
+                console.debug("Taskbar is on the right.");\r
+                trayWindowX = (Qt.platform.os !== "linux") ? (Screen.width - taskbarWidth - trayWindow.width - 4)\r
+                                                           : (Screen.width + Screen.virtualX - taskbarWidth - trayWindow.width - 4);\r
+                trayWindowY = trayIconCenter.y;\r
+                break;\r
+        }\r
+\r
+        console.debug("Screen.height:",Screen.height);\r
+        console.debug("Screen.desktopAvailableHeight:",Screen.desktopAvailableHeight);\r
+        console.debug("Screen.virtualY:",Screen.virtualY);\r
+        console.debug("Screen.width:",Screen.width);\r
+        console.debug("Screen.desktopAvailableWidth:",Screen.desktopAvailableWidth);\r
+        console.debug("Screen.virtualX:",Screen.virtualX);\r
+        console.debug("Taskbar height:",taskbarHeight);\r
+        console.debug("Taskbar width:",taskbarWidth);\r
+\r
+        if (Screen.width <= trayWindowX + trayWindow.width) {\r
+            console.debug("Out-of-screen condition on the right detected. Adjusting window position.");\r
+            if (Qt.platform.os !== "linux") {\r
+                trayWindowX = Screen.width - trayWindow.width - 4;\r
+            } else {\r
+                trayWindowX = Screen.width + Screen.virtualX - trayWindow.width - 4 - (tbOrientation === 3 ? taskbarWidth : 0);\r
+            }\r
+        }\r
+        if (trayWindowX <= Screen.x && Qt.platform.os !== "linux") {\r
+            console.debug("Out-of-screen condition on the left detected. Adjusting window position.");\r
+            trayWindowX = Screen.x + 4;\r
+        }\r
+        if (trayWindowX <= Screen.virtualX && Qt.platform.os === "linux") {\r
+           console.debug("Out-of-screen condition on the left detected. Adjusting window position.");\r
+           trayWindowX = Screen.virtualX + 4 + (tbOrientation === 1 ? taskbarWidth : 0)\r
+        }\r
+        if (trayWindowY <= Screen.y && Qt.platform.os !== "linux") {\r
+            console.debug("Out-of-screen condition on the top detected. Adjusting window position.");\r
+            trayWindowY = Screen.y + 4;\r
+        }\r
+        if (trayWindowY <= Screen.virtualY && Qt.platform.os === "linux") {\r
+            console.debug("Out-of-screen condition on the top detected. Adjusting window position.");\r
+            trayWindowY = Screen.virtualY + 4 + (tbOrientation === 2 ? taskbarHeight : 0);\r
+        }\r
+        if (Screen.height <= trayWindowY - Screen.virtualY + trayWindow.height) {\r
+            console.debug("Out-of-screen condition on the bottom detected. Adjusting window position.");\r
+            if (Qt.platform.os !== "linux") {\r
+                trayWindowY = Screen.height - trayWindow.height - 4;\r
+            } else {\r
+                trayWindowY = Screen.height + Screen.virtualY - trayWindow.height - 4;\r
+            }\r
+\r
+        }\r
+        console.debug("Tray window position: x =",trayWindowX," y =",trayWindowY);\r
+        trayWindow.setX(trayWindowX);\r
+        trayWindow.setY(trayWindowY);\r
+    }\r
+\r
     id:         trayWindow\r
 \r
     width:      Style.trayWindowWidth\r
@@ -62,11 +181,7 @@ Window {
         target: systrayBackend\r
         onShowWindow: {\r
             accountMenu.close();\r
-            trayWindow.show();\r
-            trayWindow.raise();\r
-            trayWindow.requestActivate();\r
-            trayWindow.setX( Qt.application.screens[systrayBackend.screenIndex()].virtualX + systrayBackend.calcTrayWindowX());\r
-            trayWindow.setY( Qt.application.screens[systrayBackend.screenIndex()].virtualY + systrayBackend.calcTrayWindowY());\r
+            setTrayWindowPosition();\r
             systrayBackend.setOpened();\r
             userModelBackend.fetchCurrentActivityModel();\r
         }\r