diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt index 407964b49..8a899dfa3 100644 --- a/autotests/integration/CMakeLists.txt +++ b/autotests/integration/CMakeLists.txt @@ -1,87 +1,88 @@ add_subdirectory(helper) add_library(KWinIntegrationTestFramework STATIC kwin_wayland_test.cpp test_helpers.cpp) target_link_libraries(KWinIntegrationTestFramework kwin Qt5::Test) function(integrationTest) set(optionArgs WAYLAND_ONLY) set(oneValueArgs NAME) set(multiValueArgs SRCS LIBS) cmake_parse_arguments(ARGS "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) add_executable(${ARGS_NAME} ${ARGS_SRCS}) target_link_libraries(${ARGS_NAME} KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}) if (${ARGS_WAYLAND_ONLY}) add_executable(${ARGS_NAME}_waylandonly ${ARGS_SRCS}) set_target_properties(${ARGS_NAME}_waylandonly PROPERTIES COMPILE_DEFINITIONS "NO_XWAYLAND") target_link_libraries(${ARGS_NAME}_waylandonly KWinIntegrationTestFramework kwin Qt5::Test ${ARGS_LIBS}) add_test(NAME kwin-${ARGS_NAME}-waylandonly COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}_waylandonly) endif() endfunction() integrationTest(WAYLAND_ONLY NAME testStart SRCS start_test.cpp) integrationTest(WAYLAND_ONLY NAME testTransientNoInput SRCS transient_no_input_test.cpp) integrationTest(NAME testDontCrashGlxgears SRCS dont_crash_glxgears.cpp) integrationTest(NAME testLockScreen SRCS lockscreen.cpp) integrationTest(WAYLAND_ONLY NAME testDecorationInput SRCS decoration_input_test.cpp) integrationTest(WAYLAND_ONLY NAME testInternalWindow SRCS internal_window.cpp) integrationTest(WAYLAND_ONLY NAME testTouchInput SRCS touch_input_test.cpp) integrationTest(WAYLAND_ONLY NAME testInputStackingOrder SRCS input_stacking_order.cpp) integrationTest(NAME testPointerInput SRCS pointer_input.cpp) integrationTest(NAME testPlatformCursor SRCS platformcursor.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashCancelAnimation SRCS dont_crash_cancel_animation.cpp) integrationTest(WAYLAND_ONLY NAME testTransientPlacmenet SRCS transient_placement.cpp) integrationTest(NAME testDebugConsole SRCS debug_console_test.cpp) integrationTest(NAME testDontCrashEmptyDeco SRCS dont_crash_empty_deco.cpp) integrationTest(WAYLAND_ONLY NAME testPlasmaSurface SRCS plasma_surface_test.cpp) integrationTest(WAYLAND_ONLY NAME testMaximized SRCS maximize_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClient SRCS shell_client_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashNoBorder SRCS dont_crash_no_border.cpp) integrationTest(NAME testXClipboardSync SRCS xclipboardsync_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGL SRCS scene_opengl_test.cpp generic_scene_opengl_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGLShadow SRCS scene_opengl_shadow_test.cpp) integrationTest(WAYLAND_ONLY NAME testSceneOpenGLES SRCS scene_opengl_es_test.cpp generic_scene_opengl_test.cpp) integrationTest(WAYLAND_ONLY NAME testNoXdgRuntimeDir SRCS no_xdg_runtime_dir_test.cpp) integrationTest(WAYLAND_ONLY NAME testScreenChanges SRCS screen_changes_test.cpp) integrationTest(NAME testModiferOnlyShortcut SRCS modifier_only_shortcut_test.cpp) integrationTest(WAYLAND_ONLY NAME testTabBox SRCS tabbox_test.cpp) integrationTest(WAYLAND_ONLY NAME testWindowSelection SRCS window_selection_test.cpp) integrationTest(WAYLAND_ONLY NAME testPointerConstraints SRCS pointer_constraints_test.cpp) integrationTest(WAYLAND_ONLY NAME testKeyboardLayout SRCS keyboard_layout_test.cpp) integrationTest(WAYLAND_ONLY NAME testKeymapCreationFailure SRCS keymap_creation_failure_test.cpp) integrationTest(WAYLAND_ONLY NAME testShowingDesktop SRCS showing_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashUseractionsMenu SRCS dont_crash_useractions_menu.cpp) integrationTest(WAYLAND_ONLY NAME testKWinBindings SRCS kwinbindings_test.cpp) integrationTest(WAYLAND_ONLY NAME testVirtualDesktop SRCS virtual_desktop_test.cpp) integrationTest(WAYLAND_ONLY NAME testShellClientRules SRCS shell_client_rules_test.cpp) integrationTest(WAYLAND_ONLY NAME testIdleInhibition SRCS idle_inhibition_test.cpp) integrationTest(WAYLAND_ONLY NAME testColorCorrectNightColor SRCS colorcorrect_nightcolor_test.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashCursorPhysicalSizeEmpty SRCS dont_crash_cursor_physical_size_empty.cpp) integrationTest(WAYLAND_ONLY NAME testDontCrashReinitializeCompositor SRCS dont_crash_reinitialize_compositor.cpp) integrationTest(WAYLAND_ONLY NAME testNoGlobalShortcuts SRCS no_global_shortcuts_test.cpp) if (XCB_ICCCM_FOUND) integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStruts SRCS struts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testShade SRCS shade_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testDontCrashAuroraeDestroyDeco SRCS dont_crash_aurorae_destroy_deco.cpp LIBS XCB::ICCCM) integrationTest(NAME testPlasmaWindow SRCS plasmawindow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testScreenEdgeClientShow SRCS screenedge_client_show_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11DesktopWindow SRCS desktop_window_x11_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testXwaylandInput SRCS xwayland_input_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testWindowRules SRCS window_rules_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testX11Client SRCS x11_client_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testSceneQPainterShadow SRCS scene_qpainter_shadow_test.cpp LIBS XCB::ICCCM) integrationTest(NAME testStackingOrder SRCS stacking_order_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testDbusInterface SRCS dbus_interface_test.cpp LIBS XCB::ICCCM) if (KWIN_BUILD_ACTIVITIES) integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) endif() endif() add_subdirectory(scripting) add_subdirectory(effects) add_subdirectory(fakes) diff --git a/autotests/integration/dbus_interface_test.cpp b/autotests/integration/dbus_interface_test.cpp new file mode 100644 index 000000000..cde2319a1 --- /dev/null +++ b/autotests/integration/dbus_interface_test.cpp @@ -0,0 +1,413 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2018 Martin Flöser + +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) version 3 or any later version +accepted by the membership of KDE e.V. (or its successor approved +by the membership of KDE e.V.), which shall act as a proxy +defined in Section 14 of version 3 of the license. + +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 "kwin_wayland_test.h" +#include "atoms.h" +#include "client.h" +#include "deleted.h" +#include "platform.h" +#include "rules.h" +#include "screens.h" +#include "shell_client.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dbus_interface-0"); + +const QString s_destination{QStringLiteral("org.kde.KWin")}; +const QString s_path{QStringLiteral("/KWin")}; +const QString s_interface{QStringLiteral("org.kde.KWin")}; + +class TestDbusInterface : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testGetWindowInfoInvalidUuid(); + void testGetWindowInfoShellClient_data(); + void testGetWindowInfoShellClient(); + void testGetWindowInfoX11Client(); +}; + +void TestDbusInterface::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy workspaceCreatedSpy(kwinApp(), &Application::workspaceCreated); + QVERIFY(workspaceCreatedSpy.isValid()); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName.toLocal8Bit())); + + kwinApp()->start(); + QVERIFY(workspaceCreatedSpy.wait()); + waylandServer()->initWorkspace(); + VirtualDesktopManager::self()->setCount(4); +} + +void TestDbusInterface::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void TestDbusInterface::cleanup() +{ + Test::destroyWaylandConnection(); +} + +namespace { +QDBusPendingCall getWindowInfo(const QUuid &uuid) +{ + auto msg = QDBusMessage::createMethodCall(s_destination, s_path, s_interface, QStringLiteral("getWindowInfo")); + msg.setArguments({uuid.toString()}); + return QDBusConnection::sessionBus().asyncCall(msg); +} +} + +void TestDbusInterface::testGetWindowInfoInvalidUuid() +{ + QDBusPendingReply reply{getWindowInfo(QUuid::createUuid())}; + reply.waitForFinished(); + QVERIFY(reply.isValid()); + QVERIFY(!reply.isError()); + const auto windowData = reply.value(); + QVERIFY(windowData.empty()); +} + +void TestDbusInterface::testGetWindowInfoShellClient_data() +{ + QTest::addColumn("type"); + + QTest::newRow("wlShell") << Test::ShellSurfaceType::WlShell; + QTest::newRow("xdgShellV5") << Test::ShellSurfaceType::XdgShellV5; + QTest::newRow("xdgShellV6") << Test::ShellSurfaceType::XdgShellV6; + QTest::newRow("xdgWmBase") << Test::ShellSurfaceType::XdgShellStable; +} + +void TestDbusInterface::testGetWindowInfoShellClient() +{ + QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded); + QVERIFY(clientAddedSpy.isValid()); + + QScopedPointer surface(Test::createSurface()); + QFETCH(Test::ShellSurfaceType, type); + QScopedPointer shellSurface(Test::createShellSurface(type, surface.data())); + if (type != Test::ShellSurfaceType::WlShell) { + qobject_cast(shellSurface.data())->setAppId(QByteArrayLiteral("org.kde.foo")); + qobject_cast(shellSurface.data())->setTitle(QStringLiteral("Test window")); + } + + // now let's render + Test::render(surface.data(), QSize(100, 50), Qt::blue); + QVERIFY(clientAddedSpy.isEmpty()); + QVERIFY(clientAddedSpy.wait()); + auto client = clientAddedSpy.first().first().value(); + QVERIFY(client); + + // let's get the window info + QDBusPendingReply reply{getWindowInfo(client->internalId())}; + reply.waitForFinished(); + QVERIFY(reply.isValid()); + QVERIFY(!reply.isError()); + auto windowData = reply.value(); + QVERIFY(!windowData.isEmpty()); + QCOMPARE(windowData.size(), 23); + QCOMPARE(windowData.value(QStringLiteral("type")).toInt(), NET::Normal); + QCOMPARE(windowData.value(QStringLiteral("x")).toInt(), client->x()); + QCOMPARE(windowData.value(QStringLiteral("y")).toInt(), client->y()); + QCOMPARE(windowData.value(QStringLiteral("width")).toInt(), client->width()); + QCOMPARE(windowData.value(QStringLiteral("height")).toInt(), client->height()); + QCOMPARE(windowData.value(QStringLiteral("x11DesktopNumber")).toInt(), 1); + QCOMPARE(windowData.value(QStringLiteral("minimized")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("shaded")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("fullscreen")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("keepAbove")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("keepBelow")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("skipTaskbar")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("skipPager")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("skipSwitcher")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("maximizeHorizontal")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("maximizeVertical")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("noBorder")).toBool(), true); + QCOMPARE(windowData.value(QStringLiteral("clientMachine")).toString(), QString()); + QCOMPARE(windowData.value(QStringLiteral("role")).toString(), QString()); + QCOMPARE(windowData.value(QStringLiteral("resourceName")).toString(), QStringLiteral("testDbusInterface")); + if (type == Test::ShellSurfaceType::WlShell) { + QCOMPARE(windowData.value(QStringLiteral("resourceClass")).toString(), QString()); + QCOMPARE(windowData.value(QStringLiteral("desktopFile")).toString(), QString()); + QCOMPARE(windowData.value(QStringLiteral("caption")).toString(), QString()); + } else { + QCOMPARE(windowData.value(QStringLiteral("resourceClass")).toString(), QStringLiteral("org.kde.foo")); + QCOMPARE(windowData.value(QStringLiteral("desktopFile")).toString(), QStringLiteral("org.kde.foo")); + QCOMPARE(windowData.value(QStringLiteral("caption")).toString(), QStringLiteral("Test window")); + } + + auto verifyProperty = [client] (const QString &name) { + QDBusPendingReply reply{getWindowInfo(client->internalId())}; + reply.waitForFinished(); + return reply.value().value(name).toBool(); + }; + + QVERIFY(!client->isMinimized()); + client->setMinimized(true); + QVERIFY(client->isMinimized()); + QCOMPARE(verifyProperty(QStringLiteral("minimized")), true); + + QVERIFY(!client->keepAbove()); + client->setKeepAbove(true); + QVERIFY(client->keepAbove()); + QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true); + + QVERIFY(!client->keepBelow()); + client->setKeepBelow(true); + QVERIFY(client->keepBelow()); + QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true); + + QVERIFY(!client->skipTaskbar()); + client->setSkipTaskbar(true); + QVERIFY(client->skipTaskbar()); + QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true); + + QVERIFY(!client->skipPager()); + client->setSkipPager(true); + QVERIFY(client->skipPager()); + QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true); + + QVERIFY(!client->skipSwitcher()); + client->setSkipSwitcher(true); + QVERIFY(client->skipSwitcher()); + QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true); + + // not testing shaded as that's X11 + // not testing fullscreen, maximizeHorizontal, maximizeVertical and noBorder as those require window geometry changes + + QCOMPARE(client->desktop(), 1); + workspace()->sendClientToDesktop(client, 2, false); + QCOMPARE(client->desktop(), 2); + reply = getWindowInfo(client->internalId()); + reply.waitForFinished(); + QCOMPARE(reply.value().value(QStringLiteral("x11DesktopNumber")).toInt(), 2); + + client->move(10, 20); + reply = getWindowInfo(client->internalId()); + reply.waitForFinished(); + QCOMPARE(reply.value().value(QStringLiteral("x")).toInt(), client->x()); + QCOMPARE(reply.value().value(QStringLiteral("y")).toInt(), client->y()); + // not testing width, height as that would require window geometry change + + // finally close window + const auto id = client->internalId(); + QSignalSpy windowClosedSpy(client, &ShellClient::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); + QCOMPARE(windowClosedSpy.count(), 1); + + reply = getWindowInfo(id); + reply.waitForFinished(); + QVERIFY(reply.value().empty()); +} + + +struct XcbConnectionDeleter +{ + static inline void cleanup(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void TestDbusInterface::testGetWindowInfoX11Client() +{ + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + const QRect windowGeometry(0, 0, 600, 400); + xcb_window_t w = xcb_generate_id(c.data()); + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); + xcb_icccm_set_wm_class(c.data(), w, 7, "foo\0bar"); + NETWinInfo winInfo(c.data(), w, rootWindow(), NET::Properties(), NET::Properties2()); + winInfo.setName("Some caption"); + winInfo.setDesktopFileName("org.kde.foo"); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + // we should get a client for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + Client *client = windowCreatedSpy.first().first().value(); + QVERIFY(client); + QCOMPARE(client->window(), w); + QCOMPARE(client->clientSize(), windowGeometry.size()); + + // let's get the window info + QDBusPendingReply reply{getWindowInfo(client->internalId())}; + reply.waitForFinished(); + QVERIFY(reply.isValid()); + QVERIFY(!reply.isError()); + auto windowData = reply.value(); + QVERIFY(!windowData.isEmpty()); + QCOMPARE(windowData.size(), 23); + QCOMPARE(windowData.value(QStringLiteral("type")).toInt(), NET::Normal); + QCOMPARE(windowData.value(QStringLiteral("x")).toInt(), client->x()); + QCOMPARE(windowData.value(QStringLiteral("y")).toInt(), client->y()); + QCOMPARE(windowData.value(QStringLiteral("width")).toInt(), client->width()); + QCOMPARE(windowData.value(QStringLiteral("height")).toInt(), client->height()); + QCOMPARE(windowData.value(QStringLiteral("x11DesktopNumber")).toInt(), 1); + QCOMPARE(windowData.value(QStringLiteral("minimized")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("shaded")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("fullscreen")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("keepAbove")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("keepBelow")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("skipTaskbar")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("skipPager")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("skipSwitcher")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("maximizeHorizontal")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("maximizeVertical")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("noBorder")).toBool(), false); + QCOMPARE(windowData.value(QStringLiteral("role")).toString(), QString()); + QCOMPARE(windowData.value(QStringLiteral("resourceName")).toString(), QStringLiteral("foo")); + QCOMPARE(windowData.value(QStringLiteral("resourceClass")).toString(), QStringLiteral("bar")); + QCOMPARE(windowData.value(QStringLiteral("desktopFile")).toString(), QStringLiteral("org.kde.foo")); + QCOMPARE(windowData.value(QStringLiteral("caption")).toString(), QStringLiteral("Some caption")); + // not testing clientmachine as that is system dependent + + auto verifyProperty = [client] (const QString &name) { + QDBusPendingReply reply{getWindowInfo(client->internalId())}; + reply.waitForFinished(); + return reply.value().value(name).toBool(); + }; + + QVERIFY(!client->isMinimized()); + client->setMinimized(true); + QVERIFY(client->isMinimized()); + QCOMPARE(verifyProperty(QStringLiteral("minimized")), true); + + QVERIFY(!client->keepAbove()); + client->setKeepAbove(true); + QVERIFY(client->keepAbove()); + QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true); + + QVERIFY(!client->keepBelow()); + client->setKeepBelow(true); + QVERIFY(client->keepBelow()); + QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true); + + QVERIFY(!client->skipTaskbar()); + client->setSkipTaskbar(true); + QVERIFY(client->skipTaskbar()); + QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true); + + QVERIFY(!client->skipPager()); + client->setSkipPager(true); + QVERIFY(client->skipPager()); + QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true); + + QVERIFY(!client->skipSwitcher()); + client->setSkipSwitcher(true); + QVERIFY(client->skipSwitcher()); + QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true); + + QVERIFY(!client->isShade()); + client->setShade(ShadeNormal); + QVERIFY(client->isShade()); + QCOMPARE(verifyProperty(QStringLiteral("shaded")), true); + client->setShade(ShadeNone); + QVERIFY(!client->isShade()); + + QVERIFY(!client->noBorder()); + client->setNoBorder(true); + QVERIFY(client->noBorder()); + QCOMPARE(verifyProperty(QStringLiteral("noBorder")), true); + client->setNoBorder(false); + QVERIFY(!client->noBorder()); + + QVERIFY(!client->isFullScreen()); + client->setFullScreen(true); + QVERIFY(client->isFullScreen()); + QVERIFY(client->clientSize() != windowGeometry.size()); + QCOMPARE(verifyProperty(QStringLiteral("fullscreen")), true); + reply = getWindowInfo(client->internalId()); + reply.waitForFinished(); + QCOMPARE(reply.value().value(QStringLiteral("width")).toInt(), client->width()); + QCOMPARE(reply.value().value(QStringLiteral("height")).toInt(), client->height()); + client->setFullScreen(false); + QVERIFY(!client->isFullScreen()); + + // maximize + client->setMaximize(true, false); + QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), true); + QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), false); + client->setMaximize(false, true); + QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), false); + QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), true); + + const auto id = client->internalId(); + // destroy the window + xcb_unmap_window(c.data(), w); + xcb_flush(c.data()); + + QSignalSpy windowClosedSpy(client, &Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.data(), w); + c.reset(); + + reply = getWindowInfo(id); + reply.waitForFinished(); + QVERIFY(reply.value().empty()); +} + +WAYLANDTEST_MAIN(TestDbusInterface) +#include "dbus_interface_test.moc" diff --git a/dbusinterface.cpp b/dbusinterface.cpp index 1a4da99a6..76667db6d 100644 --- a/dbusinterface.cpp +++ b/dbusinterface.cpp @@ -1,495 +1,512 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 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 . *********************************************************************/ // own #include "dbusinterface.h" #include "compositingadaptor.h" #include "virtualdesktopmanageradaptor.h" // kwin #include "abstract_client.h" #include "atoms.h" #include "composite.h" #include "debug_console.h" #include "main.h" #include "placement.h" #include "platform.h" #include "kwinadaptor.h" #include "scene.h" #include "workspace.h" #include "virtualdesktops.h" #ifdef KWIN_BUILD_ACTIVITIES #include "activities.h" #endif // Qt #include #include namespace KWin { DBusInterface::DBusInterface(QObject *parent) : QObject(parent) , m_serviceName(QStringLiteral("org.kde.KWin")) { (void) new KWinAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/KWin"), this); const QByteArray dBusSuffix = qgetenv("KWIN_DBUS_SERVICE_SUFFIX"); if (!dBusSuffix.isNull()) { m_serviceName = m_serviceName + QLatin1Char('.') + dBusSuffix; } if (!dbus.registerService(m_serviceName)) { QDBusServiceWatcher *dog = new QDBusServiceWatcher(m_serviceName, dbus, QDBusServiceWatcher::WatchForUnregistration, this); connect (dog, SIGNAL(serviceUnregistered(QString)), SLOT(becomeKWinService(QString))); } else { announceService(); } dbus.connect(QString(), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"), Workspace::self(), SLOT(slotReloadConfig())); connect(kwinApp(), &Application::x11ConnectionChanged, this, &DBusInterface::announceService); } void DBusInterface::becomeKWinService(const QString &service) { // TODO: this watchdog exists to make really safe that we at some point get the service // but it's probably no longer needed since we explicitly unregister the service with the deconstructor if (service == m_serviceName && QDBusConnection::sessionBus().registerService(m_serviceName) && sender()) { sender()->deleteLater(); // bye doggy :'( announceService(); } } DBusInterface::~DBusInterface() { QDBusConnection::sessionBus().unregisterService(m_serviceName); // KApplication automatically also grabs org.kde.kwin, so it's often been used externally - ensure to free it as well QDBusConnection::sessionBus().unregisterService(QStringLiteral("org.kde.kwin")); if (kwinApp()->x11Connection()) { xcb_delete_property(kwinApp()->x11Connection(), kwinApp()->x11RootWindow(), atoms->kwin_dbus_service); } } void DBusInterface::announceService() { if (!kwinApp()->x11Connection()) { return; } const QByteArray service = m_serviceName.toUtf8(); xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_REPLACE, kwinApp()->x11RootWindow(), atoms->kwin_dbus_service, atoms->utf8_string, 8, service.size(), service.constData()); } // wrap void methods with no arguments to Workspace #define WRAP(name) \ void DBusInterface::name() \ {\ Workspace::self()->name();\ } WRAP(reconfigure) #undef WRAP void DBusInterface::killWindow() { Workspace::self()->slotKillWindow(); } #define WRAP(name) \ void DBusInterface::name() \ {\ Placement::self()->name();\ } WRAP(cascadeDesktop) WRAP(unclutterDesktop) #undef WRAP // wrap returning methods with no arguments to Workspace #define WRAP( rettype, name ) \ rettype DBusInterface::name( ) \ {\ return Workspace::self()->name(); \ } WRAP(QString, supportInformation) #undef WRAP bool DBusInterface::startActivity(const QString &in0) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return false; } return Activities::self()->start(in0); #else Q_UNUSED(in0) return false; #endif } bool DBusInterface::stopActivity(const QString &in0) { #ifdef KWIN_BUILD_ACTIVITIES if (!Activities::self()) { return false; } return Activities::self()->stop(in0); #else Q_UNUSED(in0) return false; #endif } int DBusInterface::currentDesktop() { return VirtualDesktopManager::self()->current(); } bool DBusInterface::setCurrentDesktop(int desktop) { return VirtualDesktopManager::self()->setCurrent(desktop); } void DBusInterface::nextDesktop() { VirtualDesktopManager::self()->moveTo(); } void DBusInterface::previousDesktop() { VirtualDesktopManager::self()->moveTo(); } void DBusInterface::showDebugConsole() { DebugConsole *console = new DebugConsole; console->show(); } +namespace { +QVariantMap clientToVariantMap(const AbstractClient *c) +{ + return { + {QStringLiteral("resourceClass"), c->resourceClass()}, + {QStringLiteral("resourceName"), c->resourceName()}, + {QStringLiteral("desktopFile"), c->desktopFileName()}, + {QStringLiteral("role"), c->windowRole()}, + {QStringLiteral("caption"), c->captionNormal()}, + {QStringLiteral("clientMachine"), c->wmClientMachine(true)}, + {QStringLiteral("type"), c->windowType()}, + {QStringLiteral("x"), c->x()}, + {QStringLiteral("y"), c->y()}, + {QStringLiteral("width"), c->width()}, + {QStringLiteral("height"), c->height()}, + {QStringLiteral("x11DesktopNumber"), c->desktop()}, + {QStringLiteral("minimized"), c->isMinimized()}, + {QStringLiteral("shaded"), c->isShade()}, + {QStringLiteral("fullscreen"), c->isFullScreen()}, + {QStringLiteral("keepAbove"), c->keepAbove()}, + {QStringLiteral("keepBelow"), c->keepBelow()}, + {QStringLiteral("noBorder"), c->noBorder()}, + {QStringLiteral("skipTaskbar"), c->skipTaskbar()}, + {QStringLiteral("skipPager"), c->skipPager()}, + {QStringLiteral("skipSwitcher"), c->skipSwitcher()}, + {QStringLiteral("maximizeHorizontal"), c->maximizeMode() & MaximizeHorizontal}, + {QStringLiteral("maximizeVertical"), c->maximizeMode() & MaximizeVertical} + }; +} +} + QVariantMap DBusInterface::queryWindowInfo() { m_replyQueryWindowInfo = message(); setDelayedReply(true); kwinApp()->platform()->startInteractiveWindowSelection( [this] (Toplevel *t) { if (auto c = qobject_cast(t)) { - const QVariantMap ret{ - {QStringLiteral("resourceClass"), c->resourceClass()}, - {QStringLiteral("resourceName"), c->resourceName()}, - {QStringLiteral("desktopFile"), c->desktopFileName()}, - {QStringLiteral("role"), c->windowRole()}, - {QStringLiteral("caption"), c->captionNormal()}, - {QStringLiteral("clientMachine"), c->wmClientMachine(true)}, - {QStringLiteral("type"), c->windowType()}, - {QStringLiteral("x"), c->x()}, - {QStringLiteral("y"), c->y()}, - {QStringLiteral("width"), c->width()}, - {QStringLiteral("height"), c->height()}, - {QStringLiteral("x11DesktopNumber"), c->desktop()}, - {QStringLiteral("minimized"), c->isMinimized()}, - {QStringLiteral("shaded"), c->isShade()}, - {QStringLiteral("fullscreen"), c->isFullScreen()}, - {QStringLiteral("keepAbove"), c->keepAbove()}, - {QStringLiteral("keepBelow"), c->keepBelow()}, - {QStringLiteral("noBorder"), c->noBorder()}, - {QStringLiteral("skipTaskbar"), c->skipTaskbar()}, - {QStringLiteral("skipPager"), c->skipPager()}, - {QStringLiteral("skipSwitcher"), c->skipSwitcher()}, - {QStringLiteral("maximizeHorizontal"), c->maximizeMode() & MaximizeHorizontal}, - {QStringLiteral("maximizeVertical"), c->maximizeMode() & MaximizeVertical} - }; - QDBusConnection::sessionBus().send(m_replyQueryWindowInfo.createReply(ret)); + QDBusConnection::sessionBus().send(m_replyQueryWindowInfo.createReply(clientToVariantMap(c))); } else { QDBusConnection::sessionBus().send(m_replyQueryWindowInfo.createErrorReply(QString(), QString())); } } ); return QVariantMap{}; } +QVariantMap DBusInterface::getWindowInfo(const QString &uuid) +{ + const auto id = QUuid::fromString(uuid); + const auto client = workspace()->findAbstractClient([&id] (const AbstractClient *c) { return c->internalId() == id; }); + if (client) { + return clientToVariantMap(client); + } else { + return {}; + } +} + CompositorDBusInterface::CompositorDBusInterface(Compositor *parent) : QObject(parent) , m_compositor(parent) { connect(m_compositor, &Compositor::compositingToggled, this, &CompositorDBusInterface::compositingToggled); new CompositingAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.registerObject(QStringLiteral("/Compositor"), this); dbus.connect(QString(), QStringLiteral("/Compositor"), QStringLiteral("org.kde.kwin.Compositing"), QStringLiteral("reinit"), m_compositor, SLOT(slotReinitialize())); } QString CompositorDBusInterface::compositingNotPossibleReason() const { return kwinApp()->platform()->compositingNotPossibleReason(); } QString CompositorDBusInterface::compositingType() const { if (!m_compositor->hasScene()) { return QStringLiteral("none"); } switch (m_compositor->scene()->compositingType()) { case XRenderCompositing: return QStringLiteral("xrender"); case OpenGL2Compositing: if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { return QStringLiteral("gles"); } else { return QStringLiteral("gl2"); } case QPainterCompositing: return QStringLiteral("qpainter"); case NoCompositing: default: return QStringLiteral("none"); } } bool CompositorDBusInterface::isActive() const { return m_compositor->isActive(); } bool CompositorDBusInterface::isCompositingPossible() const { return kwinApp()->platform()->compositingPossible(); } bool CompositorDBusInterface::isOpenGLBroken() const { return kwinApp()->platform()->openGLCompositingIsBroken(); } bool CompositorDBusInterface::platformRequiresCompositing() const { return kwinApp()->platform()->requiresCompositing(); } void CompositorDBusInterface::resume() { m_compositor->resume(Compositor::ScriptSuspend); } void CompositorDBusInterface::suspend() { m_compositor->suspend(Compositor::ScriptSuspend); } QStringList CompositorDBusInterface::supportedOpenGLPlatformInterfaces() const { QStringList interfaces; bool supportsGlx = false; #if HAVE_EPOXY_GLX supportsGlx = (kwinApp()->operationMode() == Application::OperationModeX11); #endif if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGLES) { supportsGlx = false; } if (supportsGlx) { interfaces << QStringLiteral("glx"); } interfaces << QStringLiteral("egl"); return interfaces; } VirtualDesktopManagerDBusInterface::VirtualDesktopManagerDBusInterface(VirtualDesktopManager *parent) : QObject(parent) , m_manager(parent) { qDBusRegisterMetaType(); qDBusRegisterMetaType(); new VirtualDesktopManagerAdaptor(this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/VirtualDesktopManager"), QStringLiteral("org.kde.KWin.VirtualDesktopManager"), this ); connect(m_manager, &VirtualDesktopManager::currentChanged, this, [this](uint previousDesktop, uint newDesktop) { Q_UNUSED(previousDesktop); Q_UNUSED(newDesktop); emit currentChanged(m_manager->currentDesktop()->id()); } ); connect(m_manager, &VirtualDesktopManager::countChanged, this, [this](uint previousCount, uint newCount) { Q_UNUSED(previousCount); emit countChanged(newCount); emit desktopsChanged(desktops()); } ); connect(m_manager, &VirtualDesktopManager::navigationWrappingAroundChanged, this, [this]() { emit navigationWrappingAroundChanged(isNavigationWrappingAround()); } ); connect(m_manager, &VirtualDesktopManager::rowsChanged, this, &VirtualDesktopManagerDBusInterface::rowsChanged); for (auto *vd : m_manager->desktops()) { connect(vd, &VirtualDesktop::x11DesktopNumberChanged, this, [this, vd]() { DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; emit desktopDataChanged(vd->id(), data); emit desktopsChanged(desktops()); } ); connect(vd, &VirtualDesktop::nameChanged, this, [this, vd]() { DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; emit desktopDataChanged(vd->id(), data); emit desktopsChanged(desktops()); } ); } connect(m_manager, &VirtualDesktopManager::desktopCreated, this, [this](VirtualDesktop *vd) { connect(vd, &VirtualDesktop::x11DesktopNumberChanged, this, [this, vd]() { DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; emit desktopDataChanged(vd->id(), data); emit desktopsChanged(desktops()); } ); connect(vd, &VirtualDesktop::nameChanged, this, [this, vd]() { DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; emit desktopDataChanged(vd->id(), data); emit desktopsChanged(desktops()); } ); DBusDesktopDataStruct data{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; emit desktopCreated(vd->id(), data); emit desktopsChanged(desktops()); } ); connect(m_manager, &VirtualDesktopManager::desktopRemoved, this, [this](VirtualDesktop *vd) { emit desktopRemoved(vd->id()); emit desktopsChanged(desktops()); } ); } uint VirtualDesktopManagerDBusInterface::count() const { return m_manager->count(); } void VirtualDesktopManagerDBusInterface::setRows(uint rows) { if (static_cast(m_manager->grid().height()) == rows) { return; } m_manager->setRows(rows); m_manager->save(); } uint VirtualDesktopManagerDBusInterface::rows() const { return m_manager->rows(); } void VirtualDesktopManagerDBusInterface::setCurrent(const QString &id) { if (m_manager->currentDesktop()->id() == id) { return; } auto *vd = m_manager->desktopForId(id.toUtf8()); if (vd) { m_manager->setCurrent(vd); } } QString VirtualDesktopManagerDBusInterface::current() const { return m_manager->currentDesktop()->id(); } void VirtualDesktopManagerDBusInterface::setNavigationWrappingAround(bool wraps) { if (m_manager->isNavigationWrappingAround() == wraps) { return; } m_manager->setNavigationWrappingAround(wraps); } bool VirtualDesktopManagerDBusInterface::isNavigationWrappingAround() const { return m_manager->isNavigationWrappingAround(); } DBusDesktopDataVector VirtualDesktopManagerDBusInterface::desktops() const { const auto desks = m_manager->desktops(); DBusDesktopDataVector desktopVect; desktopVect.reserve(m_manager->count()); std::transform(desks.constBegin(), desks.constEnd(), std::back_inserter(desktopVect), [] (const VirtualDesktop *vd) { return DBusDesktopDataStruct{.position = vd->x11DesktopNumber() - 1, .id = vd->id(), .name = vd->name()}; } ); return desktopVect; } void VirtualDesktopManagerDBusInterface::createDesktop(uint position, const QString &name) { m_manager->createVirtualDesktop(position + 1, name); } void VirtualDesktopManagerDBusInterface::setDesktopName(const QString &id, const QString &name) { VirtualDesktop *vd = m_manager->desktopForId(id.toUtf8()); if (!vd) { return; } if (vd->name() == name) { return; } vd->setName(name); m_manager->save(); } void VirtualDesktopManagerDBusInterface::removeDesktop(const QString &id) { m_manager->removeVirtualDesktop(id.toUtf8()); } } // namespace diff --git a/dbusinterface.h b/dbusinterface.h index e07019ba3..0f921f17a 100644 --- a/dbusinterface.h +++ b/dbusinterface.h @@ -1,247 +1,248 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2012 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 . *********************************************************************/ #ifndef KWIN_DBUS_INTERFACE_H #define KWIN_DBUS_INTERFACE_H #include #include #include "virtualdesktopsdbustypes.h" namespace KWin { class Compositor; class VirtualDesktopManager; /** * @brief This class is a wrapper for the org.kde.KWin D-Bus interface. * * The main purpose of this class is to be exported on the D-Bus as object /KWin. * It is a pure wrapper to provide the deprecated D-Bus methods which have been * removed from Workspace which used to implement the complete D-Bus interface. * * Nowadays the D-Bus interfaces are distributed, parts of it are exported on * /Compositor, parts on /Effects and parts on /KWin. The implementation in this * class just delegates the method calls to the actual implementation in one of the * three singletons. * * @author Martin Gräßlin **/ class DBusInterface: public QObject, protected QDBusContext { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin") public: explicit DBusInterface(QObject *parent); virtual ~DBusInterface(); public: // PROPERTIES public Q_SLOTS: // METHODS Q_NOREPLY void cascadeDesktop(); int currentDesktop(); Q_NOREPLY void killWindow(); void nextDesktop(); void previousDesktop(); Q_NOREPLY void reconfigure(); bool setCurrentDesktop(int desktop); bool startActivity(const QString &in0); bool stopActivity(const QString &in0); QString supportInformation(); Q_NOREPLY void unclutterDesktop(); Q_NOREPLY void showDebugConsole(); QVariantMap queryWindowInfo(); + QVariantMap getWindowInfo(const QString &uuid); private Q_SLOTS: void becomeKWinService(const QString &service); private: void announceService(); QString m_serviceName; QDBusMessage m_replyQueryWindowInfo; }; class CompositorDBusInterface : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kwin.Compositing") /** * @brief Whether the Compositor is active. That is a Scene is present and the Compositor is * not shutting down itself. **/ Q_PROPERTY(bool active READ isActive) /** * @brief Whether compositing is possible. Mostly means whether the required X extensions * are available. **/ Q_PROPERTY(bool compositingPossible READ isCompositingPossible) /** * @brief The reason why compositing is not possible. Empty String if compositing is possible. **/ Q_PROPERTY(QString compositingNotPossibleReason READ compositingNotPossibleReason) /** * @brief Whether OpenGL has failed badly in the past (crash) and is considered as broken. **/ Q_PROPERTY(bool openGLIsBroken READ isOpenGLBroken) /** * The type of the currently used Scene: * @li @c none No Compositing * @li @c xrender XRender * @li @c gl1 OpenGL 1 * @li @c gl2 OpenGL 2 * @li @c gles OpenGL ES 2 **/ Q_PROPERTY(QString compositingType READ compositingType) /** * @brief All currently supported OpenGLPlatformInterfaces. * * Possible values: * @li glx * @li egl * * Values depend on operation mode and compile time options. **/ Q_PROPERTY(QStringList supportedOpenGLPlatformInterfaces READ supportedOpenGLPlatformInterfaces) Q_PROPERTY(bool platformRequiresCompositing READ platformRequiresCompositing) public: explicit CompositorDBusInterface(Compositor *parent); virtual ~CompositorDBusInterface() = default; bool isActive() const; bool isCompositingPossible() const; QString compositingNotPossibleReason() const; bool isOpenGLBroken() const; QString compositingType() const; QStringList supportedOpenGLPlatformInterfaces() const; bool platformRequiresCompositing() const; public Q_SLOTS: /** * @brief Suspends the Compositor if it is currently active. * * Note: it is possible that the Compositor is not able to suspend. Use @link isActive to check * whether the Compositor has been suspended. * * @return void * @see resume * @see isActive **/ void suspend(); /** * @brief Resumes the Compositor if it is currently suspended. * * Note: it is possible that the Compositor cannot be resumed, that is there might be Clients * blocking the usage of Compositing or the Scene might be broken. Use @link isActive to check * whether the Compositor has been resumed. Also check @link isCompositingPossible and * @link isOpenGLBroken. * * Note: The starting of the Compositor can require some time and is partially done threaded. * After this method returns the setup may not have been completed. * * @return void * @see suspend * @see isActive * @see isCompositingPossible * @see isOpenGLBroken **/ void resume(); Q_SIGNALS: void compositingToggled(bool active); private: Compositor *m_compositor; }; //TODO: disable all of this in case of kiosk? class VirtualDesktopManagerDBusInterface : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.VirtualDesktopManager") /** * The number of virtual desktops currently available. * The ids of the virtual desktops are in the range [1, VirtualDesktopManager::maximum()]. **/ Q_PROPERTY(uint count READ count NOTIFY countChanged) /** * The number of rows the virtual desktops will be laid out in **/ Q_PROPERTY(uint rows READ rows WRITE setRows NOTIFY rowsChanged) /** * The id of the virtual desktop which is currently in use. **/ Q_PROPERTY(QString current READ current WRITE setCurrent NOTIFY currentChanged) /** * Whether navigation in the desktop layout wraps around at the borders. **/ Q_PROPERTY(bool navigationWrappingAround READ isNavigationWrappingAround WRITE setNavigationWrappingAround NOTIFY navigationWrappingAroundChanged) /** * list of key/value pairs which every one of them is representing a desktop */ Q_PROPERTY(KWin::DBusDesktopDataVector desktops READ desktops NOTIFY desktopsChanged); public: VirtualDesktopManagerDBusInterface(VirtualDesktopManager *parent); ~VirtualDesktopManagerDBusInterface() = default; uint count() const; void setRows(uint rows); uint rows() const; void setCurrent(const QString &id); QString current() const; void setNavigationWrappingAround(bool wraps); bool isNavigationWrappingAround() const; KWin::DBusDesktopDataVector desktops() const; Q_SIGNALS: void countChanged(uint count); void rowsChanged(uint rows); void currentChanged(const QString &id); void navigationWrappingAroundChanged(bool wraps); void desktopsChanged(KWin::DBusDesktopDataVector); void desktopDataChanged(const QString &id, KWin::DBusDesktopDataStruct); void desktopCreated(const QString &id, KWin::DBusDesktopDataStruct); void desktopRemoved(const QString &id); public Q_SLOTS: /** * Create a desktop with a new name at a given position * note: the position starts from 1 */ void createDesktop(uint position, const QString &name); void setDesktopName(const QString &id, const QString &name); void removeDesktop(const QString &id); private: VirtualDesktopManager *m_manager; }; } // namespace #endif // KWIN_DBUS_INTERFACE_H diff --git a/org.kde.KWin.xml b/org.kde.KWin.xml index 4794335f5..b810c2337 100644 --- a/org.kde.KWin.xml +++ b/org.kde.KWin.xml @@ -1,44 +1,49 @@ + + + + +