From 1e2598398d5e80e714baaa3ff0bb6d026c7e4ccb Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 13 Sep 2021 12:22:09 +0800 Subject: [PATCH 01/16] backbone of new FramelessHelper --- framelesshelper.cpp | 212 +++++++++++--------------------------------- framelesshelper.h | 32 ++++++- 2 files changed, 80 insertions(+), 164 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index dca059b..e139fa4 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -34,177 +34,67 @@ FRAMELESSHELPER_BEGIN_NAMESPACE -FramelessHelper::FramelessHelper(QObject *parent) : QObject(parent) {} - -void FramelessHelper::removeWindowFrame(QWindow *window) +FramelessHelper::FramelessHelper(QWindow *window) + : QObject(window) + , m_window(window) { - Q_ASSERT(window); - if (!window) { - return; - } - window->setFlags(window->flags() | Qt::FramelessWindowHint); - window->installEventFilter(this); - window->setProperty(Constants::kFramelessModeFlag, true); + Q_ASSERT(window != nullptr && window->isTopLevel()); } -void FramelessHelper::bringBackWindowFrame(QWindow *window) +/*! + Setup the window, make it frameless. + */ +void FramelessHelper::install() { - Q_ASSERT(window); - if (!window) { + QRect origRect = m_window->geometry(); + + resizeWindow(origRect.size()); +} + +/*! + Restore the window to its original state + */ +void FramelessHelper::uninstall() +{ + resizeWindow(QSize()); +} + +/*! + Resize non-client area + */ +void FramelessHelper::resizeWindow(const QSize& windowSize) +{ + if (windowSize == this->windowSize()) return; - } - window->removeEventFilter(this); - window->setFlags(window->flags() & ~Qt::FramelessWindowHint); - window->setProperty(Constants::kFramelessModeFlag, false); + + setWindowSize(windowSize); +} + +QRect FramelessHelper::titleBarRect() +{ + return QRect(0, 0, windowSize().width(), titleBarHeight()); +} + +QRect FramelessHelper::clientRect() +{ + QRect rect(0, 0, windowSize().width(), windowSize().height()); + rect = rect.adjusted( + resizeBorderThickness(), titleBarHeight(), + -resizeBorderThickness(), -resizeBorderThickness() + ); + return rect; +} + +QRegion FramelessHelper::nonClientRegion() +{ + QRegion region(QRect(QPoint(0, 0), windowSize())); + region -= clientRect(); + return region; } bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { - Q_ASSERT(object); - Q_ASSERT(event); - if (!object || !event) { - return false; - } - // Only monitor window events. - if (!object->isWindowType()) { - return false; - } - const QEvent::Type type = event->type(); - // We are only interested in mouse events. - if ((type != QEvent::MouseButtonDblClick) && (type != QEvent::MouseButtonPress) - && (type != QEvent::MouseMove)) { - return false; - } - const auto window = qobject_cast(object); - const int resizeBorderThickness = FramelessWindowsManager::getResizeBorderThickness(window); - const int titleBarHeight = FramelessWindowsManager::getTitleBarHeight(window); - const bool resizable = FramelessWindowsManager::getResizable(window); - const int windowWidth = window->width(); - const auto mouseEvent = static_cast(event); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) - const QPoint localMousePosition = mouseEvent->position().toPoint(); -#else - const QPoint localMousePosition = mouseEvent->windowPos().toPoint(); -#endif - const Qt::Edges edges = [window, resizeBorderThickness, windowWidth, &localMousePosition] { - const int windowHeight = window->height(); - if (localMousePosition.y() <= resizeBorderThickness) { - if (localMousePosition.x() <= resizeBorderThickness) { - return Qt::TopEdge | Qt::LeftEdge; - } - if (localMousePosition.x() >= (windowWidth - resizeBorderThickness)) { - return Qt::TopEdge | Qt::RightEdge; - } - return Qt::Edges{Qt::TopEdge}; - } - if (localMousePosition.y() >= (windowHeight - resizeBorderThickness)) { - if (localMousePosition.x() <= resizeBorderThickness) { - return Qt::BottomEdge | Qt::LeftEdge; - } - if (localMousePosition.x() >= (windowWidth - resizeBorderThickness)) { - return Qt::BottomEdge | Qt::RightEdge; - } - return Qt::Edges{Qt::BottomEdge}; - } - if (localMousePosition.x() <= resizeBorderThickness) { - return Qt::Edges{Qt::LeftEdge}; - } - if (localMousePosition.x() >= (windowWidth - resizeBorderThickness)) { - return Qt::Edges{Qt::RightEdge}; - } - return Qt::Edges{}; - } (); - const bool hitTestVisible = Utilities::isHitTestVisibleInChrome(window); - bool isInTitlebarArea = false; - if ((window->windowState() == Qt::WindowMaximized) - || (window->windowState() == Qt::WindowFullScreen)) { - isInTitlebarArea = (localMousePosition.y() >= 0) - && (localMousePosition.y() <= titleBarHeight) - && (localMousePosition.x() >= 0) - && (localMousePosition.x() <= windowWidth) - && !hitTestVisible; - } - if (window->windowState() == Qt::WindowNoState) { - isInTitlebarArea = (localMousePosition.y() > resizeBorderThickness) - && (localMousePosition.y() <= titleBarHeight) - && (localMousePosition.x() > resizeBorderThickness) - && (localMousePosition.x() < (windowWidth - resizeBorderThickness)) - && !hitTestVisible; - } - // Determine if the mouse click occurred in the title bar - static bool titlebarClicked = false; - if (type == QEvent::MouseButtonPress) { - if (isInTitlebarArea) - titlebarClicked = true; - else - titlebarClicked = false; - } - - if (type == QEvent::MouseButtonDblClick) { - if (mouseEvent->button() != Qt::MouseButton::LeftButton) { - return false; - } - if (isInTitlebarArea) { - if (window->windowState() == Qt::WindowState::WindowFullScreen) { - return false; - } - if (window->windowState() == Qt::WindowState::WindowMaximized) { - window->showNormal(); - } else { - window->showMaximized(); - } - window->setCursor(Qt::ArrowCursor); - } - } else if (type == QEvent::MouseMove) { - // Display resize indicators - static bool cursorChanged = false; - if ((window->windowState() == Qt::WindowState::WindowNoState) && resizable) { - if (((edges & Qt::TopEdge) && (edges & Qt::LeftEdge)) - || ((edges & Qt::BottomEdge) && (edges & Qt::RightEdge))) { - window->setCursor(Qt::SizeFDiagCursor); - cursorChanged = true; - } else if (((edges & Qt::TopEdge) && (edges & Qt::RightEdge)) - || ((edges & Qt::BottomEdge) && (edges & Qt::LeftEdge))) { - window->setCursor(Qt::SizeBDiagCursor); - cursorChanged = true; - } else if ((edges & Qt::TopEdge) || (edges & Qt::BottomEdge)) { - window->setCursor(Qt::SizeVerCursor); - cursorChanged = true; - } else if ((edges & Qt::LeftEdge) || (edges & Qt::RightEdge)) { - window->setCursor(Qt::SizeHorCursor); - cursorChanged = true; - } else { - if (cursorChanged) { - window->setCursor(Qt::ArrowCursor); - cursorChanged = false; - } - } - } - - if ((mouseEvent->buttons() & Qt::LeftButton) && titlebarClicked) { - if (edges == Qt::Edges{}) { - if (isInTitlebarArea) { - if (!window->startSystemMove()) { - // ### FIXME: TO BE IMPLEMENTED! - qWarning() << "Current OS doesn't support QWindow::startSystemMove()."; - } - } - } - } - - } else if (type == QEvent::MouseButtonPress) { - if (edges != Qt::Edges{}) { - if ((window->windowState() == Qt::WindowState::WindowNoState) && !hitTestVisible && resizable) { - if (!window->startSystemResize(edges)) { - // ### FIXME: TO BE IMPLEMENTED! - qWarning() << "Current OS doesn't support QWindow::startSystemResize()."; - } - } - } - } - - return false; } FRAMELESSHELPER_END_NAMESPACE diff --git a/framelesshelper.h b/framelesshelper.h index 5b7dd86..b923f56 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -42,14 +42,40 @@ class FRAMELESSHELPER_API FramelessHelper : public QObject Q_DISABLE_COPY_MOVE(FramelessHelper) public: - explicit FramelessHelper(QObject *parent = nullptr); + explicit FramelessHelper(QWindow *window); ~FramelessHelper() override = default; - void removeWindowFrame(QWindow *window); - void bringBackWindowFrame(QWindow *window); + void install(); + void uninstall(); + + QWindow *window() { return m_window; } + + QSize windowSize() { return m_windowSize; } + void setWindowSize(const QSize& size) { m_windowSize = size; } + void resizeWindow(const QSize& windowSize); + + int titleBarHeight() { return m_titleBarHeight; } + int setTitleBarHeight(int height) { m_titleBarHeight = height; } + QRect titleBarRect(); + + int resizeBorderThickness() { return m_resizeBorderThickness; } + void setResizeBorderThickness(int thickness) { m_resizeBorderThickness = thickness; } + + bool resizable() { return m_resizable; } + void setResizable(bool resizable) { m_resizable = resizable; } + + QRect clientRect(); + QRegion nonClientRegion(); protected: bool eventFilter(QObject *object, QEvent *event) override; + +private: + QWindow *m_window; + QSize m_windowSize; + int m_titleBarHeight; + int m_resizeBorderThickness; + bool m_resizable; }; FRAMELESSHELPER_END_NAMESPACE From 3784ef0e7faf8f0a7bff5108e8d6c2fb1abfa259 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 13 Sep 2021 19:59:35 +0800 Subject: [PATCH 02/16] map position to frame section --- framelesshelper.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++ framelesshelper.h | 4 +++ 2 files changed, 69 insertions(+) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index e139fa4..17f5e62 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -47,7 +47,15 @@ FramelessHelper::FramelessHelper(QWindow *window) void FramelessHelper::install() { QRect origRect = m_window->geometry(); + m_origWindowFlags = m_window->flags(); +#ifdef Q_OS_MAC + m_window->setFlags(Qt::Window); +#else + m_window->setFlags(m_origWindowFlags | Qt::FramelessWindowHint); +#endif + + m_window->setGeometry(origRect); resizeWindow(origRect.size()); } @@ -56,6 +64,8 @@ void FramelessHelper::install() */ void FramelessHelper::uninstall() { + m_window->setFlags(m_origWindowFlags); + m_origWindowFlags = Qt::WindowFlags(); resizeWindow(QSize()); } @@ -92,6 +102,61 @@ QRegion FramelessHelper::nonClientRegion() return region; } +bool FramelessHelper::isInTitlebarArea(const QPoint& pos) +{ + return nonClientRegion().contains(pos); +} + +Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) +{ + int border = 0; + + // TODO: get system default resize border + const int sysBorder = Utilities::getSystemMetric(window(), SystemMetric::ResizeBorderThickness, false); + + Qt::WindowStates states = window()->windowState(); + if (!(states & Qt::WindowMaximized) && !(states & Qt::WindowFullScreen)) + { + border = resizeBorderThickness(); + border = qMin(border, sysBorder); + } + + QRect windowRect(0, 0, windowSize().width(), windowSize().height()); + + if (windowRect.contains(pos)) + { + QPoint mappedPos = pos - windowRect.topLeft(); + if (QRect(0, 0, border, border).contains(mappedPos)) + return Qt::TopLeftSection; + + if (QRect(border, 0, windowRect.width() - border * 2, border).contains(mappedPos)) + return Qt::TopSection; + + if (QRect(windowRect.width() - border, 0, border, border).contains(mappedPos)) + return Qt::TopRightSection; + + if (QRect(windowRect.width() - border, border, border, windowRect.height() - border * 2).contains(mappedPos)) + return Qt::RightSection; + + if (QRect(windowRect.width() - border, windowRect.height() - border, border, border).contains(mappedPos)) + return Qt::BottomRightSection; + + if (QRect(border, windowRect.height() - border, windowRect.width() - border * 2, border).contains(mappedPos)) + return Qt::BottomSection; + + if (QRect(0, windowRect.height() - border, border, border).contains(mappedPos)) + return Qt::BottomLeftSection; + + if (QRect(0, border, border, windowRect.height() - border * 2).contains(mappedPos)) + return Qt::LeftSection; + + if (isInTitlebarArea(pos)) + return Qt::TitleBarArea; + } + + return Qt::NoSection; +} + bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { diff --git a/framelesshelper.h b/framelesshelper.h index b923f56..62904fb 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -67,6 +67,9 @@ public: QRect clientRect(); QRegion nonClientRegion(); + bool isInTitlebarArea(const QPoint& pos); + Qt::WindowFrameSection mapPosToFrameSection(const QPoint& pos); + protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -76,6 +79,7 @@ private: int m_titleBarHeight; int m_resizeBorderThickness; bool m_resizable; + Qt::WindowFlags m_origWindowFlags; }; FRAMELESSHELPER_END_NAMESPACE From da342753a2fb6d6dc8481f952d7dab73b24e2ee4 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Sun, 19 Sep 2021 17:47:14 +0800 Subject: [PATCH 03/16] change cursor according mouse state --- framelesshelper.cpp | 65 +++++++++++++++++++++++++++++++++++++++++++++ framelesshelper.h | 9 +++++++ 2 files changed, 74 insertions(+) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 17f5e62..34fa01a 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -157,6 +157,71 @@ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) return Qt::NoSection; } +bool FramelessHelper::isHoverResizeHandler() +{ + return m_hoveredFrameSection == Qt::LeftSection || + m_hoveredFrameSection == Qt::RightSection || + m_hoveredFrameSection == Qt::TopSection || + m_hoveredFrameSection == Qt::BottomSection || + m_hoveredFrameSection == Qt::TopLeftSection || + m_hoveredFrameSection == Qt::TopRightSection || + m_hoveredFrameSection == Qt::BottomLeftSection || + m_hoveredFrameSection == Qt::BottomRightSection; +} + +QCursor FramelessHelper::cursorForFrameSection(Qt::WindowFrameSection frameSection) +{ + Qt::CursorShape cursor = Qt::ArrowCursor; + + switch (frameSection) + { + case Qt::LeftSection: + case Qt::RightSection: + cursor = Qt::SizeHorCursor; + break; + case Qt::BottomSection: + case Qt::TopSection: + cursor = Qt::SizeVerCursor; + break; + case Qt::TopLeftSection: + case Qt::BottomRightSection: + cursor = Qt::SizeFDiagCursor; + break; + case Qt::TopRightSection: + case Qt::BottomLeftSection: + cursor = Qt::SizeBDiagCursor; + break; + case Qt::TitleBarArea: + cursor = Qt::ArrowCursor; + break; + default: + break; + } + + return QCursor(cursor); +} + +void FramelessHelper::setCursor(const QCursor& cursor) +{ + m_window->setCursor(cursor); + m_cursorChanged = true; +} + +void FramelessHelper::unsetCursor() +{ + if (!m_cursorChanged) + return; + + m_window->unsetCursor(); + m_cursorChanged = false; +} + +void FramelessHelper::updateCursor() +{ + if (isHoverResizeHandler()) + setCursor(cursorForFrameSection(m_hoveredFrameSection)); +} + bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { diff --git a/framelesshelper.h b/framelesshelper.h index 62904fb..12d4bfd 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -70,6 +70,13 @@ public: bool isInTitlebarArea(const QPoint& pos); Qt::WindowFrameSection mapPosToFrameSection(const QPoint& pos); + bool isHoverResizeHandler(); + + QCursor cursorForFrameSection(Qt::WindowFrameSection frameSection); + void setCursor(const QCursor& cursor); + void unsetCursor(); + void updateCursor(); + protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -80,6 +87,8 @@ private: int m_resizeBorderThickness; bool m_resizable; Qt::WindowFlags m_origWindowFlags; + bool m_cursorChanged; + Qt::WindowFrameSection m_hoveredFrameSection; }; FRAMELESSHELPER_END_NAMESPACE From 678f903f5c3adb5fb7e9e6e16a1f5e927316715b Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Sun, 19 Sep 2021 21:32:32 +0800 Subject: [PATCH 04/16] update hovered states --- framelesshelper.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ framelesshelper.h | 3 +++ 2 files changed, 44 insertions(+) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 34fa01a..4ee1ce8 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -57,6 +57,8 @@ void FramelessHelper::install() m_window->setGeometry(origRect); resizeWindow(origRect.size()); + + m_window->installEventFilter(this); } /*! @@ -67,6 +69,8 @@ void FramelessHelper::uninstall() m_window->setFlags(m_origWindowFlags); m_origWindowFlags = Qt::WindowFlags(); resizeWindow(QSize()); + + m_window->removeEventFilter(this); } /*! @@ -222,9 +226,46 @@ void FramelessHelper::updateCursor() setCursor(cursorForFrameSection(m_hoveredFrameSection)); } +void FramelessHelper::updateMouse(const QPoint& pos) +{ + updateHoverStates(pos); + updateCursor(); +} + +void FramelessHelper::updateHoverStates(const QPoint& pos) +{ + m_hoveredFrameSection = mapPosToFrameSection(pos); +} + bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { + if (object == m_window) { + switch (event->type()) + { + case QEvent::Resize: + { + QResizeEvent* re = static_cast(event); + resizeWindow(re->size()); + break; + } + case QEvent::NonClientAreaMouseMove: + case QEvent::MouseMove: + { + auto ev = static_cast(event); + updateMouse(ev->pos()); + break; + } + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::MouseButtonPress: + break; + case QEvent::NonClientAreaMouseButtonRelease: + case QEvent::MouseButtonRelease: + break; + default: + break; + } + } } FRAMELESSHELPER_END_NAMESPACE diff --git a/framelesshelper.h b/framelesshelper.h index 12d4bfd..62be8d3 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -77,6 +77,9 @@ public: void unsetCursor(); void updateCursor(); + void updateMouse(const QPoint& pos); + void updateHoverStates(const QPoint& pos); + protected: bool eventFilter(QObject *object, QEvent *event) override; From 93ac6f6ee3035e2217a5247e8ff18235988a8515 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Sun, 19 Sep 2021 22:24:00 +0800 Subject: [PATCH 05/16] minimal example that only depends on Core API --- examples/CMakeLists.txt | 1 + examples/minimal/CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++ examples/minimal/flwindow.cpp | 38 ++++++++++++++++++++++++++++++++ examples/minimal/flwindow.h | 17 ++++++++++++++ examples/minimal/main.cpp | 21 ++++++++++++++++++ framelesshelper.cpp | 9 +++++++- framelesshelper.h | 1 + framelesswindowsmanager.cpp | 6 ++--- 8 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 examples/minimal/CMakeLists.txt create mode 100644 examples/minimal/flwindow.cpp create mode 100644 examples/minimal/flwindow.h create mode 100644 examples/minimal/main.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d106427..db13084 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,6 +4,7 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets) if(TARGET Qt${QT_VERSION_MAJOR}::Widgets) add_subdirectory(widget) add_subdirectory(mainwindow) + add_subdirectory(minimal) endif() if(TARGET Qt${QT_VERSION_MAJOR}::Quick) diff --git a/examples/minimal/CMakeLists.txt b/examples/minimal/CMakeLists.txt new file mode 100644 index 0000000..4e846d3 --- /dev/null +++ b/examples/minimal/CMakeLists.txt @@ -0,0 +1,39 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets REQUIRED) +find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets REQUIRED) + +set(SOURCES + ../images.qrc + main.cpp + flwindow.h + flwindow.cpp +) + +if(WIN32) + enable_language(RC) + list(APPEND SOURCES ../example.rc ../example.manifest) +endif() + +add_executable(minimal WIN32 ${SOURCES}) + +target_link_libraries(minimal PRIVATE + Qt${QT_VERSION_MAJOR}::Widgets + wangwenx190::FramelessHelper +) + +target_compile_definitions(minimal PRIVATE + QT_NO_CAST_FROM_ASCII + QT_NO_CAST_TO_ASCII + QT_NO_KEYWORDS + QT_DEPRECATED_WARNINGS + QT_DISABLE_DEPRECATED_BEFORE=0x060100 +) + +if(WIN32) + target_link_libraries(minimal PRIVATE dwmapi) +endif() diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp new file mode 100644 index 0000000..137cf74 --- /dev/null +++ b/examples/minimal/flwindow.cpp @@ -0,0 +1,38 @@ +#include "flwindow.h" +#include "../../framelesshelper.h" + +FRAMELESSHELPER_USE_NAMESPACE + +FLWindow::FLWindow(QWidget *parent) : QWidget(parent) +{ + setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); + setStyleSheet(QString::fromLatin1("background:blue")); + + setAttribute(Qt::WA_ShowModal); + resize(500, 500); +} + +FLWindow::~FLWindow() +{ + +} + +void FLWindow::initFramelessWindow() +{ + FramelessHelper* helper = new FramelessHelper(windowHandle()); + helper->setResizeBorderThickness(8); + helper->setTitleBarHeight(20); + helper->setResizable(true); + helper->install(); +} + +void FLWindow::showEvent(QShowEvent *event) +{ + QWidget::showEvent(event); + + static bool inited = false; + if (!inited) { + inited = true; + initFramelessWindow(); + } +} \ No newline at end of file diff --git a/examples/minimal/flwindow.h b/examples/minimal/flwindow.h new file mode 100644 index 0000000..2fb26b0 --- /dev/null +++ b/examples/minimal/flwindow.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class FLWindow : public QWidget +{ + Q_OBJECT +public: + explicit FLWindow(QWidget *parent = nullptr); + ~FLWindow(); + +protected: + void showEvent(QShowEvent *event) override; + +private: + void initFramelessWindow(); +}; \ No newline at end of file diff --git a/examples/minimal/main.cpp b/examples/minimal/main.cpp new file mode 100644 index 0000000..d99e8ad --- /dev/null +++ b/examples/minimal/main.cpp @@ -0,0 +1,21 @@ +#include +#include "flwindow.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif +#endif + + QApplication application(argc, argv); + + FLWindow win; + win.show(); + + return QApplication::exec(); +} diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 4ee1ce8..d6d38e4 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -222,8 +222,11 @@ void FramelessHelper::unsetCursor() void FramelessHelper::updateCursor() { - if (isHoverResizeHandler()) + if (isHoverResizeHandler()) { setCursor(cursorForFrameSection(m_hoveredFrameSection)); + } else { + unsetCursor(); + } } void FramelessHelper::updateMouse(const QPoint& pos) @@ -239,6 +242,8 @@ void FramelessHelper::updateHoverStates(const QPoint& pos) bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { + bool filterOut = false; + if (object == m_window) { switch (event->type()) { @@ -266,6 +271,8 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) break; } } + + return filterOut; } FRAMELESSHELPER_END_NAMESPACE diff --git a/framelesshelper.h b/framelesshelper.h index 62be8d3..358f151 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -29,6 +29,7 @@ #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #include +#include QT_BEGIN_NAMESPACE QT_FORWARD_DECLARE_CLASS(QWindow) diff --git a/framelesswindowsmanager.cpp b/framelesswindowsmanager.cpp index 093e8f7..c2874b2 100644 --- a/framelesswindowsmanager.cpp +++ b/framelesswindowsmanager.cpp @@ -38,7 +38,7 @@ FRAMELESSHELPER_BEGIN_NAMESPACE #ifdef FRAMELESSHELPER_USE_UNIX_VERSION -Q_GLOBAL_STATIC(FramelessHelper, framelessHelperUnix) +//Q_GLOBAL_STATIC(FramelessHelper, framelessHelperUnix) #endif void FramelessWindowsManager::addWindow(QWindow *window) @@ -51,7 +51,7 @@ void FramelessWindowsManager::addWindow(QWindow *window) QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); } #ifdef FRAMELESSHELPER_USE_UNIX_VERSION - framelessHelperUnix()->removeWindowFrame(window); + //framelessHelperUnix()->removeWindowFrame(window); #else FramelessHelperWin::addFramelessWindow(window); // Work-around a Win32 multi-monitor bug. @@ -165,7 +165,7 @@ void FramelessWindowsManager::removeWindow(QWindow *window) return; } #ifdef FRAMELESSHELPER_USE_UNIX_VERSION - framelessHelperUnix()->bringBackWindowFrame(window); + //framelessHelperUnix()->bringBackWindowFrame(window); #else FramelessHelperWin::removeFramelessWindow(window); #endif From a365499455d82e8ab793fb68229ef93316ef568f Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 00:08:43 +0800 Subject: [PATCH 06/16] implemented linux x11 moving --- CMakeLists.txt | 9 ++++ examples/minimal/flwindow.cpp | 9 ++-- framelesshelper.cpp | 23 +++++++++++ framelesshelper.h | 3 ++ utilities.h | 5 +++ utilities_linux.cpp | 77 ++++++++++++++++++++++++++++++++++- 6 files changed, 121 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e66e8c5..faf6194 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ else() if(MACOS) list(APPEND SOURCES utilities_macos.mm) else() + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) list(APPEND SOURCES utilities_linux.cpp) endif() endif() @@ -87,6 +88,14 @@ if(WIN32) target_link_libraries(${PROJECT_NAME} PRIVATE dwmapi ) +else() + if(MACOS) + #TODO + else() + target_link_libraries(${PROJECT_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::X11Extras + ) + endif() endif() target_link_libraries(${PROJECT_NAME} PRIVATE diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp index 137cf74..39fbcc9 100644 --- a/examples/minimal/flwindow.cpp +++ b/examples/minimal/flwindow.cpp @@ -1,15 +1,16 @@ #include "flwindow.h" #include "../../framelesshelper.h" +#include + FRAMELESSHELPER_USE_NAMESPACE FLWindow::FLWindow(QWidget *parent) : QWidget(parent) { - setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog); + setWindowFlags(Qt::FramelessWindowHint); setStyleSheet(QString::fromLatin1("background:blue")); - setAttribute(Qt::WA_ShowModal); - resize(500, 500); + move(screen()->geometry().center() - frameGeometry().center()); } FLWindow::~FLWindow() @@ -21,7 +22,7 @@ void FLWindow::initFramelessWindow() { FramelessHelper* helper = new FramelessHelper(windowHandle()); helper->setResizeBorderThickness(8); - helper->setTitleBarHeight(20); + helper->setTitleBarHeight(40); helper->setResizable(true); helper->install(); } diff --git a/framelesshelper.cpp b/framelesshelper.cpp index d6d38e4..336d583 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -240,6 +240,16 @@ void FramelessHelper::updateHoverStates(const QPoint& pos) m_hoveredFrameSection = mapPosToFrameSection(pos); } +void FramelessHelper::startMove(QMouseEvent* event) +{ +#ifdef Q_OS_LINUX + Utilities::sendX11ButtonRelease(m_window, event->globalPos()); + Utilities::startX11Moving(m_window, event->globalPos()); + event->accept(); + return; +#endif +} + bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { bool filterOut = false; @@ -263,7 +273,20 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) } case QEvent::NonClientAreaMouseButtonPress: case QEvent::MouseButtonPress: + { + auto ev = static_cast(event); + if (isHoverResizeHandler()) { + // Start system resize + filterOut = true; + } else if (isInTitlebarArea(ev->pos())) { + // Start system move + startMove(ev); + filterOut = true; + } + break; + } + case QEvent::NonClientAreaMouseButtonRelease: case QEvent::MouseButtonRelease: break; diff --git a/framelesshelper.h b/framelesshelper.h index 358f151..2654373 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -33,6 +33,7 @@ QT_BEGIN_NAMESPACE QT_FORWARD_DECLARE_CLASS(QWindow) +QT_FORWARD_DECLARE_CLASS(QMouseEvent) QT_END_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE @@ -81,6 +82,8 @@ public: void updateMouse(const QPoint& pos); void updateHoverStates(const QPoint& pos); + void startMove(QMouseEvent* event); + protected: bool eventFilter(QObject *object, QEvent *event) override; diff --git a/utilities.h b/utilities.h index f7be5c2..246f9b0 100644 --- a/utilities.h +++ b/utilities.h @@ -58,6 +58,11 @@ FRAMELESSHELPER_API void updateQtFrameMargins(QWindow *window, const bool enable [[nodiscard]] FRAMELESSHELPER_API QString getSystemErrorMessage(const QString &function); #endif +#ifdef Q_OS_LINUX +FRAMELESSHELPER_API void sendX11ButtonRelease(QWindow *w, const QPoint &pos); +FRAMELESSHELPER_API void startX11Moving(QWindow *w, const QPoint &pos); +#endif + } FRAMELESSHELPER_END_NAMESPACE diff --git a/utilities_linux.cpp b/utilities_linux.cpp index 5aad9f7..40e5609 100644 --- a/utilities_linux.cpp +++ b/utilities_linux.cpp @@ -25,9 +25,25 @@ #include "utilities.h" #include +#include +#include +#include FRAMELESSHELPER_BEGIN_NAMESPACE +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + static constexpr int kDefaultResizeBorderThickness = 8; static constexpr int kDefaultCaptionHeight = 23; @@ -106,7 +122,8 @@ bool Utilities::shouldAppsUseDarkMode() ColorizationArea Utilities::getColorizationArea() { // ### TO BE IMPLEMENTED - return ColorizationArea::None; + //return ColorizationArea::None; // ‘None’ has been defined as a macro in X11 headers. + return ColorizationArea::All; } bool Utilities::isThemeChanged(const void *data) @@ -132,4 +149,62 @@ bool Utilities::showSystemMenu(const WId winId, const QPointF &pos) return false; } +void Utilities::sendX11ButtonRelease(QWindow *w, const QPoint &pos) +{ + QPoint clientPos = w->mapFromGlobal(pos); + Display *display = QX11Info::display(); + int screen = QX11Info::appScreen(); + unsigned long rootWindow = QX11Info::appRootWindow(screen); + + XEvent event; + memset(&event, 0, sizeof (event)); + + event.xbutton.button = 0; + event.xbutton.same_screen = True; + event.xbutton.send_event = True; + Window window = w->winId(); + event.xbutton.window = window; + event.xbutton.root = rootWindow; + event.xbutton.x_root = pos.x(); + event.xbutton.y_root = pos.y(); + event.xbutton.x = clientPos.x(); + event.xbutton.y = clientPos.y(); + event.xbutton.type = ButtonRelease; + event.xbutton.time = CurrentTime; + + if (XSendEvent(display, window, True, ButtonReleaseMask, &event) == 0) + qWarning() << "Cant send ButtonRelease event."; + XFlush(display); +} + +void Utilities::startX11Moving(QWindow *w, const QPoint &pos) +{ + Display *display = QX11Info::display(); + int screen = QX11Info::appScreen(); + unsigned long rootWindow = QX11Info::appRootWindow(screen); + static Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", False); + + XUngrabPointer(display, CurrentTime); + + XEvent event; + memset(&event, 0x00, sizeof(event)); + event.xclient.type = ClientMessage; + event.xclient.window = w->winId(); + event.xclient.message_type = netMoveResize; + event.xclient.serial = 0; + event.xclient.display = display; + event.xclient.send_event = True; + event.xclient.format = 32; + event.xclient.data.l[0] = pos.x(); + event.xclient.data.l[1] = pos.y(); + event.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; + event.xclient.data.l[3] = Button1; + event.xclient.data.l[4] = 0; /* unused */ + if (XSendEvent(display, rootWindow, + False, SubstructureRedirectMask | SubstructureNotifyMask, &event) == 0) + qWarning() << "Cant send Move event."; + XFlush(display); +} + + FRAMELESSHELPER_END_NAMESPACE \ No newline at end of file From 501ff149cfc004941e2db132d72b45e507dd1872 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 09:10:04 +0800 Subject: [PATCH 07/16] implemented X11 resizing --- framelesshelper.cpp | 13 +++++++++++- framelesshelper.h | 1 + utilities.h | 4 +++- utilities_linux.cpp | 52 ++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 63 insertions(+), 7 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 336d583..79f5a12 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -243,13 +243,23 @@ void FramelessHelper::updateHoverStates(const QPoint& pos) void FramelessHelper::startMove(QMouseEvent* event) { #ifdef Q_OS_LINUX - Utilities::sendX11ButtonRelease(m_window, event->globalPos()); + Utilities::sendX11ButtonReleaseEvent(m_window, event->globalPos()); Utilities::startX11Moving(m_window, event->globalPos()); event->accept(); return; #endif } +void FramelessHelper::startResize(QMouseEvent* event, Qt::WindowFrameSection frameSection) +{ +#ifdef Q_OS_LINUX + Utilities::sendX11ButtonReleaseEvent(m_window, event->globalPos()); + Utilities::startX11Resizing(m_window, event->globalPos(), frameSection); + event->accept(); + return; +#endif +} + bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { bool filterOut = false; @@ -277,6 +287,7 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) auto ev = static_cast(event); if (isHoverResizeHandler()) { // Start system resize + startResize(ev, m_hoveredFrameSection); filterOut = true; } else if (isInTitlebarArea(ev->pos())) { // Start system move diff --git a/framelesshelper.h b/framelesshelper.h index 2654373..f6ef065 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -83,6 +83,7 @@ public: void updateHoverStates(const QPoint& pos); void startMove(QMouseEvent* event); + void startResize(QMouseEvent* event, Qt::WindowFrameSection frameSection); protected: bool eventFilter(QObject *object, QEvent *event) override; diff --git a/utilities.h b/utilities.h index 246f9b0..5e2786b 100644 --- a/utilities.h +++ b/utilities.h @@ -59,8 +59,10 @@ FRAMELESSHELPER_API void updateQtFrameMargins(QWindow *window, const bool enable #endif #ifdef Q_OS_LINUX -FRAMELESSHELPER_API void sendX11ButtonRelease(QWindow *w, const QPoint &pos); +FRAMELESSHELPER_API void sendX11ButtonReleaseEvent(QWindow *w, const QPoint &pos); +FRAMELESSHELPER_API void sendX11MoveResizeEvent(QWindow *w, const QPoint &pos, int section); FRAMELESSHELPER_API void startX11Moving(QWindow *w, const QPoint &pos); +FRAMELESSHELPER_API void startX11Resizing(QWindow *w, const QPoint &pos, Qt::WindowFrameSection frameSection); #endif } diff --git a/utilities_linux.cpp b/utilities_linux.cpp index 40e5609..6394a55 100644 --- a/utilities_linux.cpp +++ b/utilities_linux.cpp @@ -149,7 +149,7 @@ bool Utilities::showSystemMenu(const WId winId, const QPointF &pos) return false; } -void Utilities::sendX11ButtonRelease(QWindow *w, const QPoint &pos) +void Utilities::sendX11ButtonReleaseEvent(QWindow *w, const QPoint &pos) { QPoint clientPos = w->mapFromGlobal(pos); Display *display = QX11Info::display(); @@ -177,7 +177,7 @@ void Utilities::sendX11ButtonRelease(QWindow *w, const QPoint &pos) XFlush(display); } -void Utilities::startX11Moving(QWindow *w, const QPoint &pos) +void Utilities::sendX11MoveResizeEvent(QWindow *w, const QPoint &pos, int section) { Display *display = QX11Info::display(); int screen = QX11Info::appScreen(); @@ -197,14 +197,56 @@ void Utilities::startX11Moving(QWindow *w, const QPoint &pos) event.xclient.format = 32; event.xclient.data.l[0] = pos.x(); event.xclient.data.l[1] = pos.y(); - event.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE; + event.xclient.data.l[2] = section; event.xclient.data.l[3] = Button1; - event.xclient.data.l[4] = 0; /* unused */ + event.xclient.data.l[4] = 0; if (XSendEvent(display, rootWindow, False, SubstructureRedirectMask | SubstructureNotifyMask, &event) == 0) - qWarning() << "Cant send Move event."; + qWarning("Cant send Move or Resize event."); XFlush(display); } +void Utilities::startX11Moving(QWindow *w, const QPoint &pos) +{ + sendX11MoveResizeEvent(w, pos, _NET_WM_MOVERESIZE_MOVE); +} + +void Utilities::startX11Resizing(QWindow *w, const QPoint &pos, Qt::WindowFrameSection frameSection) +{ + int section = -1; + + switch (frameSection) + { + case Qt::LeftSection: + section = _NET_WM_MOVERESIZE_SIZE_LEFT; + break; + case Qt::TopLeftSection: + section = _NET_WM_MOVERESIZE_SIZE_TOPLEFT; + break; + case Qt::TopSection: + section = _NET_WM_MOVERESIZE_SIZE_TOP; + break; + case Qt::TopRightSection: + section = _NET_WM_MOVERESIZE_SIZE_TOPRIGHT; + break; + case Qt::RightSection: + section = _NET_WM_MOVERESIZE_SIZE_RIGHT; + break; + case Qt::BottomRightSection: + section = _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; + break; + case Qt::BottomSection: + section = _NET_WM_MOVERESIZE_SIZE_BOTTOM; + break; + case Qt::BottomLeftSection: + section = _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; + break; + default: + break; + } + + if (section != -1) + sendX11MoveResizeEvent(w, pos, section); +} FRAMELESSHELPER_END_NAMESPACE \ No newline at end of file From 1aed38e882a3943b88856f903fd911ad1538b425 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 09:19:04 +0800 Subject: [PATCH 08/16] double click make window maximized --- framelesshelper.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 79f5a12..92a77a8 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -116,9 +116,9 @@ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) int border = 0; // TODO: get system default resize border - const int sysBorder = Utilities::getSystemMetric(window(), SystemMetric::ResizeBorderThickness, false); + const int sysBorder = Utilities::getSystemMetric(m_window, SystemMetric::ResizeBorderThickness, false); - Qt::WindowStates states = window()->windowState(); + Qt::WindowStates states = m_window->windowState(); if (!(states & Qt::WindowMaximized) && !(states & Qt::WindowFullScreen)) { border = resizeBorderThickness(); @@ -281,6 +281,7 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) updateMouse(ev->pos()); break; } + case QEvent::NonClientAreaMouseButtonPress: case QEvent::MouseButtonPress: { @@ -301,6 +302,21 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) case QEvent::NonClientAreaMouseButtonRelease: case QEvent::MouseButtonRelease: break; + + case QEvent::NonClientAreaMouseButtonDblClick: + case QEvent::MouseButtonDblClick: + { + auto ev = static_cast(event); + if (ev->button() == Qt::LeftButton) { + Qt::WindowStates states = m_window->windowState(); + if (states & Qt::WindowMaximized) + m_window->showNormal(); + else + m_window->showMaximized(); + } + break; + } + default: break; } From 96f49ded2f1e7e2094f4a685ba17de068b59f2b0 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 14:47:49 +0800 Subject: [PATCH 09/16] fix move and resize determination --- examples/minimal/flwindow.cpp | 2 +- framelesshelper.cpp | 82 ++++++++++++++++++++++++++++------- framelesshelper.h | 3 ++ utilities.h | 3 ++ utilities_linux.cpp | 77 +++++++++++++++++++++++++++++++- 5 files changed, 149 insertions(+), 18 deletions(-) diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp index 39fbcc9..d361418 100644 --- a/examples/minimal/flwindow.cpp +++ b/examples/minimal/flwindow.cpp @@ -21,7 +21,7 @@ FLWindow::~FLWindow() void FLWindow::initFramelessWindow() { FramelessHelper* helper = new FramelessHelper(windowHandle()); - helper->setResizeBorderThickness(8); + helper->setResizeBorderThickness(4); helper->setTitleBarHeight(40); helper->setResizable(true); helper->install(); diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 92a77a8..c144c20 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -37,6 +37,8 @@ FRAMELESSHELPER_BEGIN_NAMESPACE FramelessHelper::FramelessHelper(QWindow *window) : QObject(window) , m_window(window) + , m_hoveredFrameSection(Qt::NoSection) + , m_clickedFrameSection(Qt::NoSection) { Q_ASSERT(window != nullptr && window->isTopLevel()); } @@ -89,6 +91,14 @@ QRect FramelessHelper::titleBarRect() return QRect(0, 0, windowSize().width(), titleBarHeight()); } + +QRegion FramelessHelper::titleBarRegion() +{ + // TODO: consider HitTestVisibleObject + QRegion region(titleBarRect()); + return region; +} + QRect FramelessHelper::clientRect() { QRect rect(0, 0, windowSize().width(), windowSize().height()); @@ -101,6 +111,7 @@ QRect FramelessHelper::clientRect() QRegion FramelessHelper::nonClientRegion() { + // TODO: consider HitTestVisibleObject QRegion region(QRect(QPoint(0, 0), windowSize())); region -= clientRect(); return region; @@ -108,7 +119,7 @@ QRegion FramelessHelper::nonClientRegion() bool FramelessHelper::isInTitlebarArea(const QPoint& pos) { - return nonClientRegion().contains(pos); + return titleBarRegion().contains(pos); } Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) @@ -173,6 +184,18 @@ bool FramelessHelper::isHoverResizeHandler() m_hoveredFrameSection == Qt::BottomRightSection; } +bool FramelessHelper::isClickResizeHandler() +{ + return m_clickedFrameSection == Qt::LeftSection || + m_clickedFrameSection == Qt::RightSection || + m_clickedFrameSection == Qt::TopSection || + m_clickedFrameSection == Qt::BottomSection || + m_clickedFrameSection == Qt::TopLeftSection || + m_clickedFrameSection == Qt::TopRightSection || + m_clickedFrameSection == Qt::BottomLeftSection || + m_clickedFrameSection == Qt::BottomRightSection; +} + QCursor FramelessHelper::cursorForFrameSection(Qt::WindowFrameSection frameSection) { Qt::CursorShape cursor = Qt::ArrowCursor; @@ -222,11 +245,24 @@ void FramelessHelper::unsetCursor() void FramelessHelper::updateCursor() { +#ifdef Q_OS_LINUX + if (isHoverResizeHandler()) { + Utilities::setX11CursorShape(m_window, + Utilities::getX11CursorForFrameSection(m_hoveredFrameSection)); + m_cursorChanged = true; + } else { + if (!m_cursorChanged) + return; + Utilities::resetX1CursorShape(m_window); + m_cursorChanged = false; + } +#else if (isHoverResizeHandler()) { setCursor(cursorForFrameSection(m_hoveredFrameSection)); } else { unsetCursor(); } +#endif } void FramelessHelper::updateMouse(const QPoint& pos) @@ -279,41 +315,57 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { auto ev = static_cast(event); updateMouse(ev->pos()); - break; - } - case QEvent::NonClientAreaMouseButtonPress: - case QEvent::MouseButtonPress: - { - auto ev = static_cast(event); - if (isHoverResizeHandler()) { - // Start system resize - startResize(ev, m_hoveredFrameSection); - filterOut = true; - } else if (isInTitlebarArea(ev->pos())) { + if (m_clickedFrameSection == Qt::TitleBarArea + && isInTitlebarArea(ev->pos())) { // Start system move startMove(ev); filterOut = true; } - + + // We don't rely on the MouseMove event to determine the resize operation, + // because when the mouse is moved out of the window, the resize cannot + // be triggered. + break; } - + case QEvent::Leave: + { + updateMouse(m_window->mapFromGlobal(QCursor::pos())); + break; + } + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::MouseButtonPress: + { + auto ev = static_cast(event); + m_clickedFrameSection = m_hoveredFrameSection; + if (isHoverResizeHandler()) { + // Start system resize + startResize(ev, m_hoveredFrameSection); + filterOut = true; + } + break; + } + case QEvent::NonClientAreaMouseButtonRelease: case QEvent::MouseButtonRelease: + { + m_clickedFrameSection = Qt::NoSection; break; + } case QEvent::NonClientAreaMouseButtonDblClick: case QEvent::MouseButtonDblClick: { auto ev = static_cast(event); - if (ev->button() == Qt::LeftButton) { + if (isInTitlebarArea(ev->pos()) && ev->button() == Qt::LeftButton) { Qt::WindowStates states = m_window->windowState(); if (states & Qt::WindowMaximized) m_window->showNormal(); else m_window->showMaximized(); } + // TODO: double click resize handler break; } diff --git a/framelesshelper.h b/framelesshelper.h index f6ef065..104f7a8 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -59,6 +59,7 @@ public: int titleBarHeight() { return m_titleBarHeight; } int setTitleBarHeight(int height) { m_titleBarHeight = height; } QRect titleBarRect(); + QRegion titleBarRegion(); int resizeBorderThickness() { return m_resizeBorderThickness; } void setResizeBorderThickness(int thickness) { m_resizeBorderThickness = thickness; } @@ -73,6 +74,7 @@ public: Qt::WindowFrameSection mapPosToFrameSection(const QPoint& pos); bool isHoverResizeHandler(); + bool isClickResizeHandler(); QCursor cursorForFrameSection(Qt::WindowFrameSection frameSection); void setCursor(const QCursor& cursor); @@ -97,6 +99,7 @@ private: Qt::WindowFlags m_origWindowFlags; bool m_cursorChanged; Qt::WindowFrameSection m_hoveredFrameSection; + Qt::WindowFrameSection m_clickedFrameSection; }; FRAMELESSHELPER_END_NAMESPACE diff --git a/utilities.h b/utilities.h index 5e2786b..e589f5f 100644 --- a/utilities.h +++ b/utilities.h @@ -63,6 +63,9 @@ FRAMELESSHELPER_API void sendX11ButtonReleaseEvent(QWindow *w, const QPoint &pos FRAMELESSHELPER_API void sendX11MoveResizeEvent(QWindow *w, const QPoint &pos, int section); FRAMELESSHELPER_API void startX11Moving(QWindow *w, const QPoint &pos); FRAMELESSHELPER_API void startX11Resizing(QWindow *w, const QPoint &pos, Qt::WindowFrameSection frameSection); +FRAMELESSHELPER_API void setX11CursorShape(QWindow *w, int cursorId); +FRAMELESSHELPER_API void resetX1CursorShape(QWindow *w); +FRAMELESSHELPER_API unsigned int getX11CursorForFrameSection(Qt::WindowFrameSection frameSection); #endif } diff --git a/utilities_linux.cpp b/utilities_linux.cpp index 6394a55..e07640e 100644 --- a/utilities_linux.cpp +++ b/utilities_linux.cpp @@ -173,7 +173,7 @@ void Utilities::sendX11ButtonReleaseEvent(QWindow *w, const QPoint &pos) event.xbutton.time = CurrentTime; if (XSendEvent(display, window, True, ButtonReleaseMask, &event) == 0) - qWarning() << "Cant send ButtonRelease event."; + qWarning() << "Failed to send ButtonRelease event."; XFlush(display); } @@ -202,7 +202,7 @@ void Utilities::sendX11MoveResizeEvent(QWindow *w, const QPoint &pos, int sectio event.xclient.data.l[4] = 0; if (XSendEvent(display, rootWindow, False, SubstructureRedirectMask | SubstructureNotifyMask, &event) == 0) - qWarning("Cant send Move or Resize event."); + qWarning("Failed to send Move or Resize event."); XFlush(display); } @@ -249,4 +249,77 @@ void Utilities::startX11Resizing(QWindow *w, const QPoint &pos, Qt::WindowFrameS sendX11MoveResizeEvent(w, pos, section); } +enum class X11CursorType +{ + kArrow = 2, + kTop = 138, + kTopRight = 136, + kRight = 96, + kBottomRight = 14, + kBottom = 16, + kBottomLeft = 12, + kLeft = 70, + kTopLeft = 134, +}; + +void Utilities::setX11CursorShape(QWindow *w, int cursorId) +{ + const auto display = QX11Info::display(); + const WId window_id = w->winId(); + const Cursor cursor = XCreateFontCursor(display, cursorId); + if (!cursor) { + qWarning() << "Failed to set cursor."; + } + XDefineCursor(display, window_id, cursor); + XFlush(display); +} + +void Utilities::resetX1CursorShape(QWindow *w) +{ + const auto display = QX11Info::display(); + const WId window_id = w->winId(); + XUndefineCursor(display, window_id); + XFlush(display); +} + +unsigned int Utilities::getX11CursorForFrameSection(Qt::WindowFrameSection frameSection) +{ + X11CursorType cursor = X11CursorType::kArrow; + + switch (frameSection) + { + case Qt::LeftSection: + cursor = X11CursorType::kLeft; + break; + case Qt::RightSection: + cursor = X11CursorType::kRight; + break; + case Qt::BottomSection: + cursor = X11CursorType::kBottom; + break; + case Qt::TopSection: + cursor = X11CursorType::kTop; + break; + case Qt::TopLeftSection: + cursor = X11CursorType::kTopLeft; + break; + case Qt::BottomRightSection: + cursor = X11CursorType::kBottomRight; + break; + case Qt::TopRightSection: + cursor = X11CursorType::kTopRight; + break; + case Qt::BottomLeftSection: + cursor = X11CursorType::kBottomLeft; + break; + case Qt::TitleBarArea: + cursor = X11CursorType::kArrow; + break; + default: + break; + } + + return (unsigned int)cursor; +} + FRAMELESSHELPER_END_NAMESPACE \ No newline at end of file From 0964483b209d5830a265317bfc005e1dfd2efacc Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 16:45:30 +0800 Subject: [PATCH 10/16] enlarge the resize corner area --- framelesshelper.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index c144c20..c60a3fb 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -122,6 +122,15 @@ bool FramelessHelper::isInTitlebarArea(const QPoint& pos) return titleBarRegion().contains(pos); } +const int kCornerFactor = 2; + +/*! + \brief Determine window frame section by coordinates. + + Returns the window frame section at position \a pos, or \c Qt::NoSection + if there is no window frame section at this position. + + */ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) { int border = 0; @@ -141,30 +150,34 @@ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) if (windowRect.contains(pos)) { QPoint mappedPos = pos - windowRect.topLeft(); - if (QRect(0, 0, border, border).contains(mappedPos)) + + // The corner is kCornerFactor times the size of the border + if (QRect(0, 0, border * kCornerFactor, border * kCornerFactor).contains(mappedPos)) return Qt::TopLeftSection; - if (QRect(border, 0, windowRect.width() - border * 2, border).contains(mappedPos)) + if (QRect(border * kCornerFactor, 0, windowRect.width() - border * 2 * kCornerFactor, border).contains(mappedPos)) return Qt::TopSection; - if (QRect(windowRect.width() - border, 0, border, border).contains(mappedPos)) + if (QRect(windowRect.width() - border * kCornerFactor, 0, border * kCornerFactor, border * kCornerFactor).contains(mappedPos)) return Qt::TopRightSection; - if (QRect(windowRect.width() - border, border, border, windowRect.height() - border * 2).contains(mappedPos)) + if (QRect(windowRect.width() - border, border * kCornerFactor, border, windowRect.height() - border * 2 * kCornerFactor).contains(mappedPos)) return Qt::RightSection; - if (QRect(windowRect.width() - border, windowRect.height() - border, border, border).contains(mappedPos)) + if (QRect(windowRect.width() - border * kCornerFactor, windowRect.height() - border * kCornerFactor, border * kCornerFactor, border * kCornerFactor).contains(mappedPos)) return Qt::BottomRightSection; - if (QRect(border, windowRect.height() - border, windowRect.width() - border * 2, border).contains(mappedPos)) + if (QRect(border * kCornerFactor, windowRect.height() - border, windowRect.width() - border * 2 * kCornerFactor, border).contains(mappedPos)) return Qt::BottomSection; - if (QRect(0, windowRect.height() - border, border, border).contains(mappedPos)) + if (QRect(0, windowRect.height() - border * kCornerFactor, border * kCornerFactor, border * kCornerFactor).contains(mappedPos)) return Qt::BottomLeftSection; - if (QRect(0, border, border, windowRect.height() - border * 2).contains(mappedPos)) + if (QRect(0, border * kCornerFactor, border, windowRect.height() - border * 2 * kCornerFactor).contains(mappedPos)) return Qt::LeftSection; + // Determining window frame secion is the highest priority, + // so the determination of the title bar area can be simpler. if (isInTitlebarArea(pos)) return Qt::TitleBarArea; } From 5ab2024f54995675f14f2ee7471554b15c00f89e Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 16:54:52 +0800 Subject: [PATCH 11/16] only allow left button to move or resize --- framelesshelper.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index c60a3fb..1b5c3aa 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -351,8 +351,11 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) case QEvent::MouseButtonPress: { auto ev = static_cast(event); - m_clickedFrameSection = m_hoveredFrameSection; - if (isHoverResizeHandler()) { + + if (ev->button() == Qt::LeftButton) + m_clickedFrameSection = m_hoveredFrameSection; + + if (ev->button() == Qt::LeftButton && isHoverResizeHandler()) { // Start system resize startResize(ev, m_hoveredFrameSection); filterOut = true; From 5f58937588b2e1fcc1e6cef3a55c645e44de9044 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 19:41:24 +0800 Subject: [PATCH 12/16] add hitTestVisible logic --- examples/minimal/flwindow.cpp | 57 +++++++++++++++++++++++++++++++++-- examples/minimal/flwindow.h | 9 ++++++ examples/widget/widget.cpp | 1 + framelesshelper.cpp | 48 +++++++++++++++++++++++++++-- framelesshelper.h | 5 +++ 5 files changed, 116 insertions(+), 4 deletions(-) diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp index d361418..0a998d4 100644 --- a/examples/minimal/flwindow.cpp +++ b/examples/minimal/flwindow.cpp @@ -2,13 +2,15 @@ #include "../../framelesshelper.h" #include +#include +#include FRAMELESSHELPER_USE_NAMESPACE FLWindow::FLWindow(QWidget *parent) : QWidget(parent) { setWindowFlags(Qt::FramelessWindowHint); - setStyleSheet(QString::fromLatin1("background:blue")); + setupUi(); move(screen()->geometry().center() - frameGeometry().center()); } @@ -22,8 +24,11 @@ void FLWindow::initFramelessWindow() { FramelessHelper* helper = new FramelessHelper(windowHandle()); helper->setResizeBorderThickness(4); - helper->setTitleBarHeight(40); + helper->setTitleBarHeight(m_titleBarWidget->height()); helper->setResizable(true); + helper->setHitTestVisible(m_minimizeButton); + helper->setHitTestVisible(m_maximizeButton); + helper->setHitTestVisible(m_closeButton); helper->install(); } @@ -36,4 +41,52 @@ void FLWindow::showEvent(QShowEvent *event) inited = true; initFramelessWindow(); } +} + +void FLWindow::setupUi() +{ + resize(800, 600); + + m_titleBarWidget = new QWidget(this); + m_titleBarWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + m_titleBarWidget->setFixedHeight(40); + m_titleBarWidget->setStyleSheet(QString::fromLatin1("background:grey")); + + m_minimizeButton = new QPushButton(m_titleBarWidget); + m_minimizeButton->setText(QStringLiteral("Min")); + m_minimizeButton->setObjectName(QStringLiteral("MinimizeButton")); + connect(m_minimizeButton, &QPushButton::clicked, this, &QWidget::showMinimized); + + m_maximizeButton = new QPushButton(m_titleBarWidget); + m_maximizeButton->setText(QStringLiteral("Max")); + m_maximizeButton->setObjectName(QStringLiteral("MaximizeButton")); + connect(m_maximizeButton, &QPushButton::clicked, this, [this](){ + if (isMaximized() || isFullScreen()) { + showNormal(); + } else { + showMaximized(); + } + }); + + m_closeButton = new QPushButton(m_titleBarWidget); + m_closeButton->setText(QStringLiteral("Close")); + m_closeButton->setObjectName(QStringLiteral("CloseButton")); + connect(m_closeButton, &QPushButton::clicked, this, &QWidget::close); + + const auto titleBarLayout = new QHBoxLayout(m_titleBarWidget); + titleBarLayout->setContentsMargins(0, 0, 0, 0); + titleBarLayout->setSpacing(10); + titleBarLayout->addStretch(); + titleBarLayout->addWidget(m_minimizeButton); + titleBarLayout->addWidget(m_maximizeButton); + titleBarLayout->addWidget(m_closeButton); + titleBarLayout->addStretch(); + m_titleBarWidget->setLayout(titleBarLayout); + + const auto mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->setSpacing(0); + mainLayout->addWidget(m_titleBarWidget); + mainLayout->addStretch(); + setLayout(mainLayout); } \ No newline at end of file diff --git a/examples/minimal/flwindow.h b/examples/minimal/flwindow.h index 2fb26b0..e4507a8 100644 --- a/examples/minimal/flwindow.h +++ b/examples/minimal/flwindow.h @@ -2,6 +2,8 @@ #include +class QPushButton; + class FLWindow : public QWidget { Q_OBJECT @@ -14,4 +16,11 @@ protected: private: void initFramelessWindow(); + void setupUi(); + +private: + QWidget *m_titleBarWidget = nullptr; + QPushButton *m_minimizeButton = nullptr; + QPushButton *m_maximizeButton = nullptr; + QPushButton *m_closeButton = nullptr; }; \ No newline at end of file diff --git a/examples/widget/widget.cpp b/examples/widget/widget.cpp index 4bca9da..9718982 100644 --- a/examples/widget/widget.cpp +++ b/examples/widget/widget.cpp @@ -31,6 +31,7 @@ #include #include "../../utilities.h" #include "../../framelesswindowsmanager.h" +#include "../../framelesshelper.h" FRAMELESSHELPER_USE_NAMESPACE diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 1b5c3aa..24547a4 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -94,8 +94,20 @@ QRect FramelessHelper::titleBarRect() QRegion FramelessHelper::titleBarRegion() { - // TODO: consider HitTestVisibleObject QRegion region(titleBarRect()); + + for (const auto obj : m_HTVObjects) { + if (!obj || !(obj->isWidgetType() || obj->inherits("QQuickItem"))) { + continue; + } + + if (!obj->property("visible").toBool()) { + continue; + } + + region -= getHTVObjectRect(obj); + } + return region; } @@ -111,9 +123,21 @@ QRect FramelessHelper::clientRect() QRegion FramelessHelper::nonClientRegion() { - // TODO: consider HitTestVisibleObject QRegion region(QRect(QPoint(0, 0), windowSize())); region -= clientRect(); + + for (const auto obj : m_HTVObjects) { + if (!obj || !(obj->isWidgetType() || obj->inherits("QQuickItem"))) { + continue; + } + + if (!obj->property("visible").toBool()) { + continue; + } + + region -= getHTVObjectRect(obj); + } + return region; } @@ -309,6 +333,26 @@ void FramelessHelper::startResize(QMouseEvent* event, Qt::WindowFrameSection fra #endif } +void FramelessHelper::setHitTestVisible(QObject *obj) +{ + m_HTVObjects.push_back(obj); +} + +bool FramelessHelper::isHitTestVisible(QObject *obj) +{ + return m_HTVObjects.contains(obj); +} + +QRect FramelessHelper::getHTVObjectRect(QObject *obj) +{ + const QPoint originPoint = m_window->mapFromGlobal( + Utilities::mapOriginPointToWindow(obj).toPoint()); + const int width = obj->property("width").toInt(); + const int height = obj->property("height").toInt(); + + return QRect(originPoint, QSize(width, height)); +} + bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { bool filterOut = false; diff --git a/framelesshelper.h b/framelesshelper.h index 104f7a8..518d755 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -87,6 +87,10 @@ public: void startMove(QMouseEvent* event); void startResize(QMouseEvent* event, Qt::WindowFrameSection frameSection); + void setHitTestVisible(QObject *obj); + bool isHitTestVisible(QObject *obj); + QRect getHTVObjectRect(QObject *obj); + protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -100,6 +104,7 @@ private: bool m_cursorChanged; Qt::WindowFrameSection m_hoveredFrameSection; Qt::WindowFrameSection m_clickedFrameSection; + QList m_HTVObjects; }; FRAMELESSHELPER_END_NAMESPACE From 9b2dc893b6f2680210759c350454992248ba648e Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 20 Sep 2021 20:40:03 +0800 Subject: [PATCH 13/16] double click resize handler will change geometry --- framelesshelper.cpp | 56 ++++++++++++++++++++++++++++----------------- framelesshelper.h | 4 ++-- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 24547a4..03a1a50 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "framelesswindowsmanager.h" #include "utilities.h" @@ -163,6 +164,7 @@ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) const int sysBorder = Utilities::getSystemMetric(m_window, SystemMetric::ResizeBorderThickness, false); Qt::WindowStates states = m_window->windowState(); + // Resizing is disabled when WindowMaximized or WindowFullScreen if (!(states & Qt::WindowMaximized) && !(states & Qt::WindowFullScreen)) { border = resizeBorderThickness(); @@ -313,23 +315,19 @@ void FramelessHelper::updateHoverStates(const QPoint& pos) m_hoveredFrameSection = mapPosToFrameSection(pos); } -void FramelessHelper::startMove(QMouseEvent* event) +void FramelessHelper::startMove(const QPoint &globalPos) { #ifdef Q_OS_LINUX - Utilities::sendX11ButtonReleaseEvent(m_window, event->globalPos()); - Utilities::startX11Moving(m_window, event->globalPos()); - event->accept(); - return; + Utilities::sendX11ButtonReleaseEvent(m_window, globalPos); + Utilities::startX11Moving(m_window, globalPos); #endif } -void FramelessHelper::startResize(QMouseEvent* event, Qt::WindowFrameSection frameSection) +void FramelessHelper::startResize(const QPoint &globalPos, Qt::WindowFrameSection frameSection) { #ifdef Q_OS_LINUX - Utilities::sendX11ButtonReleaseEvent(m_window, event->globalPos()); - Utilities::startX11Resizing(m_window, event->globalPos(), frameSection); - event->accept(); - return; + Utilities::sendX11ButtonReleaseEvent(m_window, globalPos); + Utilities::startX11Resizing(m_window, globalPos, frameSection); #endif } @@ -376,13 +374,20 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) if (m_clickedFrameSection == Qt::TitleBarArea && isInTitlebarArea(ev->pos())) { // Start system move - startMove(ev); + startMove(ev->globalPos()); + filterOut = true; + } else if (isClickResizeHandler() && isHoverResizeHandler()) { + // Start system resize + startResize(ev->globalPos(), m_hoveredFrameSection); filterOut = true; } - // We don't rely on the MouseMove event to determine the resize operation, - // because when the mouse is moved out of the window, the resize cannot - // be triggered. + // This case takes into account that the mouse moves outside the window boundary + QRect windowRect(0, 0, windowSize().width(), windowSize().height()); + if (m_clickedFrameSection != Qt::NoSection && !windowRect.contains(ev->pos())) { + startResize(ev->globalPos(), m_clickedFrameSection); + filterOut = true; + } break; } @@ -399,11 +404,6 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) if (ev->button() == Qt::LeftButton) m_clickedFrameSection = m_hoveredFrameSection; - if (ev->button() == Qt::LeftButton && isHoverResizeHandler()) { - // Start system resize - startResize(ev, m_hoveredFrameSection); - filterOut = true; - } break; } @@ -418,14 +418,28 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) case QEvent::MouseButtonDblClick: { auto ev = static_cast(event); - if (isInTitlebarArea(ev->pos()) && ev->button() == Qt::LeftButton) { + if (isHoverResizeHandler() && ev->button() == Qt::LeftButton) { + // double click resize handler + if (m_clickedFrameSection == Qt::TopSection + || m_clickedFrameSection == Qt::BottomSection) { + QRect screenRect = m_window->screen()->availableGeometry(); + QRect winRect = m_window->geometry(); + m_window->setGeometry(winRect.x(), 0, winRect.width(), screenRect.height()); + } else if (m_clickedFrameSection == Qt::LeftSection + || m_clickedFrameSection == Qt::RightSection) { + QRect screenRect = m_window->screen()->availableGeometry(); + QRect winRect = m_window->geometry(); + m_window->setGeometry(0, winRect.y(), screenRect.width(), winRect.height()); + } + + } else if (isInTitlebarArea(ev->pos()) && ev->button() == Qt::LeftButton) { Qt::WindowStates states = m_window->windowState(); if (states & Qt::WindowMaximized) m_window->showNormal(); else m_window->showMaximized(); } - // TODO: double click resize handler + break; } diff --git a/framelesshelper.h b/framelesshelper.h index 518d755..7b61c25 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -84,8 +84,8 @@ public: void updateMouse(const QPoint& pos); void updateHoverStates(const QPoint& pos); - void startMove(QMouseEvent* event); - void startResize(QMouseEvent* event, Qt::WindowFrameSection frameSection); + void startMove(const QPoint &globalPos); + void startResize(const QPoint &globalPos, Qt::WindowFrameSection frameSection); void setHitTestVisible(QObject *obj); bool isHitTestVisible(QObject *obj); From 3a3e39c0c0bb3155b0305a8f421c022cde2c40ca Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Tue, 21 Sep 2021 10:17:42 +0800 Subject: [PATCH 14/16] change X11 event impl --- utilities.h | 8 ++--- utilities_linux.cpp | 81 +++++++++++++++++++++------------------------ 2 files changed, 41 insertions(+), 48 deletions(-) diff --git a/utilities.h b/utilities.h index e589f5f..222f0aa 100644 --- a/utilities.h +++ b/utilities.h @@ -59,10 +59,10 @@ FRAMELESSHELPER_API void updateQtFrameMargins(QWindow *window, const bool enable #endif #ifdef Q_OS_LINUX -FRAMELESSHELPER_API void sendX11ButtonReleaseEvent(QWindow *w, const QPoint &pos); -FRAMELESSHELPER_API void sendX11MoveResizeEvent(QWindow *w, const QPoint &pos, int section); -FRAMELESSHELPER_API void startX11Moving(QWindow *w, const QPoint &pos); -FRAMELESSHELPER_API void startX11Resizing(QWindow *w, const QPoint &pos, Qt::WindowFrameSection frameSection); +FRAMELESSHELPER_API void sendX11ButtonReleaseEvent(QWindow *w, const QPoint &globalPos); +FRAMELESSHELPER_API void sendX11MoveResizeEvent(QWindow *w, const QPoint &globalPos, int section); +FRAMELESSHELPER_API void startX11Moving(QWindow *w, const QPoint &globalPos); +FRAMELESSHELPER_API void startX11Resizing(QWindow *w, const QPoint &globalPos, Qt::WindowFrameSection frameSection); FRAMELESSHELPER_API void setX11CursorShape(QWindow *w, int cursorId); FRAMELESSHELPER_API void resetX1CursorShape(QWindow *w); FRAMELESSHELPER_API unsigned int getX11CursorForFrameSection(Qt::WindowFrameSection frameSection); diff --git a/utilities_linux.cpp b/utilities_linux.cpp index e07640e..3fa80b1 100644 --- a/utilities_linux.cpp +++ b/utilities_linux.cpp @@ -149,61 +149,54 @@ bool Utilities::showSystemMenu(const WId winId, const QPointF &pos) return false; } -void Utilities::sendX11ButtonReleaseEvent(QWindow *w, const QPoint &pos) +void Utilities::sendX11ButtonReleaseEvent(QWindow *w, const QPoint &globalPos) { - QPoint clientPos = w->mapFromGlobal(pos); - Display *display = QX11Info::display(); - int screen = QX11Info::appScreen(); - unsigned long rootWindow = QX11Info::appRootWindow(screen); + const QPoint pos = w->mapFromGlobal(globalPos); + const auto display = QX11Info::display(); + const auto screen = QX11Info::appScreen(); - XEvent event; - memset(&event, 0, sizeof (event)); + XEvent xevent; + memset(&xevent, 0, sizeof(XEvent)); - event.xbutton.button = 0; - event.xbutton.same_screen = True; - event.xbutton.send_event = True; - Window window = w->winId(); - event.xbutton.window = window; - event.xbutton.root = rootWindow; - event.xbutton.x_root = pos.x(); - event.xbutton.y_root = pos.y(); - event.xbutton.x = clientPos.x(); - event.xbutton.y = clientPos.y(); - event.xbutton.type = ButtonRelease; - event.xbutton.time = CurrentTime; + xevent.type = ButtonRelease; + xevent.xbutton.button = Button1; + xevent.xbutton.window = w->winId(); + xevent.xbutton.x = pos.x(); + xevent.xbutton.y = pos.y(); + xevent.xbutton.x_root = globalPos.x(); + xevent.xbutton.y_root = globalPos.y(); + xevent.xbutton.display = display; - if (XSendEvent(display, window, True, ButtonReleaseMask, &event) == 0) + if (XSendEvent(display, w->winId(), False, ButtonReleaseMask, &xevent) == 0) qWarning() << "Failed to send ButtonRelease event."; - XFlush(display); + XFlush(display); } -void Utilities::sendX11MoveResizeEvent(QWindow *w, const QPoint &pos, int section) +void Utilities::sendX11MoveResizeEvent(QWindow *w, const QPoint &globalPos, int section) { - Display *display = QX11Info::display(); - int screen = QX11Info::appScreen(); - unsigned long rootWindow = QX11Info::appRootWindow(screen); - static Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", False); + const auto display = QX11Info::display(); + const auto winId = w->winId(); + const auto screen = QX11Info::appScreen(); - XUngrabPointer(display, CurrentTime); + XEvent xev; + const Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", false); + xev.xclient.type = ClientMessage; + xev.xclient.message_type = netMoveResize; + xev.xclient.display = display; + xev.xclient.window = winId; + xev.xclient.format = 32; - XEvent event; - memset(&event, 0x00, sizeof(event)); - event.xclient.type = ClientMessage; - event.xclient.window = w->winId(); - event.xclient.message_type = netMoveResize; - event.xclient.serial = 0; - event.xclient.display = display; - event.xclient.send_event = True; - event.xclient.format = 32; - event.xclient.data.l[0] = pos.x(); - event.xclient.data.l[1] = pos.y(); - event.xclient.data.l[2] = section; - event.xclient.data.l[3] = Button1; - event.xclient.data.l[4] = 0; - if (XSendEvent(display, rootWindow, - False, SubstructureRedirectMask | SubstructureNotifyMask, &event) == 0) + xev.xclient.data.l[0] = globalPos.x(); + xev.xclient.data.l[1] = globalPos.y(); + xev.xclient.data.l[2] = section; + xev.xclient.data.l[3] = Button1; + xev.xclient.data.l[4] = 1; + XUngrabPointer(display, QX11Info::appTime()); + + if(XSendEvent(display, QX11Info::appRootWindow(screen), + false, SubstructureRedirectMask | SubstructureNotifyMask, &xev) == 0) qWarning("Failed to send Move or Resize event."); - XFlush(display); + XFlush(display); } void Utilities::startX11Moving(QWindow *w, const QPoint &pos) From 989de57f33c8f835b46ddaf15daf4df1a4c9fd9a Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Tue, 21 Sep 2021 15:42:11 +0800 Subject: [PATCH 15/16] fix HiDPI position calculate error --- examples/minimal/main.cpp | 3 +- framelesshelper.cpp | 13 ++++++- utilities_linux.cpp | 77 ++++++++++++++++++++++----------------- 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/examples/minimal/main.cpp b/examples/minimal/main.cpp index d99e8ad..dc6d197 100644 --- a/examples/minimal/main.cpp +++ b/examples/minimal/main.cpp @@ -3,6 +3,7 @@ int main(int argc, char *argv[]) { +#if 1 QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -11,7 +12,7 @@ int main(int argc, char *argv[]) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif #endif - +#endif QApplication application(argc, argv); FLWindow win; diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 03a1a50..6d04dbf 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -318,6 +318,9 @@ void FramelessHelper::updateHoverStates(const QPoint& pos) void FramelessHelper::startMove(const QPoint &globalPos) { #ifdef Q_OS_LINUX + // On HiDPI screen, X11 ButtonRelease is likely to trigger + // a QEvent::MouseMove, so we reset m_clickedFrameSection in advance. + m_clickedFrameSection = Qt::NoSection; Utilities::sendX11ButtonReleaseEvent(m_window, globalPos); Utilities::startX11Moving(m_window, globalPos); #endif @@ -326,6 +329,9 @@ void FramelessHelper::startMove(const QPoint &globalPos) void FramelessHelper::startResize(const QPoint &globalPos, Qt::WindowFrameSection frameSection) { #ifdef Q_OS_LINUX + // On HiDPI screen, X11 ButtonRelease is likely to trigger + // a QEvent::MouseMove, so we reset m_clickedFrameSection in advance. + m_clickedFrameSection = Qt::NoSection; Utilities::sendX11ButtonReleaseEvent(m_window, globalPos); Utilities::startX11Resizing(m_window, globalPos, frameSection); #endif @@ -375,17 +381,20 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) && isInTitlebarArea(ev->pos())) { // Start system move startMove(ev->globalPos()); + ev->accept(); filterOut = true; } else if (isClickResizeHandler() && isHoverResizeHandler()) { // Start system resize startResize(ev->globalPos(), m_hoveredFrameSection); + ev->accept(); filterOut = true; } // This case takes into account that the mouse moves outside the window boundary QRect windowRect(0, 0, windowSize().width(), windowSize().height()); - if (m_clickedFrameSection != Qt::NoSection && !windowRect.contains(ev->pos())) { + if (isClickResizeHandler() && !windowRect.contains(ev->pos())) { startResize(ev->globalPos(), m_clickedFrameSection); + ev->accept(); filterOut = true; } @@ -401,7 +410,7 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) { auto ev = static_cast(event); - if (ev->button() == Qt::LeftButton) + if (ev->button() == Qt::LeftButton) m_clickedFrameSection = m_hoveredFrameSection; break; diff --git a/utilities_linux.cpp b/utilities_linux.cpp index 3fa80b1..41216c0 100644 --- a/utilities_linux.cpp +++ b/utilities_linux.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -152,51 +153,59 @@ bool Utilities::showSystemMenu(const WId winId, const QPointF &pos) void Utilities::sendX11ButtonReleaseEvent(QWindow *w, const QPoint &globalPos) { const QPoint pos = w->mapFromGlobal(globalPos); - const auto display = QX11Info::display(); - const auto screen = QX11Info::appScreen(); + const auto display = QX11Info::display(); + const auto screen = QX11Info::appScreen(); - XEvent xevent; - memset(&xevent, 0, sizeof(XEvent)); + XEvent xevent; + memset(&xevent, 0, sizeof(XEvent)); - xevent.type = ButtonRelease; - xevent.xbutton.button = Button1; - xevent.xbutton.window = w->winId(); - xevent.xbutton.x = pos.x(); - xevent.xbutton.y = pos.y(); - xevent.xbutton.x_root = globalPos.x(); - xevent.xbutton.y_root = globalPos.y(); - xevent.xbutton.display = display; + xevent.type = ButtonRelease; + xevent.xbutton.time = CurrentTime; + xevent.xbutton.button = 0; + xevent.xbutton.same_screen = True; + xevent.xbutton.send_event = True; + xevent.xbutton.window = w->winId(); + xevent.xbutton.root = QX11Info::appRootWindow(screen); + xevent.xbutton.x = pos.x() * w->screen()->devicePixelRatio(); + xevent.xbutton.y = pos.y() * w->screen()->devicePixelRatio(); + xevent.xbutton.x_root = globalPos.x() * w->screen()->devicePixelRatio(); + xevent.xbutton.y_root = globalPos.y() * w->screen()->devicePixelRatio(); + xevent.xbutton.display = display; - if (XSendEvent(display, w->winId(), False, ButtonReleaseMask, &xevent) == 0) + if (XSendEvent(display, w->winId(), True, ButtonReleaseMask, &xevent) == 0) qWarning() << "Failed to send ButtonRelease event."; - XFlush(display); + XFlush(display); } void Utilities::sendX11MoveResizeEvent(QWindow *w, const QPoint &globalPos, int section) { - const auto display = QX11Info::display(); - const auto winId = w->winId(); - const auto screen = QX11Info::appScreen(); + const auto display = QX11Info::display(); + const auto winId = w->winId(); + const auto screen = QX11Info::appScreen(); - XEvent xev; - const Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", false); - xev.xclient.type = ClientMessage; - xev.xclient.message_type = netMoveResize; - xev.xclient.display = display; - xev.xclient.window = winId; - xev.xclient.format = 32; + XUngrabPointer(display, CurrentTime); - xev.xclient.data.l[0] = globalPos.x(); - xev.xclient.data.l[1] = globalPos.y(); - xev.xclient.data.l[2] = section; - xev.xclient.data.l[3] = Button1; - xev.xclient.data.l[4] = 1; - XUngrabPointer(display, QX11Info::appTime()); + XEvent xev; + memset(&xev, 0x00, sizeof(xev)); + const Atom netMoveResize = XInternAtom(display, "_NET_WM_MOVERESIZE", False); + xev.xclient.type = ClientMessage; + xev.xclient.message_type = netMoveResize; + xev.xclient.serial = 0; + xev.xclient.display = display; + xev.xclient.send_event = True; + xev.xclient.window = winId; + xev.xclient.format = 32; - if(XSendEvent(display, QX11Info::appRootWindow(screen), - false, SubstructureRedirectMask | SubstructureNotifyMask, &xev) == 0) + xev.xclient.data.l[0] = globalPos.x() * w->screen()->devicePixelRatio(); + xev.xclient.data.l[1] = globalPos.y() * w->screen()->devicePixelRatio(); + xev.xclient.data.l[2] = section; + xev.xclient.data.l[3] = Button1; + xev.xclient.data.l[4] = 0; + + if(XSendEvent(display, QX11Info::appRootWindow(screen), + False, SubstructureRedirectMask | SubstructureNotifyMask, &xev) == 0) qWarning("Failed to send Move or Resize event."); - XFlush(display); + XFlush(display); } void Utilities::startX11Moving(QWindow *w, const QPoint &pos) @@ -315,4 +324,4 @@ unsigned int Utilities::getX11CursorForFrameSection(Qt::WindowFrameSection frame return (unsigned int)cursor; } -FRAMELESSHELPER_END_NAMESPACE \ No newline at end of file +FRAMELESSHELPER_END_NAMESPACE From 6a6fea8ac9ce9270ea136f24be35e77f468942c5 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Tue, 21 Sep 2021 17:56:43 +0800 Subject: [PATCH 16/16] improve resize handler db click behavior --- framelesshelper.cpp | 49 ++++++++++++++++++++++++++++++++++----------- framelesshelper.h | 1 + 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 6d04dbf..99ab882 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -429,18 +429,7 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) auto ev = static_cast(event); if (isHoverResizeHandler() && ev->button() == Qt::LeftButton) { // double click resize handler - if (m_clickedFrameSection == Qt::TopSection - || m_clickedFrameSection == Qt::BottomSection) { - QRect screenRect = m_window->screen()->availableGeometry(); - QRect winRect = m_window->geometry(); - m_window->setGeometry(winRect.x(), 0, winRect.width(), screenRect.height()); - } else if (m_clickedFrameSection == Qt::LeftSection - || m_clickedFrameSection == Qt::RightSection) { - QRect screenRect = m_window->screen()->availableGeometry(); - QRect winRect = m_window->geometry(); - m_window->setGeometry(0, winRect.y(), screenRect.width(), winRect.height()); - } - + handleResizeHandlerDblClicked(); } else if (isInTitlebarArea(ev->pos()) && ev->button() == Qt::LeftButton) { Qt::WindowStates states = m_window->windowState(); if (states & Qt::WindowMaximized) @@ -460,6 +449,42 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event) return filterOut; } +void FramelessHelper::handleResizeHandlerDblClicked() +{ + QRect screenRect = m_window->screen()->availableGeometry(); + QRect winRect = m_window->geometry(); + + switch (m_clickedFrameSection) + { + case Qt::TopSection: + m_window->setGeometry(winRect.x(), 0, winRect.width(), winRect.height() + winRect.y()); + break; + case Qt::BottomSection: + m_window->setGeometry(winRect.x(), winRect.y(), winRect.width(), screenRect.height() - winRect.y()); + break; + case Qt::LeftSection: + m_window->setGeometry(0, winRect.y(), winRect.x() + winRect.width(), winRect.height()); + break; + case Qt::RightSection: + m_window->setGeometry(winRect.x(), winRect.y(), screenRect.width() - winRect.x(), winRect.height()); + break; + case Qt::TopLeftSection: + m_window->setGeometry(0, 0, winRect.x() + winRect.width(), winRect.y() + winRect.height()); + break; + case Qt::TopRightSection: + m_window->setGeometry(winRect.x(), 0, screenRect.width() - winRect.x(), winRect.y() + winRect.height()); + break; + case Qt::BottomLeftSection: + m_window->setGeometry(0, winRect.y(), winRect.x() + winRect.width(), screenRect.height() - winRect.y()); + break; + case Qt::BottomRightSection: + m_window->setGeometry(winRect.x(), winRect.y(), screenRect.width() - winRect.x(), screenRect.height() - winRect.y()); + break; + default: + break; + } +} + FRAMELESSHELPER_END_NAMESPACE #endif diff --git a/framelesshelper.h b/framelesshelper.h index 7b61c25..81ff7af 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -93,6 +93,7 @@ public: protected: bool eventFilter(QObject *object, QEvent *event) override; + void handleResizeHandlerDblClicked(); private: QWindow *m_window;