Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
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#include <QtGui/private/qhighdpiscaling_p.h>
19
20#include <QtCore/private/qcore_mac_p.h>
21#include <QtCore/private/qeventdispatcher_cf_p.h>
22
24
25namespace CoreGraphics {
29 Moved = kCGDisplayMovedFlag,
30 SetMain = kCGDisplaySetMainFlag,
31 SetMode = kCGDisplaySetModeFlag,
32 Added = kCGDisplayAddFlag,
33 Removed = kCGDisplayRemoveFlag,
34 Enabled = kCGDisplayEnabledFlag,
35 Disabled = kCGDisplayDisabledFlag,
36 Mirrored = kCGDisplayMirrorFlag,
37 UnMirrored = kCGDisplayUnMirrorFlag,
38 DesktopShapeChanged = kCGDisplayDesktopShapeChangedFlag
39 };
41}
42
43QMacNotificationObserver QCocoaScreen::s_screenParameterObserver;
44CGDisplayReconfigurationCallBack QCocoaScreen::s_displayReconfigurationCallBack = nullptr;
45
46void QCocoaScreen::initializeScreens()
47{
48 updateScreens();
49
50 s_displayReconfigurationCallBack = [](CGDirectDisplayID displayId, CGDisplayChangeSummaryFlags flags, void *userInfo) {
51 Q_UNUSED(userInfo);
52
53 const bool beforeReconfigure = flags & kCGDisplayBeginConfigurationFlag;
54 qCDebug(lcQpaScreen).verbosity(0) << "Display" << displayId
55 << (beforeReconfigure ? "beginning" : "finished") << "reconfigure"
56 << QFlags<CoreGraphics::DisplayChange>(flags);
57
58 if (!beforeReconfigure)
59 updateScreens();
60 };
61 CGDisplayRegisterReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
62
63 s_screenParameterObserver = QMacNotificationObserver(NSApplication.sharedApplication,
64 NSApplicationDidChangeScreenParametersNotification, [&]() {
65 qCDebug(lcQpaScreen) << "Received screen parameter change notification";
66 updateScreens();
67 });
68}
69
70/*
71 Update the list of available QScreens, and the properties of existing screens.
72
73 At this point we rely on the NSScreen.screens to be up to date.
74*/
75void QCocoaScreen::updateScreens()
76{
77 // Adding, updating, or removing a screen below might trigger
78 // Qt or the application to move a window to a different screen,
79 // recursing back here via QCocoaWindow::windowDidChangeScreen.
80 // The update code is not re-entrant, so bail out if we end up
81 // in this situation. The screens will stabilize eventually.
82 static bool updatingScreens = false;
83 if (updatingScreens) {
84 qCInfo(lcQpaScreen) << "Skipping screen update, already updating";
85 return;
86 }
87 QBoolBlocker recursionGuard(updatingScreens);
88
89 uint32_t displayCount = 0;
90 if (CGGetOnlineDisplayList(0, nullptr, &displayCount) != kCGErrorSuccess)
91 qFatal("Failed to get number of online displays");
92
93 QVector<CGDirectDisplayID> onlineDisplays(displayCount);
94 if (CGGetOnlineDisplayList(displayCount, onlineDisplays.data(), &displayCount) != kCGErrorSuccess)
95 qFatal("Failed to get online displays");
96
97 qCInfo(lcQpaScreen) << "Updating screens with" << displayCount
98 << "online displays:" << onlineDisplays;
99
100 // TODO: Verify whether we can always assume the main display is first
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);
107 }
108
109 for (CGDirectDisplayID displayId : onlineDisplays) {
110 Q_ASSERT(CGDisplayIsOnline(displayId));
111
112 if (CGDisplayMirrorsDisplay(displayId))
113 continue;
114
115 // A single physical screen can map to multiple displays IDs,
116 // depending on which GPU is in use or which physical port the
117 // screen is connected to. By mapping the display ID to a UUID,
118 // which are shared between displays that target the same screen,
119 // we can pick an existing QScreen to update instead of needlessly
120 // adding and removing QScreens.
121 QCFType<CFUUIDRef> uuid = CGDisplayCreateUUIDFromDisplayID(displayId);
122 Q_ASSERT(uuid);
123
124 if (QCocoaScreen *existingScreen = QCocoaScreen::get(uuid)) {
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;
130 }
131 } else {
132 QCocoaScreen::add(displayId);
133 }
134 }
135
136 for (QScreen *screen : QGuiApplication::screens()) {
137 QCocoaScreen *platformScreen = static_cast<QCocoaScreen*>(screen->handle());
138 if (!platformScreen->isOnline() || platformScreen->isMirroring())
139 platformScreen->remove();
140 }
141}
142
143void QCocoaScreen::add(CGDirectDisplayID displayId)
144{
145 const bool isPrimary = CGDisplayIsMain(displayId);
146 QCocoaScreen *cocoaScreen = new QCocoaScreen(displayId);
147 qCInfo(lcQpaScreen) << "Adding" << cocoaScreen
148 << (isPrimary ? "as new primary screen" : "");
149 QWindowSystemInterface::handleScreenAdded(cocoaScreen, isPrimary);
150}
151
152QCocoaScreen::QCocoaScreen(CGDirectDisplayID displayId)
153 : QPlatformScreen(), m_displayId(displayId)
154{
155 update(m_displayId);
156 m_cursor = new QCocoaCursor;
157}
158
159void QCocoaScreen::cleanupScreens()
160{
161 // Remove screens in reverse order to avoid crash in case of multiple screens
162 for (QScreen *screen : backwards(QGuiApplication::screens()))
163 static_cast<QCocoaScreen*>(screen->handle())->remove();
164
165 Q_ASSERT(s_displayReconfigurationCallBack);
166 CGDisplayRemoveReconfigurationCallback(s_displayReconfigurationCallBack, nullptr);
167 s_displayReconfigurationCallBack = nullptr;
168
169 s_screenParameterObserver.remove();
170}
171
172void QCocoaScreen::remove()
173{
174 // This may result in the application responding to QGuiApplication::screenRemoved
175 // by moving the window to another screen, either by setGeometry, or by setScreen.
176 // If the window isn't moved by the application, Qt will as a fallback move it to
177 // the primary screen via setScreen. Due to the way setScreen works, this won't
178 // actually recreate the window on the new screen, it will just assign the new
179 // QScreen to the window. The associated NSWindow will have an NSScreen determined
180 // by AppKit. AppKit will then move the window to another screen by changing the
181 // geometry, and we will get a callback in QCocoaWindow::windowDidMove and then
182 // QCocoaWindow::windowDidChangeScreen. At that point the window will appear to have
183 // already changed its screen, but that's only true if comparing the Qt screens,
184 // not when comparing the NSScreens.
185 qCInfo(lcQpaScreen) << "Removing " << this;
187}
188
190{
191 Q_ASSERT_X(!screen(), "QCocoaScreen", "QScreen should be deleted first");
192
193 delete m_cursor;
194
195 CVDisplayLinkRelease(m_displayLink);
196 if (m_displayLinkSource)
197 dispatch_release(m_displayLinkSource);
198}
199
200static QString displayName(CGDirectDisplayID displayID)
201{
202 QIOType<io_iterator_t> iterator;
203 if (IOServiceGetMatchingServices(kIOMainPortDefault,
204 IOServiceMatching("IODisplayConnect"), &iterator))
205 return QString();
206
207 QIOType<io_service_t> display;
208 while ((display = IOIteratorNext(iterator)) != 0)
209 {
210 NSDictionary *info = [(__bridge NSDictionary*)IODisplayCreateInfoDictionary(
211 display, kIODisplayOnlyPreferredName) autorelease];
212
213 if ([[info objectForKey:@kDisplayVendorID] unsignedIntValue] != CGDisplayVendorNumber(displayID))
214 continue;
215
216 if ([[info objectForKey:@kDisplayProductID] unsignedIntValue] != CGDisplayModelNumber(displayID))
217 continue;
218
219 if ([[info objectForKey:@kDisplaySerialNumber] unsignedIntValue] != CGDisplaySerialNumber(displayID))
220 continue;
221
222 NSDictionary *localizedNames = [info objectForKey:@kDisplayProductName];
223 if (![localizedNames count])
224 break; // Correct screen, but no name in dictionary
225
226 return QString::fromNSString([localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]);
227 }
228
229 return QString();
230}
231
232void QCocoaScreen::update(CGDirectDisplayID displayId)
233{
234 if (displayId != m_displayId) {
235 qCDebug(lcQpaScreen) << "Reconnecting" << this << "as display" << displayId;
236 m_displayId = displayId;
237 }
238
239 Q_ASSERT(isOnline());
240
241 // Some properties are only available via NSScreen
242 NSScreen *nsScreen = nativeScreen();
243 if (!nsScreen) {
244 qCDebug(lcQpaScreen) << "Corresponding NSScreen not yet available. Deferring update";
245 return;
246 }
247
248 const QRect previousGeometry = m_geometry;
249 const QRect previousAvailableGeometry = m_availableGeometry;
250 const qreal previousRefreshRate = m_refreshRate;
251 const double previousRotation = m_rotation;
252
253 // The reference screen for the geometry is always the primary screen
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();
257
258 m_devicePixelRatio = nsScreen.backingScaleFactor;
259
260 m_format = QImage::Format_RGB32;
261 m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
262 m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData));
263 if (!m_colorSpace.isValid()) {
264 qCWarning(lcQpaScreen) << "Failed to parse ICC profile for" << nsScreen.colorSpace
265 << "with ICC data" << nsScreen.colorSpace.ICCProfileData
266 << "- Falling back to sRGB";
267 m_colorSpace = QColorSpace::SRgb;
268 }
269
270 CGSize size = CGDisplayScreenSize(m_displayId);
271 m_physicalSize = QSizeF(size.width, size.height);
272
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);
277
278 if (@available(macOS 10.15, *))
279 m_name = QString::fromNSString(nsScreen.localizedName);
280 else
281 m_name = displayName(m_displayId);
282
283 const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
284
285 if (m_rotation != previousRotation)
287
288 if (didChangeGeometry)
290 if (m_refreshRate != previousRefreshRate)
292}
293
294// ----------------------- Display link -----------------------
295
296Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg);
297
299{
300 Q_ASSERT(m_displayId);
301
302 if (!isOnline()) {
303 qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request";
304 return false;
305 }
306
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;
311 return false;
312 }
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;
317 return false;
318 }
319 CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*,
320 const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int {
321 // FIXME: It would be nice if update requests would include timing info
322 static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests();
323 return kCVReturnSuccess;
324 }, this);
325
326 // During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop
327 // in event-tracking mode, dequeuing only the mouse drag events needed to update the window's
328 // frame. It will repeatedly spin this loop until no longer receiving any mouse drag events,
329 // and will then update the frame (effectively coalescing/compressing the events). Unfortunately
330 // the events are pulled out using -[NSApplication nextEventMatchingEventMask:untilDate:inMode:dequeue:]
331 // which internally uses CFRunLoopRunSpecific, so the event loop will also process GCD queues and other
332 // runloop sources that have been added to the tracking mode. This includes the GCD display-link
333 // source that we use to marshal the display-link callback over to the main thread. If the
334 // subsequent delivery of the update-request on the main thread stalls due to inefficient
335 // user code, the NSEventThread will have had time to deliver additional mouse drag events,
336 // and the logic in -[NSWindow _resizeWithEvent:] will keep on compressing events and never
337 // get to the point of actually updating the window frame, making it seem like the window
338 // is stuck in its original size. Only when the user stops moving their mouse, and the event
339 // queue is completely drained of drag events, will the window frame be updated.
340
341 // By keeping an event tap listening for drag events, registered as a version 1 runloop source,
342 // we prevent the GCD source from being prioritized, giving the resize logic enough time
343 // to finish coalescing the events. This is incidental, but conveniently gives us the behavior
344 // we are looking for, interleaving display-link updates and resize events.
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!";
351 return event; // Listen only tap, so what we return doesn't really matter
352 }, nullptr);
353 CGEventTapEnable(eventTap, false); // Event taps are normally enabled when created
354 static CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
355 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
356
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);
363 }];
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);
369 }];
370 return eventTap;
371 }();
372 Q_UNUSED(eventTap);
373 }
374
375 if (!CVDisplayLinkIsRunning(m_displayLink)) {
376 qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this;
377 CVDisplayLinkStart(m_displayLink);
378 }
379
380 return true;
381}
382
383// Helper to allow building up debug output in multiple steps
385{
387 if (cat.isDebugEnabled())
388 debug = new QDebug(QMessageLogger().debug(cat).nospace());
389 }
393 void flushOutput() {
394 if (debug) {
395 delete debug;
396 debug = nullptr;
397 }
398 }
399 QDebug *debug = nullptr;
400};
401
402#define qDeferredDebug(helper) if (Q_UNLIKELY(helper.debug)) *helper.debug
403
405{
406 if (!isOnline())
407 return;
408
410
411 // The CVDisplayLink callback is a notification that it's a good time to produce a new frame.
412 // Since the callback is delivered on a separate thread we have to marshal it over to the
413 // main thread, as Qt requires update requests to be delivered there. This needs to happen
414 // asynchronously, as otherwise we may end up deadlocking if the main thread calls back
415 // into any of the CVDisplayLink APIs.
416 if (!NSThread.isMainThread) {
417 // We're explicitly not using the data of the GCD source to track the pending updates,
418 // as the data isn't reset to 0 until after the event handler, and also doesn't update
419 // during the event handler, both of which we need to track late frames.
420 const int pendingUpdates = ++m_pendingUpdates;
421
422 DeferredDebugHelper screenUpdates(lcQpaScreenUpdates());
423 qDeferredDebug(screenUpdates) << "display link callback for screen " << m_displayId;
424
425 if (const int framesAheadOfDelivery = pendingUpdates - 1) {
426 // If we have more than one update pending it means that a previous display link callback
427 // has not been fully processed on the main thread, either because GCD hasn't delivered
428 // it on the main thread yet, because the processing of the update request is taking
429 // too long, or because the update request was deferred due to window live resizing.
430 qDeferredDebug(screenUpdates) << ", " << framesAheadOfDelivery << " frame(s) ahead";
431 }
432
433 qDeferredDebug(screenUpdates) << "; signaling dispatch source";
434
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, ^{
439 });
440 dispatch_resume(m_displayLinkSource);
441 }
442
443 dispatch_source_merge_data(m_displayLinkSource, 1);
444
445 } else {
446 DeferredDebugHelper screenUpdates(lcQpaScreenUpdates());
447 qDeferredDebug(screenUpdates) << "gcd event handler on main thread";
448
449 const int pendingUpdates = m_pendingUpdates;
450 if (pendingUpdates > 1)
451 qDeferredDebug(screenUpdates) << ", " << (pendingUpdates - 1) << " frame(s) behind display link";
452
453 screenUpdates.flushOutput();
454
455 bool pauseUpdates = true;
456
458 for (int i = 0; i < windows.size(); ++i) {
460 auto *platformWindow = static_cast<QCocoaWindow*>(window->handle());
461 if (!platformWindow)
462 continue;
463
464 if (!platformWindow->hasPendingUpdateRequest())
465 continue;
466
467 if (window->screen() != screen())
468 continue;
469
470 // Skip windows that are not doing update requests via display link
471 if (!platformWindow->updatesWithDisplayLink())
472 continue;
473
474 // QTBUG-107198: Skip updates in a live resize for a better resize experience.
475 if (platformWindow->isContentView() && platformWindow->view().inLiveResize) {
476 const QSurface::SurfaceType surfaceType = window->surfaceType();
477 const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
478 const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement]
479 != NSViewLayerContentsPlacementScaleAxesIndependently;
480 if (usesMetalLayer && usesNonDefaultContentsPlacement) {
481 static bool deliverDisplayLinkUpdatesDuringLiveResize =
482 qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE");
483 if (!deliverDisplayLinkUpdatesDuringLiveResize) {
484 // Must keep the link running, we do not know what the event
485 // handlers for UpdateRequest (which is not sent now) would do,
486 // would they trigger a new requestUpdate() or not.
487 pauseUpdates = false;
488 continue;
489 }
490 }
491 }
492
493 platformWindow->deliverUpdateRequest();
494
495 // Another update request was triggered, keep the display link running
496 if (platformWindow->hasPendingUpdateRequest())
497 pauseUpdates = false;
498 }
499
500 if (pauseUpdates) {
501 // Pause the display link if there are no pending update requests
502 qCDebug(lcQpaScreenUpdates) << "Stopping display link for" << this;
503 CVDisplayLinkStop(m_displayLink);
504 }
505
506 if (const int missedUpdates = m_pendingUpdates.fetchAndStoreRelaxed(0) - pendingUpdates) {
507 qCWarning(lcQpaScreenUpdates) << "main thread missed" << missedUpdates
508 << "update(s) from display link during update request delivery";
509 }
510 }
511}
512
514{
515 return m_displayLink && CVDisplayLinkIsRunning(m_displayLink);
516}
517
518// -----------------------------------------------------------
519
521{
524 // Every OSX machine has RGB pixels unless a peculiar or rotated non-Apple screen is attached
526 }
527 return type;
528}
529
531{
532 if (m_rotation == 0)
534 if (m_rotation == 90)
536 if (m_rotation == 180)
538 if (m_rotation == 270)
541}
542
544{
545 __block QWindow *window = nullptr;
546 [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
547 usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
548 if (!nsWindow)
549 return;
550
551 // Continue the search if the window does not belong to Qt
552 if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
553 return;
554
555 QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
556 if (!cocoaWindow)
557 return;
558
559 QWindow *w = cocoaWindow->window();
560 if (!w->isVisible())
561 return;
562
563 auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w);
564 if (!nativeGeometry.contains(point))
565 return;
566
568 if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft()))
569 return;
570
571 window = w;
572
573 // Continue the search if the window is not a top-level window
574 if (!window->isTopLevel())
575 return;
576
577 *stop = true;
578 }
579 ];
580
581 return window;
582}
583
590QPixmap QCocoaScreen::grabWindow(WId view, int x, int y, int width, int height) const
591{
592 /*
593 Grab the grabRect section of the specified display into a pixmap that has
594 sRGB color spec. Once Qt supports a fully color-managed flow and conversions
595 that don't lose the colorspec information, we would want the image to maintain
596 the color spec of the display from which it was grabbed. Ultimately, rendering
597 the returned pixmap on the same display from which it was grabbed should produce
598 identical visual results.
599 */
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);
606 }
608 pixmap.setDevicePixelRatio(nativeScreenForDisplayId(displayId).backingScaleFactor);
609 return pixmap;
610 };
611
612 QRect grabRect = QRect(x, y, width, height);
613 qCDebug(lcQpaScreen) << "input grab rect" << grabRect;
614
615 if (!view) {
616 // coordinates are relative to the screen
617 if (!grabRect.isValid()) // entire screen
618 grabRect = QRect(QPoint(0, 0), geometry().size());
619 else
620 grabRect.translate(-geometry().topLeft());
621 return grabFromDisplay(displayId(), grabRect);
622 }
623
624 // grab the window; grab rect in window coordinates might span multiple screens
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)];
628 QPoint position = mapFromNative(screenRect.origin).toPoint();
629 QSize size = QRectF::fromCGRect(NSRectToCGRect(nsView.bounds)).toRect().size();
630 QRect windowRect = QRect(position, size);
631 if (!grabRect.isValid())
632 grabRect = windowRect;
633 else
634 grabRect.translate(windowRect.topLeft());
635
636 // Find which displays to grab from
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)
643 return QPixmap();
644
645 qCDebug(lcQpaScreen) << "final grab rect" << grabRect << "from" << displayCount << "displays";
646
647 // Grab images from each display
648 QVector<QPixmap> pixmaps;
649 QVector<QRect> destinations;
650 for (uint i = 0; i < displayCount; ++i) {
651 auto display = displays[i];
652 const QRect displayBounds = QRectF::fromCGRect(CGDisplayBounds(display)).toRect();
653 const QRect grabBounds = displayBounds.intersected(grabRect);
654 if (grabBounds.isNull()) {
655 destinations.append(QRect());
656 pixmaps.append(QPixmap());
657 continue;
658 }
659 const QRect displayLocalGrabBounds = QRect(QPoint(grabBounds.topLeft() - displayBounds.topLeft()), grabBounds.size());
660
661 qCDebug(lcQpaScreen) << "grab display" << i << "global" << grabBounds << "local" << displayLocalGrabBounds;
662 QPixmap displayPixmap = grabFromDisplay(display, displayLocalGrabBounds);
663 // Fast path for when grabbing from a single screen only
664 if (displayCount == 1)
665 return displayPixmap;
666
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);
671 }
672
673 // Determine the highest dpr, which becomes the dpr for the returned pixmap.
674 qreal dpr = 1.0;
675 for (uint i = 0; i < displayCount; ++i)
676 dpr = qMax(dpr, pixmaps.at(i).devicePixelRatio());
677
678 // Allocate target pixmap and draw each screen's content
679 qCDebug(lcQpaScreen) << "Create grap pixmap" << grabRect.size() << "at devicePixelRatio" << dpr;
680 QPixmap windowPixmap(grabRect.size() * dpr);
681 windowPixmap.setDevicePixelRatio(dpr);
682 windowPixmap.fill(Qt::transparent);
683 QPainter painter(&windowPixmap);
684 for (uint i = 0; i < displayCount; ++i)
685 painter.drawPixmap(destinations.at(i), pixmaps.at(i));
686
687 return windowPixmap;
688}
689
690bool QCocoaScreen::isOnline() const
691{
692 // When a display is disconnected CGDisplayIsOnline and other CGDisplay
693 // functions that take a displayId will not return false, but will start
694 // returning -1 to signal that the displayId is invalid. Some functions
695 // will also assert or even crash in this case, so it's important that
696 // we double check if a display is online before calling other functions.
697 int isOnline = CGDisplayIsOnline(m_displayId);
698 static const int kCGDisplayIsDisconnected = 0xffffffff;
699 return isOnline != kCGDisplayIsDisconnected && isOnline;
700}
701
702/*
703 Returns true if a screen is mirroring another screen
704*/
705bool QCocoaScreen::isMirroring() const
706{
707 if (!isOnline())
708 return false;
709
710 return CGDisplayMirrorsDisplay(m_displayId);
711}
712
717{
718 // Note: The primary screen that Qt knows about may not match the current CGMainDisplayID()
719 // if macOS has not yet been able to inform us that the main display has changed, but we
720 // will update the primary screen accordingly once the reconfiguration callback comes in.
721 return static_cast<QCocoaScreen *>(QGuiApplication::primaryScreen()->handle());
722}
723
724QList<QPlatformScreen*> QCocoaScreen::virtualSiblings() const
725{
726 QList<QPlatformScreen*> siblings;
727
728 // Screens on macOS are always part of the same virtual desktop
730 siblings << screen->handle();
731
732 return siblings;
733}
734
735QCocoaScreen *QCocoaScreen::get(NSScreen *nsScreen)
736{
737 auto displayId = nsScreen.qt_displayId;
738 auto *cocoaScreen = get(displayId);
739 if (!cocoaScreen) {
740 qCWarning(lcQpaScreen) << "Failed to map" << nsScreen
741 << "to QCocoaScreen. Doing last minute update.";
742 updateScreens();
743 cocoaScreen = get(displayId);
744 if (!cocoaScreen)
745 qCWarning(lcQpaScreen) << "Last minute update failed!";
746 }
747 return cocoaScreen;
748}
749
750QCocoaScreen *QCocoaScreen::get(CGDirectDisplayID displayId)
751{
753 QCocoaScreen *cocoaScreen = static_cast<QCocoaScreen*>(screen->handle());
754 if (cocoaScreen->m_displayId == displayId)
755 return cocoaScreen;
756 }
757
758 return nullptr;
759}
760
762{
764 auto *platformScreen = static_cast<QCocoaScreen*>(screen->handle());
765 if (!platformScreen->isOnline())
766 continue;
767
768 auto displayId = platformScreen->displayId();
769 QCFType<CFUUIDRef> candidateUuid(CGDisplayCreateUUIDFromDisplayID(displayId));
770 Q_ASSERT(candidateUuid);
771
772 if (candidateUuid == uuid)
773 return platformScreen;
774 }
775
776 return nullptr;
777}
778
779NSScreen *QCocoaScreen::nativeScreenForDisplayId(CGDirectDisplayID displayId)
780{
781 for (NSScreen *screen in NSScreen.screens) {
782 if (screen.qt_displayId == displayId)
783 return screen;
784 }
785 return nil;
786}
787
789{
790 if (!m_displayId)
791 return nil; // The display has been disconnected
792
793 return nativeScreenForDisplayId(m_displayId);
794}
795
797{
799 return qt_mac_flip(pos, screen->geometry()).toCGPoint();
800}
801
803{
805 return qt_mac_flip(rect, screen->geometry()).toCGRect();
806}
807
809{
811 return qt_mac_flip(QPointF::fromCGPoint(pos), screen->geometry());
812}
813
815{
817 return qt_mac_flip(QRectF::fromCGRect(rect), screen->geometry());
818}
819
820#ifndef QT_NO_DEBUG_STREAM
822{
823 QDebugStateSaver saver(debug);
824 debug.nospace();
825 debug << "QCocoaScreen(" << (const void *)screen;
826 if (screen) {
827 debug << ", " << screen->name();
828 if (screen->isOnline()) {
829 if (CGDisplayIsAsleep(screen->displayId()))
830 debug << ", Sleeping";
831 if (auto mirroring = CGDisplayMirrorsDisplay(screen->displayId()))
832 debug << ", mirroring=" << mirroring;
833 } else {
834 debug << ", Offline";
835 }
836 debug << ", " << screen->geometry();
837 debug << ", dpr=" << screen->devicePixelRatio();
838 debug << ", displayId=" << screen->displayId();
839
840 if (auto nativeScreen = screen->nativeScreen())
841 debug << ", " << nativeScreen;
842 }
843 debug << ')';
844 return debug;
845}
846#endif // !QT_NO_DEBUG_STREAM
847
849
850#include "qcocoascreen.moc"
851
852@implementation NSScreen (QtExtras)
853
854- (CGDirectDisplayID)qt_displayId
855{
856 return [self.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
857}
858
859@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())
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.
\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
qsizetype size() const noexcept
Definition qlist.h:397
const_reference at(qsizetype i) const noexcept
Definition qlist.h:446
\inmodule QtCore
\inmodule QtCore
Definition qlogging.h:72
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
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:1437
The QPlatformScreen class provides an abstraction for visual displays.
virtual Qt::ScreenOrientation orientation() const
Reimplement this function in subclass to return the current orientation of the screen,...
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:217
\inmodule QtCore\reentrant
Definition qpoint.h:25
\inmodule QtCore\reentrant
Definition qrect.h:484
\inmodule QtCore\reentrant
Definition qrect.h:30
QRect intersected(const QRect &other) const noexcept
Definition qrect.h:415
The QRegion class specifies a clip region for a painter.
Definition qregion.h:27
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:208
\inmodule QtCore
Definition qsize.h:25
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
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 handleScreenOrientationChange(QScreen *screen, Qt::ScreenOrientation newOrientation)
static void handleScreenRefreshRateChange(QScreen *screen, qreal newRefreshRate)
\inmodule QtGui
Definition qwindow.h:63
rect
[4]
struct wl_display * display
Definition linuxdmabuf.h:41
@ 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.
ScreenOrientation
Definition qnamespace.h:271
@ InvertedLandscapeOrientation
Definition qnamespace.h:276
@ InvertedPortraitOrientation
Definition qnamespace.h:275
@ LandscapeOrientation
Definition qnamespace.h:274
@ PortraitOrientation
Definition qnamespace.h:273
@ transparent
Definition qnamespace.h:47
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)
#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:168
#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]
GLfloat GLfloat GLfloat w
[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 GLint GLint GLint GLint GLint GLint GLbitfield mask
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:34
double qreal
Definition qtypes.h:187
QQueue< int > queue
[0]
widget render & pixmap
QPainter painter(this)
[7]
aWidget window() -> setWindowTitle("New Window Title")
[2]
QHostInfo info
[0]
QQuickView * view
[0]
DeferredDebugHelper(const QLoggingCategory &cat)