Linux: fix the mouse grab issue, for real

Port previous workaround from 1.x to 2.0

And some minor tweaks.

Signed-off-by: Yuhang Zhao <2546789017@qq.com>
This commit is contained in:
Yuhang Zhao 2022-04-24 13:27:11 +08:00
parent 2915d1f33a
commit 3c0209c979
8 changed files with 120 additions and 105 deletions

View File

@ -119,7 +119,7 @@ if(UNIX AND NOT APPLE)
) )
target_link_libraries(${SUB_PROJ_NAME} PRIVATE target_link_libraries(${SUB_PROJ_NAME} PRIVATE
${GTK3_LIBRARIES} ${GTK3_LIBRARIES}
X11::X11 X11::xcb
) )
target_include_directories(${SUB_PROJ_NAME} PRIVATE target_include_directories(${SUB_PROJ_NAME} PRIVATE
${GTK3_INCLUDE_DIRS} ${GTK3_INCLUDE_DIRS}

View File

@ -28,7 +28,7 @@
#include <QtCore/qregularexpression.h> #include <QtCore/qregularexpression.h>
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
#include <gtk/gtk.h> #include <gtk/gtk.h>
#include <X11/Xlib.h> #include <xcb/xcb.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -113,6 +113,33 @@ template<typename T>
return -1; return -1;
} }
static inline void
emulateMouseButtonRelease(const WId windowId, const QPoint &globalPos, const QPoint &localPos)
{
Q_ASSERT(windowId);
if (!windowId) {
return;
}
xcb_connection_t * const connection = QX11Info::connection();
Q_ASSERT(connection);
const quint32 rootWindow = QX11Info::appRootWindow(QX11Info::appScreen());
Q_ASSERT(rootWindow);
xcb_button_release_event_t xev;
memset(&xev, 0, sizeof(xev));
xev.response_type = XCB_BUTTON_RELEASE;
xev.time = XCB_CURRENT_TIME;
xev.root = rootWindow;
xev.root_x = globalPos.x();
xev.root_y = globalPos.y();
xev.event = windowId;
xev.event_x = localPos.x();
xev.event_y = localPos.y();
xev.same_screen = true;
xcb_send_event(connection, false, rootWindow, XCB_EVENT_MASK_BUTTON_RELEASE,
reinterpret_cast<const char *>(&xev));
xcb_flush(connection);
}
[[maybe_unused]] static inline void [[maybe_unused]] static inline void
doStartSystemMoveResize(const WId windowId, const QPoint &globalPos, const int edges) doStartSystemMoveResize(const WId windowId, const QPoint &globalPos, const int edges)
{ {
@ -123,7 +150,7 @@ template<typename T>
} }
xcb_connection_t * const connection = QX11Info::connection(); xcb_connection_t * const connection = QX11Info::connection();
Q_ASSERT(connection); Q_ASSERT(connection);
static const xcb_atom_t moveResize = [connection]() -> xcb_atom_t { static const xcb_atom_t netMoveResize = [connection]() -> xcb_atom_t {
const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, false, const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(connection, false,
qstrlen(WM_MOVERESIZE_OPERATION_NAME), WM_MOVERESIZE_OPERATION_NAME); qstrlen(WM_MOVERESIZE_OPERATION_NAME), WM_MOVERESIZE_OPERATION_NAME);
xcb_intern_atom_reply_t * const reply = xcb_intern_atom_reply(connection, cookie, nullptr); xcb_intern_atom_reply_t * const reply = xcb_intern_atom_reply(connection, cookie, nullptr);
@ -138,17 +165,20 @@ template<typename T>
xcb_client_message_event_t xev; xcb_client_message_event_t xev;
memset(&xev, 0, sizeof(xev)); memset(&xev, 0, sizeof(xev));
xev.response_type = XCB_CLIENT_MESSAGE; xev.response_type = XCB_CLIENT_MESSAGE;
xev.type = moveResize; xev.type = netMoveResize;
xev.window = windowId; xev.window = windowId;
xev.format = 32; xev.format = 32;
xev.data.data32[0] = globalPos.x(); xev.data.data32[0] = globalPos.x();
xev.data.data32[1] = globalPos.y(); xev.data.data32[1] = globalPos.y();
xev.data.data32[2] = edges; xev.data.data32[2] = edges;
xev.data.data32[3] = XCB_BUTTON_INDEX_1; xev.data.data32[3] = XCB_BUTTON_INDEX_1;
// First we need to ungrab the pointer that may have been
// automatically grabbed by Qt on ButtonPressEvent.
xcb_ungrab_pointer(connection, XCB_CURRENT_TIME); xcb_ungrab_pointer(connection, XCB_CURRENT_TIME);
xcb_send_event(connection, false, rootWindow, xcb_send_event(connection, false, rootWindow,
(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY), (XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY),
reinterpret_cast<const char *>(&xev)); reinterpret_cast<const char *>(&xev));
xcb_flush(connection);
} }
SystemTheme Utils::getSystemTheme() SystemTheme Utils::getSystemTheme()
@ -163,15 +193,17 @@ void Utils::startSystemMove(QWindow *window, const QPoint &globalPos)
if (!window) { if (!window) {
return; return;
} }
const WId windowId = window->winId();
const qreal dpr = window->devicePixelRatio();
const QPoint deviceGlobalPos = QPointF(QPointF(globalPos) * dpr).toPoint();
const QPoint logicalLocalPos = window->mapFromGlobal(globalPos);
const QPoint deviceLocalPos = QPointF(QPointF(logicalLocalPos) * dpr).toPoint();
// Before we start the dragging we need to tell Qt that the mouse is released.
emulateMouseButtonRelease(windowId, deviceGlobalPos, deviceLocalPos);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
Q_UNUSED(globalPos);
window->startSystemMove(); window->startSystemMove();
#else #else
// Qt always gives us logical coordinates, however, the native APIs doStartSystemMoveResize(windowId, deviceGlobalPos, _NET_WM_MOVERESIZE_MOVE);
// are expecting device coordinates.
const qreal dpr = window->devicePixelRatio();
const QPoint globalPos2 = QPointF(QPointF(globalPos) * dpr).toPoint();
doStartSystemMoveResize(window->winId(), globalPos2, _NET_WM_MOVERESIZE_MOVE);
#endif #endif
} }
@ -184,19 +216,21 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi
if (edges == Qt::Edges{}) { if (edges == Qt::Edges{}) {
return; return;
} }
const WId windowId = window->winId();
const qreal dpr = window->devicePixelRatio();
const QPoint deviceGlobalPos = QPointF(QPointF(globalPos) * dpr).toPoint();
const QPoint logicalLocalPos = window->mapFromGlobal(globalPos);
const QPoint deviceLocalPos = QPointF(QPointF(logicalLocalPos) * dpr).toPoint();
// Before we start the resizing we need to tell Qt that the mouse is released.
emulateMouseButtonRelease(windowId, deviceGlobalPos, deviceLocalPos);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
Q_UNUSED(globalPos);
window->startSystemResize(edges); window->startSystemResize(edges);
#else #else
const int section = qtEdgesToWmMoveOrResizeOperation(edges); const int section = qtEdgesToWmMoveOrResizeOperation(edges);
if (section < 0) { if (section < 0) {
return; return;
} }
// Qt always gives us logical coordinates, however, the native APIs doStartSystemMoveResize(windowId, deviceGlobalPos, section);
// are expecting device coordinates.
const qreal dpr = window->devicePixelRatio();
const QPoint globalPos2 = QPointF(QPointF(globalPos) * dpr).toPoint();
doStartSystemMoveResize(window->winId(), globalPos2, section);
#endif #endif
} }

