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