From dcb5fb1da4e58391805e6bc60e9b73f7711894c9 Mon Sep 17 00:00:00 2001 From: Altair Wei Date: Mon, 11 Oct 2021 18:03:44 +0800 Subject: [PATCH] make mac window buttons location settable --- .gitignore | 3 +- examples/mainwindow/mainwindow.cpp | 2 +- examples/minimal/flwindow.cpp | 5 +- examples/widget/widget.cpp | 2 +- src/CMakeLists.txt | 3 + src/core/nswindow_proxy.h | 65 ++++++ src/core/nswindow_proxy.mm | 291 +++++++++++++++++++++++++++ src/core/scoped_nsobject.h | 203 +++++++++++++++++++ src/core/utilities.h | 11 +- src/core/utilities_macos.mm | 306 ++--------------------------- src/core/window_buttons_proxy.h | 6 +- src/core/window_buttons_proxy.mm | 19 +- 12 files changed, 604 insertions(+), 312 deletions(-) create mode 100644 src/core/nswindow_proxy.h create mode 100644 src/core/nswindow_proxy.mm create mode 100644 src/core/scoped_nsobject.h diff --git a/.gitignore b/.gitignore index 556f8ab..5fccbfe 100644 --- a/.gitignore +++ b/.gitignore @@ -71,4 +71,5 @@ Thumbs.db .qmake.conf *.res -.vscode/ \ No newline at end of file +.vscode/ +*/.DS_Store \ No newline at end of file diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp index bae30e9..49d2c0c 100644 --- a/examples/mainwindow/mainwindow.cpp +++ b/examples/mainwindow/mainwindow.cpp @@ -106,7 +106,7 @@ void MainWindow::showEvent(QShowEvent *event) titleBarWidget->minimizeButton->hide(); titleBarWidget->maximizeButton->hide(); titleBarWidget->closeButton->hide(); - Utilities::showMacWindowButton(windowHandle()); + Utilities::setStandardWindowButtonsVisibility(windowHandle(), true); #endif // Q_OS_MAC inited = true; } diff --git a/examples/minimal/flwindow.cpp b/examples/minimal/flwindow.cpp index a32beb3..7d1c997 100644 --- a/examples/minimal/flwindow.cpp +++ b/examples/minimal/flwindow.cpp @@ -36,7 +36,10 @@ void FLWindow::initFramelessWindow() m_minimizeButton->hide(); m_maximizeButton->hide(); m_closeButton->hide(); - Utilities::showMacWindowButton(windowHandle()); + Utilities::setStandardWindowButtonsVisibility(windowHandle(), true); + auto btnGroupSize = Utilities::standardWindowButtonsSize(windowHandle()); + Utilities::setStandardWindowButtonsPosition(windowHandle(), + QPoint(12, (m_titleBarWidget->height() - btnGroupSize.height())/2)); #endif } diff --git a/examples/widget/widget.cpp b/examples/widget/widget.cpp index 7710e17..819833c 100644 --- a/examples/widget/widget.cpp +++ b/examples/widget/widget.cpp @@ -113,7 +113,7 @@ void Widget::showEvent(QShowEvent *event) m_minimizeButton->hide(); m_maximizeButton->hide(); m_closeButton->hide(); - Utilities::showMacWindowButton(windowHandle()); + Utilities::setStandardWindowButtonsVisibility(windowHandle(), true); #endif // Q_OS_MAC } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80533c0..2ac9f2d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -26,8 +26,11 @@ else() if(APPLE) list(APPEND SOURCES core/utilities_macos.mm + core/nswindow_proxy.h + core/nswindow_proxy.mm core/window_buttons_proxy.h core/window_buttons_proxy.mm + core/scoped_nsobject.h ) else() find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) diff --git a/src/core/nswindow_proxy.h b/src/core/nswindow_proxy.h new file mode 100644 index 0000000..e8c742d --- /dev/null +++ b/src/core/nswindow_proxy.h @@ -0,0 +1,65 @@ +#ifndef NSWINDOWPROXY_H +#define NSWINDOWPROXY_H + +#include +#include +#include +#include + +#include + +#include "framelesshelper.h" +#include "scoped_nsobject.h" +#include "window_buttons_proxy.h" + +@class NSWindowProxyDelegate; + +class NSWindowProxy +{ +private: + NSWindow* m_window; + scoped_nsobject m_buttonProxy; + scoped_nsobject m_windowDelegate; + bool m_windowButtonVisibility; + QPoint m_trafficLightPosition; + +public: + NSWindowProxy(NSWindow *window); + ~NSWindowProxy(); + + NSWindow* window() { return m_window; } + + QPoint trafficLightPosition() { return m_trafficLightPosition; } + void setTrafficLightPosition(const QPoint &pos); + + bool windowButtonVisibility() { return m_windowButtonVisibility; } + void setWindowButtonVisibility(bool visible); + + void redrawTrafficLights(); + + bool isFullscreen() const; + void setTitle(const QString& title); + + void notifyWindowEnterFullScreen(); + void notifyWindowLeaveFullScreen(); + void notifyWindowWillEnterFullScreen(); + void notifyWindowWillLeaveFullScreen(); + +}; + +@interface NSWindowProxyDelegate : NSObject { + @private + NSWindowProxy* m_windowProxy; + bool m_isZooming; + int m_level; + bool m_isResizable; + + // Only valid during a live resize. + // Used to keep track of whether a resize is happening horizontally or + // vertically, even if physically the user is resizing in both directions. + bool m_resizingHorizontally; +} +- (id)initWithWindowProxy:(NSWindowProxy*)proxy; +@end + +#endif // NSWINDOWPROXY_H diff --git a/src/core/nswindow_proxy.mm b/src/core/nswindow_proxy.mm new file mode 100644 index 0000000..398a68a --- /dev/null +++ b/src/core/nswindow_proxy.mm @@ -0,0 +1,291 @@ +#include "nswindow_proxy.h" + +#include + +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(); +} + +typedef BOOL (*isFlippedType)(id, SEL); +static isFlippedType gOrigIsFlipped = nullptr; +static BOOL __isFlipped(id obj, SEL sel) +{ + if (!gFlsWindows.contains(reinterpret_cast(obj))) + return true; + + if (gOrigIsFlipped != nullptr) + return gOrigIsFlipped(obj, sel); + + return false; +} + +/*! + 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); + //gOrigIsFlipped = (isFlippedType) replaceMethod( + // cls, @selector (isFlipped), (IMP) __isFlipped); + + 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; + + //restoreMethod(cls, @selector(isFlipped), (IMP) gOrigIsFlipped); + //gOrigIsFlipped = nullptr; + + gNSWindowOverrode = false; + } + +} + +NSWindowProxy::NSWindowProxy(NSWindow *window) + : m_windowButtonVisibility(false) + , m_buttonProxy(nullptr) + , m_window(window) +{ + overrideNSWindowMethods(window); + m_buttonProxy.reset([[WindowButtonsProxy alloc] initWithWindow:window]); + m_windowDelegate.reset([[NSWindowProxyDelegate alloc] initWithWindowProxy:this]); + [m_window setDelegate:m_windowDelegate.get()]; +} + +NSWindowProxy::~NSWindowProxy() +{ + restoreNSWindowMethods(m_window); + [m_buttonProxy release]; +} + +void NSWindowProxy::setTrafficLightPosition(const QPoint &pos) { + m_trafficLightPosition = pos; + if (m_buttonProxy) { + [m_buttonProxy setMargin:m_trafficLightPosition]; + } +} + +void NSWindowProxy::setWindowButtonVisibility(bool visible) { + m_windowButtonVisibility = visible; + // The visibility of window buttons are managed by |buttons_proxy_| if the + // style is customButtonsOnHover. + if (false /*title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover*/) + [m_buttonProxy setVisible:visible]; + else { + [[m_window standardWindowButton:NSWindowCloseButton] setHidden:!visible]; + [[m_window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!visible]; + [[m_window standardWindowButton:NSWindowZoomButton] setHidden:!visible]; + } +} + +bool NSWindowProxy::isFullscreen() const { + return [m_window styleMask] & NSWindowStyleMaskFullScreen; +} + +void NSWindowProxy::redrawTrafficLights() { + if (m_buttonProxy && !isFullscreen()) + [m_buttonProxy redraw]; +} + +void NSWindowProxy::setTitle(const QString& title) { + [m_window setTitle:title.toNSString()]; + if (m_buttonProxy) + [m_buttonProxy redraw]; +} + +void NSWindowProxy::notifyWindowEnterFullScreen() { + // Restore the window title under fullscreen mode. + if (m_buttonProxy) { + [m_window setTitleVisibility:NSWindowTitleVisible]; + } +} + +void NSWindowProxy::notifyWindowLeaveFullScreen() { + // Restore window buttons. + if (m_buttonProxy && m_windowButtonVisibility) { + [m_buttonProxy redraw]; + [m_buttonProxy setVisible:YES]; + } +} + +void NSWindowProxy::notifyWindowWillEnterFullScreen() { + +} + +void NSWindowProxy::notifyWindowWillLeaveFullScreen() { + if (m_buttonProxy) { + // Hide window title when leaving fullscreen. + [m_window setTitleVisibility:NSWindowTitleHidden]; + // Hide the container otherwise traffic light buttons jump. + [m_buttonProxy setVisible:NO]; + } +} + +@implementation NSWindowProxyDelegate +- (id)initWithWindowProxy:(NSWindowProxy*)proxy { + m_windowProxy = proxy; + return self; +} + +- (void)windowDidBecomeMain:(NSNotification*)notification { + m_windowProxy->redrawTrafficLights(); +} + +- (void)windowDidResignMain:(NSNotification*)notification { + m_windowProxy->redrawTrafficLights(); +} + +- (void)windowDidBecomeKey:(NSNotification*)notification { + m_windowProxy->redrawTrafficLights(); +} + +- (void)windowDidResignKey:(NSNotification*)notification { + // If our app is still active and we're still the key window, ignore this + // message, since it just means that a menu extra (on the "system status bar") + // was activated; we'll get another |-windowDidResignKey| if we ever really + // lose key window status. + if ([NSApp isActive] && ([NSApp keyWindow] == [notification object])) + return; + + m_windowProxy->redrawTrafficLights(); +} + +- (void)windowDidResize:(NSNotification*)notification { + m_windowProxy->redrawTrafficLights(); +} + +- (void)windowWillEnterFullScreen:(NSNotification*)notification { + m_windowProxy->notifyWindowWillEnterFullScreen(); +} + +- (void)windowDidEnterFullScreen:(NSNotification*)notification { + m_windowProxy->notifyWindowEnterFullScreen(); +} + +- (void)windowWillExitFullScreen:(NSNotification*)notification { + m_windowProxy->notifyWindowWillLeaveFullScreen(); +} + +- (void)windowDidExitFullScreen:(NSNotification*)notification { + m_windowProxy->notifyWindowLeaveFullScreen(); +} +@end diff --git a/src/core/scoped_nsobject.h b/src/core/scoped_nsobject.h new file mode 100644 index 0000000..de506a5 --- /dev/null +++ b/src/core/scoped_nsobject.h @@ -0,0 +1,203 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SCOPED_NSOBJECT_H +#define SCOPED_NSOBJECT_H + +// Include NSObject.h directly because Foundation.h pulls in many dependencies. +// (Approx 100k lines of code versus 1.5k for NSObject.h). scoped_nsobject gets +// singled out because it is most typically included from other header files. +#import + +#if !defined(__GNUC__) && !defined(__clang__) && !defined(_MSC_VER) +#error Unsupported compiler. +#endif + +// Annotate a variable indicating it's ok if the variable is not used. +// (Typically used to silence a compiler warning when the assignment +// is important for some other reason.) +// Use like: +// int x = ...; +// FML_ALLOW_UNUSED_LOCAL(x); +#define FML_ALLOW_UNUSED_LOCAL(x) false ? (void)x : (void)0 + +// Annotate a typedef or function indicating it's ok if it's not used. +// Use like: +// typedef Foo Bar ALLOW_UNUSED_TYPE; +#if defined(__GNUC__) || defined(__clang__) +#define FML_ALLOW_UNUSED_TYPE __attribute__((unused)) +#else +#define FML_ALLOW_UNUSED_TYPE +#endif + +#ifndef FML_USED_ON_EMBEDDER + +#define FML_EMBEDDER_ONLY [[deprecated]] + +#else // FML_USED_ON_EMBEDDER + +#define FML_EMBEDDER_ONLY + +#endif // FML_USED_ON_EMBEDDER + +#define FML_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete + +#define FML_DISALLOW_ASSIGN(TypeName) \ + TypeName& operator=(const TypeName&) = delete + +#define FML_DISALLOW_MOVE(TypeName) \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete + +#define FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(const TypeName&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define FML_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) + +@class NSAutoreleasePool; + +// scoped_nsobject<> is patterned after scoped_ptr<>, but maintains ownership +// of an NSObject subclass object. Style deviations here are solely for +// compatibility with scoped_ptr<>'s interface, with which everyone is already +// familiar. +// +// scoped_nsobject<> takes ownership of an object (in the constructor or in +// reset()) by taking over the caller's existing ownership claim. The caller +// must own the object it gives to scoped_nsobject<>, and relinquishes an +// ownership claim to that object. scoped_nsobject<> does not call -retain, +// callers have to call this manually if appropriate. +// +// scoped_nsprotocol<> has the same behavior as scoped_nsobject, but can be used +// with protocols. +// +// scoped_nsobject<> is not to be used for NSAutoreleasePools. For +// NSAutoreleasePools use ScopedNSAutoreleasePool from +// scoped_nsautorelease_pool.h instead. +// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile +// time with a template specialization (see below). + +template +class scoped_nsprotocol { + public: + explicit scoped_nsprotocol(NST object = nil) : object_(object) {} + + scoped_nsprotocol(const scoped_nsprotocol& that) : object_([that.object_ retain]) {} + + template + scoped_nsprotocol(const scoped_nsprotocol& that) : object_([that.get() retain]) {} + + ~scoped_nsprotocol() { [object_ release]; } + + scoped_nsprotocol& operator=(const scoped_nsprotocol& that) { + reset([that.get() retain]); + return *this; + } + + void reset(NST object = nil) { + // We intentionally do not check that object != object_ as the caller must + // either already have an ownership claim over whatever it passes to this + // method, or call it with the |RETAIN| policy which will have ensured that + // the object is retained once more when reaching this point. + [object_ release]; + object_ = object; + } + + bool operator==(NST that) const { return object_ == that; } + bool operator!=(NST that) const { return object_ != that; } + + operator NST() const { return object_; } + + NST get() const { return object_; } + + void swap(scoped_nsprotocol& that) { + NST temp = that.object_; + that.object_ = object_; + object_ = temp; + } + + // Shift reference to the autorelease pool to be released later. + NST autorelease() { return [release() autorelease]; } + + private: + NST object_; + + // scoped_nsprotocol<>::release() is like scoped_ptr<>::release. It is NOT a + // wrapper for [object_ release]. To force a scoped_nsprotocol<> to call + // [object_ release], use scoped_nsprotocol<>::reset(). + [[nodiscard]] NST release() { + NST temp = object_; + object_ = nil; + return temp; + } +}; + +// Free functions +template +void swap(scoped_nsprotocol& p1, scoped_nsprotocol& p2) { + p1.swap(p2); +} + +template +bool operator==(C p1, const scoped_nsprotocol& p2) { + return p1 == p2.get(); +} + +template +bool operator!=(C p1, const scoped_nsprotocol& p2) { + return p1 != p2.get(); +} + +template +class scoped_nsobject : public scoped_nsprotocol { + public: + explicit scoped_nsobject(NST* object = nil) : scoped_nsprotocol(object) {} + + scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} + + template + scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} + + scoped_nsobject& operator=(const scoped_nsobject& that) { + scoped_nsprotocol::operator=(that); + return *this; + } +}; + +// Specialization to make scoped_nsobject work. +template <> +class scoped_nsobject : public scoped_nsprotocol { + public: + explicit scoped_nsobject(id object = nil) : scoped_nsprotocol(object) {} + + scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} + + template + scoped_nsobject(const scoped_nsobject& that) : scoped_nsprotocol(that) {} + + scoped_nsobject& operator=(const scoped_nsobject& that) { + scoped_nsprotocol::operator=(that); + return *this; + } +}; + +// Do not use scoped_nsobject for NSAutoreleasePools, use +// ScopedNSAutoreleasePool instead. This is a compile time check. See details +// at top of header. +template <> +class scoped_nsobject { + private: + explicit scoped_nsobject(NSAutoreleasePool* object = nil); + FML_DISALLOW_COPY_AND_ASSIGN(scoped_nsobject); +}; + +#endif // SCOPED_NSOBJECT_H diff --git a/src/core/utilities.h b/src/core/utilities.h index ef72684..7c3cf30 100644 --- a/src/core/utilities.h +++ b/src/core/utilities.h @@ -26,6 +26,7 @@ #include "framelesshelper_global.h" #include +#include FRAMELESSHELPER_BEGIN_NAMESPACE @@ -75,13 +76,9 @@ FRAMELESSHELPER_API bool setMacWindowFrameless(QWindow* w); FRAMELESSHELPER_API bool unsetMacWindowFrameless(QWindow* w); FRAMELESSHELPER_API bool startMacDrag(QWindow* w, const QPoint& pos); FRAMELESSHELPER_API Qt::MouseButtons getMacMouseButtons(); -FRAMELESSHELPER_API bool showMacWindowButton(QWindow *w); -FRAMELESSHELPER_API bool isMacKeyWindow(QWindow *w); -FRAMELESSHELPER_API bool isMacMainWindow(QWindow *w); -FRAMELESSHELPER_API bool makeMacKeyWindow(QWindow *w); -FRAMELESSHELPER_API bool makeMacMainWindow(QWindow *w); -FRAMELESSHELPER_API bool setStandardWindowButtonsOffset(QWindow *w, const QPoint &offset); -FRAMELESSHELPER_API bool setTrafficLightPosition(QWindow *w, const QPoint &pos); +FRAMELESSHELPER_API bool setStandardWindowButtonsVisibility(QWindow *w, bool visible); +FRAMELESSHELPER_API bool setStandardWindowButtonsPosition(QWindow *w, const QPoint &pos); +FRAMELESSHELPER_API QSize standardWindowButtonsSize(QWindow *w); #endif // Q_OS_MAC } diff --git a/src/core/utilities_macos.mm b/src/core/utilities_macos.mm index ed9e37d..c11b1af 100644 --- a/src/core/utilities_macos.mm +++ b/src/core/utilities_macos.mm @@ -24,15 +24,11 @@ #include "utilities.h" -#include -#include -#include - #include #include #include -#include "window_buttons_proxy.h" +#include "nswindow_proxy.h" FRAMELESSHELPER_BEGIN_NAMESPACE @@ -123,201 +119,7 @@ bool showSystemMenu(const WId winId, const QPointF &pos) return false; } -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(); -} - -typedef BOOL (*isFlippedType)(id, SEL); -static isFlippedType gOrigIsFlipped = nullptr; -static BOOL __isFlipped(id obj, SEL sel) -{ - if (!gFlsWindows.contains(reinterpret_cast(obj))) - return true; - - if (gOrigIsFlipped != nullptr) - return gOrigIsFlipped(obj, sel); - - return false; -} - -/*! - 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); - //gOrigIsFlipped = (isFlippedType) replaceMethod( - // cls, @selector (isFlipped), (IMP) __isFlipped); - - 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; - - //restoreMethod(cls, @selector(isFlipped), (IMP) gOrigIsFlipped); - //gOrigIsFlipped = nullptr; - - gNSWindowOverrode = false; - } - -} - -class NSWindowData -{ -private: - NSWindow* m_window; - WindowButtonsProxy *m_buttonProxy; - bool m_windowButtonVisibility; - QPoint m_trafficLightPosition; - -public: - NSWindowData(NSWindow *window) - : m_windowButtonVisibility(false) - , m_buttonProxy(nullptr) - , m_window(window) - { - overrideNSWindowMethods(window); - m_buttonProxy = [[WindowButtonsProxy alloc] initWithWindow:window]; - } - - ~NSWindowData() - { - restoreNSWindowMethods(m_window); - [m_buttonProxy release]; - } - - NSWindow* window() { return m_window; } - - QPoint trafficLightPosition() { return m_trafficLightPosition; } - bool setTrafficLightPosition(const QPoint &pos) { - m_trafficLightPosition = pos; - if (m_buttonProxy) { - [m_buttonProxy setMargin:m_trafficLightPosition]; - } - return true; - } -}; - -static QHash gQWindowToNSWindow; +static QHash gQWindowToNSWindow; static NSWindow* getNSWindow(QWindow* w) { @@ -341,7 +143,7 @@ bool setMacWindowHook(QWindow* w) if (nswindow == nullptr) return false; - NSWindowData *obj = new NSWindowData(nswindow); + NSWindowProxy *obj = new NSWindowProxy(nswindow); gQWindowToNSWindow.insert(w, obj); return true; @@ -352,7 +154,7 @@ bool unsetMacWindowHook(QWindow* w) if (!gQWindowToNSWindow.contains(w)) return false; - NSWindowData* obj = gQWindowToNSWindow[w]; + NSWindowProxy* obj = gQWindowToNSWindow[w]; gQWindowToNSWindow.remove(w); delete obj; @@ -414,20 +216,6 @@ bool unsetMacWindowFrameless(QWindow* w) return true; } -bool showMacWindowButton(QWindow *w) -{ - NSWindow* nswindow = getNSWindow(w); - 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) { NSWindow* nswindow = getNSWindow(w); @@ -447,89 +235,33 @@ Qt::MouseButtons getMacMouseButtons() return static_cast((uint)(NSEvent.pressedMouseButtons & Qt::MouseButtonMask)); } -bool isMacKeyWindow(QWindow *w) +bool setStandardWindowButtonsVisibility(QWindow *w, bool visible) { - NSWindow* nswindow = getNSWindow(w); - if (nswindow == nullptr) { - qWarning() << "Unable to get NSView."; - return false; - } - - return [nswindow isKeyWindow]; -} - -bool isMacMainWindow(QWindow *w) -{ - NSWindow* nswindow = getNSWindow(w); - if (nswindow == nullptr) - return false; - - return [nswindow isMainWindow]; -} - -bool makeMacKeyWindow(QWindow *w) -{ - NSWindow* nswindow = getNSWindow(w); - if (nswindow == nullptr) - return false; - - [nswindow makeKeyWindow]; + NSWindowProxy* obj = gQWindowToNSWindow[w]; + obj->setWindowButtonVisibility(visible); return true; } -bool makeMacMainWindow(QWindow *w) +/*! The origin of \a pos is top-left of window. */ +bool setStandardWindowButtonsPosition(QWindow *w, const QPoint &pos) { - NSWindow* nswindow = getNSWindow(w); - if (nswindow == nullptr) - return false; - - [nswindow makeMainWindow]; + NSWindowProxy* obj = gQWindowToNSWindow[w]; + obj->setWindowButtonVisibility(true); + obj->setTrafficLightPosition(pos); return true; } -static void setButtonLocation(NSButton * btn, const QPoint &offset, NSView *contentview) -{ - if (btn.superview != contentview) { - [btn.superview willRemoveSubview:btn]; - [btn removeFromSuperview]; - [btn viewWillMoveToSuperview:contentview]; - [contentview addSubview:btn]; - [btn viewDidMoveToSuperview]; - } - - auto frame = btn.frame; - btn.frame = NSMakeRect( - frame.origin.x + offset.x(), - frame.origin.y + offset.y(), - frame.size.width, frame.size.height); -} - -/*! The origin of AppKit coordinate system is bottom-left. */ -bool setStandardWindowButtonsOffset(QWindow *w, const QPoint &offset) +QSize standardWindowButtonsSize(QWindow *w) { NSWindow* nswindow = getNSWindow(w); if (nswindow == nullptr) - return false; + return QSize(); - NSView* contentview = nswindow.contentView; - if (contentview == nullptr) - return false; - - auto close = [nswindow standardWindowButton:NSWindowCloseButton]; - auto min = [nswindow standardWindowButton:NSWindowMiniaturizeButton]; - auto zoom = [nswindow standardWindowButton:NSWindowZoomButton]; - - setButtonLocation(close, offset, contentview); - setButtonLocation(min, offset, contentview); - setButtonLocation(zoom, offset, contentview); - - return true; -} - -bool setTrafficLightPosition(QWindow *w, const QPoint &pos) -{ - NSWindowData* obj = gQWindowToNSWindow[w]; - return obj->setTrafficLightPosition(pos); + NSButton* left = [nswindow standardWindowButton:NSWindowCloseButton]; + NSButton* right = [nswindow standardWindowButton:NSWindowZoomButton]; + float height = NSHeight(left.frame); + float width = NSMaxX(right.frame) - NSMinX(left.frame); + return QSize(width, height); } } // namespace Utilities diff --git a/src/core/window_buttons_proxy.h b/src/core/window_buttons_proxy.h index 17a1991..67aebfd 100644 --- a/src/core/window_buttons_proxy.h +++ b/src/core/window_buttons_proxy.h @@ -9,6 +9,8 @@ #import #include +#include "scoped_nsobject.h" + @class WindowButtonsProxy; // A helper view that floats above the window buttons. @@ -32,8 +34,8 @@ // Track mouse moves above window buttons. BOOL show_on_hover_; BOOL mouse_inside_; - NSTrackingArea* tracking_area_; - ButtonsAreaHoverView* hover_view_; + scoped_nsobject tracking_area_; + scoped_nsobject hover_view_; } - (id)initWithWindow:(NSWindow*)window; diff --git a/src/core/window_buttons_proxy.mm b/src/core/window_buttons_proxy.mm index 59ef313..ed517f4 100644 --- a/src/core/window_buttons_proxy.mm +++ b/src/core/window_buttons_proxy.mm @@ -65,15 +65,12 @@ // Put a transparent view above the window buttons so we can track mouse // events when mouse enter/leave the window buttons. if (show_on_hover_) { - auto old_ptr = hover_view_; - hover_view_ = [[ButtonsAreaHoverView alloc] initWithProxy:self]; - [old_ptr release]; + hover_view_.reset([[ButtonsAreaHoverView alloc] initWithProxy:self]); [hover_view_ setFrame:[self getButtonsBounds]]; - [titleBarContainer addSubview:hover_view_]; + [titleBarContainer addSubview:hover_view_.get()]; } else { [hover_view_ removeFromSuperview]; - [hover_view_ release]; - hover_view_ = nullptr; + hover_view_.reset(); } [self updateButtonsVisibility]; } @@ -126,16 +123,14 @@ - (void)updateTrackingAreas { if (tracking_area_) - [hover_view_ removeTrackingArea:tracking_area_]; - auto old_ptr = tracking_area_; - tracking_area_ = [[NSTrackingArea alloc] + [hover_view_ removeTrackingArea:tracking_area_.get()]; + tracking_area_.reset([[NSTrackingArea alloc] initWithRect:NSZeroRect options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect owner:self - userInfo:nil]; - [old_ptr release]; - [hover_view_ addTrackingArea:tracking_area_]; + userInfo:nil]); + [hover_view_ addTrackingArea:tracking_area_.get()]; } - (void)mouseEntered:(NSEvent*)event {