View File

@ -209,8 +209,10 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi
} }
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
Q_UNUSED(globalPos); Q_UNUSED(globalPos);
// Actually Qt doesn't implement this function, it will do nothing and always returns false.
window->startSystemResize(edges); window->startSystemResize(edges);
#else #else
// ### TODO
Q_UNUSED(globalPos); Q_UNUSED(globalPos);
#endif #endif
} }

View File

@ -342,6 +342,7 @@ void FramelessQuickWindowPrivate::setOptions(const QuickGlobal::Options value)
if (m_quickOptions == value) { if (m_quickOptions == value) {
return; return;
} }
// ### TODO: re-evaluate some usable options.
m_quickOptions = value; m_quickOptions = value;
m_settings.options = optionsQuickToCore(m_quickOptions); m_settings.options = optionsQuickToCore(m_quickOptions);
Q_EMIT q->optionsChanged(); Q_EMIT q->optionsChanged();
@ -367,17 +368,14 @@ bool FramelessQuickWindowPrivate::eventFilter(QObject *object, QEvent *event)
const auto showEvent = static_cast<QShowEvent *>(event); const auto showEvent = static_cast<QShowEvent *>(event);
showEventHandler(showEvent); showEventHandler(showEvent);
} break; } break;
#ifdef Q_OS_WINDOWS
case QEvent::MouseMove: { case QEvent::MouseMove: {
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
mouseMoveEventHandler(mouseEvent); mouseMoveEventHandler(mouseEvent);
} break; } break;
#else
case QEvent::MouseButtonPress: { case QEvent::MouseButtonPress: {
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
mousePressEventHandler(mouseEvent); mousePressEventHandler(mouseEvent);
} break; } break;
#endif
case QEvent::MouseButtonRelease: { case QEvent::MouseButtonRelease: {
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
mouseReleaseEventHandler(mouseEvent); mouseReleaseEventHandler(mouseEvent);
@ -640,31 +638,6 @@ bool FramelessQuickWindowPrivate::shouldIgnoreMouseEvents(const QPoint &pos) con
return (isNormal() && withinFrameBorder); return (isNormal() && withinFrameBorder);
} }
void FramelessQuickWindowPrivate::doStartSystemMove2(QMouseEvent *event)
{
Q_ASSERT(event);
if (!event) {
return;
}
if (m_settings.options & Option::DisableDragging) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->scenePosition().toPoint();
const QPoint globalPos = event->globalPosition().toPoint();
#else
const QPoint scenePos = event->windowPos().toPoint();
const QPoint globalPos = event->screenPos().toPoint();
#endif
if (shouldIgnoreMouseEvents(scenePos)) {
return;
}
if (!isInTitleBarDraggableArea(scenePos)) {
return;
}
startSystemMove2(globalPos);
}
bool FramelessQuickWindowPrivate::shouldDrawFrameBorder() const bool FramelessQuickWindowPrivate::shouldDrawFrameBorder() const
{ {
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
@ -702,28 +675,41 @@ void FramelessQuickWindowPrivate::showEventHandler(QShowEvent *event)
void FramelessQuickWindowPrivate::mouseMoveEventHandler(QMouseEvent *event) void FramelessQuickWindowPrivate::mouseMoveEventHandler(QMouseEvent *event)
{ {
#ifdef Q_OS_WINDOWS
Q_ASSERT(event); Q_ASSERT(event);
if (!event) { if (!event) {
return; return;
} }
doStartSystemMove2(event); if (!m_mouseLeftButtonPressed) {
return;
}
if (m_settings.options & Option::DisableDragging) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->scenePosition().toPoint();
const QPoint globalPos = event->globalPosition().toPoint();
#else #else
Q_UNUSED(event); const QPoint scenePos = event->windowPos().toPoint();
const QPoint globalPos = event->screenPos().toPoint();
#endif #endif
if (shouldIgnoreMouseEvents(scenePos)) {
return;
}
if (!isInTitleBarDraggableArea(scenePos)) {
return;
}
startSystemMove2(globalPos);
} }
void FramelessQuickWindowPrivate::mousePressEventHandler(QMouseEvent *event) void FramelessQuickWindowPrivate::mousePressEventHandler(QMouseEvent *event)
{ {
#ifdef Q_OS_WINDOWS
Q_UNUSED(event);
#else
Q_ASSERT(event); Q_ASSERT(event);
if (!event) { if (!event) {
return; return;
} }
doStartSystemMove2(event); if (event->button() == Qt::LeftButton) {
#endif m_mouseLeftButtonPressed = true;
}
} }
void FramelessQuickWindowPrivate::mouseReleaseEventHandler(QMouseEvent *event) void FramelessQuickWindowPrivate::mouseReleaseEventHandler(QMouseEvent *event)
@ -732,10 +718,14 @@ void FramelessQuickWindowPrivate::mouseReleaseEventHandler(QMouseEvent *event)
if (!event) { if (!event) {
return; return;
} }
if (m_settings.options & Option::DisableSystemMenu) { const Qt::MouseButton button = event->button();
if (button == Qt::LeftButton) {
m_mouseLeftButtonPressed = false;
}
if (button != Qt::RightButton) {
return; return;
} }
if (event->button() != Qt::RightButton) { if (m_settings.options & Option::DisableSystemMenu) {
return; return;
} }
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))

