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
qeglfscursor.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
9
10#include <qpa/qwindowsysteminterface.h>
11#include <QtGui/QOpenGLContext>
12#include <QtGui/QOpenGLFunctions>
13#include <QtCore/QFile>
14#include <QtCore/QJsonDocument>
15#include <QtCore/QJsonArray>
16#include <QtCore/QJsonObject>
17
18#include <QtGui/private/qguiapplication_p.h>
19#include <QtOpenGL/private/qopenglvertexarrayobject_p.h>
20
21#ifndef GL_VERTEX_ARRAY_BINDING
22#define GL_VERTEX_ARRAY_BINDING 0x85B5
23#endif
24
26
27using namespace Qt::StringLiterals;
28
29QEglFSCursor::QEglFSCursor(QPlatformScreen *screen)
30 : m_visible(true),
31 m_screen(static_cast<QEglFSScreen *>(screen)),
32 m_activeScreen(nullptr),
33 m_deviceListener(nullptr),
34 m_updateRequested(false)
35{
36 QByteArray hideCursorVal = qgetenv("QT_QPA_EGLFS_HIDECURSOR");
37 if (!hideCursorVal.isEmpty())
38 m_visible = hideCursorVal.toInt() == 0;
39 if (!m_visible)
40 return;
41
42 int rotation = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ROTATION");
43 if (rotation)
44 m_rotationMatrix.rotate(rotation, 0, 0, 1);
45
46 // Try to load the cursor atlas. If this fails, m_visible is set to false and
47 // paintOnScreen() and setCurrentCursor() become no-ops.
48 initCursorAtlas();
49
50 // initialize the cursor
51#ifndef QT_NO_CURSOR
52 QCursor cursor(Qt::ArrowCursor);
53 setCurrentCursor(&cursor);
54#endif
55
56 m_deviceListener = new QEglFSCursorDeviceListener(this);
57 connect(QGuiApplicationPrivate::inputDeviceManager(), &QInputDeviceManager::deviceListChanged,
58 m_deviceListener, &QEglFSCursorDeviceListener::onDeviceListChanged);
59 updateMouseStatus();
60}
61
62QEglFSCursor::~QEglFSCursor()
63{
64 resetResources();
65 delete m_deviceListener;
66}
67
68void QEglFSCursor::updateMouseStatus()
69{
70 m_visible = m_deviceListener->hasMouse();
71}
72
73bool QEglFSCursorDeviceListener::hasMouse() const
74{
75 return QGuiApplicationPrivate::inputDeviceManager()->deviceCount(QInputDeviceManager::DeviceTypePointer) > 0;
76}
77
78void QEglFSCursorDeviceListener::onDeviceListChanged(QInputDeviceManager::DeviceType type)
79{
80 if (type == QInputDeviceManager::DeviceTypePointer)
81 m_cursor->updateMouseStatus();
82}
83
84void QEglFSCursor::resetResources()
85{
86 m_cursor.customCursorPending = !m_cursor.customCursorImage.isNull();
87}
88
89void QEglFSCursor::createShaderPrograms()
90{
91 static const char *textureVertexProgram =
92 "attribute highp vec2 vertexCoordEntry;\n"
93 "attribute highp vec2 textureCoordEntry;\n"
94 "varying highp vec2 textureCoord;\n"
95 "uniform highp mat4 mat;\n"
96 "void main() {\n"
97 " textureCoord = textureCoordEntry;\n"
98 " gl_Position = mat * vec4(vertexCoordEntry, 1.0, 1.0);\n"
99 "}\n";
100
101 static const char *textureFragmentProgram =
102 "uniform sampler2D texture;\n"
103 "varying highp vec2 textureCoord;\n"
104 "void main() {\n"
105 " gl_FragColor = texture2D(texture, textureCoord).bgra;\n"
106 "}\n";
107
108 QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData;
109 gfx.program.reset(new QOpenGLShaderProgram);
110 gfx.program->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, textureVertexProgram);
111 gfx.program->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, textureFragmentProgram);
112 gfx.program->bindAttributeLocation("vertexCoordEntry", 0);
113 gfx.program->bindAttributeLocation("textureCoordEntry", 1);
114 gfx.program->link();
115
116 gfx.textureEntry = gfx.program->uniformLocation("texture");
117 gfx.matEntry = gfx.program->uniformLocation("mat");
118}
119
120void QEglFSCursor::createCursorTexture(uint *texture, const QImage &image)
121{
122 Q_ASSERT(QOpenGLContext::currentContext());
123 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
124 if (!*texture)
125 f->glGenTextures(1, texture);
126 f->glBindTexture(GL_TEXTURE_2D, *texture);
127 f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
128 f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
129 f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
130 f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
131
132 f->glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, image.width(), image.height(), 0 /* border */,
133 GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
134}
135
136void QEglFSCursor::initCursorAtlas()
137{
138 static QByteArray json = qgetenv("QT_QPA_EGLFS_CURSOR");
139 if (json.isEmpty())
140 json = ":/cursor.json";
141
142 QFile file(QString::fromUtf8(json));
143 if (!file.open(QFile::ReadOnly)) {
144 m_visible = false;
145 return;
146 }
147
148 QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
149 QJsonObject object = doc.object();
150
151 QString atlas = object.value("image"_L1).toString();
152 Q_ASSERT(!atlas.isEmpty());
153
154 const int cursorsPerRow = object.value("cursorsPerRow"_L1).toInt();
155 if (cursorsPerRow <= 0 || cursorsPerRow > Qt::LastCursor + 1) {
156 qWarning("Invalid cursorsPerRow in cursor atlas %s", json.constData());
157 m_visible = false;
158 return;
159 }
160 m_cursorAtlas.cursorsPerRow = cursorsPerRow;
161
162 const QJsonArray hotSpots = object.value("hotSpots"_L1).toArray();
163 Q_ASSERT(hotSpots.count() == Qt::LastCursor + 1);
164 for (int i = 0; i < hotSpots.count(); i++) {
165 QPoint hotSpot(hotSpots[i].toArray()[0].toDouble(), hotSpots[i].toArray()[1].toDouble());
166 m_cursorAtlas.hotSpots << hotSpot;
167 }
168
169 QImage image = QImage(atlas).convertToFormat(QImage::Format_ARGB32_Premultiplied);
170 m_cursorAtlas.cursorWidth = image.width() / m_cursorAtlas.cursorsPerRow;
171 m_cursorAtlas.cursorHeight = image.height() / ((Qt::LastCursor + cursorsPerRow) / cursorsPerRow);
172 m_cursorAtlas.width = image.width();
173 m_cursorAtlas.height = image.height();
174 m_cursorAtlas.image = image;
175}
176
177#ifndef QT_NO_CURSOR
178void QEglFSCursor::changeCursor(QCursor *cursor, QWindow *window)
179{
180 Q_UNUSED(window);
181 const QRect oldCursorRect = cursorRect();
182 if (setCurrentCursor(cursor))
183 update(oldCursorRect | cursorRect(), false);
184}
185
186bool QEglFSCursor::setCurrentCursor(QCursor *cursor)
187{
188 if (!m_visible)
189 return false;
190
191 const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor;
192 if (m_cursor.shape == newShape && newShape != Qt::BitmapCursor)
193 return false;
194
195 if (m_cursor.shape == Qt::BitmapCursor) {
196 m_cursor.customCursorImage = QImage();
197 m_cursor.customCursorPending = false;
198 }
199 m_cursor.shape = newShape;
200 if (newShape != Qt::BitmapCursor) { // standard cursor
201 const float ws = (float)m_cursorAtlas.cursorWidth / m_cursorAtlas.width,
202 hs = (float)m_cursorAtlas.cursorHeight / m_cursorAtlas.height;
203 m_cursor.textureRect = QRectF(ws * (m_cursor.shape % m_cursorAtlas.cursorsPerRow),
204 hs * (m_cursor.shape / m_cursorAtlas.cursorsPerRow),
205 ws, hs);
206 m_cursor.hotSpot = m_cursorAtlas.hotSpots[m_cursor.shape];
207 m_cursor.useCustomCursor = false;
208 m_cursor.size = QSize(m_cursorAtlas.cursorWidth, m_cursorAtlas.cursorHeight);
209 } else {
210 QImage image = cursor->pixmap().toImage();
211 m_cursor.textureRect = QRectF(0, 0, 1, 1);
212 m_cursor.hotSpot = cursor->hotSpot();
213 m_cursor.useCustomCursor = false; // will get updated in the next render()
214 m_cursor.size = image.size();
215 m_cursor.customCursorImage = image;
216 m_cursor.customCursorPending = true;
217 m_cursor.customCursorKey = m_cursor.customCursorImage.cacheKey();
218 }
219
220 return true;
221}
222#endif
223
225{
226public:
227 CursorUpdateEvent(const QPoint &pos, const QRect &rect, bool allScreens)
228 : QEvent(QEvent::Type(QEvent::User + 1)),
229 m_pos(pos),
230 m_rect(rect),
231 m_allScreens(allScreens)
232 { }
233 QPoint pos() const { return m_pos; }
234 QRegion rect() const { return m_rect; }
235 bool allScreens() const { return m_allScreens; }
236
237private:
238 QPoint m_pos;
239 QRect m_rect;
240 bool m_allScreens;
241};
242
243bool QEglFSCursor::event(QEvent *e)
244{
245 if (e->type() == QEvent::User + 1) {
246 CursorUpdateEvent *ev = static_cast<CursorUpdateEvent *>(e);
247 m_updateRequested = false;
248 if (!ev->allScreens()) {
249 QWindow *w = m_screen->topLevelAt(ev->pos()); // works for the entire virtual desktop, no need to loop
250 if (w) {
251 QWindowSystemInterface::handleExposeEvent(w, ev->rect());
252 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
253 }
254 } else {
255 const auto windows = qGuiApp->topLevelWindows();
256 for (QWindow *w : windows)
257 QWindowSystemInterface::handleExposeEvent(w, w->geometry());
258 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
259 }
260 return true;
261 }
262 return QPlatformCursor::event(e);
263}
264
265void QEglFSCursor::update(const QRect &rect, bool allScreens)
266{
267 if (!m_updateRequested) {
268 // Must not flush the window system events directly from here since we are likely to
269 // be a called directly from QGuiApplication's processMouseEvents. Flushing events
270 // could cause reentering by dispatching more queued mouse events.
271 m_updateRequested = true;
272 QCoreApplication::postEvent(this, new CursorUpdateEvent(m_cursor.pos, rect, allScreens));
273 }
274}
275
276QRect QEglFSCursor::cursorRect() const
277{
278 return QRect(m_cursor.pos - m_cursor.hotSpot, m_cursor.size);
279}
280
281QPoint QEglFSCursor::pos() const
282{
283 return m_cursor.pos;
284}
285
286void QEglFSCursor::setPos(const QPoint &pos)
287{
288 QGuiApplicationPrivate::inputDeviceManager()->setCursorPos(pos);
289 const QRect oldCursorRect = cursorRect();
290 m_cursor.pos = pos;
291 update(oldCursorRect | cursorRect(), false);
292 const auto siblings = m_screen->virtualSiblings();
293 for (QPlatformScreen *screen : siblings)
294 static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos);
295}
296
297void QEglFSCursor::pointerEvent(const QMouseEvent &event)
298{
299 if (event.type() != QEvent::MouseMove)
300 return;
301 const QRect oldCursorRect = cursorRect();
302 m_cursor.pos = event.globalPosition().toPoint();
303 update(oldCursorRect | cursorRect(), false);
304 const auto siblings = m_screen->virtualSiblings();
305 for (QPlatformScreen *screen : siblings)
306 static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos);
307}
308
309void QEglFSCursor::paintOnScreen()
310{
311 if (!m_visible)
312 return;
313
314 // cr must be a QRectF, otherwise cr.right() and bottom() would be off by
315 // one in the calculations below.
316 QRectF cr = cursorRect(); // hotspot included
317
318 // Support virtual desktop too. Backends with multi-screen support (e.g. all
319 // variants of KMS/DRM) will enable this by default. In this case all
320 // screens are siblings of each other. When not enabled, the sibling list
321 // only contains m_screen itself.
322 const auto siblings = m_screen->virtualSiblings();
323 for (QPlatformScreen *screen : siblings) {
324 if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot))
325 {
326 cr.translate(-screen->geometry().topLeft());
327 const QSize screenSize = screen->geometry().size();
328 const GLfloat x1 = 2 * (cr.left() / GLfloat(screenSize.width())) - 1;
329 const GLfloat x2 = 2 * (cr.right() / GLfloat(screenSize.width())) - 1;
330 const GLfloat y1 = 1 - (cr.top() / GLfloat(screenSize.height())) * 2;
331 const GLfloat y2 = 1 - (cr.bottom() / GLfloat(screenSize.height())) * 2;
332 QRectF r(QPointF(x1, y1), QPointF(x2, y2));
333
334 draw(r);
335
336 if (screen != m_activeScreen) {
337 m_activeScreen = screen;
338 // Do not want a leftover cursor on the screen the cursor just left.
339 update(cursorRect(), true);
340 }
341
342 break;
343 }
344 }
345}
346
347// In order to prevent breaking code doing custom OpenGL rendering while
348// expecting the state in the context unchanged, save and restore all the state
349// we touch. The exception is Qt Quick where the scenegraph is known to be able
350// to deal with the changes we make.
352{
353 StateSaver(QOpenGLFunctions* func) {
354 f = func;
355 vaoHelper = QOpenGLVertexArrayObjectHelper::vertexArrayObjectHelperForContext(QOpenGLContext::currentContext());
356
357 static bool windowsChecked = false;
358 static bool shouldSave = true;
359 if (!windowsChecked) {
360 windowsChecked = true;
361 QWindowList windows = QGuiApplication::allWindows();
362 if (!windows.isEmpty() && windows[0]->inherits("QQuickWindow"))
363 shouldSave = false;
364 }
365 saved = shouldSave;
366 if (!shouldSave)
367 return;
368
369 f->glGetIntegerv(GL_CURRENT_PROGRAM, &program);
370 f->glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture);
371 f->glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTexture);
372 f->glGetIntegerv(GL_FRONT_FACE, &frontFace);
373 cull = f->glIsEnabled(GL_CULL_FACE);
374 depthTest = f->glIsEnabled(GL_DEPTH_TEST);
375 blend = f->glIsEnabled(GL_BLEND);
376 f->glGetIntegerv(GL_BLEND_SRC_RGB, blendFunc);
377 f->glGetIntegerv(GL_BLEND_SRC_ALPHA, blendFunc + 1);
378 f->glGetIntegerv(GL_BLEND_DST_RGB, blendFunc + 2);
379 f->glGetIntegerv(GL_BLEND_DST_ALPHA, blendFunc + 3);
380 scissor = f->glIsEnabled(GL_SCISSOR_TEST);
381 stencil = f->glIsEnabled(GL_STENCIL_TEST);
382 f->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &arrayBuf);
383 if (vaoHelper->isValid())
384 f->glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vao);
385 else
386 vao = 0;
387 for (int i = 0; i < 2; ++i) {
388 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &va[i].enabled);
389 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &va[i].size);
390 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &va[i].type);
391 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &va[i].normalized);
392 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &va[i].stride);
393 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &va[i].buffer);
394 f->glGetVertexAttribPointerv(i, GL_VERTEX_ATTRIB_ARRAY_POINTER, &va[i].pointer);
395 }
396 }
398 if (saved) {
399 f->glUseProgram(program);
400 f->glBindTexture(GL_TEXTURE_2D, texture);
401 f->glActiveTexture(activeTexture);
402 f->glFrontFace(frontFace);
403 if (cull)
404 f->glEnable(GL_CULL_FACE);
405 if (depthTest)
406 f->glEnable(GL_DEPTH_TEST);
407 if (!blend)
408 f->glDisable(GL_BLEND);
409 f->glBlendFuncSeparate(blendFunc[0], blendFunc[1], blendFunc[2], blendFunc[3]);
410 if (scissor)
411 f->glEnable(GL_SCISSOR_TEST);
412 if (stencil)
413 f->glEnable(GL_STENCIL_TEST);
414 f->glBindBuffer(GL_ARRAY_BUFFER, arrayBuf);
415 if (vaoHelper->isValid())
416 vaoHelper->glBindVertexArray(vao);
417 for (int i = 0; i < 2; ++i) {
418 if (va[i].enabled)
419 f->glEnableVertexAttribArray(i);
420 else
421 f->glDisableVertexAttribArray(i);
422 f->glBindBuffer(GL_ARRAY_BUFFER, va[i].buffer);
423 f->glVertexAttribPointer(i, va[i].size, va[i].type, va[i].normalized, va[i].stride, va[i].pointer);
424 }
425 }
426 }
429 bool saved;
434 bool cull;
436 bool blend;
443};
444
445void QEglFSCursor::draw(const QRectF &r)
446{
447 Q_ASSERT(QOpenGLContext::currentContext());
448 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
449 StateSaver stateSaver(f);
450
451 QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData;
452 if (!gfx.program) {
453 createShaderPrograms();
454
455 if (!gfx.atlasTexture) {
456 createCursorTexture(&gfx.atlasTexture, m_cursorAtlas.image);
457
458 if (m_cursor.shape != Qt::BitmapCursor)
459 m_cursor.useCustomCursor = false;
460 }
461 }
462
463 if (m_cursor.shape == Qt::BitmapCursor && (m_cursor.customCursorPending || m_cursor.customCursorKey != gfx.customCursorKey)) {
464 // upload the custom cursor
465 createCursorTexture(&gfx.customCursorTexture, m_cursor.customCursorImage);
466 m_cursor.useCustomCursor = true;
467 m_cursor.customCursorPending = false;
468 gfx.customCursorKey = m_cursor.customCursorKey;
469 }
470
471 GLuint cursorTexture = !m_cursor.useCustomCursor ? gfx.atlasTexture : gfx.customCursorTexture;
472 Q_ASSERT(cursorTexture);
473
474 gfx.program->bind();
475
476 const GLfloat x1 = r.left();
477 const GLfloat x2 = r.right();
478 const GLfloat y1 = r.top();
479 const GLfloat y2 = r.bottom();
480 const GLfloat cursorCoordinates[] = {
481 x1, y2,
482 x2, y2,
483 x1, y1,
484 x2, y1
485 };
486
487 const GLfloat s1 = m_cursor.textureRect.left();
488 const GLfloat s2 = m_cursor.textureRect.right();
489 const GLfloat t1 = m_cursor.textureRect.top();
490 const GLfloat t2 = m_cursor.textureRect.bottom();
491 const GLfloat textureCoordinates[] = {
492 s1, t2,
493 s2, t2,
494 s1, t1,
495 s2, t1
496 };
497
498 f->glActiveTexture(GL_TEXTURE0);
499 f->glBindTexture(GL_TEXTURE_2D, cursorTexture);
500
501 if (stateSaver.vaoHelper->isValid())
502 stateSaver.vaoHelper->glBindVertexArray(0);
503
504 f->glBindBuffer(GL_ARRAY_BUFFER, 0);
505
506 gfx.program->enableAttributeArray(0);
507 gfx.program->enableAttributeArray(1);
508 gfx.program->setAttributeArray(0, cursorCoordinates, 2);
509 gfx.program->setAttributeArray(1, textureCoordinates, 2);
510
511 gfx.program->setUniformValue(gfx.textureEntry, 0);
512 gfx.program->setUniformValue(gfx.matEntry, m_rotationMatrix);
513
514 f->glDisable(GL_CULL_FACE);
515 f->glFrontFace(GL_CCW);
516 f->glEnable(GL_BLEND);
517 f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
518 f->glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top
519 f->glDisable(GL_SCISSOR_TEST);
520 f->glDisable(GL_STENCIL_TEST);
521
522 f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
523
524 gfx.program->disableAttributeArray(0);
525 gfx.program->disableAttributeArray(1);
526 gfx.program->release();
527}
528
529QT_END_NAMESPACE
530
531#include "moc_qeglfscursor_p.cpp"
CursorUpdateEvent(const QPoint &pos, const QRect &rect, bool allScreens)
QPoint pos() const
QRegion rect() const
bool allScreens() const
Combined button and popup list for selecting options.
#define GL_VERTEX_ARRAY_BINDING
GLvoid * pointer
StateSaver(QOpenGLFunctions *func)
QOpenGLFunctions * f
QOpenGLVertexArrayObjectHelper * vaoHelper
GLint activeTexture
GLint blendFunc[4]