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
qlinuxfbscreen.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
6#include <QtFbSupport/private/qfbcursor_p.h>
7#include <QtFbSupport/private/qfbwindow_p.h>
8#include <QtCore/QFile>
9#include <QtCore/QRegularExpression>
10#include <QtCore/q20utility.h>
11#include <QtGui/QPainter>
12
13#include <private/qcore_unix_p.h> // overrides QT_OPEN
14#include <qimage.h>
15#include <qdebug.h>
16
17#include <unistd.h>
18#include <stdlib.h>
19#include <sys/ioctl.h>
20#include <sys/types.h>
21#include <sys/stat.h>
22#include <sys/mman.h>
23#include <linux/kd.h>
24#include <fcntl.h>
25#include <errno.h>
26#include <stdio.h>
27#include <limits.h>
28#include <signal.h>
29
30#include <linux/fb.h>
31
32QT_BEGIN_NAMESPACE
33
34using namespace Qt::StringLiterals;
35
36static int openFramebufferDevice(const QString &dev)
37{
38 int fd = -1;
39
40 if (access(dev.toLatin1().constData(), R_OK|W_OK) == 0)
41 fd = QT_OPEN(dev.toLatin1().constData(), O_RDWR);
42
43 if (fd == -1) {
44 if (access(dev.toLatin1().constData(), R_OK) == 0)
45 fd = QT_OPEN(dev.toLatin1().constData(), O_RDONLY);
46 }
47
48 return fd;
49}
50
51static int determineDepth(const fb_var_screeninfo &vinfo)
52{
53 int depth = vinfo.bits_per_pixel;
54 if (depth== 24) {
55 depth = vinfo.red.length + vinfo.green.length + vinfo.blue.length;
56 if (depth <= 0)
57 depth = 24; // reset if color component lengths are not reported
58 } else if (depth == 16) {
59 depth = vinfo.red.length + vinfo.green.length + vinfo.blue.length;
60 if (depth <= 0)
61 depth = 16;
62 }
63 return depth;
64}
65
66static QRect determineGeometry(const fb_var_screeninfo &vinfo, const QRect &userGeometry)
67{
68 int xoff = vinfo.xoffset;
69 int yoff = vinfo.yoffset;
70 int w, h;
71 if (userGeometry.isValid()) {
72 w = userGeometry.width();
73 h = userGeometry.height();
74 if (q20::cmp_greater(w, vinfo.xres))
75 w = vinfo.xres;
76 if (q20::cmp_greater(h, vinfo.yres))
77 h = vinfo.yres;
78
79 int xxoff = userGeometry.x(), yyoff = userGeometry.y();
80 if (xxoff != 0 || yyoff != 0) {
81 if (xxoff < 0 || q20::cmp_greater(xxoff + w, vinfo.xres))
82 xxoff = vinfo.xres - w;
83 if (yyoff < 0 || q20::cmp_greater(yyoff + h, vinfo.yres))
84 yyoff = vinfo.yres - h;
85 xoff += xxoff;
86 yoff += yyoff;
87 } else {
88 xoff += (vinfo.xres - w)/2;
89 yoff += (vinfo.yres - h)/2;
90 }
91 } else {
92 w = vinfo.xres;
93 h = vinfo.yres;
94 }
95
96 if (w == 0 || h == 0) {
97 qWarning("Unable to find screen geometry, using 320x240");
98 w = 320;
99 h = 240;
100 }
101
102 return QRect(xoff, yoff, w, h);
103}
104
105static QSizeF determinePhysicalSize(const fb_var_screeninfo &vinfo, const QSize &mmSize, const QSize &res)
106{
107 int mmWidth = mmSize.width(), mmHeight = mmSize.height();
108
109 if (mmWidth <= 0 && mmHeight <= 0) {
110 if (vinfo.width != 0 && vinfo.height != 0
111 && vinfo.width != UINT_MAX && vinfo.height != UINT_MAX) {
112 mmWidth = vinfo.width;
113 mmHeight = vinfo.height;
114 } else {
115 const int dpi = 100;
116 mmWidth = qRound(res.width() * 25.4 / dpi);
117 mmHeight = qRound(res.height() * 25.4 / dpi);
118 }
119 } else if (mmWidth > 0 && mmHeight <= 0) {
120 mmHeight = res.height() * mmWidth/res.width();
121 } else if (mmHeight > 0 && mmWidth <= 0) {
122 mmWidth = res.width() * mmHeight/res.height();
123 }
124
125 return QSize(mmWidth, mmHeight);
126}
127
128static QImage::Format determineFormat(const fb_var_screeninfo &info, int depth)
129{
130 const fb_bitfield rgba[4] = { info.red, info.green,
131 info.blue, info.transp };
132
133 QImage::Format format = QImage::Format_Invalid;
134
135 switch (depth) {
136 case 32: {
137 const fb_bitfield argb8888[4] = {{16, 8, 0}, {8, 8, 0},
138 {0, 8, 0}, {24, 8, 0}};
139 const fb_bitfield abgr8888[4] = {{0, 8, 0}, {8, 8, 0},
140 {16, 8, 0}, {24, 8, 0}};
141 if (memcmp(rgba, argb8888, 4 * sizeof(fb_bitfield)) == 0) {
142 format = QImage::Format_ARGB32;
143 } else if (memcmp(rgba, argb8888, 3 * sizeof(fb_bitfield)) == 0) {
144 format = QImage::Format_RGB32;
145 } else if (memcmp(rgba, abgr8888, 3 * sizeof(fb_bitfield)) == 0) {
146 format = QImage::Format_RGB32;
147 // pixeltype = BGRPixel;
148 }
149 break;
150 }
151 case 24: {
152 const fb_bitfield rgb888[4] = {{16, 8, 0}, {8, 8, 0},
153 {0, 8, 0}, {0, 0, 0}};
154 const fb_bitfield bgr888[4] = {{0, 8, 0}, {8, 8, 0},
155 {16, 8, 0}, {0, 0, 0}};
156 if (memcmp(rgba, rgb888, 3 * sizeof(fb_bitfield)) == 0) {
157 format = QImage::Format_RGB888;
158 } else if (memcmp(rgba, bgr888, 3 * sizeof(fb_bitfield)) == 0) {
159 format = QImage::Format_BGR888;
160 // pixeltype = BGRPixel;
161 }
162 break;
163 }
164 case 18: {
165 const fb_bitfield rgb666[4] = {{12, 6, 0}, {6, 6, 0},
166 {0, 6, 0}, {0, 0, 0}};
167 if (memcmp(rgba, rgb666, 3 * sizeof(fb_bitfield)) == 0)
168 format = QImage::Format_RGB666;
169 break;
170 }
171 case 16: {
172 const fb_bitfield rgb565[4] = {{11, 5, 0}, {5, 6, 0},
173 {0, 5, 0}, {0, 0, 0}};
174 const fb_bitfield bgr565[4] = {{0, 5, 0}, {5, 6, 0},
175 {11, 5, 0}, {0, 0, 0}};
176 if (memcmp(rgba, rgb565, 3 * sizeof(fb_bitfield)) == 0) {
177 format = QImage::Format_RGB16;
178 } else if (memcmp(rgba, bgr565, 3 * sizeof(fb_bitfield)) == 0) {
179 format = QImage::Format_RGB16;
180 // pixeltype = BGRPixel;
181 }
182 break;
183 }
184 case 15: {
185 const fb_bitfield rgb1555[4] = {{10, 5, 0}, {5, 5, 0},
186 {0, 5, 0}, {15, 1, 0}};
187 const fb_bitfield bgr1555[4] = {{0, 5, 0}, {5, 5, 0},
188 {10, 5, 0}, {15, 1, 0}};
189 if (memcmp(rgba, rgb1555, 3 * sizeof(fb_bitfield)) == 0) {
190 format = QImage::Format_RGB555;
191 } else if (memcmp(rgba, bgr1555, 3 * sizeof(fb_bitfield)) == 0) {
192 format = QImage::Format_RGB555;
193 // pixeltype = BGRPixel;
194 }
195 break;
196 }
197 case 12: {
198 const fb_bitfield rgb444[4] = {{8, 4, 0}, {4, 4, 0},
199 {0, 4, 0}, {0, 0, 0}};
200 if (memcmp(rgba, rgb444, 3 * sizeof(fb_bitfield)) == 0)
201 format = QImage::Format_RGB444;
202 break;
203 }
204 case 8:
205 break;
206 case 1:
207 format = QImage::Format_Mono; //###: LSB???
208 break;
209 default:
210 break;
211 }
212
213 return format;
214}
215
216static int openTtyDevice(const QString &device)
217{
218 const char *const devs[] = { "/dev/tty0", "/dev/tty", "/dev/console", nullptr };
219
220 int fd = -1;
221 if (device.isEmpty()) {
222 for (const char * const *dev = devs; *dev; ++dev) {
223 fd = QT_OPEN(*dev, O_RDWR);
224 if (fd != -1)
225 break;
226 }
227 } else {
228 fd = QT_OPEN(QFile::encodeName(device).constData(), O_RDWR);
229 }
230
231 return fd;
232}
233
234static void switchToGraphicsMode(int ttyfd, bool doSwitch, int *oldMode)
235{
236 // Do not warn if the switch fails: the ioctl fails when launching from a
237 // remote console and there is nothing we can do about it. The matching
238 // call in resetTty should at least fail then, too, so we do no harm.
239 if (ioctl(ttyfd, KDGETMODE, oldMode) == 0) {
240 if (doSwitch && *oldMode != KD_GRAPHICS)
241 ioctl(ttyfd, KDSETMODE, KD_GRAPHICS);
242 }
243}
244
245static void resetTty(int ttyfd, int oldMode)
246{
247 ioctl(ttyfd, KDSETMODE, oldMode);
248
249 QT_CLOSE(ttyfd);
250}
251
252static void blankScreen(int fd, bool on)
253{
254 ioctl(fd, FBIOBLANK, on ? VESA_POWERDOWN : VESA_NO_BLANKING);
255}
256
257QLinuxFbScreen::QLinuxFbScreen(const QStringList &args)
258 : mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(nullptr)
259{
260 mMmap.data = nullptr;
261}
262
264{
265 if (mFbFd != -1) {
266 if (mMmap.data)
267 munmap(mMmap.data - mMmap.offset, mMmap.size);
268 close(mFbFd);
269 }
270
271 if (mTtyFd != -1)
272 resetTty(mTtyFd, mOldTtyMode);
273
274 delete mBlitter;
275}
276
278{
279 QRegularExpression ttyRx("tty=(.*)"_L1);
280 QRegularExpression fbRx("fb=(.*)"_L1);
281 QRegularExpression mmSizeRx("mmsize=(\\d+)x(\\d+)"_L1);
282 QRegularExpression sizeRx("size=(\\d+)x(\\d+)"_L1);
283 QRegularExpression offsetRx("offset=(\\d+)x(\\d+)"_L1);
284
285 QString fbDevice, ttyDevice;
286 QSize userMmSize;
287 QRect userGeometry;
288 bool doSwitchToGraphicsMode = true;
289
290 // Parse arguments
291 for (const QString &arg : std::as_const(mArgs)) {
292 QRegularExpressionMatch match;
293 if (arg == "nographicsmodeswitch"_L1)
294 doSwitchToGraphicsMode = false;
295 else if (arg.contains(mmSizeRx, &match))
296 userMmSize = QSize(match.captured(1).toInt(), match.captured(2).toInt());
297 else if (arg.contains(sizeRx, &match))
298 userGeometry.setSize(QSize(match.captured(1).toInt(), match.captured(2).toInt()));
299 else if (arg.contains(offsetRx, &match))
300 userGeometry.setTopLeft(QPoint(match.captured(1).toInt(), match.captured(2).toInt()));
301 else if (arg.contains(ttyRx, &match))
302 ttyDevice = match.captured(1);
303 else if (arg.contains(fbRx, &match))
304 fbDevice = match.captured(1);
305 }
306
307 if (fbDevice.isEmpty()) {
308 fbDevice = "/dev/fb0"_L1;
309 if (!QFile::exists(fbDevice))
310 fbDevice = "/dev/graphics/fb0"_L1;
311 if (!QFile::exists(fbDevice)) {
312 qWarning("Unable to figure out framebuffer device. Specify it manually.");
313 return false;
314 }
315 }
316
317 // Open the device
318 mFbFd = openFramebufferDevice(fbDevice);
319 if (mFbFd == -1) {
320 qErrnoWarning(errno, "Failed to open framebuffer %s", qPrintable(fbDevice));
321 return false;
322 }
323
324 // Read the fixed and variable screen information
325 fb_fix_screeninfo finfo;
326 fb_var_screeninfo vinfo;
327 memset(&vinfo, 0, sizeof(vinfo));
328 memset(&finfo, 0, sizeof(finfo));
329
330 if (ioctl(mFbFd, FBIOGET_FSCREENINFO, &finfo) != 0) {
331 qErrnoWarning(errno, "Error reading fixed information");
332 return false;
333 }
334
335 if (ioctl(mFbFd, FBIOGET_VSCREENINFO, &vinfo)) {
336 qErrnoWarning(errno, "Error reading variable information");
337 return false;
338 }
339
340 mDepth = determineDepth(vinfo);
341 mBytesPerLine = finfo.line_length;
342 QRect geometry = determineGeometry(vinfo, userGeometry);
343 mGeometry = QRect(QPoint(0, 0), geometry.size());
344 mFormat = determineFormat(vinfo, mDepth);
345 mPhysicalSize = determinePhysicalSize(vinfo, userMmSize, geometry.size());
346
347 // mmap the framebuffer
348 mMmap.size = finfo.smem_len;
349 uchar *data = (unsigned char *)mmap(nullptr, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);
350 if ((long)data == -1) {
351 qErrnoWarning(errno, "Failed to mmap framebuffer");
352 return false;
353 }
354
355 mMmap.offset = geometry.y() * mBytesPerLine + geometry.x() * mDepth / 8;
356 mMmap.data = data + mMmap.offset;
357
358 QFbScreen::initializeCompositor();
359 mFbScreenImage = QImage(mMmap.data, geometry.width(), geometry.height(), mBytesPerLine, mFormat);
360
361 mCursor = new QFbCursor(this);
362
363 mTtyFd = openTtyDevice(ttyDevice);
364 if (mTtyFd == -1)
365 qErrnoWarning(errno, "Failed to open tty");
366
367 switchToGraphicsMode(mTtyFd, doSwitchToGraphicsMode, &mOldTtyMode);
368 blankScreen(mFbFd, false);
369
370 return true;
371}
372
374{
375 QRegion touched = QFbScreen::doRedraw();
376
377 if (touched.isEmpty())
378 return touched;
379
380 if (!mBlitter)
381 mBlitter = new QPainter(&mFbScreenImage);
382
383 mBlitter->setCompositionMode(QPainter::CompositionMode_Source);
384 for (const QRect &rect : touched)
385 mBlitter->drawImage(rect, mScreenImage, rect);
386
387 return touched;
388}
389
390// grabWindow() grabs "from the screen" not from the backingstores.
391// In linuxfb's case it will also include the mouse cursor.
392QPixmap QLinuxFbScreen::grabWindow(WId wid, int x, int y, int width, int height) const
393{
394 if (!wid) {
395 if (width < 0)
396 width = mFbScreenImage.width() - x;
397 if (height < 0)
398 height = mFbScreenImage.height() - y;
399 return QPixmap::fromImage(mFbScreenImage).copy(x, y, width, height);
400 }
401
402 QFbWindow *window = windowForId(wid);
403 if (window) {
404 const QRect geom = window->geometry();
405 if (width < 0)
406 width = geom.width() - x;
407 if (height < 0)
408 height = geom.height() - y;
409 QRect rect(geom.topLeft() + QPoint(x, y), QSize(width, height));
410 rect &= window->geometry();
411 return QPixmap::fromImage(mFbScreenImage).copy(rect);
412 }
413
414 return QPixmap();
415}
416
417QT_END_NAMESPACE
418
419#include "moc_qlinuxfbscreen.cpp"
QPixmap grabWindow(WId wid, int x, int y, int width, int height) const override
This function is called when Qt needs to be able to grab the content of a window.
bool initialize() override
QRegion doRedraw() override
static QRect determineGeometry(const fb_var_screeninfo &vinfo, const QRect &userGeometry)
static int openTtyDevice(const QString &device)
static void resetTty(int ttyfd, int oldMode)
static void blankScreen(int fd, bool on)
static int determineDepth(const fb_var_screeninfo &vinfo)
static int openFramebufferDevice(const QString &dev)
static QSizeF determinePhysicalSize(const fb_var_screeninfo &vinfo, const QSize &mmSize, const QSize &res)
static void switchToGraphicsMode(int ttyfd, bool doSwitch, int *oldMode)
static QImage::Format determineFormat(const fb_var_screeninfo &info, int depth)