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