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

source code of qtbase/src/plugins/platforms/eglfs/api/qeglfscursor.cpp