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
66QT_BEGIN_NAMESPACE
67
68static int openFramebufferDevice(const QString &dev)
69{
70 int fd = -1;
71
72 if (access(dev.toLatin1().constData(), R_OK|W_OK) == 0)
73 fd = QT_OPEN(dev.toLatin1().constData(), O_RDWR);
74
75 if (fd == -1) {
76 if (access(dev.toLatin1().constData(), R_OK) == 0)
77 fd = QT_OPEN(dev.toLatin1().constData(), O_RDONLY);
78 }
79
80 return fd;
81}
82
83static 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
98static 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("Unable to find screen geometry, using 320x240");
130 w = 320;
131 h = 240;
132 }
133
134 return QRect(xoff, yoff, w, h);
135}
136
137static 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(res.width() * 25.4 / dpi);
149 mmHeight = qRound(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
160static 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] = {{16, 8, 0}, {8, 8, 0},
170 {0, 8, 0}, {24, 8, 0}};
171 const fb_bitfield abgr8888[4] = {{0, 8, 0}, {8, 8, 0},
172 {16, 8, 0}, {24, 8, 0}};
173 if (memcmp(rgba, argb8888, 4 * sizeof(fb_bitfield)) == 0) {
174 format = QImage::Format_ARGB32;
175 } else if (memcmp(rgba, argb8888, 3 * sizeof(fb_bitfield)) == 0) {
176 format = QImage::Format_RGB32;
177 } else if (memcmp(rgba, abgr8888, 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] = {{16, 8, 0}, {8, 8, 0},
185 {0, 8, 0}, {0, 0, 0}};
186 const fb_bitfield bgr888[4] = {{0, 8, 0}, {8, 8, 0},
187 {16, 8, 0}, {0, 0, 0}};
188 if (memcmp(rgba, rgb888, 3 * sizeof(fb_bitfield)) == 0) {
189 format = QImage::Format_RGB888;
190 } else if (memcmp(rgba, bgr888, 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] = {{12, 6, 0}, {6, 6, 0},
198 {0, 6, 0}, {0, 0, 0}};
199 if (memcmp(rgba, rgb666, 3 * sizeof(fb_bitfield)) == 0)
200 format = QImage::Format_RGB666;
201 break;
202 }
203 case 16: {
204 const fb_bitfield rgb565[4] = {{11, 5, 0}, {5, 6, 0},
205 {0, 5, 0}, {0, 0, 0}};
206 const fb_bitfield bgr565[4] = {{0, 5, 0}, {5, 6, 0},
207 {11, 5, 0}, {0, 0, 0}};
208 if (memcmp(rgba, rgb565, 3 * sizeof(fb_bitfield)) == 0) {
209 format = QImage::Format_RGB16;
210 } else if (memcmp(rgba, bgr565, 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] = {{10, 5, 0}, {5, 5, 0},
218 {0, 5, 0}, {15, 1, 0}};
219 const fb_bitfield bgr1555[4] = {{0, 5, 0}, {5, 5, 0},
220 {10, 5, 0}, {15, 1, 0}};
221 if (memcmp(rgba, rgb1555, 3 * sizeof(fb_bitfield)) == 0) {
222 format = QImage::Format_RGB555;
223 } else if (memcmp(rgba, bgr1555, 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] = {{8, 4, 0}, {4, 4, 0},
231 {0, 4, 0}, {0, 0, 0}};
232 if (memcmp(rgba, rgb444, 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
248static 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(*dev, O_RDWR);
256 if (fd != -1)
257 break;
258 }
259 } else {
260 fd = QT_OPEN(QFile::encodeName(device).constData(), O_RDWR);
261 }
262
263 return fd;
264}
265
266static 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(ttyfd, KDGETMODE, oldMode) == 0) {
272 if (doSwitch && *oldMode != KD_GRAPHICS)
273 ioctl(ttyfd, KDSETMODE, KD_GRAPHICS);
274 }
275}
276
277static void resetTty(int ttyfd, int oldMode)
278{
279 ioctl(ttyfd, KDSETMODE, oldMode);
280
281 QT_CLOSE(ttyfd);
282}
283
284static void blankScreen(int fd, bool on)
285{
286 ioctl(fd, FBIOBLANK, on ? VESA_POWERDOWN : VESA_NO_BLANKING);
287}
288
289QLinuxFbScreen::QLinuxFbScreen(const QStringList &args)
290 : mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(0)
291{
292 mMmap.data = 0;
293}
294
295QLinuxFbScreen::~QLinuxFbScreen()
296{
297 if (mFbFd != -1) {
298 if (mMmap.data)
299 munmap(mMmap.data - mMmap.offset, mMmap.size);
300 close(mFbFd);
301 }
302
303 if (mTtyFd != -1)
304 resetTty(mTtyFd, mOldTtyMode);
305
306 delete mBlitter;
307}
308
309bool 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(mArgs)) {
324 QRegularExpressionMatch match;
325 if (arg == QLatin1String("nographicsmodeswitch"))
326 doSwitchToGraphicsMode = false;
327 else if (arg.contains(mmSizeRx, &match))
328 userMmSize = QSize(match.captured(1).toInt(), match.captured(2).toInt());
329 else if (arg.contains(sizeRx, &match))
330 userGeometry.setSize(QSize(match.captured(1).toInt(), match.captured(2).toInt()));
331 else if (arg.contains(offsetRx, &match))
332 userGeometry.setTopLeft(QPoint(match.captured(1).toInt(), match.captured(2).toInt()));
333 else if (arg.contains(ttyRx, &match))
334 ttyDevice = match.captured(1);
335 else if (arg.contains(fbRx, &match))
336 fbDevice = match.captured(1);
337 }
338
339 if (fbDevice.isEmpty()) {
340 fbDevice = QLatin1String("/dev/fb0");
341 if (!QFile::exists(fbDevice))
342 fbDevice = QLatin1String("/dev/graphics/fb0");
343 if (!QFile::exists(fbDevice)) {
344 qWarning("Unable to figure out framebuffer device. Specify it manually.");
345 return false;
346 }
347 }
348
349 // Open the device
350 mFbFd = openFramebufferDevice(fbDevice);
351 if (mFbFd == -1) {
352 qErrnoWarning(errno, "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(&vinfo, 0, sizeof(vinfo));
360 memset(&finfo, 0, sizeof(finfo));
361
362 if (ioctl(mFbFd, FBIOGET_FSCREENINFO, &finfo) != 0) {
363 qErrnoWarning(errno, "Error reading fixed information");
364 return false;
365 }
366
367 if (ioctl(mFbFd, FBIOGET_VSCREENINFO, &vinfo)) {
368 qErrnoWarning(errno, "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(vinfo, mDepth);
377 mPhysicalSize = determinePhysicalSize(vinfo, userMmSize, geometry.size());
378
379 // mmap the framebuffer
380 mMmap.size = finfo.smem_len;
381 uchar *data = (unsigned char *)mmap(0, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);
382 if ((long)data == -1) {
383 qErrnoWarning(errno, "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(ttyDevice);
396 if (mTtyFd == -1)
397 qErrnoWarning(errno, "Failed to open tty");
398
399 switchToGraphicsMode(mTtyFd, doSwitchToGraphicsMode, &mOldTtyMode);
400 blankScreen(mFbFd, false);
401
402 return true;
403}
404
405QRegion 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(rect, mScreenImage, 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.
424QPixmap 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(mFbScreenImage).copy(x, y, width, 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(mFbScreenImage).copy(rect);
444 }
445
446 return QPixmap();
447}
448
449QT_END_NAMESPACE
450
451