From 6bee7f4aac4e7b88bdd78027a284a6337c86b23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Tue, 15 Nov 2016 16:48:20 +0100 Subject: [PATCH] KillWindow support for Wayland windows Summary: AbstractClient gains a new pure virtual killWindow method and this gets implemented in ShellClient. ShellClient performs the killing by sending a term signal to the process. This can only work if the client connected through the socket and didn't get a socketpair fd passed. In that case the pid is KWin's and KWin doesn't want to terminate. Thus this is special handled to destroy the connection instead. In case terminating the process has no effect, the connection gets destroyed after five seconds. The KillWindow is adjusted to operate on AbstractClient instead of Client. This implements T4463. Test Plan: Killed windows and auto test Reviewers: #kwin, #plasma_on_wayland Subscribers: plasma-devel, kwin Tags: #plasma_on_wayland, #kwin Differential Revision: https://phabricator.kde.org/D3370 --- abstract_client.h | 7 +++ autotests/integration/helper/CMakeLists.txt | 4 ++ autotests/integration/helper/kill.cpp | 32 ++++++++++++ autotests/integration/shell_client_test.cpp | 57 +++++++++++++++++++++ client.h | 2 +- killwindow.cpp | 4 +- shell_client.cpp | 23 +++++++++ shell_client.h | 2 + 8 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 autotests/integration/helper/kill.cpp diff --git a/abstract_client.h b/abstract_client.h index 058571e5cc..25feb7f821 100644 --- a/abstract_client.h +++ b/abstract_client.h @@ -614,6 +614,13 @@ public: return m_desktopFileName; } + /** + * Tries to terminate the process of this AbstractClient. + * + * Implementing subclasses can perform a windowing system solution for terminating. + **/ + virtual void killWindow() = 0; + // TODO: remove boolean trap static bool belongToSameApplication(const AbstractClient* c1, const AbstractClient* c2, bool active_hack = false); diff --git a/autotests/integration/helper/CMakeLists.txt b/autotests/integration/helper/CMakeLists.txt index 5f6f47025d..0aba097bfb 100644 --- a/autotests/integration/helper/CMakeLists.txt +++ b/autotests/integration/helper/CMakeLists.txt @@ -5,3 +5,7 @@ ecm_mark_as_test(copy) add_executable(paste paste.cpp) target_link_libraries(paste Qt5::Gui) ecm_mark_as_test(paste) +###################### +add_executable(kill kill.cpp) +target_link_libraries(kill Qt5::Widgets) +ecm_mark_as_test(kill) diff --git a/autotests/integration/helper/kill.cpp b/autotests/integration/helper/kill.cpp new file mode 100644 index 0000000000..5abd4bc472 --- /dev/null +++ b/autotests/integration/helper/kill.cpp @@ -0,0 +1,32 @@ +/******************************************************************** +KWin - the KDE window manager +This file is part of the KDE project. + +Copyright (C) 2016 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 +#include + +int main(int argc, char *argv[]) +{ + qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("wayland")); + QApplication app(argc, argv); + QWidget w; + w.setGeometry(QRect(0, 0, 100, 200)); + w.show(); + + return app.exec(); +} diff --git a/autotests/integration/shell_client_test.cpp b/autotests/integration/shell_client_test.cpp index ae392f67ba..ee56f67aca 100644 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -33,8 +33,15 @@ along with this program. If not, see . #include #include +#include +#include #include +// system +#include +#include +#include + using namespace KWin; using namespace KWayland::Client; @@ -64,6 +71,8 @@ private Q_SLOTS: void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); + void testKillWindow_data(); + void testKillWindow(); }; void TestShellClient::initTestCase() @@ -633,5 +642,53 @@ void TestShellClient::testCaptionSimplified() QCOMPARE(c->caption(), origTitle.simplified()); } +void TestShellClient::testKillWindow_data() +{ + QTest::addColumn("socketMode"); + + QTest::newRow("display") << false; + QTest::newRow("socket") << true; +} + +void TestShellClient::testKillWindow() +{ + // this test verifies that killWindow properly terminates a process + // for this an external binary is launched + const QString kill = QFINDTESTDATA(QStringLiteral("helper/kill")); + QVERIFY(!kill.isEmpty()); + QSignalSpy shellClientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(shellClientAddedSpy.isValid()); + + QScopedPointer process(new QProcess); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QFETCH(bool, socketMode); + if (socketMode) { + int sx[2]; + QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); + waylandServer()->display()->createClient(sx[0]); + int socket = dup(sx[1]); + QVERIFY(socket != -1); + env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); + env.remove("WAYLAND_DISPLAY"); + } else { + env.insert("WAYLAND_DISPLAY", s_socketName); + } + process->setProcessEnvironment(env); + process->setProcessChannelMode(QProcess::ForwardedChannels); + process->setProgram(kill); + process->start(); + QVERIFY(process->waitForStarted()); + + AbstractClient *killClient = nullptr; + QVERIFY(shellClientAddedSpy.wait()); + killClient = shellClientAddedSpy.first().first().value(); + QVERIFY(killClient); + QSignalSpy finishedSpy(process.data(), static_cast(&QProcess::finished)); + QVERIFY(finishedSpy.isValid()); + killClient->killWindow(); + QVERIFY(finishedSpy.wait()); + QVERIFY(!finishedSpy.isEmpty()); +} + WAYLANDTEST_MAIN(TestShellClient) #include "shell_client_test.moc" diff --git a/client.h b/client.h index 89b31a15b3..8be241dd2c 100644 --- a/client.h +++ b/client.h @@ -249,7 +249,7 @@ public: static bool belongToSameApplication(const Client* c1, const Client* c2, bool active_hack = false); static bool sameAppWindowRoleMatch(const Client* c1, const Client* c2, bool active_hack); - void killWindow(); + void killWindow() override; void toggleShade(); void showContextHelp() override; void cancelShadeHoverTimer(); diff --git a/killwindow.cpp b/killwindow.cpp index 3f9ab7c93e..4667cc8ade 100644 --- a/killwindow.cpp +++ b/killwindow.cpp @@ -20,7 +20,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "killwindow.h" -#include "client.h" +#include "abstract_client.h" #include "main.h" #include "platform.h" #include "unmanaged.h" @@ -43,7 +43,7 @@ void KillWindow::start() if (!t) { return; } - if (Client *c = qobject_cast(t)) { + if (AbstractClient *c = qobject_cast(t)) { c->killWindow(); } else if (Unmanaged *u = qobject_cast(t)) { xcb_kill_client(connection(), u->window()); diff --git a/shell_client.cpp b/shell_client.cpp index 6f052b75ed..25bee757da 100644 --- a/shell_client.cpp +++ b/shell_client.cpp @@ -34,6 +34,7 @@ along with this program. If not, see . #include #include +#include #include #include #include @@ -48,6 +49,10 @@ along with this program. If not, see . #include #include +#include +#include +#include + using namespace KWayland::Server; static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH"); @@ -1380,4 +1385,22 @@ bool ShellClient::dockWantsInput() const return false; } +void ShellClient::killWindow() +{ + if (isInternal()) { + return; + } + if (!surface()) { + return; + } + auto c = surface()->client(); + if (c->processId() == getpid()) { + c->destroy(); + return; + } + ::kill(c->processId(), SIGTERM); + // give it time to terminate and only if terminate fails, try destroy Wayland connection + QTimer::singleShot(5000, c, &ClientConnection::destroy); +} + } diff --git a/shell_client.h b/shell_client.h index e1c1d790fc..32b612a801 100644 --- a/shell_client.h +++ b/shell_client.h @@ -132,6 +132,8 @@ public: void showOnScreenEdge() override; + void killWindow() override; + // TODO: const-ref void placeIn(QRect &area);