blob: d752b539a0f6016631aa7720fa50e0946675e21e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Copyright (C) 2015-2016 Oleksandr Tymoshenko <gonzo@bluezbox.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qbsdfbscreen.h"
#include <QtFbSupport/private/qfbcursor_p.h>
#include <QtFbSupport/private/qfbwindow_p.h>
#include <QtCore/QRegularExpression>
#include <QtGui/QPainter>
#include <private/qcore_unix_p.h> // overrides QT_OPEN
#include <qimage.h>
#include <qdebug.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <limits.h>
#include <signal.h>
#include <sys/consio.h>
#include <sys/fbio.h>
QT_BEGIN_NAMESPACE
enum {
DefaultDPI = 100
};
static int openFramebufferDevice(const QString &dev)
{
const QByteArray devPath = QFile::encodeName(dev);
int fd = QT_OPEN(devPath.constData(), O_RDWR);
if (fd == -1)
fd = QT_OPEN(devPath.constData(), O_RDONLY);
return fd;
}
static QRect determineGeometry(const struct fbtype &fb, const QRect &userGeometry)
{
int xoff = 0;
int yoff = 0;
int w = 0;
int h = 0;
if (userGeometry.isValid()) {
w = qMin(userGeometry.width(), fb.fb_width);
h = qMin(userGeometry.height(), fb.fb_height);
int xxoff = userGeometry.x(), yyoff = userGeometry.y();
if (xxoff != 0 || yyoff != 0) {
if (xxoff < 0 || xxoff + w > fb.fb_width)
xxoff = fb.fb_width - w;
if (yyoff < 0 || yyoff + h > fb.fb_height)
yyoff = fb.fb_height - h;
xoff += xxoff;
yoff += yyoff;
} else {
xoff += (fb.fb_width - w)/2;
yoff += (fb.fb_height - h)/2;
}
} else {
w = fb.fb_width;
h = fb.fb_height;
}
if (w == 0 || h == 0) {
qWarning("Unable to find screen geometry, using 320x240");
w = 320;
h = 240;
}
return QRect(xoff, yoff, w, h);
}
static QSizeF determinePhysicalSize(const QSize &mmSize, const QSize &res)
{
int mmWidth = mmSize.width();
int mmHeight = mmSize.height();
if (mmWidth <= 0 && mmHeight <= 0) {
const int dpi = DefaultDPI;
mmWidth = qRound(res.width() * 25.4 / dpi);
mmHeight = qRound(res.height() * 25.4 / dpi);
} else if (mmWidth > 0 && mmHeight <= 0) {
mmHeight = res.height() * mmWidth/res.width();
} else if (mmHeight > 0 && mmWidth <= 0) {
mmWidth = res.width() * mmHeight/res.height();
}
return QSize(mmWidth, mmHeight);
}
QBsdFbScreen::QBsdFbScreen(const QStringList &args)
: m_arguments(args)
{
}
QBsdFbScreen::~QBsdFbScreen()
{
if (m_framebufferFd != -1) {
munmap(m_mmap.data - m_mmap.offset, m_mmap.size);
qt_safe_close(m_framebufferFd);
}
}
bool QBsdFbScreen::initialize()
{
QRegularExpression fbRx(QLatin1String("fb=(.*)"));
QRegularExpression mmSizeRx(QLatin1String("mmsize=(\\d+)x(\\d+)"));
QRegularExpression sizeRx(QLatin1String("size=(\\d+)x(\\d+)"));
QRegularExpression offsetRx(QLatin1String("offset=(\\d+)x(\\d+)"));
QString fbDevice;
QSize userMmSize;
QRect userGeometry;
// Parse arguments
for (const QString &arg : qAsConst(m_arguments)) {
QRegularExpressionMatch match;
if (arg.contains(mmSizeRx, &match))
userMmSize = QSize(match.captured(1).toInt(), match.captured(2).toInt());
else if (arg.contains(sizeRx, &match))
userGeometry.setSize(QSize(match.captured(1).toInt(), match.captured(2).toInt()));
else if (arg.contains(offsetRx, &match))
userGeometry.setTopLeft(QPoint(match.captured(1).toInt(), match.captured(2).toInt()));
else if (arg.contains(fbRx, &match))
fbDevice = match.captured(1);
}
if (!fbDevice.isEmpty()) {
// Open the device
m_framebufferFd = openFramebufferDevice(fbDevice);
} else {
m_framebufferFd = STDIN_FILENO;
}
if (m_framebufferFd == -1) {
qErrnoWarning(errno, "Failed to open framebuffer %s", qPrintable(fbDevice));
return false;
}
struct fbtype fb;
if (ioctl(m_framebufferFd, FBIOGTYPE, &fb) != 0) {
qErrnoWarning(errno, "Error reading framebuffer information");
return false;
}
int line_length = 0;
if (ioctl(m_framebufferFd, FBIO_GETLINEWIDTH, &line_length) != 0) {
qErrnoWarning(errno, "Error reading line length information");
return false;
}
mDepth = fb.fb_depth;
m_bytesPerLine = line_length;
const QRect geometry = determineGeometry(fb, userGeometry);
mGeometry = QRect(QPoint(0, 0), geometry.size());
switch (mDepth) {
case 32:
mFormat = QImage::Format_RGB32;
break;
case 24:
mFormat = QImage::Format_RGB888;
break;
case 16:
// falling back
default:
mFormat = QImage::Format_RGB16;
break;
}
mPhysicalSize = determinePhysicalSize(userMmSize, geometry.size());
// mmap the framebuffer
const size_t pagemask = getpagesize() - 1;
m_mmap.size = (m_bytesPerLine * fb.fb_height + pagemask) & ~pagemask;
uchar *data = static_cast<uchar*>(mmap(nullptr, m_mmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, m_framebufferFd, 0));
if (data == MAP_FAILED) {
qErrnoWarning(errno, "Failed to mmap framebuffer");
return false;
}
m_mmap.offset = geometry.y() * m_bytesPerLine + geometry.x() * mDepth / 8;
m_mmap.data = data + m_mmap.offset;
QFbScreen::initializeCompositor();
m_onscreenImage = QImage(m_mmap.data, geometry.width(), geometry.height(), m_bytesPerLine, mFormat);
mCursor = new QFbCursor(this);
return true;
}
QRegion QBsdFbScreen::doRedraw()
{
const QRegion touched = QFbScreen::doRedraw();
if (touched.isEmpty())
return touched;
if (!m_blitter)
m_blitter.reset(new QPainter(&m_onscreenImage));
for (const QRect &rect : touched)
m_blitter->drawImage(rect, mScreenImage, rect);
return touched;
}
// grabWindow() grabs "from the screen" not from the backingstores.
QPixmap QBsdFbScreen::grabWindow(WId wid, int x, int y, int width, int height) const
{
if (!wid) {
if (width < 0)
width = m_onscreenImage.width() - x;
if (height < 0)
height = m_onscreenImage.height() - y;
return QPixmap::fromImage(m_onscreenImage).copy(x, y, width, height);
}
const QFbWindow *window = windowForId(wid);
if (window) {
const QRect geom = window->geometry();
if (width < 0)
width = geom.width() - x;
if (height < 0)
height = geom.height() - y;
QRect rect(geom.topLeft() + QPoint(x, y), QSize(width, height));
rect &= window->geometry();
return QPixmap::fromImage(m_onscreenImage).copy(rect);
}
return QPixmap();
}
QT_END_NAMESPACE