diff --git a/greeter/greeterapp.cpp b/greeter/greeterapp.cpp index 47fcb03..bcfcbdf 100644 --- a/greeter/greeterapp.cpp +++ b/greeter/greeterapp.cpp @@ -1,703 +1,702 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2004 Chris Howells 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 "greeterapp.h" #include "kscreensaversettings.h" #include "authenticator.h" #include "noaccessnetworkaccessmanagerfactory.h" #include "wallpaper_integration.h" // KDE #include #include #include #include #include #include //Plasma #include #include #include // KWayland #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include // Wayland #include #include // X11 #include #include #include // #include // this is usable to fake a "screensaver" installation for testing // *must* be "0" for every public commit! #define TEST_SCREENSAVER 0 namespace ScreenLocker { class FocusOutEventFilter : public QAbstractNativeEventFilter { public: bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override { Q_UNUSED(result) if (qstrcmp(eventType, "xcb_generic_event_t") != 0) { return false; } xcb_generic_event_t *event = reinterpret_cast(message); if ((event->response_type & ~0x80) == XCB_FOCUS_OUT) { return true; } return false; } }; // App UnlockApp::UnlockApp(int &argc, char **argv) : QGuiApplication(argc, argv) , m_resetRequestIgnoreTimer(new QTimer(this)) , m_delayedLockTimer(0) , m_testing(false) , m_ignoreRequests(false) , m_immediateLock(false) , m_authenticator(new Authenticator(this)) , m_graceTime(0) , m_noLock(false) , m_defaultToSwitchUser(false) , m_wallpaperIntegration(new WallpaperIntegration(this)) { connect(m_authenticator, &Authenticator::succeeded, this, &QCoreApplication::quit); initialize(); connect(this, &UnlockApp::screenAdded, this, &UnlockApp::desktopResized); connect(this, &UnlockApp::screenRemoved, this, &UnlockApp::desktopResized); if (QX11Info::isPlatformX11()) { installNativeEventFilter(new FocusOutEventFilter); } } UnlockApp::~UnlockApp() { qDeleteAll(m_views); if (m_ksldInterface) { org_kde_ksld_destroy(m_ksldInterface); } if (m_ksldRegistry) { delete m_ksldRegistry; } if (m_ksldConnection) { m_ksldConnection->deleteLater(); m_ksldConnectionThread->quit(); m_ksldConnectionThread->wait(); } } void UnlockApp::initialize() { initializeWayland(); // set up the request ignore timeout, so that multiple requests to sleep/suspend/shutdown // are not processed in quick (and confusing) succession) m_resetRequestIgnoreTimer->setSingleShot(true); m_resetRequestIgnoreTimer->setInterval(2000); connect(m_resetRequestIgnoreTimer, &QTimer::timeout, this, &UnlockApp::resetRequestIgnore); // disable DrKonqi as the crash dialog blocks the restart of the locker KCrash::setDrKonqiEnabled(false); KScreenSaverSettings::self()->load(); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); m_packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!m_packageName.isEmpty()) { package.setPath(m_packageName); } if (!KScreenSaverSettings::theme().isEmpty()) { package.setPath(KScreenSaverSettings::theme()); } m_mainQmlPath = QUrl::fromLocalFile(package.filePath("lockscreenmainscript")); m_wallpaperIntegration->setConfig(KScreenSaverSettings::self()->sharedConfig()); m_wallpaperIntegration->setPluginName(KScreenSaverSettings::self()->wallpaperPlugin()); m_wallpaperIntegration->init(); installEventFilter(this); } void UnlockApp::initializeWayland() { if (!platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { return; } using namespace KWayland::Client; auto *c = ConnectionThread::fromApplication(this); if (!c) { return; } Registry *r = new Registry(this); r->create(c); r->setup(); c->roundtrip(); const auto i = r->interface(Registry::Interface::PlasmaShell); if (i.name == 0) { return; } m_plasmaShell = r->createPlasmaShell(i.name, i.version, this); } void UnlockApp::loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view) { auto package = m_wallpaperIntegration->package(); if (!package.isValid()) { qWarning() << "Error loading the wallpaper, no valid package loaded"; return; } auto qmlObject = new KDeclarative::QmlObjectSharedEngine(view); qmlObject->setInitializationDelayed(true); qmlObject->setPackage(package); qmlObject->rootContext()->setContextProperty(QStringLiteral("wallpaper"), m_wallpaperIntegration); view->setProperty("wallpaperGraphicsObject", QVariant::fromValue(qmlObject)); connect(qmlObject, &KDeclarative::QmlObject::finished, this, [this, qmlObject, view] { auto item = qobject_cast(qmlObject->rootObject()); if (!item) { qWarning() << "Wallpaper needs to be a QtQuick Item"; return; } item->setParentItem(view->rootObject()); item->setZ(-1000); //set anchors QQmlExpression expr(qmlObject->engine()->rootContext(), item, QStringLiteral("parent")); QQmlProperty prop(item, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } ); } void UnlockApp::desktopResized() { const int nScreens = screens().count(); // remove useless views and savers while (m_views.count() > nScreens) { m_views.takeLast()->deleteLater(); } // extend views and savers to current demand for (int i = m_views.count(); i < nScreens; ++i) { // create the view auto *view = new KQuickAddons::QuickViewSharedEngine(); view->setColor(Qt::black); // first create KDeclarative, to be sure that it created a KIO Network Factory KDeclarative::KDeclarative declarative; declarative.setDeclarativeEngine(view->engine()); declarative.setupBindings(); if (!m_testing) { if (QX11Info::isPlatformX11()) { view->setFlags(Qt::X11BypassWindowManagerHint); } else { view->setFlags(Qt::FramelessWindowHint); } } if (m_ksldInterface) { view->create(); org_kde_ksld_x11window(m_ksldInterface, view->winId()); wl_display_flush(m_ksldConnection->display()); } if (m_plasmaShell) { using namespace KWayland::Client; if (Surface *surface = Surface::fromWindow(view)) { PlasmaShellSurface *shellSurface = m_plasmaShell->createSurface(surface, view); view->setProperty("plasmaShellSurface", QVariant::fromValue(shellSurface)); } } // engine stuff QQmlContext* context = view->engine()->rootContext(); const KUser user; const QString fullName = user.property(KUser::FullName).toString(); context->setContextProperty(QStringLiteral("kscreenlocker_userName"), fullName.isEmpty() ? user.loginName() : fullName); context->setContextProperty(QStringLiteral("kscreenlocker_userImage"), user.faceIconPath()); context->setContextProperty(QStringLiteral("authenticator"), m_authenticator); context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_interfaceVersion"), 2); context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_view"), view); context->setContextProperty(QStringLiteral("defaultToSwitchUser"), m_defaultToSwitchUser); view->setSource(m_mainQmlPath); // on error, load the fallback lockscreen to not lock the user out of the system if (view->status() == QQmlComponent::Error) { static const QUrl fallbackUrl(QUrl(QStringLiteral("qrc:/fallbacktheme/LockScreen.qml"))); qWarning() << "Failed to load lockscreen QML, falling back to built-in locker"; m_mainQmlPath = fallbackUrl; view->setSource(fallbackUrl); } view->setResizeMode(KQuickAddons::QuickViewSharedEngine::SizeRootObjectToView); loadWallpaperPlugin(view); // overwrite the factory set by kdeclarative auto oldFactory = view->engine()->networkAccessManagerFactory(); view->engine()->setNetworkAccessManagerFactory(nullptr); delete oldFactory; view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory); QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked")); lockProperty.write(m_immediateLock || (!m_noLock && !m_delayedLockTimer)); QQmlProperty sleepProperty(view->rootObject(), QStringLiteral("suspendToRamSupported")); sleepProperty.write(m_canSuspend); if (view->rootObject() && view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToRam()").constData()) != -1) { connect(view->rootObject(), SIGNAL(suspendToRam()), SLOT(suspendToRam())); } QQmlProperty hibernateProperty(view->rootObject(), QStringLiteral("suspendToDiskSupported")); hibernateProperty.write(m_canHibernate); if (view->rootObject() && view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToDisk()").constData()) != -1) { connect(view->rootObject(), SIGNAL(suspendToDisk()), SLOT(suspendToDisk())); } // verify that the engine's controller didn't change Q_ASSERT(dynamic_cast(view->engine()->networkAccessManagerFactory())); m_views << view; } // update geometry of all views and savers for (int i = 0; i < nScreens; ++i) { auto *view = m_views.at(i); auto screen = QGuiApplication::screens()[i]; view->setGeometry(screen->geometry()); KWayland::Client::PlasmaShellSurface *plasmaSurface = view->property("plasmaShellSurface").value(); if (plasmaSurface) { plasmaSurface->setPosition(view->geometry().topLeft()); } if (auto object = view->property("wallpaperGraphicsObject").value()) { //initialize with our size to avoid as much resize events as possible object->completeInitialization({ {QStringLiteral("width"), view->width()}, {QStringLiteral("height"), view->height()} }); } connect(screen, &QScreen::geometryChanged, view, [view, plasmaSurface](const QRect &geo) { view->setGeometry(geo); if (plasmaSurface) { plasmaSurface->setPosition(view->geometry().topLeft()); } } ); if (m_testing) { view->show(); } else { // on Wayland we may not use fullscreen as that puts all windows on one screen if (plasmaSurface) { view->show(); } else { view->showFullScreen(); } } view->raise(); connect(view, &QQuickWindow::frameSwapped, this, [this, view] { markViewsAsVisible(view); }, Qt::QueuedConnection); } } void UnlockApp::markViewsAsVisible(KQuickAddons::QuickViewSharedEngine *view) { disconnect(view, &QQuickWindow::frameSwapped, this, 0); QQmlProperty showProperty(view->rootObject(), QStringLiteral("viewVisible")); showProperty.write(true); // random state update, actually rather required on init only QMetaObject::invokeMethod(this, "getFocus", Qt::QueuedConnection); } void UnlockApp::getFocus() { if (m_views.isEmpty()) { return; } QWindow *w = 0; // this loop is required to make the qml/graphicsscene properly handle the shared keyboard input // ie. "type something into the box of every greeter" foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { - view->requestActivate(); if (!m_testing) { view->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! } // w->setFocus(Qt::OtherFocusReason); // FIXME } // determine which window should actually be active and have the real input focus/grab // FIXME - QWidget::underMouse() // foreach (QQuickView *view, m_views) { // if (view->underMouse()) { // w = view; // break; // } // } if (!w) { // try harder foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { if (view->geometry().contains(QCursor::pos())) { w = view; break; } } } if (!w) { // fallback solution w = m_views.first(); } // activate window and grab input to be sure it really ends up there. // focus setting is still required for proper internal QWidget state (and eg. visual reflection) if (!m_testing) { w->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! } w->requestActivate(); // w->setFocus(Qt::OtherFocusReason); // FIXME } void UnlockApp::setLockedPropertyOnViews() { delete m_delayedLockTimer; m_delayedLockTimer = 0; foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked")); lockProperty.write(true); } } void UnlockApp::resetRequestIgnore() { m_ignoreRequests = false; } void UnlockApp::suspendToRam() { if (m_ignoreRequests) { return; } m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); org_kde_ksld_suspendSystem(m_ksldInterface); } void UnlockApp::suspendToDisk() { if (m_ignoreRequests) { return; } m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); org_kde_ksld_hibernateSystem(m_ksldInterface); } void UnlockApp::setTesting(bool enable) { m_testing = enable; if (m_views.isEmpty()) { return; } if (enable) { // remove bypass window manager hint foreach (KQuickAddons::QuickViewSharedEngine * view, m_views) { view->setFlags(view->flags() & ~Qt::X11BypassWindowManagerHint); } } else { foreach (KQuickAddons::QuickViewSharedEngine * view, m_views) { view->setFlags(view->flags() | Qt::X11BypassWindowManagerHint); } } } void UnlockApp::setTheme(const QString &theme) { if (theme.isEmpty() || !m_testing) { return; } m_packageName = theme; KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); package.setPath(m_packageName); m_mainQmlPath = QUrl::fromLocalFile(package.filePath("lockscreenmainscript")); } void UnlockApp::setImmediateLock(bool immediate) { m_immediateLock = immediate; } void UnlockApp::lockImmediately() { setImmediateLock(true); setLockedPropertyOnViews(); } bool UnlockApp::eventFilter(QObject *obj, QEvent *event) { if (obj != this && event->type() == QEvent::Show) { KQuickAddons::QuickViewSharedEngine *view(0); foreach (KQuickAddons::QuickViewSharedEngine *v, m_views) { if (v == obj) { view = v; break; } } if (view && view->winId() && QX11Info::isPlatformX11()) { // showing greeter view window, set property static Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False); XChangeProperty(QX11Info::display(), view->winId(), tag, tag, 32, PropModeReplace, 0, 0); } // no further processing return false; } if (event->type() == QEvent::KeyPress) { // react if saver is visible shareEvent(event, qobject_cast(obj)); return false; // we don't care } else if (event->type() == QEvent::KeyRelease) { // conditionally reshow the saver QKeyEvent *ke = static_cast(event); if (ke->key() != Qt::Key_Escape) { shareEvent(event, qobject_cast(obj)); return false; // irrelevant } return true; // don't pass } return false; } /* * This function forwards an event from one greeter window to all others * It's used to have the keyboard operate on all greeter windows (on every screen) * at once so that the user gets visual feedback on the screen he's looking at - * even if the focus is actually on a powered off screen. */ void UnlockApp::shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from) { // from can be NULL any time (because the parameter is passed as qobject_cast) // m_views.contains(from) is atm. supposed to be true but required if any further // QQuickView are added (which are not part of m_views) // this makes "from" an optimization (nullptr check aversion) if (from && m_views.contains(from)) { // NOTICE any recursion in the event sharing will prevent authentication on multiscreen setups! // Any change in regarded event processing shall be tested thoroughly! removeEventFilter(this); // prevent recursion! const bool accepted = e->isAccepted(); // store state foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { if (view != from) { QCoreApplication::sendEvent(view, e); e->setAccepted(accepted); } } installEventFilter(this); } } void UnlockApp::setGraceTime(int milliseconds) { m_graceTime = milliseconds; if (milliseconds < 0 || m_delayedLockTimer || m_noLock || m_immediateLock) { return; } m_delayedLockTimer = new QTimer(this); m_delayedLockTimer->setSingleShot(true); connect(m_delayedLockTimer, &QTimer::timeout, this, &UnlockApp::setLockedPropertyOnViews); m_delayedLockTimer->start(m_graceTime); } void UnlockApp::setNoLock(bool noLock) { m_noLock = noLock; } void UnlockApp::setDefaultToSwitchUser(bool defaultToSwitchUser) { m_defaultToSwitchUser = defaultToSwitchUser; } static void osdProgress(void *data, org_kde_ksld *org_kde_ksld, const char *icon, int32_t percent, const char *text) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->osdProgress(QString::fromUtf8(icon), percent, QString::fromUtf8(text)); } static void osdText(void *data, org_kde_ksld *org_kde_ksld, const char *icon, const char *text) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->osdText(QString::fromUtf8(icon), QString::fromUtf8(text)); } static void canSuspend(void *data, org_kde_ksld *org_kde_ksld, uint suspend) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->updateCanSuspend(suspend); } static void canHibernate(void *data, org_kde_ksld *org_kde_ksld, uint hibernate) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->updateCanHibernate(hibernate); } static const struct org_kde_ksld_listener s_listener { osdProgress, osdText, canSuspend, canHibernate }; void UnlockApp::setKsldSocket(int socket) { using namespace KWayland::Client; m_ksldConnection = new ConnectionThread; m_ksldConnection->setSocketFd(socket); m_ksldRegistry = new Registry(); EventQueue *queue = new EventQueue(m_ksldRegistry); connect(m_ksldRegistry, &Registry::interfaceAnnounced, this, [this, queue] (QByteArray interface, quint32 name, quint32 version) { if (interface != QByteArrayLiteral("org_kde_ksld")) { return; } m_ksldInterface = reinterpret_cast(wl_registry_bind(*m_ksldRegistry, name, &org_kde_ksld_interface, version)); queue->addProxy(m_ksldInterface); if (version >= 2) { org_kde_ksld_add_listener(m_ksldInterface, &s_listener, this); } for (auto v : m_views) { org_kde_ksld_x11window(m_ksldInterface, v->winId()); wl_display_flush(m_ksldConnection->display()); } } ); connect(m_ksldConnection, &ConnectionThread::connected, this, [this, queue] { m_ksldRegistry->create(m_ksldConnection); queue->setup(m_ksldConnection); m_ksldRegistry->setEventQueue(queue); m_ksldRegistry->setup(); wl_display_flush(m_ksldConnection->display()); }, Qt::QueuedConnection); m_ksldConnectionThread = new QThread(this); m_ksldConnection->moveToThread(m_ksldConnectionThread); m_ksldConnectionThread->start(); m_ksldConnection->initConnection(); } void UnlockApp::osdProgress(const QString &icon, int percent, const QString &additionalText) { for (auto v : m_views) { auto osd = v->rootObject()->findChild(QStringLiteral("onScreenDisplay")); if (!osd) { continue; } osd->setProperty("osdValue", percent); osd->setProperty("osdAdditionalText", additionalText); osd->setProperty("showingProgress", true); osd->setProperty("icon", icon); QMetaObject::invokeMethod(osd, "show"); } } void UnlockApp::osdText(const QString &icon, const QString &additionalText) { for (auto v : m_views) { auto osd = v->rootObject()->findChild(QStringLiteral("onScreenDisplay")); if (!osd) { continue; } osd->setProperty("showingProgress", false); osd->setProperty("osdValue", additionalText); osd->setProperty("icon", icon); QMetaObject::invokeMethod(osd, "show"); } } void UnlockApp::updateCanSuspend(bool set) { if (m_canSuspend == set) { return; } m_canSuspend = set; for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) { QQmlProperty sleepProperty((*it)->rootObject(), QStringLiteral("suspendToRamSupported")); sleepProperty.write(m_canSuspend); } } void UnlockApp::updateCanHibernate(bool set) { if (m_canHibernate == set) { return; } m_canHibernate = set; for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) { QQmlProperty hibernateProperty((*it)->rootObject(), QStringLiteral("suspendToDiskSupported")); hibernateProperty.write(m_canHibernate); } } } // namespace diff --git a/x11locker.cpp b/x11locker.cpp index b2d2ea4..6967a67 100644 --- a/x11locker.cpp +++ b/x11locker.cpp @@ -1,517 +1,539 @@ /******************************************************************** 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 != NULL ) { 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 = 0; 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); } -static void fakeFocusIn( WId window ) +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; foreach (WId window, 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->event); + 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; foreach( WId w, 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); - fakeFocusIn(window); stayOnTop(); } } } diff --git a/x11locker.h b/x11locker.h index 9a14699..d8e83d6 100644 --- a/x11locker.h +++ b/x11locker.h @@ -1,75 +1,77 @@ /******************************************************************** 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 . *********************************************************************/ #ifndef SCREENLOCKER_LOCKWINDOW_H #define SCREENLOCKER_LOCKWINDOW_H #include "abstractlocker.h" #include #include #include class QTimer; namespace ScreenLocker { class AbstractLocker; class X11Locker : public AbstractLocker, public QAbstractNativeEventFilter { Q_OBJECT public: X11Locker(QObject *parent = nullptr); virtual ~X11Locker(); void showLockWindow() override; void hideLockWindow() override; void addAllowedWindow(quint32 window) override; virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override; private Q_SLOTS: void updateGeo(); private: void initialize(); void saveVRoot(); void setVRoot(Window win, Window vr); void removeVRoot(Window win); int findWindowInfo(Window w); + void fakeFocusIn(WId window); void stayOnTop() override; struct WindowInfo { Window window; bool viewable; }; QList m_windowInfo; QList m_lockWindows; QList m_allowedWindows; + WId m_focusedLockWindow; }; } #endif // SCREENLOCKER_LOCKWINDOW_H