View File

@ -97,7 +97,6 @@ private:
Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const; Q_NODISCARD bool isInSystemButtons(const QPoint &pos, QuickGlobal::SystemButtonType *button) const;
Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const;
Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const; Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const;
void doStartSystemMove2(QMouseEvent *event);
Q_NODISCARD bool shouldDrawFrameBorder() const; Q_NODISCARD bool shouldDrawFrameBorder() const;
private Q_SLOTS: private Q_SLOTS:
@ -116,6 +115,7 @@ private:
QPointer<QQuickItem> m_titleBarItem = nullptr; QPointer<QQuickItem> m_titleBarItem = nullptr;
QList<QQuickItem *> m_hitTestVisibleItems = {}; QList<QQuickItem *> m_hitTestVisibleItems = {};
QuickGlobal::Options m_quickOptions = {}; QuickGlobal::Options m_quickOptions = {};
bool m_mouseLeftButtonPressed = false;
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE

View File

@ -146,6 +146,9 @@ void QuickStandardTitleBar::updateTitleBarColor()
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
backgroundColor = Utils::getDwmColorizationColor(); backgroundColor = Utils::getDwmColorizationColor();
#endif #endif
#ifdef Q_OS_LINUX
backgroundColor = Utils::getWmThemeColor();
#endif
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
backgroundColor = Utils::getControlsAccentColor(); backgroundColor = Utils::getControlsAccentColor();
#endif #endif

