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