Commit 19ad1725 authored by Vlad Zahorodnii's avatar Vlad Zahorodnii
Browse files

Survive Xwayland crashes

If the Xwayland process crashes, it will bring down the entire session
together with itself. Obviously, we don't want that. At least, Wayland
clients should survive the crash.

This change refactors relevant X11 parts to handle Xwayland crashes in a
less fatal way.

In order to handle Xwayland crashes better, a pair of start() and stop()
methods had been introduced in the Xwayland class to allow starting and
stopping the Xwayland process at any moment.

If we detect that the Xwayland process has crashed, we will immediately
stop the Xwayland server, which in its turn will deactivate the socket
notifier and destroy all connected X11 clients. Unfortunately, a couple
of subtle changes in X11Client::releaseWindow() and Unmanaged::release()
had to be made to ensure that we are left with a valid state after the
Xwayland server has been stopped.
parent fff2bfe7
......@@ -83,17 +83,13 @@ WaylandTestApplication::~WaylandTestApplication()
if (effects) {
static_cast<EffectsHandlerImpl*>(effects)->unloadAllEffects();
}
if (m_xwayland) {
// needs to be done before workspace gets destroyed
m_xwayland->prepareDestroy();
}
delete m_xwayland;
m_xwayland = nullptr;
destroyWorkspace();
waylandServer()->dispatch();
if (QStyle *s = style()) {
s->unpolish(this);
}
// kill Xwayland before terminating its connection
delete m_xwayland;
waylandServer()->terminateClientConnections();
destroyCompositor();
}
......@@ -133,7 +129,7 @@ void WaylandTestApplication::continueStartupWithScreens()
void WaylandTestApplication::finalizeStartup()
{
if (m_xwayland) {
disconnect(m_xwayland, &Xwl::Xwayland::initialized, this, &WaylandTestApplication::finalizeStartup);
disconnect(m_xwayland, &Xwl::Xwayland::started, this, &WaylandTestApplication::finalizeStartup);
}
notifyStarted();
}
......@@ -160,8 +156,8 @@ void WaylandTestApplication::continueStartupWithScene()
std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl;
exit(code);
});
connect(m_xwayland, &Xwl::Xwayland::initialized, this, &WaylandTestApplication::finalizeStartup);
m_xwayland->init();
connect(m_xwayland, &Xwl::Xwayland::started, this, &WaylandTestApplication::finalizeStartup);
m_xwayland->start();
}
}
......@@ -194,7 +194,7 @@ bool Compositor::setupStart()
options->reloadCompositingSettings(true);
setupX11Support();
initializeX11();
// There might still be a deleted around, needs to be cleared before
// creating the scene (BUG 333275).
......@@ -306,8 +306,13 @@ bool Compositor::setupStart()
return true;
}
void Compositor::claimCompositorSelection()
void Compositor::initializeX11()
{
xcb_connection_t *connection = kwinApp()->x11Connection();
if (!connection) {
return;
}
if (!m_selectionOwner) {
char selection_name[ 100 ];
sprintf(selection_name, "_NET_WM_CM_S%d", Application::x11ScreenNumber());
......@@ -315,40 +320,34 @@ void Compositor::claimCompositorSelection()
connect(m_selectionOwner, &CompositorSelectionOwner::lostOwnership,
this, &Compositor::stop);
}
if (!m_selectionOwner) {
// No X11 yet.
return;
}
if (!m_selectionOwner->owning()) {
// Force claim ownership.
m_selectionOwner->claim(true);
m_selectionOwner->setOwning(true);
}
xcb_composite_redirect_subwindows(connection, kwinApp()->x11RootWindow(),
XCB_COMPOSITE_REDIRECT_MANUAL);
}
void Compositor::setupX11Support()
void Compositor::cleanupX11()
{
auto *con = kwinApp()->x11Connection();
if (!con) {
delete m_selectionOwner;
m_selectionOwner = nullptr;
return;
}
claimCompositorSelection();
xcb_composite_redirect_subwindows(con, kwinApp()->x11RootWindow(),
XCB_COMPOSITE_REDIRECT_MANUAL);
delete m_selectionOwner;
m_selectionOwner = nullptr;
}
void Compositor::startupWithWorkspace()
{
connect(kwinApp(), &Application::x11ConnectionChanged,
this, &Compositor::setupX11Support, Qt::UniqueConnection);
this, &Compositor::initializeX11, Qt::UniqueConnection);
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed,
this, &Compositor::cleanupX11, Qt::UniqueConnection);
initializeX11();
Workspace::self()->markXStackingOrderAsDirty();
Q_ASSERT(m_scene);
connect(workspace(), &Workspace::destroyed, this, [this] { compositeTimer.stop(); });
setupX11Support();
fpsInterval = options->maxFpsInterval();
if (m_scene->syncsToVBlank()) {
......
......@@ -141,9 +141,8 @@ protected:
static Compositor *s_compositor;
private:
void claimCompositorSelection();
void setupX11Support();
void initializeX11();
void cleanupX11();
void setCompositeTimer();
bool windowRepaintsPending() const;
......
......@@ -152,22 +152,13 @@ Q_ENUM_NS(SessionState)
inline
KWIN_EXPORT xcb_connection_t *connection()
{
static xcb_connection_t *s_con = nullptr;
if (!s_con) {
s_con = reinterpret_cast<xcb_connection_t*>(qApp->property("x11Connection").value<void*>());
}
Q_ASSERT(qApp);
return s_con;
return reinterpret_cast<xcb_connection_t*>(qApp->property("x11Connection").value<void*>());
}
inline
KWIN_EXPORT xcb_window_t rootWindow()
{
static xcb_window_t s_rootWindow = XCB_WINDOW_NONE;
if (s_rootWindow == XCB_WINDOW_NONE) {
s_rootWindow = qApp->property("x11RootWindow").value<quint32>();
}
return s_rootWindow;
return qApp->property("x11RootWindow").value<quint32>();
}
inline
......@@ -179,19 +170,19 @@ KWIN_EXPORT xcb_timestamp_t xTime()
inline
KWIN_EXPORT xcb_screen_t *defaultScreen()
{
static xcb_screen_t *s_screen = nullptr;
if (s_screen) {
return s_screen;
xcb_connection_t *c = connection();
if (!c) {
return nullptr;
}
int screen = qApp->property("x11ScreenNumber").toInt();
for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(connection()));
for (xcb_screen_iterator_t it = xcb_setup_roots_iterator(xcb_get_setup(c));
it.rem;
--screen, xcb_screen_next(&it)) {
if (screen == 0) {
s_screen = it.data;
return it.data;
}
}
return s_screen;
return nullptr;
}
inline
......
......@@ -309,11 +309,16 @@ void Application::createOptions()
options = new Options;
}
void Application::setupEventFilters()
void Application::installNativeX11EventFilter()
{
installNativeEventFilter(m_eventFilter.data());
}
void Application::removeNativeX11EventFilter()
{
removeNativeEventFilter(m_eventFilter.data());
}
void Application::destroyWorkspace()
{
delete Workspace::self();
......
......@@ -206,7 +206,8 @@ protected:
void createWorkspace();
void createAtoms();
void createOptions();
void setupEventFilters();
void installNativeX11EventFilter();
void removeNativeX11EventFilter();
void destroyWorkspace();
void destroyCompositor();
/**
......
......@@ -134,19 +134,14 @@ ApplicationWayland::~ApplicationWayland()
if (effects) {
static_cast<EffectsHandlerImpl*>(effects)->unloadAllEffects();
}
if (m_xwayland) {
// needs to be done before workspace gets destroyed
m_xwayland->prepareDestroy();
}
delete m_xwayland;
m_xwayland = nullptr;
destroyWorkspace();
waylandServer()->dispatch();
if (QStyle *s = style()) {
s->unpolish(this);
}
// kill Xwayland before terminating its connection
delete m_xwayland;
m_xwayland = nullptr;
waylandServer()->terminateClientConnections();
destroyCompositor();
}
......@@ -196,7 +191,7 @@ void ApplicationWayland::continueStartupWithScreens()
void ApplicationWayland::finalizeStartup()
{
if (m_xwayland) {
disconnect(m_xwayland, &Xwl::Xwayland::initialized, this, &ApplicationWayland::finalizeStartup);
disconnect(m_xwayland, &Xwl::Xwayland::started, this, &ApplicationWayland::finalizeStartup);
}
startSession();
notifyStarted();
......@@ -225,8 +220,8 @@ void ApplicationWayland::continueStartupWithScene()
std::cerr << "Xwayland had a critical error. Going to exit now." << std::endl;
exit(code);
});
connect(m_xwayland, &Xwl::Xwayland::initialized, this, &ApplicationWayland::finalizeStartup);
m_xwayland->init();
connect(m_xwayland, &Xwl::Xwayland::started, this, &ApplicationWayland::finalizeStartup);
m_xwayland->start();
}
void ApplicationWayland::startSession()
......
......@@ -223,7 +223,7 @@ void ApplicationX11::performStartup()
});
connect(owner.data(), SIGNAL(lostOwnership()), SLOT(lostSelection()));
connect(owner.data(), &KSelectionOwner::claimedOwnership, [this]{
setupEventFilters();
installNativeX11EventFilter();
// first load options - done internally by a different thread
createOptions();
......
......@@ -936,8 +936,9 @@ RuleBook::RuleBook(QObject *parent)
, m_updatesDisabled(false)
, m_temporaryRulesMessages()
{
initWithX11();
connect(kwinApp(), &Application::x11ConnectionChanged, this, &RuleBook::initWithX11);
initializeX11();
connect(kwinApp(), &Application::x11ConnectionChanged, this, &RuleBook::initializeX11);
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &RuleBook::cleanupX11);
connect(m_updateTimer, SIGNAL(timeout()), SLOT(save()));
m_updateTimer->setInterval(1000);
m_updateTimer->setSingleShot(true);
......@@ -949,17 +950,21 @@ RuleBook::~RuleBook()
deleteAll();
}
void RuleBook::initWithX11()
void RuleBook::initializeX11()
{
auto c = kwinApp()->x11Connection();
if (!c) {
m_temporaryRulesMessages.reset();
return;
}
m_temporaryRulesMessages.reset(new KXMessages(c, kwinApp()->x11RootWindow(), "_KDE_NET_WM_TEMPORARY_RULES", nullptr));
connect(m_temporaryRulesMessages.data(), SIGNAL(gotMessage(QString)), SLOT(temporaryRulesMessage(QString)));
}
void RuleBook::cleanupX11()
{
m_temporaryRulesMessages.reset();
}
void RuleBook::deleteAll()
{
qDeleteAll(m_rules);
......
......@@ -312,7 +312,8 @@ private Q_SLOTS:
private:
void deleteAll();
void initWithX11();
void initializeX11();
void cleanupX11();
QTimer *m_updateTimer;
bool m_updatesDisabled;
QList<Rules*> m_rules;
......
......@@ -109,9 +109,9 @@ void Unmanaged::release(ReleaseReason releaseReason)
xcb_shape_select_input(connection(), window(), false);
Xcb::selectInput(window(), XCB_EVENT_MASK_NO_EVENT);
}
workspace()->removeUnmanaged(this);
addWorkspaceRepaint(visibleRect());
if (releaseReason != ReleaseReason::KWinShutsDown) {
workspace()->removeUnmanaged(this);
addWorkspaceRepaint(del->visibleRect());
disownDataPassedToDeleted();
del->unrefWindow();
}
......
......@@ -101,6 +101,11 @@ WaylandServer::~WaylandServer()
destroyInputMethodConnection();
}
KWaylandServer::ClientConnection *WaylandServer::xWaylandConnection() const
{
return m_xwaylandConnection;
}
void WaylandServer::destroyInternalConnection()
{
emit terminatingInternalClientConnection();
......@@ -603,23 +608,17 @@ int WaylandServer::createXWaylandConnection()
if (!socket.connection) {
return -1;
}
m_xwayland.client = socket.connection;
m_xwayland.destroyConnection = connect(m_xwayland.client, &KWaylandServer::ClientConnection::disconnected, this,
[] {
qFatal("Xwayland Connection died");
}
);
m_xwaylandConnection = socket.connection;
return socket.fd;
}
void WaylandServer::destroyXWaylandConnection()
{
if (!m_xwayland.client) {
if (!m_xwaylandConnection) {
return;
}
disconnect(m_xwayland.destroyConnection);
m_xwayland.client->destroy();
m_xwayland.client = nullptr;
m_xwaylandConnection->destroy();
m_xwaylandConnection = nullptr;
}
int WaylandServer::createInputMethodConnection()
......
......@@ -184,9 +184,7 @@ public:
void createInternalConnection();
void initWorkspace();
KWaylandServer::ClientConnection *xWaylandConnection() const {
return m_xwayland.client;
}
KWaylandServer::ClientConnection *xWaylandConnection() const;
KWaylandServer::ClientConnection *inputMethodConnection() const {
return m_inputMethodServerConnection;
}
......@@ -281,10 +279,7 @@ private:
KWaylandServer::LinuxDmabufUnstableV1Interface *m_linuxDmabuf = nullptr;
KWaylandServer::KeyboardShortcutsInhibitManagerV1Interface *m_keyboardShortcutsInhibitManager = nullptr;
QSet<KWaylandServer::LinuxDmabufUnstableV1Buffer*> m_linuxDmabufBuffers;
struct {
KWaylandServer::ClientConnection *client = nullptr;
QMetaObject::Connection destroyConnection;
} m_xwayland;
QPointer<KWaylandServer::ClientConnection> m_xwaylandConnection;
KWaylandServer::ClientConnection *m_inputMethodServerConnection = nullptr;
KWaylandServer::ClientConnection *m_screenLockerClientConnection = nullptr;
struct {
......
......@@ -125,7 +125,6 @@ Workspace::Workspace()
, client_keys_client(nullptr)
, global_shortcuts_disabled_for_client(false)
, workspaceInit(true)
, startup(nullptr)
, set_active_client_recursion(0)
, block_stacking_updates(0)
, m_sessionManager(new SessionManager(this))
......@@ -295,7 +294,11 @@ void Workspace::init()
active_client = nullptr;
initWithX11();
// We want to have some xcb connection while tearing down X11 components. We don't really
// care if the xcb connection is broken or has an error.
connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initializeX11);
connect(kwinApp(), &Application::x11ConnectionAboutToBeDestroyed, this, &Workspace::cleanupX11);
initializeX11();
Scripting::create(this);
......@@ -315,24 +318,22 @@ void Workspace::init()
// TODO: ungrabXServer()
}
void Workspace::initWithX11()
void Workspace::initializeX11()
{
if (!kwinApp()->x11Connection()) {
connect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11, Qt::UniqueConnection);
return;
}
disconnect(kwinApp(), &Application::x11ConnectionChanged, this, &Workspace::initWithX11);
atoms->retrieveHelpers();
// first initialize the extensions
Xcb::Extensions::self();
ColorMapper *colormaps = new ColorMapper(this);
connect(this, &Workspace::clientActivated, colormaps, &ColorMapper::update);
m_colorMapper.reset(new ColorMapper(this));
connect(this, &Workspace::clientActivated, m_colorMapper.data(), &ColorMapper::update);
// Call this before XSelectInput() on the root window
startup = new KStartupInfo(
KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this);
m_startup.reset(new KStartupInfo(
KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this));
// Select windowmanager privileges
selectWmInputEventMask();
......@@ -455,32 +456,54 @@ void Workspace::initWithX11()
activateClient(new_active_client);
}
Workspace::~Workspace()
void Workspace::cleanupX11()
{
blockStackingUpdates(true);
// We expect that other components will unregister their X11 event filters after the
// connection to the X server has been lost.
// TODO: grabXServer();
StackingUpdatesBlocker blocker(this);
// Use stacking_order, so that kwin --replace keeps stacking order
const QList<Toplevel *> stack = stacking_order;
// "mutex" the stackingorder, since anything trying to access it from now on will find
// many dangeling pointers and crash
stacking_order.clear();
// Use stacking_order, so that kwin --replace keeps stacking order.
const QList<X11Client *> orderedClients = ensureStackingOrder(clients);
for (X11Client *client : orderedClients) {
client->releaseWindow(true);
unconstrained_stacking_order.removeOne(client);
stacking_order.removeOne(client);
}
for (auto it = stack.constBegin(), end = stack.constEnd(); it != end; ++it) {
X11Client *c = qobject_cast<X11Client *>(const_cast<Toplevel*>(*it));
if (!c) {
continue;
}
// Only release the window
c->releaseWindow(true);
// No removeClient() is called, it does more than just removing.
// However, remove from some lists to e.g. prevent performTransiencyCheck()
// from crashing.
clients.removeAll(c);
m_allClients.removeAll(c);
for (Unmanaged *overrideRedirect : unmanaged) {
overrideRedirect->release(ReleaseReason::KWinShutsDown);
unconstrained_stacking_order.removeOne(overrideRedirect);
stacking_order.removeOne(overrideRedirect);
}
manual_overlays.clear();
VirtualDesktopManager *desktopManager = VirtualDesktopManager::self();
desktopManager->setRootInfo(nullptr);
X11Client::cleanupX11();
RootInfo::destroy();
Xcb::Extensions::destroy();
if (xcb_connection_t *connection = kwinApp()->x11Connection()) {
xcb_delete_property(connection, kwinApp()->x11RootWindow(), atoms->kwin_running);
}
m_colorMapper.reset();
m_movingClientFilter.reset();
m_startup.reset();
m_nullFocus.reset();
m_syncAlarmFilter.reset();
m_wasUserInteractionFilter.reset();
m_xStackingQueryTree.reset();
}
Workspace::~Workspace()
{
blockStackingUpdates(true);
cleanupX11();
if (waylandServer()) {
const QList<AbstractClient *> shellClients = waylandServer()->clients();
......@@ -489,17 +512,10 @@ Workspace::~Workspace()
}
}
for (auto it = unmanaged.begin(), end = unmanaged.end(); it != end; ++it)
(*it)->release(ReleaseReason::KWinShutsDown);
for (InternalClient *client : m_internalClients) {
client->destroyClient();
}
if (auto c = kwinApp()->x11Connection()) {
xcb_delete_property(c, kwinApp()->x11RootWindow(), atoms->kwin_running);
}
for (auto it = deleted.begin(); it != deleted.end();) {
emit deletedRemoved(*it);
it = deleted.erase(it);
......@@ -508,15 +524,10 @@ Workspace::~Workspace()
delete RuleBook::self();
kwinApp()->config()->sync();
RootInfo::destroy();
delete startup;
delete Placement::self();
delete client_keys_dialog;
qDeleteAll(session);
// TODO: ungrabXServer();
Xcb::Extensions::destroy();
_self = nullptr;
}
......@@ -642,7 +653,8 @@ void Workspace::removeClient(X11Client *c)
if (c == most_recently_raised)
most_recently_raised = nullptr;
should_get_focus.removeAll(c);
Q_ASSERT(c != active_client);
if (c == active_client)
active_client = nullptr;
if (c == last_active_client)
last_active_client = nullptr;
if (c == delayfocus_client)
......@@ -757,6 +769,7 @@ void Workspace::addShellClient(AbstractClient *client)
void Workspace::removeShellClient(AbstractClient *client)
{
clientHidden(client);
m_allClients.removeAll(client);
if (client == most_recently_raised) {
most_recently_raised = nullptr;
......@@ -764,6 +777,9 @@ void Workspace::removeShellClient(AbstractClient *client)
if (client == delayfocus_client) {
cancelDelayFocus();
}
if (client == active_client) {
active_client = nullptr;
}
if (client == last_active_client) {
last_active_client = nullptr;
}
......@@ -773,7 +789,6 @@ void Workspace::removeShellClient(AbstractClient *client)
if (!client->shortcut().isEmpty()) {
client->setShortcut(QString()); // Remove from client_keys
}
clientHidden(client);
emit clientRemoved(client);
markXStackingOrderAsDirty();
updateStackingOrder(true);
......@@ -1258,7 +1273,7 @@ void Workspace::cancelDelayFocus()
bool Workspace::checkStartupNotification(xcb_window_t w, KStartupInfoId &id, KStartupInfoData &data)
{
return startup->checkStartup(w, id, data) == KStartupInfo::Match;
return m_startup->checkStartup(w, id, data) == KStartupInfo::Match;
}
/**
......
......@@ -52,6 +52,7 @@ class Window;
}
class AbstractClient;
class ColorMapper;
class Compositor;
class Deleted;
class Group;
......@@ -530,7 +531,8 @@ Q_SIGNALS:
private:
void init();
void initWithX11();
void initializeX11();
void cleanupX11();
void initShortcuts();
template <typename Slot>
void initShortcut(const QString &actionName, const QString &description, const QKeySequence &shortcut,
......@@ -642,7 +644,8 @@ private:
bool workspaceInit;
KStartupInfo* startup;
QScopedPointer<KStartupInfo> m_startup;
QScopedPointer<ColorMapper> m_colorMapper;
QVector<QRect> workarea; // Array of workareas for virtual desktops
// Array of restricted areas that window cannot be moved into
......
......@@ -248,8 +248,8 @@ void X11Client::releaseWindow(bool on_shutdown)
m_frame.unmap(); // Destroying decoration would cause ugly visual effect
destroyDecoration();
cleanGrouping();
workspace()->removeClient(this);
if (!on_shutdown) {
workspace()->removeClient(this);
// Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
info->setDesktop(0);