Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoacursor.mm
Go to the documentation of this file.
1// Copyright (C) 2016 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// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
7#include "qcocoacursor.h"
8#include "qcocoawindow.h"
9#include "qcocoascreen.h"
10#include "qcocoahelpers.h"
11#include <QtGui/private/qcoregraphics_p.h>
12
13#include <QtGui/QBitmap>
14
15#if !defined(QT_APPLE_NO_PRIVATE_APIS)
16@interface NSCursor()
17+ (id)busyButClickableCursor;
18+ (id)_helpCursor;
19+ (id)_windowResizeNorthWestSouthEastCursor;
20+ (id)_windowResizeNorthEastSouthWestCursor;
21+ (id)_windowResizeNorthSouthCursor;
22+ (id)_windowResizeEastWestCursor;
23@end
24#endif // QT_APPLE_NO_PRIVATE_APIS
25
26QT_BEGIN_NAMESPACE
27
28using namespace Qt::StringLiterals;
29
30QCocoaCursor::QCocoaCursor()
31{
32}
33
34QCocoaCursor::~QCocoaCursor()
35{
36 // release cursors
37 QHash<Qt::CursorShape, NSCursor *>::const_iterator i = m_cursors.constBegin();
38 while (i != m_cursors.constEnd()) {
39 [*i release];
40 ++i;
41 }
42}
43
44void QCocoaCursor::changeCursor(QCursor *cursor, QWindow *window)
45{
46 NSCursor *cocoaCursor = convertCursor(cursor);
47
48 if (QPlatformWindow * platformWindow = window->handle())
49 static_cast<QCocoaWindow *>(platformWindow)->setWindowCursor(cocoaCursor);
50}
51
52QPoint QCocoaCursor::pos() const
53{
54 return QCocoaScreen::mapFromNative([NSEvent mouseLocation]).toPoint();
55}
56
57void QCocoaCursor::setPos(const QPoint &position)
58{
59 CGPoint pos;
60 pos.x = position.x();
61 pos.y = position.y();
62
63 CGEventRef e = CGEventCreateMouseEvent(nullptr, kCGEventMouseMoved, pos, kCGMouseButtonLeft);
64 CGEventPost(kCGHIDEventTap, e);
65 CFRelease(e);
66}
67
68
69QSize QCocoaCursor::size() const
70{
71 NSCursor *cocoaCursor = NSCursor.currentSystemCursor;
72 if (!cocoaCursor)
73 return QPlatformCursor::size();
74 NSImage *cursorImage = cocoaCursor.image;
75 if (!cursorImage)
76 return QPlatformCursor::size();
77
78 QSizeF size = QSizeF::fromCGSize(cursorImage.size);
79 NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
80 NSDictionary *accessSettings = [defaults persistentDomainForName:@"com.apple.universalaccess"];
81 if (accessSettings == nil)
82 return size.toSize();
83
84 float sizeScale = [accessSettings[@"mouseDriverCursorSize"] floatValue];
85 if (sizeScale > 0) {
86 size.rwidth() *= sizeScale;
87 size.rheight() *= sizeScale;
88 }
89
90 return size.toSize();
91}
92
93NSCursor *QCocoaCursor::convertCursor(QCursor *cursor)
94{
95 if (!cursor)
96 return nil;
97
98 const Qt::CursorShape newShape = cursor->shape();
99 NSCursor *cocoaCursor = nil;
100
101 // Check for a suitable built-in NSCursor first:
102 switch (newShape) {
103 case Qt::ArrowCursor:
104 cocoaCursor= [NSCursor arrowCursor];
105 break;
106 case Qt::ForbiddenCursor:
107 cocoaCursor = [NSCursor operationNotAllowedCursor];
108 break;
109 case Qt::CrossCursor:
110 cocoaCursor = [NSCursor crosshairCursor];
111 break;
112 case Qt::IBeamCursor:
113 cocoaCursor = [NSCursor IBeamCursor];
114 break;
115 case Qt::WhatsThisCursor:
116#if !defined(QT_APPLE_NO_PRIVATE_APIS)
117 if ([NSCursor respondsToSelector:@selector(_helpCursor)]) {
118 cocoaCursor = [NSCursor performSelector:@selector(_helpCursor)];
119 break;
120 }
121#endif
122 // For now use the pointing hand as a fallback
123 Q_FALLTHROUGH();
124 case Qt::PointingHandCursor:
125 cocoaCursor = [NSCursor pointingHandCursor];
126 break;
127 case Qt::SplitVCursor:
128 cocoaCursor = [NSCursor resizeUpDownCursor];
129 break;
130 case Qt::SplitHCursor:
131 cocoaCursor = [NSCursor resizeLeftRightCursor];
132 break;
133 case Qt::OpenHandCursor:
134 cocoaCursor = [NSCursor openHandCursor];
135 break;
136 case Qt::ClosedHandCursor:
137 cocoaCursor = [NSCursor closedHandCursor];
138 break;
139 case Qt::DragMoveCursor:
140 cocoaCursor = [NSCursor crosshairCursor];
141 break;
142 case Qt::DragCopyCursor:
143 cocoaCursor = [NSCursor dragCopyCursor];
144 break;
145 case Qt::DragLinkCursor:
146 cocoaCursor = [NSCursor dragLinkCursor];
147 break;
148 case Qt::BusyCursor:
149#if !defined(QT_APPLE_NO_PRIVATE_APIS)
150 if ([NSCursor respondsToSelector:@selector(busyButClickableCursor)])
151 cocoaCursor = [NSCursor performSelector:@selector(busyButClickableCursor)];
152#endif
153 break;
154 case Qt::SizeVerCursor:
155 case Qt::SizeHorCursor:
156 case Qt::SizeBDiagCursor:
157 case Qt::SizeFDiagCursor: {
158#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(150000)
159 if (@available(macOS 15, *)) {
160 auto position = [newShape]{
161 switch (newShape) {
162 case Qt::SizeVerCursor: return NSCursorFrameResizePositionTop;
163 case Qt::SizeHorCursor: return NSCursorFrameResizePositionLeft;
164 case Qt::SizeBDiagCursor: return NSCursorFrameResizePositionTopRight;
165 case Qt::SizeFDiagCursor: return NSCursorFrameResizePositionTopLeft;
166 default: Q_UNREACHABLE();
167 }
168 }();
169 cocoaCursor = [NSCursor frameResizeCursorFromPosition:position
170 inDirections:NSCursorFrameResizeDirectionsAll];
171 break;
172 }
173#endif // macOS 15 SDK
174#if !defined(QT_APPLE_NO_PRIVATE_APIS)
175 auto selector = [newShape]{
176 switch (newShape) {
177 case Qt::SizeVerCursor: return @selector(_windowResizeNorthSouthCursor);
178 case Qt::SizeHorCursor: return @selector(_windowResizeEastWestCursor);
179 case Qt::SizeBDiagCursor: return @selector(_windowResizeNorthEastSouthWestCursor);
180 case Qt::SizeFDiagCursor: return @selector(_windowResizeNorthWestSouthEastCursor);
181 default: Q_UNREACHABLE();
182 }
183 }();
184
185 if ([NSCursor respondsToSelector:selector])
186 cocoaCursor = [NSCursor performSelector:selector];
187#endif // QT_APPLE_NO_PRIVATE_APIS
188 break;
189 }
190 default:
191 break;
192 }
193
194 if (!cocoaCursor) {
195 // No suitable OS cursor exist, use cursors provided
196 // by Qt for the rest. Check for a cached cursor:
197 cocoaCursor = m_cursors.value(newShape);
198 if (cocoaCursor && cursor->shape() == Qt::BitmapCursor) {
199 [cocoaCursor release];
200 cocoaCursor = nil;
201 }
202 if (!cocoaCursor) {
203 cocoaCursor = createCursorData(cursor);
204 if (!cocoaCursor)
205 return [NSCursor arrowCursor];
206
207 m_cursors.insert(newShape, cocoaCursor);
208 }
209 }
210 return cocoaCursor;
211}
212
213
214// Creates an NSCursor for the given QCursor.
215NSCursor *QCocoaCursor::createCursorData(QCursor *cursor)
216{
217 /* Note to self... ***
218 * mask x data
219 * 0xFF x 0x00 == fully opaque white
220 * 0x00 x 0xFF == xor'd black
221 * 0xFF x 0xFF == fully opaque black
222 * 0x00 x 0x00 == fully transparent
223 */
224#define QT_USE_APPROXIMATE_CURSORS
226 static const uchar cur_ver_bits[] = {
227 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x07, 0xe0, 0x0f, 0xf0,
228 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0f, 0xf0,
229 0x07, 0xe0, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00 };
230 static const uchar mcur_ver_bits[] = {
231 0x00, 0x00, 0x03, 0x80, 0x07, 0xc0, 0x0f, 0xe0, 0x1f, 0xf0, 0x3f, 0xf8,
232 0x7f, 0xfc, 0x07, 0xc0, 0x07, 0xc0, 0x07, 0xc0, 0x7f, 0xfc, 0x3f, 0xf8,
233 0x1f, 0xf0, 0x0f, 0xe0, 0x07, 0xc0, 0x03, 0x80 };
234
235 static const uchar cur_hor_bits[] = {
236 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x18, 0x30,
237 0x38, 0x38, 0x7f, 0xfc, 0x7f, 0xfc, 0x38, 0x38, 0x18, 0x30, 0x08, 0x20,
238 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
239 static const uchar mcur_hor_bits[] = {
240 0x00, 0x00, 0x00, 0x00, 0x04, 0x40, 0x0c, 0x60, 0x1c, 0x70, 0x3c, 0x78,
241 0x7f, 0xfc, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0x7f, 0xfc, 0x3c, 0x78,
242 0x1c, 0x70, 0x0c, 0x60, 0x04, 0x40, 0x00, 0x00 };
243
244 static const uchar cur_fdiag_bits[] = {
245 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0xf8, 0x00, 0x78,
246 0x00, 0xf8, 0x01, 0xd8, 0x23, 0x88, 0x37, 0x00, 0x3e, 0x00, 0x3c, 0x00,
247 0x3e, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00 };
248 static const uchar mcur_fdiag_bits[] = {
249 0x00, 0x00, 0x00, 0x00, 0x07, 0xfc, 0x03, 0xfc, 0x01, 0xfc, 0x00, 0xfc,
250 0x41, 0xfc, 0x63, 0xfc, 0x77, 0xdc, 0x7f, 0x8c, 0x7f, 0x04, 0x7e, 0x00,
251 0x7f, 0x00, 0x7f, 0x80, 0x7f, 0xc0, 0x00, 0x00 };
252
253 static const uchar cur_bdiag_bits[] = {
254 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x3e, 0x00, 0x3c, 0x00, 0x3e, 0x00,
255 0x37, 0x00, 0x23, 0x88, 0x01, 0xd8, 0x00, 0xf8, 0x00, 0x78, 0x00, 0xf8,
256 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
257 static const uchar mcur_bdiag_bits[] = {
258 0x00, 0x00, 0x7f, 0xc0, 0x7f, 0x80, 0x7f, 0x00, 0x7e, 0x00, 0x7f, 0x04,
259 0x7f, 0x8c, 0x77, 0xdc, 0x63, 0xfc, 0x41, 0xfc, 0x00, 0xfc, 0x01, 0xfc,
260 0x03, 0xfc, 0x07, 0xfc, 0x00, 0x00, 0x00, 0x00 };
261
262 static const unsigned char cur_up_arrow_bits[] = {
263 0x00, 0x80, 0x01, 0x40, 0x01, 0x40, 0x02, 0x20, 0x02, 0x20, 0x04, 0x10,
264 0x04, 0x10, 0x08, 0x08, 0x0f, 0x78, 0x01, 0x40, 0x01, 0x40, 0x01, 0x40,
265 0x01, 0x40, 0x01, 0x40, 0x01, 0x40, 0x01, 0xc0 };
266 static const unsigned char mcur_up_arrow_bits[] = {
267 0x00, 0x80, 0x01, 0xc0, 0x01, 0xc0, 0x03, 0xe0, 0x03, 0xe0, 0x07, 0xf0,
268 0x07, 0xf0, 0x0f, 0xf8, 0x0f, 0xf8, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
269 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0 };
270#endif
271 const uchar *cursorData = nullptr;
272 const uchar *cursorMaskData = nullptr;
273 QPoint hotspot = cursor->hotSpot();
274
275 switch (cursor->shape()) {
276 case Qt::BitmapCursor: {
277 if (cursor->pixmap().isNull())
278 return createCursorFromBitmap(cursor->bitmap(), cursor->mask(), hotspot);
279 else
280 return createCursorFromPixmap(cursor->pixmap(), hotspot);
281 break; }
282 case Qt::BlankCursor: {
283 QPixmap pixmap = QPixmap(16, 16);
284 pixmap.fill(Qt::transparent);
285 return createCursorFromPixmap(pixmap);
286 break; }
287 case Qt::WaitCursor: {
288 QPixmap pixmap = QPixmap(":/qt-project.org/mac/cursors/images/spincursor.png"_L1);
289 return createCursorFromPixmap(pixmap, hotspot);
290 break; }
291 case Qt::SizeAllCursor: {
292 QPixmap pixmap = QPixmap(":/qt-project.org/mac/cursors/images/sizeallcursor.png"_L1);
293 return createCursorFromPixmap(pixmap, QPoint(8, 8));
294 break; }
295 case Qt::BusyCursor: {
296 QPixmap pixmap = QPixmap(":/qt-project.org/mac/cursors/images/waitcursor.png"_L1);
297 return createCursorFromPixmap(pixmap, hotspot);
298 break; }
299#define QT_USE_APPROXIMATE_CURSORS
301 case Qt::SizeVerCursor:
302 cursorData = cur_ver_bits;
303 cursorMaskData = mcur_ver_bits;
304 hotspot = QPoint(8, 8);
305 break;
306 case Qt::SizeHorCursor:
307 cursorData = cur_hor_bits;
308 cursorMaskData = mcur_hor_bits;
309 hotspot = QPoint(8, 8);
310 break;
311 case Qt::SizeBDiagCursor:
312 cursorData = cur_fdiag_bits;
313 cursorMaskData = mcur_fdiag_bits;
314 hotspot = QPoint(8, 8);
315 break;
316 case Qt::SizeFDiagCursor:
317 cursorData = cur_bdiag_bits;
318 cursorMaskData = mcur_bdiag_bits;
319 hotspot = QPoint(8, 8);
320 break;
321 case Qt::UpArrowCursor:
322 cursorData = cur_up_arrow_bits;
323 cursorMaskData = mcur_up_arrow_bits;
324 hotspot = QPoint(8, 0);
325 break;
326#endif
327 default:
328 qWarning("Qt: QCursor::update: Invalid cursor shape %d", cursor->shape());
329 return nil;
330 }
331
332 // Create an NSCursor from image data if this a self-provided cursor.
333 if (cursorData) {
334 QBitmap bitmap(QBitmap::fromData(QSize(16, 16), cursorData, QImage::Format_Mono));
335 QBitmap mask(QBitmap::fromData(QSize(16, 16), cursorMaskData, QImage::Format_Mono));
336 return (createCursorFromBitmap(bitmap, mask, hotspot));
337 }
338
339 return nil; // should not happen, all cases covered above
340}
341
342NSCursor *QCocoaCursor::createCursorFromBitmap(const QBitmap &bitmap, const QBitmap &mask, const QPoint hotspot)
343{
344 QImage finalCursor(bitmap.size(), QImage::Format_ARGB32);
345 QImage bmi = bitmap.toImage().convertToFormat(QImage::Format_RGB32);
346 QImage bmmi = mask.toImage().convertToFormat(QImage::Format_RGB32);
347 for (int row = 0; row < finalCursor.height(); ++row) {
348 QRgb *bmData = reinterpret_cast<QRgb *>(bmi.scanLine(row));
349 QRgb *bmmData = reinterpret_cast<QRgb *>(bmmi.scanLine(row));
350 QRgb *finalData = reinterpret_cast<QRgb *>(finalCursor.scanLine(row));
351 for (int col = 0; col < finalCursor.width(); ++col) {
352 if (bmmData[col] == 0xff000000 && bmData[col] == 0xffffffff) {
353 finalData[col] = 0xffffffff;
354 } else if (bmmData[col] == 0xff000000 && bmData[col] == 0xffffffff) {
355 finalData[col] = 0x7f000000;
356 } else if (bmmData[col] == 0xffffffff && bmData[col] == 0xffffffff) {
357 finalData[col] = 0x00000000;
358 } else {
359 finalData[col] = 0xff000000;
360 }
361 }
362 }
363
364 return createCursorFromPixmap(QPixmap::fromImage(finalCursor), hotspot);
365}
366
367NSCursor *QCocoaCursor::createCursorFromPixmap(const QPixmap &pixmap, const QPoint hotspot)
368{
369 NSPoint hotSpot = NSMakePoint(hotspot.x(), hotspot.y());
370 auto *image = [NSImage imageFromQImage:pixmap.toImage()];
371 return [[NSCursor alloc] initWithImage:image hotSpot:hotSpot];
372}
373
374QT_END_NAMESPACE
#define QT_USE_APPROXIMATE_CURSORS