From 4b6c1448047362b8c38d265e6414f0e3e59b8d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Mon, 25 Sep 2017 14:11:00 +0200 Subject: Revert "Revert "macOS: Deduplicate QNSWindow/QNSPanel code"" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 3ad8295451aa30a212eb6efe6330b5bfccd9f599. The fix for the issue was landed in 5.10 as 1be1ed014b1deacb51fef4, and will be merged into dev as a followup. Change-Id: Id7773d1cc2caecbe358aadd9ade427a9c1eed9ef Reviewed-by: Morten Johan Sørvig --- src/plugins/platforms/cocoa/qcocoahelpers.h | 129 ++++++++++++ src/plugins/platforms/cocoa/qcocoaintegration.mm | 2 +- src/plugins/platforms/cocoa/qcocoawindow.mm | 27 ++- src/plugins/platforms/cocoa/qnswindow.h | 50 +---- src/plugins/platforms/cocoa/qnswindow.mm | 237 +++++++---------------- 5 files changed, 232 insertions(+), 213 deletions(-) diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h index 4478895538..1f4f9cd276 100644 --- a/src/plugins/platforms/cocoa/qcocoahelpers.h +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -55,6 +55,9 @@ #include #include +#include +#include + Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSView)); QT_BEGIN_NAMESPACE @@ -188,5 +191,131 @@ QT_END_NAMESPACE QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper); +// ------------------------------------------------------------------------- + +// Depending on the ABI of the platform, we may need to use objc_msgSendSuper_stret: +// - http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html +// - https://lists.apple.com/archives/cocoa-dev/2008/Feb/msg02338.html +template +struct objc_msgsend_requires_stret +{ static const bool value = +#if defined(Q_PROCESSOR_X86) + // Any return value larger than two registers on i386/x86_64 + sizeof(T) > sizeof(void*) * 2; +#elif defined(Q_PROCESSOR_ARM_32) + // Any return value larger than a single register on arm + sizeof(T) > sizeof(void*); +#elif defined(Q_PROCESSOR_ARM_64) + // Stret not used on arm64 + false; +#endif +}; + +template <> +struct objc_msgsend_requires_stret +{ static const bool value = false; }; + +template +ReturnType qt_msgSendSuper(id receiver, SEL selector, Args... args) +{ + static_assert(!objc_msgsend_requires_stret::value, + "The given return type requires stret on this platform"); + + typedef ReturnType (*SuperFn)(objc_super *, SEL, Args...); + SuperFn superFn = reinterpret_cast(objc_msgSendSuper); + objc_super sup = { receiver, class_getSuperclass(object_getClass(receiver)) }; + return superFn(&sup, selector, args...); +} + +template +ReturnType qt_msgSendSuper_stret(id receiver, SEL selector, Args... args) +{ + static_assert(objc_msgsend_requires_stret::value, + "The given return type does not use stret on this platform"); + + typedef void (*SuperStretFn)(ReturnType *, objc_super *, SEL, Args...); + SuperStretFn superStretFn = reinterpret_cast(objc_msgSendSuper_stret); + + objc_super sup = { receiver, class_getSuperclass(object_getClass(receiver)) }; + ReturnType ret; + superStretFn(&ret, &sup, selector, args...); + return ret; +} + +template +class QSendSuperHelper { +public: + QSendSuperHelper(id receiver, SEL sel, Args... args) + : m_receiver(receiver), m_selector(sel), m_args(std::make_tuple(args...)), m_sent(false) + { + } + + ~QSendSuperHelper() + { + if (!m_sent) + msgSendSuper(m_args); + } + + template + operator ReturnType() + { +#if defined(QT_DEBUG) + Method method = class_getInstanceMethod(object_getClass(m_receiver), m_selector); + char returnTypeEncoding[256]; + method_getReturnType(method, returnTypeEncoding, sizeof(returnTypeEncoding)); + NSUInteger alignedReturnTypeSize = 0; + NSGetSizeAndAlignment(returnTypeEncoding, nullptr, &alignedReturnTypeSize); + Q_ASSERT(alignedReturnTypeSize == sizeof(ReturnType)); +#endif + m_sent = true; + return msgSendSuper(m_args); + } + +private: + template + struct index {}; + + template + struct gen_seq : gen_seq {}; + + template + struct gen_seq<0, Ts...> : index {}; + + template + using if_requires_stret = typename std::enable_if::value == V, ReturnType>::type; + + template + if_requires_stret msgSendSuper(std::tuple& args, index) + { + return qt_msgSendSuper(m_receiver, m_selector, std::get(args)...); + } + + template + if_requires_stret msgSendSuper(std::tuple& args, index) + { + return qt_msgSendSuper_stret(m_receiver, m_selector, std::get(args)...); + } + + template + ReturnType msgSendSuper(std::tuple& args) + { + return msgSendSuper(args, gen_seq{}); + } + + id m_receiver; + SEL m_selector; + std::tuple m_args; + bool m_sent; +}; + +template +QSendSuperHelper qt_objcDynamicSuperHelper(id receiver, SEL selector, Args... args) +{ + return QSendSuperHelper(receiver, selector, args...); +} + +// Same as calling super, but the super_class field resolved at runtime instead of compile time +#define qt_objcDynamicSuper(...) qt_objcDynamicSuperHelper(self, _cmd, ##__VA_ARGS__) + #endif //QCOCOAHELPERS_H diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 1df74c986a..7b1e689388 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -218,7 +218,7 @@ QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const continue; id proto = static_cast >(nsWindow); - QCocoaWindow *cocoaWindow = proto.helper.platformWindow; + QCocoaWindow *cocoaWindow = proto.platformWindow; if (!cocoaWindow) continue; window = cocoaWindow->window(); diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index d3f26df6c5..e906f0fd1c 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -210,7 +210,6 @@ QCocoaWindow::~QCocoaWindow() QMacAutoReleasePool pool; [m_nsWindow makeFirstResponder:nil]; [m_nsWindow setContentView:nil]; - [m_nsWindow.helper detachFromPlatformWindow]; if ([m_view superview]) [m_view removeFromSuperview]; @@ -1279,10 +1278,34 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel) // Create NSWindow Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class]; QCocoaNSWindow *nsWindow = [[windowClass alloc] initWithContentRect:frame - screen:cocoaScreen->nativeScreen() styleMask:windowStyleMask(flags) qPlatformWindow:this]; + styleMask:windowStyleMask(flags) + // Deferring window creation breaks OpenGL (the GL context is + // set up before the window is shown and needs a proper window) + backing:NSBackingStoreBuffered defer:NO + screen:cocoaScreen->nativeScreen()]; + Q_ASSERT_X(nsWindow.screen == cocoaScreen->nativeScreen(), "QCocoaWindow", "Resulting NSScreen should match the requested NSScreen"); + nsWindow.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; + + // Prevent Cocoa from releasing the window on close. Qt + // handles the close event asynchronously and we want to + // make sure that NSWindow stays valid until the + // QCocoaWindow is deleted by Qt. + [nsWindow setReleasedWhenClosed:NO]; + + if (alwaysShowToolWindow()) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillResignActiveNotification object:nil]; + [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:) + name:NSApplicationWillBecomeActiveNotification object:nil]; + }); + } + if (targetScreen != window()->screen()) QWindowSystemInterface::handleWindowScreenChanged(window(), targetScreen); diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h index b13b6d42a9..ac9cbb978f 100644 --- a/src/plugins/platforms/cocoa/qnswindow.h +++ b/src/plugins/platforms/cocoa/qnswindow.h @@ -47,62 +47,26 @@ #include QT_FORWARD_DECLARE_CLASS(QCocoaWindow) -Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSWindowHelper)); - -// ------------------------------------------------------------------------- @interface NSWindow (FullScreenProperty) @property(readonly) BOOL qt_fullScreen; @end -// ------------------------------------------------------------------------- - @protocol QNSWindowProtocol - -@property (nonatomic, readonly) QT_MANGLE_NAMESPACE(QNSWindowHelper) *helper; - -- (id)initWithContentRect:(NSRect)contentRect screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle qPlatformWindow:(QCocoaWindow *)qpw; - -- (void)superSendEvent:(NSEvent *)theEvent; +@optional +- (BOOL)canBecomeKeyWindow; +- (void)sendEvent:(NSEvent*)theEvent; - (void)closeAndRelease; - -@end - -typedef NSWindow QCocoaNSWindow; - -// ------------------------------------------------------------------------- - -@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject -{ - QPointer _platformWindow; -} - -@property (nonatomic, readonly) QCocoaNSWindow *window; +- (void)dealloc; @property (nonatomic, readonly) QCocoaWindow *platformWindow; - -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow; -- (void)handleWindowEvent:(NSEvent *)theEvent; -- (void)detachFromPlatformWindow; - @end -QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindowHelper); - -// ------------------------------------------------------------------------- - -@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow -@end +typedef NSWindow QCocoaNSWindow; +@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); -// ------------------------------------------------------------------------- - -@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel -@end - +@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanel); -// ------------------------------------------------------------------------- - #endif // QNSWINDOW_H diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm index e44db3ff3b..e5ddd3ca0f 100644 --- a/src/plugins/platforms/cocoa/qnswindow.mm +++ b/src/plugins/platforms/cocoa/qnswindow.mm @@ -88,32 +88,72 @@ static bool isMouseEvent(NSEvent *ev) } @end -@implementation QNSWindowHelper +#define super USE_qt_objcDynamicSuper_INSTEAD + +@implementation QNSWindow + ++ (void)load +{ + const Class windowClass = [self class]; + const Class panelClass = [QNSPanel class]; + + unsigned int methodDescriptionsCount; + objc_method_description *methods = protocol_copyMethodDescriptionList( + objc_getProtocol("QNSWindowProtocol"), NO, YES, &methodDescriptionsCount); + + for (unsigned int i = 0; i < methodDescriptionsCount; ++i) { + objc_method_description method = methods[i]; + class_addMethod(panelClass, method.name, + class_getMethodImplementation(windowClass, method.name), + method.types); + } + + free(methods); +} - (QCocoaWindow *)platformWindow { - return _platformWindow.data(); + return qnsview_cast(self.contentView).platformWindow; } -- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow +- (BOOL)canBecomeKeyWindow { - if (self = [super init]) { - _window = window; - _platformWindow = platformWindow; + QCocoaWindow *pw = self.platformWindow; + if (!pw) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; - _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow]; + if ([self isKindOfClass:[QNSPanel class]]) { + // Only tool or dialog windows should become key: + Qt::WindowType type = pw->window()->type(); + if (type == Qt::Tool || type == Qt::Dialog) + return YES; - // Prevent Cocoa from releasing the window on close. Qt - // handles the close event asynchronously and we want to - // make sure that NSWindow stays valid until the - // QCocoaWindow is deleted by Qt. - [_window setReleasedWhenClosed:NO]; + return NO; + } else { + // The default implementation returns NO for title-bar less windows, + // override and return yes here to make sure popup windows such as + // the combobox popup can become the key window. + return YES; } +} + +- (BOOL)canBecomeMainWindow +{ + BOOL canBecomeMain = YES; // By default, windows can become the main window - return self; + // Windows with a transient parent (such as combobox popup windows) + // cannot become the main window: + QCocoaWindow *pw = self.platformWindow; + if (!pw || pw->window()->transientParent()) + canBecomeMain = NO; + + return canBecomeMain; } -- (void)handleWindowEvent:(NSEvent *)theEvent +- (void)sendEvent:(NSEvent*)theEvent { // We might get events for a NSWindow after the corresponding platform // window has been deleted, as the NSWindow can outlive the QCocoaWindow @@ -129,7 +169,7 @@ static bool isMouseEvent(NSEvent *ev) return; } - [self.window superSendEvent:theEvent]; + qt_objcDynamicSuper(theEvent); if (!self.platformWindow) return; // Platform window went away while processing event @@ -137,108 +177,31 @@ static bool isMouseEvent(NSEvent *ev) QCocoaWindow *pw = self.platformWindow; if (pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { NSPoint loc = [theEvent locationInWindow]; - NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; - NSRect contentFrame = [[self.window contentView] frame]; + NSRect windowFrame = [self convertRectFromScreen:self.frame]; + NSRect contentFrame = self.contentView.frame; if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) [qnsview_cast(pw->view()) handleFrameStrutMouseEvent:theEvent]; } } -- (void)detachFromPlatformWindow -{ - _platformWindow.clear(); - [self.window.delegate release]; - self.window.delegate = nil; -} - -- (void)dealloc -{ - _window = nil; - _platformWindow.clear(); - [super dealloc]; -} - -@end - -// Deferring window creation breaks OpenGL (the GL context is -// set up before the window is shown and needs a proper window) -static const bool kNoDefer = NO; - -@implementation QNSWindow - -@synthesize helper = _helper; - -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - if (self = [super initWithContentRect:contentRect styleMask:windowStyle - backing:NSBackingStoreBuffered defer:kNoDefer screen:screen]) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // The default implementation returns NO for title-bar less windows, - // override and return yes here to make sure popup windows such as - // the combobox popup can become the key window. - return YES; -} - -- (BOOL)canBecomeMainWindow -{ - BOOL canBecomeMain = YES; // By default, windows can become the main window - - // Windows with a transient parent (such as combobox popup windows) - // cannot become the main window: - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw || pw->window()->transientParent()) - canBecomeMain = NO; - - return canBecomeMain; -} - -- (void)sendEvent:(NSEvent*)theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - - (void)closeAndRelease { qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - [self.helper detachFromPlatformWindow]; + [self.delegate release]; + self.delegate = nil; + [self close]; [self release]; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" - (void)dealloc { - [_helper release]; - _helper = nil; - [super dealloc]; + qt_objcDynamicSuper(); } - -@end - -@implementation QNSPanel - -@synthesize helper = _helper; +#pragma clang diagnostic pop + (void)applicationActivationChanged:(NSNotification*)notification { @@ -284,7 +247,7 @@ static const bool kNoDefer = NO; continue; if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { - QCocoaWindow *cocoaWindow = static_cast>(window).helper.platformWindow; + QCocoaWindow *cocoaWindow = static_cast(window).platformWindow; window.level = notification.name == NSApplicationWillResignActiveNotification ? NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); } @@ -305,70 +268,10 @@ static const bool kNoDefer = NO; } } -- (id)initWithContentRect:(NSRect)contentRect - screen:(NSScreen*)screen - styleMask:(NSUInteger)windowStyle - qPlatformWindow:(QCocoaWindow *)qpw -{ - if (self = [super initWithContentRect:contentRect styleMask:windowStyle - backing:NSBackingStoreBuffered defer:kNoDefer screen:screen]) { - _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; - - if (qpw->alwaysShowToolWindow()) { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillResignActiveNotification object:nil]; - [center addObserver:[self class] selector:@selector(applicationActivationChanged:) - name:NSApplicationWillBecomeActiveNotification object:nil]; - }); - } - } - return self; -} - -- (BOOL)canBecomeKeyWindow -{ - QCocoaWindow *pw = self.helper.platformWindow; - if (!pw) - return NO; - - if (pw->shouldRefuseKeyWindowAndFirstResponder()) - return NO; - - // Only tool or dialog windows should become key: - Qt::WindowType type = pw->window()->type(); - if (type == Qt::Tool || type == Qt::Dialog) - return YES; - - return NO; -} - -- (void)sendEvent:(NSEvent*)theEvent -{ - [self.helper handleWindowEvent:theEvent]; -} - -- (void)superSendEvent:(NSEvent *)theEvent -{ - [super sendEvent:theEvent]; -} - -- (void)closeAndRelease -{ - qCDebug(lcQpaCocoaWindow) << "closeAndRelease" << self; - - [self.helper detachFromPlatformWindow]; - [self close]; - [self release]; -} - -- (void)dealloc -{ - [_helper release]; - _helper = nil; - [super dealloc]; -} +@end +@implementation QNSPanel +// Implementation shared with QNSWindow, see +[QNSWindow load] above @end + +#undef super -- cgit v1.2.3