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/TitleBar.ui b/examples/mainwindow/TitleBar.ui
index 29686ec..25a5b01 100644
--- a/examples/mainwindow/TitleBar.ui
+++ b/examples/mainwindow/TitleBar.ui
@@ -126,7 +126,7 @@
- Segoe UI
+ Arial
9
@@ -283,6 +283,8 @@
+
+
diff --git a/examples/mainwindow/mainwindow.cpp b/examples/mainwindow/mainwindow.cpp
index ea4461f..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;
}
@@ -172,4 +172,4 @@ void MainWindow::paintEvent(QPaintEvent *event)
painter.restore();
}
}
-#endif // Q_OS_MAC
\ No newline at end of file
+#endif // Q_OS_MAC
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/minimal/flwindow.h b/examples/minimal/flwindow.h
index 5d1bb19..2fbb5ed 100644
--- a/examples/minimal/flwindow.h
+++ b/examples/minimal/flwindow.h
@@ -28,4 +28,4 @@ private:
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 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 a66547e..2ac9f2d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -24,7 +24,14 @@ if(WIN32)
)
else()
if(APPLE)
- list(APPEND SOURCES core/utilities_macos.mm)
+ 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)
list(APPEND SOURCES core/utilities_linux.cpp)
diff --git a/src/core/framelesshelper.cpp b/src/core/framelesshelper.cpp
index 60dd0ba..586da14 100644
--- a/src/core/framelesshelper.cpp
+++ b/src/core/framelesshelper.cpp
@@ -528,7 +528,6 @@ bool FramelessHelper::eventFilter(QObject *object, QEvent *event)
resizeWindow(re->size());
break;
}
-
case QEvent::NonClientAreaMouseMove:
case QEvent::MouseMove:
{
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 05b264e..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,7 +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 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 6141aad..c11b1af 100644
--- a/src/core/utilities_macos.mm
+++ b/src/core/utilities_macos.mm
@@ -24,12 +24,11 @@
#include "utilities.h"
-#include
-#include
-#include
-
#include
#include
+#include
+
+#include "nswindow_proxy.h"
FRAMELESSHELPER_BEGIN_NAMESPACE
@@ -120,154 +119,22 @@ 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();
-}
-
-/*!
- 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 QHash gQWindowToNSWindow;
static NSWindow* getNSWindow(QWindow* w)
{
NSView* view = reinterpret_cast(w->winId());
- if (view == nullptr)
+ if (view == nullptr) {
+ qWarning() << "Unable to get NSView.";
return nullptr;
- return [view window];
+ }
+ NSWindow* nswindow = [view window];
+ if (nswindow == nullptr) {
+ qWarning() << "Unable to get NSWindow.";
+ return nullptr;
+ }
+
+ return nswindow;
}
bool setMacWindowHook(QWindow* w)
@@ -276,8 +143,8 @@ bool setMacWindowHook(QWindow* w)
if (nswindow == nullptr)
return false;
- gQWindowToNSWindow.insert(w, nswindow);
- overrideNSWindowMethods(nswindow);
+ NSWindowProxy *obj = new NSWindowProxy(nswindow);
+ gQWindowToNSWindow.insert(w, obj);
return true;
}
@@ -286,9 +153,10 @@ bool unsetMacWindowHook(QWindow* w)
{
if (!gQWindowToNSWindow.contains(w))
return false;
- NSWindow* obj = gQWindowToNSWindow[w];
+
+ NSWindowProxy* obj = gQWindowToNSWindow[w];
gQWindowToNSWindow.remove(w);
- restoreNSWindowMethods(obj);
+ delete obj;
return true;
}
@@ -348,29 +216,9 @@ bool unsetMacWindowFrameless(QWindow* w)
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());
- if (view == nullptr)
- return false;
- NSWindow* nswindow = [view window];
+ NSWindow* nswindow = getNSWindow(w);
if (nswindow == nullptr)
return false;
@@ -387,6 +235,35 @@ Qt::MouseButtons getMacMouseButtons()
return static_cast((uint)(NSEvent.pressedMouseButtons & Qt::MouseButtonMask));
}
+bool setStandardWindowButtonsVisibility(QWindow *w, bool visible)
+{
+ NSWindowProxy* obj = gQWindowToNSWindow[w];
+ obj->setWindowButtonVisibility(visible);
+ return true;
+}
+
+/*! The origin of \a pos is top-left of window. */
+bool setStandardWindowButtonsPosition(QWindow *w, const QPoint &pos)
+{
+ NSWindowProxy* obj = gQWindowToNSWindow[w];
+ obj->setWindowButtonVisibility(true);
+ obj->setTrafficLightPosition(pos);
+ return true;
+}
+
+QSize standardWindowButtonsSize(QWindow *w)
+{
+ NSWindow* nswindow = getNSWindow(w);
+ if (nswindow == nullptr)
+ return QSize();
+
+ 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
FRAMELESSHELPER_END_NAMESPACE
diff --git a/src/core/window_buttons_proxy.h b/src/core/window_buttons_proxy.h
new file mode 100644
index 0000000..67aebfd
--- /dev/null
+++ b/src/core/window_buttons_proxy.h
@@ -0,0 +1,59 @@
+// Copyright (c) 2021 Microsoft, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_PROXY_H_
+#define SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_PROXY_H_
+
+#include
+#import
+#include
+
+#include "scoped_nsobject.h"
+
+@class WindowButtonsProxy;
+
+// A helper view that floats above the window buttons.
+@interface ButtonsAreaHoverView : NSView {
+ @private
+ WindowButtonsProxy* proxy_;
+}
+- (id)initWithProxy:(WindowButtonsProxy*)proxy;
+@end
+
+// Manipulating the window buttons.
+@interface WindowButtonsProxy : NSObject {
+ @private
+ NSWindow* window_;
+
+ // Current left-top margin of buttons.
+ QPoint margin_;
+ // The default left-top margin.
+ QPoint default_margin_;
+
+ // Track mouse moves above window buttons.
+ BOOL show_on_hover_;
+ BOOL mouse_inside_;
+ scoped_nsobject tracking_area_;
+ scoped_nsobject hover_view_;
+}
+
+- (id)initWithWindow:(NSWindow*)window;
+
+- (void)setVisible:(BOOL)visible;
+- (BOOL)isVisible;
+
+// Only show window buttons when mouse moves above them.
+- (void)setShowOnHover:(BOOL)yes;
+
+// Set left-top margin of the window buttons..
+- (void)setMargin:(const QPoint&)margin;
+
+// Return the bounds of all 3 buttons, with margin on all sides.
+- (NSRect)getButtonsContainerBounds;
+
+- (void)redraw;
+- (void)updateTrackingAreas;
+@end
+
+#endif // SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_PROXY_H_
diff --git a/src/core/window_buttons_proxy.mm b/src/core/window_buttons_proxy.mm
new file mode 100644
index 0000000..ed517f4
--- /dev/null
+++ b/src/core/window_buttons_proxy.mm
@@ -0,0 +1,220 @@
+// Copyright (c) 2021 Microsoft, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "window_buttons_proxy.h"
+
+@implementation ButtonsAreaHoverView : NSView
+
+- (id)initWithProxy:(WindowButtonsProxy*)proxy {
+ if ((self = [super init])) {
+ proxy_ = proxy;
+ }
+ return self;
+}
+
+// Ignore all mouse events.
+- (NSView*)hitTest:(NSPoint)aPoint {
+ return nil;
+}
+
+- (void)updateTrackingAreas {
+ [proxy_ updateTrackingAreas];
+}
+
+@end
+
+@implementation WindowButtonsProxy
+
+- (id)initWithWindow:(NSWindow*)window {
+ window_ = window;
+ show_on_hover_ = NO;
+ mouse_inside_ = NO;
+
+ // Remember the default margin.
+ margin_ = default_margin_ = [self getCurrentMargin];
+
+ return self;
+}
+
+- (void)dealloc {
+ if (hover_view_)
+ [hover_view_ removeFromSuperview];
+ [super dealloc];
+}
+
+- (void)setVisible:(BOOL)visible {
+ NSView* titleBarContainer = [self titleBarContainer];
+ if (!titleBarContainer)
+ return;
+ [titleBarContainer setHidden:!visible];
+}
+
+- (BOOL)isVisible {
+ NSView* titleBarContainer = [self titleBarContainer];
+ if (!titleBarContainer)
+ return YES;
+ return ![titleBarContainer isHidden];
+}
+
+- (void)setShowOnHover:(BOOL)yes {
+ NSView* titleBarContainer = [self titleBarContainer];
+ if (!titleBarContainer)
+ return;
+ show_on_hover_ = yes;
+ // 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_) {
+ hover_view_.reset([[ButtonsAreaHoverView alloc] initWithProxy:self]);
+ [hover_view_ setFrame:[self getButtonsBounds]];
+ [titleBarContainer addSubview:hover_view_.get()];
+ } else {
+ [hover_view_ removeFromSuperview];
+ hover_view_.reset();
+ }
+ [self updateButtonsVisibility];
+}
+
+- (void)setMargin:(const QPoint&)margin {
+ if (!margin.isNull())
+ margin_ = margin;
+ else
+ margin_ = default_margin_;
+ [self redraw];
+}
+
+- (NSRect)getButtonsContainerBounds {
+ return NSInsetRect([self getButtonsBounds], -margin_.x(), -margin_.y());
+}
+
+- (void)redraw {
+ NSView* titleBarContainer = [self titleBarContainer];
+ if (!titleBarContainer)
+ return;
+
+ NSView* left = [self leftButton];
+ NSView* middle = [self middleButton];
+ NSView* right = [self rightButton];
+
+ float button_width = NSWidth(left.frame);
+ float button_height = NSHeight(left.frame);
+ float padding = NSMinX(middle.frame) - NSMaxX(left.frame);
+ float start;
+ if (false /*base::i18n::IsRTL()*/)
+ start =
+ NSWidth(window_.frame) - 3 * button_width - 2 * padding - margin_.x();
+ else
+ start = margin_.x();
+
+ NSRect cbounds = titleBarContainer.frame;
+ cbounds.size.height = button_height + 2 * margin_.y();
+ cbounds.origin.y = NSHeight(window_.frame) - NSHeight(cbounds);
+ [titleBarContainer setFrame:cbounds];
+
+ [left setFrameOrigin:NSMakePoint(start, margin_.y())];
+ start += button_width + padding;
+ [middle setFrameOrigin:NSMakePoint(start, margin_.y())];
+ start += button_width + padding;
+ [right setFrameOrigin:NSMakePoint(start, margin_.y())];
+
+ if (hover_view_)
+ [hover_view_ setFrame:[self getButtonsBounds]];
+}
+
+- (void)updateTrackingAreas {
+ if (tracking_area_)
+ [hover_view_ removeTrackingArea:tracking_area_.get()];
+ tracking_area_.reset([[NSTrackingArea alloc]
+ initWithRect:NSZeroRect
+ options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
+ NSTrackingInVisibleRect
+ owner:self
+ userInfo:nil]);
+ [hover_view_ addTrackingArea:tracking_area_.get()];
+}
+
+- (void)mouseEntered:(NSEvent*)event {
+ mouse_inside_ = YES;
+ [self updateButtonsVisibility];
+}
+
+- (void)mouseExited:(NSEvent*)event {
+ mouse_inside_ = NO;
+ [self updateButtonsVisibility];
+}
+
+- (void)updateButtonsVisibility {
+ NSArray* buttons = @[
+ [window_ standardWindowButton:NSWindowCloseButton],
+ [window_ standardWindowButton:NSWindowMiniaturizeButton],
+ [window_ standardWindowButton:NSWindowZoomButton],
+ ];
+ // Show buttons when mouse hovers above them.
+ BOOL hidden = show_on_hover_ && !mouse_inside_;
+ // Always show buttons under fullscreen.
+ if ([window_ styleMask] & NSWindowStyleMaskFullScreen)
+ hidden = NO;
+ for (NSView* button in buttons) {
+ [button setHidden:hidden];
+ [button setNeedsDisplay:YES];
+ }
+}
+
+// Return the bounds of all 3 buttons.
+- (NSRect)getButtonsBounds {
+ NSView* left = [self leftButton];
+ NSView* right = [self rightButton];
+ return NSMakeRect(NSMinX(left.frame), NSMinY(left.frame),
+ NSMaxX(right.frame) - NSMinX(left.frame),
+ NSHeight(left.frame));
+}
+
+// Compute margin from position of current buttons.
+- (QPoint)getCurrentMargin {
+ QPoint result;
+ NSView* titleBarContainer = [self titleBarContainer];
+ if (!titleBarContainer)
+ return result;
+
+ NSView* left = [self leftButton];
+ NSView* right = [self rightButton];
+
+ result.setX((NSHeight(titleBarContainer.frame) - NSHeight(left.frame)) / 2);
+
+ if (false /*base::i18n::IsRTL()*/)
+ result.setX(NSWidth(window_.frame) - NSMaxX(right.frame));
+ else
+ result.setX(NSMinX(left.frame));
+ return result;
+}
+
+// Receive the titlebar container, which might be nil if the window does not
+// have the NSWindowStyleMaskTitled style.
+- (NSView*)titleBarContainer {
+ NSView* left = [self leftButton];
+ if (!left.superview)
+ return nil;
+ return left.superview.superview;
+}
+
+// Receive the window buttons, note that the buttons might be removed and
+// re-added on the fly so we should not cache them.
+- (NSButton*)leftButton {
+ if (false /*base::i18n::IsRTL()*/)
+ return [window_ standardWindowButton:NSWindowZoomButton];
+ else
+ return [window_ standardWindowButton:NSWindowCloseButton];
+}
+
+- (NSButton*)middleButton {
+ return [window_ standardWindowButton:NSWindowMiniaturizeButton];
+}
+
+- (NSButton*)rightButton {
+ if (false /*base::i18n::IsRTL()*/)
+ return [window_ standardWindowButton:NSWindowCloseButton];
+ else
+ return [window_ standardWindowButton:NSWindowZoomButton];
+}
+
+@end