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()) {
166 // If we have a parent window 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 && !((window()->type() & Qt::SubWindow) == Qt::SubWindow)
1813 && !isForeignWindow();
1814
1815 if (shouldManageTopLevelWindow) {
1816 auto *windowController = qt_objc_cast<QNSWindowController*>(m_view.window.windowController);
1817 const bool isControllerContentView = m_view == windowController.window.contentView;
1818 const bool windowClassIsCorrect = [m_view.window isKindOfClass:windowClass()];
1819
1820 if (!isControllerContentView || !windowClassIsCorrect) {
1821 qCDebug(lcQpaWindow) << "Creating new window controller for" << m_view;
1822 windowController = [[QNSWindowController alloc] initWithCocoaWindow:this];
1823
1824 m_view.postsFrameChangedNotifications = NO;
1825 windowController.window.contentView = m_view;
1826 m_view.postsFrameChangedNotifications = YES;
1827
1828 // The view now owns the window controller
1829 [windowController release];
1830 } else {
1831 qCDebug(lcQpaWindow) << "Re-using existing window controller" << windowController;
1832 }
1833 } else if (parentWindow) {
1834 if (m_view.superview != parentWindow->view()) {
1835 qCDebug(lcQpaWindow) << "Reparenting view to new parent" << parentWindow;
1836 [parentWindow->view() addSubview:m_view];
1837 }
1838 } else if (qnsview_cast(m_view.superview) && !isEmbedded()) {
1839 qCDebug(lcQpaWindow) << "Removing view from superview" << m_view.superview;
1840 [m_view removeFromSuperview];
1841 }
1842}
1843
1844Class QCocoaWindow::windowClass() const
1845{
1846 const auto type = window()->type();
1847 return ((type & Qt::Popup) == Qt::Popup
1848 || (type & Qt::Dialog) == Qt::Dialog) ?
1849 QNSPanel.class : QNSWindow.class;
1850}
1851
1852void QCocoaWindow::requestUpdate()
1853{
1854 qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window()
1855 << "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
1856
1857 if (updatesWithDisplayLink()) {
1858 if (!static_cast<QCocoaScreen *>(screen())->requestUpdate()) {
1859 qCDebug(lcQpaDrawing) << "Falling back to timer-based update request";
1860 QPlatformWindow::requestUpdate();
1861 }
1862 } else {
1863 // Fall back to the un-throttled timer-based callback
1864 QPlatformWindow::requestUpdate();
1865 }
1866}
1867
1868bool QCocoaWindow::updatesWithDisplayLink() const
1869{
1870 // Update via CVDisplayLink if Vsync is enabled
1871 return format().swapInterval() != 0;
1872}
1873
1874void QCocoaWindow::deliverUpdateRequest()
1875{
1876 qCDebug(lcQpaDrawing) << "Delivering update request to" << window();
1877 QScopedValueRollback<bool> blocker(m_deliveringUpdateRequest, true);
1878
1879 if (auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(contentLayer())) {
1880 // We attempt a read lock here, so that the animation/render thread is
1881 // prioritized lower than the main thread's displayLayer processing.
1882 // Without this the two threads might fight over the next drawable,
1883 // starving the main thread's presentation of the resized layer.
1884 if (!qtMetalLayer.displayLock.tryLockForRead()) {
1885 qCDebug(lcQpaDrawing) << "Deferring update request"
1886 << "due to" << qtMetalLayer << "needing display";
1887 return;
1888 }
1889
1890 // But we don't hold the lock, as the update request can recurse
1891 // back into setNeedsDisplay, which would deadlock.
1892 qtMetalLayer.displayLock.unlock();
1893 }
1894
1895 QPlatformWindow::deliverUpdateRequest();
1896}
1897
1898void QCocoaWindow::requestActivateWindow()
1899{
1900 QMacAutoReleasePool pool;
1901 [m_view.window makeFirstResponder:m_view];
1902 [m_view.window makeKeyWindow];
1903}
1904
1905/*
1906 Closes all popups, and removes observers and monitors.
1907*/
1908void QCocoaWindow::closeAllPopups()
1909{
1910 QGuiApplicationPrivate::instance()->closeAllPopups();
1911
1912 removePopupMonitor();
1913}
1914
1915void QCocoaWindow::removePopupMonitor()
1916{
1917 if (s_globalMouseMonitor) {
1918 [NSEvent removeMonitor:s_globalMouseMonitor];
1919 s_globalMouseMonitor = nil;
1920 }
1921 if (s_applicationActivationObserver) {
1922 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver];
1923 s_applicationActivationObserver = nil;
1924 }
1925}
1926
1927void QCocoaWindow::setupPopupMonitor()
1928{
1929 // we open a popup window while we are not active. None of our existing event
1930 // handlers will get called if the user now clicks anywhere outside the application
1931 // or activates another window. Use a global event monitor to watch for mouse
1932 // presses, and close popups. We also want mouse tracking in the popup to work, so
1933 // also watch for MouseMoved.
1934 if (!s_globalMouseMonitor) {
1935 // we only get LeftMouseDown events when we also set LeftMouseUp.
1936 constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp
1937 | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown
1938 | NSEventMaskMouseMoved;
1939 s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask
1940 handler:^(NSEvent *e){
1941 if (!QGuiApplicationPrivate::instance()->activePopupWindow()) {
1942 removePopupMonitor();
1943 return;
1944 }
1945 const auto eventType = cocoaEvent2QtMouseEvent(e);
1946 if (eventType == QEvent::MouseMove) {
1947 if (s_windowUnderMouse) {
1948 QWindow *window = s_windowUnderMouse->window();
1949 const auto button = cocoaButton2QtButton(e);
1950 const auto buttons = currentlyPressedMouseButtons();
1951 const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
1952 const auto localPoint = window->mapFromGlobal(globalPoint.toPoint());
1953 QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint,
1954 buttons, button, eventType);
1955 }
1956 } else {
1957 closeAllPopups();
1958 }
1959 }];
1960 }
1961 // The activation observer also gets called when we become active because the user clicks
1962 // into the popup. This should not close the popup, so QCocoaApplicationDelegate's
1963 // applicationDidBecomeActive implementation removes this observer.
1964 if (!s_applicationActivationObserver) {
1965 s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
1966 addObserverForName:NSWorkspaceDidActivateApplicationNotification
1967 object:nil queue:nil
1968 usingBlock:^(NSNotification *){
1969 closeAllPopups();
1970 }];
1971 }
1972}
1973
1974QCocoaNSWindow *QCocoaWindow::createNSWindow()
1975{
1976 QMacAutoReleasePool pool;
1977
1978 Qt::WindowType type = window()->type();
1979 Qt::WindowFlags flags = window()->flags();
1980
1981 QRect rect = geometry();
1982
1983 QScreen *targetScreen = nullptr;
1984 for (QScreen *screen : QGuiApplication::screens()) {
1985 if (screen->geometry().contains(rect.topLeft())) {
1986 targetScreen = screen;
1987 break;
1988 }
1989 }
1990
1991 NSWindowStyleMask styleMask = windowStyleMask(flags);
1992
1993 if (!targetScreen) {
1994 qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen";
1995 targetScreen = QGuiApplication::primaryScreen();
1996 // Unless the window is created as borderless AppKit won't find a position and
1997 // screen that's close to the requested invalid position, and will always place
1998 // the window on the primary screen.
1999 styleMask = NSWindowStyleMaskBorderless;
2000 }
2001
2002 rect.translate(-targetScreen->geometry().topLeft());
2003 auto *targetCocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle());
2004 NSRect contentRect = QCocoaScreen::mapToNative(rect, targetCocoaScreen);
2005
2006 if (targetScreen->primaryOrientation() == Qt::PortraitOrientation) {
2007 // The macOS window manager has a bug, where if a screen is rotated, it will not allow
2008 // a window to be created within the area of the screen that has a Y coordinate (I quadrant)
2009 // higher than the height of the screen in its non-rotated state (including a magic padding
2010 // of 24 points), unless the window is created with the NSWindowStyleMaskBorderless style mask.
2011 if (styleMask && (contentRect.origin.y + 24 > targetScreen->geometry().width())) {
2012 qCDebug(lcQpaWindow) << "Window positioned on portrait screen."
2013 << "Adjusting style mask during creation";
2014 styleMask = NSWindowStyleMaskBorderless;
2015 }
2016 }
2017
2018 // Create NSWindow
2019 QCocoaNSWindow *nsWindow = [[windowClass() alloc] initWithContentRect:contentRect
2020 // Mask will be updated in setWindowFlags if not the final mask
2021 styleMask:styleMask
2022 // Deferring window creation breaks OpenGL (the GL context is
2023 // set up before the window is shown and needs a proper window)
2024 backing:NSBackingStoreBuffered defer:NO
2025 screen:targetCocoaScreen->nativeScreen()
2026 platformWindow:this];
2027
2028 // The resulting screen can be different from the screen requested if
2029 // for example the application has been assigned to a specific display.
2030 auto resultingScreen = QCocoaScreen::get(nsWindow.screen);
2031
2032 // But may not always be resolved at this point, in which case we fall back
2033 // to the target screen. The real screen will be delivered as a screen change
2034 // when resolved as part of ordering the window on screen.
2035 if (!resultingScreen)
2036 resultingScreen = targetCocoaScreen;
2037
2038 if (resultingScreen->screen() != window()->screen()) {
2039 QWindowSystemInterface::handleWindowScreenChanged<
2040 QWindowSystemInterface::SynchronousDelivery>(window(), resultingScreen->screen());
2041 }
2042
2043 static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init],
2044 [](QNSWindowDelegate *delegate) { [delegate release]; });
2045 nsWindow.delegate = sharedDelegate.get();
2046
2047 // Prevent Cocoa from releasing the window on close. Qt
2048 // handles the close event asynchronously and we want to
2049 // make sure that NSWindow stays valid until the
2050 // QCocoaWindow is deleted by Qt.
2051 [nsWindow setReleasedWhenClosed:NO];
2052
2053 if (alwaysShowToolWindow()) {
2054 static dispatch_once_t onceToken;
2055 dispatch_once(&onceToken, ^{
2056 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
2057 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
2058 name:NSApplicationWillResignActiveNotification object:nil];
2059 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
2060 name:NSApplicationWillBecomeActiveNotification object:nil];
2061 });
2062 }
2063
2064 nsWindow.restorable = NO;
2065 nsWindow.level = windowLevel(flags);
2066 nsWindow.tabbingMode = NSWindowTabbingModeDisallowed;
2067
2068 if ([nsWindow isKindOfClass:NSPanel.class]) {
2069 // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
2070 nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
2071
2072 // Make popup windows show on the same desktop as the parent window
2073 nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
2074 | NSWindowCollectionBehaviorMoveToActiveSpace;
2075
2076 if ((type & Qt::Popup) == Qt::Popup) {
2077 nsWindow.hasShadow = YES;
2078 nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
2079 if (QGuiApplication::applicationState() != Qt::ApplicationActive)
2080 setupPopupMonitor();
2081 }
2082 }
2083
2084 // We propagate the view's color space granulary to both the IOSurfaces
2085 // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
2086 // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
2087 // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
2088 // workaround we set the NSWindow's color space, which affects GL drawing
2089 // with NSOpenGLContext as well. This does not conflict with the granular
2090 // modifications we do to each surface for raster or Metal.
2091 if (auto *qtView = qnsview_cast(m_view))
2092 nsWindow.colorSpace = qtView.colorSpace;
2093
2094 return nsWindow;
2095}
2096
2097bool QCocoaWindow::alwaysShowToolWindow() const
2098{
2099 return qt_mac_resolveOption(false, window(), "_q_macAlwaysShowToolWindow", "");
2100}
2101
2102bool QCocoaWindow::setWindowModified(bool modified)
2103{
2104 QMacAutoReleasePool pool;
2105
2106 if (!isContentView())
2107 return false;
2108
2109 m_view.window.documentEdited = modified;
2110 return true;
2111}
2112
2113void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
2114{
2115 m_menubar = mb;
2116}
2117
2118QCocoaMenuBar *QCocoaWindow::menubar() const
2119{
2120 return m_menubar;
2121}
2122
2123void QCocoaWindow::setWindowCursor(NSCursor *cursor)
2124{
2125 QMacAutoReleasePool pool;
2126
2127 // Setting a cursor in a foreign view is not supported
2128 if (isForeignWindow())
2129 return;
2130
2131 qCInfo(lcQpaMouse) << "Setting" << this << "cursor to" << cursor;
2132
2133 QNSView *view = qnsview_cast(m_view);
2134 if (cursor == view.cursor)
2135 return;
2136
2137 view.cursor = cursor;
2138
2139 // We're not using the the legacy cursor rects API to manage our
2140 // cursor, but calling this function also invalidates AppKit's
2141 // view of whether or not we need a cursorUpdate callback for
2142 // our tracking area.
2143 [m_view.window invalidateCursorRectsForView:m_view];
2144
2145 // We've informed AppKit that we need a cursorUpdate, but cursor
2146 // updates for tracking areas are deferred in some cases, such as
2147 // when the mouse is down, whereas we want a synchronous update.
2148 // To ensure an updated cursor we synthesize a cursor update event
2149 // now if the window is otherwise allowed to change the cursor.
2150 auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
2151 auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
2152 bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
2153 auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
2154 bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
2155 if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
2156 qCDebug(lcQpaMouse) << "Synthesizing cursor update";
2157 [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
2158 location:locationInWindow modifierFlags:0 timestamp:0
2159 windowNumber:m_view.window.windowNumber context:nil
2160 eventNumber:0 trackingNumber:0 userData:0]];
2161 }
2162}
2163
2164void QCocoaWindow::registerTouch(bool enable)
2165{
2166 m_registerTouchCount += enable ? 1 : -1;
2167 if (enable && m_registerTouchCount == 1)
2168 m_view.allowedTouchTypes |= NSTouchTypeMaskIndirect;
2169 else if (m_registerTouchCount == 0)
2170 m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
2171}
2172
2173qreal QCocoaWindow::devicePixelRatio() const
2174{
2175 // The documented way to observe the relationship between device-independent
2176 // and device pixels is to use one for the convertToBacking functions. Other
2177 // methods such as [NSWindow backingScaleFactor] might not give the correct
2178 // result, for example if setWantsBestResolutionOpenGLSurface is not set or
2179 // or ignored by the OpenGL driver.
2180 NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
2181 return backingSize.height;
2182}
2183
2184QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint)
2185{
2186 QWindow *targetWindow = window();
2187 for (QObject *child : targetWindow->children())
2188 if (QWindow *childWindow = qobject_cast<QWindow *>(child))
2189 if (QPlatformWindow *handle = childWindow->handle())
2190 if (handle->isExposed() && childWindow->geometry().contains(windowPoint))
2191 targetWindow = static_cast<QCocoaWindow*>(handle)->childWindowAt(windowPoint - childWindow->position());
2192
2193 return targetWindow;
2194}
2195
2196bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
2197{
2198 // This function speaks up if there's any reason
2199 // to refuse key window or first responder state.
2200
2201 if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
2202 return true;
2203
2204 // For application modal windows, as well as direct parent windows
2205 // of window modal windows, AppKit takes care of blocking interaction.
2206 // The Qt expectation however, is that all transient parents of a
2207 // window modal window is blocked, as reflected by QGuiApplication.
2208 // We reflect this by returning false from this function for transient
2209 // parents blocked by a modal window, but limit it to the cases not
2210 // covered by AppKit to avoid potential unwanted side effects.
2211 QWindow *modalWindow = nullptr;
2212 if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
2213 if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
2214 qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
2215 << "blocked by" << modalWindow;
2216 return true;
2217 }
2218 }
2219
2220 if (m_inSetVisible) {
2221 QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
2222 if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
2223 return true;
2224 }
2225
2226 return false;
2227}
2228
2229bool QCocoaWindow::windowEvent(QEvent *event)
2230{
2231 switch (event->type()) {
2232 case QEvent::WindowBlocked:
2233 case QEvent::WindowUnblocked:
2234 updateTitleBarButtons(window()->flags());
2235 break;
2236 default:
2237 break;
2238 }
2239
2240 return QPlatformWindow::windowEvent(event);
2241}
2242
2243QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
2244{
2245 if (!m_view)
2246 return QPoint();
2247 const NSPoint origin = [m_view isFlipped] ? NSMakePoint(0, [m_view frame].size.height)
2248 : NSMakePoint(0, 0);
2249 const NSRect visibleRect = [m_view visibleRect];
2250
2251 return QPoint(visibleRect.origin.x, -visibleRect.origin.y + (origin.y - visibleRect.size.height));
2252}
2253
2254QMargins QCocoaWindow::frameMargins() const
2255{
2256 QMacAutoReleasePool pool;
2257
2258 // Child windows don't have frame margins. We explicitly don't check
2259 // isContentView() here, as we want to know the frame argins also for
2260 // windows that haven't gotten their NSWindow yet.
2261 if (QPlatformWindow::parent())
2262 return QMargins();
2263
2264 NSRect frameRect;
2265 NSRect contentRect;
2266
2267 if (m_view.window) {
2268 frameRect = m_view.window.frame;
2269 contentRect = [m_view.window contentRectForFrameRect:frameRect];
2270 } else {
2271 contentRect = m_view.frame;
2272 frameRect = [NSWindow frameRectForContentRect:contentRect styleMask:windowStyleMask(window()->flags())];
2273 }
2274
2275 return QMargins(frameRect.origin.x - contentRect.origin.x,
2276 (frameRect.origin.y + frameRect.size.height) - (contentRect.origin.y + contentRect.size.height),
2277 (frameRect.origin.x + frameRect.size.width) - (contentRect.origin.x + contentRect.size.width),
2278 contentRect.origin.y - contentRect.origin.y);
2279}
2280
2281void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled)
2282{
2283 m_frameStrutEventsEnabled = enabled;
2284}
2285
2286QPoint QCocoaWindow::mapToGlobal(const QPoint &point) const
2287{
2288 NSPoint windowPoint = [m_view convertPoint:point.toCGPoint() toView:nil];
2289 NSPoint screenPoint = [m_view.window convertPointToScreen:windowPoint];
2290 return QCocoaScreen::mapFromNative(screenPoint).toPoint();
2291}
2292
2293QPoint QCocoaWindow::mapFromGlobal(const QPoint &point) const
2294{
2295 NSPoint screenPoint = QCocoaScreen::mapToNative(point);
2296 NSPoint windowPoint = [m_view.window convertPointFromScreen:screenPoint];
2297 return QPointF::fromCGPoint([m_view convertPoint:windowPoint fromView:nil]).toPoint();
2298}
2299
2300CGPoint QCocoaWindow::mapToNative(const QPointF &point, NSView *referenceView)
2301{
2302 if (!referenceView || referenceView.flipped)
2303 return point.toCGPoint();
2304 else
2305 return qt_mac_flip(point, QRectF::fromCGRect(referenceView.bounds)).toCGPoint();
2306}
2307
2308CGRect QCocoaWindow::mapToNative(const QRectF &rect, NSView *referenceView)
2309{
2310 if (!referenceView || referenceView.flipped)
2311 return rect.toCGRect();
2312 else
2313 return qt_mac_flip(rect, QRectF::fromCGRect(referenceView.bounds)).toCGRect();
2314}
2315
2316QPointF QCocoaWindow::mapFromNative(CGPoint point, NSView *referenceView)
2317{
2318 if (!referenceView || referenceView.flipped)
2319 return QPointF::fromCGPoint(point);
2320 else
2321 return qt_mac_flip(QPointF::fromCGPoint(point), QRectF::fromCGRect(referenceView.bounds));
2322}
2323
2324QRectF QCocoaWindow::mapFromNative(CGRect rect, NSView *referenceView)
2325{
2326 if (!referenceView || referenceView.flipped)
2327 return QRectF::fromCGRect(rect);
2328 else
2329 return qt_mac_flip(QRectF::fromCGRect(rect), QRectF::fromCGRect(referenceView.bounds));
2330}
2331
2332CALayer *QCocoaWindow::contentLayer() const
2333{
2334 auto *layer = m_view.layer;
2335 if (auto *containerLayer = qt_objc_cast<QContainerLayer*>(layer))
2336 layer = containerLayer.contentLayer;
2337 return layer;
2338}
2339
2340void QCocoaWindow::manageVisualEffectArea(quintptr identifier, const QRect &rect,
2341 NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode,
2342 NSVisualEffectState activationState)
2343{
2344 if (!qt_objc_cast<QContainerLayer*>(m_view.layer)) {
2345 qCWarning(lcQpaWindow) << "Can not manage visual effect areas"
2346 << "in views without a container layer";
2347 return;
2348 }
2349
2350 qCDebug(lcQpaWindow) << "Updating visual effect area" << identifier
2351 << "to" << rect << "with material" << material << "blend mode"
2352 << blendMode << "and activation state" << activationState;
2353
2354 NSVisualEffectView *effectView = nullptr;
2355 if (m_effectViews.contains(identifier)) {
2356 effectView = m_effectViews.value(identifier);
2357 if (rect.isEmpty()) {
2358 [effectView removeFromSuperview];
2359 m_effectViews.remove(identifier);
2360 return;
2361 }
2362 } else if (!rect.isEmpty()) {
2363 effectView = [NSVisualEffectView new];
2364 // Ensure that the visual effect layer is stacked well
2365 // below our content layer (which defaults to a z of 0).
2366 effectView.wantsLayer = YES;
2367 effectView.layer.zPosition = -FLT_MAX;
2368 [m_view addSubview:effectView];
2369 m_effectViews.insert(identifier, effectView);
2370 }
2371
2372 if (!effectView)
2373 return;
2374
2375 effectView.frame = rect.toCGRect();
2376 effectView.material = material;
2377 effectView.blendingMode = blendMode;
2378 effectView.state = activationState;
2379}
2380
2381#ifndef QT_NO_DEBUG_STREAM
2382QDebug operator<<(QDebug debug, const QCocoaWindow *window)
2383{
2384 QDebugStateSaver saver(debug);
2385 debug.nospace();
2386 debug << "QCocoaWindow(" << (const void *)window;
2387 if (window)
2388 debug << ", window=" << window->window();
2389 debug << ')';
2390 return debug;
2391}
2392#endif // !QT_NO_DEBUG_STREAM
2393
2394QT_END_NAMESPACE
2395
2396#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")