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;
528HCURSOR QWindowsCursor::m_overrideCursor = nullptr;
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
602// QTBUG-69637: Override cursors can get reset externally when moving across
603// window borders. Enforce the cursor again (to be called from enter event).
605{
606 if (hasOverrideCursor() && m_overrideCursor != GetCursor())
607 SetCursor(m_overrideCursor);
608}
609
610void QWindowsCursor::setOverrideCursor(const QCursor &cursor)
611{
612 const CursorHandlePtr wcursor = cursorHandle(cursor);
613 if (const auto overrideCursor = wcursor->handle()) {
614 m_overrideCursor = overrideCursor;
615 const HCURSOR previousCursor = SetCursor(overrideCursor);
616 if (m_overriddenCursor == nullptr)
617 m_overriddenCursor = previousCursor;
618 } else {
619 qWarning("%s: Unable to obtain system cursor for %d",
620 __FUNCTION__, cursor.shape());
621 }
622}
623
625{
626 if (m_overriddenCursor) {
627 SetCursor(m_overriddenCursor);
628 m_overriddenCursor = m_overrideCursor = nullptr;
629 }
630 auto &windows = QWindowsContext::instance()->windows();
631 for (auto it = windows.cbegin(), end = windows.cend(); it != end; ++it) {
632 if (it.value()->screen() == m_screen)
633 it.value()->setFlag(QWindowsWindow::RestoreOverrideCursor);
634 }
635}
636
637QPoint QWindowsCursor::mousePosition()
638{
639 POINT p;
640 if (GetCursorPos(&p))
641 m_cursorPositionCache = p;
642 return QPoint(m_cursorPositionCache.x, m_cursorPositionCache.y);
643}
644
646{
647 enum { cursorShowing = 0x1, cursorSuppressed = 0x2 }; // Windows 8: CURSOR_SUPPRESSED
648 CURSORINFO cursorInfo;
649 cursorInfo.cbSize = sizeof(CURSORINFO);
650 if (GetCursorInfo(&cursorInfo)) {
651 if (cursorInfo.flags & cursorShowing)
652 return State::Showing;
653 if (cursorInfo.flags & cursorSuppressed)
654 return State::Suppressed;
655 }
656 return State::Hidden;
657}
658
660{
661 return mousePosition();
662}
663
664void QWindowsCursor::setPos(const QPoint &pos)
665{
666 m_cursorPositionCache = {pos.x(), pos.y()};
667 SetCursorPos(pos.x() , pos.y());
668}
669
670/*
671 The standard size is 32x32, even though the cursor is actually just
672 16 pixels large. If a large cursor is set in the accessibility settings,
673 then the cursor increases with 8 pixels for each step.
674*/
676{
677 const auto cursorSizeSetting =
678 QWinRegistryKey(HKEY_CURRENT_USER, LR"(Control Panel\Cursors)")
679 .value<DWORD>(L"CursorBaseSize");
680 const int baseSize = screenCursorSize(m_screen).width() / 2;
681 if (!cursorSizeSetting)
682 return QSize(baseSize / 2, baseSize / 2);
683
684 // The registry values are dpi-independent, so we need to scale the result.
685 int cursorSizeValue = *cursorSizeSetting * m_screen->logicalDpi().first
686 / m_screen->logicalBaseDpi().first;
687
688 // map from registry value 32-256 to 0-14, and from there to pixels
689 cursorSizeValue = (cursorSizeValue - 2 * baseSize) / baseSize;
690 const int cursorSize = baseSize + cursorSizeValue * (baseSize / 2);
691 return QSize(cursorSize, cursorSize);
692}
693
694QPixmap QWindowsCursor::dragDefaultCursor(Qt::DropAction action) const
695{
696 switch (action) {
697 case Qt::CopyAction:
698 if (m_copyDragCursor.isNull())
699 m_copyDragCursor = QWindowsCursor::customCursor(Qt::DragCopyCursor, m_screen).pixmap;
700 return m_copyDragCursor;
701 case Qt::TargetMoveAction:
702 case Qt::MoveAction:
703 if (m_moveDragCursor.isNull())
704 m_moveDragCursor = QWindowsCursor::customCursor(Qt::DragMoveCursor, m_screen).pixmap;
705 return m_moveDragCursor;
706 case Qt::LinkAction:
707 if (m_linkDragCursor.isNull())
708 m_linkDragCursor = QWindowsCursor::customCursor(Qt::DragLinkCursor, m_screen).pixmap;
709 return m_linkDragCursor;
710 default:
711 break;
712 }
713
714#if QT_CONFIG(imageformat_xpm)
715 static const char * const ignoreDragCursorXpmC[] = {
716 "24 30 3 1",
717 ". c None",
718 "a c #000000",
719 "X c #FFFFFF",
720 "aa......................",
721 "aXa.....................",
722 "aXXa....................",
723 "aXXXa...................",
724 "aXXXXa..................",
725 "aXXXXXa.................",
726 "aXXXXXXa................",
727 "aXXXXXXXa...............",
728 "aXXXXXXXXa..............",
729 "aXXXXXXXXXa.............",
730 "aXXXXXXaaaa.............",
731 "aXXXaXXa................",
732 "aXXaaXXa................",
733 "aXa..aXXa...............",
734 "aa...aXXa...............",
735 "a.....aXXa..............",
736 "......aXXa.....XXXX.....",
737 ".......aXXa..XXaaaaXX...",
738 ".......aXXa.XaaaaaaaaX..",
739 "........aa.XaaaXXXXaaaX.",
740 "...........XaaaaX..XaaX.",
741 "..........XaaXaaaX..XaaX",
742 "..........XaaXXaaaX.XaaX",
743 "..........XaaX.XaaaXXaaX",
744 "..........XaaX..XaaaXaaX",
745 "...........XaaX..XaaaaX.",
746 "...........XaaaXXXXaaaX.",
747 "............XaaaaaaaaX..",
748 ".............XXaaaaXX...",
749 "...............XXXX....."};
750#endif
751
752 if (m_ignoreDragCursor.isNull()) {
753 HCURSOR cursor = LoadCursor(nullptr, IDC_NO);
754 ICONINFO iconInfo = {0, 0, 0, nullptr, nullptr};
755 GetIconInfo(cursor, &iconInfo);
756 BITMAP bmColor = {0, 0, 0, 0, 0, 0, nullptr};
757
758 if (iconInfo.hbmColor
759 && GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bmColor)
760 && bmColor.bmWidth == bmColor.bmWidthBytes / 4) {
761 const int colorBitsLength = bmColor.bmHeight * bmColor.bmWidthBytes;
762 auto *colorBits = new uchar[colorBitsLength];
763 GetBitmapBits(iconInfo.hbmColor, colorBitsLength, colorBits);
764 const QImage colorImage(colorBits, bmColor.bmWidth, bmColor.bmHeight,
765 bmColor.bmWidthBytes, QImage::Format_ARGB32);
766
767 m_ignoreDragCursor = QPixmap::fromImage(colorImage);
768 delete [] colorBits;
769#if QT_CONFIG(imageformat_xpm)
770 } else {
771 m_ignoreDragCursor = QPixmap(ignoreDragCursorXpmC);
772#endif
773 }
774
775 DeleteObject(iconInfo.hbmMask);
776 DeleteObject(iconInfo.hbmColor);
777 DestroyCursor(cursor);
778 }
779 return m_ignoreDragCursor;
780}
781
782HCURSOR QWindowsCursor::hCursor(const QCursor &c) const
783{
784 const Qt::CursorShape shape = c.shape();
785 if (shape == Qt::BitmapCursor) {
786 const qreal scaleFactor = QHighDpiScaling::factor(m_screen);
787 const auto pit = m_pixmapCursorCache.constFind(QWindowsPixmapCursorCacheKey(c, scaleFactor));
788 if (pit != m_pixmapCursorCache.constEnd())
789 return pit.value()->handle();
790 } else {
791 const auto sit = m_standardCursorCache.constFind(shape);
792 if (sit != m_standardCursorCache.constEnd())
793 return sit.value()->handle();
794 }
795 return HCURSOR(nullptr);
796}
797
798/*!
799 \class QWindowsWindowCursor
800 \brief Per-Window cursor. Contains a QCursor and manages its associated system
801 cursor handle resource.
802
803 \internal
804 \sa QWindowsCursor
805*/
806
807QT_END_NAMESPACE
808
809#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 HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor=1)
QWindowsPixmapCursorCacheKey(const QCursor &c, qreal scaleFactor)