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"
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;
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 {
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
381 qCDebug(lcQpaScreenUpdates) <<
this <<
"is not online. Ignoring update request delivery";
385 QMacAutoReleasePool pool;
392 if (!NSThread.isMainThread) {
396 const int pendingUpdates = ++m_pendingDisplayLinkUpdates;
398 const int pendingUpdateRequests = m_pendingUpdateRequests;
401 qDeferredDebug(screenUpdates) <<
"display link callback for screen " << m_displayId
402 <<
" with " << pendingUpdateRequests <<
" pending update requests";
404 if (
const int framesAheadOfDelivery = pendingUpdates - 1) {
409 qDeferredDebug(screenUpdates) <<
", " << framesAheadOfDelivery <<
" frame(s) ahead";
412 if (!pendingUpdateRequests) {
417 qDeferredDebug(screenUpdates) <<
"; skipping signaling dispatch source";
418 m_pendingDisplayLinkUpdates = 0;
424 if (!m_displayLinkSource) {
425 m_displayLinkSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
426 dispatch_source_set_event_handler(m_displayLinkSource, ^{
429 dispatch_resume(m_displayLinkSource);
432 dispatch_source_merge_data(m_displayLinkSource, 1);
438 const int pendingUpdates = m_pendingDisplayLinkUpdates;
439 if (pendingUpdates > 1)
440 qDeferredDebug(screenUpdates) <<
", " << (pendingUpdates - 1) <<
" frame(s) behind display link";
444 int pendingUpdateRequests = 0;
446 auto windows = QGuiApplication::allWindows();
447 for (
int i = 0; i < windows.size(); ++i) {
448 QWindow *window = windows.at(i);
449 if (window->screen() != screen())
452 QPointer<QCocoaWindow> platformWindow =
static_cast<
QCocoaWindow*>(window->handle());
456 if (!platformWindow->hasPendingUpdateRequest())
460 if (!platformWindow->updatesWithDisplayLink())
463 platformWindow->deliverUpdateRequest();
472 if (platformWindow->hasPendingUpdateRequest())
473 ++pendingUpdateRequests;
476 m_pendingUpdateRequests = pendingUpdateRequests;
478 if (
const int missedUpdates = m_pendingDisplayLinkUpdates.fetchAndStoreRelaxed(0) - pendingUpdates) {
479 qCWarning(lcQpaScreenUpdates) <<
"main thread missed" << missedUpdates
480 <<
"update(s) from display link during update request delivery";
487 if (!CVDisplayLinkIsRunning(m_displayLink))
490 const auto windows = QGuiApplication::allWindows();
491 for (
auto *window : windows) {
492 if (window->screen() != screen())
495 QPointer<QCocoaWindow> platformWindow =
static_cast<QCocoaWindow*>(window->handle());
499 if (window->isExposed())
502 if (platformWindow->hasPendingUpdateRequest())
506 qCDebug(lcQpaScreenUpdates) <<
"Stopping display link for" <<
this;
507 CVDisplayLinkStop(m_displayLink);
515 if (@available(macOS 14, *)) {
516 for (
auto *window : QGuiApplication::allWindows()) {
517 auto *platformWindow =
static_cast<QCocoaWindow*>(window->handle());
521 NSView *view = platformWindow->view();
523 if (!view.layer.wantsExtendedDynamicRangeContent)
526 [view setNeedsDisplay:YES];
535 QPlatformScreen::SubpixelAntialiasingType type = QPlatformScreen::subpixelAntialiasingTypeHint();
536 if (type == QPlatformScreen::Subpixel_None) {
538 type = QPlatformScreen::Subpixel_RGB;
546 return Qt::LandscapeOrientation;
547 if (m_rotation == 90)
548 return Qt::PortraitOrientation;
549 if (m_rotation == 180)
550 return Qt::InvertedLandscapeOrientation;
551 if (m_rotation == 270)
552 return Qt::InvertedPortraitOrientation;
553 return QPlatformScreen::orientation();
558 __block QWindow *window =
nullptr;
559 [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
560 usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
565 if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
568 QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
572 QWindow *w = cocoaWindow->window();
576 auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w);
577 if (!nativeGeometry.contains(point))
580 QRegion mask = QHighDpi::toNativeLocalPosition(w->mask(), w);
581 if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft()))
587 if (!window->isTopLevel())
598
599
600
601
602
606
607
608
609
610
611
612
613 auto grabFromDisplay = [](CGDirectDisplayID displayId,
const QRect &grabRect) -> QPixmap {
614 QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displayId, grabRect.toCGRect());
615 const QCFType<CGColorSpaceRef> sRGBcolorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
616 if (CGImageGetColorSpace(image) != sRGBcolorSpace) {
617 qCDebug(lcQpaScreen) <<
"applying color correction for display" << displayId;
618 image = CGImageCreateCopyWithColorSpace(image, sRGBcolorSpace);
620 QPixmap pixmap = QPixmap::fromImage(qt_mac_toQImage(image));
621 pixmap.setDevicePixelRatio(nativeScreenForDisplayId(displayId).backingScaleFactor);
625 QRect grabRect = QRect(x, y, width, height);
626 qCDebug(lcQpaScreen) <<
"input grab rect" << grabRect;
630 if (!grabRect.isValid())
631 grabRect = QRect(QPoint(0, 0), geometry().size());
633 grabRect.translate(-geometry().topLeft());
634 return grabFromDisplay(displayId(), grabRect);
638 NSView *nsView =
reinterpret_cast<NSView*>(view);
639 NSPoint windowPoint = [nsView convertPoint:NSMakePoint(0, 0) toView:nil];
640 NSRect screenRect = [nsView.window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)];
641 QPoint position = mapFromNative(screenRect.origin).toPoint();
642 QSize size = QRectF::fromCGRect(NSRectToCGRect(nsView.bounds)).toRect().size();
643 QRect windowRect = QRect(position, size);
644 if (!grabRect.isValid())
645 grabRect = windowRect;
647 grabRect.translate(windowRect.topLeft());
650 const int maxDisplays = 128;
651 CGDirectDisplayID displays[maxDisplays];
652 CGDisplayCount displayCount;
653 CGRect cgRect = grabRect.isValid() ? grabRect.toCGRect() : CGRectInfinite;
654 const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount);
655 if (err || displayCount == 0)
658 qCDebug(lcQpaScreen) <<
"final grab rect" << grabRect <<
"from" << displayCount <<
"displays";
661 QVector<QPixmap> pixmaps;
662 QVector<QRect> destinations;
663 for (uint i = 0; i < displayCount; ++i) {
664 auto display = displays[i];
665 const QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect();
666 const QRect grabBounds = displayBounds.intersected(grabRect);
667 if (grabBounds.isNull()) {
668 destinations.append(QRect());
669 pixmaps.append(QPixmap());
672 const QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size());
674 qCDebug(lcQpaScreen) <<
"grab display" << i <<
"global" << grabBounds <<
"local" << displayLocalGrabBounds;
675 QPixmap displayPixmap = grabFromDisplay(display, displayLocalGrabBounds);
677 if (displayCount == 1)
678 return displayPixmap;
680 qCDebug(lcQpaScreen) <<
"grab sub-image size" << displayPixmap.size() <<
"devicePixelRatio" << displayPixmap.devicePixelRatio();
681 pixmaps.append(displayPixmap);
682 const QRect destBounds = QRect(QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size());
683 destinations.append(destBounds);
688 for (uint i = 0; i < displayCount; ++i)
689 dpr = qMax(dpr, pixmaps.at(i).devicePixelRatio());
692 qCDebug(lcQpaScreen) <<
"Create grap pixmap" << grabRect.size() <<
"at devicePixelRatio" << dpr;
693 QPixmap windowPixmap(grabRect.size() * dpr);
694 windowPixmap.setDevicePixelRatio(dpr);
695 windowPixmap.fill(Qt::transparent);
696 QPainter painter(&windowPixmap);
697 for (uint i = 0; i < displayCount; ++i)
698 painter.drawPixmap(destinations.at(i), pixmaps.at(i));
710 int isOnline = CGDisplayIsOnline(m_displayId);
711 static const int kCGDisplayIsDisconnected = 0xffffffff;
712 return isOnline != kCGDisplayIsDisconnected && isOnline;
716
717
723 return CGDisplayMirrorsDisplay(m_displayId);
727
728
734 return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
739 QList<QPlatformScreen*> siblings;
742 for (QScreen *screen : QGuiApplication::screens())
743 siblings << screen->handle();
750 auto displayId = nsScreen.qt_displayId;
751 auto *cocoaScreen = get(displayId);
753 qCWarning(lcQpaScreen) <<
"Failed to map" << nsScreen
754 <<
"to QCocoaScreen. Doing last minute update.";
756 cocoaScreen = get(displayId);
758 qCWarning(lcQpaScreen) <<
"Last minute update failed!";
765 for (QScreen *screen : QGuiApplication::screens()) {
766 QCocoaScreen *cocoaScreen =
static_cast<QCocoaScreen*>(screen->handle());
767 if (cocoaScreen->m_displayId == displayId)
776 for (QScreen *screen : QGuiApplication::screens()) {
777 auto *platformScreen =
static_cast<QCocoaScreen*>(screen->handle());
778 if (!platformScreen->isOnline())
781 auto displayId = platformScreen->displayId();
782 QCFType<CFUUIDRef> candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId));
783 Q_ASSERT(candidateUuid);
785 if (candidateUuid == uuid)
786 return platformScreen;
792NSScreen *
QCocoaScreen::nativeScreenForDisplayId(CGDirectDisplayID displayId)
794 for (NSScreen *screen in NSScreen.screens) {
795 if (screen.qt_displayId == displayId)
806 return nativeScreenForDisplayId(m_displayId);
812 return qt_mac_flip(pos, screen->geometry()).toCGPoint();
818 return qt_mac_flip(rect, screen->geometry()).toCGRect();
824 return qt_mac_flip(QPointF::fromCGPoint(pos), screen->geometry());
830 return qt_mac_flip(QRectF::fromCGRect(rect), screen->geometry());
833#ifndef QT_NO_DEBUG_STREAM
836 QDebugStateSaver saver(debug);
838 debug <<
"QCocoaScreen(" << (
const void *)screen;
840 debug <<
", " << screen->name();
842 if (CGDisplayIsAsleep(screen->displayId()))
843 debug <<
", Sleeping";
844 if (
auto mirroring = CGDisplayMirrorsDisplay(screen->displayId()))
845 debug <<
", mirroring=" << mirroring;
847 debug <<
", Offline";
849 debug <<
", " << screen->geometry();
850 debug <<
", dpr=" << screen->devicePixelRatio();
851 debug <<
", displayId=" << screen->displayId();
853 if (
auto nativeScreen = screen->nativeScreen())
854 debug <<
", " << nativeScreen;
863#include "qcocoascreen.moc"
865@implementation NSScreen (QtExtras)
867- (CGDirectDisplayID)qt_displayId
869 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)