Major multi monitor improvements and rewrite of tray window positioning

Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com>
This commit is contained in:
Dominique Fuchs 2020-04-24 18:34:11 +02:00 коммит произвёл Kevin Ottens (Rebase PR Action)
Родитель 121e23e322
Коммит f44df7f61a
6 изменённых файлов: 225 добавлений и 121 удалений

1
src/3rdparty/qtmacgoodies поставляемый Submodule

@ -0,0 +1 @@
Subproject commit b59d091b3e6b98e7219cf636f7d21fb267242c27

Просмотреть файл

@ -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

Просмотреть файл

@ -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();
}

Просмотреть файл

@ -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,109 +125,6 @@ void Systray::setToolTip(const QString &tip)
QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip));
}
int Systray::calcTrayWindowX()
{
#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();
}
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();
} else {
topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio();
topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio();
}
// get x coordinate from top center point of tray icon
int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x();
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;
}
}
#endif
}
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);
}
#endif
}
bool Systray::syncIsPaused()
{
return _syncIsPaused;
@ -252,4 +141,71 @@ void Systray::pauseResumeSync()
}
}
/********************************************************************************************/
/* Helper functions for cross-platform tray icon position and taskbar orientation detection */
/********************************************************************************************/
/// 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;
}
}
return 0;
}
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
}
QRect Systray::taskbarRect()
{
#if defined(Q_OS_WIN)
return Utility::getTaskbarDimensions();
#else
return QRect(0, 0, 0, 32);
#endif
}
QPoint Systray::calcTrayIconCenter()
{
// 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

Просмотреть файл

@ -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();

Просмотреть файл

@ -10,6 +10,125 @@ import QtGraphicalEffects 1.0
import Style 1.0
Window {
function setTrayWindowPosition()
{
var trayIconCenter = systrayBackend.calcTrayIconCenter();
console.debug("Calculated tray icon center:",trayIconCenter);
var currentScreen = systrayBackend.screenIndex();
console.debug("Tray menu about to show on screen",currentScreen,".");
trayWindow.screen = Qt.application.screens[currentScreen];
trayWindow.show();
trayWindow.raise();
trayWindow.requestActivate();
var trayWindowX;
var trayWindowY;
var taskbarHeight;
var taskbarWidth;
var tbOrientation;
if (Qt.platform.os === "linux") {
var distBottom = Screen.height - (trayIconCenter.y - Screen.virtualY);
var distRight = Screen.width - (trayIconCenter.x - Screen.virtualX);
var distLeft = trayIconCenter.x - Screen.virtualX;
var distTop = trayIconCenter.y - Screen.virtualY;
if (distBottom < distRight && distBottom < distTop && distBottom < distLeft) {
tbOrientation = 0;
} else if (distLeft < distTop && distLeft < distRight && distLeft < distBottom) {
tbOrientation = 1;
} else if (distTop < distRight && distTop < distBottom && distTop < distLeft) {
tbOrientation = 2;
} else {
tbOrientation = 3;
}
} else {
tbOrientation = systrayBackend.taskbarOrientation();
}
if (Qt.platform.os === "osx") {
taskbarHeight = 22;
taskbarWidth = Screen.width;
} else if (Qt.platform.os === "linux") {
taskbarHeight = (tbOrientation === 0 || tbOrientation === 2) ? 32 : Screen.height;
taskbarWidth = (tbOrientation === 0 || tbOrientation === 2) ? Screen.width : 32;
} else {
taskbarHeight = systrayBackend.taskbarRect().height;
taskbarWidth = systrayBackend.taskbarRect().width;
}
switch(tbOrientation) {
// Platform separation here: Windows and macOS draw coordinates have to be given in screen-coordinates
// KDE and most xorg based DEs expect them as virtual coordinates
case 0:
console.debug("Taskbar is on the bottom.");
trayWindowX = trayIconCenter.x - trayWindow.width / 2;
trayWindowY = (Qt.platform.os !== "linux") ? (Screen.height - taskbarHeight - trayWindow.height - 4)
: (Screen.height + Screen.virtualY - taskbarHeight - trayWindow.height - 4);
break;
case 1:
console.debug("Taskbar is on the left.");
trayWindowX = (Qt.platform.os !== "linux") ? (taskbarWidth + 4)
: (Screen.virtualX + taskbarWidth + 4);
trayWindowY = trayIconCenter.y;
break;
case 2:
console.debug("Taskbar is on the top.");
trayWindowX = trayIconCenter.x - trayWindow.width / 2;
trayWindowY = Screen.virtualY + taskbarHeight + 4;
break;
case 3:
console.debug("Taskbar is on the right.");
trayWindowX = (Qt.platform.os !== "linux") ? (Screen.width - taskbarWidth - trayWindow.width - 4)
: (Screen.width + Screen.virtualX - taskbarWidth - trayWindow.width - 4);
trayWindowY = trayIconCenter.y;
break;
}
console.debug("Screen.height:",Screen.height);
console.debug("Screen.desktopAvailableHeight:",Screen.desktopAvailableHeight);
console.debug("Screen.virtualY:",Screen.virtualY);
console.debug("Screen.width:",Screen.width);
console.debug("Screen.desktopAvailableWidth:",Screen.desktopAvailableWidth);
console.debug("Screen.virtualX:",Screen.virtualX);
console.debug("Taskbar height:",taskbarHeight);
console.debug("Taskbar width:",taskbarWidth);
if (Screen.width <= trayWindowX + trayWindow.width) {
console.debug("Out-of-screen condition on the right detected. Adjusting window position.");
if (Qt.platform.os !== "linux") {
trayWindowX = Screen.width - trayWindow.width - 4;
} else {
trayWindowX = Screen.width + Screen.virtualX - trayWindow.width - 4 - (tbOrientation === 3 ? taskbarWidth : 0);
}
}
if (trayWindowX <= Screen.x && Qt.platform.os !== "linux") {
console.debug("Out-of-screen condition on the left detected. Adjusting window position.");
trayWindowX = Screen.x + 4;
}
if (trayWindowX <= Screen.virtualX && Qt.platform.os === "linux") {
console.debug("Out-of-screen condition on the left detected. Adjusting window position.");
trayWindowX = Screen.virtualX + 4 + (tbOrientation === 1 ? taskbarWidth : 0)
}
if (trayWindowY <= Screen.y && Qt.platform.os !== "linux") {
console.debug("Out-of-screen condition on the top detected. Adjusting window position.");
trayWindowY = Screen.y + 4;
}
if (trayWindowY <= Screen.virtualY && Qt.platform.os === "linux") {
console.debug("Out-of-screen condition on the top detected. Adjusting window position.");
trayWindowY = Screen.virtualY + 4 + (tbOrientation === 2 ? taskbarHeight : 0);
}
if (Screen.height <= trayWindowY - Screen.virtualY + trayWindow.height) {
console.debug("Out-of-screen condition on the bottom detected. Adjusting window position.");
if (Qt.platform.os !== "linux") {
trayWindowY = Screen.height - trayWindow.height - 4;
} else {
trayWindowY = Screen.height + Screen.virtualY - trayWindow.height - 4;
}
}
console.debug("Tray window position: x =",trayWindowX," y =",trayWindowY);
trayWindow.setX(trayWindowX);
trayWindow.setY(trayWindowY);
}
id: trayWindow
width: Style.trayWindowWidth
@ -62,11 +181,7 @@ Window {
target: systrayBackend
onShowWindow: {
accountMenu.close();
trayWindow.show();
trayWindow.raise();
trayWindow.requestActivate();
trayWindow.setX( Qt.application.screens[systrayBackend.screenIndex()].virtualX + systrayBackend.calcTrayWindowX());
trayWindow.setY( Qt.application.screens[systrayBackend.screenIndex()].virtualY + systrayBackend.calcTrayWindowY());
setTrayWindowPosition();
systrayBackend.setOpened();
userModelBackend.fetchCurrentActivityModel();
}