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
${GTK3_LIBRARIES}
X11::X11
X11::xcb
)
target_include_directories(${SUB_PROJ_NAME} PRIVATE
${GTK3_INCLUDE_DIRS}

View File

@ -28,7 +28,7 @@
#include <QtCore/qregularexpression.h>
#include <QtGui/qwindow.h>
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <xcb/xcb.h>
FRAMELESSHELPER_BEGIN_NAMESPACE
@ -113,6 +113,33 @@ template<typename T>
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
doStartSystemMoveResize(const WId windowId, const QPoint &globalPos, const int edges)
{
@ -123,7 +150,7 @@ template<typename T>
}
xcb_connection_t * const connection = QX11Info::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,
qstrlen(WM_MOVERESIZE_OPERATION_NAME), WM_MOVERESIZE_OPERATION_NAME);
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;
memset(&xev, 0, sizeof(xev));
xev.response_type = XCB_CLIENT_MESSAGE;
xev.type = moveResize;
xev.type = netMoveResize;
xev.window = windowId;
xev.format = 32;
xev.data.data32[0] = globalPos.x();
xev.data.data32[1] = globalPos.y();
xev.data.data32[2] = edges;
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_send_event(connection, false, rootWindow,
(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY),
reinterpret_cast<const char *>(&xev));
xcb_flush(connection);
}
SystemTheme Utils::getSystemTheme()
@ -163,15 +193,17 @@ void Utils::startSystemMove(QWindow *window, const QPoint &globalPos)
if (!window) {
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))
Q_UNUSED(globalPos);
window->startSystemMove();
#else
// Qt always gives us logical coordinates, however, the native APIs
// 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);
doStartSystemMoveResize(windowId, deviceGlobalPos, _NET_WM_MOVERESIZE_MOVE);
#endif
}
@ -184,19 +216,21 @@ void Utils::startSystemResize(QWindow *window, const Qt::Edges edges, const QPoi
if (edges == Qt::Edges{}) {
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))
Q_UNUSED(globalPos);
window->startSystemResize(edges);
#else
const int section = qtEdgesToWmMoveOrResizeOperation(edges);
if (section < 0) {
return;
}
// Qt always gives us logical coordinates, however, the native APIs
// are expecting device coordinates.
const qreal dpr = window->devicePixelRatio();
const QPoint globalPos2 = QPointF(QPointF(globalPos) * dpr).toPoint();
doStartSystemMoveResize(window->winId(), globalPos2, section);
doStartSystemMoveResize(windowId, deviceGlobalPos, section);
#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))
Q_UNUSED(globalPos);
// Actually Qt doesn't implement this function, it will do nothing and always returns false.
window->startSystemResize(edges);
#else
// ### TODO
Q_UNUSED(globalPos);
#endif
}

View File

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

View File

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

View File

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

View File

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