From ce69d1a4c537b5d98560edf55b75a0a10b7f33a2 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Sat, 2 Oct 2021 14:19:46 +0800 Subject: [PATCH 1/2] implement Core API on MacOS --- CMakeLists.txt | 8 +- examples/minimal/main.cpp | 3 + framelesshelper.cpp | 32 +++++- framelesshelper.h | 4 - utilities.h | 12 +- utilities_macos.mm | 235 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 280 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index faf6194..3a942a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ if(WIN32) framelesshelper_win32.cpp ) else() - if(MACOS) + if(APPLE) list(APPEND SOURCES utilities_macos.mm) else() find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) @@ -89,8 +89,10 @@ if(WIN32) dwmapi ) else() - if(MACOS) - #TODO + if(APPLE) + target_link_libraries(${PROJECT_NAME} PRIVATE + "-framework Cocoa -framework Carbon" + ) else() target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::X11Extras diff --git a/examples/minimal/main.cpp b/examples/minimal/main.cpp index dc6d197..42bd66d 100644 --- a/examples/minimal/main.cpp +++ b/examples/minimal/main.cpp @@ -1,6 +1,8 @@ #include #include "flwindow.h" +#include + int main(int argc, char *argv[]) { #if 1 @@ -14,6 +16,7 @@ int main(int argc, char *argv[]) #endif #endif QApplication application(argc, argv); + application.setStyle(QStyleFactory::create(QStringLiteral("fusion"))); FLWindow win; win.show(); diff --git a/framelesshelper.cpp b/framelesshelper.cpp index 99ab882..354f1d2 100644 --- a/framelesshelper.cpp +++ b/framelesshelper.cpp @@ -24,8 +24,6 @@ #include "framelesshelper.h" -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - #include #include #include @@ -42,6 +40,11 @@ FramelessHelper::FramelessHelper(QWindow *window) , m_clickedFrameSection(Qt::NoSection) { Q_ASSERT(window != nullptr && window->isTopLevel()); + +#ifdef Q_OS_MAC + if(qEnvironmentVariable("QT_MAC_WANTS_LAYER") != QStringLiteral("1")) + qputenv("QT_MAC_WANTS_LAYER", "1"); +#endif } /*! @@ -62,6 +65,11 @@ void FramelessHelper::install() resizeWindow(origRect.size()); m_window->installEventFilter(this); + +#ifdef Q_OS_MAC + Utilities::setMacWindowHook(m_window); + Utilities::setMacWindowFrameless(m_window); +#endif } /*! @@ -74,6 +82,10 @@ void FramelessHelper::uninstall() resizeWindow(QSize()); m_window->removeEventFilter(this); + +#ifdef Q_OS_MAC + Utilities::unsetMacWindowHook(m_window); +#endif } /*! @@ -147,7 +159,8 @@ bool FramelessHelper::isInTitlebarArea(const QPoint& pos) return titleBarRegion().contains(pos); } -const int kCornerFactor = 2; +/*! This variable is used to enlarge the corner resize handler area. */ +static const int kCornerFactor = 2; /*! \brief Determine window frame section by coordinates. @@ -160,6 +173,9 @@ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) { int border = 0; + // On MacOS we use native resize border. + +#ifndef Q_OS_MAC // TODO: get system default resize border const int sysBorder = Utilities::getSystemMetric(m_window, SystemMetric::ResizeBorderThickness, false); @@ -170,6 +186,7 @@ Qt::WindowFrameSection FramelessHelper::mapPosToFrameSection(const QPoint& pos) border = resizeBorderThickness(); border = qMin(border, sysBorder); } +#endif // Q_OS_MAC QRect windowRect(0, 0, windowSize().width(), windowSize().height()); @@ -324,6 +341,10 @@ void FramelessHelper::startMove(const QPoint &globalPos) Utilities::sendX11ButtonReleaseEvent(m_window, globalPos); Utilities::startX11Moving(m_window, globalPos); #endif + +#ifdef Q_OS_MAC + Utilities::startMacDrag(m_window, globalPos); +#endif } void FramelessHelper::startResize(const QPoint &globalPos, Qt::WindowFrameSection frameSection) @@ -335,6 +356,9 @@ void FramelessHelper::startResize(const QPoint &globalPos, Qt::WindowFrameSectio Utilities::sendX11ButtonReleaseEvent(m_window, globalPos); Utilities::startX11Resizing(m_window, globalPos, frameSection); #endif + + // On MacOS, we use native resize handler. So, we do not need to implement + // any resize function. } void FramelessHelper::setHitTestVisible(QObject *obj) @@ -486,5 +510,3 @@ void FramelessHelper::handleResizeHandlerDblClicked() } FRAMELESSHELPER_END_NAMESPACE - -#endif diff --git a/framelesshelper.h b/framelesshelper.h index 81ff7af..f144264 100644 --- a/framelesshelper.h +++ b/framelesshelper.h @@ -26,8 +26,6 @@ #include "framelesshelper_global.h" -#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) - #include #include @@ -109,5 +107,3 @@ private: }; FRAMELESSHELPER_END_NAMESPACE - -#endif diff --git a/utilities.h b/utilities.h index 222f0aa..e2b50ea 100644 --- a/utilities.h +++ b/utilities.h @@ -56,7 +56,7 @@ FRAMELESSHELPER_API void updateFrameMargins(const WId winId, const bool reset); FRAMELESSHELPER_API void updateQtFrameMargins(QWindow *window, const bool enable); [[nodiscard]] FRAMELESSHELPER_API QString getSystemErrorMessage(const QString &function, const HRESULT hr); [[nodiscard]] FRAMELESSHELPER_API QString getSystemErrorMessage(const QString &function); -#endif +#endif // Q_OS_WINDOWS #ifdef Q_OS_LINUX FRAMELESSHELPER_API void sendX11ButtonReleaseEvent(QWindow *w, const QPoint &globalPos); @@ -66,7 +66,15 @@ FRAMELESSHELPER_API void startX11Resizing(QWindow *w, const QPoint &globalPos, Q FRAMELESSHELPER_API void setX11CursorShape(QWindow *w, int cursorId); FRAMELESSHELPER_API void resetX1CursorShape(QWindow *w); FRAMELESSHELPER_API unsigned int getX11CursorForFrameSection(Qt::WindowFrameSection frameSection); -#endif +#endif // Q_OS_LINUX + +#ifdef Q_OS_MAC +FRAMELESSHELPER_API bool setMacWindowHook(QWindow* w); +FRAMELESSHELPER_API bool unsetMacWindowHook(QWindow* w); +FRAMELESSHELPER_API bool setMacWindowFrameless(QWindow* w); +FRAMELESSHELPER_API bool startMacDrag(QWindow* w, const QPoint& pos); +FRAMELESSHELPER_API Qt::MouseButtons getMacMouseButtons(); +#endif // Q_OS_MAC } diff --git a/utilities_macos.mm b/utilities_macos.mm index 53fb5a7..638cc11 100644 --- a/utilities_macos.mm +++ b/utilities_macos.mm @@ -23,3 +23,238 @@ */ #include "utilities.h" + +#include +#include +#include + +#include +#include + +FRAMELESSHELPER_BEGIN_NAMESPACE + +namespace Utilities { + +static QList gFlsWindows; +static bool gNSWindowOverrode = false; + +typedef void (*setStyleMaskType)(id, SEL, NSWindowStyleMask); +static setStyleMaskType gOrigSetStyleMask = nullptr; +static void __setStyleMask(id obj, SEL sel, NSWindowStyleMask styleMask) +{ + if (gFlsWindows.contains(reinterpret_cast(obj))) + { + styleMask = styleMask | NSWindowStyleMaskFullSizeContentView; + } + + if (gOrigSetStyleMask != nullptr) + gOrigSetStyleMask(obj, sel, styleMask); +} + +typedef void (*setTitlebarAppearsTransparentType)(id, SEL, BOOL); +static setTitlebarAppearsTransparentType gOrigSetTitlebarAppearsTransparent = nullptr; +static void __setTitlebarAppearsTransparent(id obj, SEL sel, BOOL transparent) +{ + if (gFlsWindows.contains(reinterpret_cast(obj))) + transparent = true; + + if (gOrigSetTitlebarAppearsTransparent != nullptr) + gOrigSetTitlebarAppearsTransparent(obj, sel, transparent); +} + +typedef BOOL (*canBecomeKeyWindowType)(id, SEL); +static canBecomeKeyWindowType gOrigCanBecomeKeyWindow = nullptr; +static BOOL __canBecomeKeyWindow(id obj, SEL sel) +{ + if (gFlsWindows.contains(reinterpret_cast(obj))) + { + return true; + } + + if (gOrigCanBecomeKeyWindow != nullptr) + return gOrigCanBecomeKeyWindow(obj, sel); + + return true; +} + +typedef BOOL (*canBecomeMainWindowType)(id, SEL); +static canBecomeMainWindowType gOrigCanBecomeMainWindow = nullptr; +static BOOL __canBecomeMainWindow(id obj, SEL sel) +{ + if (gFlsWindows.contains(reinterpret_cast(obj))) + { + return true; + } + + if (gOrigCanBecomeMainWindow != nullptr) + return gOrigCanBecomeMainWindow(obj, sel); + return true; +} + +typedef void (*sendEventType)(id, SEL, NSEvent*); +static sendEventType gOrigSendEvent = nullptr; +static void __sendEvent(id obj, SEL sel, NSEvent* event) +{ + if (gOrigSendEvent != nullptr) + gOrigSendEvent(obj, sel, event); + + + if (!gFlsWindows.contains(reinterpret_cast(obj))) + return; + + if (event.type == NSEventTypeLeftMouseDown) + QGuiApplication::processEvents(); +} + +/*! + Replace origin method \a origSEL of class \a cls with new one \a newIMP , + then return old method as function pointer. + */ +static void* replaceMethod(Class cls, SEL origSEL, IMP newIMP) +{ + Method origMethod = class_getInstanceMethod(cls, origSEL); + void *funcPtr = (void *)method_getImplementation(origMethod); + if (!class_addMethod(cls, origSEL, newIMP, method_getTypeEncoding(origMethod))) { + method_setImplementation(origMethod, newIMP); + } + + return funcPtr; +} + +static void restoreMethod(Class cls, SEL origSEL, IMP oldIMP) +{ + Method method = class_getInstanceMethod(cls, origSEL); + method_setImplementation(method, oldIMP); +} + +static void overrideNSWindowMethods(NSWindow* window) +{ + if (!gNSWindowOverrode) { + Class cls = [window class]; + + gOrigSetStyleMask = (setStyleMaskType) replaceMethod( + cls, @selector(setStyleMask:), (IMP) __setStyleMask); + gOrigSetTitlebarAppearsTransparent = (setTitlebarAppearsTransparentType) replaceMethod( + cls, @selector(setTitlebarAppearsTransparent:), (IMP) __setTitlebarAppearsTransparent); + gOrigCanBecomeKeyWindow = (canBecomeKeyWindowType) replaceMethod( + cls, @selector(canBecomeKeyWindow), (IMP) __canBecomeKeyWindow); + gOrigCanBecomeMainWindow = (canBecomeMainWindowType) replaceMethod( + cls, @selector(canBecomeMainWindow), (IMP) __canBecomeMainWindow); + gOrigSendEvent = (sendEventType) replaceMethod( + cls, @selector(sendEvent:), (IMP) __sendEvent); + + gNSWindowOverrode = true; + } + + gFlsWindows.append(window); +} + +static void restoreNSWindowMethods(NSWindow* window) +{ + gFlsWindows.removeAll(window); + if (gFlsWindows.size() == 0) { + Class cls = [window class]; + + restoreMethod(cls, @selector(setStyleMask:), (IMP) gOrigSetStyleMask); + gOrigSetStyleMask = nullptr; + + restoreMethod(cls, @selector(setTitlebarAppearsTransparent:), (IMP) gOrigSetTitlebarAppearsTransparent); + gOrigSetTitlebarAppearsTransparent = nullptr; + + restoreMethod(cls, @selector(canBecomeKeyWindow), (IMP) gOrigCanBecomeKeyWindow); + gOrigCanBecomeKeyWindow = nullptr; + + restoreMethod(cls, @selector(canBecomeMainWindow), (IMP) gOrigCanBecomeMainWindow); + gOrigCanBecomeMainWindow = nullptr; + + restoreMethod(cls, @selector(sendEvent:), (IMP) gOrigSendEvent); + gOrigSendEvent = nullptr; + + gNSWindowOverrode = false; + } + +} + +static QHash gQWindowToNSWindow; + +static NSWindow* getNSWindow(QWindow* w) +{ + NSView* view = reinterpret_cast(w->winId()); + if (view == nullptr) + return nullptr; + return [view window]; +} + +bool setMacWindowHook(QWindow* w) +{ + NSWindow* nswindow = getNSWindow(w); + if (nswindow == nullptr) + return false; + + gQWindowToNSWindow.insert(w, nswindow); + overrideNSWindowMethods(nswindow); + + return true; +} + +bool unsetMacWindowHook(QWindow* w) +{ + if (!gQWindowToNSWindow.contains(w)) + return false; + NSWindow* obj = gQWindowToNSWindow[w]; + gQWindowToNSWindow.remove(w); + restoreNSWindowMethods(obj); + + return true; +} + +bool setMacWindowFrameless(QWindow* w) +{ + NSView* view = reinterpret_cast(w->winId()); + if (view == nullptr) + return false; + NSWindow* nswindow = [view window]; + if (nswindow == nullptr) + return false; + + view.wantsLayer = YES; + + nswindow.styleMask = nswindow.styleMask | NSWindowStyleMaskFullSizeContentView; + nswindow.titlebarAppearsTransparent = true; + nswindow.titleVisibility = NSWindowTitleHidden; + nswindow.hasShadow = true; + nswindow.showsToolbarButton = false; + nswindow.movableByWindowBackground = false; + nswindow.movable = false; + [nswindow standardWindowButton:NSWindowCloseButton].hidden = true; + [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = true; + [nswindow standardWindowButton:NSWindowZoomButton].hidden = true; + [nswindow makeKeyWindow]; + return true; +} + +bool startMacDrag(QWindow* w, const QPoint& pos) +{ + NSView* view = reinterpret_cast(w->winId()); + if (view == nullptr) + return false; + NSWindow* nswindow = [view window]; + if (nswindow == nullptr) + return false; + + CGEventRef clickDown = CGEventCreateMouseEvent( + NULL, kCGEventLeftMouseDown, CGPointMake(pos.x(), pos.y()), kCGMouseButtonLeft); + NSEvent *nsevent = [NSEvent eventWithCGEvent:clickDown]; + [nswindow performWindowDragWithEvent:nsevent]; + CFRelease(clickDown); + return true; +} + +Qt::MouseButtons getMacMouseButtons() +{ + return static_cast((uint)(NSEvent.pressedMouseButtons & Qt::MouseButtonMask)); +} + +} // namespace Utilities + +FRAMELESSHELPER_END_NAMESPACE From de1d6abaf019252ed6d8369314f18b55b1b1bf9d Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Sat, 2 Oct 2021 15:50:22 +0800 Subject: [PATCH 2/2] add showMacWindowButton --- examples/minimal/flwindow.cpp | 10 +++++++++- utilities.h | 1 + utilities_macos.mm | 28 ++++++++++++++++++++++++---- 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp index 0a998d4..44e9346 100644 --- a/examples/minimal/flwindow.cpp +++ b/examples/minimal/flwindow.cpp @@ -1,5 +1,6 @@ #include "flwindow.h" #include "../../framelesshelper.h" +#include "../../utilities.h" #include #include @@ -30,6 +31,13 @@ void FLWindow::initFramelessWindow() helper->setHitTestVisible(m_maximizeButton); helper->setHitTestVisible(m_closeButton); helper->install(); + +#ifdef Q_OS_MAC + m_minimizeButton->hide(); + m_maximizeButton->hide(); + m_closeButton->hide(); + Utilities::showMacWindowButton(windowHandle()); +#endif } void FLWindow::showEvent(QShowEvent *event) @@ -89,4 +97,4 @@ void FLWindow::setupUi() mainLayout->addWidget(m_titleBarWidget); mainLayout->addStretch(); setLayout(mainLayout); -} \ No newline at end of file +} diff --git a/utilities.h b/utilities.h index e2b50ea..9e2b154 100644 --- a/utilities.h +++ b/utilities.h @@ -74,6 +74,7 @@ FRAMELESSHELPER_API bool unsetMacWindowHook(QWindow* w); FRAMELESSHELPER_API bool setMacWindowFrameless(QWindow* w); FRAMELESSHELPER_API bool startMacDrag(QWindow* w, const QPoint& pos); FRAMELESSHELPER_API Qt::MouseButtons getMacMouseButtons(); +FRAMELESSHELPER_API bool showMacWindowButton(QWindow *w); #endif // Q_OS_MAC } diff --git a/utilities_macos.mm b/utilities_macos.mm index 638cc11..816f07a 100644 --- a/utilities_macos.mm +++ b/utilities_macos.mm @@ -223,16 +223,36 @@ bool setMacWindowFrameless(QWindow* w) nswindow.titlebarAppearsTransparent = true; nswindow.titleVisibility = NSWindowTitleHidden; nswindow.hasShadow = true; - nswindow.showsToolbarButton = false; + nswindow.movableByWindowBackground = false; nswindow.movable = false; - [nswindow standardWindowButton:NSWindowCloseButton].hidden = true; - [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = true; - [nswindow standardWindowButton:NSWindowZoomButton].hidden = true; + + nswindow.showsToolbarButton = false; + [nswindow standardWindowButton:NSWindowCloseButton].hidden = true; + [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = true; + [nswindow standardWindowButton:NSWindowZoomButton].hidden = true; + [nswindow makeKeyWindow]; return true; } +bool showMacWindowButton(QWindow *w) +{ + NSView* view = reinterpret_cast(w->winId()); + if (view == nullptr) + return false; + NSWindow* nswindow = [view window]; + if (nswindow == nullptr) + return false; + + nswindow.showsToolbarButton = true; + [nswindow standardWindowButton:NSWindowCloseButton].hidden = false; + [nswindow standardWindowButton:NSWindowMiniaturizeButton].hidden = false; + [nswindow standardWindowButton:NSWindowZoomButton].hidden = false; + + return true; +} + bool startMacDrag(QWindow* w, const QPoint& pos) { NSView* view = reinterpret_cast(w->winId());