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
4#include "qlinuxfbscreen.h"
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(name: dev.toLatin1().constData(), R_OK|W_OK) == 0)
40 fd = QT_OPEN(pathname: dev.toLatin1().constData(), O_RDWR);
41
42 if (fd == -1) {
43 if (access(name: dev.toLatin1().constData(), R_OK) == 0)
44 fd = QT_OPEN(pathname: 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(t: w, u: vinfo.xres))
74 w = vinfo.xres;
75 if (q20::cmp_greater(t: h, u: 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(t: xxoff + w, u: vinfo.xres))
81 xxoff = vinfo.xres - w;
82 if (yyoff < 0 || q20::cmp_greater(t: yyoff + h, u: 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(msg: "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(d: res.width() * 25.4 / dpi);
116 mmHeight = qRound(d: 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] = {{.offset: 16, .length: 8, .msb_right: 0}, {.offset: 8, .length: 8, .msb_right: 0},
137 {.offset: 0, .length: 8, .msb_right: 0}, {.offset: 24, .length: 8, .msb_right: 0}};
138 const fb_bitfield abgr8888[4] = {{.offset: 0, .length: 8, .msb_right: 0}, {.offset: 8, .length: 8, .msb_right: 0},
139 {.offset: 16, .length: 8, .msb_right: 0}, {.offset: 24, .length: 8, .msb_right: 0}};
140 if (memcmp(s1: rgba, s2: argb8888, n: 4 * sizeof(fb_bitfield)) == 0) {
141 format = QImage::Format_ARGB32;
142 } else if (memcmp(s1: rgba, s2: argb8888, n: 3 * sizeof(fb_bitfield)) == 0) {
143 format = QImage::Format_RGB32;
144 } else if (memcmp(s1: rgba, s2: abgr8888, n: 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] = {{.offset: 16, .length: 8, .msb_right: 0}, {.offset: 8, .length: 8, .msb_right: 0},
152 {.offset: 0, .length: 8, .msb_right: 0}, {.offset: 0, .length: 0, .msb_right: 0}};
153 const fb_bitfield bgr888[4] = {{.offset: 0, .length: 8, .msb_right: 0}, {.offset: 8, .length: 8, .msb_right: 0},
154 {.offset: 16, .length: 8, .msb_right: 0}, {.offset: 0, .length: 0, .msb_right: 0}};
155 if (memcmp(s1: rgba, s2: rgb888, n: 3 * sizeof(fb_bitfield)) == 0) {
156 format = QImage::Format_RGB888;
157 } else if (memcmp(s1: rgba, s2: bgr888, n: 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] = {{.offset: 12, .length: 6, .msb_right: 0}, {.offset: 6, .length: 6, .msb_right: 0},
165 {.offset: 0, .length: 6, .msb_right: 0}, {.offset: 0, .length: 0, .msb_right: 0}};
166 if (memcmp(s1: rgba, s2: rgb666, n: 3 * sizeof(fb_bitfield)) == 0)
167 format = QImage::Format_RGB666;
168 break;
169 }
170 case 16: {
171 const fb_bitfield rgb565[4] = {{.offset: 11, .length: 5, .msb_right: 0}, {.offset: 5, .length: 6, .msb_right: 0},
172 {.offset: 0, .length: 5, .msb_right: 0}, {.offset: 0, .length: 0, .msb_right: 0}};
173 const fb_bitfield bgr565[4] = {{.offset: 0, .length: 5, .msb_right: 0}, {.offset: 5, .length: 6, .msb_right: 0},
174 {.offset: 11, .length: 5, .msb_right: 0}, {.offset: 0, .length: 0, .msb_right: 0}};
175 if (memcmp(s1: rgba, s2: rgb565, n: 3 * sizeof(fb_bitfield)) == 0) {
176 format = QImage::Format_RGB16;
177 } else if (memcmp(s1: rgba, s2: bgr565, n: 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] = {{.offset: 10, .length: 5, .msb_right: 0}, {.offset: 5, .length: 5, .msb_right: 0},
185 {.offset: 0, .length: 5, .msb_right: 0}, {.offset: 15, .length: 1, .msb_right: 0}};
186 const fb_bitfield bgr1555[4] = {{.offset: 0, .length: 5, .msb_right: 0}, {.offset: 5, .length: 5, .msb_right: 0},
187 {.offset: 10, .length: 5, .msb_right: 0}, {.offset: 15, .length: 1, .msb_right: 0}};
188 if (memcmp(s1: rgba, s2: rgb1555, n: 3 * sizeof(fb_bitfield)) == 0) {
189 format = QImage::Format_RGB555;
190 } else if (memcmp(s1: rgba, s2: bgr1555, n: 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] = {{.offset: 8, .length: 4, .msb_right: 0}, {.offset: 4, .length: 4, .msb_right: 0},
198 {.offset: 0, .length: 4, .msb_right: 0}, {.offset: 0, .length: 0, .msb_right: 0}};
199 if (memcmp(s1: rgba, s2: rgb444, n: 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(pathname: *dev, O_RDWR);
223 if (fd != -1)
224 break;
225 }
226 } else {
227 fd = QT_OPEN(pathname: QFile::encodeName(fileName: 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(fd: ttyfd, KDGETMODE, oldMode) == 0) {
239 if (doSwitch && *oldMode != KD_GRAPHICS)
240 ioctl(fd: ttyfd, KDSETMODE, KD_GRAPHICS);
241 }
242}
243
244static void resetTty(int ttyfd, int oldMode)
245{
246 ioctl(fd: ttyfd, KDSETMODE, oldMode);
247
248 QT_CLOSE(fd: ttyfd);
249}
250
251static void blankScreen(int fd, bool on)
252{
253 ioctl(fd: 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
262QLinuxFbScreen::~QLinuxFbScreen()
263{
264 if (mFbFd != -1) {
265 if (mMmap.data)
266 munmap(addr: mMmap.data - mMmap.offset, len: mMmap.size);
267 close(fd: mFbFd);
268 }
269
270 if (mTtyFd != -1)
271 resetTty(ttyfd: mTtyFd, oldMode: mOldTtyMode);
272
273 delete mBlitter;
274}
275
276bool QLinuxFbScreen::initialize()
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(t&: mArgs)) {
291 QRegularExpressionMatch match;
292 if (arg == "nographicsmodeswitch"_L1)
293 doSwitchToGraphicsMode = false;
294 else if (arg.contains(re: mmSizeRx, rmatch: &match))
295 userMmSize = QSize(match.captured(nth: 1).toInt(), match.captured(nth: 2).toInt());
296 else if (arg.contains(re: sizeRx, rmatch: &match))
297 userGeometry.setSize(QSize(match.captured(nth: 1).toInt(), match.captured(nth: 2).toInt()));
298 else if (arg.contains(re: offsetRx, rmatch: &match))
299 userGeometry.setTopLeft(QPoint(match.captured(nth: 1).toInt(), match.captured(nth: 2).toInt()));
300 else if (arg.contains(re: ttyRx, rmatch: &match))
301 ttyDevice = match.captured(nth: 1);
302 else if (arg.contains(re: fbRx, rmatch: &match))
303 fbDevice = match.captured(nth: 1);
304 }
305
306 if (fbDevice.isEmpty()) {
307 fbDevice = "/dev/fb0"_L1;
308 if (!QFile::exists(fileName: fbDevice))
309 fbDevice = "/dev/graphics/fb0"_L1;
310 if (!QFile::exists(fileName: fbDevice)) {
311 qWarning(msg: "Unable to figure out framebuffer device. Specify it manually.");
312 return false;
313 }
314 }
315
316 // Open the device
317 mFbFd = openFramebufferDevice(dev: fbDevice);
318 if (mFbFd == -1) {
319 qErrnoWarning(errno, msg: "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(s: &vinfo, c: 0, n: sizeof(vinfo));
327 memset(s: &finfo, c: 0, n: sizeof(finfo));
328
329 if (ioctl(fd: mFbFd, FBIOGET_FSCREENINFO, &finfo) != 0) {
330 qErrnoWarning(errno, msg: "Error reading fixed information");
331 return false;
332 }
333
334 if (ioctl(fd: mFbFd, FBIOGET_VSCREENINFO, &vinfo)) {
335 qErrnoWarning(errno, msg: "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(info: vinfo, depth: mDepth);
344 mPhysicalSize = determinePhysicalSize(vinfo, mmSize: userMmSize, res: geometry.size());
345
346 // mmap the framebuffer
347 mMmap.size = finfo.smem_len;
348 uchar *data = (unsigned char *)mmap(addr: nullptr, len: mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd: mFbFd, offset: 0);
349 if ((long)data == -1) {
350 qErrnoWarning(errno, msg: "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(device: ttyDevice);
363 if (mTtyFd == -1)
364 qErrnoWarning(errno, msg: "Failed to open tty");
365
366 switchToGraphicsMode(ttyfd: mTtyFd, doSwitch: doSwitchToGraphicsMode, oldMode: &mOldTtyMode);
367 blankScreen(fd: mFbFd, on: false);
368
369 return true;
370}
371
372QRegion QLinuxFbScreen::doRedraw()
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(targetRect: rect, image: mScreenImage, sourceRect: 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(image: mFbScreenImage).copy(ax: x, ay: y, awidth: width, aheight: 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(image: mFbScreenImage).copy(rect);
411 }
412
413 return QPixmap();
414}
415
416QT_END_NAMESPACE
417
418#include "moc_qlinuxfbscreen.cpp"
419
420

source code of qtbase/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp