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 
openFramebufferDevice(const QString & dev)68 static 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 
determineDepth(const fb_var_screeninfo & vinfo)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 
determineGeometry(const fb_var_screeninfo & vinfo,const QRect & userGeometry)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("Unable to find screen geometry, using 320x240");
130         w = 320;
131         h = 240;
132     }
133 
134     return QRect(xoff, yoff, w, h);
135 }
136 
determinePhysicalSize(const fb_var_screeninfo & vinfo,const QSize & mmSize,const QSize & res)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(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 
determineFormat(const fb_var_screeninfo & info,int depth)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] = {{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 
openTtyDevice(const QString & device)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(*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 
switchToGraphicsMode(int ttyfd,bool doSwitch,int * oldMode)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(ttyfd, KDGETMODE, oldMode) == 0) {
272         if (doSwitch && *oldMode != KD_GRAPHICS)
273             ioctl(ttyfd, KDSETMODE, KD_GRAPHICS);
274     }
275 }
276 
resetTty(int ttyfd,int oldMode)277 static void resetTty(int ttyfd, int oldMode)
278 {
279     ioctl(ttyfd, KDSETMODE, oldMode);
280 
281     QT_CLOSE(ttyfd);
282 }
283 
blankScreen(int fd,bool on)284 static void blankScreen(int fd, bool on)
285 {
286     ioctl(fd, FBIOBLANK, on ? VESA_POWERDOWN : VESA_NO_BLANKING);
287 }
288 
QLinuxFbScreen(const QStringList & args)289 QLinuxFbScreen::QLinuxFbScreen(const QStringList &args)
290     : mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(0)
291 {
292     mMmap.data = 0;
293 }
294 
~QLinuxFbScreen()295 QLinuxFbScreen::~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 
initialize()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(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 
doRedraw()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(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.
grabWindow(WId wid,int x,int y,int width,int height) const424 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(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 
449 QT_END_NAMESPACE
450 
451