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

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