2015-04-09 12:49:32 +00:00
|
|
|
/********************************************************************
|
|
|
|
KWin - the KDE window manager
|
|
|
|
This file is part of the KDE project.
|
|
|
|
|
|
|
|
Copyright (C) 2015 Martin Gräßlin <mgraesslin@kde.org>
|
|
|
|
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*********************************************************************/
|
|
|
|
#include "drm_backend.h"
|
2015-04-13 07:23:03 +00:00
|
|
|
#include "composite.h"
|
2015-04-13 14:25:34 +00:00
|
|
|
#include "cursor.h"
|
2015-04-09 12:49:32 +00:00
|
|
|
#include "logind.h"
|
|
|
|
#include "scene_qpainter.h"
|
|
|
|
#include "screens_drm.h"
|
|
|
|
#include "udev.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include "virtual_terminal.h"
|
2015-04-10 08:44:07 +00:00
|
|
|
#if HAVE_GBM
|
|
|
|
#include "egl_gbm_backend.h"
|
|
|
|
#endif
|
2015-04-13 07:23:03 +00:00
|
|
|
// Qt
|
|
|
|
#include <QSocketNotifier>
|
2015-04-13 14:25:34 +00:00
|
|
|
#include <QPainter>
|
2015-04-09 12:49:32 +00:00
|
|
|
// system
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
|
|
|
// drm
|
|
|
|
#include <xf86drm.h>
|
|
|
|
#include <xf86drmMode.h>
|
|
|
|
#include <libdrm/drm_mode.h>
|
2015-04-10 08:44:07 +00:00
|
|
|
#if HAVE_GBM
|
|
|
|
#include <gbm.h>
|
|
|
|
#endif
|
2015-04-09 12:49:32 +00:00
|
|
|
|
2015-04-13 14:25:34 +00:00
|
|
|
#ifndef DRM_CAP_CURSOR_WIDTH
|
|
|
|
#define DRM_CAP_CURSOR_WIDTH 0x8
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifndef DRM_CAP_CURSOR_HEIGHT
|
|
|
|
#define DRM_CAP_CURSOR_HEIGHT 0x9
|
|
|
|
#endif
|
|
|
|
|
2015-04-09 12:49:32 +00:00
|
|
|
#include <QDebug>
|
|
|
|
|
|
|
|
namespace KWin
|
|
|
|
{
|
|
|
|
|
|
|
|
DrmBackend::DrmBackend(QObject *parent)
|
|
|
|
: AbstractBackend(parent)
|
|
|
|
, m_udev(new Udev)
|
|
|
|
{
|
2015-04-13 14:25:34 +00:00
|
|
|
m_cursor[0] = nullptr;
|
|
|
|
m_cursor[1] = nullptr;
|
2015-04-09 12:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DrmBackend::~DrmBackend()
|
|
|
|
{
|
|
|
|
if (m_fd >= 0) {
|
2015-04-13 14:25:34 +00:00
|
|
|
hideCursor();
|
|
|
|
delete m_cursor[0];
|
|
|
|
delete m_cursor[1];
|
2015-04-09 12:49:32 +00:00
|
|
|
close(m_fd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::init()
|
|
|
|
{
|
|
|
|
LogindIntegration *logind = LogindIntegration::self();
|
|
|
|
auto takeControl = [logind, this]() {
|
|
|
|
if (logind->hasSessionControl()) {
|
|
|
|
openDrm();
|
|
|
|
} else {
|
|
|
|
logind->takeControl();
|
|
|
|
connect(logind, &LogindIntegration::hasSessionControlChanged, this, &DrmBackend::openDrm);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if (logind->isConnected()) {
|
|
|
|
takeControl();
|
|
|
|
} else {
|
|
|
|
connect(logind, &LogindIntegration::connectedChanged, this, takeControl);
|
|
|
|
}
|
|
|
|
VirtualTerminal::create(this);
|
|
|
|
}
|
|
|
|
|
2015-04-13 07:23:03 +00:00
|
|
|
void DrmBackend::pageFlipHandler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data)
|
|
|
|
{
|
|
|
|
Q_UNUSED(fd)
|
|
|
|
Q_UNUSED(frame)
|
|
|
|
Q_UNUSED(sec)
|
|
|
|
Q_UNUSED(usec)
|
|
|
|
DrmBuffer *buffer = reinterpret_cast<DrmBuffer*>(data);
|
|
|
|
buffer->m_backend->m_pageFlipPending = false;
|
2015-04-10 08:44:07 +00:00
|
|
|
#if HAVE_GBM
|
|
|
|
if (buffer->m_bo) {
|
|
|
|
gbm_surface_release_buffer(buffer->m_surface, buffer->m_bo);
|
|
|
|
buffer->m_bo = nullptr;
|
|
|
|
}
|
|
|
|
#endif
|
2015-04-13 07:23:03 +00:00
|
|
|
Compositor::self()->bufferSwapComplete();
|
|
|
|
}
|
|
|
|
|
2015-04-09 12:49:32 +00:00
|
|
|
void DrmBackend::openDrm()
|
|
|
|
{
|
|
|
|
VirtualTerminal::self()->init();
|
|
|
|
UdevDevice::Ptr device = m_udev->primaryGpu();
|
|
|
|
if (!device) {
|
|
|
|
qCWarning(KWIN_CORE) << "Did not find a GPU";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
int fd = LogindIntegration::self()->takeDevice(device->devNode());
|
|
|
|
if (fd < 0) {
|
|
|
|
qCWarning(KWIN_CORE) << "failed to open drm device at" << device->devNode();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_fd = fd;
|
2015-04-13 07:23:03 +00:00
|
|
|
QSocketNotifier *notifier = new QSocketNotifier(m_fd, QSocketNotifier::Read, this);
|
|
|
|
connect(notifier, &QSocketNotifier::activated, this,
|
|
|
|
[this] {
|
|
|
|
drmEventContext e;
|
|
|
|
memset(&e, 0, sizeof e);
|
|
|
|
e.version = DRM_EVENT_CONTEXT_VERSION;
|
|
|
|
e.page_flip_handler = pageFlipHandler;
|
|
|
|
drmHandleEvent(m_fd, &e);
|
|
|
|
}
|
|
|
|
);
|
2015-04-09 12:49:32 +00:00
|
|
|
m_drmId = device->sysNum();
|
|
|
|
queryResources();
|
|
|
|
emit screensQueried();
|
2015-04-13 14:25:34 +00:00
|
|
|
|
|
|
|
initCursor();
|
2015-04-09 12:49:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template <typename Pointer, void (*cleanupFunc)(Pointer*)>
|
|
|
|
struct DrmCleanup
|
|
|
|
{
|
|
|
|
static inline void cleanup(Pointer *ptr)
|
|
|
|
{
|
|
|
|
cleanupFunc(ptr);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
template <typename T, void (*cleanupFunc)(T*)> using ScopedDrmPointer = QScopedPointer<T, DrmCleanup<T, cleanupFunc>>;
|
|
|
|
|
|
|
|
void DrmBackend::queryResources()
|
|
|
|
{
|
|
|
|
if (m_fd < 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ScopedDrmPointer<_drmModeRes, &drmModeFreeResources> resources(drmModeGetResources(m_fd));
|
|
|
|
if (!resources) {
|
|
|
|
qCWarning(KWIN_CORE) << "drmModeGetResources failed";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (int i = 0; i < resources->count_connectors; ++i) {
|
|
|
|
const auto id = resources->connectors[i];
|
|
|
|
ScopedDrmPointer<_drmModeConnector, &drmModeFreeConnector> connector(drmModeGetConnector(m_fd, id));
|
|
|
|
if (!connector) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (connector->connection != DRM_MODE_CONNECTED) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
ScopedDrmPointer<_drmModeEncoder, &drmModeFreeEncoder> encoder(drmModeGetEncoder(m_fd, connector->encoder_id));
|
|
|
|
if (!encoder) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
ScopedDrmPointer<_drmModeCrtc, &drmModeFreeCrtc> crtc(drmModeGetCrtc(m_fd, encoder->crtc_id));
|
|
|
|
if (!crtc) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
m_resolution = QSize(crtc->mode.hdisplay, crtc->mode.vdisplay);
|
|
|
|
m_crtcId = encoder->crtc_id;
|
|
|
|
m_connector = connector->connector_id;
|
|
|
|
m_mode = crtc->mode;
|
2015-04-13 07:23:03 +00:00
|
|
|
// TODO: improve
|
|
|
|
DrmBuffer *b = new DrmBuffer(this, m_resolution);
|
|
|
|
b->map();
|
|
|
|
b->image()->fill(Qt::black);
|
|
|
|
drmModeSetCrtc(m_fd, m_crtcId, b->m_bufferId, 0, 0, &m_connector, 1, &m_mode);
|
2015-04-09 12:49:32 +00:00
|
|
|
// for the moment only one crtc
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::present(DrmBuffer *buffer)
|
|
|
|
{
|
2015-04-13 07:23:03 +00:00
|
|
|
if (!buffer || buffer->m_bufferId == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (m_pageFlipPending) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_pageFlipPending = true;
|
|
|
|
Compositor::self()->aboutToSwapBuffers();
|
|
|
|
drmModePageFlip(m_fd, m_crtcId, buffer->m_bufferId, DRM_MODE_PAGE_FLIP_EVENT, buffer);
|
2015-04-09 12:49:32 +00:00
|
|
|
}
|
|
|
|
|
2015-04-13 14:25:34 +00:00
|
|
|
void DrmBackend::installCursorFromServer()
|
|
|
|
{
|
|
|
|
updateCursorFromServer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::installCursorImage(Qt::CursorShape shape)
|
|
|
|
{
|
|
|
|
updateCursorImage(shape);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::initCursor()
|
|
|
|
{
|
|
|
|
uint64_t capability = 0;
|
|
|
|
QSize cursorSize;
|
|
|
|
if (drmGetCap(m_fd, DRM_CAP_CURSOR_WIDTH, &capability) == 0) {
|
|
|
|
cursorSize.setWidth(capability);
|
|
|
|
} else {
|
|
|
|
cursorSize.setWidth(64);
|
|
|
|
}
|
|
|
|
if (drmGetCap(m_fd, DRM_CAP_CURSOR_HEIGHT, &capability) == 0) {
|
|
|
|
cursorSize.setHeight(capability);
|
|
|
|
} else {
|
|
|
|
cursorSize.setHeight(64);
|
|
|
|
}
|
|
|
|
m_cursor[0] = createBuffer(cursorSize);
|
|
|
|
m_cursor[0]->map(QImage::Format_ARGB32_Premultiplied);
|
|
|
|
m_cursor[0]->image()->fill(Qt::transparent);
|
|
|
|
m_cursor[1] = createBuffer(cursorSize);
|
|
|
|
m_cursor[1]->map(QImage::Format_ARGB32_Premultiplied);
|
|
|
|
m_cursor[0]->image()->fill(Qt::transparent);
|
|
|
|
// now we have screens and can set cursors, so start tracking
|
|
|
|
connect(this, &DrmBackend::cursorChanged, this, &DrmBackend::updateCursor);
|
|
|
|
connect(Cursor::self(), &Cursor::posChanged, this, &DrmBackend::moveCursor);
|
|
|
|
installCursorImage(Qt::ArrowCursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::setCursor()
|
|
|
|
{
|
|
|
|
DrmBuffer *c = m_cursor[m_cursorIndex];
|
|
|
|
m_cursorIndex = (m_cursorIndex + 1) % 2;
|
|
|
|
drmModeSetCursor(m_fd, m_crtcId, c->m_handle, c->m_size.width(), c->m_size.height());
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::updateCursor()
|
|
|
|
{
|
|
|
|
const QImage &cursorImage = softwareCursor();
|
|
|
|
if (cursorImage.isNull()) {
|
|
|
|
hideCursor();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
QImage *c = m_cursor[m_cursorIndex]->image();
|
|
|
|
c->fill(Qt::transparent);
|
|
|
|
QPainter p;
|
|
|
|
p.begin(c);
|
|
|
|
p.drawImage(QPoint(0, 0), cursorImage);
|
|
|
|
p.end();
|
|
|
|
|
|
|
|
setCursor();
|
|
|
|
moveCursor();
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::hideCursor()
|
|
|
|
{
|
|
|
|
drmModeSetCursor(m_fd, m_crtcId, 0, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DrmBackend::moveCursor()
|
|
|
|
{
|
|
|
|
const QPoint p = Cursor::pos() - softwareCursorHotspot();
|
|
|
|
drmModeMoveCursor(m_fd, m_crtcId, p.x(), p.y());
|
|
|
|
}
|
|
|
|
|
2015-04-09 12:49:32 +00:00
|
|
|
Screens *DrmBackend::createScreens(QObject *parent)
|
|
|
|
{
|
|
|
|
return new DrmScreens(this, parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
QPainterBackend *DrmBackend::createQPainterBackend()
|
|
|
|
{
|
|
|
|
return new DrmQPainterBackend(this);
|
|
|
|
}
|
|
|
|
|
2015-04-10 08:44:07 +00:00
|
|
|
OpenGLBackend *DrmBackend::createOpenGLBackend()
|
|
|
|
{
|
|
|
|
#if HAVE_GBM
|
|
|
|
return new EglGbmBackend(this);
|
|
|
|
#endif
|
|
|
|
return AbstractBackend::createOpenGLBackend();
|
|
|
|
}
|
|
|
|
|
2015-04-09 12:49:32 +00:00
|
|
|
DrmBuffer *DrmBackend::createBuffer(const QSize &size)
|
|
|
|
{
|
|
|
|
return new DrmBuffer(this, size);
|
|
|
|
}
|
|
|
|
|
2015-04-10 08:44:07 +00:00
|
|
|
DrmBuffer *DrmBackend::createBuffer(gbm_surface *surface)
|
|
|
|
{
|
|
|
|
#if HAVE_GBM
|
|
|
|
return new DrmBuffer(this, surface);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-04-09 12:49:32 +00:00
|
|
|
DrmBuffer::DrmBuffer(DrmBackend *backend, const QSize &size)
|
|
|
|
: m_backend(backend)
|
|
|
|
, m_size(size)
|
|
|
|
{
|
|
|
|
drm_mode_create_dumb createArgs;
|
|
|
|
memset(&createArgs, 0, sizeof createArgs);
|
|
|
|
createArgs.bpp = 32;
|
|
|
|
createArgs.width = size.width();
|
|
|
|
createArgs.height = size.height();
|
|
|
|
if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_CREATE_DUMB, &createArgs) != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_handle = createArgs.handle;
|
|
|
|
m_bufferSize = createArgs.size;
|
|
|
|
m_stride = createArgs.pitch;
|
|
|
|
drmModeAddFB(m_backend->fd(), size.width(), size.height(), 24, 32,
|
|
|
|
m_stride, createArgs.handle, &m_bufferId);
|
|
|
|
}
|
|
|
|
|
2015-04-10 08:44:07 +00:00
|
|
|
|
|
|
|
#if HAVE_GBM
|
|
|
|
static void gbmCallback(gbm_bo *bo, void *data)
|
|
|
|
{
|
|
|
|
Q_UNUSED(bo);
|
|
|
|
delete reinterpret_cast<DrmBuffer*>(data);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
DrmBuffer::DrmBuffer(DrmBackend *backend, gbm_surface *surface)
|
|
|
|
: m_backend(backend)
|
|
|
|
, m_surface(surface)
|
|
|
|
{
|
|
|
|
#if HAVE_GBM
|
|
|
|
m_bo = gbm_surface_lock_front_buffer(surface);
|
|
|
|
if (!m_bo) {
|
|
|
|
qWarning(KWIN_CORE) << "Locking front buffer failed";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_size = QSize(gbm_bo_get_width(m_bo), gbm_bo_get_height(m_bo));
|
|
|
|
m_stride = gbm_bo_get_stride(m_bo);
|
|
|
|
if (drmModeAddFB(m_backend->fd(), m_size.width(), m_size.height(), 24, 32, m_stride, gbm_bo_get_handle(m_bo).u32, &m_bufferId) != 0) {
|
|
|
|
qWarning(KWIN_CORE) << "drmModeAddFB failed";
|
|
|
|
}
|
|
|
|
gbm_bo_set_user_data(m_bo, this, gbmCallback);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-04-09 12:49:32 +00:00
|
|
|
DrmBuffer::~DrmBuffer()
|
|
|
|
{
|
|
|
|
delete m_image;
|
|
|
|
if (m_memory) {
|
|
|
|
munmap(m_memory, m_bufferSize);
|
|
|
|
}
|
|
|
|
if (m_bufferId) {
|
|
|
|
drmModeRmFB(m_backend->fd(), m_bufferId);
|
|
|
|
}
|
|
|
|
if (m_handle) {
|
|
|
|
drm_mode_destroy_dumb destroyArgs;
|
|
|
|
destroyArgs.handle = m_handle;
|
|
|
|
drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_DESTROY_DUMB, &destroyArgs);
|
|
|
|
}
|
2015-04-10 08:44:07 +00:00
|
|
|
#if HAVE_GBM
|
|
|
|
if (m_bo) {
|
|
|
|
gbm_surface_release_buffer(m_surface, m_bo);
|
|
|
|
}
|
|
|
|
#endif
|
2015-04-09 12:49:32 +00:00
|
|
|
}
|
|
|
|
|
2015-04-13 14:25:34 +00:00
|
|
|
bool DrmBuffer::map(QImage::Format format)
|
2015-04-09 12:49:32 +00:00
|
|
|
{
|
|
|
|
if (!m_handle || !m_bufferId) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
drm_mode_map_dumb mapArgs;
|
|
|
|
memset(&mapArgs, 0, sizeof mapArgs);
|
|
|
|
mapArgs.handle = m_handle;
|
|
|
|
if (drmIoctl(m_backend->fd(), DRM_IOCTL_MODE_MAP_DUMB, &mapArgs) != 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
void *address = mmap(nullptr, m_bufferSize, PROT_WRITE, MAP_SHARED, m_backend->fd(), mapArgs.offset);
|
|
|
|
if (address == MAP_FAILED) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
m_memory = address;
|
2015-04-13 14:25:34 +00:00
|
|
|
m_image = new QImage((uchar*)m_memory, m_size.width(), m_size.height(), m_stride, format);
|
2015-04-09 12:49:32 +00:00
|
|
|
return !m_image->isNull();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|