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>
18#include <QtGui/private/qguiapplication_p.h>
19#include <QtOpenGL/private/qopenglvertexarrayobject_p.h>
21#ifndef GL_VERTEX_ARRAY_BINDING
22#define GL_VERTEX_ARRAY_BINDING 0x85B5
27using namespace Qt::StringLiterals;
29QEglFSCursor::QEglFSCursor(QPlatformScreen *screen)
31 m_screen(
static_cast<QEglFSScreen *>(screen)),
32 m_activeScreen(
nullptr),
33 m_deviceListener(
nullptr),
34 m_updateRequested(
false)
36 QByteArray hideCursorVal = qgetenv(
"QT_QPA_EGLFS_HIDECURSOR");
37 if (!hideCursorVal.isEmpty())
38 m_visible = hideCursorVal.toInt() == 0;
42 int rotation = qEnvironmentVariableIntValue(
"QT_QPA_EGLFS_ROTATION");
44 m_rotationMatrix.rotate(rotation, 0, 0, 1);
52 QCursor cursor(Qt::ArrowCursor);
53 setCurrentCursor(&cursor);
56 m_deviceListener =
new QEglFSCursorDeviceListener(
this);
57 connect(QGuiApplicationPrivate::inputDeviceManager(), &QInputDeviceManager::deviceListChanged,
58 m_deviceListener, &QEglFSCursorDeviceListener::onDeviceListChanged);
62QEglFSCursor::~QEglFSCursor()
65 delete m_deviceListener;
68void QEglFSCursor::updateMouseStatus()
70 m_visible = m_deviceListener->hasMouse();
75 return QGuiApplicationPrivate::inputDeviceManager()->deviceCount(QInputDeviceManager::DeviceTypePointer) > 0;
80 if (type == QInputDeviceManager::DeviceTypePointer)
81 m_cursor->updateMouseStatus();
84void QEglFSCursor::resetResources()
86 m_cursor.customCursorPending = !m_cursor.customCursorImage.isNull();
89void QEglFSCursor::createShaderPrograms()
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"
97 " textureCoord = textureCoordEntry;\n"
98 " gl_Position = mat * vec4(vertexCoordEntry, 1.0, 1.0);\n"
101 static const char *textureFragmentProgram =
102 "uniform sampler2D texture;\n"
103 "varying highp vec2 textureCoord;\n"
105 " gl_FragColor = texture2D(texture, textureCoord).bgra;\n"
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);
116 gfx.textureEntry = gfx.program->uniformLocation(
"texture");
117 gfx.matEntry = gfx.program->uniformLocation(
"mat");
120void QEglFSCursor::createCursorTexture(uint *texture,
const QImage &image)
122 Q_ASSERT(QOpenGLContext::currentContext());
123 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
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);
132 f->glTexImage2D(GL_TEXTURE_2D, 0 , GL_RGBA, image.width(), image.height(), 0 ,
133 GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
136void QEglFSCursor::initCursorAtlas()
138 static QByteArray json = qgetenv(
"QT_QPA_EGLFS_CURSOR");
140 json =
":/cursor.json";
142 QFile file(QString::fromUtf8(json));
143 if (!file.open(QFile::ReadOnly)) {
148 QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
149 QJsonObject object = doc.object();
151 const QString atlas = object.value(
"image"_L1).toString();
152 const int cursorsPerRow = object.value(
"cursorsPerRow"_L1).toInt();
153 const QJsonArray hotSpots = object.value(
"hotSpots"_L1).toArray();
155 if (atlas.isEmpty() || cursorsPerRow <= 0 || cursorsPerRow > Qt::LastCursor + 1
156 || hotSpots.count() != Qt::LastCursor + 1)
158 qWarning(
"Invalid cursor atlas '%s', disabling the cursor. Expected a non-empty "
159 "'image', a 'cursorsPerRow' between 1 and %d, and %d 'hotSpots' entries.",
160 json.constData(), Qt::LastCursor + 1, Qt::LastCursor + 1);
165 m_cursorAtlas.cursorsPerRow = cursorsPerRow;
167 for (
int i = 0; i < hotSpots.count(); i++) {
168 QPoint hotSpot(hotSpots[i].toArray()[0].toDouble(), hotSpots[i].toArray()[1].toDouble());
169 m_cursorAtlas.hotSpots << hotSpot;
172 QImage image = QImage(atlas).convertToFormat(QImage::Format_ARGB32_Premultiplied);
173 if (image.isNull()) {
174 qWarning(
"Failed to load cursor atlas image '%s', disabling the cursor",
180 m_cursorAtlas.cursorWidth = image.width() / m_cursorAtlas.cursorsPerRow;
181 m_cursorAtlas.cursorHeight = image.height() / ((Qt::LastCursor + cursorsPerRow) / cursorsPerRow);
182 m_cursorAtlas.width = image.width();
183 m_cursorAtlas.height = image.height();
184 m_cursorAtlas.image = image;
188void QEglFSCursor::changeCursor(QCursor *cursor, QWindow *window)
191 const QRect oldCursorRect = cursorRect();
192 if (setCurrentCursor(cursor))
193 update(oldCursorRect | cursorRect(),
false);
196bool QEglFSCursor::setCurrentCursor(QCursor *cursor)
201 const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor;
202 if (m_cursor.shape == newShape && newShape != Qt::BitmapCursor)
205 if (m_cursor.shape == Qt::BitmapCursor) {
206 m_cursor.customCursorImage = QImage();
207 m_cursor.customCursorPending =
false;
209 m_cursor.shape = newShape;
210 if (newShape != Qt::BitmapCursor) {
211 const float ws = (
float)m_cursorAtlas.cursorWidth / m_cursorAtlas.width,
212 hs = (
float)m_cursorAtlas.cursorHeight / m_cursorAtlas.height;
213 m_cursor.textureRect = QRectF(ws * (m_cursor.shape % m_cursorAtlas.cursorsPerRow),
214 hs * (m_cursor.shape / m_cursorAtlas.cursorsPerRow),
216 m_cursor.hotSpot = m_cursorAtlas.hotSpots[m_cursor.shape];
217 m_cursor.useCustomCursor =
false;
218 m_cursor.size = QSize(m_cursorAtlas.cursorWidth, m_cursorAtlas.cursorHeight);
220 QImage image = cursor->pixmap().toImage();
221 m_cursor.textureRect = QRectF(0, 0, 1, 1);
222 m_cursor.hotSpot = cursor->hotSpot();
223 m_cursor.useCustomCursor =
false;
224 m_cursor.size = image.size();
225 m_cursor.customCursorImage = image;
226 m_cursor.customCursorPending =
true;
227 m_cursor.customCursorKey = m_cursor.customCursorImage.cacheKey();
241 m_allScreens(allScreens)
253bool QEglFSCursor::event(QEvent *e)
255 if (e->type() == QEvent::User + 1) {
256 CursorUpdateEvent *ev =
static_cast<CursorUpdateEvent *>(e);
257 m_updateRequested =
false;
258 if (!ev->allScreens()) {
259 QWindow *w = m_screen->topLevelAt(ev->pos());
261 QWindowSystemInterface::handleExposeEvent(w, ev->rect());
262 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
265 const auto windows = qGuiApp->topLevelWindows();
266 for (QWindow *w : windows)
267 QWindowSystemInterface::handleExposeEvent(w, w->geometry());
268 QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
272 return QPlatformCursor::event(e);
275void QEglFSCursor::update(
const QRect &rect,
bool allScreens)
277 if (!m_updateRequested) {
281 m_updateRequested =
true;
282 QCoreApplication::postEvent(
this,
new CursorUpdateEvent(m_cursor.pos, rect, allScreens));
286QRect QEglFSCursor::cursorRect()
const
288 return QRect(m_cursor.pos - m_cursor.hotSpot, m_cursor.size);
291QPoint QEglFSCursor::pos()
const
296void QEglFSCursor::setPos(
const QPoint &pos)
298 QGuiApplicationPrivate::inputDeviceManager()->setCursorPos(pos);
299 const QRect oldCursorRect = cursorRect();
301 update(oldCursorRect | cursorRect(),
false);
302 const auto siblings = m_screen->virtualSiblings();
303 for (QPlatformScreen *screen : siblings)
304 static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos);
307void QEglFSCursor::pointerEvent(
const QMouseEvent &event)
309 if (event.type() != QEvent::MouseMove)
311 const QRect oldCursorRect = cursorRect();
312 m_cursor.pos = event.globalPosition().toPoint();
313 update(oldCursorRect | cursorRect(),
false);
314 const auto siblings = m_screen->virtualSiblings();
315 for (QPlatformScreen *screen : siblings)
316 static_cast<QEglFSScreen *>(screen)->handleCursorMove(m_cursor.pos);
319void QEglFSCursor::paintOnScreen()
326 QRectF cr = cursorRect();
332 const auto siblings = m_screen->virtualSiblings();
333 for (QPlatformScreen *screen : siblings) {
334 if (screen->geometry().contains(cr.topLeft().toPoint() + m_cursor.hotSpot))
336 cr.translate(-screen->geometry().topLeft());
337 const QSize screenSize = screen->geometry().size();
338 const GLfloat x1 = 2 * (cr.left() / GLfloat(screenSize.width())) - 1;
339 const GLfloat x2 = 2 * (cr.right() / GLfloat(screenSize.width())) - 1;
340 const GLfloat y1 = 1 - (cr.top() / GLfloat(screenSize.height())) * 2;
341 const GLfloat y2 = 1 - (cr.bottom() / GLfloat(screenSize.height())) * 2;
342 QRectF r(QPointF(x1, y1), QPointF(x2, y2));
346 if (screen != m_activeScreen) {
347 m_activeScreen = screen;
349 update(cursorRect(),
true);
365 vaoHelper = QOpenGLVertexArrayObjectHelper::vertexArrayObjectHelperForContext(QOpenGLContext::currentContext());
367 static bool windowsChecked =
false;
368 static bool shouldSave =
true;
369 if (!windowsChecked) {
370 windowsChecked =
true;
371 QWindowList windows = QGuiApplication::allWindows();
372 if (!windows.isEmpty() && windows[0]->inherits(
"QQuickWindow"))
379 f->glGetIntegerv(GL_CURRENT_PROGRAM, &program);
380 f->glGetIntegerv(GL_TEXTURE_BINDING_2D, &texture);
381 f->glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTexture);
382 f->glGetIntegerv(GL_FRONT_FACE, &frontFace);
383 cull = f->glIsEnabled(GL_CULL_FACE);
384 depthTest = f->glIsEnabled(GL_DEPTH_TEST);
385 blend = f->glIsEnabled(GL_BLEND);
386 f->glGetIntegerv(GL_BLEND_SRC_RGB, blendFunc);
387 f->glGetIntegerv(GL_BLEND_SRC_ALPHA, blendFunc + 1);
388 f->glGetIntegerv(GL_BLEND_DST_RGB, blendFunc + 2);
389 f->glGetIntegerv(GL_BLEND_DST_ALPHA, blendFunc + 3);
390 scissor = f->glIsEnabled(GL_SCISSOR_TEST);
391 stencil = f->glIsEnabled(GL_STENCIL_TEST);
392 f->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &arrayBuf);
393 if (vaoHelper->isValid())
397 for (
int i = 0; i < 2; ++i) {
398 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &va[i].enabled);
399 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_SIZE, &va[i].size);
400 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_TYPE, &va[i].type);
401 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, &va[i].normalized);
402 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_STRIDE, &va[i].stride);
403 f->glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, &va[i].buffer);
404 f->glGetVertexAttribPointerv(i, GL_VERTEX_ATTRIB_ARRAY_POINTER, &va[i].pointer);
409 f->glUseProgram(program);
410 f->glBindTexture(GL_TEXTURE_2D, texture);
411 f->glActiveTexture(activeTexture);
412 f->glFrontFace(frontFace);
414 f->glEnable(GL_CULL_FACE);
416 f->glEnable(GL_DEPTH_TEST);
418 f->glDisable(GL_BLEND);
419 f->glBlendFuncSeparate(blendFunc[0], blendFunc[1], blendFunc[2], blendFunc[3]);
421 f->glEnable(GL_SCISSOR_TEST);
423 f->glEnable(GL_STENCIL_TEST);
424 f->glBindBuffer(GL_ARRAY_BUFFER, arrayBuf);
425 if (vaoHelper->isValid())
426 vaoHelper->glBindVertexArray(vao);
427 for (
int i = 0; i < 2; ++i) {
429 f->glEnableVertexAttribArray(i);
431 f->glDisableVertexAttribArray(i);
432 f->glBindBuffer(GL_ARRAY_BUFFER, va[i].buffer);
433 f->glVertexAttribPointer(i, va[i].size, va[i].type, va[i].normalized, va[i].stride, va[i].pointer);
455void QEglFSCursor::draw(
const QRectF &r)
457 Q_ASSERT(QOpenGLContext::currentContext());
458 QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
459 StateSaver stateSaver(f);
461 QEglFSCursorData &gfx =
static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData;
463 createShaderPrograms();
465 if (!gfx.atlasTexture) {
466 createCursorTexture(&gfx.atlasTexture, m_cursorAtlas.image);
468 if (m_cursor.shape != Qt::BitmapCursor)
469 m_cursor.useCustomCursor =
false;
473 if (m_cursor.shape == Qt::BitmapCursor && (m_cursor.customCursorPending || m_cursor.customCursorKey != gfx.customCursorKey)) {
475 createCursorTexture(&gfx.customCursorTexture, m_cursor.customCursorImage);
476 m_cursor.useCustomCursor =
true;
477 m_cursor.customCursorPending =
false;
478 gfx.customCursorKey = m_cursor.customCursorKey;
481 GLuint cursorTexture = !m_cursor.useCustomCursor ? gfx.atlasTexture : gfx.customCursorTexture;
482 Q_ASSERT(cursorTexture);
486 const GLfloat x1 = r.left();
487 const GLfloat x2 = r.right();
488 const GLfloat y1 = r.top();
489 const GLfloat y2 = r.bottom();
490 const GLfloat cursorCoordinates[] = {
497 const GLfloat s1 = m_cursor.textureRect.left();
498 const GLfloat s2 = m_cursor.textureRect.right();
499 const GLfloat t1 = m_cursor.textureRect.top();
500 const GLfloat t2 = m_cursor.textureRect.bottom();
501 const GLfloat textureCoordinates[] = {
508 f->glActiveTexture(GL_TEXTURE0);
509 f->glBindTexture(GL_TEXTURE_2D, cursorTexture);
511 if (stateSaver.vaoHelper->isValid())
512 stateSaver.vaoHelper->glBindVertexArray(0);
514 f->glBindBuffer(GL_ARRAY_BUFFER, 0);
516 gfx.program->enableAttributeArray(0);
517 gfx.program->enableAttributeArray(1);
518 gfx.program->setAttributeArray(0, cursorCoordinates, 2);
519 gfx.program->setAttributeArray(1, textureCoordinates, 2);
521 gfx.program->setUniformValue(gfx.textureEntry, 0);
522 gfx.program->setUniformValue(gfx.matEntry, m_rotationMatrix);
524 f->glDisable(GL_CULL_FACE);
525 f->glFrontFace(GL_CCW);
526 f->glEnable(GL_BLEND);
527 f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
528 f->glDisable(GL_DEPTH_TEST);
529 f->glDisable(GL_SCISSOR_TEST);
530 f->glDisable(GL_STENCIL_TEST);
532 f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
534 gfx.program->disableAttributeArray(0);
535 gfx.program->disableAttributeArray(1);
536 gfx.program->release();
541#include "moc_qeglfscursor_p.cpp"
CursorUpdateEvent(const QPoint &pos, const QRect &rect, bool allScreens)
Combined button and popup list for selecting options.
#define GL_VERTEX_ARRAY_BINDING
StateSaver(QOpenGLFunctions *func)
QOpenGLVertexArrayObjectHelper * vaoHelper