You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

424 lines
11 KiB

/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright 1999 Martin R. Jones <mjones@kde.org>
Copyright 2003 Oswald Buddenhagen <ossi@kde.org>
Copyright 2008 Chani Armitage <chanika@gmail.com>
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "ksldapp.h"
#include "interface.h"
#include "lockwindow.h"
#include "logind.h"
#include "kscreensaversettings.h"
#include <config-ksmserver.h>
// workspace
#include <kdisplaymanager.h>
// KDE
#include <KActionCollection>
#include <KAuthorized>
#include <KIdleTime>
#include <KLocalizedString>
// #include <KNotification>
#include <KGlobalAccel>
#include <KCrash>
// Qt
#include <QAction>
#include <QTimer>
#include <QProcess>
#include <QX11Info>
// X11
#include <X11/Xlib.h>
#include <xcb/xcb.h>
// other
#include <unistd.h>
#include <signal.h>
namespace ScreenLocker
{
static KSldApp * s_instance = 0;
KSldApp* KSldApp::self()
{
if (!s_instance) {
s_instance = new KSldApp();
}
return s_instance;
}
KSldApp::KSldApp(QObject * parent)
: QObject(parent)
, m_actionCollection(NULL)
, m_lockState(Unlocked)
, m_lockProcess(NULL)
, m_lockWindow(NULL)
, m_lockedTimer(QElapsedTimer())
, m_idleId(0)
, m_lockGrace(0)
, m_inGraceTime(false)
, m_graceTimer(new QTimer(this))
, m_inhibitCounter(0)
{
initialize();
}
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_actionCollection;
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;
void KSldApp::initialize()
{
KCrash::setFlags(KCrash::AutoRestart);
// 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);
// Global keys
m_actionCollection = new KActionCollection(this);
if (KAuthorized::authorize(QLatin1String("lock_screen"))) {
qDebug() << "Configuring Lock Action";
QAction *a = m_actionCollection->addAction(QLatin1String("Lock Session"));
a->setText(i18n("Lock Session"));
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << Qt::Key_ScreenSaver << Qt::ALT+Qt::CTRL+Qt::Key_L);
KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << Qt::Key_ScreenSaver << Qt::ALT+Qt::CTRL+Qt::Key_L);
connect(a, &QAction::triggered, this,
[this]() {
lock(EstablishLock::Immediate);
}
);
}
m_actionCollection->readSettings();
// idle support
auto idleTimeSignal = static_cast<void (KIdleTime:: *)(int)>(&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) {
// 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;
m_graceTimer->start(m_lockGrace);
} 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->setReadChannel(QProcess::StandardOutput);
auto finishedSignal = static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished);
connect(m_lockProcess, finishedSignal, this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
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
startLockProcess(EstablishLock::Immediate);
}
);
connect(m_lockProcess, &QProcess::readyReadStandardOutput, this,
[this]() {
m_lockState = Locked;
m_lockedTimer.restart();
emit locked();
}
);
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
LogindIntegration *logind = new LogindIntegration(this);
connect(logind, &LogindIntegration::requestLock, this,
[this]() {
lock(EstablishLock::Immediate);
}
);
connect(logind, &LogindIntegration::requestUnlock, this,
[this]() {
if (lockState() == Locked) {
s_logindExit = true;
m_lockProcess->kill();
}
}
);
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
if (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;
}
}
void KSldApp::lock(EstablishLock establishLock)
{
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;
}
qDebug() << "lock called";
if (!establishGrab()) {
qCritical() << "Could not establish screen lock";
return;
}
KDisplayManager().setLock(true);
// KNotification::event(QLatin1String( "locked" ));
// blank the screen
showLockWindow();
m_lockState = AcquiringLock;
// start unlock screen process
if (!startLockProcess(establishLock)) {
doUnlock();
qCritical() << "Greeter Process not available";
}
}
KActionCollection *KSldApp::actionCollection()
{
return m_actionCollection;
}
/*
* Forward declarations:
* Only called from KSldApp::establishGrab(). Using from somewhere else is incorrect usage!
**/
static bool grabKeyboard();
static bool grabMouse();
bool KSldApp::establishGrab()
{
XSync(QX11Info::display(), False);
if (!grabKeyboard()) {
sleep(1);
if (!grabKeyboard()) {
return false;
}
}
if (!grabMouse()) {
sleep(1);
if (!grabMouse()) {
XUngrabKeyboard(QX11Info::display(), CurrentTime);
return false;
}
}
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";
xcb_connection_t *c = QX11Info::connection();
xcb_ungrab_keyboard(c, XCB_CURRENT_TIME);
xcb_ungrab_pointer(c, XCB_CURRENT_TIME);
xcb_flush(c);
hideLockWindow();
// delete the window again, to get rid of event filter
delete m_lockWindow;
m_lockWindow = NULL;
m_lockState = Unlocked;
m_lockedTimer.invalidate();
endGraceTime();
KDisplayManager().setLock(false);
emit unlocked();
// KNotification::event( QLatin1String("unlocked"));
}
bool KSldApp::startLockProcess(EstablishLock establishLock)
{
QStringList args;
if (establishLock == EstablishLock::Immediate) {
args << "--immediateLock";
}
if (m_graceTimer->isActive()) {
args << "--graceTime";
args << QString::number(m_graceTimer->remainingTime());
}
if (m_lockGrace == -1) {
args << "--nolock";
}
m_lockProcess->start(QStringLiteral(KSCREENLOCKER_GREET_BIN), args);
// we wait one minute
if (!m_lockProcess->waitForStarted(60000)) {
m_lockProcess->kill();
return false;
}
return true;
}
void KSldApp::showLockWindow()
{
if (!m_lockWindow) {
m_lockWindow = new LockWindow();
connect(m_lockWindow, &LockWindow::userActivity, this,
[this]() {
if (isGraceTime()) {
unlock();
}
},
Qt::QueuedConnection
);
}
m_lockWindow->showLockWindow();
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;
}
} // namespace