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
qwindowscursor.cpp
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#ifndef QT_NO_CURSOR
10
11#include <QtGui/qbitmap.h>
12#include <QtGui/qimage.h>
13#include <QtGui/qbitmap.h>
14#include <QtGui/qguiapplication.h>
15#include <QtGui/qscreen.h>
16#include <QtGui/private/qguiapplication_p.h> // getPixmapCursor()
17#include <QtGui/private/qhighdpiscaling_p.h>
18#include <QtGui/private/qpixmap_win_p.h>
19#include <QtCore/private/qwinregistry_p.h>
20
21#include <QtCore/qdebug.h>
22#include <QtCore/qscopedpointer.h>
23
24static bool initResources()
25{
26#if QT_CONFIG(imageformat_png)
27 Q_INIT_RESOURCE(cursors);
28#endif
29 return true;
30}
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36/*!
37 \class QWindowsCursorCacheKey
38 \brief Cache key for storing values in a QHash with a QCursor as key.
39
40 \internal
41*/
42
45{
46 if (!bitmapCacheKey) {
47 Q_ASSERT(!c.bitmap().isNull());
48 Q_ASSERT(!c.mask().isNull());
49 bitmapCacheKey = c.bitmap().cacheKey();
50 maskCacheKey = c.mask().cacheKey();
51 }
52 hotspotCacheKey.x = c.hotSpot().x();
53 hotspotCacheKey.y = c.hotSpot().y();
54}
55
56/*!
57 \class QWindowsCursor
58 \brief Platform cursor implementation
59
60 Note that whereas under X11, a cursor can be set as a property of
61 a window, there is only a global SetCursor() function on Windows.
62 Each Window sets on the global cursor on receiving a Enter-event
63 as do the Window manager frames (resize/move handles).
64
65 \internal
66 \sa QWindowsWindowCursor
67*/
68
69HCURSOR QWindowsCursor::createPixmapCursor(QPixmap pixmap, const QPoint &hotSpot, qreal scaleFactor)
70{
71 HCURSOR cur = nullptr;
72 const qreal pixmapScaleFactor = scaleFactor / pixmap.devicePixelRatio();
73 if (!qFuzzyCompare(pixmapScaleFactor, 1)) {
74 pixmap = pixmap.scaled((pixmapScaleFactor * QSizeF(pixmap.size())).toSize(),
75 Qt::KeepAspectRatio, Qt::SmoothTransformation);
76 }
77 QBitmap mask = pixmap.mask();
78 if (mask.isNull()) {
79 mask = QBitmap(pixmap.size());
80 mask.fill(Qt::color1);
81 }
82
83 HBITMAP ic = qt_pixmapToWinHBITMAP(pixmap, /* HBitmapAlpha */ 2);
84 const HBITMAP im = qt_createIconMask(mask);
85
86 ICONINFO ii;
87 ii.fIcon = 0;
88 ii.xHotspot = DWORD(qRound(hotSpot.x() * scaleFactor));
89 ii.yHotspot = DWORD(qRound(hotSpot.y() * scaleFactor));
90 ii.hbmMask = im;
91 ii.hbmColor = ic;
92
93 cur = CreateIconIndirect(&ii);
94
95 DeleteObject(ic);
96 DeleteObject(im);
97 return cur;
98}
99
100// Create a cursor from image and mask of the format QImage::Format_Mono.
101static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits,
102 QPoint hotSpot = QPoint(-1, -1),
103 bool invb = false, bool invm = false)
104{
105 const int width = bbits.width();
106 const int height = bbits.height();
107 if (hotSpot.x() < 0)
108 hotSpot.setX(width / 2);
109 if (hotSpot.y() < 0)
110 hotSpot.setY(height / 2);
111 // a ddb is word aligned, QImage depends on bow it was created
112 const auto bplDdb = qMax(1, ((width + 15) >> 4) << 1);
113 const auto bplImg = int(bbits.bytesPerLine());
114 QScopedArrayPointer<uchar> xBits(new uchar[height * bplDdb]);
115 QScopedArrayPointer<uchar> xMask(new uchar[height * bplDdb]);
116 int x = 0;
117 for (int i = 0; i < height; ++i) {
118 const uchar *bits = bbits.constScanLine(i);
119 const uchar *mask = mbits.constScanLine(i);
120 for (int j = 0; j < bplImg && j < bplDdb; ++j) {
121 uchar b = bits[j];
122 uchar m = mask[j];
123 if (invb)
124 b ^= 0xff;
125 if (invm)
126 m ^= 0xff;
127 xBits[x] = ~m;
128 xMask[x] = b ^ m;
129 ++x;
130 }
131 for (int i = bplImg; i < bplDdb; ++i) {
132 xBits[x] = 0;
133 xMask[x] = 0;
134 ++x;
135 }
136 }
137 return CreateCursor(GetModuleHandle(nullptr), hotSpot.x(), hotSpot.y(), width, height,
138 xBits.data(), xMask.data());
139}
140
141// Create a cursor from image and mask of the format QImage::Format_Mono.
142static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor = 1)
143{
144 Q_ASSERT(cursor.shape() == Qt::BitmapCursor && !cursor.bitmap().isNull());
145 QImage bbits = cursor.bitmap().toImage();
146 QImage mbits = cursor.mask().toImage();
147 scaleFactor /= bbits.devicePixelRatio();
148 if (!qFuzzyCompare(scaleFactor, 1)) {
149 const QSize scaledSize = (QSizeF(bbits.size()) * scaleFactor).toSize();
150 bbits = bbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
151 mbits = mbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
152 }
153 bbits = std::move(bbits).convertToFormat(QImage::Format_Mono);
154 mbits = std::move(mbits).convertToFormat(QImage::Format_Mono);
155 const bool invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1));
156 const bool invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1));
157 return createBitmapCursor(bbits, mbits, cursor.hotSpot(), invb, invm);
158}
159
160static QSize systemCursorSize() { return QSize(GetSystemMetrics(SM_CXCURSOR), GetSystemMetrics(SM_CYCURSOR)); }
161
162static QSize screenCursorSize(const QPlatformScreen *screen = nullptr)
163{
164 const QSize primaryScreenCursorSize = systemCursorSize();
165 if (screen) {
166 // Correct the size if the DPI value of the screen differs from
167 // that of the primary screen.
168 if (const QScreen *primaryQScreen = QGuiApplication::primaryScreen()) {
169 const QPlatformScreen *primaryScreen = primaryQScreen->handle();
170 if (screen != primaryScreen) {
171 const qreal logicalDpi = screen->logicalDpi().first;
172 const qreal primaryScreenLogicalDpi = primaryScreen->logicalDpi().first;
173 if (!qFuzzyCompare(logicalDpi, primaryScreenLogicalDpi))
174 return (QSizeF(primaryScreenCursorSize) * logicalDpi / primaryScreenLogicalDpi).toSize();
175 }
176 }
177 }
178 return primaryScreenCursorSize;
179}
180
181#if !QT_CONFIG(imageformat_png)
182
183static inline QSize standardCursorSize() { return QSize(32, 32); }
184
185// Create pixmap cursors from data and scale the image if the cursor size is
186// higher than the standard 32. Note that bitmap cursors as produced by
187// createBitmapCursor() only work for standard sizes (32,48,64...), which does
188// not work when scaling the 16x16 openhand cursor bitmaps to 150% (resulting
189// in a non-standard 24x24 size).
190static QWindowsCursor::PixmapCursor createPixmapCursorFromData(const QSize &screenCursorSize,
191 // The cursor size the bitmap is targeted for
192 const QSize &bitmapTargetCursorSize,
193 // The actual size of the bitmap data
194 int bitmapSize, const uchar *bits,
195 const uchar *maskBits)
196{
197 QPixmap rawImage = QPixmap::fromImage(QBitmap::fromData(QSize(bitmapSize, bitmapSize), bits).toImage());
198 rawImage.setMask(QBitmap::fromData(QSize(bitmapSize, bitmapSize), maskBits));
199
200 const qreal factor = qreal(screenCursorSize.width()) / qreal(bitmapTargetCursorSize.width());
201 // Scale images if the cursor size is significantly different, starting with 150% where the system cursor
202 // size is 48.
203 if (qAbs(factor - 1.0) > 0.4) {
204 const QTransform transform = QTransform::fromScale(factor, factor);
205 rawImage = rawImage.transformed(transform, Qt::SmoothTransformation);
206 }
207 const QPoint hotSpot(rawImage.width() / 2, rawImage.height() / 2);
208 return QWindowsCursor::PixmapCursor(rawImage, hotSpot);
209}
210
211QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursorShape,
212 const QPlatformScreen *screen)
213{
214 // Non-standard Windows cursors are created from bitmaps
215 static const uchar vsplit_bits[] = {
216 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
217 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
218 0x00, 0x80, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00,
219 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
220 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00,
221 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x00,
222 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
223 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xe0, 0x03, 0x00,
224 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
225 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
226 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
227 static const uchar vsplitm_bits[] = {
228 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
229 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
230 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x07, 0x00,
231 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00,
232 0x00, 0xc0, 0x01, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00,
233 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00, 0x80, 0xff, 0xff, 0x00,
234 0x80, 0xff, 0xff, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00,
235 0x00, 0xc0, 0x01, 0x00, 0x00, 0xf8, 0x0f, 0x00, 0x00, 0xf0, 0x07, 0x00,
236 0x00, 0xe0, 0x03, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
237 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
238 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
239 static const uchar hsplit_bits[] = {
240 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
241 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
242 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
243 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
244 0x00, 0x41, 0x82, 0x00, 0x80, 0x41, 0x82, 0x01, 0xc0, 0x7f, 0xfe, 0x03,
245 0x80, 0x41, 0x82, 0x01, 0x00, 0x41, 0x82, 0x00, 0x00, 0x40, 0x02, 0x00,
246 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00,
247 0x00, 0x40, 0x02, 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
248 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
249 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
250 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
251 static const uchar hsplitm_bits[] = {
252 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
253 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
254 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00,
255 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe3, 0xc7, 0x00,
256 0x80, 0xe3, 0xc7, 0x01, 0xc0, 0xff, 0xff, 0x03, 0xe0, 0xff, 0xff, 0x07,
257 0xc0, 0xff, 0xff, 0x03, 0x80, 0xe3, 0xc7, 0x01, 0x00, 0xe3, 0xc7, 0x00,
258 0x00, 0xe2, 0x47, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00,
259 0x00, 0xe0, 0x07, 0x00, 0x00, 0xe0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
260 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
261 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
262 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
263 static const uchar openhand_bits[] = {
264 0x80,0x01,0x58,0x0e,0x64,0x12,0x64,0x52,0x48,0xb2,0x48,0x92,
265 0x16,0x90,0x19,0x80,0x11,0x40,0x02,0x40,0x04,0x40,0x04,0x20,
266 0x08,0x20,0x10,0x10,0x20,0x10,0x00,0x00};
267 static const uchar openhandm_bits[] = {
268 0x80,0x01,0xd8,0x0f,0xfc,0x1f,0xfc,0x5f,0xf8,0xff,0xf8,0xff,
269 0xf6,0xff,0xff,0xff,0xff,0x7f,0xfe,0x7f,0xfc,0x7f,0xfc,0x3f,
270 0xf8,0x3f,0xf0,0x1f,0xe0,0x1f,0x00,0x00};
271 static const uchar closedhand_bits[] = {
272 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0x48,0x32,0x08,0x50,
273 0x10,0x40,0x18,0x40,0x04,0x40,0x04,0x20,0x08,0x20,0x10,0x10,
274 0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00};
275 static const uchar closedhandm_bits[] = {
276 0x00,0x00,0x00,0x00,0x00,0x00,0xb0,0x0d,0xf8,0x3f,0xf8,0x7f,
277 0xf0,0x7f,0xf8,0x7f,0xfc,0x7f,0xfc,0x3f,0xf8,0x3f,0xf0,0x1f,
278 0xe0,0x1f,0xe0,0x1f,0x00,0x00,0x00,0x00};
279
280 static const char * const moveDragCursorXpmC[] = {
281 "11 20 3 1",
282 ". c None",
283 "a c #FFFFFF",
284 "X c #000000", // X11 cursor is traditionally black
285 "aa.........",
286 "aXa........",
287 "aXXa.......",
288 "aXXXa......",
289 "aXXXXa.....",
290 "aXXXXXa....",
291 "aXXXXXXa...",
292 "aXXXXXXXa..",
293 "aXXXXXXXXa.",
294 "aXXXXXXXXXa",
295 "aXXXXXXaaaa",
296 "aXXXaXXa...",
297 "aXXaaXXa...",
298 "aXa..aXXa..",
299 "aa...aXXa..",
300 "a.....aXXa.",
301 "......aXXa.",
302 ".......aXXa",
303 ".......aXXa",
304 "........aa."};
305
306 static const char * const copyDragCursorXpmC[] = {
307 "24 30 3 1",
308 ". c None",
309 "a c #000000",
310 "X c #FFFFFF",
311 "XX......................",
312 "XaX.....................",
313 "XaaX....................",
314 "XaaaX...................",
315 "XaaaaX..................",
316 "XaaaaaX.................",
317 "XaaaaaaX................",
318 "XaaaaaaaX...............",
319 "XaaaaaaaaX..............",
320 "XaaaaaaaaaX.............",
321 "XaaaaaaXXXX.............",
322 "XaaaXaaX................",
323 "XaaXXaaX................",
324 "XaX..XaaX...............",
325 "XX...XaaX...............",
326 "X.....XaaX..............",
327 "......XaaX..............",
328 ".......XaaX.............",
329 ".......XaaX.............",
330 "........XX...aaaaaaaaaaa",
331 ".............aXXXXXXXXXa",
332 ".............aXXXXXXXXXa",
333 ".............aXXXXaXXXXa",
334 ".............aXXXXaXXXXa",
335 ".............aXXaaaaaXXa",
336 ".............aXXXXaXXXXa",
337 ".............aXXXXaXXXXa",
338 ".............aXXXXXXXXXa",
339 ".............aXXXXXXXXXa",
340 ".............aaaaaaaaaaa"};
341
342 static const char * const linkDragCursorXpmC[] = {
343 "24 30 3 1",
344 ". c None",
345 "a c #000000",
346 "X c #FFFFFF",
347 "XX......................",
348 "XaX.....................",
349 "XaaX....................",
350 "XaaaX...................",
351 "XaaaaX..................",
352 "XaaaaaX.................",
353 "XaaaaaaX................",
354 "XaaaaaaaX...............",
355 "XaaaaaaaaX..............",
356 "XaaaaaaaaaX.............",
357 "XaaaaaaXXXX.............",
358 "XaaaXaaX................",
359 "XaaXXaaX................",
360 "XaX..XaaX...............",
361 "XX...XaaX...............",
362 "X.....XaaX..............",
363 "......XaaX..............",
364 ".......XaaX.............",
365 ".......XaaX.............",
366 "........XX...aaaaaaaaaaa",
367 ".............aXXXXXXXXXa",
368 ".............aXXXaaaaXXa",
369 ".............aXXXXaaaXXa",
370 ".............aXXXaaaaXXa",
371 ".............aXXaaaXaXXa",
372 ".............aXXaaXXXXXa",
373 ".............aXXaXXXXXXa",
374 ".............aXXXaXXXXXa",
375 ".............aXXXXXXXXXa",
376 ".............aaaaaaaaaaa"};
377
378 switch (cursorShape) {
379 case Qt::SplitVCursor:
380 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 32, vsplit_bits, vsplitm_bits);
381 case Qt::SplitHCursor:
382 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 32, hsplit_bits, hsplitm_bits);
383 case Qt::OpenHandCursor:
384 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 16, openhand_bits, openhandm_bits);
385 case Qt::ClosedHandCursor:
386 return createPixmapCursorFromData(screenCursorSize(screen), standardCursorSize(), 16, closedhand_bits, closedhandm_bits);
387 case Qt::DragCopyCursor:
388 return QWindowsCursor::PixmapCursor(QPixmap(copyDragCursorXpmC), QPoint(0, 0));
389 case Qt::DragMoveCursor:
390 return QWindowsCursor::PixmapCursor(QPixmap(moveDragCursorXpmC), QPoint(0, 0));
391 case Qt::DragLinkCursor:
392 return QWindowsCursor::PixmapCursor(QPixmap(linkDragCursorXpmC), QPoint(0, 0));
393 }
394
395 return QWindowsCursor::PixmapCursor();
396}
397#else // QT_NO_IMAGEFORMAT_PNG
398struct QWindowsCustomPngCursor {
399 Qt::CursorShape shape;
400 int size;
401 const char *fileName;
402 int hotSpotX;
403 int hotSpotY;
404};
405
406QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursorShape, const QPlatformScreen *screen)
407{
408 static const QWindowsCustomPngCursor pngCursors[] = {
409 { Qt::SplitVCursor, 32, "splitvcursor_32.png", 11, 11 },
410 { Qt::SplitVCursor, 48, "splitvcursor_48.png", 16, 17 },
411 { Qt::SplitVCursor, 64, "splitvcursor_64.png", 22, 22 },
412 { Qt::SplitHCursor, 32, "splithcursor_32.png", 11, 11 },
413 { Qt::SplitHCursor, 48, "splithcursor_48.png", 16, 17 },
414 { Qt::SplitHCursor, 64, "splithcursor_64.png", 22, 22 },
415 { Qt::OpenHandCursor, 32, "openhandcursor_32.png", 10, 12 },
416 { Qt::OpenHandCursor, 48, "openhandcursor_48.png", 15, 16 },
417 { Qt::OpenHandCursor, 64, "openhandcursor_64.png", 20, 24 },
418 { Qt::ClosedHandCursor, 32, "closedhandcursor_32.png", 10, 12 },
419 { Qt::ClosedHandCursor, 48, "closedhandcursor_48.png", 15, 16 },
420 { Qt::ClosedHandCursor, 64, "closedhandcursor_64.png", 20, 24 },
421 { Qt::DragCopyCursor, 32, "dragcopycursor_32.png", 0, 0 },
422 { Qt::DragCopyCursor, 48, "dragcopycursor_48.png", 0, 0 },
423 { Qt::DragCopyCursor, 64, "dragcopycursor_64.png", 0, 0 },
424 { Qt::DragMoveCursor, 32, "dragmovecursor_32.png", 0, 0 },
425 { Qt::DragMoveCursor, 48, "dragmovecursor_48.png", 0, 0 },
426 { Qt::DragMoveCursor, 64, "dragmovecursor_64.png", 0, 0 },
427 { Qt::DragLinkCursor, 32, "draglinkcursor_32.png", 0, 0 },
428 { Qt::DragLinkCursor, 48, "draglinkcursor_48.png", 0, 0 },
429 { Qt::DragLinkCursor, 64, "draglinkcursor_64.png", 0, 0 }
430 };
431
432 const QSize cursorSize = screenCursorSize(screen);
433 const QWindowsCustomPngCursor *sEnd = pngCursors + sizeof(pngCursors) / sizeof(pngCursors[0]);
434 const QWindowsCustomPngCursor *bestFit = nullptr;
435 int sizeDelta = INT_MAX;
436 for (const QWindowsCustomPngCursor *s = pngCursors; s < sEnd; ++s) {
437 if (s->shape != cursorShape)
438 continue;
439 const int currentSizeDelta = qMax(s->size, cursorSize.width()) - qMin(s->size, cursorSize.width());
440 if (currentSizeDelta < sizeDelta) {
441 bestFit = s;
442 if (currentSizeDelta == 0)
443 break; // Perfect match found
444 sizeDelta = currentSizeDelta;
445 }
446 }
447
448 if (!bestFit)
449 return PixmapCursor();
450
451 const QPixmap rawImage(":/qt-project.org/windows/cursors/images/"_L1 +
452 QLatin1StringView(bestFit->fileName));
453 return PixmapCursor(rawImage, QPoint(bestFit->hotSpotX, bestFit->hotSpotY));
454}
455#endif // !QT_NO_IMAGEFORMAT_PNG
456
461
462HCURSOR QWindowsCursor::createCursorFromShape(Qt::CursorShape cursorShape, const QPlatformScreen *screen)
463{
464 Q_ASSERT(cursorShape != Qt::BitmapCursor);
465
466 static const QWindowsStandardCursorMapping standardCursors[] = {
467 { Qt::ArrowCursor, IDC_ARROW},
468 { Qt::UpArrowCursor, IDC_UPARROW },
469 { Qt::CrossCursor, IDC_CROSS },
470 { Qt::WaitCursor, IDC_WAIT },
471 { Qt::IBeamCursor, IDC_IBEAM },
472 { Qt::SizeVerCursor, IDC_SIZENS },
473 { Qt::SizeHorCursor, IDC_SIZEWE },
474 { Qt::SizeBDiagCursor, IDC_SIZENESW },
475 { Qt::SizeFDiagCursor, IDC_SIZENWSE },
476 { Qt::SizeAllCursor, IDC_SIZEALL },
477 { Qt::ForbiddenCursor, IDC_NO },
478 { Qt::WhatsThisCursor, IDC_HELP },
479 { Qt::BusyCursor, IDC_APPSTARTING },
480 { Qt::PointingHandCursor, IDC_HAND }
481 };
482
483 switch (cursorShape) {
484 case Qt::BlankCursor: {
485 QImage blank = QImage(systemCursorSize(), QImage::Format_Mono);
486 blank.fill(0); // ignore color table
487 return createBitmapCursor(blank, blank);
488 }
489 case Qt::SplitVCursor:
490 case Qt::SplitHCursor:
491 case Qt::OpenHandCursor:
492 case Qt::ClosedHandCursor:
493 case Qt::DragCopyCursor:
494 case Qt::DragMoveCursor:
495 case Qt::DragLinkCursor:
496 return QWindowsCursor::createPixmapCursor(customCursor(cursorShape, screen));
497 default:
498 break;
499 }
500
501 // Load available standard cursors from resources
502 for (const QWindowsStandardCursorMapping &s : standardCursors) {
503 if (s.shape == cursorShape) {
504 return static_cast<HCURSOR>(LoadImage(nullptr, s.resource, IMAGE_CURSOR,
505 0, 0, LR_DEFAULTSIZE | LR_SHARED));
506 }
507 }
508
509 qWarning("%s: Invalid cursor shape %d", __FUNCTION__, cursorShape);
510 return nullptr;
511}
512
513/*!
514 \brief Return cached standard cursor resources or create new ones.
515*/
516
518{
519 StandardCursorCache::Iterator it = m_standardCursorCache.find(shape);
520 if (it == m_standardCursorCache.end()) {
521 if (const HCURSOR hc = QWindowsCursor::createCursorFromShape(shape, m_screen))
522 it = m_standardCursorCache.insert(shape, CursorHandlePtr(new CursorHandle(hc)));
523 }
524 return it != m_standardCursorCache.end() ? it.value() : CursorHandlePtr(new CursorHandle);
525}
526
527HCURSOR QWindowsCursor::m_overriddenCursor = nullptr;
528QCursor QWindowsCursor::m_overrideCursor = QCursor();
529POINT QWindowsCursor::m_cursorPositionCache = {0,0};
530
531/*!
532 \brief Return cached pixmap cursor or create new one.
533*/
534
536{
537 const qreal scaleFactor = QHighDpiScaling::factor(m_screen);
538 const QWindowsPixmapCursorCacheKey cacheKey(c, scaleFactor);
539 PixmapCursorCache::iterator it = m_pixmapCursorCache.find(cacheKey);
540 if (it == m_pixmapCursorCache.end()) {
541 if (m_pixmapCursorCache.size() > 50) {
542 // Prevent the cursor cache from growing indefinitely hitting GDI resource
543 // limits if new pixmap cursors are created repetitively by purging out
544 // all-noncurrent pixmap cursors (QTBUG-43515)
545 const HCURSOR currentCursor = GetCursor();
546 for (it = m_pixmapCursorCache.begin(); it != m_pixmapCursorCache.end() ; ) {
547 if (it.value()->handle() != currentCursor)
548 it = m_pixmapCursorCache.erase(it);
549 else
550 ++it;
551 }
552 }
553 const QPixmap pixmap = c.pixmap();
554 const HCURSOR hc = pixmap.isNull()
555 ? createBitmapCursor(c, scaleFactor)
556 : QWindowsCursor::createPixmapCursor(pixmap, c.hotSpot(), scaleFactor);
557 it = m_pixmapCursorCache.insert(cacheKey, CursorHandlePtr(new CursorHandle(hc)));
558 }
559 return it.value();
560}
561
562QWindowsCursor::QWindowsCursor(const QPlatformScreen *screen)
563 : m_screen(screen)
564{
565 static const bool dummy = initResources();
566 Q_UNUSED(dummy);
567}
568
569inline CursorHandlePtr QWindowsCursor::cursorHandle(const QCursor &cursor)
570{
571 return cursor.shape() == Qt::BitmapCursor
572 ? pixmapWindowCursor(cursor)
573 : standardWindowCursor(cursor.shape());
574}
575
576/*!
577 \brief Set a cursor on a window.
578
579 This is called frequently as the mouse moves over widgets in the window
580 (QLineEdits, etc).
581*/
582
583void QWindowsCursor::changeCursor(QCursor *cursorIn, QWindow *window)
584{
585 QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(window);
586 if (!platformWindow) // Desktop/Foreign window.
587 return;
588
589 if (!cursorIn) {
590 platformWindow->setCursor(CursorHandlePtr(new CursorHandle));
591 return;
592 }
593 const CursorHandlePtr wcursor = cursorHandle(*cursorIn);
594 if (wcursor->handle()) {
595 platformWindow->setCursor(wcursor);
596 } else {
597 qWarning("%s: Unable to obtain system cursor for %d",
598 __FUNCTION__, cursorIn->shape());
599 }
600}
601
603{
604 QScreen *qscreen = QGuiApplication::screenAt(QCursor::pos());
605 if (!qscreen)
606 qscreen = QGuiApplication::primaryScreen();
607 if (!qscreen || !qscreen->handle())
608 return nullptr;
609 return static_cast<QWindowsCursor *>(qscreen->handle()->cursor());
610}
611
612// QTBUG-69637: Override cursors can get reset externally when moving across
613// window borders. Enforce the cursor again (to be called from enter event).
615{
617 return;
619 if (!cursor)
620 return;
621 const CursorHandlePtr wcursor = cursor->cursorHandle(m_overrideCursor);
622 if (wcursor->handle() && wcursor->handle() != GetCursor())
623 SetCursor(wcursor->handle());
624}
625
626void QWindowsCursor::setOverrideCursor(const QCursor &cursor)
627{
629 if (!screenCursor)
630 screenCursor = this;
631 const CursorHandlePtr wcursor = screenCursor->cursorHandle(cursor);
632 if (const auto overrideCursor = wcursor->handle()) {
633 m_overrideCursor = cursor;
634 const HCURSOR previousCursor = SetCursor(overrideCursor);
635 if (m_overriddenCursor == nullptr)
636 m_overriddenCursor = previousCursor;
637 } else {
638 qWarning("%s: Unable to obtain system cursor for %d",
639 __FUNCTION__, cursor.shape());
640 }
641}
642
644{
645 if (m_overriddenCursor) {
646 SetCursor(m_overriddenCursor);
647 m_overriddenCursor = nullptr;
648 }
649 auto &windows = QWindowsContext::instance()->windows();
650 for (auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
651 if (it.value()->screen() == m_screen)
652 it.value()->setFlag(QWindowsWindow::RestoreOverrideCursor);
653 }
654}
655
656QPoint QWindowsCursor::mousePosition()
657{
658 POINT p;
659 if (GetCursorPos(&p))
660 m_cursorPositionCache = p;
661 return QPoint(m_cursorPositionCache.x, m_cursorPositionCache.y);
662}
663
665{
666 enum { cursorShowing = 0x1, cursorSuppressed = 0x2 }; // Windows 8: CURSOR_SUPPRESSED
667 CURSORINFO cursorInfo;
668 cursorInfo.cbSize = sizeof(CURSORINFO);
669 if (GetCursorInfo(&cursorInfo)) {
670 if (cursorInfo.flags & cursorShowing)
671 return State::Showing;
672 if (cursorInfo.flags & cursorSuppressed)
673 return State::Suppressed;
674 }
675 return State::Hidden;
676}
677
679{
680 return mousePosition();
681}
682
683void QWindowsCursor::setPos(const QPoint &pos)
684{
685 m_cursorPositionCache = {pos.x(), pos.y()};
686 SetCursorPos(pos.x() , pos.y());
687}
688
689/*
690 The standard size is 32x32, even though the cursor is actually just
691 16 pixels large. If a large cursor is set in the accessibility settings,
692 then the cursor increases with 8 pixels for each step.
693*/
695{
696 const auto cursorSizeSetting =
697 QWinRegistryKey(HKEY_CURRENT_USER, LR"(Control Panel\Cursors)")
698 .value<DWORD>(L"CursorBaseSize");
699 const int baseSize = screenCursorSize(m_screen).width() / 2;
700 if (!cursorSizeSetting)
701 return QSize(baseSize / 2, baseSize / 2);
702
703 // The registry values are dpi-independent, so we need to scale the result.
704 int cursorSizeValue = *cursorSizeSetting * m_screen->logicalDpi().first
705 / m_screen->logicalBaseDpi().first;
706
707 // map from registry value 32-256 to 0-14, and from there to pixels
708 cursorSizeValue = (cursorSizeValue - 2 * baseSize) / baseSize;
709 const int cursorSize = baseSize + cursorSizeValue * (baseSize / 2);
710 return QSize(cursorSize, cursorSize);
711}
712
713QPixmap QWindowsCursor::dragDefaultCursor(Qt::DropAction action) const
714{
715 switch (action) {
716 case Qt::CopyAction:
717 if (m_copyDragCursor.isNull())
718 m_copyDragCursor = QWindowsCursor::customCursor(Qt::DragCopyCursor, m_screen).pixmap;
719 return m_copyDragCursor;
720 case Qt::TargetMoveAction:
721 case Qt::MoveAction:
722 if (m_moveDragCursor.isNull())
723 m_moveDragCursor = QWindowsCursor::customCursor(Qt::DragMoveCursor, m_screen).pixmap;
724 return m_moveDragCursor;
725 case Qt::LinkAction:
726 if (m_linkDragCursor.isNull())
727 m_linkDragCursor = QWindowsCursor::customCursor(Qt::DragLinkCursor, m_screen).pixmap;
728 return m_linkDragCursor;
729 default:
730 break;
731 }
732
733#if QT_CONFIG(imageformat_xpm)
734 static const char * const ignoreDragCursorXpmC[] = {
735 "24 30 3 1",
736 ". c None",
737 "a c #000000",
738 "X c #FFFFFF",
739 "aa......................",
740 "aXa.....................",
741 "aXXa....................",
742 "aXXXa...................",
743 "aXXXXa..................",
744 "aXXXXXa.................",
745 "aXXXXXXa................",
746 "aXXXXXXXa...............",
747 "aXXXXXXXXa..............",
748 "aXXXXXXXXXa.............",
749 "aXXXXXXaaaa.............",
750 "aXXXaXXa................",
751 "aXXaaXXa................",
752 "aXa..aXXa...............",
753 "aa...aXXa...............",
754 "a.....aXXa..............",
755 "......aXXa.....XXXX.....",
756 ".......aXXa..XXaaaaXX...",
757 ".......aXXa.XaaaaaaaaX..",
758 "........aa.XaaaXXXXaaaX.",
759 "...........XaaaaX..XaaX.",
760 "..........XaaXaaaX..XaaX",
761 "..........XaaXXaaaX.XaaX",
762 "..........XaaX.XaaaXXaaX",
763 "..........XaaX..XaaaXaaX",
764 "...........XaaX..XaaaaX.",
765 "...........XaaaXXXXaaaX.",
766 "............XaaaaaaaaX..",
767 ".............XXaaaaXX...",
768 "...............XXXX....."};
769#endif
770
771 if (m_ignoreDragCursor.isNull()) {
772 HCURSOR cursor = LoadCursor(nullptr, IDC_NO);
773 ICONINFO iconInfo = {0, 0, 0, nullptr, nullptr};
774 GetIconInfo(cursor, &iconInfo);
775 BITMAP bmColor = {0, 0, 0, 0, 0, 0, nullptr};
776
777 if (iconInfo.hbmColor
778 && GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bmColor)
779 && bmColor.bmWidth == bmColor.bmWidthBytes / 4) {
780 const int colorBitsLength = bmColor.bmHeight * bmColor.bmWidthBytes;
781 auto *colorBits = new uchar[colorBitsLength];
782 GetBitmapBits(iconInfo.hbmColor, colorBitsLength, colorBits);
783 const QImage colorImage(colorBits, bmColor.bmWidth, bmColor.bmHeight,
784 bmColor.bmWidthBytes, QImage::Format_ARGB32);
785
786 m_ignoreDragCursor = QPixmap::fromImage(colorImage);
787 delete [] colorBits;
788#if QT_CONFIG(imageformat_xpm)
789 } else {
790 m_ignoreDragCursor = QPixmap(ignoreDragCursorXpmC);
791#endif
792 }
793
794 DeleteObject(iconInfo.hbmMask);
795 DeleteObject(iconInfo.hbmColor);
796 DestroyCursor(cursor);
797 }
798 return m_ignoreDragCursor;
799}
800
801HCURSOR QWindowsCursor::hCursor(const QCursor &c) const
802{
803 const Qt::CursorShape shape = c.shape();
804 if (shape == Qt::BitmapCursor) {
805 const qreal scaleFactor = QHighDpiScaling::factor(m_screen);
806 const auto pit = m_pixmapCursorCache.constFind(QWindowsPixmapCursorCacheKey(c, scaleFactor));
807 if (pit != m_pixmapCursorCache.constEnd())
808 return pit.value()->handle();
809 } else {
810 const auto sit = m_standardCursorCache.constFind(shape);
811 if (sit != m_standardCursorCache.constEnd())
812 return sit.value()->handle();
813 }
814 return HCURSOR(nullptr);
815}
816
817/*!
818 \class QWindowsWindowCursor
819 \brief Per-Window cursor. Contains a QCursor and manages its associated system
820 cursor handle resource.
821
822 \internal
823 \sa QWindowsCursor
824*/
825
826QT_END_NAMESPACE
827
828#endif // !QT_NO_CURSOR
\inmodule QtCore\reentrant
Definition qpoint.h:30
Singleton container for all relevant information.
static QWindowsContext * instance()
Platform cursor implementation.
static bool hasOverrideCursor()
void setOverrideCursor(const QCursor &cursor) override
Reimplement this function in subclass to set an override cursor on the associated screen and return t...
void changeCursor(QCursor *widgetCursor, QWindow *widget) override
Set a cursor on a window.
HCURSOR hCursor(const QCursor &c) const
void clearOverrideCursor() override
Reimplement this function in subclass to clear the override cursor.
CursorHandlePtr standardWindowCursor(Qt::CursorShape s=Qt::ArrowCursor)
Return cached standard cursor resources or create new ones.
static State cursorState()
static void enforceOverrideCursor()
QPixmap dragDefaultCursor(Qt::DropAction action) const
CursorHandlePtr pixmapWindowCursor(const QCursor &c)
Return cached pixmap cursor or create new one.
QSize size() const override
Returns the size of the cursor, in native pixels.
QPoint pos() const override
Raster or OpenGL Window.
static bool initResources()
static QSize screenCursorSize(const QPlatformScreen *screen=nullptr)
static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits, QPoint hotSpot=QPoint(-1, -1), bool invb=false, bool invm=false)
static QSize systemCursorSize()
static QWindowsCursor * cursorForPointerScreen()
static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor=1)
QWindowsPixmapCursorCacheKey(const QCursor &c, qreal scaleFactor)