make mac window buttons location settable

This commit is contained in:
Altair Wei 2021-10-11 18:03:44 +08:00
parent 92681dc9d9
commit dcb5fb1da4
12 changed files with 604 additions and 312 deletions

1
.gitignore vendored
View File

@ -72,3 +72,4 @@ Thumbs.db
*.res *.res
.vscode/ .vscode/
*/.DS_Store

View File

@ -106,7 +106,7 @@ void MainWindow::showEvent(QShowEvent *event)
titleBarWidget->minimizeButton->hide(); titleBarWidget->minimizeButton->hide();
titleBarWidget->maximizeButton->hide(); titleBarWidget->maximizeButton->hide();
titleBarWidget->closeButton->hide(); titleBarWidget->closeButton->hide();
Utilities::showMacWindowButton(windowHandle()); Utilities::setStandardWindowButtonsVisibility(windowHandle(), true);
#endif // Q_OS_MAC #endif // Q_OS_MAC
inited = true; inited = true;
} }

View File

@ -36,7 +36,10 @@ void FLWindow::initFramelessWindow()
m_minimizeButton->hide(); m_minimizeButton->hide();
m_maximizeButton->hide(); m_maximizeButton->hide();
m_closeButton->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 #endif
} }

View File

@ -113,7 +113,7 @@ void Widget::showEvent(QShowEvent *event)
m_minimizeButton->hide(); m_minimizeButton->hide();
m_maximizeButton->hide(); m_maximizeButton->hide();
m_closeButton->hide(); m_closeButton->hide();
Utilities::showMacWindowButton(windowHandle()); Utilities::setStandardWindowButtonsVisibility(windowHandle(), true);
#endif // Q_OS_MAC #endif // Q_OS_MAC
} }
} }

View File

@ -26,8 +26,11 @@ else()
if(APPLE) if(APPLE)
list(APPEND SOURCES list(APPEND SOURCES
core/utilities_macos.mm core/utilities_macos.mm
core/nswindow_proxy.h
core/nswindow_proxy.mm
core/window_buttons_proxy.h core/window_buttons_proxy.h
core/window_buttons_proxy.mm core/window_buttons_proxy.mm
core/scoped_nsobject.h
) )
else() else()
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED)

