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.
 
 
 
 
 

708 lines
22 KiB

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "xwayland.h"
#include "config-kwin.h"
#include "databridge.h"
#include "dnd.h"
#include "window.h"
#include "xwaylandlauncher.h"
#include "xwldrophandler.h"
#include "core/output.h"
#include "input_event_spy.h"
#include "keyboard_input.h"
#include "main_wayland.h"
#include "utils/common.h"
#include "utils/xcbutils.h"
#include "wayland_server.h"
#include "waylandwindow.h"
#include "workspace.h"
#include "x11eventfilter.h"
#include "xkb.h"
#include "xwayland_logging.h"
#include <KSelectionOwner>
#include <wayland/keyboard.h>
#include <wayland/pointer.h>
#include <wayland/seat.h>
#include <wayland/surface.h>
#include <QAbstractEventDispatcher>
#include <QDataStream>
#include <QFile>
#include <QRandomGenerator>
#include <QScopeGuard>
#include <QSocketNotifier>
#include <QTimer>
#include <QtConcurrentRun>
#include <cerrno>
#include <cstring>
#include <input_event.h>
#include <sys/socket.h>
#include <unistd.h>
#include <xkbcommon/xkbcommon-keysyms.h>
namespace KWin
{
namespace Xwl
{
class XrandrEventFilter : public X11EventFilter
{
public:
explicit XrandrEventFilter(Xwayland *backend);
bool event(xcb_generic_event_t *event) override;
private:
Xwayland *const m_backend;
};
XrandrEventFilter::XrandrEventFilter(Xwayland *backend)
: X11EventFilter(Xcb::Extensions::self()->randrNotifyEvent())
, m_backend(backend)
{
}
bool XrandrEventFilter::event(xcb_generic_event_t *event)
{
Q_ASSERT((event->response_type & ~0x80) == Xcb::Extensions::self()->randrNotifyEvent());
m_backend->updatePrimary();
return false;
}
class XwaylandInputSpy : public QObject, public KWin::InputEventSpy
{
public:
XwaylandInputSpy()
{
connect(waylandServer()->seat(), &SeatInterface::focusedKeyboardSurfaceAboutToChange,
this, [this](SurfaceInterface *newSurface) {
auto keyboard = waylandServer()->seat()->keyboard();
if (!newSurface) {
return;
}
if (waylandServer()->xWaylandConnection() == newSurface->client()) {
// Since this is a spy but the keyboard interface gets its normal sendKey calls through filters,
// there can be a mismatch in both states.
// This loop makes sure all key press events are reset before we switch back to the
// Xwayland client and the state is correctly restored.
for (auto it = m_states.constBegin(); it != m_states.constEnd(); ++it) {
if (it.value() == KeyboardKeyState::Pressed) {
keyboard->sendKey(it.key(), KeyboardKeyState::Released, waylandServer()->xWaylandConnection());
}
}
m_states.clear();
}
});
}
void setMode(XwaylandEavesdropsMode mode, bool eavesdropsMouse)
{
static const Qt::KeyboardModifiers modifierKeys = {
Qt::ControlModifier,
Qt::AltModifier,
Qt::MetaModifier,
};
static const QSet<quint32> characterKeys = {
Qt::Key_Any,
Qt::Key_Space,
Qt::Key_Exclam,
Qt::Key_QuoteDbl,
Qt::Key_NumberSign,
Qt::Key_Dollar,
Qt::Key_Percent,
Qt::Key_Ampersand,
Qt::Key_Apostrophe,
Qt::Key_ParenLeft,
Qt::Key_ParenRight,
Qt::Key_Asterisk,
Qt::Key_Plus,
Qt::Key_Comma,
Qt::Key_Minus,
Qt::Key_Period,
Qt::Key_Slash,
Qt::Key_0,
Qt::Key_1,
Qt::Key_2,
Qt::Key_3,
Qt::Key_4,
Qt::Key_5,
Qt::Key_6,
Qt::Key_7,
Qt::Key_8,
Qt::Key_9,
Qt::Key_Colon,
Qt::Key_Semicolon,
Qt::Key_Less,
Qt::Key_Equal,
Qt::Key_Greater,
Qt::Key_Question,
Qt::Key_At,
Qt::Key_A,
Qt::Key_B,
Qt::Key_C,
Qt::Key_D,
Qt::Key_E,
Qt::Key_F,
Qt::Key_G,
Qt::Key_H,
Qt::Key_I,
Qt::Key_J,
Qt::Key_K,
Qt::Key_L,
Qt::Key_M,
Qt::Key_N,
Qt::Key_O,
Qt::Key_P,
Qt::Key_Q,
Qt::Key_R,
Qt::Key_S,
Qt::Key_T,
Qt::Key_U,
Qt::Key_V,
Qt::Key_W,
Qt::Key_X,
Qt::Key_Y,
Qt::Key_Z,
Qt::Key_BracketLeft,
Qt::Key_Backslash,
Qt::Key_BracketRight,
Qt::Key_AsciiCircum,
Qt::Key_Underscore,
Qt::Key_QuoteLeft,
Qt::Key_BraceLeft,
Qt::Key_Bar,
Qt::Key_BraceRight,
Qt::Key_AsciiTilde,
Qt::Key_nobreakspace,
Qt::Key_exclamdown,
Qt::Key_cent,
Qt::Key_sterling,
Qt::Key_currency,
Qt::Key_yen,
Qt::Key_brokenbar,
Qt::Key_section,
Qt::Key_diaeresis,
Qt::Key_copyright,
Qt::Key_ordfeminine,
Qt::Key_guillemotleft,
Qt::Key_notsign,
Qt::Key_hyphen,
Qt::Key_registered,
Qt::Key_macron,
Qt::Key_degree,
Qt::Key_plusminus,
Qt::Key_twosuperior,
Qt::Key_threesuperior,
Qt::Key_acute,
Qt::Key_mu,
Qt::Key_paragraph,
Qt::Key_periodcentered,
Qt::Key_cedilla,
Qt::Key_onesuperior,
Qt::Key_masculine,
Qt::Key_guillemotright,
Qt::Key_onequarter,
Qt::Key_onehalf,
Qt::Key_threequarters,
Qt::Key_questiondown,
Qt::Key_Agrave,
Qt::Key_Aacute,
Qt::Key_Acircumflex,
Qt::Key_Atilde,
Qt::Key_Adiaeresis,
Qt::Key_Aring,
Qt::Key_AE,
Qt::Key_Ccedilla,
Qt::Key_Egrave,
Qt::Key_Eacute,
Qt::Key_Ecircumflex,
Qt::Key_Ediaeresis,
Qt::Key_Igrave,
Qt::Key_Iacute,
Qt::Key_Icircumflex,
Qt::Key_Idiaeresis,
Qt::Key_ETH,
Qt::Key_Ntilde,
Qt::Key_Ograve,
Qt::Key_Oacute,
Qt::Key_Ocircumflex,
Qt::Key_Otilde,
Qt::Key_Odiaeresis,
Qt::Key_multiply,
Qt::Key_Ooblique,
Qt::Key_Ugrave,
Qt::Key_Uacute,
Qt::Key_Ucircumflex,
Qt::Key_Udiaeresis,
Qt::Key_Yacute,
Qt::Key_THORN,
Qt::Key_ssharp,
Qt::Key_division,
Qt::Key_ydiaeresis,
Qt::Key_Multi_key,
Qt::Key_Codeinput,
Qt::Key_SingleCandidate,
Qt::Key_MultipleCandidate,
Qt::Key_PreviousCandidate,
Qt::Key_Mode_switch,
Qt::Key_Kanji,
Qt::Key_Muhenkan,
Qt::Key_Henkan,
Qt::Key_Romaji,
Qt::Key_Hiragana,
Qt::Key_Katakana,
Qt::Key_Hiragana_Katakana,
Qt::Key_Zenkaku,
Qt::Key_Hankaku,
Qt::Key_Zenkaku_Hankaku,
Qt::Key_Touroku,
Qt::Key_Massyo,
Qt::Key_Kana_Lock,
Qt::Key_Kana_Shift,
Qt::Key_Eisu_Shift,
Qt::Key_Eisu_toggle,
Qt::Key_Hangul,
Qt::Key_Hangul_Start,
Qt::Key_Hangul_End,
Qt::Key_Hangul_Hanja,
Qt::Key_Hangul_Jamo,
Qt::Key_Hangul_Romaja,
Qt::Key_Hangul_Jeonja,
Qt::Key_Hangul_Banja,
Qt::Key_Hangul_PreHanja,
Qt::Key_Hangul_PostHanja,
Qt::Key_Hangul_Special,
Qt::Key_Dead_Grave,
Qt::Key_Dead_Acute,
Qt::Key_Dead_Circumflex,
Qt::Key_Dead_Tilde,
Qt::Key_Dead_Macron,
Qt::Key_Dead_Breve,
Qt::Key_Dead_Abovedot,
Qt::Key_Dead_Diaeresis,
Qt::Key_Dead_Abovering,
Qt::Key_Dead_Doubleacute,
Qt::Key_Dead_Caron,
Qt::Key_Dead_Cedilla,
Qt::Key_Dead_Ogonek,
Qt::Key_Dead_Iota,
Qt::Key_Dead_Voiced_Sound,
Qt::Key_Dead_Semivoiced_Sound,
Qt::Key_Dead_Belowdot,
Qt::Key_Dead_Hook,
Qt::Key_Dead_Horn,
Qt::Key_Dead_Stroke,
Qt::Key_Dead_Abovecomma,
Qt::Key_Dead_Abovereversedcomma,
Qt::Key_Dead_Doublegrave,
Qt::Key_Dead_Belowring,
Qt::Key_Dead_Belowmacron,
Qt::Key_Dead_Belowcircumflex,
Qt::Key_Dead_Belowtilde,
Qt::Key_Dead_Belowbreve,
Qt::Key_Dead_Belowdiaeresis,
Qt::Key_Dead_Invertedbreve,
Qt::Key_Dead_Belowcomma,
Qt::Key_Dead_Currency,
Qt::Key_Dead_a,
Qt::Key_Dead_A,
Qt::Key_Dead_e,
Qt::Key_Dead_E,
Qt::Key_Dead_i,
Qt::Key_Dead_I,
Qt::Key_Dead_o,
Qt::Key_Dead_O,
Qt::Key_Dead_u,
Qt::Key_Dead_U,
Qt::Key_Dead_Small_Schwa,
Qt::Key_Dead_Capital_Schwa,
Qt::Key_Dead_Greek,
Qt::Key_Dead_Lowline,
Qt::Key_Dead_Aboveverticalline,
Qt::Key_Dead_Belowverticalline};
switch (mode) {
case None:
m_filterKey = {};
m_filterMouse = false;
break;
case NonCharacterKeys:
m_filterKey = [](int key, Qt::KeyboardModifiers) {
return !characterKeys.contains(key);
};
m_filterMouse = eavesdropsMouse;
break;
case AllKeysWithModifier:
m_filterKey = [](int key, Qt::KeyboardModifiers m) {
return m.testAnyFlags(modifierKeys) || !characterKeys.contains(key);
};
m_filterMouse = eavesdropsMouse;
break;
case All:
m_filterKey = [](int, Qt::KeyboardModifiers) {
return true;
};
m_filterMouse = eavesdropsMouse;
break;
}
}
void keyEvent(KWin::KeyEvent *event) override
{
if (event->isAutoRepeat()) {
return;
}
Window *window = workspace()->activeWindow();
if (!m_filterKey || !m_filterKey(event->key(), event->modifiers()) || (window && window->isLockScreen())) {
return;
}
auto keyboard = waylandServer()->seat()->keyboard();
auto surface = keyboard->focusedSurface();
ClientConnection *xwaylandClient = waylandServer()->xWaylandConnection();
if (surface) {
ClientConnection *client = surface->client();
if (xwaylandClient && xwaylandClient == client) {
return;
}
}
KeyboardKeyState state{event->type() == QEvent::KeyPress};
if (!updateKey(event->nativeScanCode(), state)) {
return;
}
auto xkb = input()->keyboard()->xkb();
keyboard->sendModifiers(xkb->modifierState().depressed,
xkb->modifierState().latched,
xkb->modifierState().locked,
xkb->currentLayout());
keyboard->sendKey(event->nativeScanCode(), state, xwaylandClient);
}
void pointerEvent(KWin::MouseEvent *event) override
{
Window *window = workspace()->activeWindow();
if (!m_filterMouse || (window && window->isLockScreen())) {
return;
}
if (event->type() != QEvent::MouseButtonPress && event->type() != QEvent::MouseButtonRelease) {
return;
}
auto pointer = waylandServer()->seat()->pointer();
auto surface = pointer->focusedSurface();
ClientConnection *xwaylandClient = waylandServer()->xWaylandConnection();
if (surface) {
ClientConnection *client = surface->client();
if (xwaylandClient && xwaylandClient == client) {
return;
}
}
PointerButtonState state{event->type() == QEvent::MouseButtonPress};
pointer->sendButton(event->nativeButton(), state, xwaylandClient);
}
bool updateKey(quint32 key, KeyboardKeyState state)
{
auto it = m_states.find(key);
if (it == m_states.end()) {
m_states.insert(key, state);
return true;
}
if (it.value() == state) {
return false;
}
it.value() = state;
return true;
}
QHash<quint32, KeyboardKeyState> m_states;
std::function<bool(int key, Qt::KeyboardModifiers)> m_filterKey;
bool m_filterMouse = false;
};
Xwayland::Xwayland(Application *app)
: m_app(app)
, m_launcher(new XwaylandLauncher(this))
{
connect(m_launcher, &XwaylandLauncher::started, this, &Xwayland::handleXwaylandReady);
connect(m_launcher, &XwaylandLauncher::finished, this, &Xwayland::handleXwaylandFinished);
connect(m_launcher, &XwaylandLauncher::errorOccurred, this, &Xwayland::errorOccurred);
}
Xwayland::~Xwayland()
{
m_launcher->stop();
}
void Xwayland::init()
{
m_launcher->enable();
auto env = m_app->processStartupEnvironment();
env.insert(QStringLiteral("DISPLAY"), m_launcher->displayName());
env.insert(QStringLiteral("XAUTHORITY"), m_launcher->xauthority());
qputenv("DISPLAY", m_launcher->displayName().toLatin1());
qputenv("XAUTHORITY", m_launcher->xauthority().toLatin1());
m_app->setProcessStartupEnvironment(env);
}
XwaylandLauncher *Xwayland::xwaylandLauncher() const
{
return m_launcher;
}
void Xwayland::dispatchEvents(DispatchEventsMode mode)
{
xcb_connection_t *connection = kwinApp()->x11Connection();
if (!connection) {
qCWarning(KWIN_XWL, "Attempting to dispatch X11 events with no connection");
return;
}
const int connectionError = xcb_connection_has_error(connection);
if (connectionError) {
qCWarning(KWIN_XWL, "The X11 connection broke (error %d)", connectionError);
m_launcher->stop();
return;
}
auto pollEventFunc = mode == DispatchEventsMode::Poll ? xcb_poll_for_event : xcb_poll_for_queued_event;
while (xcb_generic_event_t *event = pollEventFunc(connection)) {
qintptr result = 0;
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
free(event);
}
xcb_flush(connection);
}
void Xwayland::installSocketNotifier()
{
const int fileDescriptor = xcb_get_file_descriptor(kwinApp()->x11Connection());
m_socketNotifier = new QSocketNotifier(fileDescriptor, QSocketNotifier::Read, this);
connect(m_socketNotifier, &QSocketNotifier::activated, this, [this]() {
dispatchEvents(DispatchEventsMode::Poll);
});
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, [this]() {
dispatchEvents(DispatchEventsMode::EventQueue);
});
connect(dispatcher, &QAbstractEventDispatcher::awake, this, [this]() {
dispatchEvents(DispatchEventsMode::EventQueue);
});
}
void Xwayland::uninstallSocketNotifier()
{
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
disconnect(dispatcher, nullptr, this, nullptr);
delete m_socketNotifier;
m_socketNotifier = nullptr;
}
void Xwayland::handleXwaylandFinished()
{
disconnect(workspace(), &Workspace::outputOrderChanged, this, &Xwayland::updatePrimary);
delete m_xrandrEventsFilter;
m_xrandrEventsFilter = nullptr;
// If Xwayland has crashed, we must deactivate the socket notifier and ensure that no X11
// events will be dispatched before blocking; otherwise we will simply hang...
uninstallSocketNotifier();
m_dataBridge.reset();
m_compositingManagerSelectionOwner.reset();
m_windowManagerSelectionOwner.reset();
m_inputSpy.reset();
disconnect(options, &Options::xwaylandEavesdropsChanged, this, &Xwayland::refreshEavesdropping);
disconnect(options, &Options::xwaylandEavesdropsMouseChanged, this, &Xwayland::refreshEavesdropping);
destroyX11Connection();
}
void Xwayland::handleXwaylandReady()
{
if (!createX11Connection()) {
Q_EMIT errorOccurred();
return;
}
qCInfo(KWIN_XWL) << "Xwayland server started on display" << m_launcher->displayName();
m_compositingManagerSelectionOwner = std::make_unique<KSelectionOwner>("_NET_WM_CM_S0", kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
m_compositingManagerSelectionOwner->claim(true);
xcb_composite_redirect_subwindows(kwinApp()->x11Connection(),
kwinApp()->x11RootWindow(),
XCB_COMPOSITE_REDIRECT_MANUAL);
// create selection owner for WM_S0 - magic X display number expected by XWayland
m_windowManagerSelectionOwner = std::make_unique<KSelectionOwner>("WM_S0", kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
m_windowManagerSelectionOwner->claim(true);
m_dataBridge = std::make_unique<DataBridge>();
connect(workspace(), &Workspace::outputOrderChanged, this, &Xwayland::updatePrimary);
updatePrimary();
delete m_xrandrEventsFilter;
m_xrandrEventsFilter = new XrandrEventFilter(this);
refreshEavesdropping();
connect(options, &Options::xwaylandEavesdropsChanged, this, &Xwayland::refreshEavesdropping);
connect(options, &Options::xwaylandEavesdropsMouseChanged, this, &Xwayland::refreshEavesdropping);
Q_EMIT started();
}
void Xwayland::refreshEavesdropping()
{
if (!waylandServer()->seat()->keyboard()) {
return;
}
const bool enabled = options->xwaylandEavesdrops() != None;
if (enabled == bool(m_inputSpy)) {
if (m_inputSpy) {
m_inputSpy->setMode(options->xwaylandEavesdrops(), options->xwaylandEavesdropsMouse());
}
return;
}
if (enabled) {
m_inputSpy = std::make_unique<XwaylandInputSpy>();
input()->installInputEventSpy(m_inputSpy.get());
m_inputSpy->setMode(options->xwaylandEavesdrops(), options->xwaylandEavesdropsMouse());
} else {
input()->uninstallInputEventSpy(m_inputSpy.get());
m_inputSpy.reset();
}
}
void Xwayland::updatePrimary()
{
if (workspace()->outputOrder().empty()) {
return;
}
Xcb::RandR::ScreenResources resources(kwinApp()->x11RootWindow());
xcb_randr_crtc_t *crtcs = resources.crtcs();
if (!crtcs) {
return;
}
Output *const primaryOutput = workspace()->outputOrder().front();
const QRect primaryOutputGeometry = Xcb::toXNative(primaryOutput->geometryF());
for (int i = 0; i < resources->num_crtcs; ++i) {
Xcb::RandR::CrtcInfo crtcInfo(crtcs[i], resources->config_timestamp);
const QRect geometry = crtcInfo.rect();
if (geometry.topLeft() == primaryOutputGeometry.topLeft()) {
auto outputs = crtcInfo.outputs();
if (outputs && crtcInfo->num_outputs > 0) {
qCDebug(KWIN_XWL) << "Setting primary" << primaryOutput << outputs[0];
xcb_randr_set_output_primary(kwinApp()->x11Connection(), kwinApp()->x11RootWindow(), outputs[0]);
break;
}
}
}
}
bool Xwayland::createX11Connection()
{
xcb_connection_t *connection = xcb_connect_to_fd(m_launcher->xcbConnectionFd(), nullptr);
const int errorCode = xcb_connection_has_error(connection);
if (errorCode) {
qCDebug(KWIN_XWL, "Failed to establish the XCB connection (error %d)", errorCode);
return false;
}
xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
Q_ASSERT(screen);
m_app->setX11Connection(connection);
m_app->setX11RootWindow(screen->root);
m_app->createAtoms();
m_app->installNativeX11EventFilter();
installSocketNotifier();
// Note that it's very important to have valid x11RootWindow(), and atoms when the
// rest of kwin is notified about the new X11 connection.
Q_EMIT m_app->x11ConnectionChanged();
return true;
}
void Xwayland::destroyX11Connection()
{
if (!m_app->x11Connection()) {
return;
}
Q_EMIT m_app->x11ConnectionAboutToBeDestroyed();
Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
m_app->destroyAtoms();
m_app->removeNativeX11EventFilter();
xcb_disconnect(m_app->x11Connection());
m_app->setX11Connection(nullptr);
m_app->setX11RootWindow(XCB_WINDOW_NONE);
Q_EMIT m_app->x11ConnectionChanged();
}
DragEventReply Xwayland::dragMoveFilter(Window *target)
{
if (m_dataBridge) {
return m_dataBridge->dragMoveFilter(target);
} else {
return DragEventReply::Wayland;
}
}
AbstractDropHandler *Xwayland::xwlDropHandler()
{
if (m_dataBridge) {
return m_dataBridge->dnd()->dropHandler();
} else {
return nullptr;
}
}
} // namespace Xwl
} // namespace KWin
#include "moc_xwayland.cpp"