diff --git a/greeter/autotests/killtest.cpp b/greeter/autotests/killtest.cpp index f3e14a9..e53f30c 100644 --- a/greeter/autotests/killtest.cpp +++ b/greeter/autotests/killtest.cpp @@ -1,181 +1,180 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2014 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ // own #include // Qt -#include #include // system #include Q_DECLARE_METATYPE(QProcess::ExitStatus) class KillTest : public QObject { Q_OBJECT private Q_SLOTS: void testKill_data(); void testKill(); void testImmediateKill_data(); void testImmediateKill(); }; void KillTest::testKill_data() { QTest::addColumn("signal"); QTest::addColumn("expectedQuit"); QTest::addColumn("exitStatus"); QTest::newRow("SIGHUP") << SIGHUP << true << QProcess::CrashExit; QTest::newRow("SIGINT") << SIGINT << true << QProcess::CrashExit; QTest::newRow("SIGQUIT") << SIGQUIT << true << QProcess::CrashExit; QTest::newRow("SIGILL") << SIGILL << true << QProcess::CrashExit; QTest::newRow("SIGTRAP") << SIGTRAP << true << QProcess::CrashExit; QTest::newRow("SIGABRT") << SIGABRT << true << QProcess::CrashExit; QTest::newRow("SIGIOT") << SIGIOT << true << QProcess::CrashExit; QTest::newRow("SIGBUS") << SIGBUS << true << QProcess::CrashExit; QTest::newRow("SIGFPE") << SIGFPE << true << QProcess::CrashExit; QTest::newRow("SIGKILL") << SIGKILL << true << QProcess::CrashExit; QTest::newRow("SIGUSR1") << SIGUSR1 << false << QProcess::CrashExit; QTest::newRow("SIGSEGV") << SIGSEGV << true << QProcess::CrashExit; QTest::newRow("SIGUSR2") << SIGUSR2 << true << QProcess::CrashExit; QTest::newRow("SIGPIPE") << SIGPIPE << true << QProcess::CrashExit; QTest::newRow("SIGALRM") << SIGALRM << true << QProcess::CrashExit; QTest::newRow("SIGTERM") << SIGTERM << true << QProcess::NormalExit; // ignore //QTest::newRow("SIGCHLD") << SIGCHLD; //QTest::newRow("SIGCONT") << SIGCONT; //QTest::newRow("SIGSTOP") << SIGSTOP; //QTest::newRow("SIGTSTP") << SIGTSTP; //QTest::newRow("SIGTTIN") << SIGTTIN; //QTest::newRow("SIGTTOU") << SIGTTOU; //QTest::newRow("SIGURG") << SIGURG; //QTest::newRow("SIGXCPU") << SIGXCPU; //QTest::newRow("SIGXFSZ") << SIGXFSZ; //QTest::newRow("SIGVTALRM") << SIGVTALRM; //QTest::newRow("SIGPROF") << SIGPROF; //QTest::newRow("SIGWINCH") << SIGWINCH; //QTest::newRow("SIGIO") << SIGIO; //QTest::newRow("SIGPWR") << SIGPWR; QTest::newRow("SIGSYS") << SIGSYS << true << QProcess::CrashExit; #ifdef Q_OS_LINUX #ifdef SIGUNUSED QTest::newRow("SIGUNUSED") << SIGUNUSED << true << QProcess::CrashExit; #endif #ifdef SIGSTKFLT QTest::newRow("SIGSTKFLT") << SIGSTKFLT << true << QProcess::CrashExit; #endif #endif } void KillTest::testKill() { QProcess greeter(this); greeter.setReadChannel(QProcess::StandardOutput); greeter.start(QStringLiteral(KSCREENLOCKER_GREET_BIN), QStringList({QStringLiteral("--testing")})); QVERIFY(greeter.waitForStarted()); // wait some time till it's really set up QTest::qSleep(5000); // now kill QFETCH(int, signal); kill(greeter.pid(), signal); QFETCH(bool, expectedQuit); QCOMPARE(greeter.waitForFinished(1000), expectedQuit); if (greeter.state() == QProcess::Running) { greeter.terminate(); QVERIFY(greeter.waitForFinished()); } else { QFETCH(QProcess::ExitStatus, exitStatus); QCOMPARE(greeter.exitStatus(), exitStatus); if (greeter.exitStatus() == QProcess::NormalExit) { // exit code is only valid for NormalExit QCOMPARE(greeter.exitCode(), 1); } } } void KillTest::testImmediateKill_data() { QTest::addColumn("signal"); QTest::newRow("SIGHUP") << SIGHUP; QTest::newRow("SIGINT") << SIGINT; QTest::newRow("SIGQUIT") << SIGQUIT; QTest::newRow("SIGILL") << SIGILL; QTest::newRow("SIGTRAP") << SIGTRAP; QTest::newRow("SIGABRT") << SIGABRT; QTest::newRow("SIGIOT") << SIGIOT; QTest::newRow("SIGBUS") << SIGBUS; QTest::newRow("SIGFPE") << SIGFPE; QTest::newRow("SIGKILL") << SIGKILL; QTest::newRow("SIGUSR1") << SIGUSR1; QTest::newRow("SIGSEGV") << SIGSEGV; QTest::newRow("SIGUSR2") << SIGUSR2; QTest::newRow("SIGPIPE") << SIGPIPE; QTest::newRow("SIGALRM") << SIGALRM; QTest::newRow("SIGTERM") << SIGTERM; // ignore //QTest::newRow("SIGCHLD") << SIGCHLD; //QTest::newRow("SIGCONT") << SIGCONT; //QTest::newRow("SIGSTOP") << SIGSTOP; //QTest::newRow("SIGTSTP") << SIGTSTP; //QTest::newRow("SIGTTIN") << SIGTTIN; //QTest::newRow("SIGTTOU") << SIGTTOU; //QTest::newRow("SIGURG") << SIGURG; //QTest::newRow("SIGXCPU") << SIGXCPU; //QTest::newRow("SIGXFSZ") << SIGXFSZ; //QTest::newRow("SIGVTALRM") << SIGVTALRM; //QTest::newRow("SIGPROF") << SIGPROF; //QTest::newRow("SIGWINCH") << SIGWINCH; //QTest::newRow("SIGIO") << SIGIO; //QTest::newRow("SIGPWR") << SIGPWR; QTest::newRow("SIGSYS") << SIGSYS; #ifdef Q_OS_LINUX #ifdef SIGSTKFLT QTest::newRow("SIGSTKFLT") << SIGSTKFLT; #endif #ifdef SIGUNUSED QTest::newRow("SIGUNUSED") << SIGUNUSED; #endif #endif } void KillTest::testImmediateKill() { // this test ensures that the greeter indicates crashexit when a signal is sent to the greeter // before it had time to properly setup QProcess greeter(this); greeter.start(QStringLiteral(KSCREENLOCKER_GREET_BIN), QStringList({QStringLiteral("--testing")})); QVERIFY(greeter.waitForStarted()); // now kill QFETCH(int, signal); kill(greeter.pid(), signal); QVERIFY(greeter.waitForFinished()); QCOMPARE(greeter.exitStatus(), QProcess::CrashExit); } QTEST_MAIN(KillTest) #include "killtest.moc" diff --git a/greeter/greeterapp.h b/greeter/greeterapp.h index 14c795e..6f065db 100644 --- a/greeter/greeterapp.h +++ b/greeter/greeterapp.h @@ -1,127 +1,126 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #ifndef SCREENLOCKER_GREETERAPP_H #define SCREENLOCKER_GREETERAPP_H #include - #include -#include +#include namespace KWayland { namespace Client { class ConnectionThread; class Registry; class PlasmaShell; } } namespace KQuickAddons { class QuickViewSharedEngine; } class Authenticator; struct org_kde_ksld; namespace ScreenLocker { class WallpaperIntegration; class LnFIntegration; class UnlockApp : public QGuiApplication { Q_OBJECT public: explicit UnlockApp(int &argc, char **argv); ~UnlockApp() override; void setTesting(bool enable); void setTheme(const QString &theme); void setImmediateLock(bool immediateLock); void lockImmediately(); void setGraceTime(int milliseconds); void setNoLock(bool noLock); void setKsldSocket(int socket); void setDefaultToSwitchUser(bool defaultToSwitchUser); void osdProgress(const QString &icon, int percent, const QString &additionalText); void osdText(const QString &icon, const QString &additionalText); void updateCanSuspend(bool set); void updateCanHibernate(bool set); bool supportsSeccomp() const { return m_supportsSeccomp; } public Q_SLOTS: void desktopResized(); protected: bool eventFilter(QObject *obj, QEvent *event) override; private Q_SLOTS: void resetRequestIgnore(); void suspendToRam(); void suspendToDisk(); void getFocus(); void markViewsAsVisible(KQuickAddons::QuickViewSharedEngine *view); void setLockedPropertyOnViews(); private: void initialize(); void initializeWayland(); void shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from); void loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view); Authenticator *createAuthenticator(); QWindow *getActiveScreen(); QString m_packageName; QUrl m_mainQmlPath; QList m_views; QTimer *m_resetRequestIgnoreTimer; QTimer *m_delayedLockTimer; KPackage::Package m_package; bool m_testing; bool m_ignoreRequests; bool m_immediateLock; bool m_runtimeInitialized; Authenticator *m_authenticator; int m_graceTime; bool m_noLock; bool m_defaultToSwitchUser; bool m_canSuspend = false; bool m_canHibernate = false; KWayland::Client::ConnectionThread *m_ksldConnection = nullptr; KWayland::Client::Registry *m_ksldRegistry = nullptr; QThread *m_ksldConnectionThread = nullptr; org_kde_ksld *m_ksldInterface = nullptr; KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; WallpaperIntegration *m_wallpaperIntegration; LnFIntegration *m_lnfIntegration; bool m_supportsSeccomp = false; }; } // namespace #endif // SCREENLOCKER_GREETERAPP_H diff --git a/greeter/kwinglplatform.cpp b/greeter/kwinglplatform.cpp index ca7f17c..bf9ce72 100644 --- a/greeter/kwinglplatform.cpp +++ b/greeter/kwinglplatform.cpp @@ -1,1070 +1,1067 @@ /******************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2010 Fredrik Höglund This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "kwinglplatform.h" #include -#include -#include -#include #include #include #include #include #include namespace KWin { GLPlatform *GLPlatform::s_platform = nullptr; static qint64 parseVersionString(const QByteArray &version) { // Skip any leading non digit int start = 0; while (start < version.length() && !QChar::fromLatin1(version[start]).isDigit()) start++; // Strip any non digit, non '.' characters from the end int end = start; while (end < version.length() && (version[end] == '.' || QChar::fromLatin1(version[end]).isDigit())) end++; const QByteArray result = version.mid(start, end-start); const QList tokens = result.split('.'); const qint64 major = tokens.at(0).toInt(); const qint64 minor = tokens.count() > 1 ? tokens.at(1).toInt() : 0; const qint64 patch = tokens.count() > 2 ? tokens.at(2).toInt() : 0; return kVersionNumber(major, minor, patch); } static qint64 getKernelVersion() { struct utsname name; uname(&name); if (qstrcmp(name.sysname, "Linux") == 0) return parseVersionString(name.release); return 0; } // Extracts the portion of a string that matches a regular expression static QString extract(const QString &string, const QString &match, int offset = 0) { QString result; QRegExp rx(match); int pos = rx.indexIn(string, offset); if (pos != -1) result = string.mid(pos, rx.matchedLength()); return result; } static ChipClass detectRadeonClass(const QByteArray &chipset) { if (chipset.isEmpty()) return UnknownRadeon; if (chipset.contains("R100") || chipset.contains("RV100") || chipset.contains("RS100")) return R100; if (chipset.contains("RV200") || chipset.contains("RS200") || chipset.contains("R200") || chipset.contains("RV250") || chipset.contains("RS300") || chipset.contains("RV280")) return R200; if (chipset.contains("R300") || chipset.contains("R350") || chipset.contains("R360") || chipset.contains("RV350") || chipset.contains("RV370") || chipset.contains("RV380")) return R300; if (chipset.contains("R420") || chipset.contains("R423") || chipset.contains("R430") || chipset.contains("R480") || chipset.contains("R481") || chipset.contains("RV410") || chipset.contains("RS400") || chipset.contains("RC410") || chipset.contains("RS480") || chipset.contains("RS482") || chipset.contains("RS600") || chipset.contains("RS690") || chipset.contains("RS740")) return R400; if (chipset.contains("RV515") || chipset.contains("R520") || chipset.contains("RV530") || chipset.contains("R580") || chipset.contains("RV560") || chipset.contains("RV570")) return R500; if (chipset.contains("R600") || chipset.contains("RV610") || chipset.contains("RV630") || chipset.contains("RV670") || chipset.contains("RV620") || chipset.contains("RV635") || chipset.contains("RS780") || chipset.contains("RS880")) return R600; if (chipset.contains("R700") || chipset.contains("RV770") || chipset.contains("RV730") || chipset.contains("RV710") || chipset.contains("RV740")) return R700; if (chipset.contains("EVERGREEN") || // Not an actual chipset, but returned by R600G in 7.9 chipset.contains("CEDAR") || chipset.contains("REDWOOD") || chipset.contains("JUNIPER") || chipset.contains("CYPRESS") || chipset.contains("HEMLOCK") || chipset.contains("PALM")) return Evergreen; if (chipset.contains("SUMO") || chipset.contains("SUMO2") || chipset.contains("BARTS") || chipset.contains("TURKS") || chipset.contains("CAICOS") || chipset.contains("CAYMAN")) return NorthernIslands; const QString chipset16 = QString::fromLatin1(chipset); QString name = extract(chipset16, QStringLiteral("HD [0-9]{4}")); // HD followed by a space and 4 digits if (!name.isEmpty()) { const int id = name.rightRef(4).toInt(); if (id == 6250 || id == 6310) // Palm return Evergreen; if (id >= 6000 && id < 7000) return NorthernIslands; // HD 6xxx if (id >= 5000 && id < 6000) return Evergreen; // HD 5xxx if (id >= 4000 && id < 5000) return R700; // HD 4xxx if (id >= 2000 && id < 4000) // HD 2xxx/3xxx return R600; return UnknownRadeon; } name = extract(chipset16, QStringLiteral("X[0-9]{3,4}")); // X followed by 3-4 digits if (!name.isEmpty()) { const int id = name.midRef(1, -1).toInt(); // X1xxx if (id >= 1300) return R500; // X7xx, X8xx, X12xx, 2100 if ((id >= 700 && id < 1000) || id >= 1200) return R400; // X200, X3xx, X5xx, X6xx, X10xx, X11xx if ((id >= 300 && id < 700) || (id >= 1000 && id < 1200)) return R300; return UnknownRadeon; } name = extract(chipset16, QStringLiteral("\\b[0-9]{4}\\b")); // A group of 4 digits if (!name.isEmpty()) { const int id = name.toInt(); // 7xxx if (id >= 7000 && id < 8000) return R100; // 8xxx, 9xxx if (id >= 8000 && id < 9500) return R200; // 9xxx if (id >= 9500) return R300; if (id == 2100) return R400; } return UnknownRadeon; } static ChipClass detectNVidiaClass(const QString &chipset) { QString name = extract(chipset, QStringLiteral("\\bNV[0-9,A-F]{2}\\b")); // NV followed by two hexadecimal digits if (!name.isEmpty()) { const int id = chipset.midRef(2, -1).toInt(nullptr, 16); // Strip the 'NV' from the id switch(id & 0xf0) { case 0x00: case 0x10: return NV10; case 0x20: return NV20; case 0x30: return NV30; case 0x40: case 0x60: return NV40; case 0x50: case 0x80: case 0x90: case 0xA0: return G80; default: return UnknownNVidia; } } if (chipset.contains(QLatin1String("GeForce2")) || chipset.contains(QLatin1String("GeForce 256"))) return NV10; if (chipset.contains(QLatin1String("GeForce3"))) return NV20; if (chipset.contains(QLatin1String("GeForce4"))) { if (chipset.contains(QLatin1String("MX 420")) || chipset.contains(QLatin1String("MX 440")) || // including MX 440SE chipset.contains(QLatin1String("MX 460")) || chipset.contains(QLatin1String("MX 4000")) || chipset.contains(QLatin1String("PCX 4300"))) return NV10; return NV20; } // GeForce 5,6,7,8,9 name = extract(chipset, QStringLiteral("GeForce (FX |PCX |Go )?\\d{4}(M|\\b)")).trimmed(); if (!name.isEmpty()) { if (!name[name.length() - 1].isDigit()) name.chop(1); const int id = name.rightRef(4).toInt(); if (id < 6000) return NV30; if (id >= 6000 && id < 8000) return NV40; if (id >= 8000) return G80; return UnknownNVidia; } // GeForce 100/200/300/400/500 name = extract(chipset, QStringLiteral("GeForce (G |GT |GTX |GTS )?\\d{3}(M|\\b)")).trimmed(); if (!name.isEmpty()) { if (!name[name.length() - 1].isDigit()) name.chop(1); const int id = name.rightRef(3).toInt(); if (id >= 100 && id < 600) { if (id >= 400) return GF100; return G80; } return UnknownNVidia; } return UnknownNVidia; } static inline ChipClass detectNVidiaClass(const QByteArray &chipset) { return detectNVidiaClass(QString::fromLatin1(chipset)); } static ChipClass detectIntelClass(const QByteArray &chipset) { // see mesa repository: src/mesa/drivers/dri/intel/intel_context.c // GL 1.3, DX8? SM ? if (chipset.contains("845G") || chipset.contains("830M") || chipset.contains("852GM/855GM") || chipset.contains("865G")) return I8XX; // GL 1.4, DX 9.0, SM 2.0 if (chipset.contains("915G") || chipset.contains("E7221G") || chipset.contains("915GM") || chipset.contains("945G") || // DX 9.0c chipset.contains("945GM") || chipset.contains("945GME") || chipset.contains("Q33") || // GL1.5 chipset.contains("Q35") || chipset.contains("G33") || chipset.contains("965Q") || // GMA 3000, but apparently considered gen 4 by the driver chipset.contains("946GZ") || // GMA 3000, but apparently considered gen 4 by the driver chipset.contains("IGD")) return I915; // GL 2.0, DX 9.0c, SM 3.0 if (chipset.contains("965G") || chipset.contains("G45/G43") || // SM 4.0 chipset.contains("965GM") || // GL 2.1 chipset.contains("965GME/GLE") || chipset.contains("GM45") || chipset.contains("Q45/Q43") || chipset.contains("G41") || chipset.contains("B43") || chipset.contains("Ironlake")) return I965; // GL 3.1, CL 1.1, DX 10.1 if (chipset.contains("Sandybridge")) { return SandyBridge; } // GL4.0, CL1.1, DX11, SM 5.0 if (chipset.contains("Ivybridge")) { return IvyBridge; } // GL4.0, CL1.2, DX11.1, SM 5.0 if (chipset.contains("Haswell")) { return Haswell; } return UnknownIntel; } static ChipClass detectQualcommClass(const QByteArray &chipClass) { if (!chipClass.contains("Adreno")) { return UnknownChipClass; } const auto parts = chipClass.split(' '); if (parts.count() < 3) { return UnknownAdreno; } bool ok = false; const int value = parts.at(2).toInt(&ok); if (ok) { if (value >= 100 && value < 200) { return Adreno1XX; } if (value >= 200 && value < 300) { return Adreno2XX; } if (value >= 300 && value < 400) { return Adreno3XX; } if (value >= 400 && value < 500) { return Adreno4XX; } if (value >= 500 && value < 600) { return Adreno5XX; } } return UnknownAdreno; } QString GLPlatform::versionToString(qint64 version) { return QString::fromLatin1(versionToString8(version)); } QByteArray GLPlatform::versionToString8(qint64 version) { int major = (version >> 32); int minor = (version >> 16) & 0xffff; int patch = version & 0xffff; QByteArray string = QByteArray::number(major) + '.' + QByteArray::number(minor); if (patch != 0) string += '.' + QByteArray::number(patch); return string; } QString GLPlatform::driverToString(Driver driver) { return QString::fromLatin1(driverToString8(driver)); } QByteArray GLPlatform::driverToString8(Driver driver) { switch(driver) { case Driver_R100: return QByteArrayLiteral("Radeon"); case Driver_R200: return QByteArrayLiteral("R200"); case Driver_R300C: return QByteArrayLiteral("R300C"); case Driver_R300G: return QByteArrayLiteral("R300G"); case Driver_R600C: return QByteArrayLiteral("R600C"); case Driver_R600G: return QByteArrayLiteral("R600G"); case Driver_Nouveau: return QByteArrayLiteral("Nouveau"); case Driver_Intel: return QByteArrayLiteral("Intel"); case Driver_NVidia: return QByteArrayLiteral("NVIDIA"); case Driver_Catalyst: return QByteArrayLiteral("Catalyst"); case Driver_Swrast: return QByteArrayLiteral("Software rasterizer"); case Driver_Softpipe: return QByteArrayLiteral("softpipe"); case Driver_Llvmpipe: return QByteArrayLiteral("LLVMpipe"); case Driver_VirtualBox: return QByteArrayLiteral("VirtualBox (Chromium)"); case Driver_VMware: return QByteArrayLiteral("VMware (SVGA3D)"); case Driver_Qualcomm: return QByteArrayLiteral("Qualcomm"); default: return QByteArrayLiteral("Unknown"); } } QString GLPlatform::chipClassToString(ChipClass chipClass) { return QString::fromLatin1(chipClassToString8(chipClass)); } QByteArray GLPlatform::chipClassToString8(ChipClass chipClass) { switch(chipClass) { case R100: return QByteArrayLiteral("R100"); case R200: return QByteArrayLiteral("R200"); case R300: return QByteArrayLiteral("R300"); case R400: return QByteArrayLiteral("R400"); case R500: return QByteArrayLiteral("R500"); case R600: return QByteArrayLiteral("R600"); case R700: return QByteArrayLiteral("R700"); case Evergreen: return QByteArrayLiteral("EVERGREEN"); case NorthernIslands: return QByteArrayLiteral("NI"); case NV10: return QByteArrayLiteral("NV10"); case NV20: return QByteArrayLiteral("NV20"); case NV30: return QByteArrayLiteral("NV30"); case NV40: return QByteArrayLiteral("NV40/G70"); case G80: return QByteArrayLiteral("G80/G90"); case GF100: return QByteArrayLiteral("GF100"); case I8XX: return QByteArrayLiteral("i830/i835"); case I915: return QByteArrayLiteral("i915/i945"); case I965: return QByteArrayLiteral("i965"); case SandyBridge: return QByteArrayLiteral("SandyBridge"); case IvyBridge: return QByteArrayLiteral("IvyBridge"); case Haswell: return QByteArrayLiteral("Haswell"); case Adreno1XX: return QByteArrayLiteral("Adreno 1xx series"); case Adreno2XX: return QByteArrayLiteral("Adreno 2xx series"); case Adreno3XX: return QByteArrayLiteral("Adreno 3xx series"); case Adreno4XX: return QByteArrayLiteral("Adreno 4xx series"); case Adreno5XX: return QByteArrayLiteral("Adreno 5xx series"); default: return QByteArrayLiteral("Unknown"); } } // ------- GLPlatform::GLPlatform() : m_driver(Driver_Unknown), m_chipClass(UnknownChipClass), m_glVersion(0), m_glslVersion(0), m_mesaVersion(0), m_driverVersion(0), m_galliumVersion(0), m_serverVersion(0), m_kernelVersion(0), m_looseBinding(false), m_supportsGLSL(false), m_limitedGLSL(false), m_textureNPOT(false), m_limitedNPOT(false), m_virtualMachine(false), m_preferBufferSubData(false), m_gles(false) { } GLPlatform::~GLPlatform() { } void GLPlatform::detect() { QOpenGLFunctions gl; gl.initializeOpenGLFunctions(); m_vendor = (const char*)gl.glGetString(GL_VENDOR); m_renderer = (const char*)gl.glGetString(GL_RENDERER); m_version = (const char*)gl.glGetString(GL_VERSION); // Parse the OpenGL version const QList versionTokens = m_version.split(' '); if (versionTokens.count() > 0) { const QByteArray version = QByteArray(m_version); m_glVersion = parseVersionString(version); if (version.startsWith("OpenGL ES")) { // from GLES 2: "Returns a version or release number of the form OpenGLES." // from GLES 3: "Returns a version or release number." and "The version number uses one of these forms: major_number.minor_number major_number.minor_number.release_number" m_gles = true; } } const QByteArray extensions = (const char *) gl.glGetString(GL_EXTENSIONS); m_extensions = QSet::fromList(extensions.split(' ')); // Parse the Mesa version const int mesaIndex = versionTokens.indexOf("Mesa"); if (mesaIndex != -1) { const QByteArray version = versionTokens.at(mesaIndex + 1); m_mesaVersion = parseVersionString(version); } if (isGLES()) { m_supportsGLSL = true; m_textureNPOT = true; } else { m_supportsGLSL = m_extensions.contains("GL_ARB_shader_objects") && m_extensions.contains("GL_ARB_fragment_shader") && m_extensions.contains("GL_ARB_vertex_shader"); m_textureNPOT = m_extensions.contains("GL_ARB_texture_non_power_of_two"); } m_kernelVersion = getKernelVersion(); m_glslVersion = 0; m_glsl_version.clear(); if (m_supportsGLSL) { // Parse the GLSL version m_glsl_version = (const char*)gl.glGetString(GL_SHADING_LANGUAGE_VERSION); m_glslVersion = parseVersionString(m_glsl_version); } m_chipset = QByteArrayLiteral("Unknown"); m_preferBufferSubData = false; // Mesa classic drivers // ==================================================== // Radeon if (m_renderer.startsWith("Mesa DRI R")) { // Sample renderer string: Mesa DRI R600 (RV740 94B3) 20090101 x86/MMX/SSE2 TCL DRI2 const QList tokens = m_renderer.split(' '); const QByteArray chipClass = tokens.at(2); m_chipset = tokens.at(3).mid(1, -1); // Strip the leading '(' if (chipClass == "R100") // Vendor: Tungsten Graphics, Inc. m_driver = Driver_R100; else if (chipClass == "R200") // Vendor: Tungsten Graphics, Inc. m_driver = Driver_R200; else if (chipClass == "R300") // Vendor: DRI R300 Project m_driver = Driver_R300C; else if (chipClass == "R600") // Vendor: Advanced Micro Devices, Inc. m_driver = Driver_R600C; m_chipClass = detectRadeonClass(m_chipset); } // Intel else if (m_renderer.contains("Intel")) { // Vendor: Tungsten Graphics, Inc. // Sample renderer string: Mesa DRI Mobile Intel® GM45 Express Chipset GEM 20100328 2010Q1 QByteArray chipset; if (m_renderer.startsWith("Intel(R) Integrated Graphics Device")) chipset = "IGD"; else chipset = m_renderer; m_driver = Driver_Intel; m_chipClass = detectIntelClass(chipset); } // Properietary drivers // ==================================================== else if (m_vendor == "ATI Technologies Inc.") { m_chipClass = detectRadeonClass(m_renderer); m_driver = Driver_Catalyst; if (versionTokens.count() > 1 && versionTokens.at(2)[0] == '(') m_driverVersion = parseVersionString(versionTokens.at(1)); else if (versionTokens.count() > 0) m_driverVersion = parseVersionString(versionTokens.at(0)); else m_driverVersion = 0; } else if (m_vendor == "NVIDIA Corporation") { m_chipClass = detectNVidiaClass(m_renderer); m_driver = Driver_NVidia; int index = versionTokens.indexOf("NVIDIA"); if (versionTokens.count() > index) m_driverVersion = parseVersionString(versionTokens.at(index + 1)); else m_driverVersion = 0; } else if (m_vendor == "Qualcomm") { m_driver = Driver_Qualcomm; m_chipClass = detectQualcommClass(m_renderer); } else if (m_renderer == "Software Rasterizer") { m_driver = Driver_Swrast; } // Virtual Hardware // ==================================================== else if (m_vendor == "Humper" && m_renderer == "Chromium") { // Virtual Box m_driver = Driver_VirtualBox; const int index = versionTokens.indexOf("Chromium"); if (versionTokens.count() > index) m_driverVersion = parseVersionString(versionTokens.at(index + 1)); else m_driverVersion = 0; } // Gallium drivers // ==================================================== else { const QList tokens = m_renderer.split(' '); if (m_renderer.contains("Gallium")) { // Sample renderer string: Gallium 0.4 on AMD RV740 m_galliumVersion = parseVersionString(tokens.at(1)); m_chipset = (tokens.at(3) == "AMD" || tokens.at(3) == "ATI") ? tokens.at(4) : tokens.at(3); } else { // The renderer string does not contain "Gallium" anymore. m_chipset = tokens.at(0); // We don't know the actual version anymore, but it's at least 0.4. m_galliumVersion = kVersionNumber(0, 4, 0); } // R300G if (m_vendor == QByteArrayLiteral("X.Org R300 Project")) { m_chipClass = detectRadeonClass(m_chipset); m_driver = Driver_R300G; } // R600G else if (m_vendor == "X.Org" && (m_renderer.contains("R6") || m_renderer.contains("R7") || m_renderer.contains("RV6") || m_renderer.contains("RV7") || m_renderer.contains("RS780") || m_renderer.contains("RS880") || m_renderer.contains("CEDAR") || m_renderer.contains("REDWOOD") || m_renderer.contains("JUNIPER") || m_renderer.contains("CYPRESS") || m_renderer.contains("HEMLOCK") || m_renderer.contains("PALM") || m_renderer.contains("EVERGREEN") || m_renderer.contains("SUMO") || m_renderer.contains("SUMO2") || m_renderer.contains("BARTS") || m_renderer.contains("TURKS") || m_renderer.contains("CAICOS") || m_renderer.contains("CAYMAN"))) { m_chipClass = detectRadeonClass(m_chipset); m_driver = Driver_R600G; } // Nouveau else if (m_vendor == "nouveau") { m_chipClass = detectNVidiaClass(m_chipset); m_driver = Driver_Nouveau; } // softpipe else if (m_vendor == "VMware, Inc." && m_chipset == "softpipe" ) { m_driver = Driver_Softpipe; } // llvmpipe else if (m_vendor == "VMware, Inc." && m_chipset == "llvmpipe") { m_driver = Driver_Llvmpipe; } // SVGA3D else if (m_vendor == "VMware, Inc." && m_chipset.contains("SVGA3D")) { m_driver = Driver_VMware; } } // Driver/GPU specific features // ==================================================== if (isRadeon()) { // R200 technically has a programmable pipeline, but since it's SM 1.4, // it's too limited to to be of any practical value to us. if (m_chipClass < R300) m_supportsGLSL = false; m_limitedGLSL = false; m_limitedNPOT = false; if (m_chipClass < R600) { if (driver() == Driver_Catalyst) m_textureNPOT = m_limitedNPOT = false; // Software fallback else if (driver() == Driver_R300G) m_limitedNPOT = m_textureNPOT; m_limitedGLSL = m_supportsGLSL; } if (driver() == Driver_R600G || (driver() == Driver_R600C && m_renderer.contains("DRI2"))) { m_looseBinding = true; } } if (isNvidia()) { if (m_driver == Driver_NVidia && m_chipClass < NV40) m_supportsGLSL = false; // High likelihood of software emulation if (m_driver == Driver_NVidia) { m_looseBinding = true; m_preferBufferSubData = true; } m_limitedNPOT = m_textureNPOT && m_chipClass < NV40; m_limitedGLSL = m_supportsGLSL && m_chipClass < G80; } if (isIntel()) { if (m_chipClass < I915) m_supportsGLSL = false; m_limitedGLSL = m_supportsGLSL && m_chipClass < I965; // see https://bugs.freedesktop.org/show_bug.cgi?id=80349#c1 m_looseBinding = false; } if (isSoftwareEmulation()) { if (m_driver < Driver_Llvmpipe) { // Software emulation does not provide GLSL m_limitedGLSL = m_supportsGLSL = false; } else { m_limitedGLSL = false; m_supportsGLSL = true; } } if (m_chipClass == UnknownChipClass && m_driver == Driver_Unknown) { // we don't know the hardware. Let's be optimistic and assume OpenGL compatible hardware m_supportsGLSL = true; } if (isVirtualBox()) { m_virtualMachine = true; } if (isVMware()) { m_virtualMachine = true; } // and force back to shader supported on gles, we wouldn't have got a context if not supported if (isGLES()) { m_supportsGLSL = true; m_limitedGLSL = false; } } static void print(const QByteArray &label, const QByteArray &setting) { std::cout << std::setw(40) << std::left << label.data() << setting.data() << std::endl; } void GLPlatform::printResults() const { print(QByteArrayLiteral("OpenGL vendor string:"), m_vendor); print(QByteArrayLiteral("OpenGL renderer string:"), m_renderer); print(QByteArrayLiteral("OpenGL version string:"), m_version); if (m_supportsGLSL) print(QByteArrayLiteral("OpenGL shading language version string:"), m_glsl_version); print(QByteArrayLiteral("Driver:"), driverToString8(m_driver)); if (!isMesaDriver()) print(QByteArrayLiteral("Driver version:"), versionToString8(m_driverVersion)); print(QByteArrayLiteral("GPU class:"), chipClassToString8(m_chipClass)); print(QByteArrayLiteral("OpenGL version:"), versionToString8(m_glVersion)); if (m_supportsGLSL) print(QByteArrayLiteral("GLSL version:"), versionToString8(m_glslVersion)); if (isMesaDriver()) print(QByteArrayLiteral("Mesa version:"), versionToString8(mesaVersion())); //if (galliumVersion() > 0) // print("Gallium version:", versionToString(m_galliumVersion)); if (serverVersion() > 0) print(QByteArrayLiteral("X server version:"), versionToString8(m_serverVersion)); if (kernelVersion() > 0) print(QByteArrayLiteral("Linux kernel version:"), versionToString8(m_kernelVersion)); print(QByteArrayLiteral("Requires strict binding:"), !m_looseBinding ? QByteArrayLiteral("yes") : QByteArrayLiteral("no")); print(QByteArrayLiteral("GLSL shaders:"), m_supportsGLSL ? (m_limitedGLSL ? QByteArrayLiteral("limited") : QByteArrayLiteral("yes")) : QByteArrayLiteral("no")); print(QByteArrayLiteral("Texture NPOT support:"), m_textureNPOT ? (m_limitedNPOT ? QByteArrayLiteral("limited") : QByteArrayLiteral("yes")) : QByteArrayLiteral("no")); print(QByteArrayLiteral("Virtual Machine:"), m_virtualMachine ? QByteArrayLiteral("yes") : QByteArrayLiteral("no")); } bool GLPlatform::supports(GLFeature feature) const { switch(feature) { case LooseBinding: return m_looseBinding; case GLSL: return m_supportsGLSL; case LimitedGLSL: return m_limitedGLSL; case TextureNPOT: return m_textureNPOT; case LimitedNPOT: return m_limitedNPOT; default: return false; } } qint64 GLPlatform::glVersion() const { return m_glVersion; } qint64 GLPlatform::glslVersion() const { return m_glslVersion; } qint64 GLPlatform::mesaVersion() const { return m_mesaVersion; } qint64 GLPlatform::galliumVersion() const { return m_galliumVersion; } qint64 GLPlatform::serverVersion() const { return m_serverVersion; } qint64 GLPlatform::kernelVersion() const { return m_kernelVersion; } qint64 GLPlatform::driverVersion() const { if (isMesaDriver()) return mesaVersion(); return m_driverVersion; } Driver GLPlatform::driver() const { return m_driver; } ChipClass GLPlatform::chipClass() const { return m_chipClass; } bool GLPlatform::isMesaDriver() const { return mesaVersion() > 0; } bool GLPlatform::isGalliumDriver() const { return galliumVersion() > 0; } bool GLPlatform::isRadeon() const { return m_chipClass >= R100 && m_chipClass <= UnknownRadeon; } bool GLPlatform::isNvidia() const { return m_chipClass >= NV10 && m_chipClass <= UnknownNVidia; } bool GLPlatform::isIntel() const { return m_chipClass >= I8XX && m_chipClass <= UnknownIntel; } bool GLPlatform::isVirtualBox() const { return m_driver == Driver_VirtualBox; } bool GLPlatform::isVMware() const { return m_driver == Driver_VMware; } bool GLPlatform::isSoftwareEmulation() const { return m_driver == Driver_Softpipe || m_driver == Driver_Swrast || m_driver == Driver_Llvmpipe; } bool GLPlatform::isAdreno() const { return m_chipClass >= Adreno1XX && m_chipClass <= UnknownAdreno; } const QByteArray &GLPlatform::glRendererString() const { return m_renderer; } const QByteArray &GLPlatform::glVendorString() const { return m_vendor; } const QByteArray &GLPlatform::glVersionString() const { return m_version; } const QByteArray &GLPlatform::glShadingLanguageVersionString() const { return m_glsl_version; } bool GLPlatform::isLooseBinding() const { return m_looseBinding; } bool GLPlatform::isVirtualMachine() const { return m_virtualMachine; } bool GLPlatform::preferBufferSubData() const { return m_preferBufferSubData; } bool GLPlatform::isGLES() const { return m_gles; } void GLPlatform::cleanup() { delete s_platform; s_platform = nullptr; } } // namespace KWin diff --git a/greeter/lnf_integration.cpp b/greeter/lnf_integration.cpp index 6e626eb..661c7a8 100644 --- a/greeter/lnf_integration.cpp +++ b/greeter/lnf_integration.cpp @@ -1,74 +1,72 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2016 Martin Gräßlin Copyright (C) 2017 David Edmundson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "lnf_integration.h" #include #include #include -#include #include #include -#include namespace ScreenLocker { LnFIntegration::LnFIntegration(QObject *parent) : QObject(parent) { qRegisterMetaType(); } LnFIntegration::~LnFIntegration() = default; void LnFIntegration::init() { if (!m_package.isValid()) { return; } if (auto config = configScheme()) { m_configuration = new KDeclarative::ConfigPropertyMap(config, this); } } KConfigLoader *LnFIntegration::configScheme() { if (!m_configLoader) { const QString xmlPath = m_package.filePath(QByteArrayLiteral("lockscreen"), QStringLiteral("config.xml")); const KConfigGroup cfg = m_config->group("Greeter").group("LnF"); if (xmlPath.isEmpty()) { m_configLoader = new KConfigLoader(cfg, nullptr, this); } else { QFile file(xmlPath); m_configLoader = new KConfigLoader(cfg, &file, this); } } return m_configLoader; } } diff --git a/greeter/main.cpp b/greeter/main.cpp index bdc3709..503916a 100644 --- a/greeter/main.cpp +++ b/greeter/main.cpp @@ -1,200 +1,201 @@ /******************************************************************** This file is part of the KDE project. Copyright (C) 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include #include #include #include #include +#include #include #include #include "greeterapp.h" #include #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #include #endif #if HAVE_SECCOMP #include "seccomp_filter.h" #endif static void signalHandler(int signum) { ScreenLocker::UnlockApp *instance = qobject_cast(QCoreApplication::instance()); if (!instance) return; switch(signum) { case SIGTERM: // exit gracefully to not leave behind screensaver processes (bug#224200) // return exit code 1 to indicate that a valid password was not entered, // to prevent circumventing the password input by sending a SIGTERM instance->exit(1); break; case SIGUSR1: instance->lockImmediately(); break; } } int main(int argc, char* argv[]) { // disable ptrace on the greeter #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 0); #endif #if HAVE_PROC_TRACE_CTL int mode = PROC_TRACE_CTL_DISABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif KLocalizedString::setApplicationDomain("kscreenlocker_greet"); // explicitly disable input methods as it makes it impossible to unlock, see BUG 306932 // but explicitly set on screen keyboard such as maliit is allowed if (!qEnvironmentVariableIsSet("QT_IM_MODULE") || (qEnvironmentVariableIsSet("QT_IM_MODULE") && qgetenv("QT_IM_MODULE") != QByteArrayLiteral("maliit"))) { qputenv("QT_IM_MODULE", QByteArrayLiteral("qtvirtualkeyboard")); } // Suppresses modal warnings about unwritable configuration files which may render the system inaccessible qputenv("KDE_HOME_READONLY", "1"); ScreenLocker::UnlockApp app(argc, argv); app.setQuitOnLastWindowClosed(false); QCoreApplication::setApplicationName(QStringLiteral("kscreenlocker_greet")); QCoreApplication::setApplicationVersion(QStringLiteral("0.1")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); KQuickAddons::QtQuickSettings::init(); // disable session management for the greeter auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); QCommandLineParser parser; parser.setApplicationDescription(i18n("Greeter for the KDE Plasma Workspaces Screen locker")); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption testingOption(QStringLiteral("testing"), i18n("Starts the greeter in testing mode")); QCommandLineOption themeOption(QStringLiteral("theme"), i18n("Starts the greeter with the selected theme (only in Testing mode)"), QStringLiteral("theme"), QStringLiteral("")); QCommandLineOption immediateLockOption(QStringLiteral("immediateLock"), i18n("Lock immediately, ignoring any grace time etc.")); QCommandLineOption graceTimeOption(QStringLiteral("graceTime"), i18n("Delay till the lock user interface gets shown in milliseconds."), QStringLiteral("milliseconds"), QStringLiteral("0")); QCommandLineOption nolockOption(QStringLiteral("nolock"), i18n("Don't show any lock user interface.")); QCommandLineOption switchUserOption(QStringLiteral("switchuser"), i18n("Default to the switch user UI.")); QCommandLineOption waylandFdOption(QStringLiteral("ksldfd"), i18n("File descriptor for connecting to ksld."), QStringLiteral("fd")); parser.addOption(testingOption); parser.addOption(themeOption); parser.addOption(immediateLockOption); parser.addOption(graceTimeOption); parser.addOption(nolockOption); parser.addOption(switchUserOption); parser.addOption(waylandFdOption); parser.process(app); if (parser.isSet(testingOption)) { app.setTesting(true); app.setImmediateLock(true); //parse theme option const QString theme = parser.value(themeOption); if (!theme.isEmpty()) { app.setTheme(theme); } // allow ptrace if testing is enabled #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 1); #endif #if HAVE_PROC_TRACE_CTL int mode = PROC_TRACE_CTL_ENABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif } else { app.setImmediateLock(parser.isSet(immediateLockOption)); } app.setNoLock(parser.isSet(nolockOption)); bool ok = false; int graceTime = parser.value(graceTimeOption).toInt(&ok); if (ok) { app.setGraceTime(graceTime); } if (parser.isSet(switchUserOption)) { app.setDefaultToSwitchUser(true); } if (parser.isSet(waylandFdOption)) { ok = false; const int fd = parser.value(waylandFdOption).toInt(&ok); if (ok) { app.setKsldSocket(fd); } } // init the sandbox #if HAVE_SECCOMP if (app.supportsSeccomp()) { ScreenLocker::SecComp::init(); } #endif app.desktopResized(); // This allow ksmserver to know when the applicaion has actually finished setting itself up. // Crucial for blocking until it is ready, ensuring locking happens before sleep, e.g. std::cout << "Locked at " << QDateTime::currentDateTime().toTime_t() << std::endl; struct sigaction sa; sa.sa_handler = signalHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGTERM, &sa, nullptr); sigaction(SIGUSR1, &sa, nullptr); return app.exec(); } diff --git a/interface.cpp b/interface.cpp index c4c3b91..fed0f50 100644 --- a/interface.cpp +++ b/interface.cpp @@ -1,215 +1,214 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright 1999 Martin R. Jones Copyright (C) 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "interface.h" #include "ksldapp.h" #include "screensaveradaptor.h" #include "kscreensaveradaptor.h" #include "powerdevilpolicyagent.h" // KDE #include #include #include // Qt #include -#include #include #include namespace ScreenLocker { const uint ChangeScreenSettings = 4; Interface::Interface(KSldApp *parent) : QObject(parent) , m_daemon(parent) , m_serviceWatcher(new QDBusServiceWatcher(this)) , m_next_cookie(0) { (void) new ScreenSaverAdaptor( this ); QDBusConnection::sessionBus().registerService(QStringLiteral("org.freedesktop.ScreenSaver")) ; (void) new KScreenSaverAdaptor( this ); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.screensaver")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/ScreenSaver"), this); QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/freedesktop/ScreenSaver"), this); connect(m_daemon, &KSldApp::locked, this, &Interface::slotLocked); connect(m_daemon, &KSldApp::unlocked, this, &Interface::slotUnlocked); connect(m_daemon, &KSldApp::aboutToLock, this, &Interface::AboutToLock); m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration); connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &Interface::serviceUnregistered); // I make it a really random number to avoid // some assumptions in clients, but just increase // while gnome-ss creates a random number every time m_next_cookie = KRandom::random() % 20000; } Interface::~Interface() { } bool Interface::GetActive() { return m_daemon->lockState() == KSldApp::Locked; } uint Interface::GetActiveTime() { return m_daemon->activeTime(); } uint Interface::GetSessionIdleTime() { return KIdleTime::instance()->idleTime(); } void Interface::Lock() { if (!KAuthorized::authorizeAction(QStringLiteral("lock_screen"))) { return; } m_daemon->lock(calledFromDBus() ? EstablishLock::Immediate : EstablishLock::Delayed); if (calledFromDBus() && m_daemon->lockState() == KSldApp::AcquiringLock) { m_lockReplies << message().createReply(); setDelayedReply(true); } } void Interface::SwitchUser() { if (!KAuthorized::authorizeAction(QStringLiteral("lock_screen"))) { return; } m_daemon->lock(EstablishLock::DefaultToSwitchUser); if (calledFromDBus() && m_daemon->lockState() == KSldApp::AcquiringLock) { m_lockReplies << message().createReply(); setDelayedReply(true); } } bool Interface::SetActive (bool state) { // TODO: what should the return value be? if (state) { Lock(); return true; } // set inactive is ignored return false; } uint Interface::Inhibit(const QString &application_name, const QString &reason_for_inhibit) { OrgKdeSolidPowerManagementPolicyAgentInterface policyAgent(QStringLiteral("org.kde.Solid.PowerManagement.PolicyAgent"), QStringLiteral("/org/kde/Solid/PowerManagement/PolicyAgent"), QDBusConnection::sessionBus()); QDBusReply reply = policyAgent.AddInhibition(ChangeScreenSettings, application_name, reason_for_inhibit); InhibitRequest sr; sr.cookie = m_next_cookie++; sr.dbusid = message().service(); sr.powerdevilcookie = reply.isValid() ? reply : 0; m_requests.append(sr); m_serviceWatcher->addWatchedService(sr.dbusid); KSldApp::self()->inhibit(); return sr.cookie; } void Interface::UnInhibit(uint cookie) { QMutableListIterator it(m_requests); while (it.hasNext()) { if (it.next().cookie == cookie) { if (uint powerdevilcookie = it.value().powerdevilcookie) { OrgKdeSolidPowerManagementPolicyAgentInterface policyAgent(QStringLiteral("org.kde.Solid.PowerManagement.PolicyAgent"), QStringLiteral("/org/kde/Solid/PowerManagement/PolicyAgent"), QDBusConnection::sessionBus()); policyAgent.ReleaseInhibition(powerdevilcookie); } it.remove(); KSldApp::self()->uninhibit(); break; } } } void Interface::serviceUnregistered(const QString &name) { m_serviceWatcher->removeWatchedService(name); QListIterator it(m_requests); while (it.hasNext()) { const InhibitRequest &r = it.next(); if (r.dbusid == name) { UnInhibit(r.cookie); } } } void Interface::SimulateUserActivity() { KIdleTime::instance()->simulateUserActivity(); } uint Interface::Throttle(const QString &application_name, const QString &reason_for_inhibit) { Q_UNUSED(application_name) Q_UNUSED(reason_for_inhibit) // TODO: implement me return 0; } void Interface::UnThrottle(uint cookie) { Q_UNUSED(cookie) // TODO: implement me } void Interface::slotLocked() { sendLockReplies(); emit ActiveChanged(true); } void Interface::slotUnlocked() { sendLockReplies(); emit ActiveChanged(false); } void Interface::configure() { m_daemon->configure(); } void Interface::sendLockReplies() { for (const QDBusMessage &reply : qAsConst(m_lockReplies)) { QDBusConnection::sessionBus().send(reply); } m_lockReplies.clear(); } } // namespace diff --git a/ksldapp.cpp b/ksldapp.cpp index 8c41c92..1ce30a9 100644 --- a/ksldapp.cpp +++ b/ksldapp.cpp @@ -1,752 +1,750 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright 1999 Martin R. Jones Copyright 2003 Oswald Buddenhagen Copyright 2008 Chani Armitage Copyright (C) 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "ksldapp.h" #include "interface.h" #include "globalaccel.h" #include "x11locker.h" #include "waylandlocker.h" #include "logind.h" #include "kscreensaversettings.h" #include "powermanagement_inhibition.h" #include #include #include "waylandserver.h" // KDE #include #include #include #include #include //kwayland #include #include // Qt #include #include #include #include #include -#include -#include // X11 #include #include #ifdef X11_Xinput_FOUND #include #endif // other #include #include #include #include namespace ScreenLocker { static const QString s_qtQuickBackend = QStringLiteral("QT_QUICK_BACKEND"); static KSldApp * s_instance = nullptr; KSldApp* KSldApp::self() { if (!s_instance) { s_instance = new KSldApp(); } return s_instance; } KSldApp::KSldApp(QObject * parent) : QObject(parent) , m_lockState(Unlocked) , m_lockProcess(nullptr) , m_lockWindow(nullptr) , m_waylandServer(new WaylandServer(this)) , m_waylandDisplay(nullptr) , m_lockedTimer(QElapsedTimer()) , m_idleId(0) , m_lockGrace(0) , m_inGraceTime(false) , m_graceTimer(new QTimer(this)) , m_inhibitCounter(0) , m_logind(nullptr) , m_greeterEnv(QProcessEnvironment::systemEnvironment()) , m_powerManagementInhibition(new PowerManagementInhibition(this)) { m_isX11 = QX11Info::isPlatformX11(); m_isWayland = QCoreApplication::instance()->property("platformName").toString().startsWith( QLatin1String("wayland"), Qt::CaseInsensitive); } KSldApp::~KSldApp() { } static int s_XTimeout; static int s_XInterval; static int s_XBlanking; static int s_XExposures; void KSldApp::cleanUp() { if (m_lockProcess && m_lockProcess->state() != QProcess::NotRunning) { m_lockProcess->terminate(); } delete m_lockProcess; delete m_lockWindow; // Restore X screensaver parameters XSetScreenSaver(QX11Info::display(), s_XTimeout, s_XInterval, s_XBlanking, s_XExposures); } static bool s_graceTimeKill = false; static bool s_logindExit = false; static bool hasXInput() { #ifdef X11_Xinput_FOUND Display *dpy = QX11Info::display(); int xi_opcode, event, error; // init XInput extension if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) { return false; } // verify that the XInput extension is at at least version 2.0 int major = 2, minor = 0; int result = XIQueryVersion(dpy, &major, &minor); if (result == BadImplementation) { // Xinput 2.2 returns BadImplementation if checked against 2.0 major = 2; minor = 2; return XIQueryVersion(dpy, &major, &minor) == Success; } return result == Success; #else return false; #endif } void KSldApp::initializeX11() { m_hasXInput2 = hasXInput(); // Save X screensaver parameters XGetScreenSaver(QX11Info::display(), &s_XTimeout, &s_XInterval, &s_XBlanking, &s_XExposures); // And disable it. The internal X screensaver is not used at all, but we use its // internal idle timer (and it is also used by DPMS support in X). This timer must not // be altered by this code, since e.g. resetting the counter after activating our // screensaver would prevent DPMS from activating. We use the timer merely to detect // user activity. XSetScreenSaver(QX11Info::display(), 0, s_XInterval, s_XBlanking, s_XExposures); } void KSldApp::initialize() { if (m_isX11) { initializeX11(); } // Global keys if (KAuthorized::authorizeAction(QStringLiteral("lock_screen"))) { qDebug() << "Configuring Lock Action"; QAction *a = new QAction(this); a->setObjectName(QStringLiteral("Lock Session")); a->setProperty("componentName", QStringLiteral("ksmserver")); a->setText(i18n("Lock Session")); KGlobalAccel::self()->setGlobalShortcut(a, QList() << Qt::META+Qt::Key_L << Qt::ALT+Qt::CTRL+Qt::Key_L << Qt::Key_ScreenSaver ); connect(a, &QAction::triggered, this, [this]() { lock(EstablishLock::Immediate); } ); } // idle support auto idleTimeSignal = static_cast(&KIdleTime::timeoutReached); connect(KIdleTime::instance(), idleTimeSignal, this, [this](int identifier) { if (identifier != m_idleId) { // not our identifier return; } if (lockState() != Unlocked) { return; } if (m_inhibitCounter // either we got a direct inhibit request thru our outdated o.f.Screensaver iface ... || isFdoPowerInhibited()) { // ... or the newer one at o.f.PowerManagement.Inhibit // there is at least one process blocking the auto lock of screen locker return; } if (m_lockGrace) { // short-circuit if grace time is zero m_inGraceTime = true; } else if (m_lockGrace == -1) { m_inGraceTime = true; // if no timeout configured, grace time lasts forever } lock(EstablishLock::Delayed); } ); m_lockProcess = new QProcess(); m_lockProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_lockProcess->setReadChannel(QProcess::StandardOutput); auto finishedSignal = static_cast(&QProcess::finished); connect(m_lockProcess, finishedSignal, this, [this](int exitCode, QProcess::ExitStatus exitStatus) { if (m_isWayland && m_waylandDisplay && m_greeterClientConnection) { m_greeterClientConnection->destroy(); } if ((!exitCode && exitStatus == QProcess::NormalExit) || s_graceTimeKill || s_logindExit) { // unlock process finished successfully - we can remove the lock grab s_graceTimeKill = false; s_logindExit = false; doUnlock(); return; } // failure, restart lock process m_greeterCrashedCounter++; if (m_greeterCrashedCounter < 4) { // Perhaps it crashed due to a graphics driver issue, force software rendering now setForceSoftwareRendering(true); startLockProcess(EstablishLock::Immediate); } else if (m_lockWindow) { m_lockWindow->emergencyShow(); } } ); connect(m_lockProcess, static_cast(&QProcess::error), this, [this](QProcess::ProcessError error) { if (error == QProcess::FailedToStart) { doUnlock(); m_waylandServer->stop(); qCritical() << "Greeter Process not available"; } } ); m_lockedTimer.invalidate(); m_graceTimer->setSingleShot(true); connect(m_graceTimer, &QTimer::timeout, this, &KSldApp::endGraceTime); // create our D-Bus interface new Interface(this); // connect to logind m_logind = new LogindIntegration(this); connect(m_logind, &LogindIntegration::requestLock, this, [this]() { lock(EstablishLock::Immediate); } ); connect(m_logind, &LogindIntegration::requestUnlock, this, [this]() { if (lockState() == Locked || lockState() == AcquiringLock) { if (m_lockProcess->state() != QProcess::NotRunning) { s_logindExit = true; m_lockProcess->terminate(); } else { doUnlock(); } } } ); connect(m_logind, &LogindIntegration::prepareForSleep, this, [this](bool goingToSleep) { if (!goingToSleep) { // not interested in doing anything on wakeup return; } if (KScreenSaverSettings::lockOnResume()) { lock(EstablishLock::Immediate); } } ); connect(m_logind, &LogindIntegration::inhibited, this, [this]() { // if we are already locked, we immediatelly remove the inhibition lock if (m_lockState == KSldApp::Locked) { m_logind->uninhibit(); } } ); connect(m_logind, &LogindIntegration::connectedChanged, this, [this]() { if (m_logind->isConnected() && m_lockState == ScreenLocker::KSldApp::Unlocked && KScreenSaverSettings::lockOnResume()) { m_logind->inhibit(); } } ); connect(this, &KSldApp::locked, this, [this]() { m_logind->uninhibit(); if (m_lockGrace > 0 && m_inGraceTime) { m_graceTimer->start(m_lockGrace); } } ); connect(this, &KSldApp::unlocked, this, [this]() { if (KScreenSaverSettings::lockOnResume()) { m_logind->inhibit(); } } ); m_globalAccel = new GlobalAccel(this); connect(this, &KSldApp::locked, m_globalAccel, &GlobalAccel::prepare); connect(this, &KSldApp::unlocked, m_globalAccel, &GlobalAccel::release); // fallback for non-logind systems: // connect to signal emitted by Solid. This is emitted unconditionally also on logind enabled systems // ksld ignores it in case logind is used QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.Solid.PowerManagement"), QStringLiteral("/org/kde/Solid/PowerManagement/Actions/SuspendSession"), QStringLiteral("org.kde.Solid.PowerManagement.Actions.SuspendSession"), QStringLiteral("aboutToSuspend"), this, SLOT(solidSuspend())); configure(); } void KSldApp::configure() { KScreenSaverSettings::self()->load(); // idle support if (m_idleId) { KIdleTime::instance()->removeIdleTimeout(m_idleId); m_idleId = 0; } const int timeout = KScreenSaverSettings::timeout(); // screen saver enabled means there is an auto lock timer // timeout > 0 is for backwards compatibility with old configs if (KScreenSaverSettings::autolock() && timeout > 0) { // timeout stored in minutes m_idleId = KIdleTime::instance()->addIdleTimeout(timeout*1000*60); } if (KScreenSaverSettings::lock()) { // lockGrace is stored in seconds m_lockGrace = KScreenSaverSettings::lockGrace() * 1000; } else { m_lockGrace = -1; } if (m_logind && m_logind->isConnected()) { if (KScreenSaverSettings::lockOnResume() && !m_logind->isInhibited()) { m_logind->inhibit(); } else if (!KScreenSaverSettings::lockOnResume() && m_logind->isInhibited()) { m_logind->uninhibit(); } } } void KSldApp::lock(EstablishLock establishLock, int attemptCount) { if (lockState() != Unlocked) { // already locked or acquiring lock, no need to lock again // but make sure it's really locked endGraceTime(); if (establishLock == EstablishLock::Immediate) { // signal the greeter to switch to immediateLock mode kill(m_lockProcess->pid(), SIGUSR1); } return; } if (attemptCount == 0) { emit aboutToLock(); } qDebug() << "lock called"; if (!establishGrab()) { if (attemptCount < 3) { qWarning() << "Could not establish screen lock. Trying again in 10ms"; QTimer::singleShot(10, this, [=]() { lock(establishLock, attemptCount+1); }); } else { qCritical() << "Could not establish screen lock"; } return; } KNotification::event(QStringLiteral("locked"), i18n("Screen locked"), QPixmap(), nullptr, KNotification::CloseOnTimeout, QStringLiteral("ksmserver")); // blank the screen showLockWindow(); m_lockState = AcquiringLock; setForceSoftwareRendering(false); // start unlock screen process startLockProcess(establishLock); emit lockStateChanged(); } /* * Forward declarations: * Only called from KSldApp::establishGrab(). Using from somewhere else is incorrect usage! **/ static bool grabKeyboard(); static bool grabMouse(); class XServerGrabber { public: XServerGrabber() { xcb_grab_server(QX11Info::connection()); } ~XServerGrabber() { xcb_ungrab_server(QX11Info::connection()); xcb_flush(QX11Info::connection()); } }; bool KSldApp::establishGrab() { if (!m_isX11) { return true; } XSync(QX11Info::display(), False); XServerGrabber serverGrabber; if (!grabKeyboard()) { return false; } if (!grabMouse()) { XUngrabKeyboard(QX11Info::display(), CurrentTime); XFlush(QX11Info::display()); return false; } #ifdef X11_Xinput_FOUND if (m_hasXInput2) { // get all devices Display *dpy = QX11Info::display(); int numMasters; XIDeviceInfo *masters = XIQueryDevice(dpy, XIAllMasterDevices, &numMasters); bool success = true; for (int i = 0; i < numMasters; ++i) { // ignoring core pointer and core keyboard as we already grabbed them if (qstrcmp(masters[i].name, "Virtual core pointer") == 0) { continue; } if (qstrcmp(masters[i].name, "Virtual core keyboard") == 0) { continue; } XIEventMask mask; uchar bitmask[] = { 0, 0 }; mask.deviceid = masters[i].deviceid; mask.mask = bitmask; mask.mask_len = sizeof(bitmask); XISetMask(bitmask, XI_ButtonPress); XISetMask(bitmask, XI_ButtonRelease); XISetMask(bitmask, XI_Motion); XISetMask(bitmask, XI_Enter); XISetMask(bitmask, XI_Leave); const int result = XIGrabDevice(dpy, masters[i].deviceid, QX11Info::appRootWindow(), XCB_TIME_CURRENT_TIME, XCB_CURSOR_NONE, XIGrabModeAsync, XIGrabModeAsync, True, &mask); if (result != XIGrabSuccess) { success = false; break; } } if (!success) { // ungrab all devices again for (int i = 0; i < numMasters; ++i) { XIUngrabDevice(dpy, masters[i].deviceid, XCB_TIME_CURRENT_TIME); } xcb_connection_t *c = QX11Info::connection(); xcb_ungrab_keyboard(c, XCB_CURRENT_TIME); xcb_ungrab_pointer(c, XCB_CURRENT_TIME); } XIFreeDeviceInfo(masters); XFlush(dpy); return success; } #endif return true; } static bool grabKeyboard() { int rv = XGrabKeyboard( QX11Info::display(), QX11Info::appRootWindow(), True, GrabModeAsync, GrabModeAsync, CurrentTime ); return (rv == GrabSuccess); } static bool grabMouse() { #define GRABEVENTS ButtonPressMask | ButtonReleaseMask | PointerMotionMask | \ EnterWindowMask | LeaveWindowMask int rv = XGrabPointer( QX11Info::display(), QX11Info::appRootWindow(), True, GRABEVENTS, GrabModeAsync, GrabModeAsync, None, None, CurrentTime ); #undef GRABEVENTS return (rv == GrabSuccess); } void KSldApp::doUnlock() { qDebug() << "Grab Released"; if (m_isX11) { xcb_connection_t *c = QX11Info::connection(); xcb_ungrab_keyboard(c, XCB_CURRENT_TIME); xcb_ungrab_pointer(c, XCB_CURRENT_TIME); xcb_flush(c); #ifdef X11_Xinput_FOUND if (m_hasXInput2) { // get all devices Display *dpy = QX11Info::display(); int numMasters; XIDeviceInfo *masters = XIQueryDevice(dpy, XIAllMasterDevices, &numMasters); // ungrab all devices again for (int i = 0; i < numMasters; ++i) { XIUngrabDevice(dpy, masters[i].deviceid, XCB_TIME_CURRENT_TIME); } XIFreeDeviceInfo(masters); XFlush(dpy); } #endif } hideLockWindow(); // delete the window again, to get rid of event filter delete m_lockWindow; m_lockWindow = nullptr; m_lockState = Unlocked; m_lockedTimer.invalidate(); m_greeterCrashedCounter = 0; endGraceTime(); m_waylandServer->stop(); KNotification::event(QStringLiteral("unlocked"), i18n("Screen unlocked"), QPixmap(), nullptr, KNotification::CloseOnTimeout, QStringLiteral("ksmserver")); emit unlocked(); emit lockStateChanged(); } bool KSldApp::isFdoPowerInhibited() const { return m_powerManagementInhibition->isInhibited(); } void KSldApp::startLockProcess(EstablishLock establishLock) { QProcessEnvironment env = m_greeterEnv; if (m_isWayland && m_waylandDisplay) { int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qWarning() << "Can not create socket"; emit m_lockProcess->error(QProcess::FailedToStart); return; } m_greeterClientConnection = m_waylandDisplay->createClient(sx[0]); connect(m_greeterClientConnection, &QObject::destroyed, this, [this] (QObject *destroyedObject) { if (destroyedObject != m_greeterClientConnection) { return; } m_greeterClientConnection = nullptr; emit greeterClientConnectionChanged(); } ); emit greeterClientConnectionChanged(); int socket = dup(sx[1]); if (socket >= 0) { env.insert(QStringLiteral("WAYLAND_SOCKET"), QString::number(socket)); } } QStringList args; if (establishLock == EstablishLock::Immediate) { args << QStringLiteral("--immediateLock"); } if (establishLock == EstablishLock::DefaultToSwitchUser) { args << QStringLiteral("--immediateLock"); args << QStringLiteral("--switchuser"); } if (m_lockGrace > 0) { args << QStringLiteral("--graceTime"); args << QString::number(m_lockGrace); } if (m_lockGrace == -1) { args << QStringLiteral("--nolock"); } if (m_forceSoftwareRendering) { env.insert(s_qtQuickBackend, QStringLiteral("software")); } // start the Wayland server int fd = m_waylandServer->start(); if (fd == -1) { emit m_lockProcess->error(QProcess::FailedToStart); return; } args << QStringLiteral("--ksldfd"); args << QString::number(fd); m_lockProcess->setProcessEnvironment(env); m_lockProcess->start(QStringLiteral(KSCREENLOCKER_GREET_BIN), args); close(fd); } void KSldApp::showLockWindow() { if (!m_lockWindow) { if (m_isX11) { m_lockWindow = new X11Locker(this); } if (m_isWayland) { m_lockWindow = new WaylandLocker(m_waylandDisplay, this); } if (!m_lockWindow) { return; } m_lockWindow->setGlobalAccel(m_globalAccel); connect(m_lockWindow, &AbstractLocker::userActivity, m_lockWindow, [this]() { if (isGraceTime()) { unlock(); } }, Qt::QueuedConnection ); connect(m_lockWindow, &AbstractLocker::lockWindowShown, m_lockWindow, [this] { lockScreenShown(); } , Qt::QueuedConnection); connect(m_waylandServer, &WaylandServer::x11WindowAdded, m_lockWindow, &AbstractLocker::addAllowedWindow); } m_lockWindow->showLockWindow(); if (m_isX11) { XSync(QX11Info::display(), False); } } void KSldApp::hideLockWindow() { if (!m_lockWindow) { return; } m_lockWindow->hideLockWindow(); } uint KSldApp::activeTime() const { if (m_lockedTimer.isValid()) { return m_lockedTimer.elapsed(); } return 0; } bool KSldApp::isGraceTime() const { return m_inGraceTime; } void KSldApp::endGraceTime() { m_graceTimer->stop(); m_inGraceTime = false; } void KSldApp::unlock() { if (!isGraceTime()) { return; } s_graceTimeKill = true; m_lockProcess->terminate(); } void KSldApp::inhibit() { ++m_inhibitCounter; } void KSldApp::uninhibit() { --m_inhibitCounter; } void KSldApp::solidSuspend() { // ignore in case that we use logind if (m_logind && m_logind->isConnected()) { return; } if (KScreenSaverSettings::lockOnResume()) { lock(EstablishLock::Immediate); } } void KSldApp::setWaylandDisplay(KWayland::Server::Display *display) { if (m_waylandDisplay != display) { m_waylandDisplay = display; } } void KSldApp::lockScreenShown() { if (m_lockState == Locked) { return; } m_lockState = Locked; m_lockedTimer.restart(); emit locked(); emit lockStateChanged(); } void KSldApp::setGreeterEnvironment(const QProcessEnvironment &env) { m_greeterEnv = env; if (m_isWayland) { m_greeterEnv.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("wayland")); } } bool KSldApp::event(QEvent *event) { if (m_globalAccel && event->type() == QEvent::KeyPress) { if (m_globalAccel->keyEvent(static_cast(event))) { event->setAccepted(true); } } return false; } } // namespace diff --git a/x11locker.cpp b/x11locker.cpp index 3b2a6f8..66f1666 100644 --- a/x11locker.cpp +++ b/x11locker.cpp @@ -1,539 +1,534 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 1999 Martin R. Jones Copyright (C) 2002 Luboš Luňák Copyright (C) 2003 Oswald Buddenhagen Copyright (C) 2008 Chani Armitage Copyright (C) 2011 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "x11locker.h" #include "globalaccel.h" #include "abstractlocker.h" // KDE -#include // Qt #include #include -#include -#include #include -#include -#include #include // X11 #include #include #include static Window gVRoot = 0; static Window gVRootData = 0; static Atom gXA_VROOT; static Atom gXA_SCREENSAVER_VERSION; namespace ScreenLocker { X11Locker::X11Locker(QObject *parent) : AbstractLocker(parent) , QAbstractNativeEventFilter() , m_focusedLockWindow(XCB_WINDOW_NONE) { initialize(); } X11Locker::~X11Locker() { qApp->removeNativeEventFilter(this); } void X11Locker::initialize() { qApp->installNativeEventFilter(this); XWindowAttributes rootAttr; XGetWindowAttributes(QX11Info::display(), QX11Info::appRootWindow(), &rootAttr); QApplication::desktop(); // make Qt set its event mask on the root window first XSelectInput( QX11Info::display(), QX11Info::appRootWindow(), SubstructureNotifyMask | rootAttr.your_event_mask ); // Get root window size updateGeo(); // virtual root property gXA_VROOT = XInternAtom (QX11Info::display(), "__SWM_VROOT", False); gXA_SCREENSAVER_VERSION = XInternAtom (QX11Info::display(), "_SCREENSAVER_VERSION", False); // read the initial information about all toplevel windows Window r, p; Window* real; unsigned nreal; if( XQueryTree( QX11Info::display(), QX11Info::appRootWindow(), &r, &p, &real, &nreal ) && real != nullptr ) { for( unsigned i = 0; i < nreal; ++i ) { XWindowAttributes winAttr; if (XGetWindowAttributes(QX11Info::display(), real[ i ], &winAttr)) { WindowInfo info; info.window = real[ i ]; info.viewable = ( winAttr.map_state == IsViewable ); m_windowInfo.append( info ); // ordered bottom to top } } XFree( real ); } connect(QApplication::desktop(), &QDesktopWidget::resized, this, &X11Locker::updateGeo); connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, this, &X11Locker::updateGeo); } void X11Locker::showLockWindow() { m_background->hide(); // Some xscreensaver hacks check for this property const char *version = "KDE 4.0"; XChangeProperty (QX11Info::display(), m_background->winId(), gXA_SCREENSAVER_VERSION, XA_STRING, 8, PropModeReplace, (unsigned char *) version, strlen(version)); qDebug() << "Lock window Id: " << m_background->winId(); m_background->setPosition(0, 0); XSync(QX11Info::display(), False); setVRoot( m_background->winId(), m_background->winId() ); } //--------------------------------------------------------------------------- // // Hide the screen locker window // void X11Locker::hideLockWindow() { emit userActivity(); m_background->hide(); m_background->lower(); removeVRoot(m_background->winId()); XDeleteProperty(QX11Info::display(), m_background->winId(), gXA_SCREENSAVER_VERSION); if ( gVRoot ) { unsigned long vroot_data[1] = { gVRootData }; XChangeProperty(QX11Info::display(), gVRoot, gXA_VROOT, XA_WINDOW, 32, PropModeReplace, (unsigned char *)vroot_data, 1); gVRoot = 0; } XSync(QX11Info::display(), False); m_allowedWindows.clear(); } //--------------------------------------------------------------------------- static int ignoreXError(Display *, XErrorEvent *) { return 0; } //--------------------------------------------------------------------------- // // Save the current virtual root window // void X11Locker::saveVRoot() { Window rootReturn, parentReturn, *children; unsigned int numChildren; Window root = QX11Info::appRootWindow(); gVRoot = 0; gVRootData = 0; int (*oldHandler)(Display *, XErrorEvent *); oldHandler = XSetErrorHandler(ignoreXError); if (XQueryTree(QX11Info::display(), root, &rootReturn, &parentReturn, &children, &numChildren)) { for (unsigned int i = 0; i < numChildren; i++) { Atom actual_type; int actual_format; unsigned long nitems, bytesafter; unsigned char *newRoot = nullptr; if ((XGetWindowProperty(QX11Info::display(), children[i], gXA_VROOT, 0, 1, False, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytesafter, &newRoot) == Success) && newRoot) { gVRoot = children[i]; Window *dummy = (Window*)newRoot; gVRootData = *dummy; XFree ((char*) newRoot); break; } } if (children) { XFree((char *)children); } } XSetErrorHandler(oldHandler); } //--------------------------------------------------------------------------- // // Set the virtual root property // void X11Locker::setVRoot(Window win, Window vr) { if (gVRoot) removeVRoot(gVRoot); unsigned long rw = QX11Info::appRootWindow(); unsigned long vroot_data[1] = { vr }; Window rootReturn, parentReturn, *children; unsigned int numChildren; Window top = win; while (1) { if (!XQueryTree(QX11Info::display(), top , &rootReturn, &parentReturn, &children, &numChildren)) return; if (children) XFree((char *)children); if (parentReturn == rw) { break; } else top = parentReturn; } XChangeProperty(QX11Info::display(), top, gXA_VROOT, XA_WINDOW, 32, PropModeReplace, (unsigned char *)vroot_data, 1); } //--------------------------------------------------------------------------- // // Remove the virtual root property // void X11Locker::removeVRoot(Window win) { XDeleteProperty (QX11Info::display(), win, gXA_VROOT); } void X11Locker::fakeFocusIn( WId window ) { if (window == m_focusedLockWindow) { return; } // We have keyboard grab, so this application will // get keyboard events even without having focus. // Fake FocusIn to make Qt realize it has the active // window, so that it will correctly show cursor in the dialog. XEvent ev; memset(&ev, 0, sizeof(ev)); ev.xfocus.display = QX11Info::display(); ev.xfocus.type = FocusIn; ev.xfocus.window = window; ev.xfocus.mode = NotifyNormal; ev.xfocus.detail = NotifyAncestor; XSendEvent( QX11Info::display(), window, False, NoEventMask, &ev ); XFlush(QX11Info::display()); m_focusedLockWindow = window; } template< typename T> void coordFromEvent(xcb_generic_event_t *event, int *x, int *y) { T *e = reinterpret_cast(event); *x = e->event_x; *y = e->event_y; } template void sendEvent(xcb_generic_event_t *event, xcb_window_t target, int x, int y) { T e = *(reinterpret_cast(event)); e.event = target; e.child = target; e.event_x = x; e.event_y = y; xcb_send_event(QX11Info::connection(), false, target, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast(&e)); } bool X11Locker::nativeEventFilter(const QByteArray &eventType, void *message, long int *) { if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { return false; } xcb_generic_event_t *event = reinterpret_cast(message); const uint8_t responseType = event->response_type & ~0x80; if (globalAccel() && responseType == XCB_KEY_PRESS) { if (globalAccel()->checkKeyPress(reinterpret_cast(event))) { emit userActivity(); return true; } } bool ret = false; switch (responseType) { case XCB_BUTTON_PRESS: case XCB_BUTTON_RELEASE: case XCB_KEY_PRESS: case XCB_KEY_RELEASE: case XCB_MOTION_NOTIFY: emit userActivity(); if (!m_lockWindows.isEmpty()) { int x = 0; int y = 0; if (responseType == XCB_KEY_PRESS || responseType == XCB_KEY_RELEASE) { coordFromEvent(event, &x, &y); } else if (responseType == XCB_BUTTON_PRESS || responseType == XCB_BUTTON_RELEASE) { coordFromEvent(event, &x, &y); } else if (responseType == XCB_MOTION_NOTIFY) { coordFromEvent(event, &x, &y); } Window root_return; int x_return, y_return; unsigned int width_return, height_return, border_width_return, depth_return; for (WId window : qAsConst(m_lockWindows)) { if (XGetGeometry(QX11Info::display(), window, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return) && (x>=x_return && x<=x_return+(int)width_return) && (y>=y_return && y<=y_return+(int)height_return) ) { // We need to do our own focus handling (see comment in fakeFocusIn). // For now: Focus on clicks inside the window if (responseType == XCB_BUTTON_PRESS) { fakeFocusIn(window); } const int targetX = x - x_return; const int targetY = y - y_return; if (responseType == XCB_KEY_PRESS || responseType == XCB_KEY_RELEASE) { sendEvent(event, window, targetX, targetY); } else if (responseType == XCB_BUTTON_PRESS || responseType == XCB_BUTTON_RELEASE) { sendEvent(event, window, targetX, targetY); } else if (responseType == XCB_MOTION_NOTIFY) { sendEvent(event, window, targetX, targetY); } break; } } ret = true; } break; case XCB_CONFIGURE_NOTIFY: { // from SubstructureNotifyMask on the root window xcb_configure_notify_event_t *xc = reinterpret_cast(event); if (xc->event == QX11Info::appRootWindow()) { int index = findWindowInfo( xc->window ); if( index >= 0 ) { int index2 = xc->above_sibling ? findWindowInfo( xc->above_sibling ) : 0; if( index2 < 0 ) qDebug() << "Unknown above for ConfigureNotify"; else { // move just above the other window if( index2 < index ) ++index2; m_windowInfo.move( index, index2 ); } } else qDebug() << "Unknown toplevel for ConfigureNotify"; //kDebug() << "ConfigureNotify:"; //the stacking order changed, so let's change the stacking order again to what we want stayOnTop(); ret = true; } break; } case XCB_MAP_NOTIFY: { // from SubstructureNotifyMask on the root window xcb_map_notify_event_t *xm = reinterpret_cast(event); if (xm->event == QX11Info::appRootWindow()) { qDebug() << "MapNotify:" << xm->window; int index = findWindowInfo( xm->window ); if( index >= 0 ) m_windowInfo[ index ].viewable = true; else qDebug() << "Unknown toplevel for MapNotify"; if (m_allowedWindows.contains(xm->window)) { if (m_lockWindows.contains(xm->window)) { qDebug() << "uhoh! duplicate!"; } else { if (!m_background->isVisible()) { // not yet shown and we have a lock window, so we show our own window m_background->show(); } m_lockWindows.prepend(xm->window); fakeFocusIn(xm->window); } } if (xm->window == m_background->winId()) { m_background->update(); emit lockWindowShown(); return false; } stayOnTop(); ret = true; } break; } case XCB_UNMAP_NOTIFY: { xcb_unmap_notify_event_t *xu = reinterpret_cast(event); if (xu->event == QX11Info::appRootWindow()) { qDebug() << "UnmapNotify:" << xu->window; int index = findWindowInfo( xu->window ); if( index >= 0 ) m_windowInfo[ index ].viewable = false; else qDebug() << "Unknown toplevel for MapNotify"; m_lockWindows.removeAll(xu->window); if (m_focusedLockWindow == xu->event && !m_lockWindows.empty()) { // The currently focused window vanished, just focus the first one in the list fakeFocusIn(m_lockWindows[0]); } ret = true; } break; } case XCB_CREATE_NOTIFY: { xcb_create_notify_event_t *xc = reinterpret_cast(event); if (xc->parent == QX11Info::appRootWindow()) { qDebug() << "CreateNotify:" << xc->window; int index = findWindowInfo( xc->window ); if( index >= 0 ) qDebug() << "Already existing toplevel for CreateNotify"; else { WindowInfo info; info.window = xc->window; info.viewable = false; m_windowInfo.append( info ); } ret = true; } break; } case XCB_DESTROY_NOTIFY: { xcb_destroy_notify_event_t *xd = reinterpret_cast(event); if (xd->event == QX11Info::appRootWindow()) { int index = findWindowInfo( xd->window ); if( index >= 0 ) m_windowInfo.removeAt( index ); else qDebug() << "Unknown toplevel for DestroyNotify"; ret = true; } break; } case XCB_REPARENT_NOTIFY: { xcb_reparent_notify_event_t *xr = reinterpret_cast(event); if (xr->event == QX11Info::appRootWindow() && xr->parent != QX11Info::appRootWindow()) { int index = findWindowInfo( xr->window ); if( index >= 0 ) m_windowInfo.removeAt( index ); else qDebug() << "Unknown toplevel for ReparentNotify away"; } else if (xr->parent == QX11Info::appRootWindow()) { int index = findWindowInfo( xr->window ); if( index >= 0 ) qDebug() << "Already existing toplevel for ReparentNotify"; else { WindowInfo info; info.window = xr->window; info.viewable = false; m_windowInfo.append( info ); } } break; } case XCB_CIRCULATE_NOTIFY: { xcb_circulate_notify_event_t *xc = reinterpret_cast(event); if (xc->event == QX11Info::appRootWindow()) { int index = findWindowInfo( xc->window ); if( index >= 0 ) { m_windowInfo.move( index, xc->place == PlaceOnTop ? m_windowInfo.size() - 1 : 0 ); } else qDebug() << "Unknown toplevel for CirculateNotify"; } break; } } return ret; } int X11Locker::findWindowInfo(Window w) { for( int i = 0; i < m_windowInfo.size(); ++i ) if( m_windowInfo[ i ].window == w ) return i; return -1; } void X11Locker::stayOnTop() { // this restacking is written in a way so that // if the stacking positions actually don't change, // all restacking operations will be no-op, // and no ConfigureNotify will be generated, // thus avoiding possible infinite loops QVector< Window > stack( m_lockWindows.count() + 1 ); int count = 0; for ( WId w : qAsConst(m_lockWindows)) stack[ count++ ] = w; // finally, the lock window stack[ count++ ] = m_background->winId(); // do the actual restacking if needed XRaiseWindow( QX11Info::display(), stack[ 0 ] ); if( count > 1 ) XRestackWindows( QX11Info::display(), stack.data(), count ); XFlush(QX11Info::display()); } void X11Locker::updateGeo() { QDesktopWidget *desktop = QApplication::desktop(); m_background->setGeometry(desktop->geometry()); m_background->update(); } void X11Locker::addAllowedWindow(quint32 window) { m_allowedWindows << window; // test whether it's to show const int index = findWindowInfo( window ); if (index == -1 || !m_windowInfo[ index ].viewable) { return; } if (m_lockWindows.contains(window)) { qDebug() << "uhoh! duplicate!"; } else { if (!m_background->isVisible()) { // not yet shown and we have a lock window, so we show our own window m_background->show(); } if (m_lockWindows.empty()) { // Make sure to focus the first window m_focusedLockWindow = XCB_WINDOW_NONE; fakeFocusIn(window); } m_lockWindows.prepend(window); stayOnTop(); } } }