65
src/core/nswindow_proxy.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef NSWINDOWPROXY_H
#define NSWINDOWPROXY_H
#include <objc/runtime.h>
#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <Quartz/Quartz.h>
#include <QtCore/qpoint.h>
#include "framelesshelper.h"
#include "scoped_nsobject.h"
#include "window_buttons_proxy.h"
@class NSWindowProxyDelegate;
class NSWindowProxy
{
private:
NSWindow* m_window;
scoped_nsobject<WindowButtonsProxy> m_buttonProxy;
scoped_nsobject<NSWindowProxyDelegate> 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<NSWindowDelegate> {
@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

291
src/core/nswindow_proxy.mm Normal file
View File

@ -0,0 +1,291 @@
#include "nswindow_proxy.h"
#include <QtGui/qguiapplication.h>
static QList<NSWindow*> 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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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

203
src/core/scoped_nsobject.h Normal file
View File

@ -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 <Foundation/NSObject.h>
#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 <typename NST>
class scoped_nsprotocol {
public:
explicit scoped_nsprotocol(NST object = nil) : object_(object) {}
scoped_nsprotocol(const scoped_nsprotocol<NST>& that) : object_([that.object_ retain]) {}
template <typename NSU>
scoped_nsprotocol(const scoped_nsprotocol<NSU>& that) : object_([that.get() retain]) {}
~scoped_nsprotocol() { [object_ release]; }
scoped_nsprotocol& operator=(const scoped_nsprotocol<NST>& 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 <class C>
void swap(scoped_nsprotocol<C>& p1, scoped_nsprotocol<C>& p2) {
p1.swap(p2);
}
template <class C>
bool operator==(C p1, const scoped_nsprotocol<C>& p2) {
return p1 == p2.get();
}
template <class C>
bool operator!=(C p1, const scoped_nsprotocol<C>& p2) {
return p1 != p2.get();
}
template <typename NST>
class scoped_nsobject : public scoped_nsprotocol<NST*> {
public:
explicit scoped_nsobject(NST* object = nil) : scoped_nsprotocol<NST*>(object) {}
scoped_nsobject(const scoped_nsobject<NST>& that) : scoped_nsprotocol<NST*>(that) {}
template <typename NSU>
scoped_nsobject(const scoped_nsobject<NSU>& that) : scoped_nsprotocol<NST*>(that) {}
scoped_nsobject& operator=(const scoped_nsobject<NST>& that) {
scoped_nsprotocol<NST*>::operator=(that);
return *this;
}
};
// Specialization to make scoped_nsobject<id> work.
template <>
class scoped_nsobject<id> : public scoped_nsprotocol<id> {
public:
explicit scoped_nsobject(id object = nil) : scoped_nsprotocol<id>(object) {}
scoped_nsobject(const scoped_nsobject<id>& that) : scoped_nsprotocol<id>(that) {}
template <typename NSU>
scoped_nsobject(const scoped_nsobject<NSU>& that) : scoped_nsprotocol<id>(that) {}
scoped_nsobject& operator=(const scoped_nsobject<id>& that) {
scoped_nsprotocol<id>::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<NSAutoreleasePool> {
private:
explicit scoped_nsobject(NSAutoreleasePool* object = nil);
FML_DISALLOW_COPY_AND_ASSIGN(scoped_nsobject);
};
#endif // SCOPED_NSOBJECT_H

View File

@ -26,6 +26,7 @@
#include "framelesshelper_global.h" #include "framelesshelper_global.h"
#include <QtGui/qwindow.h> #include <QtGui/qwindow.h>
#include <QtCore/qsize.h>
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -75,13 +76,9 @@ FRAMELESSHELPER_API bool setMacWindowFrameless(QWindow* w);
FRAMELESSHELPER_API bool unsetMacWindowFrameless(QWindow* w); FRAMELESSHELPER_API bool unsetMacWindowFrameless(QWindow* w);
FRAMELESSHELPER_API bool startMacDrag(QWindow* w, const QPoint& pos); FRAMELESSHELPER_API bool startMacDrag(QWindow* w, const QPoint& pos);
FRAMELESSHELPER_API Qt::MouseButtons getMacMouseButtons(); FRAMELESSHELPER_API Qt::MouseButtons getMacMouseButtons();
FRAMELESSHELPER_API bool showMacWindowButton(QWindow *w); FRAMELESSHELPER_API bool setStandardWindowButtonsVisibility(QWindow *w, bool visible);
FRAMELESSHELPER_API bool isMacKeyWindow(QWindow *w); FRAMELESSHELPER_API bool setStandardWindowButtonsPosition(QWindow *w, const QPoint &pos);
FRAMELESSHELPER_API bool isMacMainWindow(QWindow *w); FRAMELESSHELPER_API QSize standardWindowButtonsSize(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);
#endif // Q_OS_MAC #endif // Q_OS_MAC
} }

View File

@ -24,15 +24,11 @@
#include "utilities.h" #include "utilities.h"
#include <objc/runtime.h>
#include <Cocoa/Cocoa.h>
#include <Carbon/Carbon.h>
#include <QtGui/qguiapplication.h> #include <QtGui/qguiapplication.h>
#include <QtCore/qlist.h> #include <QtCore/qlist.h>
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include "window_buttons_proxy.h" #include "nswindow_proxy.h"
FRAMELESSHELPER_BEGIN_NAMESPACE FRAMELESSHELPER_BEGIN_NAMESPACE
@ -123,201 +119,7 @@ bool showSystemMenu(const WId winId, const QPointF &pos)
return false; return false;
} }
static QList<NSWindow*> gFlsWindows; static QHash<QWindow*, NSWindowProxy*> gQWindowToNSWindow;
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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<NSWindow *>(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<QWindow*, NSWindowData*> gQWindowToNSWindow;
static NSWindow* getNSWindow(QWindow* w) static NSWindow* getNSWindow(QWindow* w)
{ {
@ -341,7 +143,7 @@ bool setMacWindowHook(QWindow* w)
if (nswindow == nullptr) if (nswindow == nullptr)
return false; return false;
NSWindowData *obj = new NSWindowData(nswindow); NSWindowProxy *obj = new NSWindowProxy(nswindow);
gQWindowToNSWindow.insert(w, obj); gQWindowToNSWindow.insert(w, obj);
return true; return true;
@ -352,7 +154,7 @@ bool unsetMacWindowHook(QWindow* w)
if (!gQWindowToNSWindow.contains(w)) if (!gQWindowToNSWindow.contains(w))
return false; return false;
NSWindowData* obj = gQWindowToNSWindow[w]; NSWindowProxy* obj = gQWindowToNSWindow[w];
gQWindowToNSWindow.remove(w); gQWindowToNSWindow.remove(w);
delete obj; delete obj;
@ -414,20 +216,6 @@ bool unsetMacWindowFrameless(QWindow* w)
return true; 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) bool startMacDrag(QWindow* w, const QPoint& pos)
{ {
NSWindow* nswindow = getNSWindow(w); NSWindow* nswindow = getNSWindow(w);
@ -447,89 +235,33 @@ Qt::MouseButtons getMacMouseButtons()
return static_cast<Qt::MouseButtons>((uint)(NSEvent.pressedMouseButtons & Qt::MouseButtonMask)); return static_cast<Qt::MouseButtons>((uint)(NSEvent.pressedMouseButtons & Qt::MouseButtonMask));
} }
bool isMacKeyWindow(QWindow *w) bool setStandardWindowButtonsVisibility(QWindow *w, bool visible)
{ {
NSWindow* nswindow = getNSWindow(w); NSWindowProxy* obj = gQWindowToNSWindow[w];
if (nswindow == nullptr) { obj->setWindowButtonVisibility(visible);
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];
return true; 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); NSWindowProxy* obj = gQWindowToNSWindow[w];
if (nswindow == nullptr) obj->setWindowButtonVisibility(true);
return false; obj->setTrafficLightPosition(pos);
[nswindow makeMainWindow];
return true; return true;
} }
static void setButtonLocation(NSButton * btn, const QPoint &offset, NSView *contentview) QSize standardWindowButtonsSize(QWindow *w)
{
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)
{ {
NSWindow* nswindow = getNSWindow(w); NSWindow* nswindow = getNSWindow(w);
if (nswindow == nullptr) if (nswindow == nullptr)
return false; return QSize();
NSView* contentview = nswindow.contentView; NSButton* left = [nswindow standardWindowButton:NSWindowCloseButton];
if (contentview == nullptr) NSButton* right = [nswindow standardWindowButton:NSWindowZoomButton];
return false; float height = NSHeight(left.frame);
float width = NSMaxX(right.frame) - NSMinX(left.frame);
auto close = [nswindow standardWindowButton:NSWindowCloseButton]; return QSize(width, height);
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);
} }
} // namespace Utilities } // namespace Utilities

View File

@ -9,6 +9,8 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#include <QtCore/qpoint.h> #include <QtCore/qpoint.h>
#include "scoped_nsobject.h"
@class WindowButtonsProxy; @class WindowButtonsProxy;
// A helper view that floats above the window buttons. // A helper view that floats above the window buttons.
@ -32,8 +34,8 @@
// Track mouse moves above window buttons. // Track mouse moves above window buttons.
BOOL show_on_hover_; BOOL show_on_hover_;
BOOL mouse_inside_; BOOL mouse_inside_;
NSTrackingArea* tracking_area_; scoped_nsobject<NSTrackingArea> tracking_area_;
ButtonsAreaHoverView* hover_view_; scoped_nsobject<ButtonsAreaHoverView> hover_view_;
} }
- (id)initWithWindow:(NSWindow*)window; - (id)initWithWindow:(NSWindow*)window;

