You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

639 lines
19 KiB

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "wayland_backend.h"
#include "dmabuftexture.h"
#include "dpmsinputeventfilter.h"
#include "input.h"
#include "wayland_display.h"
#include "wayland_egl_backend.h"
#include "wayland_logging.h"
#include "wayland_output.h"
#include "wayland_qpainter_backend.h"
#include <KWayland/Client/keyboard.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/pointergestures.h>
#include <KWayland/Client/relativepointer.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/touch.h>
#include <QAbstractEventDispatcher>
#include <drm_fourcc.h>
#include <fcntl.h>
#include <gbm.h>
#include <linux/input.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include "../drm/gbm_dmabuf.h"
namespace KWin
{
namespace Wayland
{
using namespace KWayland::Client;
inline static QPointF sizeToPoint(const QSizeF &size)
{
return QPointF(size.width(), size.height());
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Keyboard *keyboard, WaylandSeat *seat)
: m_seat(seat)
, m_keyboard(keyboard)
{
connect(keyboard, &Keyboard::keyChanged, this, [this](quint32 key, Keyboard::KeyState nativeState, quint32 time) {
InputRedirection::KeyboardKeyState state;
switch (nativeState) {
case Keyboard::KeyState::Pressed:
if (key == KEY_RIGHTCTRL) {
m_seat->backend()->togglePointerLock();
}
state = InputRedirection::KeyboardKeyPressed;
break;
case Keyboard::KeyState::Released:
state = InputRedirection::KeyboardKeyReleased;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT keyChanged(key, state, std::chrono::milliseconds(time), this);
});
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Pointer *pointer, WaylandSeat *seat)
: m_seat(seat)
, m_pointer(pointer)
{
connect(pointer, &Pointer::entered, this, [this](quint32 serial, const QPointF &relativeToSurface) {
WaylandOutput *output = m_seat->backend()->findOutput(m_pointer->enteredSurface());
Q_ASSERT(output);
output->cursor()->setPointer(m_pointer.get());
});
connect(pointer, &Pointer::left, this, [this]() {
// wl_pointer.leave carries the wl_surface, but KWayland::Client::Pointer::left does not.
const auto outputs = m_seat->backend()->outputs();
for (Output *output : outputs) {
WaylandOutput *waylandOutput = static_cast<WaylandOutput *>(output);
if (waylandOutput->cursor()->pointer()) {
waylandOutput->cursor()->setPointer(nullptr);
}
}
});
connect(pointer, &Pointer::motion, this, [this](const QPointF &relativeToSurface, quint32 time) {
WaylandOutput *output = m_seat->backend()->findOutput(m_pointer->enteredSurface());
Q_ASSERT(output);
const QPointF absolutePos = output->geometry().topLeft() + relativeToSurface;
Q_EMIT pointerMotionAbsolute(absolutePos, std::chrono::milliseconds(time), this);
});
connect(pointer, &Pointer::buttonStateChanged, this, [this](quint32 serial, quint32 time, quint32 button, Pointer::ButtonState nativeState) {
InputRedirection::PointerButtonState state;
switch (nativeState) {
case Pointer::ButtonState::Pressed:
state = InputRedirection::PointerButtonPressed;
break;
case Pointer::ButtonState::Released:
state = InputRedirection::PointerButtonReleased;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT pointerButtonChanged(button, state, std::chrono::milliseconds(time), this);
});
// TODO: Send discreteDelta and source as well.
connect(pointer, &Pointer::axisChanged, this, [this](quint32 time, Pointer::Axis nativeAxis, qreal delta) {
InputRedirection::PointerAxis axis;
switch (nativeAxis) {
case Pointer::Axis::Horizontal:
axis = InputRedirection::PointerAxisHorizontal;
break;
case Pointer::Axis::Vertical:
axis = InputRedirection::PointerAxisVertical;
break;
default:
Q_UNREACHABLE();
}
Q_EMIT pointerAxisChanged(axis, delta, 0, InputRedirection::PointerAxisSourceUnknown, std::chrono::milliseconds(time), this);
});
KWayland::Client::PointerGestures *pointerGestures = m_seat->backend()->display()->pointerGestures();
if (pointerGestures) {
m_pinchGesture.reset(pointerGestures->createPinchGesture(m_pointer.get(), this));
connect(m_pinchGesture.get(), &PointerPinchGesture::started, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureBegin(m_pinchGesture->fingerCount(), std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::updated, this, [this](const QSizeF &delta, qreal scale, qreal rotation, quint32 time) {
Q_EMIT pinchGestureUpdate(scale, rotation, sizeToPoint(delta), std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::ended, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureEnd(std::chrono::milliseconds(time), this);
});
connect(m_pinchGesture.get(), &PointerPinchGesture::cancelled, this, [this](quint32 serial, quint32 time) {
Q_EMIT pinchGestureCancelled(std::chrono::milliseconds(time), this);
});
m_swipeGesture.reset(pointerGestures->createSwipeGesture(m_pointer.get(), this));
connect(m_swipeGesture.get(), &PointerSwipeGesture::started, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureBegin(m_swipeGesture->fingerCount(), std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::updated, this, [this](const QSizeF &delta, quint32 time) {
Q_EMIT swipeGestureUpdate(sizeToPoint(delta), std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::ended, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureEnd(std::chrono::milliseconds(time), this);
});
connect(m_swipeGesture.get(), &PointerSwipeGesture::cancelled, this, [this](quint32 serial, quint32 time) {
Q_EMIT swipeGestureCancelled(std::chrono::milliseconds(time), this);
});
}
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::RelativePointer *relativePointer, WaylandSeat *seat)
: m_seat(seat)
, m_relativePointer(relativePointer)
{
connect(relativePointer, &RelativePointer::relativeMotion, this, [this](const QSizeF &delta, const QSizeF &deltaNonAccelerated, quint64 timestamp) {
Q_EMIT pointerMotion(sizeToPoint(delta), sizeToPoint(deltaNonAccelerated), std::chrono::microseconds(timestamp), this);
});
}
WaylandInputDevice::WaylandInputDevice(KWayland::Client::Touch *touch, WaylandSeat *seat)
: m_seat(seat)
, m_touch(touch)
{
connect(touch, &Touch::sequenceCanceled, this, [this]() {
Q_EMIT touchCanceled(this);
});
connect(touch, &Touch::frameEnded, this, [this]() {
Q_EMIT touchFrame(this);
});
connect(touch, &Touch::sequenceStarted, this, [this](TouchPoint *tp) {
Q_EMIT touchDown(tp->id(), tp->position(), std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointAdded, this, [this](TouchPoint *tp) {
Q_EMIT touchDown(tp->id(), tp->position(), std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointRemoved, this, [this](TouchPoint *tp) {
Q_EMIT touchUp(tp->id(), std::chrono::milliseconds(tp->time()), this);
});
connect(touch, &Touch::pointMoved, this, [this](TouchPoint *tp) {
Q_EMIT touchMotion(tp->id(), tp->position(), std::chrono::milliseconds(tp->time()), this);
});
}
WaylandInputDevice::~WaylandInputDevice()
{
}
QString WaylandInputDevice::sysName() const
{
return QString();
}
QString WaylandInputDevice::name() const
{
return QString();
}
bool WaylandInputDevice::isEnabled() const
{
return true;
}
void WaylandInputDevice::setEnabled(bool enabled)
{
}
LEDs WaylandInputDevice::leds() const
{
return LEDs();
}
void WaylandInputDevice::setLeds(LEDs leds)
{
}
bool WaylandInputDevice::isKeyboard() const
{
return m_keyboard != nullptr;
}
bool WaylandInputDevice::isAlphaNumericKeyboard() const
{
return m_keyboard != nullptr;
}
bool WaylandInputDevice::isPointer() const
{
return m_pointer || m_relativePointer;
}
bool WaylandInputDevice::isTouchpad() const
{
return false;
}
bool WaylandInputDevice::isTouch() const
{
return m_touch != nullptr;
}
bool WaylandInputDevice::isTabletTool() const
{
return false;
}
bool WaylandInputDevice::isTabletPad() const
{
return false;
}
bool WaylandInputDevice::isTabletModeSwitch() const
{
return false;
}
bool WaylandInputDevice::isLidSwitch() const
{
return false;
}
KWayland::Client::Pointer *WaylandInputDevice::nativePointer() const
{
return m_pointer.get();
}
WaylandInputBackend::WaylandInputBackend(WaylandBackend *backend, QObject *parent)
: InputBackend(parent)
, m_backend(backend)
{
}
void WaylandInputBackend::initialize()
{
WaylandSeat *seat = m_backend->seat();
if (seat->relativePointerDevice()) {
Q_EMIT deviceAdded(seat->relativePointerDevice());
}
if (seat->pointerDevice()) {
Q_EMIT deviceAdded(seat->pointerDevice());
}
if (seat->keyboardDevice()) {
Q_EMIT deviceAdded(seat->keyboardDevice());
}
if (seat->touchDevice()) {
Q_EMIT deviceAdded(seat->touchDevice());
}
connect(seat, &WaylandSeat::deviceAdded, this, &InputBackend::deviceAdded);
connect(seat, &WaylandSeat::deviceRemoved, this, &InputBackend::deviceRemoved);
}
WaylandSeat::WaylandSeat(KWayland::Client::Seat *nativeSeat, WaylandBackend *backend)
: QObject(nullptr)
, m_seat(nativeSeat)
, m_backend(backend)
{
auto updateKeyboardDevice = [this](){
if (m_seat->hasKeyboard()) {
createKeyboardDevice();
} else {
destroyKeyboardDevice();
}
};
updateKeyboardDevice();
connect(m_seat, &Seat::hasKeyboardChanged, this, updateKeyboardDevice);
auto updatePointerDevice = [this]() {
if (m_seat->hasPointer()) {
createPointerDevice();
} else {
destroyPointerDevice();
}
};
updatePointerDevice();
connect(m_seat, &Seat::hasPointerChanged, this, updatePointerDevice);
auto updateTouchDevice = [this]() {
if (m_seat->hasTouch()) {
createTouchDevice();
} else {
destroyTouchDevice();
}
};
updateTouchDevice();
connect(m_seat, &Seat::hasTouchChanged, this, updateTouchDevice);
}
WaylandSeat::~WaylandSeat()
{
destroyPointerDevice();
destroyKeyboardDevice();
destroyTouchDevice();
}
void WaylandSeat::createPointerDevice()
{
m_pointerDevice = std::make_unique<WaylandInputDevice>(m_seat->createPointer(), this);
Q_EMIT deviceAdded(m_pointerDevice.get());
}
void WaylandSeat::destroyPointerDevice()
{
if (m_pointerDevice) {
Q_EMIT deviceRemoved(m_pointerDevice.get());
destroyRelativePointer();
m_pointerDevice.reset();
}
}
void WaylandSeat::createRelativePointer()
{
KWayland::Client::RelativePointerManager *manager = m_backend->display()->relativePointerManager();
if (manager) {
m_relativePointerDevice = std::make_unique<WaylandInputDevice>(manager->createRelativePointer(m_pointerDevice->nativePointer()), this);
Q_EMIT deviceAdded(m_relativePointerDevice.get());
}
}
void WaylandSeat::destroyRelativePointer()
{
if (m_relativePointerDevice) {
Q_EMIT deviceRemoved(m_relativePointerDevice.get());
m_relativePointerDevice.reset();
}
}
void WaylandSeat::createKeyboardDevice()
{
m_keyboardDevice = std::make_unique<WaylandInputDevice>(m_seat->createKeyboard(), this);
Q_EMIT deviceAdded(m_keyboardDevice.get());
}
void WaylandSeat::destroyKeyboardDevice()
{
if (m_keyboardDevice) {
Q_EMIT deviceRemoved(m_keyboardDevice.get());
m_keyboardDevice.reset();
}
}
void WaylandSeat::createTouchDevice()
{
m_touchDevice = std::make_unique<WaylandInputDevice>(m_seat->createTouch(), this);
Q_EMIT deviceAdded(m_touchDevice.get());
}
void WaylandSeat::destroyTouchDevice()
{
if (m_touchDevice) {
Q_EMIT deviceRemoved(m_touchDevice.get());
m_touchDevice.reset();
}
}
WaylandBackend::WaylandBackend(const WaylandBackendOptions &options, QObject *parent)
: OutputBackend(parent)
, m_options(options)
{
char const *drm_render_node = "/dev/dri/renderD128";
m_drmFileDescriptor = FileDescriptor(open(drm_render_node, O_RDWR | O_CLOEXEC));
if (!m_drmFileDescriptor.isValid()) {
qCWarning(KWIN_WAYLAND_BACKEND) << "Failed to open drm render node" << drm_render_node;
m_gbmDevice = nullptr;
return;
}
m_gbmDevice = gbm_create_device(m_drmFileDescriptor.get());
}
WaylandBackend::~WaylandBackend()
{
m_eglDisplay.reset();
destroyOutputs();
m_seat.reset();
m_display.reset();
if (m_gbmDevice) {
gbm_device_destroy(m_gbmDevice);
}
qCDebug(KWIN_WAYLAND_BACKEND) << "Destroyed Wayland display";
}
bool WaylandBackend::initialize()
{
m_display = std::make_unique<WaylandDisplay>();
if (!m_display->initialize(m_options.socketName)) {
return false;
}
createOutputs();
m_seat = std::make_unique<WaylandSeat>(m_display->seat(), this);
QAbstractEventDispatcher *dispatcher = QAbstractEventDispatcher::instance();
QObject::connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, m_display.get(), &WaylandDisplay::flush);
QObject::connect(dispatcher, &QAbstractEventDispatcher::awake, m_display.get(), &WaylandDisplay::flush);
connect(this, &WaylandBackend::pointerLockChanged, this, [this](bool locked) {
if (locked) {
m_seat->createRelativePointer();
} else {
m_seat->destroyRelativePointer();
}
});
return true;
}
void WaylandBackend::createOutputs()
{
// we need to multiply the initial window size with the scale in order to
// create an output window of this size in the end
const QSize pixelSize = m_options.outputSize * m_options.outputScale;
for (int i = 0; i < m_options.outputCount; i++) {
WaylandOutput *output = createOutput(QStringLiteral("WL-%1").arg(i), pixelSize, m_options.outputScale);
m_outputs << output;
Q_EMIT outputAdded(output);
output->updateEnabled(true);
}
Q_EMIT outputsQueried();
}
WaylandOutput *WaylandBackend::createOutput(const QString &name, const QSize &size, qreal scale)
{
WaylandOutput *waylandOutput = new WaylandOutput(name, this);
waylandOutput->init(size, scale);
// Wait until the output window is configured by the host compositor.
while (!waylandOutput->isReady()) {
wl_display_roundtrip(m_display->nativeDisplay());
}
return waylandOutput;
}
void WaylandBackend::destroyOutputs()
{
while (!m_outputs.isEmpty()) {
WaylandOutput *output = m_outputs.takeLast();
output->updateEnabled(false);
Q_EMIT outputRemoved(output);
delete output;
}
}
std::unique_ptr<InputBackend> WaylandBackend::createInputBackend()
{
return std::make_unique<WaylandInputBackend>(this);
}
std::unique_ptr<OpenGLBackend> WaylandBackend::createOpenGLBackend()
{
return std::make_unique<WaylandEglBackend>(this);
}
std::unique_ptr<QPainterBackend> WaylandBackend::createQPainterBackend()
{
return std::make_unique<WaylandQPainterBackend>(this);
}
WaylandOutput *WaylandBackend::findOutput(KWayland::Client::Surface *nativeSurface) const
{
for (WaylandOutput *output : m_outputs) {
if (output->surface() == nativeSurface) {
return output;
}
}
return nullptr;
}
bool WaylandBackend::supportsPointerLock()
{
return m_display->pointerConstraints() && m_display->relativePointerManager();
}
void WaylandBackend::togglePointerLock()
{
if (!supportsPointerLock()) {
return;
}
if (!m_seat) {
return;
}
auto pointer = m_seat->pointerDevice()->nativePointer();
if (!pointer) {
return;
}
if (m_outputs.isEmpty()) {
return;
}
for (auto output : std::as_const(m_outputs)) {
output->lockPointer(m_seat->pointerDevice()->nativePointer(), !m_pointerLockRequested);
}
m_pointerLockRequested = !m_pointerLockRequested;
}
QVector<CompositingType> WaylandBackend::supportedCompositors() const
{
QVector<CompositingType> ret;
if (m_display->linuxDmabuf() && m_gbmDevice) {
ret.append(OpenGLCompositing);
}
ret.append(QPainterCompositing);
return ret;
}
Outputs WaylandBackend::outputs() const
{
return m_outputs;
}
void WaylandBackend::createDpmsFilter()
{
if (m_dpmsFilter) {
// already another output is off
return;
}
m_dpmsFilter = std::make_unique<DpmsInputEventFilter>();
input()->prependInputEventFilter(m_dpmsFilter.get());
}
void WaylandBackend::clearDpmsFilter()
{
m_dpmsFilter.reset();
}
Output *WaylandBackend::createVirtualOutput(const QString &name, const QSize &size, double scale)
{
return createOutput(name, size * scale, scale);
}
void WaylandBackend::removeVirtualOutput(Output *output)
{
WaylandOutput *waylandOutput = dynamic_cast<WaylandOutput *>(output);
if (waylandOutput && m_outputs.removeAll(waylandOutput)) {
waylandOutput->updateEnabled(false);
Q_EMIT outputRemoved(waylandOutput);
waylandOutput->unref();
}
}
std::optional<DmaBufParams> WaylandBackend::testCreateDmaBuf(const QSize &size, quint32 format, const QVector<uint64_t> &modifiers)
{
gbm_bo *bo = createGbmBo(m_gbmDevice, size, format, modifiers);
if (!bo) {
return {};
}
auto ret = dmaBufParamsForBo(bo);
gbm_bo_destroy(bo);
return ret;
}
std::shared_ptr<DmaBufTexture> WaylandBackend::createDmaBufTexture(const QSize &size, quint32 format, uint64_t modifier)
{
gbm_bo *bo = createGbmBo(m_gbmDevice, size, format, {modifier});
if (!bo) {
return {};
}
// The bo will be kept around until the last fd is closed.
std::optional<DmaBufAttributes> attributes = dmaBufAttributesForBo(bo);
gbm_bo_destroy(bo);
if (!attributes.has_value()) {
return nullptr;
}
m_eglBackend->makeCurrent();
return std::make_shared<DmaBufTexture>(m_eglBackend->importDmaBufAsTexture(attributes.value()), std::move(attributes.value()));
}
void WaylandBackend::setEglDisplay(std::unique_ptr<EglDisplay> &&display)
{
m_eglDisplay = std::move(display);
}
EglDisplay *WaylandBackend::sceneEglDisplayObject() const
{
return m_eglDisplay.get();
}
}
} // KWin