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 // If 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 // Similarly, if another application is activated while our non-active application is
1990 // showing a popup, we want to hide the popup. Except if the activated application is
1991 // our own, which might happen for example if a popup is shown during startup, before
1992 // our logic to activate the app on launch runs in applicationDidFinishLaunching, or
1993 // simply because the user clicks into the popup, thereby activating our app. Once
1994 // our app is active the observer is no longer needed, so we remove it.
1995 if (!s_applicationActivationObserver) {
1996 s_applicationActivationObserver = [NSWorkspace.sharedWorkspace.notificationCenter
1997 addObserverForName:NSWorkspaceDidActivateApplicationNotification object:nil queue:NSOperationQueue.mainQueue
1998 usingBlock:^(NSNotification *notification) {
1999 NSRunningApplication *activatedApp = notification.userInfo[NSWorkspaceApplicationKey];
2000 if ([activatedApp isEqual:NSRunningApplication.currentApplication]) {
2001 [NSWorkspace.sharedWorkspace.notificationCenter
2002 removeObserver:s_applicationActivationObserver];
2003 s_applicationActivationObserver = nil;
2004 return;
2005 }
2006 closeAllPopups();
2007 }
2008 ];
2009 }
2010}
2011
2012QCocoaNSWindow *QCocoaWindow::createNSWindow()
2013{
2014 QMacAutoReleasePool pool;
2015
2016 Qt::WindowType type = window()->type();
2017 Qt::WindowFlags flags = window()->flags();
2018
2019 QRect rect = geometry();
2020
2021 QScreen *targetScreen = nullptr;
2022 for (QScreen *screen : QGuiApplication::screens()) {
2023 if (screen->handle()->geometry().contains(rect.topLeft())) {
2024 targetScreen = screen;
2025 break;
2026 }
2027 }
2028
2029 NSWindowStyleMask styleMask = windowStyleMask(flags);
2030
2031 if (!targetScreen) {
2032 qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen";
2033 targetScreen = QGuiApplication::primaryScreen();
2034 // Unless the window is created as borderless AppKit won't find a position and
2035 // screen that's close to the requested invalid position, and will always place
2036 // the window on the primary screen.
2037 styleMask = NSWindowStyleMaskBorderless;
2038 }
2039
2040 auto *targetCocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle());
2041 rect.translate(-targetCocoaScreen->geometry().topLeft());
2042 NSRect contentRect = QCocoaScreen::mapToNative(rect, targetCocoaScreen);
2043
2044 if (targetScreen->primaryOrientation() == Qt::PortraitOrientation) {
2045 // The macOS window manager has a bug, where if a screen is rotated, it will not allow
2046 // a window to be created within the area of the screen that has a Y coordinate (I quadrant)
2047 // higher than the height of the screen in its non-rotated state (including a magic padding
2048 // of 24 points), unless the window is created with the NSWindowStyleMaskBorderless style mask.
2049 if (styleMask && (contentRect.origin.y + 24 > targetCocoaScreen->geometry().width())) {
2050 qCDebug(lcQpaWindow) << "Window positioned on portrait screen."
2051 << "Adjusting style mask during creation";
2052 styleMask = NSWindowStyleMaskBorderless;
2053 }
2054 }
2055
2056 // Create NSWindow
2057 QCocoaNSWindow *nsWindow = [[windowClass() alloc] initWithContentRect:contentRect
2058 // Mask will be updated in setWindowFlags if not the final mask
2059 styleMask:styleMask
2060 // Deferring window creation breaks OpenGL (the GL context is
2061 // set up before the window is shown and needs a proper window)
2062 backing:NSBackingStoreBuffered defer:NO
2063 screen:targetCocoaScreen->nativeScreen()
2064 platformWindow:this];
2065
2066 // The resulting screen can be different from the screen requested if
2067 // for example the application has been assigned to a specific display.
2068 auto resultingScreen = QCocoaScreen::get(nsWindow.screen);
2069
2070 // But may not always be resolved at this point, in which case we fall back
2071 // to the target screen. The real screen will be delivered as a screen change
2072 // when resolved as part of ordering the window on screen.
2073 if (!resultingScreen)
2074 resultingScreen = targetCocoaScreen;
2075
2076 if (resultingScreen->screen() != window()->screen()) {
2077 QWindowSystemInterface::handleWindowScreenChanged<
2078 QWindowSystemInterface::SynchronousDelivery>(window(), resultingScreen->screen());
2079 }
2080
2081 static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init],
2082 [](QNSWindowDelegate *delegate) { [delegate release]; });
2083 nsWindow.delegate = sharedDelegate.get();
2084
2085 // Prevent Cocoa from releasing the window on close. Qt
2086 // handles the close event asynchronously and we want to
2087 // make sure that NSWindow stays valid until the
2088 // QCocoaWindow is deleted by Qt.
2089 [nsWindow setReleasedWhenClosed:NO];
2090
2091 if (alwaysShowToolWindow()) {
2092 static dispatch_once_t onceToken;
2093 dispatch_once(&onceToken, ^{
2094 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
2095 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
2096 name:NSApplicationWillResignActiveNotification object:nil];
2097 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
2098 name:NSApplicationWillBecomeActiveNotification object:nil];
2099 });
2100 }
2101
2102 nsWindow.restorable = NO;
2103 nsWindow.level = windowLevel(flags);
2104 nsWindow.tabbingMode = NSWindowTabbingModeDisallowed;
2105
2106 if ([nsWindow isKindOfClass:NSPanel.class]) {
2107 // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
2108 nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
2109
2110 // Make popup windows show on the same desktop as the parent window
2111 nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
2112 | NSWindowCollectionBehaviorMoveToActiveSpace;
2113
2114 if ((type & Qt::Popup) == Qt::Popup) {
2115 nsWindow.hasShadow = YES;
2116 nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
2117 if (QGuiApplication::applicationState() != Qt::ApplicationActive)
2118 setupPopupMonitor();
2119 }
2120 }
2121
2122 // We propagate the view's color space granulary to both the IOSurfaces
2123 // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
2124 // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
2125 // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
2126 // workaround we set the NSWindow's color space, which affects GL drawing
2127 // with NSOpenGLContext as well. This does not conflict with the granular
2128 // modifications we do to each surface for raster or Metal.
2129 if (auto *qtView = qnsview_cast(m_view))
2130 nsWindow.colorSpace = qtView.colorSpace;
2131
2132 return nsWindow;
2133}
2134
2135bool QCocoaWindow::alwaysShowToolWindow() const
2136{
2137 return qt_mac_resolveOption(false, window(), "_q_macAlwaysShowToolWindow", "");
2138}
2139
2140bool QCocoaWindow::setWindowModified(bool modified)
2141{
2142 QMacAutoReleasePool pool;
2143
2144 if (!isContentView())
2145 return false;
2146
2147 m_view.window.documentEdited = modified;
2148 return true;
2149}
2150
2151void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
2152{
2153 m_menubar = mb;
2154}
2155
2156QCocoaMenuBar *QCocoaWindow::menubar() const
2157{
2158 return m_menubar;
2159}
2160
2161void QCocoaWindow::setWindowCursor(NSCursor *cursor)
2162{
2163 QMacAutoReleasePool pool;
2164
2165 // Setting a cursor in a foreign view is not supported
2166 if (isForeignWindow())
2167 return;
2168
2169 qCInfo(lcQpaMouse) << "Setting" << this << "cursor to" << cursor;
2170
2171 QNSView *view = qnsview_cast(m_view);
2172 if (cursor == view.cursor)
2173 return;
2174
2175 view.cursor = cursor;
2176
2177 // We're not using the the legacy cursor rects API to manage our
2178 // cursor, but calling this function also invalidates AppKit's
2179 // view of whether or not we need a cursorUpdate callback for
2180 // our tracking area.
2181 [m_view.window invalidateCursorRectsForView:m_view];
2182
2183 // We've informed AppKit that we need a cursorUpdate, but cursor
2184 // updates for tracking areas are deferred in some cases, such as
2185 // when the mouse is down, whereas we want a synchronous update.
2186 // To ensure an updated cursor we synthesize a cursor update event
2187 // now if the window is otherwise allowed to change the cursor.
2188 auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
2189 auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
2190 bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
2191 auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
2192 bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
2193 if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
2194 qCDebug(lcQpaMouse) << "Synthesizing cursor update";
2195 [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
2196 location:locationInWindow modifierFlags:0 timestamp:0
2197 windowNumber:m_view.window.windowNumber context:nil
2198 eventNumber:0 trackingNumber:0 userData:0]];
2199 }
2200}
2201
2202/*!
2203 Register if a window should generate trackpad touch events.
2204
2205 Enabling trackpad touch events has implications for delivery
2206 of other events, for example by causing scrolling event lag.
2207
2208 The registration is ref-counted: multiple clients can enable
2209 touch events, which then will be delivered until all clients
2210 deregister.
2211*/
2212void QCocoaWindow::enableTrackpadTouchDelivery(bool enable)
2213{
2214 m_registeredTrackpadTouchConsumers += enable ? 1 : -1;
2215 updateAllowedTouchTypes();
2216}
2217
2218void QCocoaWindow::updateAllowedTouchTypes()
2219{
2220 bool allowingTrackpadTouch = m_view.allowedTouchTypes & NSTouchTypeMaskIndirect;
2221 if (m_registeredTrackpadTouchConsumers > 0 && !allowingTrackpadTouch) {
2222 qCDebug(lcQpaTouch) << "Enabling trackpad touches for" << m_view;
2223 m_view.allowedTouchTypes |= NSTouchTypeMaskIndirect;
2224 } else if (!m_registeredTrackpadTouchConsumers && allowingTrackpadTouch) {
2225 const auto devices = QInputDevice::devices();
2226 for (const auto *device : devices) {
2227 if (device->type() != QInputDevice::DeviceType::TouchPad)
2228 continue;
2229 const auto *pointingDevice = static_cast<const QPointingDevice *>(device);
2230 const auto *devicePriv = QPointingDevicePrivate::get(pointingDevice);
2231 for (const auto &[pointId, point] : std::as_const(devicePriv->activePoints)) {
2232 if (QMutableEventPoint::window(point.eventPoint) == window()) {
2233 qCDebug(lcQpaTouch) << "Deferring allowed touch type update for"
2234 << m_view << "due to still active" << point.eventPoint;
2235 return;
2236 }
2237 }
2238 }
2239 qCDebug(lcQpaTouch) << "Disabling trackpad touches for" << m_view;
2240 m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
2241 }
2242}
2243
2244qreal QCocoaWindow::devicePixelRatio() const
2245{
2246 // The documented way to observe the relationship between device-independent
2247 // and device pixels is to use one for the convertToBacking functions. Other
2248 // methods such as [NSWindow backingScaleFactor] might not give the correct
2249 // result, for example if setWantsBestResolutionOpenGLSurface is not set or
2250 // or ignored by the OpenGL driver.
2251 NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
2252 return backingSize.height;
2253}
2254
2255QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint)
2256{
2257 QWindow *targetWindow = window();
2258 for (QObject *child : targetWindow->children())
2259 if (QWindow *childWindow = qobject_cast<QWindow *>(child))
2260 if (QPlatformWindow *handle = childWindow->handle())
2261 if (handle->isExposed() && childWindow->geometry().contains(windowPoint))
2262 targetWindow = static_cast<QCocoaWindow*>(handle)->childWindowAt(windowPoint - childWindow->position());
2263
2264 return targetWindow;
2265}
2266
2267bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
2268{
2269 // This function speaks up if there's any reason
2270 // to refuse key window or first responder state.
2271
2272 if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
2273 return true;
2274
2275 // For application modal windows, as well as direct parent windows
2276 // of window modal windows, AppKit takes care of blocking interaction.
2277 // The Qt expectation however, is that all transient parents of a
2278 // window modal window is blocked, as reflected by QGuiApplication.
2279 // We reflect this by returning false from this function for transient
2280 // parents blocked by a modal window, but limit it to the cases not
2281 // covered by AppKit to avoid potential unwanted side effects.
2282 QWindow *modalWindow = nullptr;
2283 if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
2284 if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
2285 qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
2286 << "blocked by" << modalWindow;
2287 return true;
2288 }
2289 }
2290
2291 if (m_inSetVisible) {
2292 QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
2293 if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
2294 return true;
2295 }
2296
2297 return false;
2298}
2299
2300bool QCocoaWindow::windowEvent(QEvent *event)
2301{
2302 switch (event->type()) {
2303 case QEvent::WindowBlocked:
2304 case QEvent::WindowUnblocked:
2305 updateTitleBarButtons(window()->flags());
2306 break;
2307 default:
2308 break;
2309 }
2310
2311 return QPlatformWindow::windowEvent(event);
2312}
2313
2314QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
2315{
2316 if (!m_view)
2317 return QPoint();
2318 const NSPoint origin = [m_view isFlipped] ? NSMakePoint(0, [m_view frame].size.height)
2319 : NSMakePoint(0, 0);
2320 const NSRect visibleRect = [m_view visibleRect];
2321
2322 return QPoint(visibleRect.origin.x, -visibleRect.origin.y + (origin.y - visibleRect.size.height));
2323}
2324
2325QMargins QCocoaWindow::frameMargins() const
2326{
2327 QMacAutoReleasePool pool;
2328
2329 // Child windows don't have frame margins. We explicitly don't check
2330 // isContentView() here, as we want to know the frame argins also for
2331 // windows that haven't gotten their NSWindow yet.
2332 if (QPlatformWindow::parent())
2333 return QMargins();
2334
2335 NSRect frameRect;
2336 NSRect contentRect;
2337
2338 if (m_view.window) {
2339 frameRect = m_view.window.frame;
2340 contentRect = [m_view.window contentRectForFrameRect:frameRect];
2341 } else {
2342 contentRect = m_view.frame;
2343 frameRect = [NSWindow frameRectForContentRect:contentRect styleMask:windowStyleMask(window()->flags())];
2344 }
2345
2346 return QMargins(frameRect.origin.x - contentRect.origin.x,
2347 (frameRect.origin.y + frameRect.size.height) - (contentRect.origin.y + contentRect.size.height),
2348 (frameRect.origin.x + frameRect.size.width) - (contentRect.origin.x + contentRect.size.width),
2349 contentRect.origin.y - contentRect.origin.y);
2350}
2351
2352void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled)
2353{
2354 m_frameStrutEventsEnabled = enabled;
2355}
2356
2357QPoint QCocoaWindow::mapToGlobal(const QPoint &point) const
2358{
2359 NSPoint windowPoint = [m_view convertPoint:point.toCGPoint() toView:nil];
2360 NSPoint screenPoint = [m_view.window convertPointToScreen:windowPoint];
2361 return QCocoaScreen::mapFromNative(screenPoint).toPoint();
2362}
2363
2364QPoint QCocoaWindow::mapFromGlobal(const QPoint &point) const
2365{
2366 NSPoint screenPoint = QCocoaScreen::mapToNative(point);
2367 NSPoint windowPoint = [m_view.window convertPointFromScreen:screenPoint];
2368 return QPointF::fromCGPoint([m_view convertPoint:windowPoint fromView:nil]).toPoint();
2369}
2370
2371CGPoint QCocoaWindow::mapToNative(const QPointF &point, NSView *referenceView)
2372{
2373 if (!referenceView || referenceView.flipped)
2374 return point.toCGPoint();
2375 else
2376 return qt_mac_flip(point, QRectF::fromCGRect(referenceView.bounds)).toCGPoint();
2377}
2378
2379CGRect QCocoaWindow::mapToNative(const QRectF &rect, NSView *referenceView)
2380{
2381 if (!referenceView || referenceView.flipped)
2382 return rect.toCGRect();
2383 else
2384 return qt_mac_flip(rect, QRectF::fromCGRect(referenceView.bounds)).toCGRect();
2385}
2386
2387QPointF QCocoaWindow::mapFromNative(CGPoint point, NSView *referenceView)
2388{
2389 if (!referenceView || referenceView.flipped)
2390 return QPointF::fromCGPoint(point);
2391 else
2392 return qt_mac_flip(QPointF::fromCGPoint(point), QRectF::fromCGRect(referenceView.bounds));
2393}
2394
2395QRectF QCocoaWindow::mapFromNative(CGRect rect, NSView *referenceView)
2396{
2397 if (!referenceView || referenceView.flipped)
2398 return QRectF::fromCGRect(rect);
2399 else
2400 return qt_mac_flip(QRectF::fromCGRect(rect), QRectF::fromCGRect(referenceView.bounds));
2401}
2402
2403CALayer *QCocoaWindow::contentLayer() const
2404{
2405 auto *layer = m_view.layer;
2406 if (auto *containerLayer = qt_objc_cast<QContainerLayer*>(layer))
2407 layer = containerLayer.contentLayer;
2408 return layer;
2409}
2410
2411void QCocoaWindow::manageVisualEffectArea(quintptr identifier, const QRect &rect,
2412 NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode,
2413 NSVisualEffectState activationState)
2414{
2415 if (!qt_objc_cast<QContainerLayer*>(m_view.layer)) {
2416 qCWarning(lcQpaWindow) << "Can not manage visual effect areas"
2417 << "in views without a container layer";
2418 return;
2419 }
2420
2421 qCDebug(lcQpaWindow) << "Updating visual effect area" << identifier
2422 << "to" << rect << "with material" << material << "blend mode"
2423 << blendMode << "and activation state" << activationState;
2424
2425 NSVisualEffectView *effectView = nullptr;
2426 if (m_effectViews.contains(identifier)) {
2427 effectView = m_effectViews.value(identifier);
2428 if (rect.isEmpty()) {
2429 [effectView removeFromSuperview];
2430 m_effectViews.remove(identifier);
2431 return;
2432 }
2433 } else if (!rect.isEmpty()) {
2434 effectView = [NSVisualEffectView new];
2435 // Ensure that the visual effect layer is stacked well
2436 // below our content layer (which defaults to a z of 0).
2437 effectView.wantsLayer = YES;
2438 effectView.layer.zPosition = -FLT_MAX;
2439 [m_view addSubview:effectView];
2440 m_effectViews.insert(identifier, effectView);
2441 }
2442
2443 if (!effectView)
2444 return;
2445
2446 effectView.frame = rect.toCGRect();
2447 effectView.material = material;
2448 effectView.blendingMode = blendMode;
2449 effectView.state = activationState;
2450}
2451
2452#ifndef QT_NO_DEBUG_STREAM
2453QDebug operator<<(QDebug debug, const QCocoaWindow *window)
2454{
2455 QDebugStateSaver saver(debug);
2456 debug.nospace();
2457 debug << "QCocoaWindow(" << (const void *)window;
2458 if (window)
2459 debug << ", window=" << window->window();
2460 debug << ')';
2461 return debug;
2462}
2463#endif // !QT_NO_DEBUG_STREAM
2464
2465// --------------------------------------------------------------------------
2466
2467#if QT_CONFIG(accessibility)
2468
2469static void setAccessibilityParent(NSView *view, id accessibilityParent)
2470{
2471 // The elements that show up in the parent's child list are the view itself
2472 // if the view is unignored, and the view's own accessible children if it is
2473 // not, so we set the parent on that same set, for the child and parent
2474 // relations to match.
2475 for (id<NSAccessibility> element in NSAccessibilityUnignoredChildrenForOnlyChild(view)) {
2476 // We can't assume all elements implement this part of the NSAccessibility protocol
2477 if (![element respondsToSelector:@selector(setAccessibilityParent:)])
2478 continue;
2479
2480 if (element.accessibilityParent == accessibilityParent)
2481 continue;
2482
2483 qCDebug(lcQpaAccessibility) << "Setting a11y parent of" << element
2484 << "for" << view << "to" << accessibilityParent;
2485 element.accessibilityParent = accessibilityParent;
2486 }
2487}
2488
2489/*
2490 Reflects the window's accessibility parent onto a foreign view.
2491
2492 Our own views resolve the parent on demand via [QNSView accessibilityParent],
2493 but a foreign view has no getter of ours to run, so the parent has to be
2494 pushed onto it. The parent we push is the unignored ancestor of the window
2495 container hosting the window, and it changes whenever anything above the
2496 container moves in the hierarchy, or stops being ignored.
2497
2498 Rather than tracking those changes we resolve the parent here, called from
2499 QCocoaAccessible::viewFor() on each of the paths that hand a foreign view to
2500 an accessibility client, so that the pushed parent is resolved as late as
2501 possible.
2502*/
2503void QCocoaWindow::updateAccessibleParent()
2504{
2505 if (!isForeignWindow())
2506 return;
2507
2508 QObject *accessibleParent = QWindowPrivate::get(window())->accessibleParent;
2509 if (QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(accessibleParent)) {
2510 setAccessibilityParent(m_view, NSAccessibilityUnignoredAncestor(
2511 [QMacAccessibilityElement elementWithInterface:iface]));
2512 m_didOverrideAccessibleParent = true;
2513 } else {
2514 clearAccessibleParent();
2515 }
2516}
2517
2518void QCocoaWindow::clearAccessibleParent()
2519{
2520 if (!isForeignWindow() || !m_didOverrideAccessibleParent)
2521 return;
2522
2523 // AppKit has no way to remove an accessibility attribute override, and
2524 // stores nil as an explicit "no parent", so the view can not be handed back
2525 // the parent it resolves by default. By the time we get here the view has
2526 // normally left the view hierarchy of its former accessible parent, so our
2527 // best option is to reflect an unknown accessible parent and let the new
2528 // parent update it if needed.
2529 setAccessibilityParent(m_view, nil);
2530
2531 m_didOverrideAccessibleParent = false;
2532}
2533
2534#endif // QT_CONFIG(accessibility)
2535
2536// --------------------------------------------------------------------------
2537
2538QT_END_NAMESPACE
2539
2540#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")