diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -614,6 +614,13 @@ 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 --- a/autotests/integration/helper/CMakeLists.txt +++ b/autotests/integration/helper/CMakeLists.txt @@ -5,3 +5,7 @@ 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 --- /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 --- a/autotests/integration/shell_client_test.cpp +++ b/autotests/integration/shell_client_test.cpp @@ -33,8 +33,15 @@ #include #include +#include +#include #include +// system +#include +#include +#include + using namespace KWin; using namespace KWayland::Client; @@ -64,6 +71,8 @@ void testHidden(); void testDesktopFileName(); void testCaptionSimplified(); + void testKillWindow_data(); + void testKillWindow(); }; void TestShellClient::initTestCase() @@ -633,5 +642,53 @@ 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 --- a/client.h +++ b/client.h @@ -249,7 +249,7 @@ 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 --- a/killwindow.cpp +++ b/killwindow.cpp @@ -20,7 +20,7 @@ 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 @@ 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.h b/shell_client.h --- a/shell_client.h +++ b/shell_client.h @@ -132,6 +132,8 @@ void showOnScreenEdge() override; + void killWindow() override; + // TODO: const-ref void placeIn(QRect &area); diff --git a/shell_client.cpp b/shell_client.cpp --- a/shell_client.cpp +++ b/shell_client.cpp @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,10 @@ #include #include +#include +#include +#include + using namespace KWayland::Server; static const QByteArray s_schemePropertyName = QByteArrayLiteral("KDE_COLOR_SCHEME_PATH"); @@ -1380,4 +1385,22 @@ 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); +} + }