12#include <QtCore/qcoreapplication.h>
13#include <QtGui/private/qcoregraphics_p.h>
15#include <IOKit/graphics/IOGraphicsLib.h>
17#include <QtGui/private/qwindow_p.h>
18#include <QtGui/private/qhighdpiscaling_p.h>
20#include <QtCore/private/qcore_mac_p.h>
21#include <QtCore/private/qeventdispatcher_cf_p.h>
44CGDisplayReconfigurationCallBack QCocoaScreen::s_displayReconfigurationCallBack =
nullptr;
46void QCocoaScreen::initializeScreens()
50 s_displayReconfigurationCallBack = [](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags
flags,
void *userInfo) {
53 const bool beforeReconfigure =
flags & kCGDisplayBeginConfigurationFlag;
54 qCDebug(lcQpaScreen).verbosity(0) <<
"Display" << displayId
55 << (beforeReconfigure ?
"beginning" :
"finished") <<
"reconfigure"
56 << QFlags<CoreGraphics::DisplayChange>(
flags);
58 if (!beforeReconfigure)
61 CGDisplayRegisterReconfigurationCallback(s_displayReconfigurationCallBack,
nullptr);
64 NSApplicationDidChangeScreenParametersNotification, [&]() {
65 qCDebug(lcQpaScreen) <<
"Received screen parameter change notification";
75void QCocoaScreen::updateScreens()
82 static bool updatingScreens =
false;
83 if (updatingScreens) {
84 qCInfo(lcQpaScreen) <<
"Skipping screen update, already updating";
89 uint32_t displayCount = 0;
90 if (CGGetOnlineDisplayList(0,
nullptr, &displayCount) != kCGErrorSuccess)
91 qFatal(
"Failed to get number of online displays");
93 QVector<CGDirectDisplayID> onlineDisplays(displayCount);
94 if (CGGetOnlineDisplayList(displayCount, onlineDisplays.data(), &displayCount) != kCGErrorSuccess)
95 qFatal(
"Failed to get online displays");
97 qCInfo(lcQpaScreen) <<
"Updating screens with" << displayCount
98 <<
"online displays:" << onlineDisplays;
101 int mainDisplayIndex = onlineDisplays.indexOf(CGMainDisplayID());
102 if (mainDisplayIndex < 0) {
103 qCWarning(lcQpaScreen) <<
"Main display not in list of online displays!";
104 }
else if (mainDisplayIndex > 0) {
105 qCWarning(lcQpaScreen) <<
"Main display not first display, making sure it is";
106 onlineDisplays.move(mainDisplayIndex, 0);
109 for (CGDirectDisplayID displayId : onlineDisplays) {
110 Q_ASSERT(CGDisplayIsOnline(displayId));
112 if (CGDisplayMirrorsDisplay(displayId))
121 QCFType<CFUUIDRef> uuid = CGDisplayCreateUUIDFromDisplayID(displayId);
125 existingScreen->update(displayId);
126 qCInfo(lcQpaScreen) <<
"Updated" << existingScreen;
127 if (CGDisplayIsMain(displayId) && existingScreen !=
qGuiApp->primaryScreen()->handle()) {
128 qCInfo(lcQpaScreen) <<
"Primary screen changed to" << existingScreen;
132 QCocoaScreen::add(displayId);
138 if (!platformScreen->isOnline() || platformScreen->isMirroring())
139 platformScreen->remove();
143void QCocoaScreen::add(CGDirectDisplayID displayId)
145 const bool isPrimary = CGDisplayIsMain(displayId);
147 qCInfo(lcQpaScreen) <<
"Adding" << cocoaScreen
148 << (isPrimary ?
"as new primary screen" :
"");
152QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId)
159void QCocoaScreen::cleanupScreens()
165 Q_ASSERT(s_displayReconfigurationCallBack);
166 CGDisplayRemoveReconfigurationCallback(s_displayReconfigurationCallBack,
nullptr);
167 s_displayReconfigurationCallBack =
nullptr;
169 s_screenParameterObserver.remove();
172void QCocoaScreen::remove()
185 qCInfo(lcQpaScreen) <<
"Removing " <<
this;
195 CVDisplayLinkRelease(m_displayLink);
196 if (m_displayLinkSource)
197 dispatch_release(m_displayLinkSource);
202 QIOType<io_iterator_t> iterator;
203 if (IOServiceGetMatchingServices(kIOMainPortDefault,
204 IOServiceMatching(
"IODisplayConnect"), &iterator))
208 while ((
display = IOIteratorNext(iterator)) != 0)
210 NSDictionary *
info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary(
211 display, kIODisplayOnlyPreferredName) autorelease];
213 if ([[
info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID))
216 if ([[
info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID))
219 if ([[
info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID))
222 NSDictionary *localizedNames = [
info objectForKey:@kDisplayProductName];
223 if (![localizedNames
count])
226 return QString::fromNSString([localizedNames objectForKey:[[localizedNames
allKeys] objectAtIndex:0]]);
232void QCocoaScreen::update(CGDirectDisplayID displayId)
234 if (displayId != m_displayId) {
235 qCDebug(lcQpaScreen) <<
"Reconnecting" <<
this <<
"as display" << displayId;
236 m_displayId = displayId;
244 qCDebug(lcQpaScreen) <<
"Corresponding NSScreen not yet available. Deferring update";
248 const QRect previousGeometry = m_geometry;
249 const QRect previousAvailableGeometry = m_availableGeometry;
250 const qreal previousRefreshRate = m_refreshRate;
251 const double previousRotation = m_rotation;
254 QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
255 m_geometry =
qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect();
256 m_availableGeometry =
qt_mac_flip(QRectF::fromCGRect(nsScreen.visibleFrame), primaryScreenGeometry).toRect();
258 m_devicePixelRatio = nsScreen.backingScaleFactor;
261 m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
264 qCWarning(lcQpaScreen) <<
"Failed to parse ICC profile for" << nsScreen.colorSpace
265 <<
"with ICC data" << nsScreen.colorSpace.ICCProfileData
266 <<
"- Falling back to sRGB";
270 CGSize
size = CGDisplayScreenSize(m_displayId);
273 QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
274 float refresh = CGDisplayModeGetRefreshRate(displayMode);
275 m_refreshRate = refresh > 0 ? refresh : 60.0;
276 m_rotation = CGDisplayRotation(displayId);
278 if (@available(macOS 10.15, *))
279 m_name = QString::fromNSString(nsScreen.localizedName);
283 const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
285 if (m_rotation != previousRotation)
288 if (didChangeGeometry)
290 if (m_refreshRate != previousRefreshRate)
303 qCDebug(lcQpaScreenUpdates) <<
this <<
"is not online. Ignoring update request";
307 if (!m_displayLink) {
308 qCDebug(lcQpaScreenUpdates) <<
"Creating display link for" <<
this;
309 if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) {
310 qCWarning(lcQpaScreenUpdates) <<
"Failed to create display link for" <<
this;
313 if (
auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) {
314 qCWarning(lcQpaScreenUpdates) <<
"Unexpected display" << displayId <<
"for display link";
315 CVDisplayLinkRelease(m_displayLink);
316 m_displayLink =
nullptr;
319 CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef,
const CVTimeStamp*,
320 const CVTimeStamp*, CVOptionFlags, CVOptionFlags*,
void* displayLinkContext) ->
int {
323 return kCVReturnSuccess;
345 static CFMachPortRef eventTap = []() {
346 CFMachPortRef eventTap = CGEventTapCreateForPid(getpid(), kCGTailAppendEventTap,
347 kCGEventTapOptionListenOnly, NSEventMaskLeftMouseDragged,
348 [](CGEventTapProxy, CGEventType
type, CGEventRef
event,
void *) -> CGEventRef {
349 if (
type == kCGEventTapDisabledByTimeout)
350 qCWarning(lcQpaScreenUpdates) <<
"Event tap disabled due to timeout!";
353 CGEventTapEnable(eventTap,
false);
354 static CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
355 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
357 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
358 [center addObserverForName:NSWindowWillStartLiveResizeNotification
object:nil
queue:nil
359 usingBlock:^(NSNotification *notification) {
360 qCDebug(lcQpaScreenUpdates) <<
"Live resize of" << notification.object
361 <<
"started. Enabling event tap";
362 CGEventTapEnable(eventTap,
true);
364 [center addObserverForName:NSWindowDidEndLiveResizeNotification
object:nil
queue:nil
365 usingBlock:^(NSNotification *notification) {
366 qCDebug(lcQpaScreenUpdates) <<
"Live resize of" << notification.object
367 <<
"ended. Disabling event tap";
368 CGEventTapEnable(eventTap,
false);
375 if (!CVDisplayLinkIsRunning(m_displayLink)) {
376 qCDebug(lcQpaScreenUpdates) <<
"Starting display link for" <<
this;
377 CVDisplayLinkStart(m_displayLink);
387 if (cat.isDebugEnabled())
402#define qDeferredDebug(helper) if (Q_UNLIKELY(helper.debug)) *helper.debug
416 if (!NSThread.isMainThread) {
420 const int pendingUpdates = ++m_pendingUpdates;
423 qDeferredDebug(screenUpdates) <<
"display link callback for screen " << m_displayId;
425 if (
const int framesAheadOfDelivery = pendingUpdates - 1) {
430 qDeferredDebug(screenUpdates) <<
", " << framesAheadOfDelivery <<
" frame(s) ahead";
435 if (!m_displayLinkSource) {
436 m_displayLinkSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
437 dispatch_source_set_event_handler(m_displayLinkSource, ^{
440 dispatch_resume(m_displayLinkSource);
443 dispatch_source_merge_data(m_displayLinkSource, 1);
447 qDeferredDebug(screenUpdates) <<
"gcd event handler on main thread";
449 const int pendingUpdates = m_pendingUpdates;
450 if (pendingUpdates > 1)
451 qDeferredDebug(screenUpdates) <<
", " << (pendingUpdates - 1) <<
" frame(s) behind display link";
453 screenUpdates.flushOutput();
455 bool pauseUpdates =
true;
464 if (!platformWindow->hasPendingUpdateRequest())
471 if (!platformWindow->updatesWithDisplayLink())
475 if (platformWindow->isContentView() && platformWindow->view().inLiveResize) {
478 const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement]
479 != NSViewLayerContentsPlacementScaleAxesIndependently;
480 if (usesMetalLayer && usesNonDefaultContentsPlacement) {
481 static bool deliverDisplayLinkUpdatesDuringLiveResize =
483 if (!deliverDisplayLinkUpdatesDuringLiveResize) {
487 pauseUpdates =
false;
493 platformWindow->deliverUpdateRequest();
496 if (platformWindow->hasPendingUpdateRequest())
497 pauseUpdates =
false;
502 qCDebug(lcQpaScreenUpdates) <<
"Stopping display link for" <<
this;
503 CVDisplayLinkStop(m_displayLink);
507 qCWarning(lcQpaScreenUpdates) <<
"main thread missed" << missedUpdates
508 <<
"update(s) from display link during update request delivery";
515 return m_displayLink && CVDisplayLinkIsRunning(m_displayLink);
534 if (m_rotation == 90)
536 if (m_rotation == 180)
538 if (m_rotation == 270)
546 [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
547 usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
552 if (![nsWindow conformsToProtocol:
@protocol(QNSWindowProtocol)])
564 if (!nativeGeometry.contains(point))
568 if (!
mask.isEmpty() && !
mask.contains(point - nativeGeometry.topLeft()))
574 if (!
window->isTopLevel())
600 auto grabFromDisplay = [](CGDirectDisplayID displayId,
const QRect &grabRect) ->
QPixmap {
601 QCFType<CGImageRef>
image = CGDisplayCreateImageForRect(displayId, grabRect.toCGRect());
602 const QCFType<CGColorSpaceRef> sRGBcolorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
603 if (CGImageGetColorSpace(
image) != sRGBcolorSpace) {
604 qCDebug(lcQpaScreen) <<
"applying color correction for display" << displayId;
605 image = CGImageCreateCopyWithColorSpace(
image, sRGBcolorSpace);
613 qCDebug(lcQpaScreen) <<
"input grab rect" << grabRect;
617 if (!grabRect.isValid())
620 grabRect.translate(-
geometry().topLeft());
621 return grabFromDisplay(displayId(), grabRect);
625 NSView *nsView =
reinterpret_cast<NSView*
>(
view);
626 NSPoint windowPoint = [nsView convertPoint:NSMakePoint(0, 0) toView:nil];
627 NSRect screenRect = [nsView.window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)];
629 QSize size = QRectF::fromCGRect(NSRectToCGRect(nsView.bounds)).toRect().size();
631 if (!grabRect.isValid())
632 grabRect = windowRect;
634 grabRect.translate(windowRect.topLeft());
637 const int maxDisplays = 128;
638 CGDirectDisplayID displays[maxDisplays];
639 CGDisplayCount displayCount;
640 CGRect cgRect = grabRect.isValid() ? grabRect.toCGRect() : CGRectInfinite;
641 const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount);
642 if (err || displayCount == 0)
645 qCDebug(lcQpaScreen) <<
"final grab rect" << grabRect <<
"from" << displayCount <<
"displays";
648 QVector<QPixmap> pixmaps;
649 QVector<QRect> destinations;
650 for (
uint i = 0;
i < displayCount; ++
i) {
652 const QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(
display)).toRect();
654 if (grabBounds.isNull()) {
655 destinations.append(
QRect());
659 const QRect displayLocalGrabBounds =
QRect(
QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size());
661 qCDebug(lcQpaScreen) <<
"grab display" <<
i <<
"global" << grabBounds <<
"local" << displayLocalGrabBounds;
662 QPixmap displayPixmap = grabFromDisplay(
display, displayLocalGrabBounds);
664 if (displayCount == 1)
665 return displayPixmap;
667 qCDebug(lcQpaScreen) <<
"grab sub-image size" << displayPixmap.size() <<
"devicePixelRatio" << displayPixmap.devicePixelRatio();
668 pixmaps.append(displayPixmap);
669 const QRect destBounds =
QRect(
QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size());
670 destinations.append(destBounds);
675 for (
uint i = 0;
i < displayCount; ++
i)
679 qCDebug(lcQpaScreen) <<
"Create grap pixmap" << grabRect.size() <<
"at devicePixelRatio" <<
dpr;
681 windowPixmap.setDevicePixelRatio(
dpr);
684 for (
uint i = 0;
i < displayCount; ++
i)
690bool QCocoaScreen::isOnline()
const
697 int isOnline = CGDisplayIsOnline(m_displayId);
698 static const int kCGDisplayIsDisconnected = 0xffffffff;
699 return isOnline != kCGDisplayIsDisconnected && isOnline;
705bool QCocoaScreen::isMirroring()
const
710 return CGDisplayMirrorsDisplay(m_displayId);
726 QList<QPlatformScreen*> siblings;
737 auto displayId = nsScreen.qt_displayId;
738 auto *cocoaScreen =
get(displayId);
740 qCWarning(lcQpaScreen) <<
"Failed to map" << nsScreen
741 <<
"to QCocoaScreen. Doing last minute update.";
743 cocoaScreen =
get(displayId);
745 qCWarning(lcQpaScreen) <<
"Last minute update failed!";
754 if (cocoaScreen->m_displayId == displayId)
765 if (!platformScreen->isOnline())
768 auto displayId = platformScreen->displayId();
769 QCFType<CFUUIDRef> candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId));
772 if (candidateUuid == uuid)
773 return platformScreen;
781 for (NSScreen *
screen in NSScreen.screens) {
782 if (
screen.qt_displayId == displayId)
820#ifndef QT_NO_DEBUG_STREAM
829 if (CGDisplayIsAsleep(
screen->displayId()))
830 debug <<
", Sleeping";
831 if (
auto mirroring = CGDisplayMirrorsDisplay(
screen->displayId()))
832 debug <<
", mirroring=" << mirroring;
834 debug <<
", Offline";
840 if (
auto nativeScreen =
screen->nativeScreen())
841 debug <<
", " << nativeScreen;
850#include "qcocoascreen.moc"
852@implementation NSScreen (QtExtras)
854- (CGDirectDisplayID)qt_displayId
856 return [
self.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
T fetchAndStoreRelaxed(T newValue) noexcept
QRect availableGeometry() const override
Reimplement in subclass to return the pixel geometry of the available space This normally is the desk...
static CGPoint mapToNative(const QPointF &pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
static NSScreen * nativeScreenForDisplayId(CGDirectDisplayID displayId)
QPixmap grabWindow(WId window, int x, int y, int width, int height) const override
static QCocoaScreen * get(NSScreen *nsScreen)
void deliverUpdateRequests()
bool isRunningDisplayLink() const
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.
static QPointF mapFromNative(CGPoint pos, QCocoaScreen *screen=QCocoaScreen::primaryScreen())
Qt::ScreenOrientation orientation() const override
Reimplement this function in subclass to return the current orientation of the screen,...
QRect geometry() const override
Reimplement in subclass to return the pixel geometry 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.
NSScreen * nativeScreen() const
bool isValid() const noexcept
Returns true if the color space is valid.
static QColorSpace fromIccProfile(const QByteArray &iccProfile)
Creates a QColorSpace from ICC profile iccProfile.
static QWindowList allWindows()
Returns a list of all the windows in the application.
QScreen * primaryScreen
the primary (or default) screen of the application.
static QList< QScreen * > screens()
Returns a list of all the screens associated with the windowing system the application is connected t...
qsizetype size() const noexcept
const_reference at(qsizetype i) const noexcept
The QPainter class performs low-level painting on widgets and other paint devices.
void drawPixmap(const QRectF &targetRect, const QPixmap &pixmap, const QRectF &sourceRect)
Draws the rectangular portion source of the given pixmap into the given target in the paint device.
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
static QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags=Qt::AutoColor)
Converts the given image to a pixmap using the specified flags to control the conversion.
\inmodule QtCore\reentrant
\inmodule QtCore\reentrant
\inmodule QtCore\reentrant
\inmodule QtCore\reentrant
QRect intersected(const QRect &other) const noexcept
The QRegion class specifies a clip region for a painter.
The QScreen class is used to query screen properties. \inmodule QtGui.
qreal devicePixelRatio
the screen's ratio between physical pixels and device-independent pixels
QRect geometry
the screen's geometry in pixels
QString name
a user presentable string representing the screen
QPlatformScreen * handle() const
Get the platform screen handle.
\macro QT_RESTRICTED_CAST_FROM_ASCII
SurfaceType
The SurfaceType enum describes what type of surface this is.
static void handleScreenGeometryChange(QScreen *screen, const QRect &newGeometry, const QRect &newAvailableGeometry)
static void handlePrimaryScreenChanged(QPlatformScreen *newPrimary)
Should be called whenever the primary screen changes.
static void handleScreenAdded(QPlatformScreen *screen, bool isPrimary=false)
Should be called by the implementation whenever a new screen is added.
static void handleScreenRemoved(QPlatformScreen *screen)
Should be called by the implementation whenever a screen is removed.
static void handleScreenOrientationChange(QScreen *screen, Qt::ScreenOrientation newOrientation)
static void handleScreenRefreshRateChange(QScreen *screen, qreal newRefreshRate)
struct wl_display * display
@ ReconfiguredWithFlagsMissing
T toNativePixels(const T &value, const C *context)
T toNativeLocalPosition(const T &value, const C *context)
Combined button and popup list for selecting options.
@ InvertedLandscapeOrientation
@ InvertedPortraitOrientation
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference)
#define qDeferredDebug(helper)
static QString displayName(CGDirectDisplayID displayID)
QDebug operator<<(QDebug debug, const QCocoaScreen *screen)
QImage qt_mac_toQImage(CGImageRef image)
#define Q_LOGGING_CATEGORY(name,...)
#define qCInfo(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMax(const T &a, const T &b)
GLuint64 GLenum void * handle
GLint GLint GLint GLint GLint x
[0]
GLfloat GLfloat GLfloat w
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLenum GLsizei count
GLint GLint GLint GLint GLint GLint GLint GLbitfield mask
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
static QT_BEGIN_NAMESPACE qreal dpr(const QWindow *w)
#define Q_ASSERT_X(cond, x, msg)
static void allKeys(HKEY parentHandle, const QString &rSubKey, NameSet *result, REGSAM access=0)
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
DeferredDebugHelper(const QLoggingCategory &cat)