diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index e686593e..b346820b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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} diff --git a/src/core/utils_linux.cpp b/src/core/utils_linux.cpp index 6e42c088..e99fdbef 100644 --- a/src/core/utils_linux.cpp +++ b/src/core/utils_linux.cpp @@ -28,7 +28,7 @@ #include #include #include -#include +#include FRAMELESSHELPER_BEGIN_NAMESPACE @@ -113,6 +113,33 @@ template 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(&xev)); + xcb_flush(connection); +} + [[maybe_unused]] static inline void doStartSystemMoveResize(const WId windowId, const QPoint &globalPos, const int edges) { @@ -123,7 +150,7 @@ template } 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 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(&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 } diff --git a/src/core/utils_mac.mm b/src/core/utils_mac.mm index eee2d227..a49c16c9 100644 --- a/src/core/utils_mac.mm +++ b/src/core/utils_mac.mm @@ -209,8 +209,10 @@ void setSystemTitleBarVisible(const bool visible) } #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 } diff --git a/src/quick/framelessquickwindow.cpp b/src/quick/framelessquickwindow.cpp index 8bb02170..b0df6a56 100644 --- a/src/quick/framelessquickwindow.cpp +++ b/src/quick/framelessquickwindow.cpp @@ -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(event); showEventHandler(showEvent); } break; -#ifdef Q_OS_WINDOWS case QEvent::MouseMove: { const auto mouseEvent = static_cast(event); mouseMoveEventHandler(mouseEvent); } break; -#else case QEvent::MouseButtonPress: { const auto mouseEvent = static_cast(event); mousePressEventHandler(mouseEvent); } break; -#endif case QEvent::MouseButtonRelease: { const auto mouseEvent = static_cast(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)) diff --git a/src/quick/framelessquickwindow_p.h b/src/quick/framelessquickwindow_p.h index 3ea6af17..daad3400 100644 --- a/src/quick/framelessquickwindow_p.h +++ b/src/quick/framelessquickwindow_p.h @@ -97,7 +97,6 @@ public Q_SLOTS: 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 Q_SLOTS: QPointer m_titleBarItem = nullptr; QList m_hitTestVisibleItems = {}; QuickGlobal::Options m_quickOptions = {}; + bool m_mouseLeftButtonPressed = false; }; FRAMELESSHELPER_END_NAMESPACE diff --git a/src/quick/quickstandardtitlebar.cpp b/src/quick/quickstandardtitlebar.cpp index b0feee0f..96cc4c8c 100644 --- a/src/quick/quickstandardtitlebar.cpp +++ b/src/quick/quickstandardtitlebar.cpp @@ -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 diff --git a/src/widgets/framelesswidgetshelper.cpp b/src/widgets/framelesswidgetshelper.cpp index b4fc2ee5..e11b27b1 100644 --- a/src/widgets/framelesswidgetshelper.cpp +++ b/src/widgets/framelesswidgetshelper.cpp @@ -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(event); paintEventHandler(paintEvent); } break; -#ifdef Q_OS_WINDOWS case QEvent::MouseMove: { const auto mouseEvent = static_cast(event); mouseMoveEventHandler(mouseEvent); } break; -#else case QEvent::MouseButtonPress: { const auto mouseEvent = static_cast(event); mousePressEventHandler(mouseEvent); } break; -#endif case QEvent::MouseButtonRelease: { const auto mouseEvent = static_cast(event); mouseReleaseEventHandler(mouseEvent); diff --git a/src/widgets/framelesswidgetshelper_p.h b/src/widgets/framelesswidgetshelper_p.h index 40fe96c8..a5269786 100644 --- a/src/widgets/framelesswidgetshelper_p.h +++ b/src/widgets/framelesswidgetshelper_p.h @@ -94,7 +94,6 @@ public Q_SLOTS: 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 Q_SLOTS: Global::UserSettings m_settings = {}; Global::SystemParameters m_params = {}; bool m_windowExposed = false; + bool m_mouseLeftButtonPressed = false; }; FRAMELESSHELPER_END_NAMESPACE