diff --git a/autotests/kstartupinfo_unittest.cpp b/autotests/kstartupinfo_unittest.cpp index b458a7a..7b4acab 100644 --- a/autotests/kstartupinfo_unittest.cpp +++ b/autotests/kstartupinfo_unittest.cpp @@ -1,329 +1,331 @@ /* This file is part of the KDE libraries Copyright 2012,2019 David Faure Copyright 2012 Kai Dombrowe This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include "netwm.h" #include #include #include #include #include Q_DECLARE_METATYPE(KStartupInfoId) Q_DECLARE_METATYPE(KStartupInfoData) class KStartupInfo_UnitTest : public QObject { Q_OBJECT public: KStartupInfo_UnitTest() : m_listener(KStartupInfo::CleanOnCantDetect, this), m_receivedCount(0) { qRegisterMetaType(); qRegisterMetaType(); connect(&m_listener, SIGNAL(gotNewStartup(KStartupInfoId,KStartupInfoData)), this, SLOT(slotNewStartup(KStartupInfoId,KStartupInfoData))); } protected Q_SLOTS: void slotNewStartup(const KStartupInfoId &id, const KStartupInfoData &data) { ++m_receivedCount; m_receivedId = id; m_receivedData = data; emit ready(); } Q_SIGNALS: void ready(); private Q_SLOTS: void testStart(); void dontCrashCleanup_data(); void dontCrashCleanup(); void checkCleanOnCantDetectTest(); void checkStartupTest_data(); void checkStartupTest(); void createNewStartupIdTest(); void createNewStartupIdForTimestampTest(); void setNewStartupIdTest(); private: KStartupInfo m_listener; int m_receivedCount; KStartupInfoId m_receivedId; KStartupInfoData m_receivedData; }; void KStartupInfo_UnitTest::testStart() { KStartupInfoId id; id.initId(KStartupInfo::createNewStartupId()); KStartupInfoData data; const QString appId = "/dir with space/kstartupinfo_unittest.desktop"; data.setApplicationId(appId); const QString iconPath = "/dir with space/kstartupinfo_unittest.png"; data.setIcon(iconPath); const QString description = "A description"; data.setDescription(description); const QString name = "A name"; data.setName(name); const int pid = 12345; data.addPid(pid); const QString bin = "dir with space/kstartupinfo_unittest"; data.setBin(bin); QSignalSpy removedSpy(&m_listener, SIGNAL(gotRemoveStartup(KStartupInfoId,KStartupInfoData))); QVERIFY(removedSpy.isValid()); KStartupInfo::sendStartup(id, data); KStartupInfo::sendFinish(id, data); QSignalSpy spy(this, SIGNAL(ready())); spy.wait(5000); QCOMPARE(m_receivedCount, 1); // qDebug() << m_receivedId.id(); // something like "$HOSTNAME;1342544979;490718;8602_TIME0" QCOMPARE(m_receivedData.name(), name); QCOMPARE(m_receivedData.description(), description); QCOMPARE(m_receivedData.applicationId(), appId); QCOMPARE(m_receivedData.icon(), iconPath); QCOMPARE(m_receivedData.bin(), bin); //qDebug() << m_receivedData.bin() << m_receivedData.name() << m_receivedData.description() << m_receivedData.icon() << m_receivedData.pids() << m_receivedData.hostname() << m_receivedData.applicationId(); int waitTime = 0; while (waitTime < 5000 && removedSpy.count() < 1) { QTest::qWait(200); waitTime += 200; } QCOMPARE(removedSpy.count(), 1); } static void doSync() { auto *c = QX11Info::connection(); const auto cookie = xcb_get_input_focus(c); xcb_generic_error_t *error = nullptr; QScopedPointer sync(xcb_get_input_focus_reply(c, cookie, &error)); if (error) { free(error); } } void KStartupInfo_UnitTest::dontCrashCleanup_data() { QTest::addColumn("silent"); QTest::addColumn("change"); QTest::addColumn("countRemoveStartup"); QTest::newRow("normal") << false << false << 2; QTest::newRow("silent") << true << false << 0; QTest::newRow("uninited") << false << true << 0; } void KStartupInfo_UnitTest::dontCrashCleanup() { qputenv("KSTARTUPINFO_TIMEOUT", QByteArrayLiteral("1")); KStartupInfoId id; KStartupInfoId id2; id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0")); id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1")); KStartupInfoData data; data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop")); data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png")); data.setDescription(QStringLiteral("A description")); data.setName(QStringLiteral("A name")); data.addPid(12345); data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest")); QFETCH(bool, silent); if (silent) { data.setSilent(KStartupInfoData::Yes); } QSignalSpy spy(&m_listener, SIGNAL(gotRemoveStartup(KStartupInfoId,KStartupInfoData))); QFETCH(bool, change); if (change) { KStartupInfo::sendChange(id, data); KStartupInfo::sendChange(id2, data); } else { KStartupInfo::sendStartup(id, data); KStartupInfo::sendStartup(id2, data); } // let's do a roundtrip to the X server doSync(); QFETCH(int, countRemoveStartup); int waitTime = 1900; QTest::qWait(1900); while (waitTime <= 5000) { QTest::qWait(200); waitTime += 200; if (spy.count() == countRemoveStartup) { break; } } QCOMPARE(spy.count(), countRemoveStartup); } void KStartupInfo_UnitTest::checkCleanOnCantDetectTest() { KStartupInfoId id; KStartupInfoId id2; id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0")); id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1")); KStartupInfoData data; data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop")); data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png")); data.setDescription(QStringLiteral("A description")); data.setName(QStringLiteral("A name")); data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest")); data.setWMClass(QByteArrayLiteral("0")); xcb_connection_t *c = QX11Info::connection(); xcb_window_t window = xcb_generate_id(c); uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE}; xcb_create_window(c, XCB_COPY_FROM_PARENT, window, QX11Info::appRootWindow(), 0, 0, 100, 100, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); KStartupInfo::sendStartup(id, data); KStartupInfo::sendStartup(id2, data); int previousCount = m_receivedCount; doSync(); QTest::qWait(10); xcb_map_window(c, window); xcb_flush(c); QTest::qWait(10); xcb_unmap_window(c, window); xcb_flush(c); QTest::qWait(100); xcb_map_window(c, window); xcb_flush(c); QCOMPARE(m_receivedCount, previousCount + 2); QCOMPARE(m_receivedId, id2); } void KStartupInfo_UnitTest::checkStartupTest_data() { QTest::addColumn("wmClass"); QTest::addColumn("pid"); QTest::newRow("wmClass") << QByteArrayLiteral("kstartupinfotest") << 0; QTest::newRow("pid") << QByteArray() << 12345; } void KStartupInfo_UnitTest::checkStartupTest() { KStartupInfoId id; KStartupInfoId id2; id.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_0")); id2.initId(QByteArrayLiteral("somefancyidwhichisrandom_kstartupinfo_unittest_1")); KStartupInfoData data; data.setApplicationId(QStringLiteral("/dir with space/kstartupinfo_unittest.desktop")); data.setIcon(QStringLiteral("/dir with space/kstartupinfo_unittest.png")); data.setDescription(QStringLiteral("A description")); data.setName(QStringLiteral("A name")); data.setBin(QStringLiteral("dir with space/kstartupinfo_unittest")); QFETCH(int, pid); data.addPid(pid); data.setHostname(QByteArrayLiteral("localhost")); // important for this test: WMClass QFETCH(QByteArray, wmClass); data.setWMClass(wmClass); xcb_connection_t *c = QX11Info::connection(); xcb_window_t window = xcb_generate_id(c); uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE}; xcb_create_window(c, XCB_COPY_FROM_PARENT, window, QX11Info::appRootWindow(), 0, 0, 100, 100, 0, XCB_COPY_FROM_PARENT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 8, wmClass.length() * 2 + 1, "kstartupinfotest\0kstartupinfotest"); xcb_change_property(c, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, 9, "localhost"); NETWinInfo winInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); winInfo.setPid(pid); KStartupInfo info(KStartupInfo::DisableKWinModule | KStartupInfo::AnnounceSilenceChanges, this); KStartupInfo::sendStartup(id, data); KStartupInfo::sendStartup(id2, data); doSync(); QTest::qWait(100); QCOMPARE(info.checkStartup(window), KStartupInfo::Match); QCOMPARE(info.checkStartup(window), KStartupInfo::Match); } void KStartupInfo_UnitTest::createNewStartupIdTest() { const QByteArray &id = KStartupInfo::createNewStartupId(); QVERIFY(!id.isEmpty()); const int index = id.indexOf(QByteArrayLiteral("TIME")); QVERIFY(index != -1); const QByteArray time = id.mid(index + 4); QVERIFY(time.toULongLong() != 0u); } void KStartupInfo_UnitTest::createNewStartupIdForTimestampTest() { const QByteArray &id = KStartupInfo::createNewStartupIdForTimestamp(5); QVERIFY(!id.isEmpty()); const int index = id.indexOf(QByteArrayLiteral("TIME")); QVERIFY(index != -1); QCOMPARE(id.mid(index + 4).toULongLong(), 5u); } void KStartupInfo_UnitTest::setNewStartupIdTest() { { QWindow window; const QByteArray str = "somefancyidwhichisrandom_kstartupinfo_unittest_2"; KStartupInfo::setNewStartupId(&window, str); QCOMPARE(KStartupInfo::startupId(), str); } +#ifndef KWINDOWSYSTEM_NO_DEPRECATED { QWidget widget; const QByteArray str = "somefancyidwhichisrandom_kstartupinfo_unittest_3"; KStartupInfo::setNewStartupId(&widget, str); // deprecated QCOMPARE(KStartupInfo::startupId(), str); } +#endif } QTEST_MAIN(KStartupInfo_UnitTest) #include "kstartupinfo_unittest.moc" diff --git a/autotests/kwindowsystemx11test.cpp b/autotests/kwindowsystemx11test.cpp index 73a069c..4783649 100644 --- a/autotests/kwindowsystemx11test.cpp +++ b/autotests/kwindowsystemx11test.cpp @@ -1,396 +1,399 @@ /* * Copyright 2013 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "nettesthelper.h" #include "kwindowsystem.h" #include "netwm.h" #include #include #include #include Q_DECLARE_METATYPE(WId) Q_DECLARE_METATYPE(NET::Properties) Q_DECLARE_METATYPE(NET::Properties2) Q_DECLARE_METATYPE(const unsigned long*) class KWindowSystemX11Test : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); // needs to be first test, would fail if run after others (X11) void testActiveWindowChanged(); void testWindowAdded(); void testWindowRemoved(); void testDesktopChanged(); void testNumberOfDesktopsChanged(); void testDesktopNamesChanged(); void testShowingDesktopChanged(); void testSetShowingDesktop(); void testWorkAreaChanged(); void testWindowTitleChanged(); void testMinimizeWindow(); void testPlatformX11(); }; void KWindowSystemX11Test::initTestCase() { QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets); } void KWindowSystemX11Test::testActiveWindowChanged() { qRegisterMetaType("WId"); QSignalSpy spy(KWindowSystem::self(), SIGNAL(activeWindowChanged(WId))); QScopedPointer widget(new QWidget); widget->show(); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toULongLong(), widget->winId()); QCOMPARE(KWindowSystem::activeWindow(), widget->winId()); } void KWindowSystemX11Test::testWindowAdded() { qRegisterMetaType("WId"); QSignalSpy spy(KWindowSystem::self(), SIGNAL(windowAdded(WId))); QSignalSpy stackingOrderSpy(KWindowSystem::self(), SIGNAL(stackingOrderChanged())); QScopedPointer widget(new QWidget); widget->show(); QVERIFY(QTest::qWaitForWindowExposed(widget.data())); QVERIFY(spy.count() > 0); bool hasWId = false; for (auto it = spy.constBegin(); it != spy.constEnd(); ++it) { if ((*it).isEmpty()) { continue; } QCOMPARE((*it).count(), 1); hasWId = (*it).at(0).toULongLong() == widget->winId(); if (hasWId) { break; } } QVERIFY(hasWId); QVERIFY(KWindowSystem::hasWId(widget->winId())); QVERIFY(!stackingOrderSpy.isEmpty()); } void KWindowSystemX11Test::testWindowRemoved() { qRegisterMetaType("WId"); QScopedPointer widget(new QWidget); widget->show(); QVERIFY(QTest::qWaitForWindowExposed(widget.data())); QVERIFY(KWindowSystem::hasWId(widget->winId())); QSignalSpy spy(KWindowSystem::self(), SIGNAL(windowRemoved(WId))); widget->hide(); spy.wait(1000); QCOMPARE(spy.first().at(0).toULongLong(), widget->winId()); QVERIFY(!KWindowSystem::hasWId(widget->winId())); } void KWindowSystemX11Test::testDesktopChanged() { // This test requires a running NETWM-compliant window manager if (KWindowSystem::numberOfDesktops() == 1) { QSKIP("At least two virtual desktops are required to test desktop changed"); } const int current = KWindowSystem::currentDesktop(); QSignalSpy spy(KWindowSystem::self(), SIGNAL(currentDesktopChanged(int))); int newDesktop = current + 1; if (newDesktop > KWindowSystem::numberOfDesktops()) { newDesktop = 1; } KWindowSystem::setCurrentDesktop(newDesktop); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::currentDesktop(), newDesktop); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toInt(), newDesktop); spy.clear(); // setting to current desktop should not change anything KWindowSystem::setCurrentDesktop(newDesktop); // set back for clean state KWindowSystem::setCurrentDesktop(current); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::currentDesktop(), current); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toInt(), current); } void KWindowSystemX11Test::testNumberOfDesktopsChanged() { // This test requires a running NETWM-compliant window manager const int oldNumber = KWindowSystem::numberOfDesktops(); QSignalSpy spy(KWindowSystem::self(), SIGNAL(numberOfDesktopsChanged(int))); // KWin has arbitrary max number of 20 desktops, so don't fail the test if we use +1 const int newNumber = oldNumber < 20 ? oldNumber + 1 : oldNumber - 1; NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops, NET::Properties2()); info.setNumberOfDesktops(newNumber); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::numberOfDesktops(), newNumber); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toInt(), newNumber); spy.clear(); // setting to same number should not change info.setNumberOfDesktops(newNumber); // set back for clean state info.setNumberOfDesktops(oldNumber); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::numberOfDesktops(), oldNumber); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toInt(), oldNumber); } void KWindowSystemX11Test::testDesktopNamesChanged() { // This test requires a running NETWM-compliant window manager const QString origName = KWindowSystem::desktopName(KWindowSystem::currentDesktop()); QSignalSpy spy(KWindowSystem::self(), SIGNAL(desktopNamesChanged())); const QString testName = QStringLiteral("testFooBar"); KWindowSystem::setDesktopName(KWindowSystem::currentDesktop(), testName); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::desktopName(KWindowSystem::currentDesktop()), testName); QCOMPARE(spy.count(), 1); spy.clear(); QX11Info::setAppTime(QX11Info::getTimestamp()); // setting back to clean state KWindowSystem::setDesktopName(KWindowSystem::currentDesktop(), origName); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::desktopName(KWindowSystem::currentDesktop()), origName); QCOMPARE(spy.count(), 1); } void KWindowSystemX11Test::testShowingDesktopChanged() { QX11Info::setAppTime(QX11Info::getTimestamp()); const bool showingDesktop = KWindowSystem::showingDesktop(); QSignalSpy spy(KWindowSystem::self(), SIGNAL(showingDesktopChanged(bool))); NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::WM2ShowingDesktop); info.setShowingDesktop(!showingDesktop); QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toBool(), !showingDesktop); QCOMPARE(KWindowSystem::showingDesktop(), !showingDesktop); spy.clear(); QX11Info::setAppTime(QX11Info::getTimestamp()); // setting again should not change info.setShowingDesktop(!showingDesktop); // setting back to clean state info.setShowingDesktop(showingDesktop); QVERIFY(spy.wait(100)); QCOMPARE(spy.count(), 1); QCOMPARE(spy.first().at(0).toBool(), showingDesktop); QCOMPARE(KWindowSystem::showingDesktop(), showingDesktop); } void KWindowSystemX11Test::testSetShowingDesktop() { QSignalSpy spy(KWindowSystem::self(), SIGNAL(showingDesktopChanged(bool))); const bool showingDesktop = KWindowSystem::showingDesktop(); // setting the same state shouldn't change it QX11Info::setAppTime(QX11Info::getTimestamp()); KWindowSystem::setShowingDesktop(showingDesktop); QCOMPARE(spy.wait(), false); // spy.wait() waits for 5s QCOMPARE(KWindowSystem::showingDesktop(), showingDesktop); spy.clear(); // set opposite state QX11Info::setAppTime(QX11Info::getTimestamp()); KWindowSystem::setShowingDesktop(!showingDesktop); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::showingDesktop(), !showingDesktop); spy.clear(); // setting back to clean state QX11Info::setAppTime(QX11Info::getTimestamp()); KWindowSystem::setShowingDesktop(showingDesktop); QVERIFY(spy.wait()); QCOMPARE(KWindowSystem::showingDesktop(), showingDesktop); spy.clear(); } void KWindowSystemX11Test::testWorkAreaChanged() { // if there are multiple screens this test can fail as workarea is not multi screen aware QSignalSpy spy(KWindowSystem::self(), SIGNAL(workAreaChanged())); QSignalSpy strutSpy(KWindowSystem::self(), SIGNAL(strutChanged())); QWidget widget; widget.setGeometry(0, 0, 100, 10); widget.show(); KWindowSystem::setExtendedStrut(widget.winId(), 10, 0, 10, 0, 0, 0, 100, 0, 100, 0, 0, 0); QVERIFY(spy.wait()); QVERIFY(!spy.isEmpty()); QVERIFY(!strutSpy.isEmpty()); } void KWindowSystemX11Test::testWindowTitleChanged() { qRegisterMetaType("WId"); qRegisterMetaType("NET::Properties"); qRegisterMetaType("NET::Properties2"); qRegisterMetaType("const ulong*"); QWidget widget; widget.setWindowTitle(QStringLiteral("foo")); widget.show(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); // wait till the window is mapped, etc. QTest::qWait(200); QSignalSpy propertiesChangedSpy(KWindowSystem::self(), SIGNAL(windowChanged(WId,NET::Properties,NET::Properties2))); - QSignalSpy propertyChangedSpy(KWindowSystem::self(), SIGNAL(windowChanged(WId,uint))); - QSignalSpy windowChangedSpy(KWindowSystem::self(), SIGNAL(windowChanged(WId))); - QVERIFY(propertiesChangedSpy.isValid()); +#ifndef KWINDOWSYSTEM_NO_DEPRECATED + QSignalSpy propertyChangedSpy(KWindowSystem::self(), SIGNAL(windowChanged(WId,uint))); QVERIFY(propertyChangedSpy.isValid()); +#endif + QSignalSpy windowChangedSpy(KWindowSystem::self(), SIGNAL(windowChanged(WId))); QVERIFY(windowChangedSpy.isValid()); widget.setWindowTitle(QStringLiteral("bar")); QX11Info::setAppTime(QX11Info::getTimestamp()); int counter = 0; bool gotWMName = false; while (propertiesChangedSpy.wait() && counter < 10) { for (auto it = propertiesChangedSpy.constBegin(); it != propertiesChangedSpy.constEnd(); ++it) { if ((*it).isEmpty()) { continue; } if ((*it).at(0).toULongLong() == widget.winId()) { NET::Properties props = (*it).at(1).value(); if (props.testFlag(NET::WMName)) { gotWMName = true; } } } if (gotWMName) { break; } propertiesChangedSpy.clear(); counter++; } QVERIFY(gotWMName); +#ifndef KWINDOWSYSTEM_NO_DEPRECATED gotWMName = false; QCOMPARE(propertyChangedSpy.isEmpty(), false); for (auto it = propertyChangedSpy.constBegin(); it != propertyChangedSpy.constEnd(); ++it) { if ((*it).isEmpty()) { continue; } if ((*it).at(0).toULongLong() == widget.winId()) { unsigned int props = (*it).at(1).value(); if (props & NET::WMName) { gotWMName = true; } } if (gotWMName) { break; } } QVERIFY(gotWMName); +#endif QCOMPARE(windowChangedSpy.isEmpty(), false); bool gotWindow = false; for (auto it = windowChangedSpy.constBegin(); it != windowChangedSpy.constEnd(); ++it) { if ((*it).isEmpty()) { continue; } if ((*it).at(0).toULongLong() == widget.winId()) { gotWindow = true; } if (gotWindow) { break; } } QVERIFY(gotWindow); // now let's verify the info in KWindowInfo // we wait a little bit more as openbox is updating the visible name QTest::qWait(500); KWindowInfo info(widget.winId(), NET::WMName | NET::WMVisibleName | NET::WMVisibleIconName | NET::WMIconName, NET::Properties2()); QVERIFY(info.valid()); const QString expectedName = QStringLiteral("bar"); QCOMPARE(info.name(), expectedName); QCOMPARE(info.visibleName(), expectedName); QCOMPARE(info.visibleIconName(), expectedName); QCOMPARE(info.iconName(), expectedName); } void KWindowSystemX11Test::testMinimizeWindow() { NETRootInfo rootInfo(QX11Info::connection(), NET::Supported | NET::SupportingWMCheck); if (qstrcmp(rootInfo.wmName(), "Openbox") != 0 && qstrcmp(rootInfo.wmName(), "KWin") != 0) { QSKIP("Test minimize window might not be supported on the used window manager."); } QWidget widget; widget.show(); QVERIFY(QTest::qWaitForWindowExposed(&widget)); KWindowInfo info(widget.winId(), NET::WMState | NET::XAWMState); QVERIFY(!info.isMinimized()); KWindowSystem::minimizeWindow(widget.winId()); // create a roundtrip, updating minimized state is done by the window manager and wait a short time QX11Info::setAppTime(QX11Info::getTimestamp()); QTest::qWait(200); KWindowInfo info2(widget.winId(), NET::WMState | NET::XAWMState); QVERIFY(info2.isMinimized()); KWindowSystem::unminimizeWindow(widget.winId()); // create a roundtrip, updating minimized state is done by the window manager and wait a short time QX11Info::setAppTime(QX11Info::getTimestamp()); QTest::qWait(200); KWindowInfo info3(widget.winId(), NET::WMState | NET::XAWMState); QVERIFY(!info3.isMinimized()); } void KWindowSystemX11Test::testPlatformX11() { QCOMPARE(KWindowSystem::platform(), KWindowSystem::Platform::X11); QCOMPARE(KWindowSystem::isPlatformX11(), true); QCOMPARE(KWindowSystem::isPlatformWayland(), false); } QTEST_MAIN(KWindowSystemX11Test) #include "kwindowsystemx11test.moc" diff --git a/autotests/kxmessages_unittest.cpp b/autotests/kxmessages_unittest.cpp index 1b6aa9d..db3c5a8 100644 --- a/autotests/kxmessages_unittest.cpp +++ b/autotests/kxmessages_unittest.cpp @@ -1,110 +1,118 @@ /* This file is part of the KDE libraries Copyright 2012 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include class KXMessages_UnitTest : public QObject { Q_OBJECT public: enum BroadcastType { BroadcastMessageObject, +#ifndef KWINDOWSYSTEM_NO_DEPRECATED BroadcastStaticDisplay, +#endif BroadcastStaticConnection }; enum ReceiverType { ReceiverTypeDefault, ReceiverTypeConnection }; KXMessages_UnitTest() : m_msgs() { } private Q_SLOTS: void testStart_data(); void testStart(); private: KXMessages m_msgs; }; Q_DECLARE_METATYPE(KXMessages_UnitTest::BroadcastType) Q_DECLARE_METATYPE(KXMessages_UnitTest::ReceiverType) void KXMessages_UnitTest::testStart_data() { QTest::addColumn("broadcastType"); QTest::addColumn("receiverType"); QTest::newRow("object") << BroadcastMessageObject << ReceiverTypeDefault; +#ifndef KWINDOWSYSTEM_NO_DEPRECATED QTest::newRow("display") << BroadcastStaticDisplay << ReceiverTypeDefault; +#endif QTest::newRow("connection") << BroadcastStaticConnection << ReceiverTypeDefault; QTest::newRow("object/xcb") << BroadcastMessageObject << ReceiverTypeConnection; +#ifndef KWINDOWSYSTEM_NO_DEPRECATED QTest::newRow("display/xcb") << BroadcastStaticDisplay << ReceiverTypeConnection; +#endif QTest::newRow("connection/xcb") << BroadcastStaticConnection << ReceiverTypeConnection; } void KXMessages_UnitTest::testStart() { QFETCH(KXMessages_UnitTest::BroadcastType, broadcastType); QFETCH(KXMessages_UnitTest::ReceiverType, receiverType); const QByteArray type = "kxmessage_unittest"; QScopedPointer receiver; switch (receiverType) { case KXMessages_UnitTest::ReceiverTypeDefault: receiver.reset(new KXMessages(type)); break; case KXMessages_UnitTest::ReceiverTypeConnection: receiver.reset(new KXMessages(QX11Info::connection(), QX11Info::appRootWindow(), type)); break; default: Q_UNREACHABLE(); break; } // Check that all message sizes work, i.e. no bug when exactly 20 or 40 bytes, // despite the internal splitting. QString message; for (int i = 1; i < 50; ++i) { QSignalSpy spy(receiver.data(), SIGNAL(gotMessage(QString))); message += "a"; switch (broadcastType) { case KXMessages_UnitTest::BroadcastMessageObject: m_msgs.broadcastMessage(type, message); break; +#ifndef KWINDOWSYSTEM_NO_DEPRECATED case KXMessages_UnitTest::BroadcastStaticDisplay: QVERIFY(KXMessages::broadcastMessageX(QX11Info::display(), type.constData(), message)); break; +#endif case KXMessages_UnitTest::BroadcastStaticConnection: QVERIFY(KXMessages::broadcastMessageX(QX11Info::connection(), type.constData(), message, QX11Info::appScreen())); break; } QVERIFY(spy.wait()); QCOMPARE(spy.count(), 1); QCOMPARE(spy.at(0).at(0).toString(), message); } } QTEST_MAIN(KXMessages_UnitTest) #include "kxmessages_unittest.moc" diff --git a/src/kstartupinfo.cpp b/src/kstartupinfo.cpp index ea40194..a4a819f 100644 --- a/src/kstartupinfo.cpp +++ b/src/kstartupinfo.cpp @@ -1,1674 +1,1674 @@ /**************************************************************************** Copyright (C) 2001-2003 Lubos Lunak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ // qDebug() can't be turned off in kdeinit #if 0 #define KSTARTUPINFO_ALL_DEBUG #ifdef __GNUC__ #warning Extra KStartupInfo debug messages enabled. #endif #endif #ifdef QT_NO_CAST_FROM_ASCII #undef QT_NO_CAST_FROM_ASCII #endif #include "kstartupinfo.h" #include "netwm_def.h" #include "kwindowsystem_debug.h" +#ifndef KWINDOWSYSTEM_NO_DEPRECATED #include +#endif #include #include // KWINDOWSYSTEM_HAVE_X11 // need to resolve INT32(qglobal.h)<>INT32(Xlibint.h) conflict #ifndef QT_CLEAN_NAMESPACE #define QT_CLEAN_NAMESPACE #endif #ifndef Q_OS_WIN #include #include #else #include #include #endif #include #include #include #if KWINDOWSYSTEM_HAVE_X11 #include #include #endif -#include +#include #include #include #include #if KWINDOWSYSTEM_HAVE_X11 #include #include #include #include #endif static const char NET_STARTUP_MSG[] = "_NET_STARTUP_INFO"; static const char NET_STARTUP_WINDOW[] = "_NET_STARTUP_ID"; // DESKTOP_STARTUP_ID is used also in kinit/wrapper.c , // kdesu in both kdelibs and kdebase and who knows where else static const char NET_STARTUP_ENV[] = "DESKTOP_STARTUP_ID"; static QByteArray s_startup_id; static long get_num(const QString &item_P); static QString get_str(const QString &item_P); static QByteArray get_cstr(const QString &item_P); static QStringList get_fields(const QString &txt_P); static QString escape_str(const QString &str_P); class Q_DECL_HIDDEN KStartupInfo::Data : public KStartupInfoData { public: Data() : age(0) {} // just because it's in a QMap Data(const QString &txt_P) : KStartupInfoData(txt_P), age(0) {} unsigned int age; }; struct Q_DECL_HIDDEN KStartupInfoId::Private { Private() : id("") {} QString to_text() const; QByteArray id; // id }; struct Q_DECL_HIDDEN KStartupInfoData::Private { Private() : desktop(0), wmclass(""), hostname(""), silent(KStartupInfoData::Unknown), screen(-1), xinerama(-1), launched_by(0) {} QString to_text() const; void remove_pid(pid_t pid); QString bin; QString name; QString description; QString icon; int desktop; QList< pid_t > pids; QByteArray wmclass; QByteArray hostname; KStartupInfoData::TriState silent; int screen; int xinerama; WId launched_by; QString application_id; }; class Q_DECL_HIDDEN KStartupInfo::Private { public: // private slots void startups_cleanup(); void startups_cleanup_no_age(); void got_message(const QString &msg); void window_added(WId w); void slot_window_added(WId w); void init(int flags); void got_startup_info(const QString &msg_P, bool update_only_P); void got_remove_startup_info(const QString &msg_P); void new_startup_info_internal(const KStartupInfoId &id_P, Data &data_P, bool update_only_P); void removeAllStartupInfoInternal(const KStartupInfoId &id_P); /** * Emits the gotRemoveStartup signal and erases the @p it from the startups map. * @returns Iterator to next item in the startups map. **/ QMap< KStartupInfoId, Data >::iterator removeStartupInfoInternal(QMap< KStartupInfoId, Data >::iterator it); void remove_startup_pids(const KStartupInfoId &id, const KStartupInfoData &data); void remove_startup_pids(const KStartupInfoData &data); startup_t check_startup_internal(WId w, KStartupInfoId *id, KStartupInfoData *data); bool find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O); bool find_pid(pid_t pid_P, const QByteArray &hostname, KStartupInfoId *id_O, KStartupInfoData *data_O); bool find_wclass(const QByteArray &res_name_P, const QByteArray &res_class_P, KStartupInfoId *id_O, KStartupInfoData *data_O); void startups_cleanup_internal(bool age_P); void clean_all_noncompliant(); static QString check_required_startup_fields(const QString &msg, const KStartupInfoData &data, int screen); KStartupInfo *q; unsigned int timeout; QMap< KStartupInfoId, KStartupInfo::Data > startups; // contains silenced ASN's only if !AnnounceSilencedChanges QMap< KStartupInfoId, KStartupInfo::Data > silent_startups; // contains ASN's that had change: but no new: yet QMap< KStartupInfoId, KStartupInfo::Data > uninited_startups; #if KWINDOWSYSTEM_HAVE_X11 KXMessages msgs; #endif QTimer *cleanup; int flags; Private(int flags_P, KStartupInfo *q) : q(q), timeout(60), #if KWINDOWSYSTEM_HAVE_X11 msgs(NET_STARTUP_MSG), #endif cleanup(nullptr), flags(flags_P) { } void createConnections() { #if KWINDOWSYSTEM_HAVE_X11 // d == nullptr means "disabled" if (!QX11Info::isPlatformX11() || !QX11Info::display()) { return; } if (!(flags & DisableKWinModule)) { QObject::connect(KWindowSystem::self(), SIGNAL(windowAdded(WId)), q, SLOT(slot_window_added(WId))); #ifdef __GNUC__ #warning "systemTrayWindowAdded signal was remove from KWindowSystem class" #endif //QObject::connect( KWindowSystem::self(), SIGNAL(systemTrayWindowAdded(WId)), q, SLOT(slot_window_added(WId))); } QObject::connect(&msgs, SIGNAL(gotMessage(QString)), q, SLOT(got_message(QString))); cleanup = new QTimer(q); QObject::connect(cleanup, SIGNAL(timeout()), q, SLOT(startups_cleanup())); #endif } }; KStartupInfo::KStartupInfo(int flags_P, QObject *parent_P) : QObject(parent_P), d(new Private(flags_P, this)) { d->createConnections(); } #ifndef KWINDOWSYSTEM_NO_DEPRECATED KStartupInfo::KStartupInfo(bool clean_on_cantdetect_P, QObject *parent_P) : QObject(parent_P), d(new Private(clean_on_cantdetect_P ? CleanOnCantDetect : 0, this)) { d->createConnections(); } #endif KStartupInfo::~KStartupInfo() { delete d; } void KStartupInfo::Private::got_message(const QString &msg_P) { #if KWINDOWSYSTEM_HAVE_X11 // TODO do something with SCREEN= ? //qCDebug(LOG_KWINDOWSYSTEM) << "got:" << msg_P; QString msg = msg_P.trimmed(); if (msg.startsWith(QLatin1String("new:"))) { // must match length below got_startup_info(msg.mid(4), false); } else if (msg.startsWith(QLatin1String("change:"))) { // must match length below got_startup_info(msg.mid(7), true); } else if (msg.startsWith(QLatin1String("remove:"))) { // must match length below got_remove_startup_info(msg.mid(7)); } #else Q_UNUSED(msg_P) #endif } // if the application stops responding for a while, KWindowSystem may get // the information about the already mapped window before KXMessages // actually gets the info about the started application (depends // on their order in the native x11 event filter) // simply delay info from KWindowSystem a bit // SELI??? namespace { class DelayedWindowEvent : public QEvent { public: DelayedWindowEvent(WId w_P) : QEvent(uniqueType()), w(w_P) {} #if KWINDOWSYSTEM_HAVE_X11 Window w; #else WId w; #endif static Type uniqueType() { return Type(QEvent::User + 15); } }; } void KStartupInfo::Private::slot_window_added(WId w_P) { qApp->postEvent(q, new DelayedWindowEvent(w_P)); } void KStartupInfo::customEvent(QEvent *e_P) { #if KWINDOWSYSTEM_HAVE_X11 if (e_P->type() == DelayedWindowEvent::uniqueType()) { d->window_added(static_cast< DelayedWindowEvent * >(e_P)->w); } else #endif QObject::customEvent(e_P); } void KStartupInfo::Private::window_added(WId w_P) { KStartupInfoId id; KStartupInfoData data; startup_t ret = check_startup_internal(w_P, &id, &data); switch (ret) { case Match: //qCDebug(LOG_KWINDOWSYSTEM) << "new window match"; break; case NoMatch: break; // nothing case CantDetect: if (flags & CleanOnCantDetect) { clean_all_noncompliant(); } break; } } void KStartupInfo::Private::got_startup_info(const QString &msg_P, bool update_P) { KStartupInfoId id(msg_P); if (id.isNull()) { return; } KStartupInfo::Data data(msg_P); new_startup_info_internal(id, data, update_P); } void KStartupInfo::Private::new_startup_info_internal(const KStartupInfoId &id_P, KStartupInfo::Data &data_P, bool update_P) { if (id_P.isNull()) { return; } if (startups.contains(id_P)) { // already reported, update startups[ id_P ].update(data_P); startups[ id_P ].age = 0; // CHECKME //qCDebug(LOG_KWINDOWSYSTEM) << "updating"; if (startups[ id_P ].silent() == KStartupInfo::Data::Yes && !(flags & AnnounceSilenceChanges)) { silent_startups[ id_P ] = startups[ id_P ]; startups.remove(id_P); emit q->gotRemoveStartup(id_P, silent_startups[ id_P ]); return; } emit q->gotStartupChange(id_P, startups[ id_P ]); return; } if (silent_startups.contains(id_P)) { // already reported, update silent_startups[ id_P ].update(data_P); silent_startups[ id_P ].age = 0; // CHECKME //qCDebug(LOG_KWINDOWSYSTEM) << "updating silenced"; if (silent_startups[ id_P ].silent() != Data::Yes) { startups[ id_P ] = silent_startups[ id_P ]; silent_startups.remove(id_P); q->emit gotNewStartup(id_P, startups[ id_P ]); return; } emit q->gotStartupChange(id_P, silent_startups[ id_P ]); return; } if (uninited_startups.contains(id_P)) { uninited_startups[ id_P ].update(data_P); //qCDebug(LOG_KWINDOWSYSTEM) << "updating uninited"; if (!update_P) { // uninited finally got new: startups[ id_P ] = uninited_startups[ id_P ]; uninited_startups.remove(id_P); emit q->gotNewStartup(id_P, startups[ id_P ]); return; } // no change announce, it's still uninited return; } if (update_P) { // change: without any new: first //qCDebug(LOG_KWINDOWSYSTEM) << "adding uninited"; uninited_startups.insert(id_P, data_P); } else if (data_P.silent() != Data::Yes || flags & AnnounceSilenceChanges) { //qCDebug(LOG_KWINDOWSYSTEM) << "adding"; startups.insert(id_P, data_P); emit q->gotNewStartup(id_P, data_P); } else { // new silenced, and silent shouldn't be announced //qCDebug(LOG_KWINDOWSYSTEM) << "adding silent"; silent_startups.insert(id_P, data_P); } cleanup->start(1000); // 1 sec } void KStartupInfo::Private::got_remove_startup_info(const QString &msg_P) { KStartupInfoId id(msg_P); KStartupInfoData data(msg_P); if (!data.pids().isEmpty()) { if (!id.isNull()) { remove_startup_pids(id, data); } else { remove_startup_pids(data); } return; } removeAllStartupInfoInternal(id); } void KStartupInfo::Private::removeAllStartupInfoInternal(const KStartupInfoId &id_P) { auto it = startups.find(id_P); if (it != startups.end()) { //qCDebug(LOG_KWINDOWSYSTEM) << "removing"; emit q->gotRemoveStartup(it.key(), it.value()); startups.erase(it); return; } it = silent_startups.find(id_P); if (it != silent_startups.end()) { silent_startups.erase(it); return; } it = uninited_startups.find(id_P); if (it != uninited_startups.end()) { uninited_startups.erase(it); } } QMap< KStartupInfoId, KStartupInfo::Data >::iterator KStartupInfo::Private::removeStartupInfoInternal(QMap< KStartupInfoId, Data >::iterator it) { emit q->gotRemoveStartup(it.key(), it.value()); return startups.erase(it); } void KStartupInfo::Private::remove_startup_pids(const KStartupInfoData &data_P) { // first find the matching info for (QMap< KStartupInfoId, KStartupInfo::Data >::Iterator it = startups.begin(); it != startups.end(); ++it) { if ((*it).hostname() != data_P.hostname()) { continue; } if (!(*it).is_pid(data_P.pids().first())) { continue; // not the matching info } remove_startup_pids(it.key(), data_P); break; } } void KStartupInfo::Private::remove_startup_pids(const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (data_P.pids().isEmpty()) { qFatal("data_P.pids().isEmpty()"); } Data *data = nullptr; if (startups.contains(id_P)) { data = &startups[ id_P ]; } else if (silent_startups.contains(id_P)) { data = &silent_startups[ id_P ]; } else if (uninited_startups.contains(id_P)) { data = &uninited_startups[ id_P ]; } else { return; } const auto pids = data_P.pids(); for (auto pid : pids) { data->d->remove_pid(pid); // remove all pids from the info } if (data->pids().isEmpty()) { // all pids removed -> remove info removeAllStartupInfoInternal(id_P); } } bool KStartupInfo::sendStartup(const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 return sendStartupXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P); #else Q_UNUSED(data_P) #endif return true; } #ifndef KWINDOWSYSTEM_NO_DEPRECATED bool KStartupInfo::sendStartupX(Display *disp_P, const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("new: %1 %2") .arg(id_P.d->to_text(), data_P.d->to_text()); msg = Private::check_required_startup_fields(msg, data_P, DefaultScreen(disp_P)); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) Q_UNUSED(data_P) return true; #endif } #endif bool KStartupInfo::sendStartupXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("new: %1 %2") .arg(id_P.d->to_text(), data_P.d->to_text()); msg = Private::check_required_startup_fields(msg, data_P, screen); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); #else Q_UNUSED(conn) Q_UNUSED(screen) Q_UNUSED(data_P) return true; #endif } QString KStartupInfo::Private::check_required_startup_fields(const QString &msg, const KStartupInfoData &data_P, int screen) { QString ret = msg; if (data_P.name().isEmpty()) { // qWarning() << "NAME not specified in initial startup message"; QString name = data_P.bin(); if (name.isEmpty()) { name = "UNKNOWN"; } ret += QString(" NAME=\"%1\"").arg(escape_str(name)); } if (data_P.screen() == -1) { // add automatically if needed ret += QString(" SCREEN=%1").arg(screen); } return ret; } bool KStartupInfo::sendChange(const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 return sendChangeXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P); #else Q_UNUSED(data_P) #endif return true; } #ifndef KWINDOWSYSTEM_NO_DEPRECATED bool KStartupInfo::sendChangeX(Display *disp_P, const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("change: %1 %2") .arg(id_P.d->to_text(), data_P.d->to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) Q_UNUSED(data_P) return true; #endif } #endif bool KStartupInfo::sendChangeXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("change: %1 %2") .arg(id_P.d->to_text(), data_P.d->to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); #else Q_UNUSED(conn) Q_UNUSED(screen) Q_UNUSED(data_P) return true; #endif } bool KStartupInfo::sendFinish(const KStartupInfoId &id_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P); #endif return true; } #ifndef KWINDOWSYSTEM_NO_DEPRECATED bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("remove: %1").arg(id_P.d->to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) return true; #endif } #endif bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P) { if (id_P.isNull()) { return false; } #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("remove: %1").arg(id_P.d->to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); #else Q_UNUSED(conn) Q_UNUSED(screen) return true; #endif } bool KStartupInfo::sendFinish(const KStartupInfoId &id_P, const KStartupInfoData &data_P) { // if( id_P.isNull()) // id may be null, the pids and hostname matter then // return false; #if KWINDOWSYSTEM_HAVE_X11 return sendFinishXcb(QX11Info::connection(), QX11Info::appScreen(), id_P, data_P); #else Q_UNUSED(id_P) Q_UNUSED(data_P) #endif return true; } #ifndef KWINDOWSYSTEM_NO_DEPRECATED bool KStartupInfo::sendFinishX(Display *disp_P, const KStartupInfoId &id_P, const KStartupInfoData &data_P) { // if( id_P.isNull()) // id may be null, the pids and hostname matter then // return false; #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("remove: %1 %2") .arg(id_P.d->to_text(), data_P.d->to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(disp_P, NET_STARTUP_MSG, msg); #else Q_UNUSED(disp_P) Q_UNUSED(id_P) Q_UNUSED(data_P) return true; #endif } #endif bool KStartupInfo::sendFinishXcb(xcb_connection_t *conn, int screen, const KStartupInfoId &id_P, const KStartupInfoData &data_P) { // if( id_P.isNull()) // id may be null, the pids and hostname matter then // return false; #if KWINDOWSYSTEM_HAVE_X11 QString msg = QString::fromLatin1("remove: %1 %2") .arg(id_P.d->to_text(), data_P.d->to_text()); #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "sending " << msg; #endif return KXMessages::broadcastMessageX(conn, NET_STARTUP_MSG, msg, screen); #else Q_UNUSED(conn) Q_UNUSED(screen) Q_UNUSED(id_P) Q_UNUSED(data_P) return true; #endif } void KStartupInfo::appStarted() { appStarted(startupId()); setStartupId("0"); // reset the id, no longer valid (must use clearStartupId() to avoid infinite loop) } void KStartupInfo::appStarted(const QByteArray &startup_id) { KStartupInfoId id; id.initId(startup_id); if (id.isNull()) { return; } #if KWINDOWSYSTEM_HAVE_X11 if (QX11Info::isPlatformX11() && !qEnvironmentVariableIsEmpty("DISPLAY")) { // don't rely on QX11Info::display() - Display *disp = XOpenDisplay(nullptr); - if (disp != nullptr) { - KStartupInfo::sendFinishX(disp, id); - XCloseDisplay(disp); - } + KStartupInfo::sendFinish(id); } #endif } void KStartupInfo::silenceStartup(bool silence) { KStartupInfoId id; id.initId(startupId()); if (id.isNull()) { return; } KStartupInfoData data; data.setSilent(silence ? KStartupInfoData::Yes : KStartupInfoData::No); sendChange(id, data); } QByteArray KStartupInfo::startupId() { if (s_startup_id.isEmpty()) { KStartupInfoId id = currentStartupIdEnv(); resetStartupEnv(); s_startup_id = id.id(); } return s_startup_id; } void KStartupInfo::setStartupId(const QByteArray &startup_id) { if (startup_id == startupId()) { return; } if (startup_id.isEmpty()) { s_startup_id = "0"; } else { s_startup_id = startup_id; #if KWINDOWSYSTEM_HAVE_X11 if (QX11Info::isPlatformX11()) { KStartupInfoId id; id.initId(startup_id); long timestamp = id.timestamp(); if (timestamp != 0) { if (QX11Info::appUserTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appUserTime()) > 0) { // time > appUserTime QX11Info::setAppUserTime(timestamp); } if (QX11Info::appTime() == 0 || NET::timestampCompare(timestamp, QX11Info::appTime()) > 0) { // time > appTime QX11Info::setAppTime(timestamp); } } } #endif } } +#ifndef KWINDOWSYSTEM_NO_DEPRECATED void KStartupInfo::setNewStartupId(QWidget *window, const QByteArray &startup_id) { // Set the WA_NativeWindow attribute to force the creation of the QWindow. // Without this QWidget::windowHandle() returns 0. window->setAttribute(Qt::WA_NativeWindow, true); setNewStartupId(window->window()->windowHandle(), startup_id); } +#endif void KStartupInfo::setNewStartupId(QWindow *window, const QByteArray &startup_id) { Q_ASSERT(window); setStartupId(startup_id); #if KWINDOWSYSTEM_HAVE_X11 bool activate = true; if (window != nullptr && QX11Info::isPlatformX11()) { if (!startup_id.isEmpty() && startup_id != "0") { NETRootInfo i(QX11Info::connection(), NET::Supported); if (i.isSupported(NET::WM2StartupId)) { KStartupInfo::setWindowStartupId(window->winId(), startup_id); activate = false; // WM will take care of it } } if (activate) { KWindowSystem::setOnDesktop(window->winId(), KWindowSystem::currentDesktop()); // This is not very nice, but there's no way how to get any // usable timestamp without ASN, so force activating the window. // And even with ASN, it's not possible to get the timestamp here, // so if the WM doesn't have support for ASN, it can't be used either. KWindowSystem::forceActiveWindow(window->winId()); } } #else Q_UNUSED(window) #endif } KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O, KStartupInfoData &data_O) { return d->check_startup_internal(w_P, &id_O, &data_O); } KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoId &id_O) { return d->check_startup_internal(w_P, &id_O, nullptr); } KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P, KStartupInfoData &data_O) { return d->check_startup_internal(w_P, nullptr, &data_O); } KStartupInfo::startup_t KStartupInfo::checkStartup(WId w_P) { return d->check_startup_internal(w_P, nullptr, nullptr); } KStartupInfo::startup_t KStartupInfo::Private::check_startup_internal(WId w_P, KStartupInfoId *id_O, KStartupInfoData *data_O) { if (startups.isEmpty()) { return NoMatch; // no startups } // Strategy: // // Is this a compliant app ? // - Yes - test for match // - No - Is this a NET_WM compliant app ? // - Yes - test for pid match // - No - test for WM_CLASS match qCDebug(LOG_KWINDOWSYSTEM) << "check_startup"; QByteArray id = windowStartupId(w_P); if (!id.isNull()) { if (id.isEmpty() || id == "0") { // means ignore this window qCDebug(LOG_KWINDOWSYSTEM) << "ignore"; return NoMatch; } return find_id(id, id_O, data_O) ? Match : NoMatch; } #if KWINDOWSYSTEM_HAVE_X11 if (!QX11Info::isPlatformX11()) { qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect"; return CantDetect; } NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::WMWindowType | NET::WMPid | NET::WMState, NET::WM2WindowClass | NET::WM2ClientMachine | NET::WM2TransientFor); pid_t pid = info.pid(); if (pid > 0) { QByteArray hostname = info.clientMachine(); if (!hostname.isEmpty() && find_pid(pid, hostname, id_O, data_O)) { return Match; } // try XClass matching , this PID stuff sucks :( } if (find_wclass(info.windowClassName(), info.windowClassClass(), id_O, data_O)) { return Match; } // ignore NET::Tool and other special window types, if they can't be matched NET::WindowType type = info.windowType(NET::NormalMask | NET::DesktopMask | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask); if (type != NET::Normal && type != NET::Override && type != NET::Unknown && type != NET::Dialog && type != NET::Utility) // && type != NET::Dock ) why did I put this here? { return NoMatch; } // lets see if this is a transient xcb_window_t transient_for = info.transientFor(); if (transient_for != QX11Info::appRootWindow() && transient_for != XCB_WINDOW_NONE) { return NoMatch; } #endif qCDebug(LOG_KWINDOWSYSTEM) << "check_startup:cantdetect"; return CantDetect; } bool KStartupInfo::Private::find_id(const QByteArray &id_P, KStartupInfoId *id_O, KStartupInfoData *data_O) { //qCDebug(LOG_KWINDOWSYSTEM) << "find_id:" << id_P; KStartupInfoId id; id.initId(id_P); if (startups.contains(id)) { if (id_O != nullptr) { *id_O = id; } if (data_O != nullptr) { *data_O = startups[ id ]; } //qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_id:match"; return true; } return false; } bool KStartupInfo::Private::find_pid(pid_t pid_P, const QByteArray &hostname_P, KStartupInfoId *id_O, KStartupInfoData *data_O) { //qCDebug(LOG_KWINDOWSYSTEM) << "find_pid:" << pid_P; for (QMap< KStartupInfoId, KStartupInfo::Data >::Iterator it = startups.begin(); it != startups.end(); ++it) { if ((*it).is_pid(pid_P) && (*it).hostname() == hostname_P) { // Found it ! if (id_O != nullptr) { *id_O = it.key(); } if (data_O != nullptr) { *data_O = *it; } // non-compliant, remove on first match removeStartupInfoInternal(it); //qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_pid:match"; return true; } } return false; } bool KStartupInfo::Private::find_wclass(const QByteArray &_res_name, const QByteArray &_res_class, KStartupInfoId *id_O, KStartupInfoData *data_O) { QByteArray res_name = _res_name.toLower(); QByteArray res_class = _res_class.toLower(); //qCDebug(LOG_KWINDOWSYSTEM) << "find_wclass:" << res_name << ":" << res_class; for (QMap< KStartupInfoId, Data >::Iterator it = startups.begin(); it != startups.end(); ++it) { const QByteArray wmclass = (*it).findWMClass(); if (wmclass.toLower() == res_name || wmclass.toLower() == res_class) { // Found it ! if (id_O != nullptr) { *id_O = it.key(); } if (data_O != nullptr) { *data_O = *it; } // non-compliant, remove on first match removeStartupInfoInternal(it); //qCDebug(LOG_KWINDOWSYSTEM) << "check_startup_wclass:match"; return true; } } return false; } QByteArray KStartupInfo::windowStartupId(WId w_P) { #if KWINDOWSYSTEM_HAVE_X11 if (!QX11Info::isPlatformX11()) { return QByteArray(); } NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::WM2StartupId | NET::WM2GroupLeader); QByteArray ret = info.startupId(); if (ret.isEmpty() && info.groupLeader() != XCB_WINDOW_NONE) { // retry with window group leader, as the spec says NETWinInfo groupLeaderInfo(QX11Info::connection(), info.groupLeader(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); ret = groupLeaderInfo.startupId(); } return ret; #else Q_UNUSED(w_P) return QByteArray(); #endif } void KStartupInfo::setWindowStartupId(WId w_P, const QByteArray &id_P) { #if KWINDOWSYSTEM_HAVE_X11 if (!QX11Info::isPlatformX11()) { return; } if (id_P.isNull()) { return; } NETWinInfo info(QX11Info::connection(), w_P, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); info.setStartupId(id_P.constData()); #else Q_UNUSED(w_P) Q_UNUSED(id_P) #endif } void KStartupInfo::setTimeout(unsigned int secs_P) { d->timeout = secs_P; // schedule removing entries that are older than the new timeout QTimer::singleShot(0, this, SLOT(startups_cleanup_no_age())); } void KStartupInfo::Private::startups_cleanup_no_age() { startups_cleanup_internal(false); } void KStartupInfo::Private::startups_cleanup() { if (startups.isEmpty() && silent_startups.isEmpty() && uninited_startups.isEmpty()) { cleanup->stop(); return; } startups_cleanup_internal(true); } void KStartupInfo::Private::startups_cleanup_internal(bool age_P) { auto checkCleanup = [this, age_P](QMap &s, bool doEmit) { auto it = s.begin(); while (it != s.end()) { if (age_P) { (*it).age++; } unsigned int tout = timeout; if ((*it).silent() == KStartupInfo::Data::Yes) { // give kdesu time to get a password tout *= 20; } const QByteArray timeoutEnvVariable = qgetenv("KSTARTUPINFO_TIMEOUT"); if (!timeoutEnvVariable.isNull()) { tout = timeoutEnvVariable.toUInt(); } if ((*it).age >= tout) { if (doEmit) { emit q->gotRemoveStartup(it.key(), it.value()); } it = s.erase(it); } else { ++it; } } }; checkCleanup(startups, true); checkCleanup(silent_startups, false); checkCleanup(uninited_startups, false); } void KStartupInfo::Private::clean_all_noncompliant() { for (QMap< KStartupInfoId, KStartupInfo::Data >::Iterator it = startups.begin(); it != startups.end(); ) { if ((*it).WMClass() != "0") { ++it; continue; } it = removeStartupInfoInternal(it); } } QByteArray KStartupInfo::createNewStartupId() { quint32 timestamp = 0; #if KWINDOWSYSTEM_HAVE_X11 if (QX11Info::isPlatformX11()) { timestamp = QX11Info::getTimestamp(); } #endif return KStartupInfo::createNewStartupIdForTimestamp(timestamp); } QByteArray KStartupInfo::createNewStartupIdForTimestamp(quint32 timestamp) { // Assign a unique id, use hostname+time+pid, that should be 200% unique. // Also append the user timestamp (for focus stealing prevention). struct timeval tm; #ifdef Q_OS_WIN //on windows only msecs accuracy instead of usecs like with gettimeofday //XXX: use Win API to get better accuracy qint64 msecsSinceEpoch = QDateTime::currentMSecsSinceEpoch(); tm.tv_sec = msecsSinceEpoch / 1000; tm.tv_usec = (msecsSinceEpoch % 1000) * 1000; #else gettimeofday(&tm, nullptr); #endif char hostname[ 256 ]; hostname[ 0 ] = '\0'; if (!gethostname(hostname, 255)) { hostname[sizeof(hostname) - 1] = '\0'; } QByteArray id = QString::fromLatin1("%1;%2;%3;%4_TIME%5").arg(hostname).arg(tm.tv_sec) .arg(tm.tv_usec).arg(getpid()).arg(timestamp).toUtf8(); //qCDebug(LOG_KWINDOWSYSTEM) << "creating: " << id << ":" << (qApp ? qAppName() : QString("unnamed app") /* e.g. kdeinit */); return id; } const QByteArray &KStartupInfoId::id() const { return d->id; } QString KStartupInfoId::Private::to_text() const { return QString::fromLatin1(" ID=\"%1\" ").arg(escape_str(id)); } KStartupInfoId::KStartupInfoId(const QString &txt_P) : d(new Private) { const QStringList items = get_fields(txt_P); const QString id_str = QLatin1String("ID="); for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) { if ((*it).startsWith(id_str)) { d->id = get_cstr(*it); } } } void KStartupInfoId::initId(const QByteArray &id_P) { if (!id_P.isEmpty()) { d->id = id_P; #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "using: " << d->id; #endif return; } const QByteArray startup_env = qgetenv(NET_STARTUP_ENV); if (!startup_env.isEmpty()) { // already has id d->id = startup_env; #ifdef KSTARTUPINFO_ALL_DEBUG qCDebug(LOG_KWINDOWSYSTEM) << "reusing: " << d->id; #endif return; } d->id = KStartupInfo::createNewStartupId(); } bool KStartupInfoId::setupStartupEnv() const { if (isNull()) { qunsetenv(NET_STARTUP_ENV); return false; } return ! qputenv(NET_STARTUP_ENV, id()) == 0; } KStartupInfoId KStartupInfo::currentStartupIdEnv() { const QByteArray startup_env = qgetenv(NET_STARTUP_ENV); KStartupInfoId id; if (!startup_env.isEmpty()) { id.d->id = startup_env; } else { id.d->id = "0"; } return id; } void KStartupInfo::resetStartupEnv() { qunsetenv(NET_STARTUP_ENV); } KStartupInfoId::KStartupInfoId() : d(new Private) { } KStartupInfoId::~KStartupInfoId() { delete d; } KStartupInfoId::KStartupInfoId(const KStartupInfoId &id_P) : d(new Private(*id_P.d)) { } KStartupInfoId &KStartupInfoId::operator=(const KStartupInfoId &id_P) { if (&id_P == this) { return *this; } *d = *id_P.d; return *this; } bool KStartupInfoId::operator==(const KStartupInfoId &id_P) const { return id() == id_P.id(); } bool KStartupInfoId::operator!=(const KStartupInfoId &id_P) const { return !(*this == id_P); } // needed for QMap bool KStartupInfoId::operator<(const KStartupInfoId &id_P) const { return id() < id_P.id(); } bool KStartupInfoId::isNull() const { return d->id.isEmpty() || d->id == "0"; } unsigned long KStartupInfoId::timestamp() const { if (isNull()) { return 0; } // As per the spec, the ID must contain the _TIME followed by the timestamp int pos = d->id.lastIndexOf("_TIME"); if (pos >= 0) { bool ok; unsigned long time = QString(d->id.mid(pos + 5)).toULong(&ok); if (!ok && d->id[ pos + 5 ] == '-') { // try if it's as a negative signed number perhaps time = QString(d->id.mid(pos + 5)).toLong(&ok); } if (ok) { return time; } } return 0; } QString KStartupInfoData::Private::to_text() const { QString ret; if (!bin.isEmpty()) { ret += QString::fromLatin1(" BIN=\"%1\"").arg(escape_str(bin)); } if (!name.isEmpty()) { ret += QString::fromLatin1(" NAME=\"%1\"").arg(escape_str(name)); } if (!description.isEmpty()) { ret += QString::fromLatin1(" DESCRIPTION=\"%1\"").arg(escape_str(description)); } if (!icon.isEmpty()) { ret += QString::fromLatin1(" ICON=\"%1\"").arg(icon); } if (desktop != 0) ret += QString::fromLatin1(" DESKTOP=%1") .arg(desktop == NET::OnAllDesktops ? NET::OnAllDesktops : desktop - 1); // spec counts from 0 if (!wmclass.isEmpty()) { ret += QString::fromLatin1(" WMCLASS=\"%1\"").arg(QString(wmclass)); } if (!hostname.isEmpty()) { ret += QString::fromLatin1(" HOSTNAME=%1").arg(QString(hostname)); } for (QList< pid_t >::ConstIterator it = pids.begin(); it != pids.end(); ++it) { ret += QString::fromLatin1(" PID=%1").arg(*it); } if (silent != KStartupInfoData::Unknown) { ret += QString::fromLatin1(" SILENT=%1").arg(silent == KStartupInfoData::Yes ? 1 : 0); } if (screen != -1) { ret += QString::fromLatin1(" SCREEN=%1").arg(screen); } if (xinerama != -1) { ret += QString::fromLatin1(" XINERAMA=%1").arg(xinerama); } if (launched_by != 0) { ret += QString::fromLatin1(" LAUNCHED_BY=%1").arg((qptrdiff)launched_by); } if (!application_id.isEmpty()) { ret += QString::fromLatin1(" APPLICATION_ID=\"%1\"").arg(application_id); } return ret; } KStartupInfoData::KStartupInfoData(const QString &txt_P) : d(new Private) { const QStringList items = get_fields(txt_P); const QString bin_str = QString::fromLatin1("BIN="); const QString name_str = QString::fromLatin1("NAME="); const QString description_str = QString::fromLatin1("DESCRIPTION="); const QString icon_str = QString::fromLatin1("ICON="); const QString desktop_str = QString::fromLatin1("DESKTOP="); const QString wmclass_str = QString::fromLatin1("WMCLASS="); const QString hostname_str = QString::fromLatin1("HOSTNAME="); // added to version 1 (2014) const QString pid_str = QString::fromLatin1("PID="); // added to version 1 (2014) const QString silent_str = QString::fromLatin1("SILENT="); const QString screen_str = QString::fromLatin1("SCREEN="); const QString xinerama_str = QString::fromLatin1("XINERAMA="); const QString launched_by_str = QString::fromLatin1("LAUNCHED_BY="); const QString application_id_str = QString::fromLatin1("APPLICATION_ID="); for (QStringList::ConstIterator it = items.begin(); it != items.end(); ++it) { if ((*it).startsWith(bin_str)) { d->bin = get_str(*it); } else if ((*it).startsWith(name_str)) { d->name = get_str(*it); } else if ((*it).startsWith(description_str)) { d->description = get_str(*it); } else if ((*it).startsWith(icon_str)) { d->icon = get_str(*it); } else if ((*it).startsWith(desktop_str)) { d->desktop = get_num(*it); if (d->desktop != NET::OnAllDesktops) ++d->desktop; // spec counts from 0 } else if ((*it).startsWith(wmclass_str)) { d->wmclass = get_cstr(*it); } else if ((*it).startsWith(hostname_str)) { d->hostname = get_cstr(*it); } else if ((*it).startsWith(pid_str)) { addPid(get_num(*it)); } else if ((*it).startsWith(silent_str)) { d->silent = get_num(*it) != 0 ? Yes : No; } else if ((*it).startsWith(screen_str)) { d->screen = get_num(*it); } else if ((*it).startsWith(xinerama_str)) { d->xinerama = get_num(*it); } else if ((*it).startsWith(launched_by_str)) { d->launched_by = (WId) get_num(*it); } else if ((*it).startsWith(application_id_str)) { d->application_id = get_str(*it); } } } KStartupInfoData::KStartupInfoData(const KStartupInfoData &data) : d(new Private(*data.d)) { } KStartupInfoData &KStartupInfoData::operator=(const KStartupInfoData &data) { if (&data == this) { return *this; } *d = *data.d; return *this; } void KStartupInfoData::update(const KStartupInfoData &data_P) { if (!data_P.bin().isEmpty()) { d->bin = data_P.bin(); } if (!data_P.name().isEmpty() && name().isEmpty()) { // don't overwrite d->name = data_P.name(); } if (!data_P.description().isEmpty() && description().isEmpty()) { // don't overwrite d->description = data_P.description(); } if (!data_P.icon().isEmpty() && icon().isEmpty()) { // don't overwrite d->icon = data_P.icon(); } if (data_P.desktop() != 0 && desktop() == 0) { // don't overwrite d->desktop = data_P.desktop(); } if (!data_P.d->wmclass.isEmpty()) { d->wmclass = data_P.d->wmclass; } if (!data_P.d->hostname.isEmpty()) { d->hostname = data_P.d->hostname; } for (QList< pid_t >::ConstIterator it = data_P.d->pids.constBegin(); it != data_P.d->pids.constEnd(); ++it) { addPid(*it); } if (data_P.silent() != Unknown) { d->silent = data_P.silent(); } if (data_P.screen() != -1) { d->screen = data_P.screen(); } if (data_P.xinerama() != -1 && xinerama() != -1) { // don't overwrite d->xinerama = data_P.xinerama(); } if (data_P.launchedBy() != 0 && launchedBy() != 0) { // don't overwrite d->launched_by = data_P.launchedBy(); } if (!data_P.applicationId().isEmpty() && applicationId().isEmpty()) { // don't overwrite d->application_id = data_P.applicationId(); } } KStartupInfoData::KStartupInfoData() : d(new Private) { } KStartupInfoData::~KStartupInfoData() { delete d; } void KStartupInfoData::setBin(const QString &bin_P) { d->bin = bin_P; } const QString &KStartupInfoData::bin() const { return d->bin; } void KStartupInfoData::setName(const QString &name_P) { d->name = name_P; } const QString &KStartupInfoData::name() const { return d->name; } const QString &KStartupInfoData::findName() const { if (!name().isEmpty()) { return name(); } return bin(); } void KStartupInfoData::setDescription(const QString &desc_P) { d->description = desc_P; } const QString &KStartupInfoData::description() const { return d->description; } const QString &KStartupInfoData::findDescription() const { if (!description().isEmpty()) { return description(); } return name(); } void KStartupInfoData::setIcon(const QString &icon_P) { d->icon = icon_P; } const QString &KStartupInfoData::findIcon() const { if (!icon().isEmpty()) { return icon(); } return bin(); } const QString &KStartupInfoData::icon() const { return d->icon; } void KStartupInfoData::setDesktop(int desktop_P) { d->desktop = desktop_P; } int KStartupInfoData::desktop() const { return d->desktop; } void KStartupInfoData::setWMClass(const QByteArray &wmclass_P) { d->wmclass = wmclass_P; } const QByteArray KStartupInfoData::findWMClass() const { if (!WMClass().isEmpty() && WMClass() != "0") { return WMClass(); } return bin().toUtf8(); } QByteArray KStartupInfoData::WMClass() const { return d->wmclass; } void KStartupInfoData::setHostname(const QByteArray &hostname_P) { if (!hostname_P.isNull()) { d->hostname = hostname_P; } else { char tmp[ 256 ]; tmp[ 0 ] = '\0'; if (!gethostname(tmp, 255)) { tmp[sizeof(tmp) - 1] = '\0'; } d->hostname = tmp; } } QByteArray KStartupInfoData::hostname() const { return d->hostname; } void KStartupInfoData::addPid(pid_t pid_P) { if (!d->pids.contains(pid_P)) { d->pids.append(pid_P); } } void KStartupInfoData::Private::remove_pid(pid_t pid_P) { pids.removeAll(pid_P); } QList< pid_t > KStartupInfoData::pids() const { return d->pids; } bool KStartupInfoData::is_pid(pid_t pid_P) const { return d->pids.contains(pid_P); } void KStartupInfoData::setSilent(TriState state_P) { d->silent = state_P; } KStartupInfoData::TriState KStartupInfoData::silent() const { return d->silent; } void KStartupInfoData::setScreen(int _screen) { d->screen = _screen; } int KStartupInfoData::screen() const { return d->screen; } void KStartupInfoData::setXinerama(int xinerama) { d->xinerama = xinerama; } int KStartupInfoData::xinerama() const { return d->xinerama; } void KStartupInfoData::setLaunchedBy(WId window) { d->launched_by = window; } WId KStartupInfoData::launchedBy() const { return d->launched_by; } void KStartupInfoData::setApplicationId(const QString &desktop) { if (desktop.startsWith('/')) { d->application_id = desktop; return; } // the spec requires this is always a full path, in order for everyone to be able to find it QString desk = QStandardPaths::locate(QStandardPaths::ApplicationsLocation, desktop); if (desk.isEmpty()) { desk = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kservices5/" + desktop); } if (desk.isEmpty()) { return; } d->application_id = desk; } QString KStartupInfoData::applicationId() const { return d->application_id; } static long get_num(const QString &item_P) { unsigned int pos = item_P.indexOf(QLatin1Char('=')); return item_P.mid(pos + 1).toLong(); } static QString get_str(const QString &item_P) { int pos = item_P.indexOf(QLatin1Char('=')); if (item_P.length() > pos + 2 && item_P.at(pos + 1) == QLatin1Char('\"')) { int pos2 = item_P.left(pos + 2).indexOf(QLatin1Char('\"')); if (pos2 < 0) { return QString(); // 01234 } return item_P.mid(pos + 2, pos2 - 2 - pos); // A="C" } return item_P.mid(pos + 1); } static QByteArray get_cstr(const QString &item_P) { return get_str(item_P).toUtf8(); } static QStringList get_fields(const QString &txt_P) { QString txt = txt_P.simplified(); QStringList ret; QString item = ""; bool in = false; bool escape = false; for (int pos = 0; pos < txt.length(); ++pos) { if (escape) { item += txt[ pos ]; escape = false; } else if (txt[ pos ] == '\\') { escape = true; } else if (txt[ pos ] == '\"') { in = !in; } else if (txt[ pos ] == ' ' && !in) { ret.append(item); item = ""; } else { item += txt[ pos ]; } } ret.append(item); return ret; } static QString escape_str(const QString &str_P) { QString ret = ""; for (int pos = 0; pos < str_P.length(); ++pos) { if (str_P[ pos ] == '\\' || str_P[ pos ] == '"') { ret += '\\'; } ret += str_P[ pos ]; } return ret; } #include "moc_kstartupinfo.cpp" diff --git a/src/kwindowsystem.cpp b/src/kwindowsystem.cpp index b35a881..29a7982 100644 --- a/src/kwindowsystem.cpp +++ b/src/kwindowsystem.cpp @@ -1,754 +1,756 @@ /* * Copyright 2014 Martin Gräßlin * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 6 of version 3 of the license. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . */ #include "kwindowsystem.h" #include "kwindowsystem_dummy_p.h" #include "kwindowsystemplugininterface_p.h" #include "pluginwrapper_p.h" #include #include #include #include #include +#ifndef KWINDOWSYSTEM_NO_DEPRECATED #include +#endif #include #if KWINDOWSYSTEM_HAVE_X11 #include #endif //QPoint and QSize all have handy / operators which are useful for scaling, positions and sizes for high DPI support //QRect does not, so we create one for internal purposes within this class inline QRect operator/(const QRect &rectangle, qreal factor) { return QRect(rectangle.topLeft() / factor, rectangle.size() / factor); } class KWindowSystemStaticContainer { public: KWindowSystemStaticContainer() { d.reset(KWindowSystemPluginWrapper::self().createWindowSystem()); if (QCoreApplication::instance()) { kwm.moveToThread(QCoreApplication::instance()->thread()); } } KWindowSystemPrivate *xcbPlugin() { if (xcbPrivate.isNull()) { QPluginLoader loader(QStringLiteral(XCB_PLUGIN_PATH)); QScopedPointer xcbPlugin(qobject_cast< KWindowSystemPluginInterface* >(loader.instance())); if (!xcbPlugin.isNull()) { xcbPrivate.reset(xcbPlugin->createWindowSystem()); } } return xcbPrivate.data(); } KWindowSystem kwm; QScopedPointer d; QScopedPointer xcbPrivate; }; Q_GLOBAL_STATIC(KWindowSystemStaticContainer, g_kwmInstanceContainer) KWindowSystemPrivate::~KWindowSystemPrivate() { } QPixmap KWindowSystemPrivate::iconFromNetWinInfo(int width, int height, bool scale, int flags, NETWinInfo *info) { Q_UNUSED(width) Q_UNUSED(height) Q_UNUSED(scale) Q_UNUSED(flags) Q_UNUSED(info) return QPixmap(); } QList KWindowSystemPrivateDummy::windows() { return QList(); } QList KWindowSystemPrivateDummy::stackingOrder() { return QList(); } WId KWindowSystemPrivateDummy::activeWindow() { return 0; } void KWindowSystemPrivateDummy::activateWindow(WId win, long time) { Q_UNUSED(win) Q_UNUSED(time) } void KWindowSystemPrivateDummy::forceActiveWindow(WId win, long time) { Q_UNUSED(win) Q_UNUSED(time) } void KWindowSystemPrivateDummy::demandAttention(WId win, bool set) { Q_UNUSED(win) Q_UNUSED(set) } bool KWindowSystemPrivateDummy::compositingActive() { return false; } int KWindowSystemPrivateDummy::currentDesktop() { return 0; } int KWindowSystemPrivateDummy::numberOfDesktops() { return 0; } void KWindowSystemPrivateDummy::setCurrentDesktop(int desktop) { Q_UNUSED(desktop) } void KWindowSystemPrivateDummy::setOnAllDesktops(WId win, bool b) { Q_UNUSED(win) Q_UNUSED(b) } void KWindowSystemPrivateDummy::setOnDesktop(WId win, int desktop) { Q_UNUSED(win) Q_UNUSED(desktop) } void KWindowSystemPrivateDummy::setOnActivities(WId win, const QStringList &activities) { Q_UNUSED(win) Q_UNUSED(activities) } #ifndef KWINDOWSYSTEM_NO_DEPRECATED WId KWindowSystemPrivateDummy::transientFor(WId window) { Q_UNUSED(window) return 0; } WId KWindowSystemPrivateDummy::groupLeader(WId window) { Q_UNUSED(window) return 0; } #endif QPixmap KWindowSystemPrivateDummy::icon(WId win, int width, int height, bool scale, int flags) { Q_UNUSED(win) Q_UNUSED(width) Q_UNUSED(height) Q_UNUSED(scale) Q_UNUSED(flags) return QPixmap(); } void KWindowSystemPrivateDummy::setIcons(WId win, const QPixmap &icon, const QPixmap &miniIcon) { Q_UNUSED(win) Q_UNUSED(icon) Q_UNUSED(miniIcon) } void KWindowSystemPrivateDummy::setType(WId win, NET::WindowType windowType) { Q_UNUSED(win) Q_UNUSED(windowType) } void KWindowSystemPrivateDummy::setState(WId win, NET::States state) { Q_UNUSED(win) Q_UNUSED(state) } void KWindowSystemPrivateDummy::clearState(WId win, NET::States state) { Q_UNUSED(win) Q_UNUSED(state) } void KWindowSystemPrivateDummy::minimizeWindow(WId win) { Q_UNUSED(win) } void KWindowSystemPrivateDummy::unminimizeWindow(WId win) { Q_UNUSED(win) } void KWindowSystemPrivateDummy::raiseWindow(WId win) { Q_UNUSED(win) } void KWindowSystemPrivateDummy::lowerWindow(WId win) { Q_UNUSED(win) } bool KWindowSystemPrivateDummy::icccmCompliantMappingState() { return false; } QRect KWindowSystemPrivateDummy::workArea(int desktop) { Q_UNUSED(desktop) return QRect(); } QRect KWindowSystemPrivateDummy::workArea(const QList &excludes, int desktop) { Q_UNUSED(excludes) Q_UNUSED(desktop) return QRect(); } QString KWindowSystemPrivateDummy::desktopName(int desktop) { Q_UNUSED(desktop) return QString(); } void KWindowSystemPrivateDummy::setDesktopName(int desktop, const QString &name) { Q_UNUSED(desktop) Q_UNUSED(name) } bool KWindowSystemPrivateDummy::showingDesktop() { return false; } void KWindowSystemPrivateDummy::setShowingDesktop(bool showing) { Q_UNUSED(showing); } void KWindowSystemPrivateDummy::setUserTime(WId win, long time) { Q_UNUSED(win) Q_UNUSED(time) } void KWindowSystemPrivateDummy::setExtendedStrut(WId win, int left_width, int left_start, int left_end, int right_width, int right_start, int right_end, int top_width, int top_start, int top_end, int bottom_width, int bottom_start, int bottom_end) { Q_UNUSED(win) Q_UNUSED(left_width) Q_UNUSED(left_start) Q_UNUSED(left_end) Q_UNUSED(right_width) Q_UNUSED(right_start) Q_UNUSED(right_end) Q_UNUSED(top_width) Q_UNUSED(top_start) Q_UNUSED(top_end) Q_UNUSED(bottom_width) Q_UNUSED(bottom_start) Q_UNUSED(bottom_end) } void KWindowSystemPrivateDummy::setStrut(WId win, int left, int right, int top, int bottom) { Q_UNUSED(win) Q_UNUSED(left) Q_UNUSED(right) Q_UNUSED(top) Q_UNUSED(bottom) } bool KWindowSystemPrivateDummy::allowedActionsSupported() { return false; } QString KWindowSystemPrivateDummy::readNameProperty(WId window, unsigned long atom) { Q_UNUSED(window) Q_UNUSED(atom) return QString(); } void KWindowSystemPrivateDummy::allowExternalProcessWindowActivation(int pid) { Q_UNUSED(pid) } void KWindowSystemPrivateDummy::setBlockingCompositing(WId window, bool active) { Q_UNUSED(window) Q_UNUSED(active) } bool KWindowSystemPrivateDummy::mapViewport() { return false; } int KWindowSystemPrivateDummy::viewportToDesktop(const QPoint &pos) { Q_UNUSED(pos) return 0; } int KWindowSystemPrivateDummy::viewportWindowToDesktop(const QRect &r) { Q_UNUSED(r) return 0; } QPoint KWindowSystemPrivateDummy::desktopToViewport(int desktop, bool absolute) { Q_UNUSED(desktop) Q_UNUSED(absolute) return QPoint(); } QPoint KWindowSystemPrivateDummy::constrainViewportRelativePosition(const QPoint &pos) { Q_UNUSED(pos) return QPoint(); } void KWindowSystemPrivateDummy::connectNotify(const QMetaMethod &signal) { Q_UNUSED(signal) } KWindowSystem *KWindowSystem::self() { return &(g_kwmInstanceContainer()->kwm); } KWindowSystemPrivate *KWindowSystem::d_func() { return g_kwmInstanceContainer()->d.data(); } void KWindowSystem::connectNotify(const QMetaMethod &signal) { Q_D(KWindowSystem); d->connectNotify(signal); QObject::connectNotify(signal); } QList KWindowSystem::windows() { Q_D(KWindowSystem); return d->windows(); } #ifndef KWINDOWSYSTEM_NO_DEPRECATED KWindowInfo KWindowSystem::windowInfo(WId win, NET::Properties properties, NET::Properties2 properties2) { return KWindowInfo(win, properties, properties2); } #endif bool KWindowSystem::hasWId(WId w) { return windows().contains(w); } QList KWindowSystem::stackingOrder() { Q_D(KWindowSystem); return d->stackingOrder(); } int KWindowSystem::currentDesktop() { Q_D(KWindowSystem); return d->currentDesktop(); } int KWindowSystem::numberOfDesktops() { Q_D(KWindowSystem); return d->numberOfDesktops(); } void KWindowSystem::setCurrentDesktop(int desktop) { Q_D(KWindowSystem); d->setCurrentDesktop(desktop); } void KWindowSystem::setOnAllDesktops(WId win, bool b) { Q_D(KWindowSystem); d->setOnAllDesktops(win, b); } void KWindowSystem::setOnDesktop(WId win, int desktop) { Q_D(KWindowSystem); d->setOnDesktop(win, desktop); } void KWindowSystem::setOnActivities(WId win, const QStringList &activities) { Q_D(KWindowSystem); d->setOnActivities(win, activities); } WId KWindowSystem::activeWindow() { Q_D(KWindowSystem); return d->activeWindow(); } void KWindowSystem::activateWindow(WId win, long time) { Q_D(KWindowSystem); d->activateWindow(win, time); } void KWindowSystem::forceActiveWindow(WId win, long time) { Q_D(KWindowSystem); d->forceActiveWindow(win, time); } void KWindowSystem::demandAttention(WId win, bool set) { Q_D(KWindowSystem); d->demandAttention(win, set); } #ifndef KWINDOWSYSTEM_NO_DEPRECATED WId KWindowSystem::transientFor(WId win) { Q_D(KWindowSystem); return d->transientFor(win); } void KWindowSystem::setMainWindow(QWidget *subWidget, WId mainWindowId) { // Set the WA_NativeWindow attribute to force the creation of the QWindow. // Without this QWidget::windowHandle() returns 0. subWidget->setAttribute(Qt::WA_NativeWindow, true); QWindow *subWindow = subWidget->windowHandle(); Q_ASSERT(subWindow); setMainWindow(subWindow, mainWindowId); } #endif void KWindowSystem::setMainWindow(QWindow *subWindow, WId mainWindowId) { QWindow *mainWindow = QWindow::fromWinId(mainWindowId); if (mainWindow) { // foreign windows not supported on all platforms subWindow->setTransientParent(mainWindow); // mainWindow is not the child of any object, so make sure it gets deleted at some point connect(subWindow, &QObject::destroyed, mainWindow, &QObject::deleteLater); } } #ifndef KWINDOWSYSTEM_NO_DEPRECATED WId KWindowSystem::groupLeader(WId win) { Q_D(KWindowSystem); return d->groupLeader(win); } #endif QPixmap KWindowSystem::icon(WId win, int width, int height, bool scale) { return icon(win, width, height, scale, NETWM | WMHints | ClassHint | XApp); } QPixmap KWindowSystem::icon(WId win, int width, int height, bool scale, int flags) { Q_D(KWindowSystem); return d->icon(win, width, height, scale, flags); } QPixmap KWindowSystem::icon(WId win, int width, int height, bool scale, int flags, NETWinInfo *info) { Q_D(KWindowSystem); width *= qApp->devicePixelRatio(); height *= qApp->devicePixelRatio(); #if KWINDOWSYSTEM_HAVE_X11 if (info) { if (isPlatformX11()) { // this is the xcb plugin, we can just delegate return d->iconFromNetWinInfo(width, height, scale, flags, info); } else { // other platform plugin, load xcb plugin to delegate to it if (KWindowSystemPrivate *p = g_kwmInstanceContainer()->xcbPlugin()) { return p->iconFromNetWinInfo(width, height, scale, flags, info); } } } #else Q_UNUSED(info) #endif return d->icon(win, width, height, scale, flags); } void KWindowSystem::setIcons(WId win, const QPixmap &icon, const QPixmap &miniIcon) { Q_D(KWindowSystem); d->setIcons(win, icon, miniIcon); } void KWindowSystem::setType(WId win, NET::WindowType windowType) { Q_D(KWindowSystem); d->setType(win, windowType); } void KWindowSystem::setState(WId win, NET::States state) { Q_D(KWindowSystem); d->setState(win, state); } void KWindowSystem::clearState(WId win, NET::States state) { Q_D(KWindowSystem); d->clearState(win, state); } void KWindowSystem::minimizeWindow(WId win) { Q_D(KWindowSystem); d->minimizeWindow(win); } void KWindowSystem::unminimizeWindow(WId win) { Q_D(KWindowSystem); d->unminimizeWindow(win); } #ifndef KWINDOWSYSTEM_NO_DEPRECATED void KWindowSystem::minimizeWindow(WId win, bool animation) { Q_UNUSED(animation) minimizeWindow(win); } #endif #ifndef KWINDOWSYSTEM_NO_DEPRECATED void KWindowSystem::unminimizeWindow(WId win, bool animation) { Q_UNUSED(animation) unminimizeWindow(win); } #endif void KWindowSystem::raiseWindow(WId win) { Q_D(KWindowSystem); d->raiseWindow(win); } void KWindowSystem::lowerWindow(WId win) { Q_D(KWindowSystem); d->lowerWindow(win); } bool KWindowSystem::compositingActive() { Q_D(KWindowSystem); return d->compositingActive(); } QRect KWindowSystem::workArea(int desktop) { Q_D(KWindowSystem); return d->workArea(desktop) / qApp->devicePixelRatio(); } QRect KWindowSystem::workArea(const QList &exclude, int desktop) { Q_D(KWindowSystem); return d->workArea(exclude, desktop) / qApp->devicePixelRatio(); } QString KWindowSystem::desktopName(int desktop) { Q_D(KWindowSystem); return d->desktopName(desktop); } void KWindowSystem::setDesktopName(int desktop, const QString &name) { Q_D(KWindowSystem); d->setDesktopName(desktop, name); } bool KWindowSystem::showingDesktop() { Q_D(KWindowSystem); return d->showingDesktop(); } void KWindowSystem::setShowingDesktop(bool showing) { Q_D(KWindowSystem); return d->setShowingDesktop(showing); } void KWindowSystem::setUserTime(WId win, long time) { Q_D(KWindowSystem); d->setUserTime(win, time); } void KWindowSystem::setExtendedStrut(WId win, int left_width, int left_start, int left_end, int right_width, int right_start, int right_end, int top_width, int top_start, int top_end, int bottom_width, int bottom_start, int bottom_end) { Q_D(KWindowSystem); const qreal dpr = qApp->devicePixelRatio(); d->setExtendedStrut(win, left_width * dpr, left_start * dpr, left_end * dpr, right_width * dpr, right_start * dpr, right_end * dpr, top_width * dpr, top_start * dpr, top_end * dpr, bottom_width * dpr, bottom_start * dpr, bottom_end * dpr); } void KWindowSystem::setStrut(WId win, int left, int right, int top, int bottom) { Q_D(KWindowSystem); const qreal dpr = qApp->devicePixelRatio(); d->setStrut(win, left * dpr, right * dpr, top * dpr, bottom * dpr); } bool KWindowSystem::icccmCompliantMappingState() { Q_D(KWindowSystem); return d->icccmCompliantMappingState(); } bool KWindowSystem::allowedActionsSupported() { Q_D(KWindowSystem); return d->allowedActionsSupported(); } QString KWindowSystem::readNameProperty(WId win, unsigned long atom) { Q_D(KWindowSystem); return d->readNameProperty(win, atom); } void KWindowSystem::allowExternalProcessWindowActivation(int pid) { Q_D(KWindowSystem); d->allowExternalProcessWindowActivation(pid); } void KWindowSystem::setBlockingCompositing(WId window, bool active) { Q_D(KWindowSystem); d->setBlockingCompositing(window, active); } bool KWindowSystem::mapViewport() { Q_D(KWindowSystem); return d->mapViewport(); } int KWindowSystem::viewportToDesktop(const QPoint &p) { Q_D(KWindowSystem); return d->viewportToDesktop(p / qApp->devicePixelRatio()); } int KWindowSystem::viewportWindowToDesktop(const QRect &r) { Q_D(KWindowSystem); return d->viewportWindowToDesktop(r / qApp->devicePixelRatio()); } QPoint KWindowSystem::desktopToViewport(int desktop, bool absolute) { Q_D(KWindowSystem); return d->desktopToViewport(desktop, absolute); } QPoint KWindowSystem::constrainViewportRelativePosition(const QPoint &pos) { Q_D(KWindowSystem); return d->constrainViewportRelativePosition(pos / qApp->devicePixelRatio()); } static inline KWindowSystem::Platform initPlatform() { auto platformName = QGuiApplication::platformName(); if (platformName == QLatin1String("flatpak")) { // here we cannot know what is the actual windowing system, let's try it's env variable const auto flatpakPlatform = QString::fromLocal8Bit(qgetenv("QT_QPA_FLATPAK_PLATFORM")); if (!flatpakPlatform.isEmpty()) { platformName = flatpakPlatform; } } #if KWINDOWSYSTEM_HAVE_X11 if (platformName == QLatin1String("xcb")) { return KWindowSystem::Platform::X11; } #endif if (platformName.startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { return KWindowSystem::Platform::Wayland; } return KWindowSystem::Platform::Unknown; } KWindowSystem::Platform KWindowSystem::platform() { static Platform s_platform = initPlatform(); return s_platform; } bool KWindowSystem::isPlatformX11() { return platform() == Platform::X11; } bool KWindowSystem::isPlatformWayland() { return platform() == Platform::Wayland; } diff --git a/src/platforms/xcb/kwindowsystem.cpp b/src/platforms/xcb/kwindowsystem.cpp index e611b22..f8ede54 100644 --- a/src/platforms/xcb/kwindowsystem.cpp +++ b/src/platforms/xcb/kwindowsystem.cpp @@ -1,1216 +1,1217 @@ /* This file is part of the KDE libraries Copyright (C) 1999 Matthias Ettrich (ettrich@kde.org) Copyright (C) 2007 Lubos Lunak (l.lunak@kde.org) Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kwindowsystem.h" #include "kwindowsystem_p_x11.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if KWINDOWSYSTEM_HAVE_XFIXES #include #endif static Atom net_wm_cm; static void create_atoms(); static inline const QRect &displayGeometry() { static QRect displayGeometry; static bool isDirty = true; if (isDirty) { static QList connections; auto dirtify = [&] { isDirty = true; foreach (const QMetaObject::Connection &con, connections) QObject::disconnect(con); connections.clear(); }; QObject::connect(qApp, &QGuiApplication::screenAdded, dirtify); QObject::connect(qApp, &QGuiApplication::screenRemoved, dirtify); const QList screenList = QGuiApplication::screens(); QRegion region; for (int i = 0; i < screenList.count(); ++i) { const QScreen *screen = screenList.at(i); connections << QObject::connect(screen, &QScreen::geometryChanged, dirtify); region += screen->geometry(); } displayGeometry = region.boundingRect(); isDirty = false; } return displayGeometry; } static inline int displayWidth() { return displayGeometry().width(); } static inline int displayHeight() { return displayGeometry().height(); } static const NET::Properties windowsProperties = NET::ClientList | NET::ClientListStacking | NET::Supported | NET::NumberOfDesktops | NET::DesktopGeometry | NET::DesktopViewport | NET::CurrentDesktop | NET::DesktopNames | NET::ActiveWindow | NET::WorkArea; static const NET::Properties2 windowsProperties2 = NET::WM2ShowingDesktop; // ClientList and ClientListStacking is not per-window information, but a desktop information, // so track it even with only INFO_BASIC static const NET::Properties desktopProperties = NET::ClientList | NET::ClientListStacking | NET::Supported | NET::NumberOfDesktops | NET::DesktopGeometry | NET::DesktopViewport | NET::CurrentDesktop | NET::DesktopNames | NET::ActiveWindow | NET::WorkArea; static const NET::Properties2 desktopProperties2 = NET::WM2ShowingDesktop; MainThreadInstantiator::MainThreadInstantiator(KWindowSystemPrivateX11::FilterInfo _what) : QObject(), m_what(_what) { } NETEventFilter *MainThreadInstantiator::createNETEventFilter() { return new NETEventFilter(m_what); } NETEventFilter::NETEventFilter(KWindowSystemPrivateX11::FilterInfo _what) : NETRootInfo(QX11Info::connection(), _what >= KWindowSystemPrivateX11::INFO_WINDOWS ? windowsProperties : desktopProperties, _what >= KWindowSystemPrivateX11::INFO_WINDOWS ? windowsProperties2 : desktopProperties2, -1, false), QAbstractNativeEventFilter(), strutSignalConnected(false), compositingEnabled(false), haveXfixes(false), what(_what), winId(XCB_WINDOW_NONE), m_appRootWindow(QX11Info::appRootWindow()) { QCoreApplication::instance()->installNativeEventFilter(this); #if KWINDOWSYSTEM_HAVE_XFIXES int errorBase; if ((haveXfixes = XFixesQueryExtension(QX11Info::display(), &xfixesEventBase, &errorBase))) { create_atoms(); winId = xcb_generate_id(QX11Info::connection()); uint32_t values[] = { true, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY }; xcb_create_window(QX11Info::connection(), XCB_COPY_FROM_PARENT, winId, m_appRootWindow, 0, 0, 1, 1, 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK, values); XFixesSelectSelectionInput(QX11Info::display(), winId, net_wm_cm, XFixesSetSelectionOwnerNotifyMask | XFixesSelectionWindowDestroyNotifyMask | XFixesSelectionClientCloseNotifyMask); compositingEnabled = XGetSelectionOwner(QX11Info::display(), net_wm_cm) != None; } #endif } NETEventFilter::~NETEventFilter() { if (QX11Info::connection() && winId != XCB_WINDOW_NONE) { xcb_destroy_window(QX11Info::connection(), winId); winId = XCB_WINDOW_NONE; } } // not virtual, but it's called directly only from init() void NETEventFilter::activate() { NETRootInfo::activate(); updateStackingOrder(); } bool NETEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) { Q_UNUSED(result) if (eventType != "xcb_generic_event_t") { // only interested in XCB events of course return false; } return nativeEventFilter(reinterpret_cast(message)); } bool NETEventFilter::nativeEventFilter(xcb_generic_event_t *ev) { KWindowSystem *s_q = KWindowSystem::self(); const uint8_t eventType = ev->response_type & ~0x80; if (eventType == xfixesEventBase + XCB_XFIXES_SELECTION_NOTIFY) { xcb_xfixes_selection_notify_event_t *event = reinterpret_cast(ev); if (event->window == winId) { bool haveOwner = event->owner != XCB_WINDOW_NONE; if (compositingEnabled != haveOwner) { compositingEnabled = haveOwner; emit s_q->compositingChanged(compositingEnabled); } return true; } // Qt compresses XFixesSelectionNotifyEvents without caring about the actual window // gui/kernel/qapplication_x11.cpp // until that can be assumed fixed, we also react on events on the root (caused by Qts own compositing tracker) if (event->window == m_appRootWindow) { if (event->selection == net_wm_cm) { bool haveOwner = event->owner != XCB_WINDOW_NONE; if (compositingEnabled != haveOwner) { compositingEnabled = haveOwner; emit s_q->compositingChanged(compositingEnabled); } // NOTICE this is not our event, we just randomly captured it from Qt -> pass on return false; } } return false; } xcb_window_t eventWindow = XCB_WINDOW_NONE; switch (eventType) { case XCB_CLIENT_MESSAGE: eventWindow = reinterpret_cast(ev)->window; break; case XCB_PROPERTY_NOTIFY: eventWindow = reinterpret_cast(ev)->window; break; case XCB_CONFIGURE_NOTIFY: eventWindow = reinterpret_cast(ev)->window; break; } if (eventWindow == m_appRootWindow) { int old_current_desktop = currentDesktop(); xcb_window_t old_active_window = activeWindow(); int old_number_of_desktops = numberOfDesktops(); bool old_showing_desktop = showingDesktop(); - unsigned long m[ 5 ]; - NETRootInfo::event(ev, m, 5); + NET::Properties props; + NET::Properties2 props2; + NETRootInfo::event(ev, &props, &props2); - if ((m[ PROTOCOLS ] & CurrentDesktop) && currentDesktop() != old_current_desktop) { + if ((props & CurrentDesktop) && currentDesktop() != old_current_desktop) { emit s_q->currentDesktopChanged(currentDesktop()); } - if ((m[ PROTOCOLS ] & DesktopViewport) && mapViewport() && currentDesktop() != old_current_desktop) { + if ((props & DesktopViewport) && mapViewport() && currentDesktop() != old_current_desktop) { emit s_q->currentDesktopChanged(currentDesktop()); } - if ((m[ PROTOCOLS ] & ActiveWindow) && activeWindow() != old_active_window) { + if ((props & ActiveWindow) && activeWindow() != old_active_window) { emit s_q->activeWindowChanged(activeWindow()); } - if (m[ PROTOCOLS ] & DesktopNames) { + if (props & DesktopNames) { emit s_q->desktopNamesChanged(); } - if ((m[ PROTOCOLS ] & NumberOfDesktops) && numberOfDesktops() != old_number_of_desktops) { + if ((props & NumberOfDesktops) && numberOfDesktops() != old_number_of_desktops) { emit s_q->numberOfDesktopsChanged(numberOfDesktops()); } - if ((m[ PROTOCOLS ] & DesktopGeometry) && mapViewport() && numberOfDesktops() != old_number_of_desktops) { + if ((props & DesktopGeometry) && mapViewport() && numberOfDesktops() != old_number_of_desktops) { emit s_q->numberOfDesktopsChanged(numberOfDesktops()); } - if (m[ PROTOCOLS ] & WorkArea) { + if (props & WorkArea) { emit s_q->workAreaChanged(); } - if (m[ PROTOCOLS ] & ClientListStacking) { + if (props & ClientListStacking) { updateStackingOrder(); emit s_q->stackingOrderChanged(); } - if ((m[ PROTOCOLS2 ] & WM2ShowingDesktop) && showingDesktop() != old_showing_desktop) { + if ((props2 & WM2ShowingDesktop) && showingDesktop() != old_showing_desktop) { emit s_q->showingDesktopChanged(showingDesktop()); } } else if (windows.contains(eventWindow)) { NETWinInfo ni(QX11Info::connection(), eventWindow, m_appRootWindow, NET::Properties(), NET::Properties2()); NET::Properties dirtyProperties; NET::Properties2 dirtyProperties2; ni.event(ev, &dirtyProperties, &dirtyProperties2); if (eventType == XCB_PROPERTY_NOTIFY) { xcb_property_notify_event_t *event = reinterpret_cast(ev); if (event->atom == XCB_ATOM_WM_HINTS) { dirtyProperties |= NET::WMIcon; // support for old icons } else if (event->atom == XCB_ATOM_WM_NAME) { dirtyProperties |= NET::WMName; // support for old name } else if (event->atom == XCB_ATOM_WM_ICON_NAME) { dirtyProperties |= NET::WMIconName; // support for old iconic name } } if (mapViewport() && (dirtyProperties & (NET::WMState | NET::WMGeometry))) { /* geometry change -> possible viewport change * state change -> possible NET::Sticky change */ dirtyProperties |= NET::WMDesktop; } if ((dirtyProperties & NET::WMStrut) != 0) { removeStrutWindow(eventWindow); if (!possibleStrutWindows.contains(eventWindow)) { possibleStrutWindows.append(eventWindow); } } if (dirtyProperties || dirtyProperties2) { emit s_q->windowChanged(eventWindow); emit s_q->windowChanged(eventWindow, dirtyProperties, dirtyProperties2); #ifndef KWINDOWSYSTEM_NO_DEPRECATED unsigned long dirty[ 2 ] = {dirtyProperties, dirtyProperties2}; emit s_q->windowChanged(eventWindow, dirty); emit s_q->windowChanged(eventWindow, dirtyProperties); #endif if ((dirtyProperties & NET::WMStrut) != 0) { emit s_q->strutChanged(); } } } return false; } bool NETEventFilter::removeStrutWindow(WId w) { for (QList< StrutData >::Iterator it = strutWindows.begin(); it != strutWindows.end(); ++it) if ((*it).window == w) { strutWindows.erase(it); return true; } return false; } void NETEventFilter::updateStackingOrder() { stackingOrder.clear(); for (int i = 0; i < clientListStackingCount(); i++) { stackingOrder.append(clientListStacking()[i]); } } void NETEventFilter::addClient(xcb_window_t w) { KWindowSystem *s_q = KWindowSystem::self(); if ((what >= KWindowSystemPrivateX11::INFO_WINDOWS)) { xcb_connection_t *c = QX11Info::connection(); QScopedPointer attr(xcb_get_window_attributes_reply(c, xcb_get_window_attributes_unchecked(c, w), nullptr)); uint32_t events = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY; if (!attr.isNull()) { events = events | attr->your_event_mask; } xcb_change_window_attributes(c, w, XCB_CW_EVENT_MASK, &events); } bool emit_strutChanged = false; if (strutSignalConnected) { NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2()); NETStrut strut = info.strut(); if (strut.left || strut.top || strut.right || strut.bottom) { strutWindows.append(StrutData(w, strut, info.desktop())); emit_strutChanged = true; } } else { possibleStrutWindows.append(w); } windows.append(w); emit s_q->windowAdded(w); if (emit_strutChanged) { emit s_q->strutChanged(); } } void NETEventFilter::removeClient(xcb_window_t w) { KWindowSystem *s_q = KWindowSystem::self(); bool emit_strutChanged = removeStrutWindow(w); if (strutSignalConnected && possibleStrutWindows.contains(w)) { NETWinInfo info(QX11Info::connection(), w, QX11Info::appRootWindow(), NET::WMStrut, NET::Properties2()); NETStrut strut = info.strut(); if (strut.left || strut.top || strut.right || strut.bottom) { emit_strutChanged = true; } } possibleStrutWindows.removeAll(w); windows.removeAll(w); emit s_q->windowRemoved(w); if (emit_strutChanged) { emit s_q->strutChanged(); } } bool NETEventFilter::mapViewport() { // compiz claims support even though it doesn't use virtual desktops :( // if( isSupported( NET::DesktopViewport ) && !isSupported( NET::NumberOfDesktops )) // this test is duplicated in KWindowSystem::mapViewport() if (isSupported(NET::DesktopViewport) && numberOfDesktops(true) <= 1 && (desktopGeometry().width > displayWidth() || desktopGeometry().height > displayHeight())) { return true; } return false; } static bool atoms_created = false; static Atom _wm_protocols; static Atom _wm_change_state; static Atom kwm_utf8_string; static void create_atoms() { if (!atoms_created) { const int max = 20; Atom *atoms[max]; const char *names[max]; Atom atoms_return[max]; int n = 0; atoms[n] = &_wm_protocols; names[n++] = "WM_PROTOCOLS"; atoms[n] = &_wm_change_state; names[n++] = "WM_CHANGE_STATE"; atoms[n] = &kwm_utf8_string; names[n++] = "UTF8_STRING"; char net_wm_cm_name[ 100 ]; sprintf(net_wm_cm_name, "_NET_WM_CM_S%d", QX11Info::appScreen()); atoms[n] = &net_wm_cm; names[n++] = net_wm_cm_name; // we need a const_cast for the shitty X API XInternAtoms(QX11Info::display(), const_cast(names), n, false, atoms_return); for (int i = 0; i < n; i++) { *atoms[i] = atoms_return[i]; } atoms_created = True; } } // optimalization - create KWindowSystemPrivate only when needed and only for what is needed void KWindowSystemPrivateX11::connectNotify(const QMetaMethod &signal) { FilterInfo what = INFO_BASIC; if (signal == QMetaMethod::fromSignal(&KWindowSystem::workAreaChanged)) { what = INFO_WINDOWS; } else if (signal == QMetaMethod::fromSignal(&KWindowSystem::strutChanged)) { what = INFO_WINDOWS; } else if (signal == QMetaMethod::fromSignal(static_cast(&KWindowSystem::windowChanged))) { what = INFO_WINDOWS; } #ifndef KWINDOWSYSTEM_NO_DEPRECATED else if (signal == QMetaMethod::fromSignal(static_cast(&KWindowSystem::windowChanged))) { what = INFO_WINDOWS; } else if (signal == QMetaMethod::fromSignal(static_cast(&KWindowSystem::windowChanged))) { what = INFO_WINDOWS; } #endif else if (signal == QMetaMethod::fromSignal(static_cast(&KWindowSystem::windowChanged))) { what = INFO_WINDOWS; } init(what); NETEventFilter *const s_d = s_d_func(); if (!s_d->strutSignalConnected && signal == QMetaMethod::fromSignal(&KWindowSystem::strutChanged)) { s_d->strutSignalConnected = true; } } // WARNING // you have to call s_d_func() again after calling this function if you want a valid pointer! void KWindowSystemPrivateX11::init(FilterInfo what) { NETEventFilter *const s_d = s_d_func(); if (what >= INFO_WINDOWS) { what = INFO_WINDOWS; } else { what = INFO_BASIC; } if (!s_d || s_d->what < what) { const bool wasCompositing = s_d ? s_d->compositingEnabled : false; MainThreadInstantiator instantiator(what); NETEventFilter *filter; if (instantiator.thread() == QCoreApplication::instance()->thread()) { filter = instantiator.createNETEventFilter(); } else { // the instantiator is not in the main app thread, which implies // we are being called in a thread that is not the main app thread // so we move the instantiator to the main app thread and invoke // the method with a blocking call instantiator.moveToThread(QCoreApplication::instance()->thread()); QMetaObject::invokeMethod(&instantiator, "createNETEventFilter", Qt::BlockingQueuedConnection, Q_RETURN_ARG(NETEventFilter *, filter)); } d.reset(filter); d->activate(); if (wasCompositing != s_d_func()->compositingEnabled) { emit KWindowSystem::self()->compositingChanged(s_d_func()->compositingEnabled); } } } QList KWindowSystemPrivateX11::windows() { init(INFO_BASIC); return s_d_func()->windows; } QList KWindowSystemPrivateX11::stackingOrder() { init(INFO_BASIC); return s_d_func()->stackingOrder; } int KWindowSystemPrivateX11::currentDesktop() { if (!QX11Info::connection()) { return 1; } if (mapViewport()) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); NETPoint p = s_d->desktopViewport(s_d->currentDesktop(true)); return viewportToDesktop(QPoint(p.x, p.y)); } NETEventFilter *const s_d = s_d_func(); if (s_d) { return s_d->currentDesktop(true); } NETRootInfo info(QX11Info::connection(), NET::CurrentDesktop); return info.currentDesktop(true); } int KWindowSystemPrivateX11::numberOfDesktops() { if (!QX11Info::connection()) { return 1; } if (mapViewport()) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); NETSize s = s_d->desktopGeometry(); return s.width / displayWidth() * s.height / displayHeight(); } NETEventFilter *const s_d = s_d_func(); if (s_d) { return s_d->numberOfDesktops(true); } NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops); return info.numberOfDesktops(true); } void KWindowSystemPrivateX11::setCurrentDesktop(int desktop) { if (mapViewport()) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); NETRootInfo info(QX11Info::connection(), NET::Properties()); QPoint pos = desktopToViewport(desktop, true); NETPoint p; p.x = pos.x(); p.y = pos.y(); info.setDesktopViewport(s_d->currentDesktop(true), p); return; } NETRootInfo info(QX11Info::connection(), NET::Properties()); info.setCurrentDesktop(desktop, true); } void KWindowSystemPrivateX11::setOnAllDesktops(WId win, bool b) { if (mapViewport()) { if (b) { setState(win, NET::Sticky); } else { clearState(win, NET::Sticky); } return; } NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2()); if (b) { info.setDesktop(NETWinInfo::OnAllDesktops, true); } else if (info.desktop(true) == NETWinInfo::OnAllDesktops) { NETRootInfo rinfo(QX11Info::connection(), NET::CurrentDesktop); info.setDesktop(rinfo.currentDesktop(true), true); } } void KWindowSystemPrivateX11::setOnDesktop(WId win, int desktop) { if (mapViewport()) { if (desktop == NET::OnAllDesktops) { return setOnAllDesktops(win, true); } else { clearState(win, NET::Sticky); } init(INFO_BASIC); QPoint p = desktopToViewport(desktop, false); Window dummy; int x, y; unsigned int w, h, b, dp; XGetGeometry(QX11Info::display(), win, &dummy, &x, &y, &w, &h, &b, &dp); // get global position XTranslateCoordinates(QX11Info::display(), win, QX11Info::appRootWindow(), 0, 0, &x, &y, &dummy); x += w / 2; // center y += h / 2; // transform to coordinates on the current "desktop" x = x % displayWidth(); y = y % displayHeight(); if (x < 0) { x = x + displayWidth(); } if (y < 0) { y = y + displayHeight(); } x += p.x(); // move to given "desktop" y += p.y(); x -= w / 2; // from center back to topleft y -= h / 2; p = constrainViewportRelativePosition(QPoint(x, y)); int flags = (NET::FromTool << 12) | (0x03 << 8) | 10; // from tool(?), x/y, static gravity NETEventFilter *const s_d = s_d_func(); s_d->moveResizeWindowRequest(win, flags, p.x(), p.y(), w, h); return; } NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMDesktop, NET::Properties2()); info.setDesktop(desktop, true); } void KWindowSystemPrivateX11::setOnActivities(WId win, const QStringList &activities) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::WM2Activities); info.setActivities(activities.join(QLatin1Char(',')).toLatin1().constData()); } WId KWindowSystemPrivateX11::activeWindow() { NETEventFilter *const s_d = s_d_func(); if (s_d) { return s_d->activeWindow(); } NETRootInfo info(QX11Info::connection(), NET::ActiveWindow); return info.activeWindow(); } void KWindowSystemPrivateX11::activateWindow(WId win, long time) { NETRootInfo info(QX11Info::connection(), NET::Properties()); if (time == 0) { time = QX11Info::appUserTime(); } info.setActiveWindow(win, NET::FromApplication, time, QGuiApplication::focusWindow() ? QGuiApplication::focusWindow()->winId() : 0); } void KWindowSystemPrivateX11::forceActiveWindow(WId win, long time) { NETRootInfo info(QX11Info::connection(), NET::Properties()); if (time == 0) { time = QX11Info::appTime(); } info.setActiveWindow(win, NET::FromTool, time, 0); } void KWindowSystemPrivateX11::demandAttention(WId win, bool set) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); info.setState(set ? NET::DemandsAttention : NET::States(), NET::DemandsAttention); } #ifndef KWINDOWSYSTEM_NO_DEPRECATED WId KWindowSystemPrivateX11::transientFor(WId win) { KWindowInfo info(win, NET::Properties(), NET::WM2TransientFor); return info.transientFor(); } #endif #ifndef KWINDOWSYSTEM_NO_DEPRECATED WId KWindowSystemPrivateX11::groupLeader(WId win) { KWindowInfo info(win, NET::Properties(), NET::WM2GroupLeader); return info.groupLeader(); } #endif QPixmap KWindowSystemPrivateX11::icon(WId win, int width, int height, bool scale, int flags) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMIcon, NET::WM2WindowClass | NET::WM2IconPixmap); return iconFromNetWinInfo(width, height, scale, flags, &info); } QPixmap KWindowSystemPrivateX11::iconFromNetWinInfo(int width, int height, bool scale, int flags, NETWinInfo *info) { QPixmap result; if (!info) { return result; } if (flags & KWindowSystem::NETWM) { NETIcon ni = info->icon(width, height); if (ni.data && ni.size.width > 0 && ni.size.height > 0) { QImage img((uchar *) ni.data, (int) ni.size.width, (int) ni.size.height, QImage::Format_ARGB32); if (scale && width > 0 && height > 0 && img.size() != QSize(width, height) && !img.isNull()) { img = img.scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } if (!img.isNull()) { result = QPixmap::fromImage(img); } return result; } } if (flags & KWindowSystem::WMHints) { xcb_pixmap_t p = info->icccmIconPixmap(); xcb_pixmap_t p_mask = info->icccmIconPixmapMask(); if (p != XCB_PIXMAP_NONE) { QPixmap pm = KXUtils::createPixmapFromHandle(info->xcbConnection(), p, p_mask); if (scale && width > 0 && height > 0 && !pm.isNull() && (pm.width() != width || pm.height() != height)) { result = QPixmap::fromImage(pm.toImage().scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { result = pm; } } } // Since width can be any arbitrary size, but the icons cannot, // take the nearest value for best results (ignoring 22 pixel // icons as they don't exist for apps): int iconWidth; if (width < 24) { iconWidth = 16; } else if (width < 40) { iconWidth = 32; } else if (width < 56) { iconWidth = 48; } else if (width < 96) { iconWidth = 64; } else if (width < 192) { iconWidth = 128; } else { iconWidth = 256; } if (flags & KWindowSystem::ClassHint) { // Try to load the icon from the classhint if the app didn't specify // its own: if (result.isNull()) { const QIcon icon = QIcon::fromTheme(QString::fromUtf8(info->windowClassClass()).toLower()); const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(iconWidth, iconWidth); if (scale && !pm.isNull()) { result = QPixmap::fromImage(pm.toImage().scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { result = pm; } } } if (flags & KWindowSystem::XApp) { // If the icon is still a null pixmap, load the icon for X applications // as a last resort: if (result.isNull()) { const QIcon icon = QIcon::fromTheme(QLatin1String("xorg")); const QPixmap pm = icon.isNull() ? QPixmap() : icon.pixmap(iconWidth, iconWidth); if (scale && !pm.isNull()) { result = QPixmap::fromImage(pm.toImage().scaled(width, height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); } else { result = pm; } } } return result; } void KWindowSystemPrivateX11::setIcons(WId win, const QPixmap &icon, const QPixmap &miniIcon) { if (icon.isNull()) { return; } NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); QImage img = icon.toImage().convertToFormat(QImage::Format_ARGB32); NETIcon ni; ni.size.width = img.size().width(); ni.size.height = img.size().height(); ni.data = (unsigned char *) img.bits(); info.setIcon(ni, true); if (miniIcon.isNull()) { return; } img = miniIcon.toImage().convertToFormat(QImage::Format_ARGB32); if (img.isNull()) { return; } ni.size.width = img.size().width(); ni.size.height = img.size().height(); ni.data = (unsigned char *) img.bits(); info.setIcon(ni, false); } void KWindowSystemPrivateX11::setType(WId win, NET::WindowType windowType) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); info.setWindowType(windowType); } void KWindowSystemPrivateX11::setState(WId win, NET::States state) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); info.setState(state, state); } void KWindowSystemPrivateX11::clearState(WId win, NET::States state) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::WMState, NET::Properties2()); info.setState(NET::States(), state); } // enum values for ICCCM 4.1.2.4 and 4.1.4, defined to not depend on xcb-icccm enum { _ICCCM_WM_STATE_WITHDRAWN = 0, _ICCCM_WM_STATE_NORMAL = 1, _ICCCM_WM_STATE_ICONIC = 3 }; void KWindowSystemPrivateX11::minimizeWindow(WId win) { create_atoms(); // as described in ICCCM 4.1.4 xcb_client_message_event_t ev; memset(&ev, 0, sizeof(ev)); ev.response_type = XCB_CLIENT_MESSAGE; ev.window = win; ev.type = _wm_change_state; ev.format = 32; ev.data.data32[0] = _ICCCM_WM_STATE_ICONIC; ev.data.data32[1] = 0; ev.data.data32[2] = 0; ev.data.data32[3] = 0; ev.data.data32[4] = 0; xcb_send_event(QX11Info::connection(), false, QX11Info::appRootWindow(), XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, reinterpret_cast(&ev)); } void KWindowSystemPrivateX11::unminimizeWindow(WId win) { xcb_map_window(QX11Info::connection(), win); } void KWindowSystemPrivateX11::raiseWindow(WId win) { NETRootInfo info(QX11Info::connection(), NET::Supported); if (info.isSupported(NET::WM2RestackWindow)) { info.restackRequest(win, NET::FromTool, XCB_WINDOW_NONE, XCB_STACK_MODE_ABOVE, QX11Info::appUserTime()); } else { const uint32_t values[] = { XCB_STACK_MODE_ABOVE }; xcb_configure_window(QX11Info::connection(), win, XCB_CONFIG_WINDOW_STACK_MODE, values); } } void KWindowSystemPrivateX11::lowerWindow(WId win) { NETRootInfo info(QX11Info::connection(), NET::Supported); if (info.isSupported(NET::WM2RestackWindow)) { info.restackRequest(win, NET::FromTool, XCB_WINDOW_NONE, XCB_STACK_MODE_BELOW, QX11Info::appUserTime()); } else { const uint32_t values[] = { XCB_STACK_MODE_BELOW }; xcb_configure_window(QX11Info::connection(), win, XCB_CONFIG_WINDOW_STACK_MODE, values); } } bool KWindowSystemPrivateX11::compositingActive() { init(INFO_BASIC); if (s_d_func()->haveXfixes) { return s_d_func()->compositingEnabled; } else { create_atoms(); return XGetSelectionOwner(QX11Info::display(), net_wm_cm); } } QRect KWindowSystemPrivateX11::workArea(int desktop) { init(INFO_BASIC); int desk = (desktop > 0 && desktop <= (int) s_d_func()->numberOfDesktops()) ? desktop : currentDesktop(); if (desk <= 0) { return displayGeometry(); } NETRect r = s_d_func()->workArea(desk); if (r.size.width <= 0 || r.size.height <= 0) { // not set return displayGeometry(); } return QRect(r.pos.x, r.pos.y, r.size.width, r.size.height); } QRect KWindowSystemPrivateX11::workArea(const QList &exclude, int desktop) { init(INFO_WINDOWS); // invalidates s_d_func's return value NETEventFilter *const s_d = s_d_func(); QRect all = displayGeometry(); QRect a = all; if (desktop == -1) { desktop = s_d->currentDesktop(); } QList::ConstIterator it1; for (it1 = s_d->windows.constBegin(); it1 != s_d->windows.constEnd(); ++it1) { if (exclude.contains(*it1)) { continue; } // Kicker (very) extensively calls this function, causing hundreds of roundtrips just // to repeatedly find out struts of all windows. Therefore strut values for strut // windows are cached here. NETStrut strut; auto it2 = s_d->strutWindows.begin(); for (; it2 != s_d->strutWindows.end(); ++it2) if ((*it2).window == *it1) { break; } if (it2 != s_d->strutWindows.end()) { if (!((*it2).desktop == desktop || (*it2).desktop == NETWinInfo::OnAllDesktops)) { continue; } strut = (*it2).strut; } else if (s_d->possibleStrutWindows.contains(*it1)) { NETWinInfo info(QX11Info::connection(), (*it1), QX11Info::appRootWindow(), NET::WMStrut | NET::WMDesktop, NET::Properties2()); strut = info.strut(); s_d->possibleStrutWindows.removeAll(*it1); s_d->strutWindows.append(NETEventFilter::StrutData(*it1, info.strut(), info.desktop())); if (!(info.desktop() == desktop || info.desktop() == NETWinInfo::OnAllDesktops)) { continue; } } else { continue; // not a strut window } QRect r = all; if (strut.left > 0) { r.setLeft(r.left() + (int) strut.left); } if (strut.top > 0) { r.setTop(r.top() + (int) strut.top); } if (strut.right > 0) { r.setRight(r.right() - (int) strut.right); } if (strut.bottom > 0) { r.setBottom(r.bottom() - (int) strut.bottom); } a = a.intersected(r); } return a; } QString KWindowSystemPrivateX11::desktopName(int desktop) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); bool isDesktopSane = (desktop > 0 && desktop <= (int) s_d->numberOfDesktops()); const char *name = s_d->desktopName(isDesktopSane ? desktop : currentDesktop()); if (name && name[0]) { return QString::fromUtf8(name); } return KWindowSystem::tr("Desktop %1").arg(desktop); } void KWindowSystemPrivateX11::setDesktopName(int desktop, const QString &name) { NETEventFilter *const s_d = s_d_func(); if (desktop <= 0 || desktop > (int) numberOfDesktops()) { desktop = currentDesktop(); } if (s_d) { s_d->setDesktopName(desktop, name.toUtf8().constData()); return; } NETRootInfo info(QX11Info::connection(), NET::Properties()); info.setDesktopName(desktop, name.toUtf8().constData()); } bool KWindowSystemPrivateX11::showingDesktop() { init(INFO_BASIC); return s_d_func()->showingDesktop(); } void KWindowSystemPrivateX11::setShowingDesktop(bool showing) { NETRootInfo info(QX11Info::connection(), NET::Properties(), NET::WM2ShowingDesktop); info.setShowingDesktop(showing); } void KWindowSystemPrivateX11::setUserTime(WId win, long time) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); info.setUserTime(time); } void KWindowSystemPrivateX11::setExtendedStrut(WId win, int left_width, int left_start, int left_end, int right_width, int right_start, int right_end, int top_width, int top_start, int top_end, int bottom_width, int bottom_start, int bottom_end) { NETWinInfo info(QX11Info::connection(), win, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); NETExtendedStrut strut; strut.left_width = left_width; strut.right_width = right_width; strut.top_width = top_width; strut.bottom_width = bottom_width; strut.left_start = left_start; strut.left_end = left_end; strut.right_start = right_start; strut.right_end = right_end; strut.top_start = top_start; strut.top_end = top_end; strut.bottom_start = bottom_start; strut.bottom_end = bottom_end; info.setExtendedStrut(strut); NETStrut oldstrut; oldstrut.left = left_width; oldstrut.right = right_width; oldstrut.top = top_width; oldstrut.bottom = bottom_width; info.setStrut(oldstrut); } void KWindowSystemPrivateX11::setStrut(WId win, int left, int right, int top, int bottom) { int w = displayWidth(); int h = displayHeight(); setExtendedStrut(win, left, 0, left != 0 ? w : 0, right, 0, right != 0 ? w : 0, top, 0, top != 0 ? h : 0, bottom, 0, bottom != 0 ? h : 0); } bool KWindowSystemPrivateX11::icccmCompliantMappingState() { static enum { noidea, yes, no } wm_is_1_2_compliant = noidea; if (wm_is_1_2_compliant == noidea) { NETRootInfo info(QX11Info::connection(), NET::Supported); wm_is_1_2_compliant = info.isSupported(NET::Hidden) ? yes : no; } return wm_is_1_2_compliant == yes; } bool KWindowSystemPrivateX11::allowedActionsSupported() { static enum { noidea, yes, no } wm_supports_allowed_actions = noidea; if (wm_supports_allowed_actions == noidea) { NETRootInfo info(QX11Info::connection(), NET::Supported); wm_supports_allowed_actions = info.isSupported(NET::WM2AllowedActions) ? yes : no; } return wm_supports_allowed_actions == yes; } QString KWindowSystemPrivateX11::readNameProperty(WId win, unsigned long atom) { XTextProperty tp; char **text = nullptr; int count; QString result; if (XGetTextProperty(QX11Info::display(), win, &tp, atom) != 0 && tp.value != nullptr) { create_atoms(); if (tp.encoding == kwm_utf8_string) { result = QString::fromUtf8((const char *) tp.value); } else if (XmbTextPropertyToTextList(QX11Info::display(), &tp, &text, &count) == Success && text != nullptr && count > 0) { result = QString::fromLocal8Bit(text[0]); } else if (tp.encoding == XA_STRING) { result = QString::fromLocal8Bit((const char *) tp.value); } if (text != nullptr) { XFreeStringList(text); } XFree(tp.value); } return result; } void KWindowSystemPrivateX11::allowExternalProcessWindowActivation(int pid) { // Normally supported by X11, but may depend on some window managers ? Q_UNUSED(pid) } void KWindowSystemPrivateX11::setBlockingCompositing(WId window, bool active) { NETWinInfo info(QX11Info::connection(), window, QX11Info::appRootWindow(), NET::Properties(), NET::Properties2()); info.setBlockingCompositing(active); } bool KWindowSystemPrivateX11::mapViewport() { NETEventFilter *const s_d = s_d_func(); if (s_d) { return s_d->mapViewport(); } // avoid creating KWindowSystemPrivate NETRootInfo infos(QX11Info::connection(), NET::Supported); if (!infos.isSupported(NET::DesktopViewport)) { return false; } NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::CurrentDesktop | NET::DesktopGeometry); if (info.numberOfDesktops(true) <= 1 && (info.desktopGeometry().width > displayWidth() || info.desktopGeometry().height > displayHeight())) { return true; } return false; } int KWindowSystemPrivateX11::viewportToDesktop(const QPoint &p) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); NETSize s = s_d->desktopGeometry(); QSize vs(displayWidth(), displayHeight()); int xs = s.width / vs.width(); int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width(); int ys = s.height / vs.height(); int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height(); return y * xs + x + 1; } int KWindowSystemPrivateX11::viewportWindowToDesktop(const QRect &r) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); QPoint p = r.center(); // make absolute p = QPoint(p.x() + s_d->desktopViewport(s_d->currentDesktop(true)).x, p.y() + s_d->desktopViewport(s_d->currentDesktop(true)).y); NETSize s = s_d->desktopGeometry(); QSize vs(displayWidth(), displayHeight()); int xs = s.width / vs.width(); int x = p.x() < 0 ? 0 : p.x() >= s.width ? xs - 1 : p.x() / vs.width(); int ys = s.height / vs.height(); int y = p.y() < 0 ? 0 : p.y() >= s.height ? ys - 1 : p.y() / vs.height(); return y * xs + x + 1; } QPoint KWindowSystemPrivateX11::desktopToViewport(int desktop, bool absolute) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); NETSize s = s_d->desktopGeometry(); QSize vs(displayWidth(), displayHeight()); int xs = s.width / vs.width(); int ys = s.height / vs.height(); if (desktop <= 0 || desktop > xs * ys) { return QPoint(0, 0); } --desktop; QPoint ret(vs.width() * (desktop % xs), vs.height() * (desktop / xs)); if (!absolute) { ret = QPoint(ret.x() - s_d->desktopViewport(s_d->currentDesktop(true)).x, ret.y() - s_d->desktopViewport(s_d->currentDesktop(true)).y); if (ret.x() >= s.width) { ret.setX(ret.x() - s.width); } if (ret.x() < 0) { ret.setX(ret.x() + s.width); } if (ret.y() >= s.height) { ret.setY(ret.y() - s.height); } if (ret.y() < 0) { ret.setY(ret.y() + s.height); } } return ret; } QPoint KWindowSystemPrivateX11::constrainViewportRelativePosition(const QPoint &pos) { init(INFO_BASIC); NETEventFilter *const s_d = s_d_func(); NETSize s = s_d->desktopGeometry(); NETPoint c = s_d->desktopViewport(s_d->currentDesktop(true)); int x = (pos.x() + c.x) % s.width; int y = (pos.y() + c.y) % s.height; if (x < 0) { x += s.width; } if (y < 0) { y += s.height; } return QPoint(x - c.x, y - c.y); } #include "moc_kwindowsystem_p_x11.cpp" diff --git a/tests/setmainwindowtest.cpp b/tests/setmainwindowtest.cpp index 205b7ba..374cace 100644 --- a/tests/setmainwindowtest.cpp +++ b/tests/setmainwindowtest.cpp @@ -1,82 +1,83 @@ /* This file is part of the KDE libraries Copyright 2013 Aurélien Gâteau This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include class Window : public QWidget { public: Window(); private: void showWindow(); QLabel *m_label; }; Window::Window() { QPushButton *button = new QPushButton("Start Test"); connect(button, &QPushButton::clicked, this, &Window::showWindow); m_label = new QLabel; m_label->setWordWrap(true); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(button); layout->addWidget(m_label); setMinimumSize(200, 150); } void Window::showWindow() { // Wait for user to select another window m_label->setText("Click on another window to show a dialog on it"); WId us = winId(); while (KWindowSystem::activeWindow() == us) { QApplication::processEvents(); } // Get the id of the selected window WId id = KWindowSystem::activeWindow(); m_label->setText(QString("Showing dialog on window with id: %1.").arg(id)); // Create test dialog QDialog *dialog = new QDialog; dialog->setAttribute(Qt::WA_DeleteOnClose, true); QHBoxLayout *layout = new QHBoxLayout(dialog); layout->addWidget(new QLabel("Test Dialog.\nYou should not be able to bring the parent window on top of me.")); // Show it - KWindowSystem::setMainWindow(dialog, id); + dialog->setAttribute(Qt::WA_NativeWindow, true); + KWindowSystem::setMainWindow(dialog->windowHandle(), id); dialog->exec(); m_label->setText(QString()); } int main(int argc, char **argv) { QApplication app(argc, argv); Window window; window.show(); return app.exec(); }