1/**************************************************************************** 2** 3** Copyright (C) 2017 The Qt Company Ltd. 4** Contact: https://www.qt.io/licensing/ 5** 6** This file is part of the plugins of the Qt Toolkit. 7** 8** $QT_BEGIN_LICENSE:LGPL$ 9** Commercial License Usage 10** Licensees holding valid commercial Qt licenses may use this file in 11** accordance with the commercial license agreement provided with the 12** Software or, alternatively, in accordance with the terms contained in 13** a written agreement between you and The Qt Company. For licensing terms 14** and conditions see https://www.qt.io/terms-conditions. For further 15** information use the contact form at https://www.qt.io/contact-us. 16** 17** GNU Lesser General Public License Usage 18** Alternatively, this file may be used under the terms of the GNU Lesser 19** General Public License version 3 as published by the Free Software 20** Foundation and appearing in the file LICENSE.LGPL3 included in the 21** packaging of this file. Please review the following information to 22** ensure the GNU Lesser General Public License version 3 requirements 23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 24** 25** GNU General Public License Usage 26** Alternatively, this file may be used under the terms of the GNU 27** General Public License version 2.0 or (at your option) the GNU General 28** Public license version 3 or any later version approved by the KDE Free 29** Qt Foundation. The licenses are as published by the Free Software 30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 31** included in the packaging of this file. Please review the following 32** information to ensure the GNU General Public License requirements will 33** be met: https://www.gnu.org/licenses/gpl-2.0.html and 34** https://www.gnu.org/licenses/gpl-3.0.html. 35** 36** $QT_END_LICENSE$ 37** 38****************************************************************************/ 39 40#if !defined(QNSWINDOW_PROTOCOL_IMPLMENTATION) 41 42#include "qnswindow.h" 43#include "qcocoawindow.h" 44#include "qcocoahelpers.h" 45#include "qcocoaeventdispatcher.h" 46 47#include <qpa/qwindowsysteminterface.h> 48#include <qoperatingsystemversion.h> 49 50Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events"); 51 52static bool isMouseEvent(NSEvent *ev) 53{ 54 switch ([ev type]) { 55 case NSEventTypeLeftMouseDown: 56 case NSEventTypeLeftMouseUp: 57 case NSEventTypeRightMouseDown: 58 case NSEventTypeRightMouseUp: 59 case NSEventTypeMouseMoved: 60 case NSEventTypeLeftMouseDragged: 61 case NSEventTypeRightMouseDragged: 62 return true; 63 default: 64 return false; 65 } 66} 67 68@implementation NSWindow (FullScreenProperty) 69 70+ (void)load 71{ 72 NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; 73 [center addObserverForName:NSWindowDidEnterFullScreenNotification object:nil queue:nil 74 usingBlock:^(NSNotification *notification) { 75 objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), 76 @(YES), OBJC_ASSOCIATION_RETAIN); 77 } 78 ]; 79 [center addObserverForName:NSWindowDidExitFullScreenNotification object:nil queue:nil 80 usingBlock:^(NSNotification *notification) { 81 objc_setAssociatedObject(notification.object, @selector(qt_fullScreen), 82 nil, OBJC_ASSOCIATION_RETAIN); 83 } 84 ]; 85} 86 87- (BOOL)qt_fullScreen 88{ 89 NSNumber *number = objc_getAssociatedObject(self, @selector(qt_fullScreen)); 90 return [number boolValue]; 91} 92@end 93 94@implementation QNSWindow 95#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1 96#include "qnswindow.mm" 97#undef QNSWINDOW_PROTOCOL_IMPLMENTATION 98 99+ (void)applicationActivationChanged:(NSNotification*)notification 100{ 101 const id sender = self; 102 NSEnumerator<NSWindow*> *windowEnumerator = nullptr; 103 NSApplication *application = [NSApplication sharedApplication]; 104 105 // Unfortunately there's no NSWindowListOrderedBackToFront, 106 // so we have to manually reverse the order using an array. 107 NSMutableArray<NSWindow *> *windows = [NSMutableArray<NSWindow *> new]; 108 [application enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack 109 usingBlock:^(NSWindow *window, BOOL *) { 110 // For some reason AppKit will give us nil-windows, skip those 111 if (!window) 112 return; 113 114 [windows addObject:window]; 115 } 116 ]; 117 118 windowEnumerator = windows.reverseObjectEnumerator; 119 120 for (NSWindow *window in windowEnumerator) { 121 // We're meddling with normal and floating windows, so leave others alone 122 if (!(window.level == NSNormalWindowLevel || window.level == NSFloatingWindowLevel)) 123 continue; 124 125 // Windows that hide automatically will keep their NSFloatingWindowLevel, 126 // and hence be on top of the window stack. We don't want to affect these 127 // windows, as otherwise we might end up with key windows being ordered 128 // behind these auto-hidden windows when activating the application by 129 // clicking on a new tool window. 130 if (window.hidesOnDeactivate) 131 continue; 132 133 if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) { 134 QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow; 135 window.level = notification.name == NSApplicationWillResignActiveNotification ? 136 NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags()); 137 } 138 139 // The documentation says that "when a window enters a new level, it’s ordered 140 // in front of all its peers in that level", but that doesn't seem to be the 141 // case in practice. To keep the order correct after meddling with the window 142 // levels, we explicitly order each window to the front. Since we are iterating 143 // the windows in back-to-front order, this is okey. The call also triggers AppKit 144 // to re-evaluate the level in relation to windows from other applications, 145 // working around an issue where our tool windows would stay on top of other 146 // application windows if activation was transferred to another application by 147 // clicking on it instead of via the application switcher or Dock. Finally, we 148 // do this re-ordering for all windows (except auto-hiding ones), otherwise we would 149 // end up triggering a bug in AppKit where the tool windows would disappear behind 150 // the application window. 151 [window orderFront:sender]; 152 } 153} 154 155@end 156 157@implementation QNSPanel 158#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1 159#include "qnswindow.mm" 160#undef QNSWINDOW_PROTOCOL_IMPLMENTATION 161 162- (BOOL)worksWhenModal 163{ 164 if (!m_platformWindow) 165 return NO; 166 167 // Conceptually there are two sets of windows we need consider: 168 // 169 // - windows 'lower' in the modal session stack 170 // - windows 'within' the current modal session 171 // 172 // The first set of windows should always be blocked by the current 173 // modal session, regardless of window type. The latter set may contain 174 // windows with a transient parent, which from Qt's point of view makes 175 // them 'child' windows, so we treat them as operable within the current 176 // modal session. 177 178 if (!NSApp.modalWindow) 179 return NO; 180 181 // Special case popup windows (menus, completions, etc), as these usually 182 // don't have a transient parent set, and we don't want to block them. The 183 // assumption is that these windows are only opened intermittently, from 184 // within windows that can already be interacted with in this modal session. 185 Qt::WindowType type = m_platformWindow->window()->type(); 186 if (type == Qt::Popup) 187 return YES; 188 189 // If the current modal window (top level modal session) is not a Qt window we 190 // have no way of knowing if this window is transient child of the modal window. 191 if (![NSApp.modalWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) 192 return NO; 193 194 if (auto *modalWindow = static_cast<QCocoaNSWindow *>(NSApp.modalWindow).platformWindow) { 195 if (modalWindow->window()->isAncestorOf(m_platformWindow->window(), QWindow::IncludeTransients)) 196 return YES; 197 } 198 199 return NO; 200} 201@end 202 203#if !defined(QT_APPLE_NO_PRIVATE_APIS) 204// When creating an NSWindow the worksWhenModal function is queried, 205// and the resulting state is used to set the corresponding window tag, 206// which the window server uses to determine whether or not the window 207// should be allowed to activate via mouse clicks in the title-bar. 208// Unfortunately, prior to macOS 10.15, this window tag was never 209// updated after the initial assignment in [NSWindow _commonAwake], 210// which meant that windows that dynamically change their worksWhenModal 211// state will behave as if they were never allowed to work when modal. 212// We work around this by manually updating the window tag when needed. 213 214typedef uint32_t CGSConnectionID; 215typedef uint32_t CGSWindowID; 216 217extern "C" { 218CGSConnectionID CGSMainConnectionID() __attribute__((weak_import)); 219OSStatus CGSSetWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import)); 220OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import)); 221} 222 223@interface QNSPanel (WorksWhenModalWindowTagWorkaround) @end 224@implementation QNSPanel (WorksWhenModalWindowTagWorkaround) 225- (void)setWorksWhenModal:(BOOL)worksWhenModal 226{ 227 [super setWorksWhenModal:worksWhenModal]; 228 229 if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSCatalina) { 230 if (CGSMainConnectionID && CGSSetWindowTags && CGSClearWindowTags) { 231 static int kWorksWhenModalWindowTag = 0x40; 232 auto *function = worksWhenModal ? CGSSetWindowTags : CGSClearWindowTags; 233 function(CGSMainConnectionID(), self.windowNumber, &kWorksWhenModalWindowTag, 64); 234 } else { 235 qWarning() << "Missing APIs for window tag handling, can not update worksWhenModal state"; 236 } 237 } 238} 239@end 240#endif // QT_APPLE_NO_PRIVATE_APIS 241 242#else // QNSWINDOW_PROTOCOL_IMPLMENTATION 243 244// The following content is mixed in to the QNSWindow and QNSPanel classes via includes 245 246{ 247 // Member variables 248 QPointer<QCocoaWindow> m_platformWindow; 249} 250 251- (instancetype)initWithContentRect:(NSRect)contentRect styleMask:(NSWindowStyleMask)style 252 backing:(NSBackingStoreType)backingStoreType defer:(BOOL)defer screen:(NSScreen *)screen 253 platformWindow:(QCocoaWindow*)window 254{ 255 // Initializing the window will end up in [NSWindow _commonAwake], which calls many 256 // of the getters below. We need to set up the platform window reference first, so 257 // we can properly reflect the window's state during initialization. 258 m_platformWindow = window; 259 260 return [super initWithContentRect:contentRect styleMask:style backing:backingStoreType defer:defer screen:screen]; 261} 262 263- (QCocoaWindow *)platformWindow 264{ 265 return m_platformWindow; 266} 267 268- (NSString *)description 269{ 270 NSMutableString *description = [NSMutableString stringWithString:[super description]]; 271 272#ifndef QT_NO_DEBUG_STREAM 273 QString contentViewDescription; 274 QDebug debug(&contentViewDescription); 275 debug.nospace() << "; contentView=" << qnsview_cast(self.contentView) << ">"; 276 277 NSRange lastCharacter = [description rangeOfComposedCharacterSequenceAtIndex:description.length - 1]; 278 [description replaceCharactersInRange:lastCharacter withString:contentViewDescription.toNSString()]; 279#endif 280 281 return description; 282} 283 284- (BOOL)canBecomeKeyWindow 285{ 286 if (!m_platformWindow) 287 return NO; 288 289 if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder()) 290 return NO; 291 292 if ([self isKindOfClass:[QNSPanel class]]) { 293 // Only tool or dialog windows should become key: 294 Qt::WindowType type = m_platformWindow->window()->type(); 295 if (type == Qt::Tool || type == Qt::Dialog) 296 return YES; 297 298 return NO; 299 } else { 300 // The default implementation returns NO for title-bar less windows, 301 // override and return yes here to make sure popup windows such as 302 // the combobox popup can become the key window. 303 return YES; 304 } 305} 306 307- (BOOL)canBecomeMainWindow 308{ 309 // Windows with a transient parent (such as combobox popup windows) 310 // cannot become the main window: 311 if (!m_platformWindow || m_platformWindow->window()->transientParent()) 312 return NO; 313 314 return [super canBecomeMainWindow]; 315} 316 317- (BOOL)isOpaque 318{ 319 return m_platformWindow ? m_platformWindow->isOpaque() : [super isOpaque]; 320} 321 322- (NSColor *)backgroundColor 323{ 324 return self.styleMask == NSWindowStyleMaskBorderless ? 325 [NSColor clearColor] : [super backgroundColor]; 326} 327 328- (void)sendEvent:(NSEvent*)theEvent 329{ 330 qCDebug(lcQpaEvents) << "Sending" << theEvent << "to" << self; 331 332 // We might get events for a NSWindow after the corresponding platform 333 // window has been deleted, as the NSWindow can outlive the QCocoaWindow 334 // e.g. if being retained by other parts of AppKit, or in an auto-release 335 // pool. We guard against this in QNSView as well, as not all callbacks 336 // come via events, but if they do there's no point in propagating them. 337 if (!m_platformWindow) 338 return; 339 340 // Prevent deallocation of this NSWindow during event delivery, as we 341 // have logic further below that depends on the window being alive. 342 [[self retain] autorelease]; 343 344 const char *eventType = object_getClassName(theEvent); 345 if (QWindowSystemInterface::handleNativeEvent(m_platformWindow->window(), 346 QByteArray::fromRawData(eventType, qstrlen(eventType)), theEvent, nullptr)) { 347 return; 348 } 349 350 [super sendEvent:theEvent]; 351 352 if (!m_platformWindow) 353 return; // Platform window went away while processing event 354 355 if (m_platformWindow->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { 356 NSPoint loc = [theEvent locationInWindow]; 357 NSRect windowFrame = [self convertRectFromScreen:self.frame]; 358 NSRect contentFrame = self.contentView.frame; 359 if (NSMouseInRect(loc, windowFrame, NO) && !NSMouseInRect(loc, contentFrame, NO)) 360 [qnsview_cast(m_platformWindow->view()) handleFrameStrutMouseEvent:theEvent]; 361 } 362} 363 364- (void)closeAndRelease 365{ 366 qCDebug(lcQpaWindow) << "Closing and releasing" << self; 367 [self close]; 368 [self release]; 369} 370 371- (void)dealloc 372{ 373 qCDebug(lcQpaWindow) << "Deallocating" << self; 374 self.delegate = nil; 375 376 [super dealloc]; 377} 378 379#endif 380