View File

@ -65,15 +65,12 @@
// Put a transparent view above the window buttons so we can track mouse // Put a transparent view above the window buttons so we can track mouse
// events when mouse enter/leave the window buttons. // events when mouse enter/leave the window buttons.
if (show_on_hover_) { if (show_on_hover_) {
auto old_ptr = hover_view_; hover_view_.reset([[ButtonsAreaHoverView alloc] initWithProxy:self]);
hover_view_ = [[ButtonsAreaHoverView alloc] initWithProxy:self];
[old_ptr release];
[hover_view_ setFrame:[self getButtonsBounds]]; [hover_view_ setFrame:[self getButtonsBounds]];
[titleBarContainer addSubview:hover_view_]; [titleBarContainer addSubview:hover_view_.get()];
} else { } else {
[hover_view_ removeFromSuperview]; [hover_view_ removeFromSuperview];
[hover_view_ release]; hover_view_.reset();
hover_view_ = nullptr;
} }
[self updateButtonsVisibility]; [self updateButtonsVisibility];
} }
@ -126,16 +123,14 @@
- (void)updateTrackingAreas { - (void)updateTrackingAreas {
if (tracking_area_) if (tracking_area_)
[hover_view_ removeTrackingArea:tracking_area_]; [hover_view_ removeTrackingArea:tracking_area_.get()];
auto old_ptr = tracking_area_; tracking_area_.reset([[NSTrackingArea alloc]
tracking_area_ = [[NSTrackingArea alloc]
initWithRect:NSZeroRect initWithRect:NSZeroRect
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
NSTrackingInVisibleRect NSTrackingInVisibleRect
owner:self owner:self
userInfo:nil]; userInfo:nil]);
[old_ptr release]; [hover_view_ addTrackingArea:tracking_area_.get()];
[hover_view_ addTrackingArea:tracking_area_];
} }
- (void)mouseEntered:(NSEvent*)event { - (void)mouseEntered:(NSEvent*)event {