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
qeglfskmsgbmcursor.cpp
Go to the documentation of this file.
1// Copyright (C) 2015 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// Copyright (C) 2016 The Qt Company Ltd.
3// Copyright (C) 2016 Pelagicore AG
4// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5// Qt-Security score:significant reason:default
6
10
11#include <private/qeglfskmsintegration_p.h>
12
13#include <QtCore/QFile>
14#include <QtCore/QJsonDocument>
15#include <QtCore/QJsonObject>
16#include <QtCore/QJsonArray>
17#include <QtCore/QLoggingCategory>
18#include <QtGui/QPainter>
19#include <QtGui/private/qguiapplication_p.h>
20
21#include <xf86drm.h>
22#include <xf86drmMode.h>
23
24#ifndef DRM_CAP_CURSOR_WIDTH
25#define DRM_CAP_CURSOR_WIDTH 0x8
26#endif
27
28#ifndef DRM_CAP_CURSOR_HEIGHT
29#define DRM_CAP_CURSOR_HEIGHT 0x9
30#endif
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36QEglFSKmsGbmCursor::QEglFSKmsGbmCursor(QEglFSKmsGbmScreen *screen)
37 : m_screen(screen)
38 , m_cursorSize(64, 64) // 64x64 is the old standard size, we now try to query the real size below
39 , m_bo(nullptr)
40 , m_cursorImage(nullptr, nullptr, 0, 0, 0, 0)
41 , m_state(CursorPendingVisible)
42 , m_deviceListener(nullptr)
43{
44 QByteArray hideCursorVal = qgetenv("QT_QPA_EGLFS_HIDECURSOR");
45 if (!hideCursorVal.isEmpty() && hideCursorVal.toInt()) {
46 m_state = CursorDisabled;
47 return;
48 }
49
50 uint64_t width, height;
51 if ((drmGetCap(m_screen->device()->fd(), DRM_CAP_CURSOR_WIDTH, &width) == 0)
52 && (drmGetCap(m_screen->device()->fd(), DRM_CAP_CURSOR_HEIGHT, &height) == 0)) {
53 m_cursorSize.setWidth(width);
54 m_cursorSize.setHeight(height);
55 }
56
57 m_bo = gbm_bo_create(static_cast<QEglFSKmsGbmDevice *>(m_screen->device())->gbmDevice(), m_cursorSize.width(), m_cursorSize.height(),
58 GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR_64X64 | GBM_BO_USE_WRITE);
59 if (!m_bo) {
60 qWarning("Could not create buffer for cursor!");
61 } else {
62 initCursorAtlas();
63 }
64
65 m_deviceListener = new QEglFSKmsGbmCursorDeviceListener(this);
66 connect(QGuiApplicationPrivate::inputDeviceManager(), &QInputDeviceManager::deviceListChanged,
67 m_deviceListener, &QEglFSKmsGbmCursorDeviceListener::onDeviceListChanged);
68 if (!m_deviceListener->hasMouse())
69 m_state = CursorPendingHidden;
70
71#ifndef QT_NO_CURSOR
72 QCursor cursor(Qt::ArrowCursor);
73 changeCursor(&cursor, nullptr);
74#endif
75 setPos(QPoint(0, 0));
76}
77
79{
80 delete m_deviceListener;
81
82 for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
83 QEglFSKmsScreen *kmsScreen = static_cast<QEglFSKmsScreen *>(screen);
84 drmModeSetCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, 0, 0, 0);
85 drmModeMoveCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, 0, 0);
86 }
87
88 if (m_bo) {
89 gbm_bo_destroy(m_bo);
90 m_bo = nullptr;
91 }
92}
93
95{
96 const bool wasVisible = m_state == CursorVisible;
97 const bool visible = m_deviceListener->hasMouse();
98 if (visible == wasVisible)
99 return;
100
101 m_state = visible ? CursorPendingVisible : CursorPendingHidden;
102
103#ifndef QT_NO_CURSOR
104 changeCursor(nullptr, m_screen->topLevelAt(pos()));
105#endif
106}
107
109{
110 return QGuiApplicationPrivate::inputDeviceManager()->deviceCount(QInputDeviceManager::DeviceTypePointer) > 0;
111}
112
113void QEglFSKmsGbmCursorDeviceListener::onDeviceListChanged(QInputDeviceManager::DeviceType type)
114{
115 if (type == QInputDeviceManager::DeviceTypePointer)
116 m_cursor->updateMouseStatus();
117}
118
119void QEglFSKmsGbmCursor::pointerEvent(const QMouseEvent &event)
120{
121 setPos(event.globalPosition().toPoint());
122}
123
124#ifndef QT_NO_CURSOR
125void QEglFSKmsGbmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
126{
127 Q_UNUSED(window);
128
129 if (!m_bo)
130 return;
131
132 if (m_state == CursorPendingHidden) {
133 m_state = CursorHidden;
134 for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
135 QEglFSKmsScreen *kmsScreen = static_cast<QEglFSKmsScreen *>(screen);
136 drmModeSetCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, 0, 0, 0);
137 }
138 }
139
140 if (m_state == CursorHidden || m_state == CursorDisabled)
141 return;
142
143 const Qt::CursorShape newShape = windowCursor ? windowCursor->shape() : Qt::ArrowCursor;
144 if (newShape == Qt::BitmapCursor) {
145 m_cursorImage.set(windowCursor->pixmap().toImage(),
146 windowCursor->hotSpot().x(),
147 windowCursor->hotSpot().y());
148 } else {
149 // Standard cursor, look up in atlas
150 const int width = m_cursorAtlas.cursorWidth;
151 const int height = m_cursorAtlas.cursorHeight;
152 const qreal ws = (qreal)m_cursorAtlas.cursorWidth / m_cursorAtlas.width;
153 const qreal hs = (qreal)m_cursorAtlas.cursorHeight / m_cursorAtlas.height;
154
155 QRect textureRect(ws * (newShape % m_cursorAtlas.cursorsPerRow) * m_cursorAtlas.width,
156 hs * (newShape / m_cursorAtlas.cursorsPerRow) * m_cursorAtlas.height,
157 width,
158 height);
159 QPoint hotSpot = m_cursorAtlas.hotSpots[newShape];
160 m_cursorImage.set(m_cursorAtlas.image.copy(textureRect),
161 hotSpot.x(),
162 hotSpot.y());
163 }
164
165 if (m_cursorImage.image()->width() > m_cursorSize.width() || m_cursorImage.image()->height() > m_cursorSize.height())
166 qWarning("Cursor larger than %dx%d, cursor will be clipped.", m_cursorSize.width(), m_cursorSize.height());
167
168 QImage cursorImage(m_cursorSize, QImage::Format_ARGB32);
169 cursorImage.fill(Qt::transparent);
170
171 QPainter painter;
172 painter.begin(&cursorImage);
173 painter.drawImage(0, 0, *m_cursorImage.image());
174 painter.end();
175
176 gbm_bo_write(m_bo, cursorImage.constBits(), cursorImage.sizeInBytes());
177
178 uint32_t handle = gbm_bo_get_handle(m_bo).u32;
179
180 if (m_state == CursorPendingVisible)
181 m_state = CursorVisible;
182
183 for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
184 QEglFSKmsScreen *kmsScreen = static_cast<QEglFSKmsScreen *>(screen);
185 if (kmsScreen->isCursorOutOfRange())
186 continue;
187 int status = drmModeSetCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, handle,
188 m_cursorSize.width(), m_cursorSize.height());
189 if (status != 0)
190 qWarning("Could not set cursor on screen %s: %d", kmsScreen->name().toLatin1().constData(), status);
191 }
192}
193#endif // QT_NO_CURSOR
194
196{
197 return m_pos;
198}
199
200void QEglFSKmsGbmCursor::setPos(const QPoint &pos)
201{
202 for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
203 QEglFSKmsScreen *kmsScreen = static_cast<QEglFSKmsScreen *>(screen);
204 const QRect screenGeom = kmsScreen->geometry();
205 const QPoint origin = screenGeom.topLeft();
206 const QPoint localPos = pos - origin;
207 const QPoint adjustedLocalPos = localPos - m_cursorImage.hotspot();
208
209 if (localPos.x() < 0 || localPos.y() < 0
210 || localPos.x() >= screenGeom.width() || localPos.y() >= screenGeom.height())
211 {
212 if (!kmsScreen->isCursorOutOfRange()) {
213 kmsScreen->setCursorOutOfRange(true);
214 drmModeSetCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, 0, 0, 0);
215 }
216 } else {
217 int ret;
218 if (kmsScreen->isCursorOutOfRange() && m_bo) {
219 kmsScreen->setCursorOutOfRange(false);
220 uint32_t handle = gbm_bo_get_handle(m_bo).u32;
221 ret = drmModeSetCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id,
222 handle, m_cursorSize.width(), m_cursorSize.height());
223 } else {
224 ret = drmModeMoveCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id,
225 adjustedLocalPos.x(), adjustedLocalPos.y());
226 }
227 if (ret == 0)
228 m_pos = pos;
229 else
230 qWarning("Failed to move cursor on screen %s: %d", kmsScreen->name().toLatin1().constData(), ret);
231
232 kmsScreen->handleCursorMove(pos);
233 }
234 }
235}
236
237void QEglFSKmsGbmCursor::initCursorAtlas()
238{
239 static QByteArray json = qgetenv("QT_QPA_EGLFS_CURSOR");
240 if (json.isEmpty())
241 json = ":/cursor.json";
242
243 qCDebug(qLcEglfsKmsDebug) << "Initializing cursor atlas from" << json;
244
245 QFile file(QString::fromUtf8(json));
246 if (!file.open(QFile::ReadOnly)) {
247 for (QPlatformScreen *screen : m_screen->virtualSiblings()) {
248 QEglFSKmsScreen *kmsScreen = static_cast<QEglFSKmsScreen *>(screen);
249 drmModeSetCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, 0, 0, 0);
250 drmModeMoveCursor(kmsScreen->device()->fd(), kmsScreen->output().crtc_id, 0, 0);
251 }
252 m_state = CursorDisabled;
253 return;
254 }
255
256 QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
257 QJsonObject object = doc.object();
258
259 QString atlas = object.value("image"_L1).toString();
260 Q_ASSERT(!atlas.isEmpty());
261
262 const int cursorsPerRow = object.value("cursorsPerRow"_L1).toDouble();
263 Q_ASSERT(cursorsPerRow);
264 m_cursorAtlas.cursorsPerRow = cursorsPerRow;
265
266 const QJsonArray hotSpots = object.value("hotSpots"_L1).toArray();
267 Q_ASSERT(hotSpots.count() == Qt::LastCursor + 1);
268 for (int i = 0; i < hotSpots.count(); i++) {
269 QPoint hotSpot(hotSpots[i].toArray()[0].toDouble(), hotSpots[i].toArray()[1].toDouble());
270 m_cursorAtlas.hotSpots << hotSpot;
271 }
272
273 QImage image = QImage(atlas).convertToFormat(QImage::Format_ARGB32);
274 m_cursorAtlas.cursorWidth = image.width() / m_cursorAtlas.cursorsPerRow;
275 m_cursorAtlas.cursorHeight = image.height() / ((Qt::LastCursor + cursorsPerRow) / cursorsPerRow);
276 m_cursorAtlas.width = image.width();
277 m_cursorAtlas.height = image.height();
278 m_cursorAtlas.image = image;
279}
280
281QT_END_NAMESPACE
void pointerEvent(const QMouseEvent &event) override
This method is called by Qt whenever a QMouseEvent is generated by the underlying pointer input.
void changeCursor(QCursor *windowCursor, QWindow *window) override
This method is called by Qt whenever the cursor graphic should be changed.
void setPos(const QPoint &pos) override
QPoint pos() const override
#define DRM_CAP_CURSOR_HEIGHT
#define DRM_CAP_CURSOR_WIDTH