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 // Moving to a new window might result in a new screen. This is normally
1354 // handled for top level windows via windowDidChangeScreen, but for child
1355 // windows we need to handle it manually.
1356 auto *previousScreen = previousWindow ? QCocoaScreen::get(previousWindow.screen) : nullptr;
1357 auto *currentScreen = m_view.window ? QCocoaScreen::get(m_view.window.screen) : nullptr;
1358 if (currentScreen && currentScreen != previousScreen)
1359 windowDidChangeScreen();
1360}
1361
1362// ----------------------- NSWindow notifications -----------------------
1363
1364// Note: The following notifications are delivered to every QCocoaWindow
1365// that is a child of the NSWindow that triggered the notification. Each
1366// callback should make sure to filter out notifications if they do not
1367// apply to that QCocoaWindow, e.g. if the window is not a content view.
1368
1369void QCocoaWindow::windowDidMove()
1370{
1371 if (!isContentView())
1372 return;
1373
1374 handleGeometryChange();
1375
1376 // Moving a window might bring it out of maximized state
1377 handleWindowStateChanged();
1378}
1379
1380void QCocoaWindow::windowDidResize()
1381{
1382 if (!isContentView())
1383 return;
1384
1385 handleGeometryChange();
1386
1387 if (!m_view.inLiveResize)
1388 handleWindowStateChanged();
1389}
1390
1391void QCocoaWindow::windowWillStartLiveResize()
1392{
1393 // Track live resizing for all windows, including
1394 // child windows, so we know if it's safe to update
1395 // the window unthrottled outside of the main thread.
1396 m_inLiveResize = true;
1397}
1398
1399bool QCocoaWindow::allowsIndependentThreadedRendering() const
1400{
1401 // Use member variable to track this instead of reflecting
1402 // NSView.inLiveResize directly, so it can be called from
1403 // non-main threads.
1404 return !m_inLiveResize;
1405}
1406
1407void QCocoaWindow::windowDidEndLiveResize()
1408{
1409 m_inLiveResize = false;
1410
1411 if (!isContentView())
1412 return;
1413
1414 handleWindowStateChanged();
1415}
1416
1417void QCocoaWindow::windowDidBecomeKey()
1418{
1419 // The NSWindow we're part of become key. Check if we're the first
1420 // responder, and if so, deliver focus window change to our window.
1421 if (m_view.window.firstResponder != m_view)
1422 return;
1423
1424 qCDebug(lcQpaWindow) << m_view.window << "became key window."
1425 << "Updating focus window to" << this << "with view" << m_view;
1426
1427 if (windowIsPopupType()) {
1428 qCDebug(lcQpaWindow) << "Window is popup. Skipping focus window change.";
1429 return;
1430 }
1431
1432 // See also [QNSView becomeFirstResponder]
1433 QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
1434 window(), Qt::ActiveWindowFocusReason);
1435}
1436
1437void QCocoaWindow::windowDidResignKey()
1438{
1439 // The NSWindow we're part of lost key. Check if we're the first
1440 // responder, and if so, deliver window deactivation to our window.
1441 if (m_view.window.firstResponder != m_view)
1442 return;
1443
1444 qCDebug(lcQpaWindow) << m_view.window << "resigned key window."
1445 << "Clearing focus window" << this << "with view" << m_view;
1446
1447 // Make sure popups are closed before we deliver activation changes, which are
1448 // otherwise ignored by QApplication.
1449 closeAllPopups();
1450
1451 // The current key window will be non-nil if another window became key. If that
1452 // window is a Qt window, we delay the window activation event until the didBecomeKey
1453 // notification is delivered to the active window, to ensure an atomic update.
1454 NSWindow *newKeyWindow = [NSApp keyWindow];
1455 if (newKeyWindow && newKeyWindow != m_view.window
1456 && [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) {
1457 qCDebug(lcQpaWindow) << "New key window" << newKeyWindow
1458 << "is Qt window. Deferring focus window change.";
1459 return;
1460 }
1461
1462 // Lost key window, go ahead and set the active window to zero
1463 if (!windowIsPopupType()) {
1464 QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
1465 nullptr, Qt::ActiveWindowFocusReason);
1466 }
1467}
1468
1469void QCocoaWindow::windowDidOrderOnScreen()
1470{
1471 // The current mouse window needs to get a leave event when a popup window opens.
1472 // For modal dialogs, QGuiApplicationPrivate::showModalWindow takes care of this.
1473 if (QWindowPrivate::get(window())->isPopup()) {
1474 QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>
1475 (QGuiApplicationPrivate::currentMouseWindow);
1476 }
1477
1478 [m_view setNeedsDisplay:YES];
1479}
1480
1481void QCocoaWindow::windowDidOrderOffScreen()
1482{
1483 handleExposeEvent(QRegion());
1484 // We are closing a window, so the window that is now under the mouse
1485 // might need to get an Enter event if it isn't already the mouse window.
1486 if (window()->type() & Qt::Window) {
1487 const QPointF screenPoint = QCocoaScreen::mapFromNative([NSEvent mouseLocation]);
1488 if (QWindow *windowUnderMouse = QGuiApplication::topLevelAt(screenPoint.toPoint())) {
1489 if (windowUnderMouse != QGuiApplicationPrivate::instance()->currentMouseWindow) {
1490 const auto windowPoint = windowUnderMouse->mapFromGlobal(screenPoint);
1491 // asynchronous delivery on purpose
1492 QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::AsynchronousDelivery>
1493 (windowUnderMouse, windowPoint, screenPoint);
1494 }
1495 }
1496 }
1497}
1498
1499void QCocoaWindow::windowDidChangeOcclusionState()
1500{
1501 // Note, we don't take the view's hiddenOrHasHiddenAncestor state into
1502 // account here, but instead leave that up to handleExposeEvent, just
1503 // like all the other signals that could potentially change the exposed
1504 // state of the window.
1505 bool visible = m_view.window.occlusionState & NSWindowOcclusionStateVisible;
1506 qCDebug(lcQpaWindow) << "Occlusion state of" << m_view.window << "for"
1507 << window() << "changed to" << (visible ? "visible" : "occluded");
1508
1509 if (visible)
1510 [m_view setNeedsDisplay:YES];
1511 else
1512 handleExposeEvent(QRegion());
1513}
1514
1515void QCocoaWindow::windowDidChangeScreen()
1516{
1517 if (!window())
1518 return;
1519
1520 // Note: When a window is resized to 0x0 Cocoa will report the window's screen as nil
1521 NSScreen *nsScreen = m_view.window.screen;
1522
1523 qCDebug(lcQpaWindow) << window() << "did change" << nsScreen;
1524 QCocoaScreen::updateScreens();
1525
1526 auto *previousScreen = static_cast<QCocoaScreen*>(screen());
1527 auto *currentScreen = QCocoaScreen::get(nsScreen);
1528
1529 qCDebug(lcQpaWindow) << "Screen changed for" << window() << "from" << previousScreen << "to" << currentScreen;
1530
1531 // Note: The previous screen may be the same as the current screen, either because
1532 // a) the screen was just reconfigured, which still results in AppKit sending an
1533 // NSWindowDidChangeScreenNotification, b) because the previous screen was removed,
1534 // and we ended up calling QWindow::setScreen to move the window, which doesn't
1535 // actually move the window to the new screen, or c) because we've delivered the
1536 // screen change to the top level window, which will make all the child windows
1537 // of that window report the new screen when requested via QWindow::screen().
1538 // We still need to deliver the screen change in all these cases, as the
1539 // device-pixel ratio may have changed, and needs to be delivered to all
1540 // windows, both top level and child windows.
1541
1542 QWindowSystemInterface::handleWindowScreenChanged<QWindowSystemInterface::SynchronousDelivery>(
1543 window(), currentScreen ? currentScreen->screen() : nullptr);
1544
1545 if (currentScreen && hasPendingUpdateRequest()) {
1546 // Restart display-link on new screen. We need to do this unconditionally,
1547 // since we can't rely on the previousScreen reflecting whether or not the
1548 // window actually moved from one screen to another, or just stayed on the
1549 // same screen.
1550 currentScreen->requestUpdate();
1551 }
1552 // If there are no exposed windows left on the previous screen
1553 // we can stop its display link if it was running.
1554 if (previousScreen)
1555 previousScreen->maybeStopDisplayLink();
1556}
1557
1558// ----------------------- NSWindowDelegate callbacks -----------------------
1559
1560bool QCocoaWindow::windowShouldClose()
1561{
1562 qCDebug(lcQpaWindow) << "QCocoaWindow::windowShouldClose" << window();
1563
1564 // This callback should technically only determine if the window
1565 // should (be allowed to) close, but since our QPA API to determine
1566 // that also involves actually closing the window we do both at the
1567 // same time, instead of doing the latter in windowWillClose.
1568
1569 // If the window is closed, we will release and deallocate the NSWindow.
1570 // But frames higher up in the stack might still expect the window to
1571 // be alive, since the windowShouldClose: callback is technically only
1572 // supposed to answer YES or NO. To ensure the window is still alive
1573 // we put an autorelease in the closest pool (typically the runloop).
1574 [[m_view.window retain] autorelease];
1575
1576 return QWindowSystemInterface::handleCloseEvent<QWindowSystemInterface::SynchronousDelivery>(window());
1577}
1578
1579// ----------------------------- QPA forwarding -----------------------------
1580
1581void QCocoaWindow::handleGeometryChange()
1582{
1583 QRect newGeometry;
1584 if (isContentView() && !isEmbedded()) {
1585 // Content views are positioned at (0, 0) in the window, so we resolve via the window
1586 CGRect contentRect = [m_view.window contentRectForFrameRect:m_view.window.frame];
1587
1588 // The result above is in native screen coordinates, so remap to the Qt coordinate system
1589 newGeometry = QCocoaScreen::mapFromNative(contentRect).toRect();
1590 } else {
1591 newGeometry = QCocoaWindow::mapFromNative(m_view.frame, m_view.superview).toRect();
1592 }
1593
1594 qCDebug(lcQpaWindow) << "QCocoaWindow::handleGeometryChange" << window()
1595 << "current" << geometry() << "new" << newGeometry;
1596
1597 // It can happen that the current NSWindow is nil (if we are changing styleMask
1598 // from/to borderless, and the content view is being re-parented), which results
1599 // in invalid coordinates.
1600 if (m_inSetStyleMask && !m_view.window) {
1601 qCDebug(lcQpaWindow) << "Lacking window during style mask update, ignoring geometry change";
1602 return;
1603 }
1604
1605 // Prevent geometry change during initialization, as that will result
1606 // in a resize event, and Qt expects those to come after the show event.
1607 // FIXME: Remove once we've clarified the Qt behavior for this.
1608 if (!m_initialized) {
1609 // But update the QPlatformWindow reality
1610 QPlatformWindow::setGeometry(newGeometry);
1611 qCDebug(lcQpaWindow) << "Window still initializing, skipping event";
1612 return;
1613 }
1614
1615 QWindowSystemInterface::handleGeometryChange(window(), newGeometry);
1616
1617 // Changing the window geometry may affect the safe area margins
1618 updateSafeAreaMarginsIfNeeded();
1619
1620 // Guard against processing window system events during QWindow::setGeometry
1621 // calls, which Qt and Qt applications do not expect.
1622 if (!m_inSetGeometry)
1623 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
1624}
1625
1626void QCocoaWindow::handleExposeEvent(const QRegion &region)
1627{
1628 // Ideally we'd implement isExposed() in terms of these properties,
1629 // plus the occlusionState of the NSWindow, and let the expose event
1630 // pull the exposed state out when needed. However, when the window
1631 // is first shown we receive a drawRect call where the occlusionState
1632 // of the window is still hidden, but we still want to prepare the
1633 // window for display by issuing an expose event to Qt. To work around
1634 // this we don't use the occlusionState directly, but instead base
1635 // the exposed state on the region we get in, which in the case of
1636 // a window being obscured is an empty region, and in the case of
1637 // a drawRect call is a non-null region, even if occlusionState
1638 // is still hidden. This ensures the window is prepared for display.
1639 if (m_view.window.visible && m_view.window.screen
1640 && !geometry().size().isEmpty() && !region.isEmpty()
1641 && !m_view.hiddenOrHasHiddenAncestor) {
1642 m_exposedRect = region.boundingRect();
1643 } else {
1644 m_exposedRect = QRect();
1645 }
1646
1647 qCDebug(lcQpaDrawing) << "QCocoaWindow::handleExposeEvent" << window() << region << "isExposed" << isExposed();
1648 QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(window(), region);
1649
1650 if (!isExposed())
1651 static_cast<QCocoaScreen *>(screen())->maybeStopDisplayLink();
1652}
1653
1654// --------------------------------------------------------------------------
1655
1656bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const
1657{
1658 if (type == Qt::Widget)
1659 type = window()->type();
1660 if (type == Qt::Tool)
1661 return false; // Qt::Tool has the Popup bit set but isn't, at least on Mac.
1662
1663 return ((type & Qt::Popup) == Qt::Popup);
1664}
1665
1666/*!
1667 Checks if the window is the content view of its immediate NSWindow.
1668
1669 Being the content view of a NSWindow means the QWindow is
1670 the highest accessible NSView object in the window's view
1671 hierarchy.
1672
1673 This is the case if the QWindow is a top level window.
1674*/
1675bool QCocoaWindow::isContentView() const
1676{
1677 return m_view.window.contentView == m_view;
1678}
1679
1680/*!
1681 Recreates (or removes) the NSWindow for this QWindow, if needed.
1682
1683 A QWindow may need a corresponding NSWindow/NSPanel, depending on
1684 whether or not it's a top level or not, window flags, etc.
1685*/
1686void QCocoaWindow::recreateWindowIfNeeded()
1687{
1688 QMacAutoReleasePool pool;
1689
1690 QPlatformWindow *parentWindow = QPlatformWindow::parent();
1691 auto *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
1692
1693 QCocoaWindow *oldParentCocoaWindow = nullptr;
1694 if (QNSView *qnsView = qnsview_cast(m_view.superview))
1695 oldParentCocoaWindow = qnsView.platformWindow;
1696
1697 if (isForeignWindow()) {
1698 // A foreign window is created as such, and can never move between being
1699 // foreign and not, so we don't need to get rid of any existing NSWindows,
1700 // nor create new ones, as a foreign window is a single simple NSView.
1701 qCDebug(lcQpaWindow) << "Skipping NSWindow management for foreign window" << this;
1702
1703 // We do however need to manage the parent relationship
1704 if (parentCocoaWindow)
1705 [parentCocoaWindow->m_view addSubview:m_view];
1706 else if (oldParentCocoaWindow)
1707 [m_view removeFromSuperview];
1708
1709 return;
1710 }
1711
1712 const bool isEmbeddedView = isEmbedded();
1713 RecreationReasons recreateReason = RecreationNotNeeded;
1714
1715 if (parentWindow != oldParentCocoaWindow)
1716 recreateReason |= ParentChanged;
1717
1718 if (isEmbeddedView && m_nsWindow)
1719 recreateReason |= EmbeddedChanged;
1720
1721 if (!m_view.window)
1722 recreateReason |= MissingWindow;
1723
1724 Qt::WindowType type = window()->type();
1725
1726 const bool shouldBeContentView = !parentWindow
1727 && !((type & Qt::SubWindow) == Qt::SubWindow)
1728 && !isEmbeddedView;
1729 if (isContentView() != shouldBeContentView)
1730 recreateReason |= ContentViewChanged;
1731
1732 const bool isPanel = isContentView() && [m_view.window isKindOfClass:[QNSPanel class]];
1733 const bool shouldBePanel = shouldBeContentView &&
1734 ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog);
1735
1736 if (isPanel != shouldBePanel)
1737 recreateReason |= PanelChanged;
1738
1739 qCDebug(lcQpaWindow) << "QCocoaWindow::recreateWindowIfNeeded" << window() << recreateReason;
1740
1741 if (recreateReason == RecreationNotNeeded)
1742 return;
1743
1744 // Remove current window (if any)
1745 if ((isContentView() && !shouldBeContentView) || (recreateReason & (PanelChanged | EmbeddedChanged))) {
1746 if (m_nsWindow) {
1747 qCDebug(lcQpaWindow) << "Getting rid of existing window" << m_nsWindow;
1748 [m_nsWindow closeAndRelease];
1749 if (isContentView() && !isEmbeddedView) {
1750 // We explicitly disassociate m_view from the window's contentView,
1751 // as AppKit does not automatically do this in response to removing
1752 // the view from the NSThemeFrame subview list, so we might end up
1753 // with a NSWindow contentView pointing to a deallocated NSView.
1754 m_view.window.contentView = nil;
1755 }
1756 m_nsWindow = nil;
1757 }
1758 }
1759
1760 if (shouldBeContentView && !m_nsWindow) {
1761 // Move view to new NSWindow if needed
1762 auto *newWindow = createNSWindow(shouldBePanel);
1763 qCDebug(lcQpaWindow) << "Ensuring that" << m_view << "is content view for" << newWindow;
1764 [m_view setPostsFrameChangedNotifications:NO];
1765 [newWindow setContentView:m_view];
1766 [m_view setPostsFrameChangedNotifications:YES];
1767
1768 m_nsWindow = newWindow;
1769 Q_ASSERT(m_view.window == m_nsWindow);
1770 }
1771
1772 if (parentCocoaWindow) {
1773 // Child windows have no NSWindow, re-parent to superview instead
1774 [parentCocoaWindow->m_view addSubview:m_view];
1775 }
1776}
1777
1778void QCocoaWindow::requestUpdate()
1779{
1780 qCDebug(lcQpaDrawing) << "QCocoaWindow::requestUpdate" << window()
1781 << "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
1782
1783 if (updatesWithDisplayLink()) {
1784 if (!static_cast<QCocoaScreen *>(screen())->requestUpdate()) {
1785 qCDebug(lcQpaDrawing) << "Falling back to timer-based update request";
1786 QPlatformWindow::requestUpdate();
1787 }
1788 } else {
1789 // Fall back to the un-throttled timer-based callback
1790 QPlatformWindow::requestUpdate();
1791 }
1792}
1793
1794bool QCocoaWindow::updatesWithDisplayLink() const
1795{
1796 // Update via CVDisplayLink if Vsync is enabled
1797 return format().swapInterval() != 0;
1798}
1799
1800void QCocoaWindow::deliverUpdateRequest()
1801{
1802 qCDebug(lcQpaDrawing) << "Delivering update request to" << window();
1803 QScopedValueRollback<bool> blocker(m_deliveringUpdateRequest, true);
1804
1805 if (auto *qtMetalLayer = qt_objc_cast<QMetalLayer*>(contentLayer())) {
1806 // We attempt a read lock here, so that the animation/render thread is
1807 // prioritized lower than the main thread's displayLayer processing.
1808 // Without this the two threads might fight over the next drawable,
1809 // starving the main thread's presentation of the resized layer.
1810 if (!qtMetalLayer.displayLock.tryLockForRead()) {
1811 qCDebug(lcQpaDrawing) << "Deferring update request"
1812 << "due to" << qtMetalLayer << "needing display";
1813 return;
1814 }
1815
1816 // But we don't hold the lock, as the update request can recurse
1817 // back into setNeedsDisplay, which would deadlock.
1818 qtMetalLayer.displayLock.unlock();
1819 }
1820
1821 QPlatformWindow::deliverUpdateRequest();
1822}
1823
1824void QCocoaWindow::requestActivateWindow()
1825{
1826 QMacAutoReleasePool pool;
1827 [m_view.window makeFirstResponder:m_view];
1828 [m_view.window makeKeyWindow];
1829}
1830
1831/*
1832 Closes all popups, and removes observers and monitors.
1833*/
1834void QCocoaWindow::closeAllPopups()
1835{
1836 QGuiApplicationPrivate::instance()->closeAllPopups();
1837
1838 removePopupMonitor();
1839}
1840
1841void QCocoaWindow::removePopupMonitor()
1842{
1843 if (s_globalMouseMonitor) {
1844 [NSEvent removeMonitor:s_globalMouseMonitor];
1845 s_globalMouseMonitor = nil;
1846 }
1847 if (s_applicationActivationObserver) {
1848 [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver];
1849 s_applicationActivationObserver = nil;
1850 }
1851}
1852
1853void QCocoaWindow::setupPopupMonitor()
1854{
1855 // we open a popup window while we are not active. None of our existing event
1856 // handlers will get called if the user now clicks anywhere outside the application
1857 // or activates another window. Use a global event monitor to watch for mouse
1858 // presses, and close popups. We also want mouse tracking in the popup to work, so
1859 // also watch for MouseMoved.
1860 if (!s_globalMouseMonitor) {
1861 // we only get LeftMouseDown events when we also set LeftMouseUp.
1862 constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp
1863 | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown
1864 | NSEventMaskMouseMoved;
1865 s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask
1866 handler:^(NSEvent *e){
1867 if (!QGuiApplicationPrivate::instance()->activePopupWindow()) {
1868 removePopupMonitor();
1869 return;
1870 }
1871 const auto eventType = cocoaEvent2QtMouseEvent(e);
1872 if (eventType == QEvent::MouseMove) {
1873 if (s_windowUnderMouse) {
1874 QWindow *window = s_windowUnderMouse->window();
1875 const auto button = cocoaButton2QtButton(e);
1876 const auto buttons = currentlyPressedMouseButtons();
1877 const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
1878 const auto localPoint = window->mapFromGlobal(globalPoint.toPoint());
1879 QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint,
1880 buttons, button, eventType);
1881 }
1882 } else {
1883 closeAllPopups();
1884 }
1885 }];
1886 }
1887 // The activation observer also gets called when we become active because the user clicks
1888 // into the popup. This should not close the popup, so QCocoaApplicationDelegate's
1889 // applicationDidBecomeActive implementation removes this observer.
1890 if (!s_applicationActivationObserver) {
1891 s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
1892 addObserverForName:NSWorkspaceDidActivateApplicationNotification
1893 object:nil queue:nil
1894 usingBlock:^(NSNotification *){
1895 closeAllPopups();
1896 }];
1897 }
1898}
1899
1900QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
1901{
1902 QMacAutoReleasePool pool;
1903
1904 Qt::WindowType type = window()->type();
1905 Qt::WindowFlags flags = window()->flags();
1906
1907 QRect rect = geometry();
1908
1909 QScreen *targetScreen = nullptr;
1910 for (QScreen *screen : QGuiApplication::screens()) {
1911 if (screen->geometry().contains(rect.topLeft())) {
1912 targetScreen = screen;
1913 break;
1914 }
1915 }
1916
1917 NSWindowStyleMask styleMask = windowStyleMask(flags);
1918
1919 if (!targetScreen) {
1920 qCWarning(lcQpaWindow) << "Window position" << rect << "outside any known screen, using primary screen";
1921 targetScreen = QGuiApplication::primaryScreen();
1922 // Unless the window is created as borderless AppKit won't find a position and
1923 // screen that's close to the requested invalid position, and will always place
1924 // the window on the primary screen.
1925 styleMask = NSWindowStyleMaskBorderless;
1926 }
1927
1928 rect.translate(-targetScreen->geometry().topLeft());
1929 auto *targetCocoaScreen = static_cast<QCocoaScreen *>(targetScreen->handle());
1930 NSRect contentRect = QCocoaScreen::mapToNative(rect, targetCocoaScreen);
1931
1932 if (targetScreen->primaryOrientation() == Qt::PortraitOrientation) {
1933 // The macOS window manager has a bug, where if a screen is rotated, it will not allow
1934 // a window to be created within the area of the screen that has a Y coordinate (I quadrant)
1935 // higher than the height of the screen in its non-rotated state (including a magic padding
1936 // of 24 points), unless the window is created with the NSWindowStyleMaskBorderless style mask.
1937 if (styleMask && (contentRect.origin.y + 24 > targetScreen->geometry().width())) {
1938 qCDebug(lcQpaWindow) << "Window positioned on portrait screen."
1939 << "Adjusting style mask during creation";
1940 styleMask = NSWindowStyleMaskBorderless;
1941 }
1942 }
1943
1944 // Create NSWindow
1945 Class windowClass = shouldBePanel ? [QNSPanel class] : [QNSWindow class];
1946 QCocoaNSWindow *nsWindow = [[windowClass alloc] initWithContentRect:contentRect
1947 // Mask will be updated in setWindowFlags if not the final mask
1948 styleMask:styleMask
1949 // Deferring window creation breaks OpenGL (the GL context is
1950 // set up before the window is shown and needs a proper window)
1951 backing:NSBackingStoreBuffered defer:NO
1952 screen:targetCocoaScreen->nativeScreen()
1953 platformWindow:this];
1954
1955 // The resulting screen can be different from the screen requested if
1956 // for example the application has been assigned to a specific display.
1957 auto resultingScreen = QCocoaScreen::get(nsWindow.screen);
1958
1959 // But may not always be resolved at this point, in which case we fall back
1960 // to the target screen. The real screen will be delivered as a screen change
1961 // when resolved as part of ordering the window on screen.
1962 if (!resultingScreen)
1963 resultingScreen = targetCocoaScreen;
1964
1965 if (resultingScreen->screen() != window()->screen()) {
1966 QWindowSystemInterface::handleWindowScreenChanged<
1967 QWindowSystemInterface::SynchronousDelivery>(window(), resultingScreen->screen());
1968 }
1969
1970 static QSharedPointer<QNSWindowDelegate> sharedDelegate([[QNSWindowDelegate alloc] init],
1971 [](QNSWindowDelegate *delegate) { [delegate release]; });
1972 nsWindow.delegate = sharedDelegate.get();
1973
1974 // Prevent Cocoa from releasing the window on close. Qt
1975 // handles the close event asynchronously and we want to
1976 // make sure that NSWindow stays valid until the
1977 // QCocoaWindow is deleted by Qt.
1978 [nsWindow setReleasedWhenClosed:NO];
1979
1980 if (alwaysShowToolWindow()) {
1981 static dispatch_once_t onceToken;
1982 dispatch_once(&onceToken, ^{
1983 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
1984 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
1985 name:NSApplicationWillResignActiveNotification object:nil];
1986 [center addObserver:[QNSWindow class] selector:@selector(applicationActivationChanged:)
1987 name:NSApplicationWillBecomeActiveNotification object:nil];
1988 });
1989 }
1990
1991 nsWindow.restorable = NO;
1992 nsWindow.level = windowLevel(flags);
1993 nsWindow.tabbingMode = NSWindowTabbingModeDisallowed;
1994
1995 if (shouldBePanel) {
1996 // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
1997 nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
1998
1999 // Make popup windows show on the same desktop as the parent window
2000 nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
2001 | NSWindowCollectionBehaviorMoveToActiveSpace;
2002
2003 if ((type & Qt::Popup) == Qt::Popup) {
2004 nsWindow.hasShadow = YES;
2005 nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
2006 if (QGuiApplication::applicationState() != Qt::ApplicationActive)
2007 setupPopupMonitor();
2008 }
2009 }
2010
2011 applyContentBorderThickness(nsWindow);
2012
2013 // We propagate the view's color space granulary to both the IOSurfaces
2014 // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
2015 // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
2016 // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
2017 // workaround we set the NSWindow's color space, which affects GL drawing
2018 // with NSOpenGLContext as well. This does not conflict with the granular
2019 // modifications we do to each surface for raster or Metal.
2020 if (auto *qtView = qnsview_cast(m_view))
2021 nsWindow.colorSpace = qtView.colorSpace;
2022
2023 return nsWindow;
2024}
2025
2026bool QCocoaWindow::alwaysShowToolWindow() const
2027{
2028 return qt_mac_resolveOption(false, window(), "_q_macAlwaysShowToolWindow", "");
2029}
2030
2031bool QCocoaWindow::setWindowModified(bool modified)
2032{
2033 QMacAutoReleasePool pool;
2034
2035 if (!isContentView())
2036 return false;
2037
2038 m_view.window.documentEdited = modified;
2039 return true;
2040}
2041
2042void QCocoaWindow::setMenubar(QCocoaMenuBar *mb)
2043{
2044 m_menubar = mb;
2045}
2046
2047QCocoaMenuBar *QCocoaWindow::menubar() const
2048{
2049 return m_menubar;
2050}
2051
2052void QCocoaWindow::setWindowCursor(NSCursor *cursor)
2053{
2054 QMacAutoReleasePool pool;
2055
2056 // Setting a cursor in a foreign view is not supported
2057 if (isForeignWindow())
2058 return;
2059
2060 qCInfo(lcQpaMouse) << "Setting" << this << "cursor to" << cursor;
2061
2062 QNSView *view = qnsview_cast(m_view);
2063 if (cursor == view.cursor)
2064 return;
2065
2066 view.cursor = cursor;
2067
2068 // We're not using the the legacy cursor rects API to manage our
2069 // cursor, but calling this function also invalidates AppKit's
2070 // view of whether or not we need a cursorUpdate callback for
2071 // our tracking area.
2072 [m_view.window invalidateCursorRectsForView:m_view];
2073
2074 // We've informed AppKit that we need a cursorUpdate, but cursor
2075 // updates for tracking areas are deferred in some cases, such as
2076 // when the mouse is down, whereas we want a synchronous update.
2077 // To ensure an updated cursor we synthesize a cursor update event
2078 // now if the window is otherwise allowed to change the cursor.
2079 auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
2080 auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
2081 bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
2082 auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
2083 bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
2084 if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
2085 qCDebug(lcQpaMouse) << "Synthesizing cursor update";
2086 [m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
2087 location:locationInWindow modifierFlags:0 timestamp:0
2088 windowNumber:m_view.window.windowNumber context:nil
2089 eventNumber:0 trackingNumber:0 userData:0]];
2090 }
2091}
2092
2093void QCocoaWindow::registerTouch(bool enable)
2094{
2095 m_registerTouchCount += enable ? 1 : -1;
2096 if (enable && m_registerTouchCount == 1)
2097 m_view.allowedTouchTypes |= NSTouchTypeMaskIndirect;
2098 else if (m_registerTouchCount == 0)
2099 m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
2100}
2101
2102void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower)
2103{
2104 m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower));
2105 applyContentBorderThickness();
2106}
2107
2108void QCocoaWindow::setContentBorderAreaEnabled(quintptr identifier, bool enable)
2109{
2110 m_enabledContentBorderAreas.insert(identifier, enable);
2111 applyContentBorderThickness();
2112}
2113
2114void QCocoaWindow::setContentBorderEnabled(bool enable)
2115{
2116 m_drawContentBorderGradient = enable;
2117 applyContentBorderThickness();
2118}
2119
2120void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
2121{
2122 QMacAutoReleasePool pool;
2123
2124 if (!window && isContentView())
2125 window = m_view.window;
2126
2127 if (!window)
2128 return;
2129
2130 if (!m_drawContentBorderGradient) {
2131 // FIXME (QTBUG-138829)
2132 window.styleMask = window.styleMask & QT_IGNORE_DEPRECATIONS(~NSWindowStyleMaskTexturedBackground);
2133 setWindowFlags(QPlatformWindow::window()->flags());
2134 [window.contentView.superview setNeedsDisplay:YES];
2135 return;
2136 }
2137
2138 // Find consecutive registered border areas, starting from the top.
2139 std::vector<BorderRange> ranges(m_contentBorderAreas.cbegin(), m_contentBorderAreas.cend());
2140 std::sort(ranges.begin(), ranges.end());
2141 int effectiveTopContentBorderThickness = 0;
2142 for (BorderRange range : ranges) {
2143 // Skip disiabled ranges (typically hidden tool bars)
2144 if (!m_enabledContentBorderAreas.value(range.identifier, false))
2145 continue;
2146
2147 // Is this sub-range adjacent to or overlapping the
2148 // existing total border area range? If so merge
2149 // it into the total range,
2150 if (range.upper <= (effectiveTopContentBorderThickness + 1))
2151 effectiveTopContentBorderThickness = qMax(effectiveTopContentBorderThickness, range.lower);
2152 else
2153 break;
2154 }
2155
2156 int effectiveBottomContentBorderThickness = 0;
2157
2158 // FIXME (QTBUG-138829)
2159 [window setStyleMask:[window styleMask] | QT_IGNORE_DEPRECATIONS(NSWindowStyleMaskTexturedBackground)];
2160 setWindowFlags(QPlatformWindow::window()->flags());
2161
2162 // Setting titlebarAppearsTransparent to YES means that the border thickness has to account
2163 // for the title bar height as well, otherwise sheets will not be presented at the correct
2164 // position, which should be (title bar height + top content border size).
2165 const NSRect frameRect = window.frame;
2166 const NSRect contentRect = [window contentRectForFrameRect:frameRect];
2167 const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height;
2168 effectiveTopContentBorderThickness += titlebarHeight;
2169
2170 [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge];
2171 [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
2172
2173 [window setContentBorderThickness:effectiveBottomContentBorderThickness forEdge:NSMinYEdge];
2174 [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMinYEdge];
2175
2176 [[[window contentView] superview] setNeedsDisplay:YES];
2177}
2178
2179bool QCocoaWindow::testContentBorderAreaPosition(int position) const
2180{
2181 if (!m_drawContentBorderGradient || !isContentView())
2182 return false;
2183
2184 // Determine if the given y position (relative to the content area) is inside the
2185 // unified toolbar area. Note that the value returned by contentBorderThicknessForEdge
2186 // includes the title bar height; subtract it.
2187 const int contentBorderThickness = [m_view.window contentBorderThicknessForEdge:NSMaxYEdge];
2188 const NSRect frameRect = m_view.window.frame;
2189 const NSRect contentRect = [m_view.window contentRectForFrameRect:frameRect];
2190 const CGFloat titlebarHeight = frameRect.size.height - contentRect.size.height;
2191 return 0 <= position && position < (contentBorderThickness - titlebarHeight);
2192}
2193
2194qreal QCocoaWindow::devicePixelRatio() const
2195{
2196 // The documented way to observe the relationship between device-independent
2197 // and device pixels is to use one for the convertToBacking functions. Other
2198 // methods such as [NSWindow backingScaleFactor] might not give the correct
2199 // result, for example if setWantsBestResolutionOpenGLSurface is not set or
2200 // or ignored by the OpenGL driver.
2201 NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
2202 return backingSize.height;
2203}
2204
2205QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint)
2206{
2207 QWindow *targetWindow = window();
2208 for (QObject *child : targetWindow->children())
2209 if (QWindow *childWindow = qobject_cast<QWindow *>(child))
2210 if (QPlatformWindow *handle = childWindow->handle())
2211 if (handle->isExposed() && childWindow->geometry().contains(windowPoint))
2212 targetWindow = static_cast<QCocoaWindow*>(handle)->childWindowAt(windowPoint - childWindow->position());
2213
2214 return targetWindow;
2215}
2216
2217bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
2218{
2219 // This function speaks up if there's any reason
2220 // to refuse key window or first responder state.
2221
2222 if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
2223 return true;
2224
2225 // For application modal windows, as well as direct parent windows
2226 // of window modal windows, AppKit takes care of blocking interaction.
2227 // The Qt expectation however, is that all transient parents of a
2228 // window modal window is blocked, as reflected by QGuiApplication.
2229 // We reflect this by returning false from this function for transient
2230 // parents blocked by a modal window, but limit it to the cases not
2231 // covered by AppKit to avoid potential unwanted side effects.
2232 QWindow *modalWindow = nullptr;
2233 if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
2234 if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
2235 qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
2236 << "blocked by" << modalWindow;
2237 return true;
2238 }
2239 }
2240
2241 if (m_inSetVisible) {
2242 QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
2243 if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
2244 return true;
2245 }
2246
2247 return false;
2248}
2249
2250bool QCocoaWindow::windowEvent(QEvent *event)
2251{
2252 switch (event->type()) {
2253 case QEvent::WindowBlocked:
2254 case QEvent::WindowUnblocked:
2255 updateTitleBarButtons(window()->flags());
2256 break;
2257 default:
2258 break;
2259 }
2260
2261 return QPlatformWindow::windowEvent(event);
2262}
2263
2264QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
2265{
2266 if (!m_view)
2267 return QPoint();
2268 const NSPoint origin = [m_view isFlipped] ? NSMakePoint(0, [m_view frame].size.height)
2269 : NSMakePoint(0, 0);
2270 const NSRect visibleRect = [m_view visibleRect];
2271
2272 return QPoint(visibleRect.origin.x, -visibleRect.origin.y + (origin.y - visibleRect.size.height));
2273}
2274
2275QMargins QCocoaWindow::frameMargins() const
2276{
2277 QMacAutoReleasePool pool;
2278
2279 // Child windows don't have frame margins. We explicitly don't check
2280 // isContentView() here, as we want to know the frame argins also for
2281 // windows that haven't gotten their NSWindow yet.
2282 if (QPlatformWindow::parent())
2283 return QMargins();
2284
2285 NSRect frameRect;
2286 NSRect contentRect;
2287
2288 if (m_view.window) {
2289 frameRect = m_view.window.frame;
2290 contentRect = [m_view.window contentRectForFrameRect:frameRect];
2291 } else {
2292 contentRect = m_view.frame;
2293 frameRect = [NSWindow frameRectForContentRect:contentRect styleMask:windowStyleMask(window()->flags())];
2294 }
2295
2296 return QMargins(frameRect.origin.x - contentRect.origin.x,
2297 (frameRect.origin.y + frameRect.size.height) - (contentRect.origin.y + contentRect.size.height),
2298 (frameRect.origin.x + frameRect.size.width) - (contentRect.origin.x + contentRect.size.width),
2299 contentRect.origin.y - contentRect.origin.y);
2300}
2301
2302void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled)
2303{
2304 m_frameStrutEventsEnabled = enabled;
2305}
2306
2307QPoint QCocoaWindow::mapToGlobal(const QPoint &point) const
2308{
2309 NSPoint windowPoint = [m_view convertPoint:point.toCGPoint() toView:nil];
2310 NSPoint screenPoint = [m_view.window convertPointToScreen:windowPoint];
2311 return QCocoaScreen::mapFromNative(screenPoint).toPoint();
2312}
2313
2314QPoint QCocoaWindow::mapFromGlobal(const QPoint &point) const
2315{
2316 NSPoint screenPoint = QCocoaScreen::mapToNative(point);
2317 NSPoint windowPoint = [m_view.window convertPointFromScreen:screenPoint];
2318 return QPointF::fromCGPoint([m_view convertPoint:windowPoint fromView:nil]).toPoint();
2319}
2320
2321CGPoint QCocoaWindow::mapToNative(const QPointF &point, NSView *referenceView)
2322{
2323 if (!referenceView || referenceView.flipped)
2324 return point.toCGPoint();
2325 else
2326 return qt_mac_flip(point, QRectF::fromCGRect(referenceView.bounds)).toCGPoint();
2327}
2328
2329CGRect QCocoaWindow::mapToNative(const QRectF &rect, NSView *referenceView)
2330{
2331 if (!referenceView || referenceView.flipped)
2332 return rect.toCGRect();
2333 else
2334 return qt_mac_flip(rect, QRectF::fromCGRect(referenceView.bounds)).toCGRect();
2335}
2336
2337QPointF QCocoaWindow::mapFromNative(CGPoint point, NSView *referenceView)
2338{
2339 if (!referenceView || referenceView.flipped)
2340 return QPointF::fromCGPoint(point);
2341 else
2342 return qt_mac_flip(QPointF::fromCGPoint(point), QRectF::fromCGRect(referenceView.bounds));
2343}
2344
2345QRectF QCocoaWindow::mapFromNative(CGRect rect, NSView *referenceView)
2346{
2347 if (!referenceView || referenceView.flipped)
2348 return QRectF::fromCGRect(rect);
2349 else
2350 return qt_mac_flip(QRectF::fromCGRect(rect), QRectF::fromCGRect(referenceView.bounds));
2351}
2352
2353CALayer *QCocoaWindow::contentLayer() const
2354{
2355 auto *layer = m_view.layer;
2356 if (auto *containerLayer = qt_objc_cast<QContainerLayer*>(layer))
2357 layer = containerLayer.contentLayer;
2358 return layer;
2359}
2360
2361void QCocoaWindow::manageVisualEffectArea(quintptr identifier, const QRect &rect,
2362 NSVisualEffectMaterial material, NSVisualEffectBlendingMode blendMode,
2363 NSVisualEffectState activationState)
2364{
2365 if (!qt_objc_cast<QContainerLayer*>(m_view.layer)) {
2366 qCWarning(lcQpaWindow) << "Can not manage visual effect areas"
2367 << "in views without a container layer";
2368 return;
2369 }
2370
2371 qCDebug(lcQpaWindow) << "Updating visual effect area" << identifier
2372 << "to" << rect << "with material" << material << "blend mode"
2373 << blendMode << "and activation state" << activationState;
2374
2375 NSVisualEffectView *effectView = nullptr;
2376 if (m_effectViews.contains(identifier)) {
2377 effectView = m_effectViews.value(identifier);
2378 if (rect.isEmpty()) {
2379 [effectView removeFromSuperview];
2380 m_effectViews.remove(identifier);
2381 return;
2382 }
2383 } else if (!rect.isEmpty()) {
2384 effectView = [NSVisualEffectView new];
2385 // Ensure that the visual effect layer is stacked well
2386 // below our content layer (which defaults to a z of 0).
2387 effectView.wantsLayer = YES;
2388 effectView.layer.zPosition = -FLT_MAX;
2389 [m_view addSubview:effectView];
2390 m_effectViews.insert(identifier, effectView);
2391 }
2392
2393 if (!effectView)
2394 return;
2395
2396 effectView.frame = rect.toCGRect();
2397 effectView.material = material;
2398 effectView.blendingMode = blendMode;
2399 effectView.state = activationState;
2400}
2401
2402#ifndef QT_NO_DEBUG_STREAM
2403QDebug operator<<(QDebug debug, const QCocoaWindow *window)
2404{
2405 QDebugStateSaver saver(debug);
2406 debug.nospace();
2407 debug << "QCocoaWindow(" << (const void *)window;
2408 if (window)
2409 debug << ", window=" << window->window();
2410 debug << ')';
2411 return debug;
2412}
2413#endif // !QT_NO_DEBUG_STREAM
2414
2415QT_END_NAMESPACE
2416
2417#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")