Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qcocoascreen.mm
Go to the documentation of this file.
1// Copyright (C) 2017 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
4#include <AppKit/AppKit.h>
5
6#include "qcocoascreen.h"
7
8#include "qcocoawindow.h"
9#include "qcocoahelpers.h"
10#include "qcocoaintegration.h"
11
12#include <QtCore/qcoreapplication.h>
13#include <QtGui/private/qcoregraphics_p.h>
14
15#include <IOKit/graphics/IOGraphicsLib.h>
16
17#include <QtGui/private/qwindow_p.h>
18
19#include <QtCore/private/qcore_mac_p.h>
20#include <QtCore/private/qeventdispatcher_cf_p.h>
21
23
24namespace CoreGraphics {
28 Moved = kCGDisplayMovedFlag,
29 SetMain = kCGDisplaySetMainFlag,
30 SetMode = kCGDisplaySetModeFlag,
31 Added = kCGDisplayAddFlag,
32 Removed = kCGDisplayRemoveFlag,
33 Enabled = kCGDisplayEnabledFlag,
34 Disabled = kCGDisplayDisabledFlag,
35 Mirrored = kCGDisplayMirrorFlag,
36 UnMirrored = kCGDisplayUnMirrorFlag,
37 DesktopShapeChanged = kCGDisplayDesktopShapeChangedFlag
38 };
40}
41
42QMacNotificationObserver QCocoaScreen::s_screenParameterObserver;
43CGDisplayReconfigurationCallBack QCocoaScreen::s_displayReconfigurationCallBack = nullptr;
44
45void QCocoaScreen::initializeScreens()
46{
47 updateScreens();
48
49 s_displayReconfigurationCallBack = [](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
50 Q_UNUSED(userInfo);
51
52 const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag;
53 qCDebug(lcQpaScreen).verbosity(0) << "Display" << displayId
54 << (beforeReconfigure ? "beginning" : "finished") << "reconfigure"
56
57 if (!beforeReconfigure)
58 updateScreens();
59 };
60 CGDisplayRegisterReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
61
62 s_screenParameterObserver = QMacNotificationObserver(NSApplication.sharedApplication,
63 NSApplicationDidChangeScreenParametersNotification, [&]() {
64 qCDebug(lcQpaScreen) << "Received screen parameter change notification";
65 updateScreens();
66 });
67}
68
69/*
70 Update the list of available QScreens, and the properties of existing screens.
71
72 At this point we rely on the NSScreen.screens to be up to date.
73*/
74void QCocoaScreen::updateScreens()
75{
76 // Adding, updating, or removing a screen below might trigger
77 // Qt or the application to move a window to a different screen,
78 // recursing back here via QCocoaWindow::windowDidChangeScreen.
79 // The update code is not re-entrant, so bail out if we end up
80 // in this situation. The screens will stabilize eventually.
81 static bool updatingScreens = false;
82 if (updatingScreens) {
83 qCInfo(lcQpaScreen) << "Skipping screen update, already updating";
84 return;
85 }
86 QBoolBlocker recursionGuard(updatingScreens);
87
88 uint32_t displayCount = 0;
89 if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess)
90 qFatal("Failed to get number of online displays");
91
92 QVector<CGDirectDisplayID> onlineDisplays(displayCount);
93 if (CGGetOnlineDisplayList(displayCount, onlineDisplays.data(), &displayCount) != kCGErrorSuccess)
94 qFatal("Failed to get online displays");
95
96 qCInfo(lcQpaScreen) << "Updating screens with" << displayCount
97 << "online displays:" << onlineDisplays;
98
99 // TODO: Verify whether we can always assume the main display is first
100 int mainDisplayIndex = onlineDisplays.indexOf(CGMainDisplayID());
101 if (mainDisplayIndex < 0) {
102 qCWarning(lcQpaScreen) << "Main display not in list of online displays!";
103 } else if (mainDisplayIndex > 0) {
104 qCWarning(lcQpaScreen) << "Main display not first display, making sure it is";
105 onlineDisplays.move(mainDisplayIndex, 0);
106 }
107
108 for (CGDirectDisplayID displayId : onlineDisplays) {
109 Q_ASSERT(CGDisplayIsOnline(displayId));
110
111 if (CGDisplayMirrorsDisplay(displayId))
112 continue;
113
114 // A single physical screen can map to multiple displays IDs,
115 // depending on which GPU is in use or which physical port the
116 // screen is connected to. By mapping the display ID to a UUID,
117 // which are shared between displays that target the same screen,
118 // we can pick an existing QScreen to update instead of needlessly
119 // adding and removing QScreens.
120 QCFType<CFUUIDRef> uuid = CGDisplayCreateUUIDFromDisplayID(displayId);
121 Q_ASSERT(uuid);
122
123 if (QCocoaScreen *existingScreen = QCocoaScreen::get(uuid)) {
124 existingScreen->update(displayId);
125 qCInfo(lcQpaScreen) << "Updated" << existingScreen;
126 if (CGDisplayIsMain(displayId) && existingScreen != qGuiApp->primaryScreen()->handle()) {
127 qCInfo(lcQpaScreen) << "Primary screen changed to" << existingScreen;
129 }
130 } else {
131 QCocoaScreen::add(displayId);
132 }
133 }
134
135 for (QScreen *screen : QGuiApplication::screens()) {
136 QCocoaScreen *platformScreen = static_cast<QCocoaScreen*>(screen->handle());
137 if (!platformScreen->isOnline() || platformScreen->isMirroring())
138 platformScreen->remove();
139 }
140}
141
142void QCocoaScreen::add(CGDirectDisplayID displayId)
143{
144 const bool isPrimary = CGDisplayIsMain(displayId);
145 QCocoaScreen *cocoaScreen = new QCocoaScreen(displayId);
146 qCInfo(lcQpaScreen) << "Adding" << cocoaScreen
147 << (isPrimary ? "as new primary screen" : "");
148 QWindowSystemInterface::handleScreenAdded(cocoaScreen, isPrimary);
149}
150
151QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId)
152 : QPlatformScreen(), m_displayId(displayId)
153{
154 update(m_displayId);
155 m_cursor = new QCocoaCursor;
156}
157
158void QCocoaScreen::cleanupScreens()
159{
160 // Remove screens in reverse order to avoid crash in case of multiple screens
161 for (QScreen *screen : backwards(QGuiApplication::screens()))
162 static_cast<QCocoaScreen*>(screen->handle())->remove();
163
164 Q_ASSERT(s_displayReconfigurationCallBack);
165 CGDisplayRemoveReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
166 s_displayReconfigurationCallBack = nullptr;
167
168 s_screenParameterObserver.remove();
169}
170
171void QCocoaScreen::remove()
172{
173 // This may result in the application responding to QGuiApplication::screenRemoved
174 // by moving the window to another screen, either by setGeometry, or by setScreen.
175 // If the window isn't moved by the application, Qt will as a fallback move it to
176 // the primary screen via setScreen. Due to the way setScreen works, this won't
177 // actually recreate the window on the new screen, it will just assign the new
178 // QScreen to the window. The associated NSWindow will have an NSScreen determined
179 // by AppKit. AppKit will then move the window to another screen by changing the
180 // geometry, and we will get a callback in QCocoaWindow::windowDidMove and then
181 // QCocoaWindow::windowDidChangeScreen. At that point the window will appear to have
182 // already changed its screen, but that's only true if comparing the Qt screens,
183 // not when comparing the NSScreens.
184 qCInfo(lcQpaScreen) << "Removing " << this;
186}
187
189{
190 Q_ASSERT_X(!screen(), "QCocoaScreen", "QScreen should be deleted first");
191
192 delete m_cursor;
193
194 CVDisplayLinkRelease(m_displayLink);
195 if (m_displayLinkSource)
196 dispatch_release(m_displayLinkSource);
197}
198
199static QString displayName(CGDirectDisplayID displayID)
200{
201 QIOType<io_iterator_t> iterator;
202 if (IOServiceGetMatchingServices(kIOMasterPortDefault,
203 IOServiceMatching("IODisplayConnect"), &iterator))
204 return QString();
205
206 QIOType<io_service_t> display;
207 while ((display = IOIteratorNext(iterator)) != 0)
208 {
209 NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary(
210 display, kIODisplayOnlyPreferredName) autorelease];
211
212 if ([[info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID))
213 continue;
214
215 if ([[info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID))
216 continue;
217
218 if ([[info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID))
219 continue;
220
221 NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName];
222 if (![localizedNames count])
223 break; // Correct screen, but no name in dictionary
224
225 return QString::fromNSString([localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]);
226 }
227
228 return QString();
229}
230
231void QCocoaScreen::update(CGDirectDisplayID displayId)
232{
233 if (displayId != m_displayId) {
234 qCDebug(lcQpaScreen) << "Reconnecting" << this << "as display" << displayId;
235 m_displayId = displayId;
236 }
237
238 Q_ASSERT(isOnline());
239
240 // Some properties are only available via NSScreen
241 NSScreen *nsScreen = nativeScreen();
242 if (!nsScreen) {
243 qCDebug(lcQpaScreen) << "Corresponding NSScreen not yet available. Deferring update";
244 return;
245 }
246
247 const QRect previousGeometry = m_geometry;
248 const QRect previousAvailableGeometry = m_availableGeometry;
249 const qreal previousRefreshRate = m_refreshRate;
250
251 // The reference screen for the geometry is always the primary screen
252 QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
253 m_geometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.frame), primaryScreenGeometry).toRect();
254 m_availableGeometry = qt_mac_flip(QRectF::fromCGRect(nsScreen.visibleFrame), primaryScreenGeometry).toRect();
255
256 m_devicePixelRatio = nsScreen.backingScaleFactor;
257
258 m_format = QImage::Format_RGB32;
259 m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
260 m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData));
261 if (!m_colorSpace.isValid()) {
262 qCWarning(lcQpaScreen) << "Failed to parse ICC profile for" << nsScreen.colorSpace
263 << "with ICC data" << nsScreen.colorSpace.ICCProfileData
264 << "- Falling back to sRGB";
265 m_colorSpace = QColorSpace::SRgb;
266 }
267
268 CGSize size = CGDisplayScreenSize(m_displayId);
269 m_physicalSize = QSizeF(size.width, size.height);
270
271 QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
272 float refresh = CGDisplayModeGetRefreshRate(displayMode);
273 m_refreshRate = refresh > 0 ? refresh : 60.0;
274
275 if (@available(macOS 10.15, *))
276 m_name = QString::fromNSString(nsScreen.localizedName);
277 else
278 m_name = displayName(m_displayId);
279
280 const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
281
282 if (didChangeGeometry)
284 if (m_refreshRate != previousRefreshRate)
286}
287
288// ----------------------- Display link -----------------------
289
290Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg);
291
293{
294 Q_ASSERT(m_displayId);
295
296 if (!isOnline()) {
297 qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request";
298 return false;
299 }
300
301 if (!m_displayLink) {
302 qCDebug(lcQpaScreenUpdates) << "Creating display link for" << this;
303 if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) {
304 qCWarning(lcQpaScreenUpdates) << "Failed to create display link for" << this;
305 return false;
306 }
307 if (auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) {
308 qCWarning(lcQpaScreenUpdates) << "Unexpected display" << displayId << "for display link";
309 CVDisplayLinkRelease(m_displayLink);
310 m_displayLink = nullptr;
311 return false;
312 }
313 CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*,
314 const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int {
315 // FIXME: It would be nice if update requests would include timing info
316 static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests();
317 return kCVReturnSuccess;
318 }, this);
319
320 // During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop
321 // in event-tracking mode, dequeuing only the mouse drag events needed to update the window's
322 // frame. It will repeatedly spin this loop until no longer receiving any mouse drag events,
323 // and will then update the frame (effectively coalescing/compressing the events). Unfortunately
324 // the events are pulled out using -[NSApplication nextEventMatchingEventMask:untilDate:inMode:dequeue:]
325 // which internally uses CFRunLoopRunSpecific, so the event loop will also process GCD queues and other
326 // runloop sources that have been added to the tracking mode. This includes the GCD display-link
327 // source that we use to marshal the display-link callback over to the main thread. If the
328 // subsequent delivery of the update-request on the main thread stalls due to inefficient
329 // user code, the NSEventThread will have had time to deliver additional mouse drag events,
330 // and the logic in -[NSWindow _resizeWithEvent:] will keep on compressing events and never
331 // get to the point of actually updating the window frame, making it seem like the window
332 // is stuck in its original size. Only when the user stops moving their mouse, and the event
333 // queue is completely drained of drag events, will the window frame be updated.
334
335 // By keeping an event tap listening for drag events, registered as a version 1 runloop source,
336 // we prevent the GCD source from being prioritized, giving the resize logic enough time
337 // to finish coalescing the events. This is incidental, but conveniently gives us the behavior
338 // we are looking for, interleaving display-link updates and resize events.
339 static CFMachPortRef eventTap = []() {
340 CFMachPortRef eventTap = CGEventTapCreateForPid(getpid(), kCGTailAppendEventTap,
341 kCGEventTapOptionListenOnly, NSEventMaskLeftMouseDragged,
342 [](CGEventTapProxy, CGEventType type, CGEventRef event, void *) -> CGEventRef {
343 if (type == kCGEventTapDisabledByTimeout)
344 qCWarning(lcQpaScreenUpdates) << "Event tap disabled due to timeout!";
345 return event; // Listen only tap, so what we return doesn't really matter
346 }, nullptr);
347 CGEventTapEnable(eventTap, false); // Event taps are normally enabled when created
348 static CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
349 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
350
351 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
352 [center addObserverForName:NSWindowWillStartLiveResizeNotification object:nil queue:nil
353 usingBlock:^(NSNotification *notification) {
354 qCDebug(lcQpaScreenUpdates) << "Live resize of" << notification.object
355 << "started. Enabling event tap";
356 CGEventTapEnable(eventTap, true);
357 }];
358 [center addObserverForName:NSWindowDidEndLiveResizeNotification object:nil queue:nil
359 usingBlock:^(NSNotification *notification) {
360 qCDebug(lcQpaScreenUpdates) << "Live resize of" << notification.object
361 << "ended. Disabling event tap";
362 CGEventTapEnable(eventTap, false);
363 }];
364 return eventTap;
365 }();
366 Q_UNUSED(eventTap);
367 }
368
369 if (!CVDisplayLinkIsRunning(m_displayLink)) {
370 qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this;
371 CVDisplayLinkStart(m_displayLink);
372 }
373
374 return true;
375}
376
377// Helper to allow building up debug output in multiple steps
379{
381 if (cat.isDebugEnabled())
382 debug = new QDebug(QMessageLogger().debug(cat).nospace());
383 }
385 flushOutput();
386 }
387 void flushOutput() {
388 if (debug) {
389 delete debug;
390 debug = nullptr;
391 }
392 }
393 QDebug *debug = nullptr;
394};
395
396#define qDeferredDebug(helper) if (Q_UNLIKELY(helper.debug)) *helper.debug
397
399{
400 if (!isOnline())
401 return;
402
404
405 // The CVDisplayLink callback is a notification that it's a good time to produce a new frame.
406 // Since the callback is delivered on a separate thread we have to marshal it over to the
407 // main thread, as Qt requires update requests to be delivered there. This needs to happen
408 // asynchronously, as otherwise we may end up deadlocking if the main thread calls back
409 // into any of the CVDisplayLink APIs.
410 if (!NSThread.isMainThread) {
411 // We're explicitly not using the data of the GCD source to track the pending updates,
412 // as the data isn't reset to 0 until after the event handler, and also doesn't update
413 // during the event handler, both of which we need to track late frames.
414 const int pendingUpdates = ++m_pendingUpdates;
415
416 DeferredDebugHelper screenUpdates(lcQpaScreenUpdates());
417 qDeferredDebug(screenUpdates) << "display link callback for screen " << m_displayId;
418
419 if (const int framesAheadOfDelivery = pendingUpdates - 1) {
420 // If we have more than one update pending it means that a previous display link callback
421 // has not been fully processed on the main thread, either because GCD hasn't delivered
422 // it on the main thread yet, because the processing of the update request is taking
423 // too long, or because the update request was deferred due to window live resizing.
424 qDeferredDebug(screenUpdates) << ", " << framesAheadOfDelivery << " frame(s) ahead";
425 }
426
427 qDeferredDebug(screenUpdates) << "; signaling dispatch source";
428
429 if (!m_displayLinkSource) {
430 m_displayLinkSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
431 dispatch_source_set_event_handler(m_displayLinkSource, ^{
433 });
434 dispatch_resume(m_displayLinkSource);
435 }
436
437 dispatch_source_merge_data(m_displayLinkSource, 1);
438
439 } else {
440 DeferredDebugHelper screenUpdates(lcQpaScreenUpdates());
441 qDeferredDebug(screenUpdates) << "gcd event handler on main thread";
442
443 const int pendingUpdates = m_pendingUpdates;
444 if (pendingUpdates > 1)
445 qDeferredDebug(screenUpdates) << ", " << (pendingUpdates - 1) << " frame(s) behind display link";
446
447 screenUpdates.flushOutput();
448
449 bool pauseUpdates = true;
450
452 for (int i = 0; i < windows.size(); ++i) {
454 auto *platformWindow = static_cast<QCocoaWindow*>(window->handle());
455 if (!platformWindow)
456 continue;
457
458 if (!platformWindow->hasPendingUpdateRequest())
459 continue;
460
461 if (window->screen() != screen())
462 continue;
463
464 // Skip windows that are not doing update requests via display link
465 if (!platformWindow->updatesWithDisplayLink())
466 continue;
467
468 // QTBUG-107198: Skip updates in a live resize for a better resize experience.
469 if (platformWindow->isContentView() && platformWindow->view().inLiveResize) {
470 const QSurface::SurfaceType surfaceType = window->surfaceType();
471 const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
472 const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement]
473 != NSViewLayerContentsPlacementScaleAxesIndependently;
474 if (usesMetalLayer && usesNonDefaultContentsPlacement) {
475 static bool deliverDisplayLinkUpdatesDuringLiveResize =
476 qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE");
477 if (!deliverDisplayLinkUpdatesDuringLiveResize) {
478 // Must keep the link running, we do not know what the event
479 // handlers for UpdateRequest (which is not sent now) would do,
480 // would they trigger a new requestUpdate() or not.
481 pauseUpdates = false;
482 continue;
483 }
484 }
485 }
486
487 platformWindow->deliverUpdateRequest();
488
489 // Another update request was triggered, keep the display link running
490 if (platformWindow->hasPendingUpdateRequest())
491 pauseUpdates = false;
492 }
493
494 if (pauseUpdates) {
495 // Pause the display link if there are no pending update requests
496 qCDebug(lcQpaScreenUpdates) << "Stopping display link for" << this;
497 CVDisplayLinkStop(m_displayLink);
498 }
499
500 if (const int missedUpdates = m_pendingUpdates.fetchAndStoreRelaxed(0) - pendingUpdates) {
501 qCWarning(lcQpaScreenUpdates) << "main thread missed" << missedUpdates
502 << "update(s) from display link during update request delivery";
503 }
504 }
505}
506
508{
509 return m_displayLink && CVDisplayLinkIsRunning(m_displayLink);
510}
511
512// -----------------------------------------------------------
513
515{
518 // Every OSX machine has RGB pixels unless a peculiar or rotated non-Apple screen is attached
520 }
521 return type;
522}
523
525{
526 NSPoint screenPoint = mapToNative(point);
527
528 // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint:
529 // belowWindowWithWindowNumber] may return windows that are not interesting
530 // to Qt. The search iterates until a suitable window or no window is found.
531 NSInteger topWindowNumber = 0;
532 QWindow *window = nullptr;
533 do {
534 // Get the top-most window, below any previously rejected window.
535 topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint
536 belowWindowWithWindowNumber:topWindowNumber];
537
538 // Continue the search if the window does not belong to this process.
539 NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber];
540 if (!nsWindow)
541 continue;
542
543 // Continue the search if the window does not belong to Qt.
544 if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
545 continue;
546
547 QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
548 if (!cocoaWindow)
549 continue;
550 window = cocoaWindow->window();
551
552 // Continue the search if the window is not a top-level window.
553 if (!window->isTopLevel())
554 continue;
555
556 // Stop searching. The current window is the correct window.
557 break;
558 } while (topWindowNumber > 0);
559
560 return window;
561}
562
569QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height) const
570{
571 /*
572 Grab the grabRect section of the specified display into a pixmap that has
573 sRGB color spec. Once Qt supports a fully color-managed flow and conversions
574 that don't lose the colorspec information, we would want the image to maintain
575 the color spec of the display from which it was grabbed. Ultimately, rendering
576 the returned pixmap on the same display from which it was grabbed should produce
577 identical visual results.
578 */
579 auto grabFromDisplay = [](CGDirectDisplayID displayId, const QRect &grabRect) -> QPixmap {
580 QCFType<CGImageRef> image = CGDisplayCreateImageForRect(displayId, grabRect.toCGRect());
581 const QCFType<CGColorSpaceRef> sRGBcolorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
582 if (CGImageGetColorSpace(image) != sRGBcolorSpace) {
583 qCDebug(lcQpaScreen) << "applying color correction for display" << displayId;
584 image = CGImageCreateCopyWithColorSpace(image, sRGBcolorSpace);
585 }
587 pixmap.setDevicePixelRatio(nativeScreenForDisplayId(displayId).backingScaleFactor);
588 return pixmap;
589 };
590
591 QRect grabRect = QRect(x, y, width, height);
592 qCDebug(lcQpaScreen) << "input grab rect" << grabRect;
593
594 if (!view) {
595 // coordinates are relative to the screen
596 if (!grabRect.isValid()) // entire screen
597 grabRect = QRect(QPoint(0, 0), geometry().size());
598 else
599 grabRect.translate(-geometry().topLeft());
600 return grabFromDisplay(displayId(), grabRect);
601 }
602
603 // grab the window; grab rect in window coordinates might span multiple screens
604 NSView *nsView = reinterpret_cast<NSView*>(view);
605 NSPoint windowPoint = [nsView convertPoint:NSMakePoint(0, 0) toView:nil];
606 NSRect screenRect = [nsView.window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)];
607 QPoint position = mapFromNative(screenRect.origin).toPoint();
608 QSize size = QRectF::fromCGRect(NSRectToCGRect(nsView.bounds)).toRect().size();
609 QRect windowRect = QRect(position, size);
610 if (!grabRect.isValid())
611 grabRect = windowRect;
612 else
613 grabRect.translate(windowRect.topLeft());
614
615 // Find which displays to grab from
616 const int maxDisplays = 128;
617 CGDirectDisplayID displays[maxDisplays];
618 CGDisplayCount displayCount;
619 CGRect cgRect = grabRect.isValid() ? grabRect.toCGRect() : CGRectInfinite;
620 const CGDisplayErr err = CGGetDisplaysWithRect(cgRect, maxDisplays, displays, &displayCount);
621 if (err || displayCount == 0)
622 return QPixmap();
623
624 qCDebug(lcQpaScreen) << "final grab rect" << grabRect << "from" << displayCount << "displays";
625
626 // Grab images from each display
627 QVector<QPixmap> pixmaps;
628 QVector<QRect> destinations;
629 for (uint i = 0; i < displayCount; ++i) {
630 auto display = displays[i];
631 const QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect();
632 const QRect grabBounds = displayBounds.intersected(grabRect);
633 if (grabBounds.isNull()) {
634 destinations.append(QRect());
635 pixmaps.append(QPixmap());
636 continue;
637 }
638 const QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size());
639
640 qCDebug(lcQpaScreen) << "grab display" << i << "global" << grabBounds << "local" << displayLocalGrabBounds;
641 QPixmap displayPixmap = grabFromDisplay(display, displayLocalGrabBounds);
642 // Fast path for when grabbing from a single screen only
643 if (displayCount == 1)
644 return displayPixmap;
645
646 qCDebug(lcQpaScreen) << "grab sub-image size" << displayPixmap.size() << "devicePixelRatio" << displayPixmap.devicePixelRatio();
647 pixmaps.append(displayPixmap);
648 const QRect destBounds = QRect(QPoint(grabBounds.topLeft() - grabRect.topLeft()), grabBounds.size());
649 destinations.append(destBounds);
650 }
651
652 // Determine the highest dpr, which becomes the dpr for the returned pixmap.
653 qreal dpr = 1.0;
654 for (uint i = 0; i < displayCount; ++i)
655 dpr = qMax(dpr, pixmaps.at(i).devicePixelRatio());
656
657 // Allocate target pixmap and draw each screen's content
658 qCDebug(lcQpaScreen) << "Create grap pixmap" << grabRect.size() << "at devicePixelRatio" << dpr;
659 QPixmap windowPixmap(grabRect.size() * dpr);
660 windowPixmap.setDevicePixelRatio(dpr);
661 windowPixmap.fill(Qt::transparent);
662 QPainter painter(&windowPixmap);
663 for (uint i = 0; i < displayCount; ++i)
664 painter.drawPixmap(destinations.at(i), pixmaps.at(i));
665
666 return windowPixmap;
667}
668
669bool QCocoaScreen::isOnline() const
670{
671 // When a display is disconnected CGDisplayIsOnline and other CGDisplay
672 // functions that take a displayId will not return false, but will start
673 // returning -1 to signal that the displayId is invalid. Some functions
674 // will also assert or even crash in this case, so it's important that
675 // we double check if a display is online before calling other functions.
676 int isOnline = CGDisplayIsOnline(m_displayId);
677 static const int kCGDisplayIsDisconnected = 0xffffffff;
678 return isOnline != kCGDisplayIsDisconnected && isOnline;
679}
680
681/*
682 Returns true if a screen is mirroring another screen
683*/
684bool QCocoaScreen::isMirroring() const
685{
686 if (!isOnline())
687 return false;
688
689 return CGDisplayMirrorsDisplay(m_displayId);
690}
691
696{
697 // Note: The primary screen that Qt knows about may not match the current CGMainDisplayID()
698 // if macOS has not yet been able to inform us that the main display has changed, but we
699 // will update the primary screen accordingly once the reconfiguration callback comes in.
700 return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
701}
702
704{
706
707 // Screens on macOS are always part of the same virtual desktop
709 siblings << screen->handle();
710
711 return siblings;
712}
713
714QCocoaScreen *QCocoaScreen::get(NSScreen *nsScreen)
715{
716 auto displayId = nsScreen.qt_displayId;
717 auto *cocoaScreen = get(displayId);
718 if (!cocoaScreen) {
719 qCWarning(lcQpaScreen) << "Failed to map" << nsScreen
720 << "to QCocoaScreen. Doing last minute update.";
721 updateScreens();
722 cocoaScreen = get(displayId);
723 if (!cocoaScreen)
724 qCWarning(lcQpaScreen) << "Last minute update failed!";
725 }
726 return cocoaScreen;
727}
728
729QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId)
730{
732 QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen*>(screen->handle());
733 if (cocoaScreen->m_displayId == displayId)
734 return cocoaScreen;
735 }
736
737 return nullptr;
738}
739
741{
743 auto *platformScreen = static_cast<QCocoaScreen*>(screen->handle());
744 if (!platformScreen->isOnline())
745 continue;
746
747 auto displayId = platformScreen->displayId();
748 QCFType<CFUUIDRef> candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId));
749 Q_ASSERT(candidateUuid);
750
751 if (candidateUuid == uuid)
752 return platformScreen;
753 }
754
755 return nullptr;
756}
757
758NSScreen *QCocoaScreen::nativeScreenForDisplayId(CGDirectDisplayID displayId)
759{
760 for (NSScreen *screen in NSScreen.screens) {
761 if (screen.qt_displayId == displayId)
762 return screen;
763 }
764 return nil;
765}
766
768{
769 if (!m_displayId)
770 return nil; // The display has been disconnected
771
772 return nativeScreenForDisplayId(m_displayId);
773}
774
776{
778 return qt_mac_flip(pos, screen->geometry()).toCGPoint();
779}
780
782{
784 return qt_mac_flip(rect, screen->geometry()).toCGRect();
785}
786
788{
790 return qt_mac_flip(QPointF::fromCGPoint(pos), screen->geometry());
791}
792
794{
796 return qt_mac_flip(QRectF::fromCGRect(rect), screen->geometry());
797}
798
799#ifndef QT_NO_DEBUG_STREAM
801{
802 QDebugStateSaver saver(debug);
803 debug.nospace();
804 debug << "QCocoaScreen(" << (const void *)screen;
805 if (screen) {
806 debug << ", " << screen->name();
807 if (screen->isOnline()) {
808 if (CGDisplayIsAsleep(screen->displayId()))
809 debug << ", Sleeping";
810 if (auto mirroring = CGDisplayMirrorsDisplay(screen->displayId()))
811 debug << ", mirroring=" << mirroring;
812 } else {
813 debug << ", Offline";
814 }
815 debug << ", " << screen->geometry();
816 debug << ", dpr=" << screen->devicePixelRatio();
817 debug << ", displayId=" << screen->displayId();
818
819 if (auto nativeScreen = screen->nativeScreen())
820 debug << ", " << nativeScreen;
821 }
822 debug << ')';
823 return debug;
824}
825#endif // !QT_NO_DEBUG_STREAM
826
828
829#include "qcocoascreen.moc"
830
831@implementation NSScreen (QtExtras)
832
833- (CGDirectDisplayID)qt_displayId
834{
835 return [self.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
836}
837
838@end
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
bool requestUpdate()
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())
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.
\inmodule QtCore
\inmodule QtCore
\macro qGuiApp
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...
@ Format_RGB32
Definition qimage.h:46
Definition qlist.h:74
qsizetype size() const noexcept
Definition qlist.h:386
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
void append(parameter_type t)
Definition qlist.h:441
\inmodule QtCore
bool isDebugEnabled() const
Returns true if debug messages should be shown for this category; false otherwise.
\inmodule QtCore
Definition qlogging.h:68
The QPainter class performs low-level painting on widgets and other paint devices.
Definition qpainter.h:46
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...
Definition qpixmap.h:27
QSize size() const
Returns the size of the pixmap.
Definition qpixmap.cpp:497
void setDevicePixelRatio(qreal scaleFactor)
Sets the device pixel ratio for the pixmap.
Definition qpixmap.cpp:608
void fill(const QColor &fillColor=Qt::white)
Fills the pixmap with the given color.
Definition qpixmap.cpp:854
qreal devicePixelRatio() const
Returns the device pixel ratio for the pixmap.
Definition qpixmap.cpp:580
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.
Definition qpixmap.cpp:1445
The QPlatformScreen class provides an abstraction for visual displays.
QScreen * screen() const
QWindowList windows() const
Return all windows residing on this screen.
virtual SubpixelAntialiasingType subpixelAntialiasingTypeHint() const
Returns a hint about this screen's subpixel layout structure.
QWindow * window() const
Returns the window which belongs to the QPlatformWindow.
\inmodule QtCore\reentrant
Definition qpoint.h:214
constexpr QPoint toPoint() const
Rounds the coordinates of this point to the nearest integer, and returns a QPoint object with the rou...
Definition qpoint.h:394
\inmodule QtCore\reentrant
Definition qpoint.h:23
\inmodule QtCore\reentrant
Definition qrect.h:483
\inmodule QtCore\reentrant
Definition qrect.h:30
constexpr bool isValid() const noexcept
Returns true if the rectangle is valid, otherwise returns false.
Definition qrect.h:169
constexpr bool isNull() const noexcept
Returns true if the rectangle is a null rectangle, otherwise returns false.
Definition qrect.h:163
QRect intersected(const QRect &other) const noexcept
Definition qrect.h:414
constexpr QPoint topLeft() const noexcept
Returns the position of the rectangle's top-left corner.
Definition qrect.h:220
constexpr QSize size() const noexcept
Returns the size of the rectangle.
Definition qrect.h:241
constexpr void translate(int dx, int dy) noexcept
Moves the rectangle dx along the x axis and dy along the y axis, relative to the current position.
Definition qrect.h:244
The QScreen class is used to query screen properties. \inmodule QtGui.
Definition qscreen.h:32
qreal devicePixelRatio
the screen's ratio between physical pixels and device-independent pixels
Definition qscreen.h:59
QRect geometry
the screen's geometry in pixels
Definition qscreen.h:45
QString name
a user presentable string representing the screen
Definition qscreen.h:36
QPlatformScreen * handle() const
Get the platform screen handle.
Definition qscreen.cpp:83
\inmodule QtCore
Definition qsize.h:207
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:127
SurfaceType
The SurfaceType enum describes what type of surface this is.
Definition qsurface.h:30
@ MetalSurface
Definition qsurface.h:36
@ VulkanSurface
Definition qsurface.h:35
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 handleScreenRefreshRateChange(QScreen *screen, qreal newRefreshRate)
\inmodule QtGui
Definition qwindow.h:63
rect
[4]
struct wl_display * display
Definition linuxdmabuf.h:41
@ ReconfiguredWithFlagsMissing
Combined button and popup list for selecting options.
@ transparent
Definition qnamespace.h:46
Definition image.cpp:4
QNSView * qnsview_cast(NSView *view)
Returns the view cast to a QNSview if possible.
QPointF qt_mac_flip(const QPointF &pos, const QRectF &reference)
long NSInteger
#define qDeferredDebug(helper)
static QString displayName(CGDirectDisplayID displayID)
QDebug operator<<(QDebug debug, const QCocoaScreen *screen)
QImage qt_mac_toQImage(CGImageRef image)
#define qGuiApp
@ QtCriticalMsg
Definition qlogging.h:32
#define qFatal
Definition qlogging.h:164
#define Q_LOGGING_CATEGORY(name,...)
#define qCInfo(category,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
GLuint64 GLenum void * handle
GLint GLint GLint GLint GLint x
[0]
GLint GLsizei GLsizei height
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLenum GLsizei count
GLuint object
[3]
GLint GLsizei width
GLenum type
GLbitfield flags
GLint y
struct _cl_event * event
GLuint in
static qreal position(const QQuickItem *item, QQuickAnchors::Anchor anchorLine)
static QT_BEGIN_NAMESPACE qreal dpr(const QWindow *w)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
static void allKeys(HKEY parentHandle, const QString &rSubKey, NameSet *result, REGSAM access=0)
QScreen * screen
[1]
Definition main.cpp:29
Q_CORE_EXPORT bool qEnvironmentVariableIsSet(const char *varName) noexcept
#define Q_ENUM_NS(x)
#define Q_NAMESPACE
#define Q_UNUSED(x)
unsigned int uint
Definition qtypes.h:29
double qreal
Definition qtypes.h:92
QFileInfo info(fileName)
[8]
QQueue< int > queue
[0]
widget render & pixmap
QPainter painter(this)
[7]
aWidget window() -> setWindowTitle("New Window Title")
[2]
QQuickView * view
[0]
DeferredDebugHelper(const QLoggingCategory &cat)