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