5#include <AppKit/AppKit.h>
13#include <QtCore/qcoreapplication.h>
14#include <QtGui/private/qcoregraphics_p.h>
16#include <IOKit/graphics/IOGraphicsLib.h>
18#include <QtGui/private/qwindow_p.h>
19#include <QtGui/private/qhighdpiscaling_p.h>
21#include <QtCore/private/qcore_mac_p.h>
22#include <QtCore/private/qeventdispatcher_cf_p.h>
41 Q_ENUM_NS(DisplayChange)
44QMacNotificationObserver
QCocoaScreen::s_screenParameterObserver;
45CGDisplayReconfigurationCallBack
QCocoaScreen::s_displayReconfigurationCallBack =
nullptr;
51 s_displayReconfigurationCallBack = [](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags,
void *userInfo) {
54 const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag;
55 qCDebug(lcQpaScreen).verbosity(0) <<
"Display" << displayId
56 << (beforeReconfigure ?
"beginning" :
"finished") <<
"reconfigure"
57 << QFlags<CoreGraphics::DisplayChange>(flags);
59 if (!beforeReconfigure)
62 CGDisplayRegisterReconfigurationCallback(s_displayReconfigurationCallBack,
nullptr);
64 s_screenParameterObserver = QMacNotificationObserver(NSApplication.sharedApplication,
65 NSApplicationDidChangeScreenParametersNotification, [&]() {
66 qCDebug(lcQpaScreen) <<
"Received screen parameter change notification";
76
77
78
79
87 static bool updatingScreens =
false;
88 if (updatingScreens) {
89 qCInfo(lcQpaScreen) <<
"Skipping screen update, already updating";
92 QScopedValueRollback recursionGuard(updatingScreens,
true);
94 uint32_t displayCount = 0;
95 if (CGGetOnlineDisplayList(0,
nullptr, &displayCount) != kCGErrorSuccess)
96 qFatal(
"Failed to get number of online displays");
98 QVector<CGDirectDisplayID> onlineDisplays(displayCount);
99 if (CGGetOnlineDisplayList(displayCount, onlineDisplays.data(), &displayCount) != kCGErrorSuccess)
100 qFatal(
"Failed to get online displays");
102 qCInfo(lcQpaScreen) <<
"Updating screens with" << displayCount
103 <<
"online displays:" << onlineDisplays;
106 int mainDisplayIndex = onlineDisplays.indexOf(CGMainDisplayID());
107 if (mainDisplayIndex < 0) {
108 qCWarning(lcQpaScreen) <<
"Main display not in list of online displays!";
109 }
else if (mainDisplayIndex > 0) {
110 qCWarning(lcQpaScreen) <<
"Main display not first display, making sure it is";
111 onlineDisplays.move(mainDisplayIndex, 0);
114 for (CGDirectDisplayID displayId : onlineDisplays) {
115 Q_ASSERT(CGDisplayIsOnline(displayId));
117 if (CGDisplayMirrorsDisplay(displayId))
126 QCFType<CFUUIDRef> uuid = CGDisplayCreateUUIDFromDisplayID(displayId);
129 if (QCocoaScreen *existingScreen = QCocoaScreen::get(uuid)) {
130 existingScreen->update(displayId);
131 qCInfo(lcQpaScreen) <<
"Updated" << existingScreen;
132 if (CGDisplayIsMain(displayId) && existingScreen != qGuiApp->primaryScreen()->handle()) {
133 qCInfo(lcQpaScreen) <<
"Primary screen changed to" << existingScreen;
134 QWindowSystemInterface::handlePrimaryScreenChanged(existingScreen);
137 QCocoaScreen::add(displayId);
141 for (QScreen *screen : QGuiApplication::screens()) {
142 QCocoaScreen *platformScreen =
static_cast<QCocoaScreen*>(screen->handle());
143 if (!platformScreen->isOnline() || platformScreen->isMirroring())
144 platformScreen->remove();
150 const bool isPrimary = CGDisplayIsMain(displayId);
152 qCInfo(lcQpaScreen) <<
"Adding" << cocoaScreen
153 << (isPrimary ?
"as new primary screen" :
"");
154 QWindowSystemInterface::handleScreenAdded(cocoaScreen, isPrimary);
158 : QPlatformScreen(), m_displayId(displayId)
161 m_cursor =
new QCocoaCursor;
167 for (QScreen *screen : backwards(QGuiApplication::screens()))
168 static_cast<QCocoaScreen*>(screen->handle())->remove();
170 Q_ASSERT(s_displayReconfigurationCallBack);
171 CGDisplayRemoveReconfigurationCallback(s_displayReconfigurationCallBack,
nullptr);
172 s_displayReconfigurationCallBack =
nullptr;
174 s_screenParameterObserver.remove();
190 qCInfo(lcQpaScreen) <<
"Removing " <<
this;
191 QWindowSystemInterface::handleScreenRemoved(
this);
196 Q_ASSERT_X(!screen(),
"QCocoaScreen",
"QScreen should be deleted first");
200 CVDisplayLinkRelease(m_displayLink);
201 if (m_displayLinkSource)
202 dispatch_release(m_displayLinkSource);
207 if (displayId != m_displayId) {
208 qCDebug(lcQpaScreen) <<
"Reconnecting" <<
this <<
"as display" << displayId;
209 m_displayId = displayId;
212 Q_ASSERT(isOnline());
215 NSScreen *nsScreen = nativeScreen();
217 qCDebug(lcQpaScreen) <<
"Corresponding NSScreen not yet available. Deferring update";
221 const QRect previousGeometry = m_geometry;
222 const QRect previousAvailableGeometry = m_availableGeometry;
223 const qreal previousRefreshRate = m_refreshRate;
224 const double previousRotation = m_rotation;
227 QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
228 m_geometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect();
229 m_availableGeometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.visibleFrame), primaryScreenGeometry).toRect();
231 m_devicePixelRatio = nsScreen.backingScaleFactor;
233 m_format = QImage::Format_RGB32;
234 m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
235 m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData));
236 if (!m_colorSpace.isValid()) {
237 qCWarning(lcQpaScreen) <<
"Failed to parse ICC profile for" << nsScreen.colorSpace
238 <<
"with ICC data" << nsScreen.colorSpace.ICCProfileData
239 <<
"- Falling back to sRGB";
240 m_colorSpace = QColorSpace::SRgb;
243 CGSize size = CGDisplayScreenSize(m_displayId);
244 m_physicalSize = QSizeF(size.width, size.height);
246 QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
247 float refresh = CGDisplayModeGetRefreshRate(displayMode);
248 m_refreshRate = refresh > 0 ? refresh : 60.0;
249 m_rotation = CGDisplayRotation(displayId);
250 m_name = QString::fromNSString(nsScreen.localizedName);
252 const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
254 if (m_rotation != previousRotation)
255 QWindowSystemInterface::handleScreenOrientationChange(screen(), orientation());
257 if (didChangeGeometry)
258 QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry());
259 if (m_refreshRate != previousRefreshRate)
260 QWindowSystemInterface::handleScreenRefreshRateChange(screen(), m_refreshRate);
269 Q_ASSERT(m_displayId);
272 qCDebug(lcQpaScreenUpdates) <<
this <<
"is not online. Ignoring update request";
279 ++m_pendingUpdateRequests;
281 if (!m_displayLink) {
282 qCDebug(lcQpaScreenUpdates) <<
"Creating display link for" <<
this;
283 if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) {
284 qCWarning(lcQpaScreenUpdates) <<
"Failed to create display link for" <<
this;
287 if (
auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) {
288 qCWarning(lcQpaScreenUpdates) <<
"Unexpected display" << displayId <<
"for display link";
289 CVDisplayLinkRelease(m_displayLink);
290 m_displayLink =
nullptr;
293 CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef,
const CVTimeStamp*,
294 const CVTimeStamp*, CVOptionFlags, CVOptionFlags*,
void* displayLinkContext) ->
int {
296 static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests();
297 return kCVReturnSuccess;
319 static CFMachPortRef eventTap = []() {
320 CFMachPortRef eventTap = CGEventTapCreateForPid(getpid(), kCGTailAppendEventTap,
321 kCGEventTapOptionListenOnly, NSEventMaskLeftMouseDragged,
322 [](CGEventTapProxy, CGEventType type, CGEventRef event,
void *) -> CGEventRef {
323 if (type == kCGEventTapDisabledByTimeout)
324 qCWarning(lcQpaScreenUpdates) <<
"Event tap disabled due to timeout!";
327 CGEventTapEnable(eventTap,
false);
328 static CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
329 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
331 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
332 [center addObserverForName:NSWindowWillStartLiveResizeNotification object:nil queue:nil
333 usingBlock:^(NSNotification *notification) {
334 qCDebug(lcQpaScreenUpdates) <<
"Live resize of" << notification.object
335 <<
"started. Enabling event tap";
336 CGEventTapEnable(eventTap,
true);
338 [center addObserverForName:NSWindowDidEndLiveResizeNotification object:nil queue:nil
339 usingBlock:^(NSNotification *notification) {
340 qCDebug(lcQpaScreenUpdates) <<
"Live resize of" << notification.object
341 <<
"ended. Disabling event tap";
342 CGEventTapEnable(eventTap,
false);
349 if (!CVDisplayLinkIsRunning(m_displayLink)) {
350 qCDebug(lcQpaScreenUpdates) <<
"Starting display link for" <<
this;
351 CVDisplayLinkStart(m_displayLink);
361 if (cat.isDebugEnabled())
362 debug =
new QDebug(QMessageLogger().debug(cat).nospace());
376#define qDeferredDebug(helper) if (Q_UNLIKELY(helper.debug)) *helper.debug
383 QMacAutoReleasePool pool;
390 if (!NSThread.isMainThread) {
394 const int pendingUpdates = ++m_pendingDisplayLinkUpdates;
396 const int pendingUpdateRequests = m_pendingUpdateRequests;
399 qDeferredDebug(screenUpdates) <<
"display link callback for screen " << m_displayId
400 <<
" with " << pendingUpdateRequests <<
" pending update requests";
402 if (
const int framesAheadOfDelivery = pendingUpdates - 1) {
407 qDeferredDebug(screenUpdates) <<
", " << framesAheadOfDelivery <<
" frame(s) ahead";
410 if (!pendingUpdateRequests) {
415 qDeferredDebug(screenUpdates) <<
"; skipping signaling dispatch source";
416 m_pendingDisplayLinkUpdates = 0;
422 if (!m_displayLinkSource) {
423 m_displayLinkSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
424 dispatch_source_set_event_handler(m_displayLinkSource, ^{
425 deliverUpdateRequests();
427 dispatch_resume(m_displayLinkSource);
430 dispatch_source_merge_data(m_displayLinkSource, 1);
436 const int pendingUpdates = m_pendingDisplayLinkUpdates;
437 if (pendingUpdates > 1)
438 qDeferredDebug(screenUpdates) <<
", " << (pendingUpdates - 1) <<
" frame(s) behind display link";
442 int pendingUpdateRequests = 0;
444 auto windows = QGuiApplication::allWindows();
445 for (
int i = 0; i < windows.size(); ++i) {
446 QWindow *window = windows.at(i);
447 if (window->screen() != screen())
450 QPointer<QCocoaWindow> platformWindow =
static_cast<
QCocoaWindow*>(window->handle());
454 if (!platformWindow->hasPendingUpdateRequest())
458 if (!platformWindow->updatesWithDisplayLink())
461 platformWindow->deliverUpdateRequest();
470 if (platformWindow->hasPendingUpdateRequest())
471 ++pendingUpdateRequests;
474 m_pendingUpdateRequests = pendingUpdateRequests;
476 if (
const int missedUpdates = m_pendingDisplayLinkUpdates.fetchAndStoreRelaxed(0) - pendingUpdates) {
477 qCWarning(lcQpaScreenUpdates) <<
"main thread missed" << missedUpdates
478 <<
"update(s) from display link during update request delivery";
485 if (!CVDisplayLinkIsRunning(m_displayLink))
488 const auto windows = QGuiApplication::allWindows();
489 for (
auto *window : windows) {
490 if (window->screen() != screen())
493 QPointer<QCocoaWindow> platformWindow =
static_cast<QCocoaWindow*>(window->handle());
497 if (window->isExposed())
500 if (platformWindow->hasPendingUpdateRequest())
504 qCDebug(lcQpaScreenUpdates) <<
"Stopping display link for" <<
this;
505 CVDisplayLinkStop(m_displayLink);
513 if (@available(macOS 14, *)) {
514 for (
auto *window : QGuiApplication::allWindows()) {
515 auto *platformWindow =
static_cast<QCocoaWindow*>(window->handle());
519 NSView *view = platformWindow->view();
521 if (!view.layer.wantsExtendedDynamicRangeContent)
524 [view setNeedsDisplay:YES];
533 QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
534 if (type == QPlatformScreen::Subpixel_None) {
536 type = QPlatformScreen::Subpixel_RGB;
544 return Qt::LandscapeOrientation;
545 if (m_rotation == 90)
546 return Qt::PortraitOrientation;
547 if (m_rotation == 180)
548 return Qt::InvertedLandscapeOrientation;
549 if (m_rotation == 270)
550 return Qt::InvertedPortraitOrientation;
551 return QPlatformScreen::orientation();
556 __block QWindow *window =
nullptr;
557 [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
558 usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
563 if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
566 QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
570 QWindow *w = cocoaWindow->window();
574 auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w);
575 if (!nativeGeometry.contains(point))
578 QRegion mask = QHighDpi::toNativeLocalPosition(w->mask(), w);
579 if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft()))
585 if (!window->isTopLevel())
596
597
598
599
600
604
605
606
607
608
609
610
611 auto grabFromDisplay = [](CGDirectDisplayID displayId,
const QRect &grabRect) -> QPixmap {
612 QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displayId, grabRect.toCGRect());
613 const QCFType<CGColorSpaceRef> sRGBcolorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
614 if (CGImageGetColorSpace(image) != sRGBcolorSpace) {
615 qCDebug(lcQpaScreen) <<
"applying color correction for display" << displayId;
616 image = CGImageCreateCopyWithColorSpace(image, sRGBcolorSpace);
618 QPixmap pixmap = QPixmap::fromImage(qt_mac_toQImage(image));
619 pixmap.setDevicePixelRatio(nativeScreenForDisplayId(displayId).backingScaleFactor);
623 QRect grabRect = QRect(x, y, width, height);
624 qCDebug(lcQpaScreen) <<
"input grab rect" << grabRect;
628 if (!grabRect.isValid())
629 grabRect = QRect(QPoint(0, 0), geometry().size());
631 grabRect.translate(-geometry().topLeft());
632 return grabFromDisplay(displayId(), grabRect);
636 NSView *nsView =
reinterpret_cast<NSView*>(view);
637 NSPoint windowPoint = [nsView convertPoint:NSMakePoint(0, 0) toView:nil];
638 NSRect screenRect = [nsView.window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)];
639 QPoint position = mapFromNative(screenRect.origin).toPoint();
640 QSize size = QRectF::fromCGRect(NSRectToCGRect(nsView.bounds)).toRect().size();
641 QRect windowRect = QRect(position, size);
642 if (!grabRect.isValid())
643 grabRect = windowRect;
645 grabRect.translate(windowRect.topLeft());
648 const int maxDisplays = 128;
649 CGDirectDisplayID displays[maxDisplays];
650 CGDisplayCount displayCount;
651 CGRect cgRect = grabRect.isValid() ? grabRect.toCGRect() : CGRectInfinite;
652 const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount);
653 if (err || displayCount == 0)
656 qCDebug(lcQpaScreen) <<
"final grab rect" << grabRect <<
"from" << displayCount <<
"displays";
659 QVector<QPixmap> pixmaps;
660 QVector<QRect> destinations;
661 for (uint i = 0; i < displayCount; ++i) {
662 auto display = displays[i];
663 const QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect();
664 const QRect grabBounds = displayBounds.intersected(grabRect);
665 if (grabBounds.isNull()) {
666 destinations.append(QRect());
667 pixmaps.append(QPixmap());
670 const QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size());
672 qCDebug(lcQpaScreen) <<
"grab display" << i <<
"global" << grabBounds <<
"local" << displayLocalGrabBounds;
673 QPixmap displayPixmap = grabFromDisplay(display, displayLocalGrabBounds);
675 if (displayCount == 1)
676 return displayPixmap;
678 qCDebug(lcQpaScreen) <<
"grab sub-image size" << displayPixmap.size() <<
"devicePixelRatio" << displayPixmap.devicePixelRatio();
679 pixmaps.append(displayPixmap);
680 const QRect destBounds = QRect(QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size());
681 destinations.append(destBounds);
686 for (uint i = 0; i < displayCount; ++i)
687 dpr = qMax(dpr, pixmaps.at(i).devicePixelRatio());
690 qCDebug(lcQpaScreen) <<
"Create grap pixmap" << grabRect.size() <<
"at devicePixelRatio" << dpr;
691 QPixmap windowPixmap(grabRect.size() * dpr);
692 windowPixmap.setDevicePixelRatio(dpr);
693 windowPixmap.fill(Qt::transparent);
694 QPainter painter(&windowPixmap);
695 for (uint i = 0; i < displayCount; ++i)
696 painter.drawPixmap(destinations.at(i), pixmaps.at(i));
708 int isOnline = CGDisplayIsOnline(m_displayId);
709 static const int kCGDisplayIsDisconnected = 0xffffffff;
710 return isOnline != kCGDisplayIsDisconnected && isOnline;
714
715
721 return CGDisplayMirrorsDisplay(m_displayId);
725
726
732 return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
737 QList<QPlatformScreen*> siblings;
740 for (QScreen *screen : QGuiApplication::screens())
741 siblings << screen->handle();
748 auto displayId = nsScreen.qt_displayId;
749 auto *cocoaScreen = get(displayId);
751 qCWarning(lcQpaScreen) <<
"Failed to map" << nsScreen
752 <<
"to QCocoaScreen. Doing last minute update.";
754 cocoaScreen = get(displayId);
756 qCWarning(lcQpaScreen) <<
"Last minute update failed!";
763 for (QScreen *screen : QGuiApplication::screens()) {
764 QCocoaScreen *cocoaScreen =
static_cast<QCocoaScreen*>(screen->handle());
765 if (cocoaScreen->m_displayId == displayId)
774 for (QScreen *screen : QGuiApplication::screens()) {
775 auto *platformScreen =
static_cast<QCocoaScreen*>(screen->handle());
776 if (!platformScreen->isOnline())
779 auto displayId = platformScreen->displayId();
780 QCFType<CFUUIDRef> candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId));
781 Q_ASSERT(candidateUuid);
783 if (candidateUuid == uuid)
784 return platformScreen;
790NSScreen *
QCocoaScreen::nativeScreenForDisplayId(CGDirectDisplayID displayId)
792 for (NSScreen *screen in NSScreen.screens) {
793 if (screen.qt_displayId == displayId)
804 return nativeScreenForDisplayId(m_displayId);
810 return qt_mac_flip(pos, screen->geometry()).toCGPoint();
816 return qt_mac_flip(rect, screen->geometry()).toCGRect();
822 return qt_mac_flip(QPointF::fromCGPoint(pos), screen->geometry());
828 return qt_mac_flip(QRectF::fromCGRect(rect), screen->geometry());
831#ifndef QT_NO_DEBUG_STREAM
834 QDebugStateSaver saver(debug);
836 debug <<
"QCocoaScreen(" << (
const void *)screen;
838 debug <<
", " << screen->name();
839 if (screen->isOnline()) {
840 if (CGDisplayIsAsleep(screen->displayId()))
841 debug <<
", Sleeping";
842 if (
auto mirroring = CGDisplayMirrorsDisplay(screen->displayId()))
843 debug <<
", mirroring=" << mirroring;
845 debug <<
", Offline";
847 debug <<
", " << screen->geometry();
848 debug <<
", dpr=" << screen->devicePixelRatio();
849 debug <<
", displayId=" << screen->displayId();
851 if (
auto nativeScreen = screen->nativeScreen())
852 debug <<
", " << nativeScreen;
861#include "qcocoascreen.moc"
863@implementation NSScreen (QtExtras)
865- (CGDirectDisplayID)qt_displayId
867 return [self.deviceDescription[@
"NSScreenNumber"] unsignedIntValue];
QPixmap grabWindow(WId window, int x, int y, int width, int height) const override
void deliverUpdateRequests()
NSScreen * nativeScreen() const override
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override
Returns a hint about this screen's subpixel layout structure.
static QCocoaScreen * primaryScreen()
The screen used as a reference for global window geometry.
Qt::ScreenOrientation orientation() const override
Reimplement this function in subclass to return the current orientation of the screen,...
QList< QPlatformScreen * > virtualSiblings() const override
Returns a list of all the platform screens that are part of the same virtual desktop.
QWindow * topLevelAt(const QPoint &point) const override
Return the given top level window for a given position.
\inmodule QtCore\reentrant
@ ReconfiguredWithFlagsMissing
#define qDeferredDebug(helper)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
DeferredDebugHelper(const QLoggingCategory &cat)