diff options
Diffstat (limited to 'src/plugins/platforms')
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoahelpers.h | 129 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoaintegration.mm | 2 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qcocoawindow.mm | 27 | ||||
-rw-r--r-- | src/plugins/platforms/cocoa/qnswindow.h | 50 | ||||
-rw-r--r-- | 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 <QtGui/qpalette.h> #include <QtGui/qscreen.h> +#include <objc/runtime.h> +#include <objc/message.h> + 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 <typename T> +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<void> +{ static const bool value = false; }; + +template <typename ReturnType, typename... Args> +ReturnType qt_msgSendSuper(id receiver, SEL selector, Args... args) +{ + static_assert(!objc_msgsend_requires_stret<ReturnType>::value, + "The given return type requires stret on this platform"); + + typedef ReturnType (*SuperFn)(objc_super *, SEL, Args...); + SuperFn superFn = reinterpret_cast<SuperFn>(objc_msgSendSuper); + objc_super sup = { receiver, class_getSuperclass(object_getClass(receiver)) }; + return superFn(&sup, selector, args...); +} + +template <typename ReturnType, typename... Args> +ReturnType qt_msgSendSuper_stret(id receiver, SEL selector, Args... args) +{ + static_assert(objc_msgsend_requires_stret<ReturnType>::value, + "The given return type does not use stret on this platform"); + + typedef void (*SuperStretFn)(ReturnType *, objc_super *, SEL, Args...); + SuperStretFn superStretFn = reinterpret_cast<SuperStretFn>(objc_msgSendSuper_stret); + + objc_super sup = { receiver, class_getSuperclass(object_getClass(receiver)) }; + ReturnType ret; + superStretFn(&ret, &sup, selector, args...); + return ret; +} + +template<typename... Args> +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<void>(m_args); + } + + template <typename ReturnType> + 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<ReturnType>(m_args); + } + +private: + template <std::size_t... Ts> + struct index {}; + + template <std::size_t N, std::size_t... Ts> + struct gen_seq : gen_seq<N - 1, N - 1, Ts...> {}; + + template <std::size_t... Ts> + struct gen_seq<0, Ts...> : index<Ts...> {}; + + template <typename ReturnType, bool V> + using if_requires_stret = typename std::enable_if<objc_msgsend_requires_stret<ReturnType>::value == V, ReturnType>::type; + + template <typename ReturnType, std::size_t... Is> + if_requires_stret<ReturnType, false> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + { + return qt_msgSendSuper<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); + } + + template <typename ReturnType, std::size_t... Is> + if_requires_stret<ReturnType, true> msgSendSuper(std::tuple<Args...>& args, index<Is...>) + { + return qt_msgSendSuper_stret<ReturnType>(m_receiver, m_selector, std::get<Is>(args)...); + } + + template <typename ReturnType> + ReturnType msgSendSuper(std::tuple<Args...>& args) + { + return msgSendSuper<ReturnType>(args, gen_seq<sizeof...(Args)>{}); + } + + id m_receiver; + SEL m_selector; + std::tuple<Args...> m_args; + bool m_sent; +}; + +template<typename... Args> +QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, Args... args) +{ + return QSendSuperHelper<Args...>(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<QNSWindowProtocol> proto = static_cast<id<QNSWindowProtocol> >(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 <AppKit/AppKit.h> 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<QNSWindowProtocol> QCocoaNSWindow; - -// ------------------------------------------------------------------------- - -@interface QT_MANGLE_NAMESPACE(QNSWindowHelper) : NSObject -{ - QPointer<QCocoaWindow> _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<QNSWindowProtocol> -@end +typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow; +@interface QT_MANGLE_NAMESPACE(QNSWindow) : NSWindow<QNSWindowProtocol> @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSWindow); -// ------------------------------------------------------------------------- - -@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> -@end - +@interface QT_MANGLE_NAMESPACE(QNSPanel) : NSPanel<QNSWindowProtocol> @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<id<QNSWindowProtocol>>(window).helper.platformWindow; + QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(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 |