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