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