diff --git a/CMakeLists.txt b/CMakeLists.txt index b124a703bf..d173a36349 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -421,6 +421,8 @@ if(HAVE_WAYLAND) set(kwin_KDEINIT_SRCS ${kwin_KDEINIT_SRCS} abstract_backend.cpp + fb_backend.cpp + screens_fb.cpp screens_wayland.cpp screens_x11windowed.cpp virtual_terminal.cpp diff --git a/fb_backend.cpp b/fb_backend.cpp new file mode 100644 index 0000000000..29bf0f612c --- /dev/null +++ b/fb_backend.cpp @@ -0,0 +1,171 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "fb_backend.h" +#include "composite.h" +#include "logind.h" +#include "scene_qpainter.h" +#include "screens_fb.h" +#include "virtual_terminal.h" +// Qt +#include +// system +#include +#include +#include +#include +// Linux +#include + +namespace KWin +{ + +FramebufferBackend::FramebufferBackend(QObject *parent) + : AbstractBackend(parent) +{ +} + +FramebufferBackend::~FramebufferBackend() +{ + unmap(); + if (m_fd >= 0) { + close(m_fd); + } +} + +Screens *FramebufferBackend::createScreens(QObject *parent) +{ + return new FramebufferScreens(this, parent); +} + +QPainterBackend *FramebufferBackend::createQPainterBackend() +{ + return new FramebufferQPainterBackend(this); +} + +void FramebufferBackend::init() +{ + LogindIntegration *logind = LogindIntegration::self(); + auto takeControl = [logind, this]() { + if (logind->hasSessionControl()) { + openFrameBuffer(); + } else { + logind->takeControl(); + connect(logind, &LogindIntegration::hasSessionControlChanged, this, &FramebufferBackend::openFrameBuffer); + } + }; + if (logind->isConnected()) { + takeControl(); + } else { + connect(logind, &LogindIntegration::connectedChanged, this, takeControl); + } + auto v = VirtualTerminal::create(this); + v->init(); +} + +void FramebufferBackend::openFrameBuffer() +{ + int fd = LogindIntegration::self()->takeDevice(m_device.toUtf8().constData()); + if (fd < 0) { + qCWarning(KWIN_CORE) << "Failed to open frame buffer device through logind, trying without"; + } + fd = open(m_device.toUtf8().constData(), O_RDWR | O_CLOEXEC); + if (fd < 0) { + qCWarning(KWIN_CORE) << "failed to open frame buffer device"; + return; + } + m_fd = fd; + queryScreenInfo(); + emit screensQueried(); +} + +bool FramebufferBackend::queryScreenInfo() +{ + if (m_fd < 0) { + return false; + } + + fb_var_screeninfo varinfo; + fb_fix_screeninfo fixinfo; + + // Probe the device for screen information. + if (ioctl(m_fd, FBIOGET_FSCREENINFO, &fixinfo) < 0 || ioctl(m_fd, FBIOGET_VSCREENINFO, &varinfo) < 0) { + return false; + } + m_resolution = QSize(varinfo.xres, varinfo.yres); + m_physicalSize = QSize(varinfo.width, varinfo.height); + m_id = QByteArray(fixinfo.id); + m_red = {varinfo.red.offset, varinfo.red.length}; + m_green = {varinfo.green.offset, varinfo.green.length}; + m_blue = {varinfo.blue.offset, varinfo.blue.length}; + m_alpha = {varinfo.transp.offset, varinfo.transp.length}; + m_bitsPerPixel = varinfo.bits_per_pixel; + m_bufferLength = fixinfo.smem_len; + m_bytesPerLine = fixinfo.line_length; + + return true; +} + +void FramebufferBackend::map() +{ + if (m_memory) { + // already mapped; + return; + } + if (m_fd < 0) { + // not valid + return; + } + void *mem = mmap(nullptr, m_bufferLength, PROT_WRITE, MAP_SHARED, m_fd, 0); + if (mem == MAP_FAILED) { + qCWarning(KWIN_CORE) << "Failed to mmap frame buffer"; + return; + } + m_memory = mem; +} + +void FramebufferBackend::unmap() +{ + if (!m_memory) { + return; + } + if (munmap(m_memory, m_bufferLength) < 0) { + qCWarning(KWIN_CORE) << "Failed to munmap frame buffer"; + } + m_memory = nullptr; +} + +QImage::Format FramebufferBackend::imageFormat() const +{ + if (m_fd < 0) { + return QImage::Format_Invalid; + } + if (m_alpha.length == 0 && + m_red.length == 8 && + m_green.length == 8 && + m_blue.length == 8 && + m_blue.offset == 0 && + m_green.offset == 8 && + m_red.offset == 16) { + return QImage::Format_RGB32; + } + return QImage::Format_Invalid; +} + +} diff --git a/fb_backend.h b/fb_backend.h new file mode 100644 index 0000000000..e689fbcfb1 --- /dev/null +++ b/fb_backend.h @@ -0,0 +1,99 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_FB_BACKEND_H +#define KWIN_FB_BACKEND_H +#include "abstract_backend.h" + +#include +#include + +namespace KWin +{ + +class KWIN_EXPORT FramebufferBackend : public AbstractBackend +{ + Q_OBJECT +public: + explicit FramebufferBackend(QObject *parent = nullptr); + virtual ~FramebufferBackend(); + + Screens *createScreens(QObject *parent = nullptr) override; + QPainterBackend *createQPainterBackend() override; + + void init(); + void setDevice(const QString &device) { + m_device = device; + } + + bool isValid() const { + return m_fd >= 0; + } + + QSize size() const { + return m_resolution; + } + QSize physicalSize() const { + return m_physicalSize; + } + + void map(); + void unmap(); + void *mappedMemory() const { + return m_memory; + } + int bytesPerLine() const { + return m_bytesPerLine; + } + int bufferSize() const { + return m_bufferLength; + } + quint32 bitsPerPixel() const { + return m_bitsPerPixel; + } + QImage::Format imageFormat() const; + +Q_SIGNALS: + void screensQueried(); + +private: + void openFrameBuffer(); + bool queryScreenInfo(); + QString m_device = QStringLiteral("/dev/fb0"); + QSize m_resolution; + QSize m_physicalSize; + QByteArray m_id; + struct Color { + quint32 offset; + quint32 length; + }; + Color m_red; + Color m_green; + Color m_blue; + Color m_alpha; + quint32 m_bitsPerPixel = 0; + int m_fd = -1; + quint32 m_bufferLength = 0; + int m_bytesPerLine = 0; + void *m_memory = nullptr; +}; + +} + +#endif diff --git a/main_wayland.cpp b/main_wayland.cpp index 7d22da9df6..17807ce0df 100644 --- a/main_wayland.cpp +++ b/main_wayland.cpp @@ -21,6 +21,7 @@ along with this program. If not, see . #include "workspace.h" #include // kwin +#include "fb_backend.h" #include "wayland_backend.h" #include "wayland_server.h" #include "xcbutils.h" @@ -119,6 +120,13 @@ void ApplicationWayland::createBackend() } } } + if (!m_framebuffer.isEmpty()) { + FramebufferBackend *b = new FramebufferBackend(this); + connect(b, &FramebufferBackend::screensQueried, this, &ApplicationWayland::continueStartupWithScreens); + b->setDevice(m_framebuffer); + b->init(); + backend = b; + } if (!backend) { std::cerr << "FATAL ERROR: could not create a backend, exiting now" << std::endl; @@ -378,6 +386,12 @@ KWIN_EXPORT int kdemain(int argc, char * argv[]) QStringLiteral("socket")); QCommandLineOption windowedOption(QStringLiteral("windowed"), i18n("Use a nested compositor in windowed mode.")); + QCommandLineOption framebufferOption(QStringLiteral("framebuffer"), + i18n("Render to framebuffer.")); + QCommandLineOption framebufferDeviceOption(QStringLiteral("fb-device"), + i18n("The framebuffer device to render to."), + QStringLiteral("fbdev")); + framebufferDeviceOption.setDefaultValue(QStringLiteral("/dev/fb0")); QCommandLineOption x11DisplayOption(QStringLiteral("x11-display"), i18n("The X11 Display to use in windowed mode on platform X11."), QStringLiteral("display")); @@ -400,6 +414,8 @@ KWIN_EXPORT int kdemain(int argc, char * argv[]) parser.addOption(windowedOption); parser.addOption(x11DisplayOption); parser.addOption(waylandDisplayOption); + parser.addOption(framebufferOption); + parser.addOption(framebufferDeviceOption); parser.addOption(widthOption); parser.addOption(heightOption); #if HAVE_INPUT @@ -415,6 +431,11 @@ KWIN_EXPORT int kdemain(int argc, char * argv[]) KWin::Application::setUseLibinput(parser.isSet(libinputOption)); #endif + if (parser.isSet(windowedOption) && parser.isSet(framebufferOption)) { + std::cerr << "FATAL ERROR Cannot have both --windowed and --framebuffer" << std::endl; + return 1; + } + a.setWindowed(parser.isSet(windowedOption)); if (parser.isSet(windowedOption)) { bool ok = false; @@ -440,6 +461,9 @@ KWIN_EXPORT int kdemain(int argc, char * argv[]) a.setWaylandDisplay(qgetenv("WAYLAND_DISPLAY")); } } + if (parser.isSet(framebufferOption)) { + a.setFramebuffer(parser.value(framebufferDeviceOption)); + } a.setStartXwayland(parser.isSet(xwaylandOption)); a.start(); diff --git a/main_wayland.h b/main_wayland.h index 36387c90c6..647a2da5a4 100644 --- a/main_wayland.h +++ b/main_wayland.h @@ -46,6 +46,9 @@ public: void setWaylandDisplay(const QByteArray &display) { m_waylandDisplay = display; } + void setFramebuffer(const QString &fbdev) { + m_framebuffer = fbdev; + } protected: void performStartup() override; @@ -62,6 +65,7 @@ private: bool m_windowed = false; QByteArray m_x11Display; QByteArray m_waylandDisplay; + QString m_framebuffer; }; } diff --git a/scene_qpainter.cpp b/scene_qpainter.cpp index 107b1b703d..dd7e44d44d 100644 --- a/scene_qpainter.cpp +++ b/scene_qpainter.cpp @@ -26,6 +26,8 @@ along with this program. If not, see . #include "main.h" #include "toplevel.h" #if HAVE_WAYLAND +#include "fb_backend.h" +#include "virtual_terminal.h" #include "wayland_backend.h" #include "wayland_server.h" #include "x11windowed_backend.h" @@ -246,6 +248,69 @@ bool X11WindowedQPainterBackend::usesOverlayWindow() const return false; } +//**************************************** +// FramebufferBackend +//**************************************** +FramebufferQPainterBackend::FramebufferQPainterBackend(FramebufferBackend *backend) + : QObject() + , QPainterBackend() + , m_renderBuffer(backend->size(), QImage::Format_RGB32) + , m_backend(backend) +{ + m_renderBuffer.fill(Qt::black); + + m_backend->map(); + + m_backBuffer = QImage((uchar*)backend->mappedMemory(), + backend->bytesPerLine() / (backend->bitsPerPixel() / 8), + backend->bufferSize() / backend->bytesPerLine(), + backend->bytesPerLine(), backend->imageFormat()); + + m_backBuffer.fill(Qt::black); + connect(VirtualTerminal::self(), &VirtualTerminal::activeChanged, this, + [this] (bool active) { + if (active) { + Compositor::self()->bufferSwapComplete(); + Compositor::self()->addRepaintFull(); + } else { + Compositor::self()->aboutToSwapBuffers(); + } + } + ); +} + +FramebufferQPainterBackend::~FramebufferQPainterBackend() = default; + +QImage *FramebufferQPainterBackend::buffer() +{ + return &m_renderBuffer; +} + +bool FramebufferQPainterBackend::needsFullRepaint() const +{ + return false; +} + +void FramebufferQPainterBackend::prepareRenderingFrame() +{ +} + +void FramebufferQPainterBackend::present(int mask, const QRegion &damage) +{ + Q_UNUSED(mask) + Q_UNUSED(damage) + if (!VirtualTerminal::self()->isActive()) { + return; + } + QPainter p(&m_backBuffer); + p.drawImage(QPoint(0, 0), m_renderBuffer); +} + +bool FramebufferQPainterBackend::usesOverlayWindow() const +{ + return false; +} + #endif //**************************************** diff --git a/scene_qpainter.h b/scene_qpainter.h index 5106c1fc09..a422f4e5b6 100644 --- a/scene_qpainter.h +++ b/scene_qpainter.h @@ -44,6 +44,7 @@ namespace Wayland { class WaylandBackend; } +class FramebufferBackend; class X11WindowedBackend; class QPainterBackend @@ -152,6 +153,25 @@ private: QImage m_backBuffer; X11WindowedBackend *m_backend; }; + +class FramebufferQPainterBackend : public QObject, public QPainterBackend +{ + Q_OBJECT +public: + FramebufferQPainterBackend(FramebufferBackend *backend); + virtual ~FramebufferQPainterBackend(); + + QImage *buffer() override; + bool needsFullRepaint() const override; + bool usesOverlayWindow() const override; + void prepareRenderingFrame() override; + void present(int mask, const QRegion &damage) override; + +private: + QImage m_renderBuffer; + QImage m_backBuffer; + FramebufferBackend *m_backend; +}; #endif class SceneQPainter : public Scene diff --git a/screens_fb.cpp b/screens_fb.cpp new file mode 100644 index 0000000000..8b5ceeb601 --- /dev/null +++ b/screens_fb.cpp @@ -0,0 +1,68 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#include "screens_fb.h" +#include "fb_backend.h" + +namespace KWin +{ + +FramebufferScreens::FramebufferScreens(FramebufferBackend *backend, QObject *parent) + : Screens(parent) + , m_backend(backend) +{ +} + +FramebufferScreens::~FramebufferScreens() = default; + +void FramebufferScreens::init() +{ + KWin::Screens::init(); + updateCount(); + emit changed(); +} + +QRect FramebufferScreens::geometry(int screen) const +{ + if (screen == 0) { + return QRect(QPoint(0, 0), size(screen)); + } + return QRect(); +} + +QSize FramebufferScreens::size(int screen) const +{ + if (screen == 0) { + return m_backend->size(); + } + return QSize(); +} + +void FramebufferScreens::updateCount() +{ + setCount(1); +} + +int FramebufferScreens::number(const QPoint &pos) const +{ + Q_UNUSED(pos) + return 0; +} + +} diff --git a/screens_fb.h b/screens_fb.h new file mode 100644 index 0000000000..a50d03ae42 --- /dev/null +++ b/screens_fb.h @@ -0,0 +1,46 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2015 Martin Gräßlin + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ +#ifndef KWIN_SCREENS_FRAMEBUFFER_H +#define KWIN_SCREENS_FRAMEBUFFER_H +#include "screens.h" + +namespace KWin +{ +class FramebufferBackend; + +class FramebufferScreens : public Screens +{ + Q_OBJECT +public: + FramebufferScreens(FramebufferBackend *backend, QObject *parent = nullptr); + virtual ~FramebufferScreens(); + void init() override; + QRect geometry(int screen) const override; + int number(const QPoint &pos) const override; + QSize size(int screen) const override; + void updateCount() override; + +private: + FramebufferBackend *m_backend; +}; + +} + +#endif