View File

@ -277,28 +277,41 @@ void FramelessWidgetsHelper::paintEventHandler(QPaintEvent *event)
void FramelessWidgetsHelper::mouseMoveEventHandler(QMouseEvent *event) void FramelessWidgetsHelper::mouseMoveEventHandler(QMouseEvent *event)
{ {
#ifdef Q_OS_WINDOWS
Q_ASSERT(event); Q_ASSERT(event);
if (!event) { if (!event) {
return; return;
} }
doStartSystemMove2(event); if (!m_mouseLeftButtonPressed) {
return;
}
if (m_settings.options & Option::DisableDragging) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->scenePosition().toPoint();
const QPoint globalPos = event->globalPosition().toPoint();
#else #else
Q_UNUSED(event); const QPoint scenePos = event->windowPos().toPoint();
const QPoint globalPos = event->screenPos().toPoint();
#endif #endif
if (shouldIgnoreMouseEvents(scenePos)) {
return;
}
if (!isInTitleBarDraggableArea(scenePos)) {
return;
}
startSystemMove2(globalPos);
} }
void FramelessWidgetsHelper::mousePressEventHandler(QMouseEvent *event) void FramelessWidgetsHelper::mousePressEventHandler(QMouseEvent *event)
{ {
#ifdef Q_OS_WINDOWS
Q_UNUSED(event);
#else
Q_ASSERT(event); Q_ASSERT(event);
if (!event) { if (!event) {
return; return;
} }
doStartSystemMove2(event); if (event->button() == Qt::LeftButton) {
#endif m_mouseLeftButtonPressed = true;
}
} }
void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event) void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event)
@ -307,10 +320,14 @@ void FramelessWidgetsHelper::mouseReleaseEventHandler(QMouseEvent *event)
if (!event) { if (!event) {
return; return;
} }
if (m_settings.options & Option::DisableSystemMenu) { const Qt::MouseButton button = event->button();
if (button == Qt::LeftButton) {
m_mouseLeftButtonPressed = false;
}
if (button != Qt::RightButton) {
return; return;
} }
if (event->button() != Qt::RightButton) { if (m_settings.options & Option::DisableSystemMenu) {
return; return;
} }
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
@ -667,31 +684,6 @@ bool FramelessWidgetsHelper::shouldIgnoreMouseEvents(const QPoint &pos) const
return (isNormal() && withinFrameBorder); return (isNormal() && withinFrameBorder);
} }
void FramelessWidgetsHelper::doStartSystemMove2(QMouseEvent *event)
{
Q_ASSERT(event);
if (!event) {
return;
}
if (m_settings.options & Option::DisableDragging) {
return;
}
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const QPoint scenePos = event->scenePosition().toPoint();
const QPoint globalPos = event->globalPosition().toPoint();
#else
const QPoint scenePos = event->windowPos().toPoint();
const QPoint globalPos = event->screenPos().toPoint();
#endif
if (shouldIgnoreMouseEvents(scenePos)) {
return;
}
if (!isInTitleBarDraggableArea(scenePos)) {
return;
}
startSystemMove2(globalPos);
}
void FramelessWidgetsHelper::updateContentsMargins() void FramelessWidgetsHelper::updateContentsMargins()
{ {
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
@ -706,29 +698,26 @@ void FramelessWidgetsHelper::updateSystemTitleBarStyleSheet()
} }
const bool active = q->isActiveWindow(); const bool active = q->isActiveWindow();
const bool dark = Utils::shouldAppsUseDarkMode(); const bool dark = Utils::shouldAppsUseDarkMode();
#ifdef Q_OS_WINDOWS
const bool colorizedTitleBar = Utils::isTitleBarColorized(); const bool colorizedTitleBar = Utils::isTitleBarColorized();
#else
constexpr const bool colorizedTitleBar = false;
#endif
const QColor systemTitleBarWidgetBackgroundColor = [active, colorizedTitleBar, dark]() -> QColor { const QColor systemTitleBarWidgetBackgroundColor = [active, colorizedTitleBar, dark]() -> QColor {
#ifndef Q_OS_WINDOWS
Q_UNUSED(colorizedTitleBar);
#endif
if (active) { if (active) {
#ifdef Q_OS_WINDOWS
if (colorizedTitleBar) { if (colorizedTitleBar) {
#ifdef Q_OS_WINDOWS
return Utils::getDwmColorizationColor(); return Utils::getDwmColorizationColor();
} else {
#endif #endif
#ifdef Q_OS_LINUX
return Utils::getWmThemeColor();
#endif
#ifdef Q_OS_MACOS
return Utils::getControlsAccentColor();
#endif
} else {
if (dark) { if (dark) {
return kDefaultBlackColor; return kDefaultBlackColor;
} else { } else {
return kDefaultWhiteColor; return kDefaultWhiteColor;
} }
#ifdef Q_OS_WINDOWS
} }
#endif
} else { } else {
if (dark) { if (dark) {
return kDefaultSystemDarkColor; return kDefaultSystemDarkColor;
@ -841,17 +830,14 @@ bool FramelessWidgetsHelper::eventFilter(QObject *object, QEvent *event)
const auto paintEvent = static_cast<QPaintEvent *>(event); const auto paintEvent = static_cast<QPaintEvent *>(event);
paintEventHandler(paintEvent); paintEventHandler(paintEvent);
} break; } break;
#ifdef Q_OS_WINDOWS
case QEvent::MouseMove: { case QEvent::MouseMove: {
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
mouseMoveEventHandler(mouseEvent); mouseMoveEventHandler(mouseEvent);
} break; } break;
#else
case QEvent::MouseButtonPress: { case QEvent::MouseButtonPress: {
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
mousePressEventHandler(mouseEvent); mousePressEventHandler(mouseEvent);
} break; } break;
#endif
case QEvent::MouseButtonRelease: { case QEvent::MouseButtonRelease: {
const auto mouseEvent = static_cast<QMouseEvent *>(event); const auto mouseEvent = static_cast<QMouseEvent *>(event);
mouseReleaseEventHandler(mouseEvent); mouseReleaseEventHandler(mouseEvent);

View File

@ -94,7 +94,6 @@ private:
Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const; Q_NODISCARD bool isInTitleBarDraggableArea(const QPoint &pos) const;
Q_NODISCARD bool shouldDrawFrameBorder() const; Q_NODISCARD bool shouldDrawFrameBorder() const;
Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const; Q_NODISCARD bool shouldIgnoreMouseEvents(const QPoint &pos) const;
void doStartSystemMove2(QMouseEvent *event);
private Q_SLOTS: private Q_SLOTS:
void updateContentsMargins(); void updateContentsMargins();
@ -119,6 +118,7 @@ private:
Global::UserSettings m_settings = {}; Global::UserSettings m_settings = {};
Global::SystemParameters m_params = {}; Global::SystemParameters m_params = {};
bool m_windowExposed = false; bool m_windowExposed = false;
bool m_mouseLeftButtonPressed = false;
}; };
FRAMELESSHELPER_END_NAMESPACE FRAMELESSHELPER_END_NAMESPACE