Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoawindow.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6#include <QuartzCore/QuartzCore.h>
7
8#include "qcocoawindow.h"
10#include "qcocoascreen.h"
13#ifndef QT_NO_OPENGL
15#endif
16#include "qcocoahelpers.h"
19#include "qnsview.h"
20#include "qnswindow.h"
21#include <QtCore/qfileinfo.h>
22#include <QtCore/private/qcore_mac_p.h>
23#include <qwindow.h>
24#include <private/qwindow_p.h>
25#include <qpa/qwindowsysteminterface.h>
26#include <qpa/qplatformscreen.h>
27#include <QtGui/qaccessible.h>
28#include <QtGui/private/qcoregraphics_p.h>
29#include <QtGui/private/qhighdpiscaling_p.h>
30#include <QtGui/private/qmetallayer_p.h>
31
32#include <QDebug>
33
34#include <vector>
35
36QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QNSWindowController, NSWindowController
37- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow;
38)
39
40@implementation QNSWindowController {
41 QPointer<QCocoaWindow> m_platformWindow;
42}
43
44- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow
45{
46 // We need a nib name, even if we don't use a NIB, otherwise
47 // Cocoa is not going to call our loadWindow override.
48 if ((self = [super initWithWindowNibName:@"QNSWindowController"]))
49 m_platformWindow = platformWindow;
50
51 return self;
52}
53
54- (void)loadWindow
55{
56 QMacAutoReleasePool pool;
57 self.window = [m_platformWindow->createNSWindow() autorelease];
58}
59- (void)dealloc
60{
61 qCDebug(lcQpaWindow) << "Disposing of" << self.window << "for" << m_platformWindow;
62 [self.window close];
63 self.window = nil;
64 [super dealloc];
65}
66@end
67
68QT_BEGIN_NAMESPACE
69
70enum {
71 defaultWindowWidth = 160,
72 defaultWindowHeight = 160
73};
74
75Q_LOGGING_CATEGORY(lcCocoaNotifications, "qt.qpa.cocoa.notifications");
76
78{
79 static const QLatin1StringView notificationHandlerPrefix(Q_NOTIFICATION_PREFIX);
80
81 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
82
83 const QMetaObject *metaObject = QMetaType(qRegisterMetaType<QCocoaWindow*>()).metaObject();
84 Q_ASSERT(metaObject);
85
86 for (int i = 0; i < metaObject->methodCount(); ++i) {
87 QMetaMethod method = metaObject->method(i);
88 const QString methodTag = QString::fromLatin1(method.tag());
89 if (!methodTag.startsWith(notificationHandlerPrefix))
90 continue;
91
92 const QString notificationName = methodTag.mid(notificationHandlerPrefix.size());
93 [center addObserverForName:notificationName.toNSString() object:nil queue:nil
94 usingBlock:^(NSNotification *notification) {
95
96 QVarLengthArray<QCocoaWindow *, 32> cocoaWindows;
97 if ([notification.object isKindOfClass:[NSWindow class]]) {
98 NSWindow *nsWindow = notification.object;
99 for (const QWindow *window : QGuiApplication::allWindows()) {
100 if (QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()))
101 if (cocoaWindow->nativeWindow() == nsWindow)
102 cocoaWindows += cocoaWindow;
103 }
104 } else if ([notification.object isKindOfClass:[NSView class]]) {
105 if (QNSView *qnsView = qnsview_cast(notification.object))
106 cocoaWindows += qnsView.platformWindow;
107 } else {
108 qCWarning(lcCocoaNotifications) << "Unhandled notification"
109 << notification.name << "for" << notification.object;
110 return;
111 }
112
113 if (lcCocoaNotifications().isDebugEnabled() && !cocoaWindows.isEmpty()) {
114 QVector<QCocoaWindow *> debugWindows;
115 for (QCocoaWindow *cocoaWindow : cocoaWindows)
116 debugWindows += cocoaWindow;
117 qCDebug(lcCocoaNotifications) << "Forwarding" << qPrintable(notificationName) <<
118 "to" << debugWindows;
119 }
120
121 // FIXME: Could be a foreign window, look up by iterating top level QWindows
122
123 for (QCocoaWindow *cocoaWindow : cocoaWindows) {
124 if (!method.invoke(cocoaWindow, Qt::DirectConnection)) {
125 qCWarning(lcQpaWindow) << "Failed to invoke NSNotification callback for"
126 << notification.name << "on" << cocoaWindow;
127 }
128 }
129 }];
130 }
131}
132Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks)
133
134const int QCocoaWindow::NoAlertRequest = -1;
136
137QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win)
138{
139 qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window();
140
141 if (nativeHandle) {
142 m_view = reinterpret_cast<NSView *>(nativeHandle);
143 [m_view retain];
144 }
145}
146
148{
149 qCDebug(lcQpaWindow) << "QCocoaWindow::initialize" << window();
150
151 QMacAutoReleasePool pool;
152
153 if (!m_view)
154 m_view = [[QNSView alloc] initWithCocoaWindow:this];
155
156 if (!isForeignWindow()) {
157 // Compute the initial geometry based on the geometry set on the
158 // QWindow, with automatic positioning and sizing, if the position
159 // or size has been left unset.
160 auto initialGeometry = QPlatformWindow::initialGeometry(window(),
161 windowGeometry(), defaultWindowWidth, defaultWindowHeight);
162
163 // Note: The initial geometry does not incorporate whether the
164 // positionPolicy includes the frame or not. It's up to us to
165 // account for that below.
166
167 if (QPlatformWindow::parent() || isSubWindow(window())) {
168 // If we're not a top level we need to establish the superview
169 // relationship first, before we can set the geometry, so that we
170 // know whether the superview is flipped or not when setting the
171 // geometry.
173 setGeometry(initialGeometry);
174 } else {
175 if (window()->flags() & Qt::ExpandedClientAreaHint) {
176 // Make sure the expanded client area hint actually expands the
177 // client size to incorporate the frame size, had it been there.
178 QRect frameGeometryWithFrame = QCocoaScreen::mapFromNative(
179 [NSWindow frameRectForContentRect:QCocoaScreen::mapToNative(initialGeometry)
180 styleMask:windowStyleMask(window()->flags() & ~Qt::ExpandedClientAreaHint)]).toRect();
181 if (qt_window_private(window())->positionPolicy == QWindowPrivate::WindowFrameExclusive)
182 initialGeometry = frameGeometryWithFrame;
183 else
184 initialGeometry.setSize(frameGeometryWithFrame.size());
185 }
186
187 // If we're a top level window we need to create the NSWindow
188 // first, so that we know the frame margins of the window. But
189 // since the geometry will be applied during setContentView we
190 // must persist the initial geometry here, so that it's picked
191 // up that that point.
192 QPlatformWindow::setGeometry(initialGeometry);
194 // We don't need to set the initial geometry again here. And we
195 // must also be careful to not setGeometry with the newly adopted
196 // geometry, as that is now effecively WindowFrameExclusive, while
197 // the QWindow might still be set to WindowFrameInclusive.
198 }
199
200 setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
201
202 m_safeAreaInsetsObserver = QMacKeyValueObserver(
203 m_view, @"safeAreaInsets", [this] {
204 // Defer to next runloop pass, so that any changes to the
205 // margins during resizing have settled down.
206 QMetaObject::invokeMethod(this, [this]{
207 updateSafeAreaMarginsIfNeeded();
208 }, Qt::QueuedConnection);
209 }, NSKeyValueObservingOptionNew);
210
211 } else {
212 // Reparent to superview if needed
214 // Pick up essential foreign window state
215 QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
216 }
217
218 m_initialized = true;
219}
220
221const NSNotificationName QCocoaWindowWillReleaseQNSViewNotification = @"QCocoaWindowWillReleaseQNSViewNotification";
222
224{
225 qCDebug(lcQpaWindow) << "QCocoaWindow::~QCocoaWindow" << window();
226
227 QMacAutoReleasePool pool;
228
229 if (window()->flags() & Qt::ExpandedClientAreaHint) {
230 // We expanded the client area during initialization, so we need
231 // to adjust the size back now, so that recreating the window again
232 // will not keep growing it as we re-expand the size.
233 auto readjustedGeometry = geometry();
234 QRect contentRectWithFrame = QCocoaScreen::mapFromNative(
235 [NSWindow contentRectForFrameRect:QCocoaScreen::mapToNative(geometry())
236 styleMask:windowStyleMask(window()->flags() & ~Qt::ExpandedClientAreaHint)]).toRect();
237 readjustedGeometry = contentRectWithFrame;
238 setGeometry(readjustedGeometry, QWindowPrivate::PositionPolicy::WindowFrameExclusive);
239 }
240
241 // Remove from superview as long as we're not a foreign
242 // window used only to contain Qt windows (ie no parent)
243 if (!isForeignWindow() || QPlatformWindow::parent())
244 [m_view removeFromSuperview];
245
246#if QT_CONFIG(accessibility)
247 if (isForeignWindow())
248 clearAccessibleParent();
249#endif
250
251 m_safeAreaInsetsObserver = {};
252
253 // Make sure to disconnect observer in all case if view is valid
254 // to avoid notifications received when deleting when using Qt::AA_NativeWindows attribute
256 [[NSNotificationCenter defaultCenter] removeObserver:m_view];
257
258#if QT_CONFIG(vulkan)
259 if (QCocoaIntegration *cocoaIntegration = QCocoaIntegration::instance()) {
260 auto vulcanInstance = cocoaIntegration->getCocoaVulkanInstance();
261 if (vulcanInstance)
262 vulcanInstance->destroySurface(m_vulkanSurface);
263 }
264#endif
265
266 // Must send notification before calling release, as doing it from
267 // [QNSView dealloc] would mean that any weak references to the view
268 // would already return nil.
269 [NSNotificationCenter.defaultCenter
270 postNotificationName:QCocoaWindowWillReleaseQNSViewNotification
271 object:m_view];
272
273 [m_view release];
274
275 // Disposing of the view and window should have resulted in an
276 // expose event with isExposed=false, but just in case we try
277 // to stop the display link here as well.
278 static_cast<QCocoaScreen *>(screen())->maybeStopDisplayLink();
279}
280
282{
283 auto format = window()->requestedFormat();
284 if (auto *view = qnsview_cast(m_view); view.colorSpace) {
285 auto colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(view.colorSpace.ICCProfileData));
286 if (!colorSpace.isValid()) {
287 qCWarning(lcQpaWindow) << "Failed to parse ICC profile for" << view.colorSpace
288 << "with ICC data" << view.colorSpace.ICCProfileData;
289 }
290 format.setColorSpace(colorSpace);
291 }
292 return format;
293}
294
296{
297 return ![m_view isKindOfClass:[QNSView class]];
298}
299
301{
302 return QPlatformWindow::geometry();
303}
304
306{
308 // Content views are positioned at (0, 0) in the window, so we resolve via the window
309 CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame];
310
311 // The result above is in native screen coordinates, so remap to the Qt coordinate system
312 return QCocoaScreen::mapFromNative(contentRect).toRect();
313 } else {
314 return QCocoaWindow::mapFromNative(m_view.frame, m_view.superview).toRect();
315 }
316}
317
318/*!
319 \brief the geometry of the window as it will appear when shown as
320 a normal (not maximized or full screen) top-level window.
321
322 For child windows this property always holds an empty rectangle.
323
324 \sa QWidget::normalGeometry()
325*/
327{
328 if (!isContentView())
329 return QRect();
330
331 // We only persist the normal the geometry when going into
332 // fullscreen and maximized states. For all other cases we
333 // can just report the geometry as is.
334
335 if (!(windowState() & (Qt::WindowFullScreen | Qt::WindowMaximized)))
336 return geometry();
337
338 return m_normalGeometry;
339}
340
342{
343 if (!isContentView())
344 return;
345
346 if (windowState() != Qt::WindowNoState)
347 return;
348
349 m_normalGeometry = geometry();
350}
351
352void QCocoaWindow::setGeometry(const QRect &rect)
353{
354 QScopedValueRollback inSetGeometry(m_inSetGeometry, true);
355 setGeometry(rect, qt_window_private(window())->positionPolicy);
356}
357
358void QCocoaWindow::setGeometry(const QRect &rectIn, QWindowPrivate::PositionPolicy positionPolicy)
359{
360 qCDebug(lcQpaWindow) << "QCocoaWindow::setGeometry" << window() << rectIn << positionPolicy;
361 QMacAutoReleasePool pool;
362
363 QRect rect = rectIn;
364 if (positionPolicy == QWindowPrivate::WindowFrameInclusive) {
365 // This means it is a call from QWindow::setFramePosition(), so the coordinates
366 // include the frame (size is still the contents rectangle). As the functionality
367 // below operates purely in content positions, we need to remove the frame margins.
368 const QMargins margins = frameMargins();
369 rect.moveTopLeft(rect.topLeft() + QPoint(margins.left(), margins.top()));
370 qCDebug(lcQpaWindow) << "Adjusted content geometry to" << rect << "by removing frame margins" << margins;
371 }
372
373 QPlatformWindow::setGeometry(rect);
374
375 const QRect originalGeometry = actualGeometry();
376
377 if (isContentView()) {
378 if (isEmbedded()) {
379 // Sizing or moving the content view doesn't make sense when
380 // we are embedded, so report the current geometry as is.
382 } else {
383 NSRect bounds = QCocoaScreen::mapToNative(rect);
384 [m_view.window setFrame:[m_view.window frameRectForContentRect:bounds] display:YES animate:NO];
385 }
386 } else {
387 m_view.frame = QCocoaWindow::mapToNative(rect, m_view.superview);
388 }
389
390 if (actualGeometry() == originalGeometry) {
391 // The requested geometry change was rejected by the OS. This can
392 // happen when trying to move a window past the top of the screen,
393 // which is not allowed. Normally, when the OS adjusts the geometry
394 // we requested, it will be updated via windowDidMove/Resize, but
395 // if the rejected change ends up the same as the old geometry these
396 // callbacks won't be called, so we have to update manually here.
397 qCDebug(lcQpaWindow) << "Native geometry didn't change. Reporting geometry change manually.";
399 }
400}
401
403{
404 QMacAutoReleasePool pool;
405
406 // The safe area of the view reflects the area not covered by navigation
407 // bars, tab bars, toolbars, and other ancestor views that might obscure
408 // the current view (by setting additionalSafeAreaInsets). If the window
409 // uses NSWindowStyleMaskFullSizeContentView this also includes the area
410 // of the view covered by the title bar.
411 QMarginsF viewSafeAreaMargins = {
412 m_view.safeAreaInsets.left,
413 m_view.safeAreaInsets.top,
414 m_view.safeAreaInsets.right,
415 m_view.safeAreaInsets.bottom
416 };
417
418 // The screen's safe area insets represent the distances from the screen's
419 // edges at which content isn't obscured. The view's safe area margins do
420 // not include the screen's insets automatically, so we need to manually
421 // merge them.
422 auto screenRect = m_view.window.screen.frame;
423 auto screenInsets = m_view.window.screen.safeAreaInsets;
424 auto screenSafeArea = QCocoaScreen::mapFromNative(NSMakeRect(
425 NSMinX(screenRect) + screenInsets.left,
426 NSMinY(screenRect) + screenInsets.bottom, // Non-flipped
427 NSWidth(screenRect) - screenInsets.left - screenInsets.right,
428 NSHeight(screenRect) - screenInsets.top - screenInsets.bottom
429 ));
430
431 auto screenRelativeViewBounds = QCocoaScreen::mapFromNative(
432 [m_view.window convertRectToScreen:
433 [m_view convertRect:m_view.bounds toView:nil]]
434 );
435
436 // The margins are relative to the screen the window is on.
437 // Note that we do not want represent the area outside of the
438 // screen as being outside of the safe area.
439 QMarginsF screenSafeAreaMargins = {
440 qMin(screenSafeArea.left() - screenRelativeViewBounds.left(), screenInsets.left),
441 qMin(screenSafeArea.top() - screenRelativeViewBounds.top(), screenInsets.top),
442 qMin(screenRelativeViewBounds.right() - screenSafeArea.right(), screenInsets.right),
443 qMin(screenRelativeViewBounds.bottom() - screenSafeArea.bottom(), screenInsets.bottom)
444 };
445
446 return (screenSafeAreaMargins | viewSafeAreaMargins).toMargins();
447}
448
450{
451 if (safeAreaMargins() != m_lastReportedSafeAreaMargins) {
452 m_lastReportedSafeAreaMargins = safeAreaMargins();
454 }
455}
456
458{
459 // Only allow move by pressing left mouse button
460 if (!(NSEvent.pressedMouseButtons == 1))
461 return false;
462
463 // Synthesize an event, so that we don't have to rely on
464 // NSApp.currentEvent, which may not be a mouse event.
465 NSEvent *mouseEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown
466 location:m_view.window.mouseLocationOutsideOfEventStream
467 modifierFlags:NSEvent.modifierFlags
468 timestamp:NSProcessInfo.processInfo.systemUptime
469 windowNumber:m_view.window.windowNumber context:nil
470 eventNumber:0 clickCount:1 pressure:1.0];
471
472 [m_view.window performWindowDragWithEvent:mouseEvent];
473 return true;
474}
475
476void QCocoaWindow::setVisible(bool visible)
477{
478 qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
479
480 // Our implementation of setVisible below is not idempotent, as for
481 // modal windows it calls beginSheet/endSheet or starts/ends modal
482 // sessions. However we can't simply guard for m_view.hidden already
483 // having the right state, as the behavior of this function differs
484 // based on whether the window has been initialized or not, as
485 // handleGeometryChange will bail out if the window is still
486 // initializing. Since we know we'll get a second setVisible
487 // call after creation, we can check for that case specifically,
488 // which means we can then safely guard on m_view.hidden changing.
489
490 if (!m_initialized) {
491 qCDebug(lcQpaWindow) << "Window still initializing, skipping setting visibility";
492 return; // We'll get another setVisible call after create is done
493 }
494
495 if (visible == !m_view.hidden && (!isContentView() || visible == m_view.window.visible)) {
496 qCDebug(lcQpaWindow) << "No change in visible status. Ignoring.";
497 return;
498 }
499
500 if (m_inSetVisible) {
501 qCWarning(lcQpaWindow) << "Already setting window visible!";
502 return;
503 }
504
505 QScopedValueRollback<bool> rollback(m_inSetVisible, true);
506
507 QMacAutoReleasePool pool;
508 QCocoaWindow *parentCocoaWindow = nullptr;
509 if (window()->transientParent())
510 parentCocoaWindow = static_cast<QCocoaWindow *>(window()->transientParent()->handle());
511
512 auto eventDispatcher = [] {
513 return static_cast<QCocoaEventDispatcherPrivate *>(QObjectPrivate::get(qApp->eventDispatcher()));
514 };
515
516 if (visible) {
517 // The flags may have changed, in which case we may need to switch window type
519
520 // We didn't send geometry changes during creation, as that would have confused
521 // Qt, which expects a show-event to be sent before any resize events. But now
522 // that the window is made visible, we know that the show-event has been sent
523 // so we can send the geometry change. FIXME: Get rid of this workaround.
525
526 if (parentCocoaWindow) {
527 // The parent window might have moved while this window was hidden,
528 // update the window geometry if there is a parent.
529 setGeometry(windowGeometry());
530
531 if (window()->type() == Qt::Popup) {
532 // QTBUG-30266: a window should not be resizable while a transient popup is open
533 // Since this isn't a native popup, the window manager doesn't close the popup when you click outside
534 NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
535 NSUInteger parentStyleMask = nativeParentWindow.styleMask;
536 if ((m_resizableTransientParent = (parentStyleMask & NSWindowStyleMaskResizable))
537 && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen))
538 nativeParentWindow.styleMask &= ~NSWindowStyleMaskResizable;
539 }
540
541 }
542
543 // Make the NSView visible first, before showing the NSWindow (in case of top level windows)
544 m_view.hidden = NO;
545
546 if (isContentView()) {
547 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
548
549 // setWindowState might have been called while the window was hidden and
550 // will not change the NSWindow state in that case. Sync up here:
551 applyWindowState(window()->windowStates());
552
553 if (window()->windowState() != Qt::WindowMinimized) {
554 if (parentCocoaWindow && (window()->modality() == Qt::WindowModal || window()->type() == Qt::Sheet)) {
555 // Show the window as a sheet
556 NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
557 if (!nativeParentWindow.attachedSheet)
558 [nativeParentWindow beginSheet:m_view.window completionHandler:nil];
559 else
560 [nativeParentWindow beginCriticalSheet:m_view.window completionHandler:nil];
561 } else if (window()->modality() == Qt::ApplicationModal) {
562 // Show the window as application modal
563 eventDispatcher()->beginModalSession(window());
564 } else if (m_view.window.canBecomeKeyWindow) {
565 bool shouldBecomeKeyNow = !NSApp.modalWindow
566 || m_view.window.worksWhenModal
567 || !NSApp.modalWindow.visible;
568
569 // Panels with becomesKeyOnlyIfNeeded set should not activate until a view
570 // with needsPanelToBecomeKey, for example a line edit, is clicked.
571 if ([m_view.window isKindOfClass:[NSPanel class]])
572 shouldBecomeKeyNow &= !(static_cast<NSPanel*>(m_view.window).becomesKeyOnlyIfNeeded);
573
574 if (shouldBecomeKeyNow)
575 [m_view.window makeKeyAndOrderFront:nil];
576 else
577 [m_view.window orderFront:nil];
578 } else {
579 [m_view.window orderFront:nil];
580 }
581 }
582 }
583 } else {
584 // Window not visible, hide it
585 if (isContentView()) {
586 if (eventDispatcher()->hasModalSession())
587 eventDispatcher()->endModalSession(window());
588 else if ([m_view.window isSheet])
589 [m_view.window.sheetParent endSheet:m_view.window];
590
591 // Note: We do not guard the order out by checking NSWindow.visible, as AppKit will
592 // in some cases, such as when hiding the application, order out and make a window
593 // invisible, but keep it in a list of "hidden windows", that it then restores again
594 // when the application is unhidden. We need to call orderOut explicitly, to bring
595 // the window out of this "hidden list".
596 [m_view.window orderOut:nil];
597
598 if (m_view.window == [NSApp keyWindow] && !eventDispatcher()->hasModalSession()) {
599 // Probably because we call runModalSession: outside [NSApp run] in QCocoaEventDispatcher
600 // (e.g., when show()-ing a modal QDialog instead of exec()-ing it), it can happen that
601 // the current NSWindow is still key after being ordered out. Then, after checking we
602 // don't have any other modal session left, it's safe to make the main window key again.
603 NSWindow *mainWindow = [NSApp mainWindow];
604 if (mainWindow && [mainWindow canBecomeKeyWindow])
605 [mainWindow makeKeyWindow];
606 }
607 }
608
609 // AppKit will in some cases set up the key view loop for child views, even if we
610 // don't set autorecalculatesKeyViewLoop, nor call recalculateKeyViewLoop ourselves.
611 // When a child window is promoted to a top level, AppKit will maintain the key view
612 // loop between the views, even if these views now cross NSWindows, even after we
613 // explicitly call recalculateKeyViewLoop. When the top level is then hidden, AppKit
614 // will complain when -[NSView _setHidden:setNeedsDisplay:] tries to transfer first
615 // responder by reading the nextValidKeyView, and it turns out to live in a different
616 // window. We mitigate this by a last second reset of the first responder, which is
617 // what AppKit also falls back to. It's unclear if the original situation of views
618 // having their nextKeyView pointing to views in other windows is kosher or not.
619 if (m_view.window.firstResponder == m_view && m_view.nextValidKeyView
620 && m_view.nextValidKeyView.window != m_view.window) {
621 qCDebug(lcQpaWindow) << "Detected nextValidKeyView" << m_view.nextValidKeyView
622 << "in different window" << m_view.nextValidKeyView.window
623 << "Resetting" << m_view.window << "first responder to nil.";
624 [m_view.window makeFirstResponder:nil];
625 }
626
627 m_view.hidden = YES;
628
629 if (parentCocoaWindow && window()->type() == Qt::Popup) {
630 NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
631 if (m_resizableTransientParent
632 && !(nativeParentWindow.styleMask & NSWindowStyleMaskFullScreen))
633 // A window should not be resizable while a transient popup is open
634 nativeParentWindow.styleMask |= NSWindowStyleMaskResizable;
635 }
636 }
637}
638
639NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags)
640{
641 Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
642
643 NSInteger windowLevel = NSNormalWindowLevel;
644
645 if (type == Qt::Tool)
646 windowLevel = NSFloatingWindowLevel;
647 else if ((type & Qt::Popup) == Qt::Popup)
648 windowLevel = NSPopUpMenuWindowLevel;
649
650 // StayOnTop window should appear above Tool windows.
651 if (flags & Qt::WindowStaysOnTopHint)
652 windowLevel = NSModalPanelWindowLevel;
653 // Tooltips should appear above StayOnTop windows.
654 if (type == Qt::ToolTip)
655 windowLevel = NSScreenSaverWindowLevel;
656
657 auto *transientParent = window()->transientParent();
658 if (transientParent && transientParent->handle()) {
659 // We try to keep windows in at least the same window level as
660 // their transient parent. Unfortunately this only works when the
661 // window is created. If the window level changes after that, as
662 // a result of a call to setWindowFlags, or by changing the level
663 // of the native window, we will not pick this up, and the window
664 // will be left behind (or in a different window level than) its
665 // parent. We could KVO-observe the window level of our transient
666 // parent, but that requires us to know when the parent goes away
667 // so that we can unregister the observation before the parent is
668 // dealloced, something we can't do for generic NSWindows. Another
669 // way would be to override [NSWindow setLevel:] and notify child
670 // windows about the change, but that doesn't work for foreign
671 // windows, which can still be transient parents via fromWinId().
672 // One area where this problem is apparent is when AppKit tweaks
673 // the window level of modal windows during application activation
674 // and deactivation. Since we don't pick up on these window level
675 // changes in a generic way, we need to add logic explicitly to
676 // re-evaluate the window level after AppKit has done its tweaks.
677
678 auto *transientCocoaWindow = static_cast<QCocoaWindow *>(transientParent->handle());
679 auto *nsWindow = transientCocoaWindow->nativeWindow();
680
681 // We only upgrade the window level for "special" windows, to work
682 // around Qt Widgets Designer parenting the designer windows to the widget
683 // palette window (QTBUG-31779). This should be fixed in designer.
684 if (type != Qt::Window)
685 windowLevel = qMax(windowLevel, nsWindow.level);
686 }
687
688 return windowLevel;
689}
690
691NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) const
692{
693 const Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
694
695 // Determine initial style mask based on whether the window should
696 // have a frame and title or not. The NSWindowStyleMaskBorderless
697 // and NSWindowStyleMaskTitled styles are mutually exclusive, with
698 // values of 0 and 1 correspondingly.
699 NSUInteger styleMask = [&]{
700 // Honor explicit requests for borderless windows
701 if (flags & Qt::FramelessWindowHint)
702 return NSWindowStyleMaskBorderless;
703
704 // Popup windows should always be borderless
705 if (windowIsPopupType(type))
706 return NSWindowStyleMaskBorderless;
707
708 if (flags & Qt::CustomizeWindowHint) {
709 // CustomizeWindowHint turns off the default window title hints,
710 // so the choice is then up to the user via Qt::WindowTitleHint.
711 return flags & Qt::WindowTitleHint
712 ? NSWindowStyleMaskTitled
713 : NSWindowStyleMaskBorderless;
714 } else {
715 // Otherwise, default to using titled windows
716 return NSWindowStyleMaskTitled;
717 }
718 }();
719
720 // We determine which buttons to show in updateTitleBarButtons,
721 // so we can enable all the relevant style masks here to ensure
722 // that behaviors that don't involve the title bar buttons are
723 // working (for example minimizing frameless windows, or resizing
724 // windows that don't have zoom or fullscreen titlebar buttons).
725 styleMask |= NSWindowStyleMaskClosable
726 | NSWindowStyleMaskMiniaturizable;
727
728 if (type != Qt::Popup) // We only care about popups exactly.
729 styleMask |= NSWindowStyleMaskResizable;
730
731 if (type == Qt::Tool)
732 styleMask |= NSWindowStyleMaskUtilityWindow;
733
734 if (flags & Qt::ExpandedClientAreaHint)
735 styleMask |= NSWindowStyleMaskFullSizeContentView;
736
737 // Don't wipe existing states for style flags we don't control here
738 styleMask |= (m_view.window.styleMask & (
739 NSWindowStyleMaskFullScreen
740 | NSWindowStyleMaskUnifiedTitleAndToolbar
741 | NSWindowStyleMaskDocModalWindow
742 | NSWindowStyleMaskNonactivatingPanel
743 | NSWindowStyleMaskHUDWindow));
744
745 return styleMask;
746}
747
748bool QCocoaWindow::isFixedSize() const
749{
750 return windowMinimumSize().isValid() && windowMaximumSize().isValid()
751 && windowMinimumSize() == windowMaximumSize();
752}
753
754void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags)
755{
756 if (!isContentView())
757 return;
758
759 static constexpr std::pair<NSWindowButton, Qt::WindowFlags> buttons[] = {
760 { NSWindowCloseButton, Qt::WindowCloseButtonHint },
761 { NSWindowMiniaturizeButton, Qt::WindowMinimizeButtonHint},
762 { NSWindowZoomButton, Qt::WindowMaximizeButtonHint | Qt::WindowFullscreenButtonHint }
763 };
764
765 bool hideButtons = true;
766 for (const auto &[button, buttonHint] : buttons) {
767 // Set up Qt defaults based on window type
768 bool enabled = true;
769 if (button == NSWindowMiniaturizeButton)
770 enabled = window()->type() != Qt::Dialog;
771
772 // Let users override via CustomizeWindowHint
773 if (windowFlags & Qt::CustomizeWindowHint)
774 enabled = windowFlags & buttonHint;
775
776 // Then do some final sanitizations
777
778 if (button == NSWindowZoomButton && isFixedSize())
779 enabled = false;
780
781 // Mimic what macOS natively does for parent windows of modal
782 // sheets, which is to disable the close button, but leave the
783 // other buttons as they were.
784 if (button == NSWindowCloseButton && enabled
785 && QWindowPrivate::get(window())->blockedByModalWindow) {
786 enabled = false;
787 // If we end up having no enabled buttons, our workaround
788 // should not be a reason for hiding all of them.
789 hideButtons = false;
790 }
791
792 [m_view.window standardWindowButton:button].enabled = enabled;
793 hideButtons &= !enabled;
794 }
795
796 // Hide buttons in case we disabled all of them
797 for (const auto &[button, buttonHint] : buttons)
798 [m_view.window standardWindowButton:button].hidden = hideButtons;
799}
800
801void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
802{
803 // Updating the window flags may affect the window's theme frame, which
804 // in the process retains and then autoreleases the NSWindow. To make
805 // sure this doesn't leave lingering releases when there is no pool in
806 // place (e.g. during main(), before exec), we add one locally here.
807 QMacAutoReleasePool pool;
808
809 if (!isContentView())
810 return;
811
812 qCDebug(lcQpaWindow) << "Setting" << flags << "for" << window();
813
814 // FIXME: Some flags may require a different NSWindow class, in
815 // which case we should recreate here, after first updating the
816 // QWindow flags state. But currently applyContentBorderThickness
817 // depends on setWindowFlags, causing a recursion, so we need to
818 // clean up that entanglement first.
819
820 {
821 // While setting style mask we can have handleGeometryChange calls on a content
822 // view with null geometry, reporting an invalid coordinates as a result.
823 QScopedValueRollback<bool> geometryChangeBlocker(m_inSetStyleMask, true);
824
825 const auto newMask = windowStyleMask(flags);
826 const bool expandedClientAreaChanged = (
827 (newMask & NSWindowStyleMaskFullSizeContentView) !=
828 (m_view.window.styleMask & NSWindowStyleMaskFullSizeContentView));
829 const auto frameGeometry = window()->frameGeometry();
830
831 m_view.window.styleMask = newMask;
832
833 if (expandedClientAreaChanged) {
834 // Maintain stable frame geometry when toggling expanded client area
835 setGeometry(frameGeometry.marginsRemoved(frameMargins()),
836 QWindowPrivate::PositionPolicy::WindowFrameExclusive);
837 }
838 }
839
840 Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
841 if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) {
842 NSWindowCollectionBehavior behavior = m_view.window.collectionBehavior;
843 const bool enableFullScreen = m_view.window.qt_fullScreen
844 || !(flags & Qt::CustomizeWindowHint)
845 || (flags & Qt::WindowFullscreenButtonHint);
846 if (enableFullScreen) {
847 behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
848 behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary;
849 } else {
850 behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary;
851 behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary;
852 }
853 m_view.window.collectionBehavior = behavior;
854 }
855
856 // Set styleMask and collectionBehavior before applying window level, as
857 // the window level change will trigger verification of the two properties.
858 m_view.window.level = this->windowLevel(flags);
859
860 m_view.window.hasShadow = !(flags & Qt::NoDropShadowWindowHint);
861
862 if (!(flags & Qt::FramelessWindowHint))
863 setWindowTitle(window()->title());
864
865 updateTitleBarButtons(flags);
866
867 // Make window ignore mouse events if WindowTransparentForInput is set.
868 // Note that ignoresMouseEvents has a special initial state where events
869 // are ignored (passed through) based on window transparency, and that
870 // setting the property to false does not return us to that state. Instead,
871 // this makes the window capture all mouse events. Take care to only
872 // set the property if needed. FIXME: recreate window if needed or find
873 // some other way to implement WindowTransparentForInput.
874 bool ignoreMouse = flags & Qt::WindowTransparentForInput;
875 if (m_view.window.ignoresMouseEvents != ignoreMouse)
876 m_view.window.ignoresMouseEvents = ignoreMouse;
877
878 m_view.window.titlebarAppearsTransparent = flags & Qt::NoTitleBarBackgroundHint;
879}
880
881// ----------------------- Window state -----------------------
882
883/*!
884 Changes the state of the NSWindow, going in/out of minimize/zoomed/fullscreen
885
886 When this is called from QWindow::setWindowState(), the QWindow state has not been
887 updated yet, so window()->windowState() will reflect the previous state that was
888 reported to QtGui.
889*/
890void QCocoaWindow::setWindowState(Qt::WindowStates state)
891{
892 if (window()->isVisible())
893 applyWindowState(state); // Window state set for hidden windows take effect when show() is called
894}
895
896void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
897{
898 if (!isContentView())
899 return;
900
901 const Qt::WindowState currentState = QWindowPrivate::effectiveState(windowState());
902 const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState);
903
904 if (newState == currentState)
905 return;
906
907 qCDebug(lcQpaWindow) << "Applying" << newState << "to" << window() << "in" << currentState;
908
909 const NSSize contentSize = m_view.frame.size;
910 if (contentSize.width <= 0 || contentSize.height <= 0) {
911 // If content view width or height is 0 then the window animations will crash so
912 // do nothing. We report the current state back to reflect the failed operation.
913 qWarning("invalid window content view size, check your window geometry");
914 handleWindowStateChanged(HandleUnconditionally);
915 return;
916 }
917
918 const NSWindow *nsWindow = m_view.window;
919
920 if (nsWindow.styleMask & NSWindowStyleMaskUtilityWindow
921 && newState & (Qt::WindowMinimized | Qt::WindowFullScreen)) {
922 qWarning() << window()->type() << "windows cannot be made" << newState;
923 handleWindowStateChanged(HandleUnconditionally);
924 return;
925 }
926
927 const id sender = nsWindow;
928
929 // First we need to exit states that can't transition directly to other states
930 switch (currentState) {
931 case Qt::WindowMinimized:
932 [nsWindow deminiaturize:sender];
933 // Deminiaturizing is not synchronous, so we need to wait for the
934 // NSWindowDidMiniaturizeNotification before continuing to apply
935 // the new state.
936 return;
937 case Qt::WindowFullScreen: {
938 toggleFullScreen();
939 // Exiting fullscreen is not synchronous, so we need to wait for the
940 // NSWindowDidExitFullScreenNotification before continuing to apply
941 // the new state.
942 return;
943 }
944 default:;
945 }
946
947 // Then we apply the new state if needed
948 if (newState == windowState())
949 return;
950
951 switch (newState) {
952 case Qt::WindowFullScreen:
953 toggleFullScreen();
954 break;
955 case Qt::WindowMaximized:
956 toggleMaximized();
957 break;
958 case Qt::WindowMinimized:
959 [nsWindow miniaturize:sender];
960 break;
961 case Qt::WindowNoState:
962 if (windowState() == Qt::WindowMaximized)
963 toggleMaximized();
964 break;
965 default:
966 Q_UNREACHABLE();
967 }
968}
969
970Qt::WindowStates QCocoaWindow::windowState() const
971{
972 Qt::WindowStates states = Qt::WindowNoState;
973 NSWindow *window = m_view.window;
974
975 if (window.miniaturized)
976 states |= Qt::WindowMinimized;
977
978 // Full screen and maximized are mutually exclusive, as macOS
979 // will report a full screen window as zoomed.
980 if (window.qt_fullScreen) {
981 states |= Qt::WindowFullScreen;
982 } else if ((window.zoomed && !isTransitioningToFullScreen())
983 || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) {
984 states |= Qt::WindowMaximized;
985 }
986
987 // Note: We do not report Qt::WindowActive, even if isActive()
988 // is true, as QtGui does not expect this window state to be set.
989
990 return states;
991}
992
993void QCocoaWindow::toggleMaximized()
994{
995 const NSWindow *window = m_view.window;
996
997 // The NSWindow needs to be resizable, otherwise the window will
998 // not be possible to zoom back to non-zoomed state.
999 const bool wasResizable = window.styleMask & NSWindowStyleMaskResizable;
1000 window.styleMask |= NSWindowStyleMaskResizable;
1001
1002 const id sender = window;
1003 [window zoom:sender];
1004
1005 if (!wasResizable)
1006 window.styleMask &= ~NSWindowStyleMaskResizable;
1007}
1008
1009void QCocoaWindow::windowWillZoom()
1010{
1011 updateNormalGeometry();
1012}
1013
1014void QCocoaWindow::toggleFullScreen()
1015{
1016 const NSWindow *window = m_view.window;
1017
1018 // The window needs to have the correct collection behavior for the
1019 // toggleFullScreen call to have an effect. The collection behavior
1020 // will be reset in windowDidEnterFullScreen/windowDidLeaveFullScreen.
1021 window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
1022
1023 const id sender = window;
1024 [window toggleFullScreen:sender];
1025}
1026
1027void QCocoaWindow::windowWillEnterFullScreen()
1028{
1029 if (!isContentView())
1030 return;
1031
1032 updateNormalGeometry();
1033
1034 // The NSWindow needs to be resizable, otherwise we'll end up with
1035 // the normal window geometry, centered in the middle of the screen
1036 // on a black background. The styleMask will be reset below.
1037 m_view.window.styleMask |= NSWindowStyleMaskResizable;
1038}
1039
1040bool QCocoaWindow::isTransitioningToFullScreen() const
1041{
1042 NSWindow *window = m_view.window;
1043 return window.styleMask & NSWindowStyleMaskFullScreen && !window.qt_fullScreen;
1044}
1045
1046void QCocoaWindow::windowDidEnterFullScreen()
1047{
1048 if (!isContentView())
1049 return;
1050
1051 Q_ASSERT_X(m_view.window.qt_fullScreen, "QCocoaWindow",
1052 "FullScreen category processes window notifications first");
1053
1054 // Reset to original styleMask
1055 setWindowFlags(window()->flags());
1056
1057 handleWindowStateChanged();
1058}
1059
1060void QCocoaWindow::windowWillExitFullScreen()
1061{
1062 if (!isContentView())
1063 return;
1064
1065 // The NSWindow needs to be resizable, otherwise we'll end up with
1066 // a weird zoom animation. The styleMask will be reset below.
1067 m_view.window.styleMask |= NSWindowStyleMaskResizable;
1068}
1069
1070void QCocoaWindow::windowDidExitFullScreen()
1071{
1072 if (!isContentView())
1073 return;
1074
1075 Q_ASSERT_X(!m_view.window.qt_fullScreen, "QCocoaWindow",
1076 "FullScreen category processes window notifications first");
1077
1078 // Reset to original styleMask
1079 setWindowFlags(window()->flags());
1080
1081 Qt::WindowState requestedState = window()->windowState();
1082
1083 // Deliver update of QWindow state
1084 handleWindowStateChanged();
1085
1086 if (requestedState != windowState() && requestedState != Qt::WindowFullScreen) {
1087 // We were only going out of full screen as an intermediate step before
1088 // progressing into the final step, so re-sync the desired state.
1089 applyWindowState(requestedState);
1090 }
1091}
1092
1093void QCocoaWindow::windowDidMiniaturize()
1094{
1095 if (!isContentView())
1096 return;
1097
1098 handleWindowStateChanged();
1099}
1100
1101void QCocoaWindow::windowDidDeminiaturize()
1102{
1103 if (!isContentView())
1104 return;
1105
1106 Qt::WindowState requestedState = window()->windowState();
1107
1108 handleWindowStateChanged();
1109
1110 if (requestedState != windowState() && requestedState != Qt::WindowMinimized) {
1111 // We were only going out of minimized as an intermediate step before
1112 // progressing into the final step, so re-sync the desired state.
1113 applyWindowState(requestedState);
1114 }
1115}
1116
1117void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
1118{
1119 Qt::WindowStates currentState = windowState();
1120 if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState)
1121 return;
1122
1123 qCDebug(lcQpaWindow) << "QCocoaWindow::handleWindowStateChanged" <<
1124 m_lastReportedWindowState << "-->" << currentState;
1125
1126 QPointer<QCocoaWindow> destructionGuard = this;
1127 QWindowSystemInterface::handleWindowStateChanged<QWindowSystemInterface::SynchronousDelivery>(
1128 window(), currentState, m_lastReportedWindowState);
1129 if (!destructionGuard)
1130 return;
1131 m_lastReportedWindowState = currentState;
1132}
1133
1134// ------------------------------------------------------------
1135
1136void QCocoaWindow::setWindowTitle(const QString &title)
1137{
1138 QMacAutoReleasePool pool;
1139
1140 if (!isContentView())
1141 return;
1142
1143 m_view.window.title = title.toNSString();
1144
1145 if (title.isEmpty() && !window()->filePath().isEmpty()) {
1146 // Clearing the title should restore the default filename
1147 setWindowFilePath(window()->filePath());
1148 }
1149}
1150
1151void QCocoaWindow::setWindowFilePath(const QString &filePath)
1152{
1153 QMacAutoReleasePool pool;
1154
1155 if (!isContentView())
1156 return;
1157
1158 if (window()->title().isNull())
1159 [m_view.window setTitleWithRepresentedFilename:filePath.toNSString()];
1160 else
1161 m_view.window.representedFilename = filePath.toNSString();
1162
1163 // Changing the file path may affect icon visibility
1164 setWindowIcon(window()->icon());
1165}
1166
1167void QCocoaWindow::setWindowIcon(const QIcon &icon)
1168{
1169 QMacAutoReleasePool pool;
1170
1171 if (!isContentView())
1172 return;
1173
1174 NSButton *iconButton = [m_view.window standardWindowButton:NSWindowDocumentIconButton];
1175 if (!iconButton) {
1176 // Window icons are only supported on macOS in combination with a document filePath
1177 return;
1178 }
1179
1180 if (icon.isNull()) {
1181 iconButton.image = [NSWorkspace.sharedWorkspace iconForFile:m_view.window.representedFilename];
1182 } else {
1183 // Fall back to a size that looks good on the highest resolution screen available
1184 // for icon engines that don't have an intrinsic size (like SVG).
1185 auto fallbackSize = QSizeF::fromCGSize(iconButton.frame.size) * qGuiApp->devicePixelRatio();
1186 iconButton.image = [NSImage imageFromQIcon:icon withSize:fallbackSize.toSize()];
1187 }
1188}
1189
1190void QCocoaWindow::setAlertState(bool enabled)
1191{
1192 if (m_alertRequest == NoAlertRequest && enabled) {
1193 m_alertRequest = [NSApp requestUserAttention:NSCriticalRequest];
1194 } else if (m_alertRequest != NoAlertRequest && !enabled) {
1195 [NSApp cancelUserAttentionRequest:m_alertRequest];
1196 m_alertRequest = NoAlertRequest;
1197 }
1198}
1199
1200bool QCocoaWindow::isAlertState() const
1201{
1202 return m_alertRequest != NoAlertRequest;
1203}
1204
1205void QCocoaWindow::raise()
1206{
1207 qCDebug(lcQpaWindow) << "QCocoaWindow::raise" << window();
1208
1209 // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm)
1210 if (isContentView()) {
1211 if (m_view.window.visible) {
1212 {
1213 // Clean up auto-released temp objects from orderFront immediately.
1214 // Failure to do so has been observed to cause leaks also beyond any outer
1215 // autorelease pool (for example around a complete QWindow
1216 // construct-show-raise-hide-delete cycle), counter to expected autoreleasepool
1217 // behavior.
1218 QMacAutoReleasePool pool;
1219 [m_view.window orderFront:m_view.window];
1220 }
1221 static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS");
1222 if (raiseProcess)
1223 [NSApp activateIgnoringOtherApps:YES];
1224 }
1225 } else {
1226 [m_view.superview addSubview:m_view positioned:NSWindowAbove relativeTo:nil];
1227 }
1228}
1229
1230void QCocoaWindow::lower()
1231{
1232 qCDebug(lcQpaWindow) << "QCocoaWindow::lower" << window();
1233
1234 if (isContentView()) {
1235 if (m_view.window.visible)
1236 [m_view.window orderBack:m_view.window];
1237 } else {
1238 [m_view.superview addSubview:m_view positioned:NSWindowBelow relativeTo:nil];
1239 }
1240}
1241
1242bool QCocoaWindow::isExposed() const
1243{
1244 return !m_exposedRect.isEmpty();
1245}
1246
1247/*!
1248 Returns \c true if the window is a child of a non-Qt window.
1249
1250 A embedded window has no parent platform window as reflected
1251 though parent(), but has a native parent handle.
1252*/
1253bool QCocoaWindow::isEmbedded() const
1254{
1255 // We compute this in viewDidMoveToSuperview, so that we don't
1256 // report inconsistent states during reparenting, where the
1257 // QPlatformWindow and QWindow parent() is not in sync with
1258 // the view's superview.
1259 return m_isEmbedded;
1260}
1261
1262bool QCocoaWindow::isOpaque() const
1263{
1264 // OpenGL surfaces can be ordered either above(default) or below the NSWindow.
1265 // When ordering below the window must be tranclucent.
1266 static GLint openglSourfaceOrder = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER");
1267
1268 bool translucent = window()->format().alphaBufferSize() > 0
1269 || window()->opacity() < 1
1270 || !window()->mask().isEmpty()
1271 || (surface()->supportsOpenGL() && openglSourfaceOrder == -1);
1272 return !translucent;
1273}
1274
1275void QCocoaWindow::propagateSizeHints()
1276{
1277 QMacAutoReleasePool pool;
1278 if (!isContentView())
1279 return;
1280
1281 qCDebug(lcQpaWindow) << "QCocoaWindow::propagateSizeHints" << window()
1282 << "min:" << windowMinimumSize() << "max:" << windowMaximumSize()
1283 << "increment:" << windowSizeIncrement()
1284 << "base:" << windowBaseSize();
1285
1286 const NSWindow *window = m_view.window;
1287
1288 // Set the minimum content size.
1289 QSize minimumSize = windowMinimumSize();
1290 if (!minimumSize.isValid()) // minimumSize is (-1, -1) when not set. Make that (0, 0) for Cocoa.
1291 minimumSize = QSize(0, 0);
1292 window.contentMinSize = NSSizeFromCGSize(minimumSize.toCGSize());
1293
1294 // Set the maximum content size.
1295 window.contentMaxSize = NSSizeFromCGSize(windowMaximumSize().toCGSize());
1296
1297 // The window may end up with a fixed size; in this case the zoom button should be disabled.
1298 updateTitleBarButtons(this->window()->flags());
1299
1300 // sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be
1301 // resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case.
1302 QSize sizeIncrement = windowSizeIncrement();
1303 if (sizeIncrement.isEmpty())
1304 sizeIncrement = QSize(1, 1);
1305 window.resizeIncrements = NSSizeFromCGSize(sizeIncrement.toCGSize());
1306
1307 QRect rect = geometry();
1308 QSize baseSize = windowBaseSize();
1309 if (!baseSize.isNull() && baseSize.isValid())
1310 [window setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES];
1311}
1312
1313void QCocoaWindow::setOpacity(qreal level)
1314{
1315 qCDebug(lcQpaWindow) << "QCocoaWindow::setOpacity" << level;
1316 if (!isContentView())
1317 return;
1318
1319 m_view.window.alphaValue = level;
1320}
1321
1322void QCocoaWindow::setMask(const QRegion &region)
1323{
1324 qCDebug(lcQpaWindow) << "QCocoaWindow::setMask" << window() << region;
1325
1326 if (!region.isEmpty()) {
1327 QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
1328 for (const QRect &r : region)
1329 CGPathAddRect(maskPath, nullptr, r.toCGRect());
1330 CAShapeLayer *maskLayer = [CAShapeLayer layer];
1331 maskLayer.path = maskPath;
1332 m_view.layer.mask = maskLayer;
1333 } else {
1334 m_view.layer.mask = nil;
1335 }
1336}
1337
1338bool QCocoaWindow::setKeyboardGrabEnabled(bool)
1339{
1340 return false; // FIXME (QTBUG-106597)
1341}
1342
1343bool QCocoaWindow::setMouseGrabEnabled(bool)
1344{
1345 return false; // FIXME (QTBUG-106597)
1346}
1347
1348WId QCocoaWindow::winId() const
1349{
1350 return WId(m_view);
1351}
1352
1353void QCocoaWindow::setParent(const QPlatformWindow *parentWindow)
1354{
1355 qCDebug(lcQpaWindow) << "QCocoaWindow::setParent" << window() << (parentWindow ? parentWindow->window() : 0);
1356
1357#if QT_CONFIG(accessibility)
1358 // The accessibility parent we push onto a foreign view is resolved when a
1359 // client reaches the view, and a client reaches it through the window
1360 // container that hosts the window. A window that changes parent is on its
1361 // way out of its container, so let go of the parent we pushed rather than
1362 // leave the view pointing into a hierarchy that no longer holds it.
1363 if (isForeignWindow())
1364 clearAccessibleParent();
1365#endif
1366
1367 // Recreate in case we need to get rid of a NSWindow, or create one
1368 recreateWindowIfNeeded();
1369
1370 setGeometry(geometry(), QWindowPrivate::WindowFrameExclusive);
1371}
1372
1373NSView *QCocoaWindow::view() const
1374{
1375 return m_view;
1376}
1377
1378NSWindow *QCocoaWindow::nativeWindow() const
1379{
1380 return m_view.window;
1381}
1382
1383void QCocoaWindow::updateEmbeddedState()
1384{
1385 const bool wasEmbedded = m_isEmbedded;
1386 m_isEmbedded = [&]{
1387 if (QPlatformWindow::parent())
1388 return false;
1389
1390 if (!m_view.superview)
1391 return false;
1392
1393 // In a top level window the view will still have the window's theme
1394 // frame as their superview, which does not count as being embedded.
1395 auto *qtWindowController = qt_objc_cast<QNSWindowController*>(m_view.window.windowController);
1396 if (m_view == qtWindowController.window.contentView)
1397 return false;
1398
1399 return true;
1400 }();
1401 if (m_isEmbedded != wasEmbedded) {
1402 qCDebug(lcQpaWindow) << "Updated embedded state from"
1403 << wasEmbedded << "to" << m_isEmbedded;
1404 }
1405}
1406
1407// ----------------------- NSView notifications -----------------------
1408
1409void QCocoaWindow::viewDidChangeFrame()
1410{
1411 // Note: When the view is the content view, it would seem redundant
1412 // to deliver geometry changes both from windowDidResize and this
1413 // callback, but in some cases such as when macOS native tabbed
1414 // windows are enabled we may end up with the wrong geometry in
1415 // the initial windowDidResize callback when a new tab is created.
1416 handleGeometryChange();
1417}
1418
1419/*!
1420 Callback for NSViewGlobalFrameDidChangeNotification.
1421
1422 Posted whenever an NSView object that has attached surfaces (that is,
1423 NSOpenGLContext objects) moves to a different screen, or other cases
1424 where the NSOpenGLContext object needs to be updated.
1425*/
1426void QCocoaWindow::viewDidChangeGlobalFrame()
1427{
1428 [m_view setNeedsDisplay:YES];
1429}
1430
1431/*!
1432 Notification that the view has moved to a different superview.
1433
1434 Unlike [NSView viewDidMoveToSuperview] this callback happens
1435 after the view's new window has been resolved.
1436*/
1437void QCocoaWindow::viewDidMoveToSuperview(NSView *previousSuperview)
1438{
1439 qCDebug(lcQpaWindow) << "Done re-parenting" << m_view
1440 << "from" << previousSuperview << "into" << m_view.superview;
1441
1442 // Update embedded state now that we have a consistent state
1443 updateEmbeddedState();
1444
1445 if (isEmbedded()) {
1446 // FIXME: Align this with logic in QCocoaWindow::setParent
1447 handleGeometryChange();
1448
1449 if (m_view.superview)
1450 [m_view setNeedsDisplay:YES];
1451 }
1452
1453 // The default coordinate system of NSViews is with the origin in the bottom
1454 // left corner (also known as non-flipped). Qt's coordinate system on the other
1455 // hand has the origin in the top left corner (flipped, in Cocoa terms). When
1456 // we're parented into a non-flipped NSView (such as for foreign window parents),
1457 // the position we set on our view in setCocoaGeometry will only accurately
1458 // represent the QWindow position as long as the superview doesn't change
1459 // its size. To ensure a stable y position (following the Qt semantics),
1460 // we explicitly set an auto resizing mask, unless one is already set.
1461 if (m_view.superview && !m_view.superview.flipped && !isContentView()) {
1462 if (m_view.autoresizingMask == NSViewNotSizable) {
1463 qCDebug(lcQpaWindow) << "Setting auto resizing mask on" << m_view
1464 << "in non-flipped superview to maintain stable y-positioning";
1465 setGeometry(geometry(), QWindowPrivate::WindowFrameExclusive);
1466 m_view.autoresizingMask = NSViewMinYMargin;
1467 }
1468 } else if (previousSuperview && !previousSuperview.flipped
1469 && m_view.autoresizingMask == NSViewMinYMargin) {
1470 // Reset back to default. This assumes someone didn't
1471 // actively set NSViewMinYMargin and want it to stay
1472 // that way. In that rare case, they can re-apply the
1473 // auto resizing mask after reparenting.
1474 qCDebug(lcQpaWindow) << "Clearing auto resizing mask on" << m_view
1475 << "as explicit stable y-positioning is no longer needed";
1476 m_view.autoresizingMask = NSViewNotSizable;
1477 }
1478}
1479
1480/*!
1481 Notification that the view has moved to a different window.
1482
1483 The viewDidMoveToSuperview callback comes in before this one.
1484*/
1485void QCocoaWindow::viewDidMoveToWindow(NSWindow *previousWindow)
1486{
1487 qCDebug(lcQpaWindow) << "Done moving" << m_view
1488 << "from" << previousWindow << "to" << m_view.window;
1489
1490 if (auto *qtWindowController = qt_objc_cast<QNSWindowController*>(m_view.window.windowController)) {
1491 qCDebug(lcQpaWindow) << "Retaining new window controller" << qtWindowController;
1492 [qtWindowController retain];
1493 }
1494 if (auto *qtWindowController = qt_objc_cast<QNSWindowController*>(previousWindow.windowController)) {
1495 qCDebug(lcQpaWindow) << "Releasing old window controller" << qtWindowController;
1496 [qtWindowController autorelease];
1497 }
1498
1499 // Moving to a new window might result in a new screen. This is normally
1500 // handled for top level windows via windowDidChangeScreen, but for child
1501 // windows we need to handle it manually.
1502 auto *previousScreen = previousWindow ? QCocoaScreen::get(previousWindow.screen) : nullptr;
1503 auto *currentScreen = m_view.window ? QCocoaScreen::get(m_view.window.screen) : nullptr;
1504 if (currentScreen && currentScreen != previousScreen)
1505 windowDidChangeScreen();
1506}
1507
1508// ----------------------- NSWindow notifications -----------------------
1509
1510// Note: The following notifications are delivered to every QCocoaWindow
1511// that is a child of the NSWindow that triggered the notification. Each
1512// callback should make sure to filter out notifications if they do not
1513// apply to that QCocoaWindow, e.g. if the window is not a content view.
1514
1515void QCocoaWindow::windowDidMove()
1516{
1517 if (!isContentView())
1518 return;
1519
1520 handleGeometryChange();
1521
1522 // Moving a window might bring it out of maximized state
1523 handleWindowStateChanged();
1524}
1525
1526void QCocoaWindow::windowDidResize()
1527{
1528 if (!isContentView())
1529 return;
1530
1531 handleGeometryChange();
1532
1533 if (!m_view.inLiveResize)
1534 handleWindowStateChanged();
1535}
1536
1537void QCocoaWindow::windowWillStartLiveResize()
1538{
1539 // Track live resizing for all windows, including
1540 // child windows, so we know if it's safe to update
1541 // the window unthrottled outside of the main thread.
1542 m_inLiveResize = true;
1543}
1544
1545bool QCocoaWindow::allowsIndependentThreadedRendering() const
1546{
1547 // Use member variable to track this instead of reflecting
1548 // NSView.inLiveResize directly, so it can be called from
1549 // non-main threads.
1550 return !m_inLiveResize;
1551}
1552
1553void QCocoaWindow::windowDidEndLiveResize()
1554{
1555 m_inLiveResize = false;
1556
1557 if (!isContentView())
1558 return;
1559
1560 handleWindowStateChanged();
1561}
1562
1563void QCocoaWindow::windowDidBecomeKey()
1564{
1565 // The NSWindow we're part of become key. Check if we're the first
1566 // responder, and if so, deliver focus window change to our window.
1567 if (m_view.window.firstResponder != m_view)
1568 return;
1569
1570 qCDebug(lcQpaWindow) << m_view.window << "became key window."
1571 << "Updating focus window to" << this << "with view" << m_view;
1572
1573 if (windowIsPopupType()) {
1574 qCDebug(lcQpaWindow) << "Window is popup. Skipping focus window change.";
1575 return;
1576 }
1577
1578 // See also [QNSView becomeFirstResponder]
1579 QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
1580 window(), Qt::ActiveWindowFocusReason);
1581}
1582
1583void QCocoaWindow::windowDidResignKey()
1584{
1585 // The NSWindow we're part of lost key. Check if we're the first
1586 // responder, and if so, deliver window deactivation to our window.
1587 if (m_view.window.firstResponder != m_view)
1588 return;
1589
1590 qCDebug(lcQpaWindow) << m_view.window << "resigned key window."
1591 << "Clearing focus window" << this << "with view" << m_view;
1592
1593 // Make sure popups are closed before we deliver activation changes, which are
1594 // otherwise ignored by QApplication.
1595 closeAllPopups();
1596
1597 // The current key window will be non-nil if another window became key. If that
1598 // window is a Qt window, we delay the window activation event until the didBecomeKey
1599 // notification is delivered to the active window, to ensure an atomic update.
1600 NSWindow *newKeyWindow = [NSApp keyWindow];
1601 if (newKeyWindow && newKeyWindow != m_view.window
1602 && [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) {
1603 qCDebug(lcQpaWindow) << "New key window" << newKeyWindow
1604 << "is Qt window. Deferring focus window change.";
1605 return;
1606 }
1607
1608 // Lost key window, go ahead and set the active window to zero
1609 if (!windowIsPopupType()) {
1610 QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
1611 nullptr, Qt::ActiveWindowFocusReason);
1612 }
1613}
1614
1615void QCocoaWindow::windowDidOrderOnScreen()
1616{
1617 // The current mouse window needs to get a leave event when a popup window opens.
1618 // For modal dialogs, QGuiApplicationPrivate::showModalWindow takes care of this.
1619 if (QWindowPrivate::get(window())->isPopup()) {
1620 QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>
1621 (QGuiApplicationPrivate::currentMouseWindow);
1622 }
1623
1624 [m_view setNeedsDisplay:YES];
1625}
1626
1627void QCocoaWindow::windowDidOrderOffScreen()
1628{
1629 handleExposeEvent(QRegion());
1630 // We are closing a window, so the window that is now under the mouse
1631 // might need to get an Enter event if it isn't already the mouse window.
1632 if (window()->type() & Qt::Window) {
1633 const QPointF screenPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]);
1634 if (QWindow *windowUnderMouse = QGuiApplication::topLevelAt(screenPoint.toPoint())) {
1635 if (windowUnderMouse != QGuiApplicationPrivate::instance()->currentMouseWindow) {
1636 const auto windowPoint = windowUnderMouse->mapFromGlobal(screenPoint);
1637 // asynchronous delivery on purpose
1638 QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::AsynchronousDelivery>
1639 (windowUnderMouse, windowPoint, screenPoint);
1640 }
1641 }
1642 }
1643}
1644
1645void QCocoaWindow::windowDidChangeOcclusionState()
1646{
1647 // Note, we don't take the view's hiddenOrHasHiddenAncestor state into
1648 // account here, but instead leave that up to handleExposeEvent, just
1649 // like all the other signals that could potentially change the exposed
1650 // state of the window.
1651 bool visible = m_view.window.occlusionState & NSWindowOcclusionStateVisible;
1652 qCDebug(lcQpaWindow) << "Occlusion state of" << m_view.window << "for"
1653 << window() << "changed to" << (visible ? "visible" : "occluded");
1654
1655 if (visible)
1656 [m_view setNeedsDisplay:YES];
1657 else
1658 handleExposeEvent(QRegion());
1659}
1660
1661void QCocoaWindow::windowDidChangeScreen()
1662{
1663 if (!window())
1664 return;
1665
1666 // Note: When a window is resized to 0x0 Cocoa will report the window's screen as nil
1667 NSScreen *nsScreen = m_view.window.screen;
1668
1669 qCDebug(lcQpaWindow) << window() << "did change" << nsScreen;
1670 QCocoaScreen::updateScreens();
1671
1672 auto *previousScreen = static_cast<QCocoaScreen*>(screen());
1673 auto *currentScreen = QCocoaScreen::get(nsScreen);
1674
1675 qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
1676
1677 // Note: The previous screen may be the same as the current screen, either because
1678 // a) the screen was just reconfigured, which still results in AppKit sending an
1679 // NSWindowDidChangeScreenNotification, b) because the previous screen was removed,
1680 // and we ended up calling QWindow::setScreen to move the window, which doesn't
1681 // actually move the window to the new screen, or c) because we've delivered the
1682 // screen change to the top level window, which will make all the child windows
1683 // of that window report the new screen when requested via QWindow::screen().
1684 // We still need to deliver the screen change in all these cases, as the
1685 // device-pixel ratio may have changed, and needs to be delivered to all
1686 // windows, both top level and child windows.
1687
1688 QPointer<QCocoaWindow> destructionGuard = this;
1689 QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(
1690 window(), currentScreen ? currentScreen->screen() : nullptr);
1691 if (!destructionGuard)
1692 return;
1693
1694 if (currentScreen && hasPendingUpdateRequest()) {
1695 // Restart display-link on new screen. We need to do this unconditionally,
1696 // since we can't rely on the previousScreen reflecting whether or not the
1697 // window actually moved from one screen to another, or just stayed on the
1698 // same screen.
1699 currentScreen->requestUpdate();
1700 }
1701 // If there are no exposed windows left on the previous screen
1702 // we can stop its display link if it was running.
1703 if (previousScreen)
1704 previousScreen->maybeStopDisplayLink();
1705}
1706
1707// ----------------------- NSWindowDelegate callbacks -----------------------
1708
1709bool QCocoaWindow::windowShouldClose()
1710{
1711 qCDebug(lcQpaWindow) << "QCocoaWindow::windowShouldClose" << window();
1712
1713 // This callback should technically only determine if the window
1714 // should (be allowed to) close, but since our QPA API to determine
1715 // that also involves actually closing the window we do both at the
1716 // same time, instead of doing the latter in windowWillClose.
1717
1718 // If the window is closed, we will release and deallocate the NSWindow.
1719 // But frames higher up in the stack might still expect the window to
1720 // be alive, since the windowShouldClose: callback is technically only
1721 // supposed to answer YES or NO. To ensure the window is still alive
1722 // we put an autorelease in the closest pool (typically the runloop).
1723 [[m_view.window retain] autorelease];
1724
1725 return QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(window());
1726}
1727
1728// ----------------------------- QPA forwarding -----------------------------
1729
1730void QCocoaWindow::handleGeometryChange()
1731{
1732 const QRect newGeometry = actualGeometry();
1733
1734 qCDebug(lcQpaWindow) << "QCocoaWindow::handleGeometryChange" << window()
1735 << "current" << geometry() << "new" << newGeometry;
1736
1737 // It can happen that the current NSWindow is nil (if we are changing styleMask
1738 // from/to borderless, and the content view is being re-parented), which results
1739 // in invalid coordinates.
1740 if (m_inSetStyleMask && !m_view.window) {
1741 qCDebug(lcQpaWindow) << "Lacking window during style mask update, ignoring geometry change";
1742 return;
1743 }
1744
1745 // Prevent geometry change during initialization, as that will result
1746 // in a resize event, and Qt expects those to come after the show event.
1747 // FIXME: Remove once we've clarified the Qt behavior for this.
1748 if (!m_initialized) {
1749 // But update the QPlatformWindow reality
1750 QPlatformWindow::setGeometry(newGeometry);
1751 qCDebug(lcQpaWindow) << "Window still initializing, skipping event";
1752 return;
1753 }
1754
1755 QWindowSystemInterface::handleGeometryChange(window(), newGeometry);
1756
1757 // Changing the window geometry may affect the safe area margins
1758 updateSafeAreaMarginsIfNeeded();
1759
1760 // Guard against processing window system events during QWindow::setGeometry
1761 // calls, which Qt and Qt applications do not expect.
1762 if (!m_inSetGeometry)
1763 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
1764}
1765
1766void QCocoaWindow::handleExposeEvent(const QRegion &region)
1767{
1768 // Ideally we'd implement isExposed() in terms of these properties,
1769 // plus the occlusionState of the NSWindow, and let the expose event
1770 // pull the exposed state out when needed. However, when the window
1771 // is first shown we receive a drawRect call where the occlusionState
1772 // of the window is still hidden, but we still want to prepare the
1773 // window for display by issuing an expose event to Qt. To work around
1774 // this we don't use the occlusionState directly, but instead base
1775 // the exposed state on the region we get in, which in the case of
1776 // a window being obscured is an empty region, and in the case of
1777 // a drawRect call is a non-null region, even if occlusionState
1778 // is still hidden. This ensures the window is prepared for display.
1779 if (m_view.window.visible && m_view.window.screen
1780 && !geometry().size().isEmpty() && !region.isEmpty()
1781 && !m_view.hiddenOrHasHiddenAncestor) {
1782 m_exposedRect = region.boundingRect();
1783 } else {
1784 m_exposedRect = QRect();
1785 }
1786
1787 qCDebug(lcQpaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed();
1788
1789 QPointer<QCocoaWindow> destructionGuard = this;
1790 QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region);
1791 if (!destructionGuard)
1792 return;
1793
1794 if (!isExposed())
1795 static_cast<QCocoaScreen *>(screen())->maybeStopDisplayLink();
1796}
1797
1798// --------------------------------------------------------------------------
1799
1800bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
1801{
1802 if (type == Qt::Widget)
1803 type = window()->type();
1804 if (type == Qt::Tool)
1805 return false; // Qt::Tool has the Popup bit set but isn't, at least on Mac.
1806
1807 return ((type & Qt::Popup) == Qt::Popup);
1808}
1809
1810/*!
1811 Checks if the window is the content view of its immediate NSWindow.
1812
1813 Being the content view of a NSWindow means the QWindow is
1814 the highest accessible NSView object in the window's view
1815 hierarchy.
1816
1817 This is the case if the QWindow is a top level window.
1818*/
1819bool QCocoaWindow::isContentView() const
1820{
1821 return m_view.window.contentView == m_view;
1822}
1823
1824/*!
1825 Recreates (or removes) the NSWindow for this QWindow, if needed.
1826
1827 A QWindow may need a corresponding NSWindow/NSPanel, depending on
1828 whether or not it's a top level or not, window flags, etc.
1829
1830 \sa windowClass
1831*/
1832void QCocoaWindow::recreateWindowIfNeeded()
1833{
1834 QMacAutoReleasePool pool;
1835
1836 auto *parentWindow = static_cast<QCocoaWindow *>(QPlatformWindow::parent());
1837
1838 const bool shouldManageTopLevelWindow = !parentWindow
1839 && !isSubWindow(window()) && !isForeignWindow();
1840
1841 if (shouldManageTopLevelWindow) {
1842 auto *windowController = qt_objc_cast<QNSWindowController*>(m_view.window.windowController);
1843 const bool isControllerContentView = m_view == windowController.window.contentView;
1844 const bool windowClassIsCorrect = [m_view.window isKindOfClass:windowClass()];
1845
1846 if (!isControllerContentView || !windowClassIsCorrect) {
1847 qCDebug(lcQpaWindow) << "Creating new window controller for" << m_view;
1848 windowController = [[QNSWindowController alloc] initWithCocoaWindow:this];
1849
1850 m_view.postsFrameChangedNotifications = NO;
1851 windowController.window.contentView = m_view;
1852 m_view.postsFrameChangedNotifications = YES;
1853
1854 // The view now owns the window controller
1855 [windowController release];
1856 } else {
1857 qCDebug(lcQpaWindow) << "Re-using existing window controller" << windowController;
1858 }
1859 } else if (parentWindow) {
1860 if (m_view.superview != parentWindow->view()) {
1861 qCDebug(lcQpaWindow) << "Reparenting view to new parent" << parentWindow;
1862 [parentWindow->view() addSubview:m_view];
1863 }
1864 } else if (qnsview_cast(m_view.superview) && !isEmbedded()) {
1865 qCDebug(lcQpaWindow) << "Removing view from superview" << m_view.superview;
1866 [m_view removeFromSuperview];
1867 }
1868}
1869
1870Class QCocoaWindow::windowClass() const
1871{
1872 const auto type = window()->type();
1873 return ((type & Qt::Popup) == Qt::Popup
1874 || (type & Qt::Dialog) == Qt::Dialog) ?
1875 QNSPanel.class : QNSWindow.class;
1876}
1877
1878void QCocoaWindow::requestUpdate()
1879{
1880 qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window()
1881 << "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
1882
1883 if (updatesWithDisplayLink()) {
1884 if (!static_cast<QCocoaScreen *>(screen())->requestUpdate()) {
1885 qCDebug(lcQpaDrawing) << "Falling back to timer-based update request";
1886 QPlatformWindow::requestUpdate();
1887 }
1888 } else {
1889 // Fall back to the un-throttled timer-based callback
1890 QPlatformWindow::requestUpdate();
1891 }
1892}
1893
1894bool QCocoaWindow::updatesWithDisplayLink() const
1895{
1896 // Update via CVDisplayLink if Vsync is enabled
1897 return format().swapInterval() != 0;
1898}
1899
1900void QCocoaWindow::deliverUpdateRequest()
1901{
1902 qCDebug(lcQpaDrawing) << "Delivering update request to" << window();
1903 QScopedValueRollback<bool> blocker(m_deliveringUpdateRequest, true);
1904
1905 if (auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(contentLayer())) {
1906 // We attempt a read lock here, so that the animation/render thread is
1907 // prioritized lower than the main thread's displayLayer processing.
1908 // Without this the two threads might fight over the next drawable,
1909 // starving the main thread's presentation of the resized layer.
1910 if (!qtMetalLayer.displayLock.tryLockForRead()) {
1911 qCDebug(lcQpaDrawing) << "Deferring update request"
1912 << "due to" << qtMetalLayer << "needing display";
1913 return;
1914 }
1915
1916 // But we don't hold the lock, as the update request can recurse
1917 // back into setNeedsDisplay, which would deadlock.
1918 qtMetalLayer.displayLock.unlock();
1919 }
1920
1921 QPlatformWindow::deliverUpdateRequest();
1922}
1923
1924void QCocoaWindow::requestActivateWindow()
1925{
1926 QMacAutoReleasePool pool;
1927 [m_view.window makeFirstResponder:m_view];
1928 [m_view.window makeKeyWindow];
1929}
1930
1931/*
1932 Closes all popups, and removes observers and monitors.
1933*/
1934void QCocoaWindow::closeAllPopups()
1935{
1936 QGuiApplicationPrivate::instance()->closeAllPopups();
1937
1938 removePopupMonitor();
1939}
1940
1941void QCocoaWindow::removePopupMonitor()
1942{
1943 if (s_globalMouseMonitor) {
1944 [NSEvent removeMonitor:s_globalMouseMonitor];
1945 s_globalMouseMonitor = nil;
1946 }
1947 if (s_applicationActivationObserver) {
1948 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver];
1949 s_applicationActivationObserver = nil;
1950 }
1951}
1952
1953void QCocoaWindow::setupPopupMonitor()
1954{
1955 // we open a popup window while we are not active. None of our existing event
1956 // handlers will get called if the user now clicks anywhere outside the application
1957 // or activates another window. Use a global event monitor to watch for mouse
1958 // presses, and close popups. We also want mouse tracking in the popup to work, so
1959 // also watch for MouseMoved.
1960 if (!s_globalMouseMonitor) {
1961 // we only get LeftMouseDown events when we also set LeftMouseUp.
1962 constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp
1963 | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown
1964 | NSEventMaskMouseMoved;
1965 s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask
1966 handler:^(NSEvent *e){
1967 if (!QGuiApplicationPrivate::instance()->activePopupWindow()) {
1968 removePopupMonitor();
1969 return;
1970 }
1971 const auto eventType = cocoaEvent2QtMouseEvent(e);
1972 if (eventType == QEvent::MouseMove) {
1973 if (s_windowUnderMouse) {
1974 QWindow *window = s_windowUnderMouse->window();
1975 const auto button = cocoaButton2QtButton(e);
1976 const auto buttons = currentlyPressedMouseButtons();
1977 const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
1978 const auto localPoint = window->mapFromGlobal(globalPoint.toPoint());
1979 QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint,
1980 buttons, button, eventType);
1981 }
1982 } else {
1983 closeAllPopups();
1984 }
1985 }];
1986 }
1987 // The activation observer also gets called when we become active because the user clicks
1988 // into the popup. This should not close the popup, so QCocoaApplicationDelegate's
1989 // applicationDidBecomeActive implementation removes this observer.
1990 if (!s_applicationActivationObserver) {
1991 s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
1992 addObserverForName:NSWorkspaceDidActivateApplicationNotification
1993 object:nil queue:nil
1994 usingBlock:^(NSNotification *){
1995 closeAllPopups();
1996 }];
1997 }
1998}
1999
2000QCocoaNSWindow *QCocoaWindow::createNSWindow()
2001{
2002 QMacAutoReleasePool pool;
2003
2004 Qt::WindowType type = window()->type();
2005 Qt::WindowFlags flags = window()->flags();
2006
2007 QRect rect = geometry();
2008
2009 QScreen *targetScreen = nullptr;
2010 for (QScreen *screen : QGuiApplication::screens()) {
2011 if (screen->geometry().contains(rect.topLeft())) {
2012 targetScreen = screen;
2013 break;
2014 }
2015 }
2016
2017 NSWindowStyleMask styleMask = windowStyleMask(flags);
2018
2019 if (!targetScreen) {
2020 qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen";
2021 targetScreen = QGuiApplication::primaryScreen();
2022 // Unless the window is created as borderless AppKit won't find a position and
2023 // screen that's close to the requested invalid position, and will always place
2024 // the window on the primary screen.
2025 styleMask = NSWindowStyleMaskBorderless;
2026 }
2027
2028 rect.translate(-targetScreen->geometry().topLeft());
2029 auto *targetCocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle());
2030 NSRect contentRect = QCocoaScreen::mapToNative(rect, targetCocoaScreen);
2031
2032 if (targetScreen->primaryOrientation() == Qt::PortraitOrientation) {
2033 // The macOS window manager has a bug, where if a screen is rotated, it will not allow
2034 // a window to be created within the area of the screen that has a Y coordinate (I quadrant)
2035 // higher than the height of the screen in its non-rotated state (including a magic padding
2036 // of 24 points), unless the window is created with the NSWindowStyleMaskBorderless style mask.
2037 if (styleMask && (contentRect.origin.y + 24 > targetScreen->geometry().width())) {
2038 qCDebug(lcQpaWindow) << "Window positioned on portrait screen."
2039 << "Adjusting style mask during creation";
2040 styleMask = NSWindowStyleMaskBorderless;
2041 }
2042 }
2043
2044 // Create NSWindow
2045 QCocoaNSWindow *nsWindow = [[windowClass() alloc] initWithContentRect:contentRect
2046 // Mask will be updated in setWindowFlags if not the final mask
2047 styleMask:styleMask
2048 // Deferring window creation breaks OpenGL (the GL context is
2049 // set up before the window is shown and needs a proper window)
2050 backing:NSBackingStoreBuffered defer:NO
2051 screen:targetCocoaScreen->nativeScreen()
2052 platformWindow:this];
2053
2054 // The resulting screen can be different from the screen requested if
2055 // for example the application has been assigned to a specific display.
2056 auto resultingScreen = QCocoaScreen::get(nsWindow.screen);
2057
2058 // But may not always be resolved at this point, in which case we fall back
2059 // to the target screen. The real screen will be delivered as a screen change
2060 // when resolved as part of ordering the window on screen.
2061 if (!resultingScreen)
2062 resultingScreen = targetCocoaScreen;
2063
2064 if (resultingScreen->screen() != window()->screen()) {
2065 QWindowSystemInterface::handleWindowScreenChanged<
2066 QWindowSystemInterface::SynchronousDelivery>(window(), resultingScreen->screen());
2067 }
2068
2069 static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init],
2070 [](QNSWindowDelegate *delegate) { [delegate release]; });
2071 nsWindow.delegate = sharedDelegate.get();
2072
2073 // Prevent Cocoa from releasing the window on close. Qt
2074 // handles the close event asynchronously and we want to
2075 // make sure that NSWindow stays valid until the
2076 // QCocoaWindow is deleted by Qt.
2077 [nsWindow setReleasedWhenClosed:NO];
2078
2079 if (alwaysShowToolWindow()) {
2080 static dispatch_once_t onceToken;
2081 dispatch_once(&onceToken, ^{
2082 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
2083 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
2084 name:NSApplicationWillResignActiveNotification object:nil];
2085 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
2086 name:NSApplicationWillBecomeActiveNotification object:nil];
2087 });
2088 }
2089
2090 nsWindow.restorable = NO;
2091 nsWindow.level = windowLevel(flags);
2092 nsWindow.tabbingMode = NSWindowTabbingModeDisallowed;
2093
2094 if ([nsWindow isKindOfClass:NSPanel.class]) {
2095 // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
2096 nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
2097
2098 // Make popup windows show on the same desktop as the parent window
2099 nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
2100 | NSWindowCollectionBehaviorMoveToActiveSpace;
2101
2102 if ((type & Qt::Popup) == Qt::Popup) {
2103 nsWindow.hasShadow = YES;
2104 nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
2105 if (QGuiApplication::applicationState() != Qt::ApplicationActive)
2106 setupPopupMonitor();
2107 }
2108 }
2109
2110 // We propagate the view's color space granulary to both the IOSurfaces
2111 // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
2112 // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
2113 // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
2114 // workaround we set the NSWindow's color space, which affects GL drawing
2115 // with NSOpenGLContext as well. This does not conflict with the granular
2116 // modifications we do to each surface for raster or Metal.
2117 if (auto *qtView = qnsview_cast(m_view))
2118 nsWindow.colorSpace = qtView.colorSpace;
2119
2120 return nsWindow;
2121}
2122
2123bool QCocoaWindow::alwaysShowToolWindow() const
2124{
2125 return qt_mac_resolveOption(false, window(), "_q_macAlwaysShowToolWindow", "");
2126}
2127
2128bool QCocoaWindow::setWindowModified(bool modified)
2129{
2130 QMacAutoReleasePool pool;
2131
2132 if (!isContentView())
2133 return false;
2134
2135 m_view.window.documentEdited = modified;
2136 return true;
2137}
2138
2139void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
2140{
2141 m_menubar = mb;
2142}
2143
2144QCocoaMenuBar *QCocoaWindow::menubar() const
2145{
2146 return m_menubar;
2147}
2148
2149void QCocoaWindow::setWindowCursor(NSCursor *cursor)
2150{
2151 QMacAutoReleasePool pool;
2152
2153 // Setting a cursor in a foreign view is not supported
2154 if (isForeignWindow())
2155 return;
2156
2157 qCInfo(lcQpaMouse) << "Setting" << this << "cursor to" << cursor;
2158
2159 QNSView *view = qnsview_cast(m_view);
2160 if (cursor == view.cursor)
2161 return;
2162
2163 view.cursor = cursor;
2164
2165 // We're not using the the legacy cursor rects API to manage our
2166 // cursor, but calling this function also invalidates AppKit's
2167 // view of whether or not we need a cursorUpdate callback for
2168 // our tracking area.
2169 [m_view.window invalidateCursorRectsForView:m_view];
2170
2171 // We've informed AppKit that we need a cursorUpdate, but cursor
2172 // updates for tracking areas are deferred in some cases, such as
2173 // when the mouse is down, whereas we want a synchronous update.
2174 // To ensure an updated cursor we synthesize a cursor update event
2175 // now if the window is otherwise allowed to change the cursor.
2176 auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
2177 auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
2178 bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
2179 auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
2180 bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
2181 if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
2182 qCDebug(lcQpaMouse) << "Synthesizing cursor update";
2183 [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
2184 location:locationInWindow modifierFlags:0 timestamp:0
2185 windowNumber:m_view.window.windowNumber context:nil
2186 eventNumber:0 trackingNumber:0 userData:0]];
2187 }
2188}
2189
2190void QCocoaWindow::registerTouch(bool enable)
2191{
2192 m_registerTouchCount += enable ? 1 : -1;
2193 if (enable && m_registerTouchCount == 1)
2194 m_view.allowedTouchTypes |= NSTouchTypeMaskIndirect;
2195 else if (m_registerTouchCount == 0)
2196 m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
2197}
2198
2199qreal QCocoaWindow::devicePixelRatio() const
2200{
2201 // The documented way to observe the relationship between device-independent
2202 // and device pixels is to use one for the convertToBacking functions. Other
2203 // methods such as [NSWindow backingScaleFactor] might not give the correct
2204 // result, for example if setWantsBestResolutionOpenGLSurface is not set or
2205 // or ignored by the OpenGL driver.
2206 NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
2207 return backingSize.height;
2208}
2209
2210QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint)
2211{
2212 QWindow *targetWindow = window();
2213 for (QObject *child : targetWindow->children())
2214 if (QWindow *childWindow = qobject_cast<QWindow *>(child))
2215 if (QPlatformWindow *handle = childWindow->handle())
2216 if (handle->isExposed() && childWindow->geometry().contains(windowPoint))
2217 targetWindow = static_cast<QCocoaWindow*>(handle)->childWindowAt(windowPoint - childWindow->position());
2218
2219 return targetWindow;
2220}
2221
2222bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
2223{
2224 // This function speaks up if there's any reason
2225 // to refuse key window or first responder state.
2226
2227 if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
2228 return true;
2229
2230 // For application modal windows, as well as direct parent windows
2231 // of window modal windows, AppKit takes care of blocking interaction.
2232 // The Qt expectation however, is that all transient parents of a
2233 // window modal window is blocked, as reflected by QGuiApplication.
2234 // We reflect this by returning false from this function for transient
2235 // parents blocked by a modal window, but limit it to the cases not
2236 // covered by AppKit to avoid potential unwanted side effects.
2237 QWindow *modalWindow = nullptr;
2238 if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
2239 if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
2240 qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
2241 << "blocked by" << modalWindow;
2242 return true;
2243 }
2244 }
2245
2246 if (m_inSetVisible) {
2247 QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
2248 if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
2249 return true;
2250 }
2251
2252 return false;
2253}
2254
2255bool QCocoaWindow::windowEvent(QEvent *event)
2256{
2257 switch (event->type()) {
2258 case QEvent::WindowBlocked:
2259 case QEvent::WindowUnblocked:
2260 updateTitleBarButtons(window()->flags());
2261 break;
2262 default:
2263 break;
2264 }
2265
2266 return QPlatformWindow::windowEvent(event);
2267}
2268
2269QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
2270{
2271 if (!m_view)
2272 return QPoint();
2273 const NSPoint origin = [m_view isFlipped] ? NSMakePoint(0, [m_view frame].size.height)
2274 : NSMakePoint(0, 0);
2275 const NSRect visibleRect = [m_view visibleRect];
2276
2277 return QPoint(visibleRect.origin.x, -visibleRect.origin.y + (origin.y - visibleRect.size.height));
2278}
2279
2280QMargins QCocoaWindow::frameMargins() const
2281{
2282 QMacAutoReleasePool pool;
2283
2284 // Child windows don't have frame margins. We explicitly don't check
2285 // isContentView() here, as we want to know the frame argins also for
2286 // windows that haven't gotten their NSWindow yet.
2287 if (QPlatformWindow::parent())
2288 return QMargins();
2289
2290 NSRect frameRect;
2291 NSRect contentRect;
2292
2293 if (m_view.window) {
2294 frameRect = m_view.window.frame;
2295 contentRect = [m_view.window contentRectForFrameRect:frameRect];
2296 } else {
2297 contentRect = m_view.frame;
2298 frameRect = [NSWindow frameRectForContentRect:contentRect styleMask:windowStyleMask(window()->flags())];
2299 }
2300
2301 return QMargins(frameRect.origin.x - contentRect.origin.x,
2302 (frameRect.origin.y + frameRect.size.height) - (contentRect.origin.y + contentRect.size.height),
2303 (frameRect.origin.x + frameRect.size.width) - (contentRect.origin.x + contentRect.size.width),
2304 contentRect.origin.y - contentRect.origin.y);
2305}
2306
2307void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled)
2308{
2309 m_frameStrutEventsEnabled = enabled;
2310}
2311
2312QPoint QCocoaWindow::mapToGlobal(const QPoint &point) const
2313{
2314 NSPoint windowPoint = [m_view convertPoint:point.toCGPoint() toView:nil];
2315 NSPoint screenPoint = [m_view.window convertPointToScreen:windowPoint];
2316 return QCocoaScreen::mapFromNative(screenPoint).toPoint();
2317}
2318
2319QPoint QCocoaWindow::mapFromGlobal(const QPoint &point) const
2320{
2321 NSPoint screenPoint = QCocoaScreen::mapToNative(point);
2322 NSPoint windowPoint = [m_view.window convertPointFromScreen:screenPoint];
2323 return QPointF::fromCGPoint([m_view convertPoint:windowPoint fromView:nil]).toPoint();
2324}
2325
2326CGPoint QCocoaWindow::mapToNative(const QPointF &point, NSView *referenceView)
2327{
2328 if (!referenceView || referenceView.flipped)
2329 return point.toCGPoint();
2330 else
2331 return qt_mac_flip(point, QRectF::fromCGRect(referenceView.bounds)).toCGPoint();
2332}
2333
2334CGRect QCocoaWindow::mapToNative(const QRectF &rect, NSView *referenceView)
2335{
2336 if (!referenceView || referenceView.flipped)
2337 return rect.toCGRect();
2338 else
2339 return qt_mac_flip(rect, QRectF::fromCGRect(referenceView.bounds)).toCGRect();
2340}
2341
2342QPointF QCocoaWindow::mapFromNative(CGPoint point, NSView *referenceView)
2343{
2344 if (!referenceView || referenceView.flipped)
2345 return QPointF::fromCGPoint(point);
2346 else
2347 return qt_mac_flip(QPointF::fromCGPoint(point), QRectF::fromCGRect(referenceView.bounds));
2348}
2349
2350QRectF QCocoaWindow::mapFromNative(CGRect rect, NSView *referenceView)
2351{
2352 if (!referenceView || referenceView.flipped)
2353 return QRectF::fromCGRect(rect);
2354 else
2355 return qt_mac_flip(QRectF::fromCGRect(rect), QRectF::fromCGRect(referenceView.bounds));
2356}
2357
2358CALayer *QCocoaWindow::contentLayer() const
2359{
2360 auto *layer = m_view.layer;
2361 if (auto *containerLayer = qt_objc_cast<QContainerLayer*>(layer))
2362 layer = containerLayer.contentLayer;
2363 return layer;
2364}
2365
2366void QCocoaWindow::manageVisualEffectArea(quintptr identifier, const QRect &rect,
2367 NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode,
2368 NSVisualEffectState activationState)
2369{
2370 if (!qt_objc_cast<QContainerLayer*>(m_view.layer)) {
2371 qCWarning(lcQpaWindow) << "Can not manage visual effect areas"
2372 << "in views without a container layer";
2373 return;
2374 }
2375
2376 qCDebug(lcQpaWindow) << "Updating visual effect area" << identifier
2377 << "to" << rect << "with material" << material << "blend mode"
2378 << blendMode << "and activation state" << activationState;
2379
2380 NSVisualEffectView *effectView = nullptr;
2381 if (m_effectViews.contains(identifier)) {
2382 effectView = m_effectViews.value(identifier);
2383 if (rect.isEmpty()) {
2384 [effectView removeFromSuperview];
2385 m_effectViews.remove(identifier);
2386 return;
2387 }
2388 } else if (!rect.isEmpty()) {
2389 effectView = [NSVisualEffectView new];
2390 // Ensure that the visual effect layer is stacked well
2391 // below our content layer (which defaults to a z of 0).
2392 effectView.wantsLayer = YES;
2393 effectView.layer.zPosition = -FLT_MAX;
2394 [m_view addSubview:effectView];
2395 m_effectViews.insert(identifier, effectView);
2396 }
2397
2398 if (!effectView)
2399 return;
2400
2401 effectView.frame = rect.toCGRect();
2402 effectView.material = material;
2403 effectView.blendingMode = blendMode;
2404 effectView.state = activationState;
2405}
2406
2407#ifndef QT_NO_DEBUG_STREAM
2408QDebug operator<<(QDebug debug, const QCocoaWindow *window)
2409{
2410 QDebugStateSaver saver(debug);
2411 debug.nospace();
2412 debug << "QCocoaWindow(" << (const void *)window;
2413 if (window)
2414 debug << ", window=" << window->window();
2415 debug << ')';
2416 return debug;
2417}
2418#endif // !QT_NO_DEBUG_STREAM
2419
2420// --------------------------------------------------------------------------
2421
2422#if QT_CONFIG(accessibility)
2423
2424static void setAccessibilityParent(NSView *view, id accessibilityParent)
2425{
2426 // The elements that show up in the parent's child list are the view itself
2427 // if the view is unignored, and the view's own accessible children if it is
2428 // not, so we set the parent on that same set, for the child and parent
2429 // relations to match.
2430 for (id<NSAccessibility> element in NSAccessibilityUnignoredChildrenForOnlyChild(view)) {
2431 // We can't assume all elements implement this part of the NSAccessibility protocol
2432 if (![element respondsToSelector:@selector(setAccessibilityParent:)])
2433 continue;
2434
2435 if (element.accessibilityParent == accessibilityParent)
2436 continue;
2437
2438 qCDebug(lcQpaAccessibility) << "Setting a11y parent of" << element
2439 << "for" << view << "to" << accessibilityParent;
2440 element.accessibilityParent = accessibilityParent;
2441 }
2442}
2443
2444/*
2445 Reflects the window's accessibility parent onto a foreign view.
2446
2447 Our own views resolve the parent on demand via [QNSView accessibilityParent],
2448 but a foreign view has no getter of ours to run, so the parent has to be
2449 pushed onto it. The parent we push is the unignored ancestor of the window
2450 container hosting the window, and it changes whenever anything above the
2451 container moves in the hierarchy, or stops being ignored.
2452
2453 Rather than tracking those changes we resolve the parent here, called from
2454 QCocoaAccessible::viewFor() on each of the paths that hand a foreign view to
2455 an accessibility client, so that the pushed parent is resolved as late as
2456 possible.
2457*/
2458void QCocoaWindow::updateAccessibleParent()
2459{
2460 if (!isForeignWindow())
2461 return;
2462
2463 QObject *accessibleParent = QWindowPrivate::get(window())->accessibleParent;
2464 if (QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(accessibleParent)) {
2465 setAccessibilityParent(m_view, NSAccessibilityUnignoredAncestor(
2466 [QMacAccessibilityElement elementWithInterface:iface]));
2467 m_didOverrideAccessibleParent = true;
2468 } else {
2469 clearAccessibleParent();
2470 }
2471}
2472
2473void QCocoaWindow::clearAccessibleParent()
2474{
2475 if (!isForeignWindow() || !m_didOverrideAccessibleParent)
2476 return;
2477
2478 // AppKit has no way to remove an accessibility attribute override, and
2479 // stores nil as an explicit "no parent", so the view can not be handed back
2480 // the parent it resolves by default. By the time we get here the view has
2481 // normally left the view hierarchy of its former accessible parent, so our
2482 // best option is to reflect an unknown accessible parent and let the new
2483 // parent update it if needed.
2484 setAccessibilityParent(m_view, nil);
2485
2486 m_didOverrideAccessibleParent = false;
2487}
2488
2489#endif // QT_CONFIG(accessibility)
2490
2491// --------------------------------------------------------------------------
2492
2493QT_END_NAMESPACE
2494
2495#include "moc_qcocoawindow.cpp"
void setGeometry(const QRect &rect, QWindowPrivate::PositionPolicy positionPolicy)
QMargins safeAreaMargins() const override
The safe area margins of a window represent the area that is safe to place content within,...
QRect normalGeometry() const override
the geometry of the window as it will appear when shown as a normal (not maximized or full screen) to...
bool isForeignWindow() const override
QRect geometry() const override
Returns the current geometry of a window.
void updateSafeAreaMarginsIfNeeded()
void setGeometry(const QRect &rect) override
This function is called by Qt whenever a window is moved or resized using the QWindow API.
void updateNormalGeometry()
QRect actualGeometry() const
bool isEmbedded() const override
Returns true if the window is a child of a non-Qt window.
static QPointer< QCocoaWindow > s_windowUnderMouse
bool isContentView() const
Checks if the window is the content view of its immediate NSWindow.
void recreateWindowIfNeeded()
Recreates (or removes) the NSWindow for this QWindow, if needed.
void handleGeometryChange()
void setVisible(bool visible) override
Reimplemented in subclasses to show the surface if visible is true, and hide it if visible is false.
bool startSystemMove() override
Reimplement this method to start a system move operation if the system supports it and return true to...
static const int NoAlertRequest
void initialize() override
Called as part of QWindow::create(), after constructing the window.
QSurfaceFormat format() const override
Returns the actual surface format of the window.
QRect window() const
Returns the window rectangle.
QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QMacAccessibilityElement, NSObject -(void) invalidate;) QT_BEGIN_NAMESPACE bool QAccessibleCache
Q_FORWARD_DECLARE_OBJC_CLASS(NSEvent)
unsigned long NSUInteger
#define Q_NOTIFICATION_PREFIX
static void qRegisterNotificationCallbacks()
const NSNotificationName QCocoaWindowWillReleaseQNSViewNotification
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")