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