1 //===========================================================================
3 // This file is part of the KDE project
5 // Copyright 1999 Martin R. Jones <mjones@kde.org>
6 // Copyright 2003 Oswald Buddenhagen <ossi@kde.org>
7 // Copyright 2008 Chani Armitage <chanika@gmail.com>
10 //krunner keeps running and checks user inactivity
11 //when it should show screensaver (and maybe lock the session),
12 //it starts kscreenlocker, who does all the locking and who
13 //actually starts the screensaver
15 //It's done this way to prevent screen unlocking when krunner
18 #include "lockprocess.h"
19 #include "lockprocessadaptor.h"
21 #include <config-workspace.h>
22 #include <config-X11.h>
23 #include <config-krunner-lock.h>
25 #include "autologout.h"
26 #include "kscreensaversettings.h"
28 #include <kdisplaymanager.h>
30 #include <KStandardDirs>
31 #include <KApplication>
32 #include <KServiceGroup>
34 #include <KMessageBox>
35 #include <KGlobalSettings>
38 #include <KPushButton>
39 #include <KStandardGuiItem>
40 #include <KAuthorized>
41 #include <KDesktopFile>
42 #include <kservicetypetrader.h>
43 #include <kmacroexpander.h>
46 #include <QtGui/QFrame>
52 #include <QSocketNotifier>
53 #include <QDesktopWidget>
55 #include <QTextStream>
57 #include <QDBusConnection>
58 #include <QDBusConnectionInterface>
59 #include <QDBusInterface>
67 #ifdef HAVE_SETPRIORITY
69 #include <sys/resource.h>
73 #include <X11/Xutil.h>
74 #include <X11/keysym.h>
75 #include <X11/Xatom.h>
83 #include <X11/extensions/dpms.h>
85 #ifndef HAVE_DPMSINFO_PROTO
86 Status
DPMSInfo ( Display
*, CARD16
*, BOOL
* );
92 #include <X11/extensions/xf86misc.h>
95 #ifdef HAVE_GLXCHOOSEVISUAL
99 #define LOCK_GRACE_DEFAULT 5000
100 #define AUTOLOGOUT_DEFAULT 600
102 static Window gVRoot
= 0;
103 static Window gVRootData
= 0;
104 static Atom gXA_VROOT
;
105 static Atom gXA_SCREENSAVER_VERSION
;
107 //#define CHECK_XSELECTINPUT
108 #ifdef CHECK_XSELECTINPUT
110 static bool check_xselectinput
= false;
112 int XSelectInput( Display
* dpy
, Window w
, long e
)
114 typedef int (*ptr
)(Display
*, Window
, long);
115 static ptr fun
= NULL
;
117 fun
= (ptr
)dlsym( RTLD_NEXT
, "XSelectInput" );
118 if( check_xselectinput
&& w
== DefaultRootWindow( dpy
))
119 kDebug() << kBacktrace();
120 return fun( dpy
, w
, e
);
125 //===========================================================================
127 // Screen saver handling process. Handles screensaver window,
128 // starting screensaver hacks, and password entry.f
130 LockProcess::LockProcess(bool child
, bool useBlankOnly
)
131 : QWidget(0L, Qt::X11BypassWindowManagerHint
),
136 mOpenGLVisual(false),
139 mUseBlankOnly(useBlankOnly
),
142 mRestoreXF86Lock(false),
144 mAutoLogoutTimerId(0)
146 setObjectName("save window");
149 new LockProcessAdaptor(this);
150 QDBusConnection::sessionBus().registerService("org.kde.screenlocker");
151 QDBusConnection::sessionBus().registerObject("/LockProcess", this);
153 kapp
->installX11EventFilter(this);
155 // Get root window size
156 XWindowAttributes rootAttr
;
158 XGetWindowAttributes(QX11Info::display(), RootWindow(QX11Info::display(),
159 info
.screen()), &rootAttr
);
160 kapp
->desktop(); // make Qt set its event mask on the root window first
161 XSelectInput( QX11Info::display(), QX11Info::appRootWindow(),
162 SubstructureNotifyMask
| rootAttr
.your_event_mask
);
163 #ifdef CHECK_XSELECTINPUT
164 check_xselectinput
= true;
166 setGeometry(0, 0, rootAttr
.width
, rootAttr
.height
);
168 // virtual root property
169 gXA_VROOT
= XInternAtom (QX11Info::display(), "__SWM_VROOT", False
);
170 gXA_SCREENSAVER_VERSION
= XInternAtom (QX11Info::display(), "_SCREENSAVER_VERSION", False
);
172 connect(&mHackProc
, SIGNAL(finished(int, QProcess::ExitStatus
)),
175 mSuspendTimer
.setSingleShot(true);
176 connect(&mSuspendTimer
, SIGNAL(timeout()), SLOT(suspend()));
179 QString::fromLatin1( ::getenv( "XDM_MANAGED" )).split(QChar(','), QString::SkipEmptyParts
);
180 for (QStringList::ConstIterator it
= dmopt
.constBegin(); it
!= dmopt
.constEnd(); ++it
)
181 if ((*it
).startsWith("method="))
182 mMethod
= (*it
).mid(7);
190 DPMSInfo(QX11Info::display(), &state
, &on
);
193 connect(&mCheckDPMS
, SIGNAL(timeout()), SLOT(checkDPMSActive()));
194 // we can save CPU if we stop it as quickly as possible
195 // but we waste CPU if we check too often -> so take 10s
196 mCheckDPMS
.start(10000);
201 greetPlugin
.library
= 0;
203 mSuppressUnlock
.setSingleShot(true);
204 connect(&mSuppressUnlock
, SIGNAL(timeout()), SLOT(deactivatePlasma()));
207 //---------------------------------------------------------------------------
209 // Destructor - usual cleanups.
211 LockProcess::~LockProcess()
213 if (greetPlugin
.library
) {
214 if (greetPlugin
.info
->done
)
215 greetPlugin
.info
->done();
216 greetPlugin
.library
->unload();
220 static int signal_pipe
[2];
222 static void sigterm_handler(int)
225 ::write( signal_pipe
[1], &tmp
, 1);
228 static void sighup_handler(int)
231 ::write( signal_pipe
[1], &tmp
, 1);
234 void LockProcess::timerEvent(QTimerEvent
*ev
)
236 if (ev
->timerId() == mAutoLogoutTimerId
)
238 killTimer(mAutoLogoutTimerId
);
239 AutoLogout
autologout(this);
240 execDialog(&autologout
);
244 void LockProcess::setupSignals()
246 struct sigaction act
;
248 act
.sa_handler
=SIG_IGN
;
249 sigemptyset(&(act
.sa_mask
));
250 sigaddset(&(act
.sa_mask
), SIGINT
);
252 sigaction(SIGINT
, &act
, 0L);
254 act
.sa_handler
=SIG_IGN
;
255 sigemptyset(&(act
.sa_mask
));
256 sigaddset(&(act
.sa_mask
), SIGQUIT
);
258 sigaction(SIGQUIT
, &act
, 0L);
259 // exit cleanly on SIGTERM
260 act
.sa_handler
= sigterm_handler
;
261 sigemptyset(&(act
.sa_mask
));
262 sigaddset(&(act
.sa_mask
), SIGTERM
);
264 sigaction(SIGTERM
, &act
, 0L);
265 // SIGHUP forces lock
266 act
.sa_handler
= sighup_handler
;
267 sigemptyset(&(act
.sa_mask
));
268 sigaddset(&(act
.sa_mask
), SIGHUP
);
270 sigaction(SIGHUP
, &act
, 0L);
273 QSocketNotifier
* notif
= new QSocketNotifier(signal_pipe
[0], QSocketNotifier::Read
, this);
274 connect( notif
, SIGNAL(activated(int)), SLOT(signalPipeSignal()));
278 void LockProcess::signalPipeSignal()
281 ::read( signal_pipe
[0], &tmp
, 1);
284 else if( tmp
== 'H' ) {
290 //---------------------------------------------------------------------------
291 bool LockProcess::lock()
294 // In case of a forced lock we don't react to events during
295 // the dead-time to give the screensaver some time to activate.
296 // That way we don't accidentally show the password dialog before
297 // the screensaver kicks in because the user moved the mouse after
298 // selecting "lock screen", that looks really untidy.
302 QTimer::singleShot(1000, this, SLOT(slotDeadTimePassed()));
310 //---------------------------------------------------------------------------
311 void LockProcess::slotDeadTimePassed()
316 //---------------------------------------------------------------------------
317 bool LockProcess::defaultSave()
322 QTimer::singleShot(mLockGrace
, this, SLOT(startLock()));
328 bool LockProcess::startSetup()
330 mPlasmaEnabled
= true; //force it on in case the user didn't click apply yet
334 //plasma startup will handle the suppressunlock bit
336 //---------------------------------------------------------------------------
337 bool LockProcess::dontLock()
343 //---------------------------------------------------------------------------
344 void LockProcess::quitSaver()
350 //---------------------------------------------------------------------------
352 // Read and apply configuration.
354 void LockProcess::configure()
356 // the configuration is stored in krunner's config file
357 if( KScreenSaverSettings::lock() ) {
358 mLockGrace
= KScreenSaverSettings::lockGrace();
361 else if (mLockGrace
> 300000)
362 mLockGrace
= 300000; // 5 minutes, keep the value sane
367 if ( KScreenSaverSettings::autoLogout() ) {
368 mAutoLogoutTimeout
= KScreenSaverSettings::autoLogoutTimeout();
369 mAutoLogoutTimerId
= startTimer(mAutoLogoutTimeout
* 1000); // in milliseconds
373 mDPMSDepend
= KScreenSaverSettings::suspendWhenInvisible();
376 mPriority
= KScreenSaverSettings::priority();
377 if (mPriority
< 0) mPriority
= 0;
378 if (mPriority
> 19) mPriority
= 19;
380 mSaver
= KScreenSaverSettings::saver();
381 if (mSaver
.isEmpty() || mUseBlankOnly
) {
382 mSaver
= "kblank.desktop";
387 mPlasmaEnabled
= KScreenSaverSettings::plasmaEnabled();
389 mSuppressUnlockTimeout
= qMax(0, KScreenSaverSettings::timeout() * 1000);
390 mSuppressUnlockTimeout
= qMax(mSuppressUnlockTimeout
, 30 * 1000); //min. 30 secs FIXME is this a good idea?
392 mPlugins
= KScreenSaverSettings::pluginsUnlock();
393 if (mPlugins
.isEmpty()) {
394 mPlugins
<< "classic" << "generic";
396 mPluginOptions
= KScreenSaverSettings::pluginOptions();
399 //---------------------------------------------------------------------------
401 // Read the command line needed to run the screensaver given a .desktop file.
403 void LockProcess::readSaver()
405 if (!mSaver
.isEmpty())
407 QString entryName
= mSaver
;
408 if( entryName
.endsWith( ".desktop" ))
409 entryName
= entryName
.left( entryName
.length() - 8 ); // strip it
410 KService::List offers
= KServiceTypeTrader::self()->query( "ScreenSaver",
411 "DesktopEntryName == '" + entryName
.toLower() + '\'' );
412 if( offers
.count() == 0 )
414 kDebug(1204) << "Cannot find screesaver: " << mSaver
;
417 QString file
= KStandardDirs::locate("services", offers
.first()->entryPath());
419 bool opengl
= KAuthorized::authorizeKAction("opengl_screensavers");
420 bool manipulatescreen
= KAuthorized::authorizeKAction("manipulatescreen_screensavers");
421 KDesktopFile
config( file
);
422 KConfigGroup desktopGroup
= config
.desktopGroup();
423 if (!desktopGroup
.readEntry("X-KDE-Type").toUtf8().isEmpty())
425 QString saverType
= desktopGroup
.readEntry("X-KDE-Type").toUtf8();
426 QStringList saverTypes
= saverType
.split( ";");
427 for (int i
= 0; i
< saverTypes
.count(); i
++)
429 if ((saverTypes
[i
] == "ManipulateScreen") && !manipulatescreen
)
431 kDebug(1204) << "Screensaver is type ManipulateScreen and ManipulateScreen is forbidden";
434 if ((saverTypes
[i
] == "OpenGL") && !opengl
)
436 kDebug(1204) << "Screensaver is type OpenGL and OpenGL is forbidden";
439 if (saverTypes
[i
] == "OpenGL")
441 mOpenGLVisual
= true;
446 kDebug(1204) << "mForbidden: " << (mForbidden
? "true" : "false");
448 if (config
.hasActionGroup("Root"))
450 mSaverExec
= config
.actionGroup("Root").readPathEntry("Exec", QString());
455 //---------------------------------------------------------------------------
457 // Create a window to draw our screen saver on.
459 void LockProcess::createSaverWindow()
461 Visual
* visual
= CopyFromParent
;
462 int depth
= CopyFromParent
;
463 XSetWindowAttributes attrs
;
464 int flags
= CWOverrideRedirect
;
465 #ifdef HAVE_GLXCHOOSEVISUAL
466 // this code is (partially) duplicated in kdebase/workspace/kcontrol/screensaver
469 static int attribs
[][ 15 ] =
471 #define R GLX_RED_SIZE
472 #define G GLX_GREEN_SIZE
473 #define B GLX_BLUE_SIZE
474 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, GLX_STENCIL_SIZE
, 1, None
},
475 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, GLX_DOUBLEBUFFER
, GLX_STENCIL_SIZE
, 1, None
},
476 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, None
},
477 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, GLX_DOUBLEBUFFER
, None
},
478 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, GLX_STENCIL_SIZE
, 1, None
},
479 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, GLX_STENCIL_SIZE
, 1, None
},
480 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, None
},
481 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, None
},
482 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, GLX_STENCIL_SIZE
, 1, None
},
483 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, None
},
484 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, GLX_STENCIL_SIZE
, 1, None
},
485 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, None
}
490 for( unsigned int i
= 0;
491 i
< sizeof( attribs
) / sizeof( attribs
[ 0 ] );
494 if( XVisualInfo
* info
= glXChooseVisual( x11Info().display(), x11Info().screen(), attribs
[ i
] ))
496 visual
= info
->visual
;
498 static Colormap colormap
= 0;
500 XFreeColormap( x11Info().display(), colormap
);
501 colormap
= XCreateColormap( x11Info().display(), RootWindow( x11Info().display(), x11Info().screen()), visual
, AllocNone
);
502 attrs
.colormap
= colormap
;
510 attrs
.override_redirect
= 1;
512 Window w
= XCreateWindow( x11Info().display(), RootWindow( x11Info().display(), x11Info().screen()),
513 x(), y(), width(), height(), 0, depth
, InputOutput
, visual
, flags
, &attrs
);
515 create( w
, false, true );
517 // Some xscreensaver hacks check for this property
518 const char *version
= "KDE 4.0";
519 XChangeProperty (QX11Info::display(), winId(),
520 gXA_SCREENSAVER_VERSION
, XA_STRING
, 8, PropModeReplace
,
521 (unsigned char *) version
, strlen(version
));
524 XSetWindowAttributes attr
;
525 attr
.event_mask
= KeyPressMask
| ButtonPressMask
| PointerMotionMask
|
526 VisibilityChangeMask
| ExposureMask
;
527 XChangeWindowAttributes(QX11Info::display(), winId(),
532 // set NoBackground so that the saver can capture the current
533 // screen state if necessary
534 setAttribute(Qt::WA_PaintOnScreen
, true);
535 setAttribute(Qt::WA_NoSystemBackground
, true);
536 setAttribute(Qt::WA_PaintOutsidePaintEvent
, true); // for bitBlt in resume()
538 setCursor( Qt::BlankCursor
);
540 kDebug(1204) << "Saver window Id: " << winId();
543 //---------------------------------------------------------------------------
545 // Hide the screensaver window
547 void LockProcess::hideSaverWindow()
551 removeVRoot(winId());
552 XDeleteProperty(QX11Info::display(), winId(), gXA_SCREENSAVER_VERSION
);
554 unsigned long vroot_data
[1] = { gVRootData
};
555 XChangeProperty(QX11Info::display(), gVRoot
, gXA_VROOT
, XA_WINDOW
, 32,
556 PropModeReplace
, (unsigned char *)vroot_data
, 1);
559 XSync(QX11Info::display(), False
);
562 //---------------------------------------------------------------------------
563 static int ignoreXError(Display
*, XErrorEvent
*)
568 //---------------------------------------------------------------------------
570 // Save the current virtual root window
572 void LockProcess::saveVRoot()
574 Window rootReturn
, parentReturn
, *children
;
575 unsigned int numChildren
;
577 Window root
= RootWindowOfScreen(ScreenOfDisplay(QX11Info::display(), info
.screen()));
582 int (*oldHandler
)(Display
*, XErrorEvent
*);
583 oldHandler
= XSetErrorHandler(ignoreXError
);
585 if (XQueryTree(QX11Info::display(), root
, &rootReturn
, &parentReturn
,
586 &children
, &numChildren
))
588 for (unsigned int i
= 0; i
< numChildren
; i
++)
592 unsigned long nitems
, bytesafter
;
593 unsigned char *newRoot
= 0;
595 if ((XGetWindowProperty(QX11Info::display(), children
[i
], gXA_VROOT
, 0, 1,
596 False
, XA_WINDOW
, &actual_type
, &actual_format
, &nitems
, &bytesafter
,
597 &newRoot
) == Success
) && newRoot
)
599 gVRoot
= children
[i
];
600 Window
*dummy
= (Window
*)newRoot
;
602 XFree ((char*) newRoot
);
608 XFree((char *)children
);
612 XSetErrorHandler(oldHandler
);
615 //---------------------------------------------------------------------------
617 // Set the virtual root property
619 void LockProcess::setVRoot(Window win
, Window vr
)
625 unsigned long rw
= RootWindowOfScreen(ScreenOfDisplay(QX11Info::display(), info
.screen()));
626 unsigned long vroot_data
[1] = { vr
};
628 Window rootReturn
, parentReturn
, *children
;
629 unsigned int numChildren
;
632 XQueryTree(QX11Info::display(), top
, &rootReturn
, &parentReturn
,
633 &children
, &numChildren
);
635 XFree((char *)children
);
636 if (parentReturn
== rw
) {
642 XChangeProperty(QX11Info::display(), top
, gXA_VROOT
, XA_WINDOW
, 32,
643 PropModeReplace
, (unsigned char *)vroot_data
, 1);
646 //---------------------------------------------------------------------------
648 // Remove the virtual root property
650 void LockProcess::removeVRoot(Window win
)
652 XDeleteProperty (QX11Info::display(), win
, gXA_VROOT
);
655 //---------------------------------------------------------------------------
657 // Grab the keyboard. Returns true on success
659 bool LockProcess::grabKeyboard()
661 int rv
= XGrabKeyboard( QX11Info::display(), QApplication::desktop()->winId(),
662 True
, GrabModeAsync
, GrabModeAsync
, CurrentTime
);
664 return (rv
== GrabSuccess
);
667 #define GRABEVENTS ButtonPressMask | ButtonReleaseMask | PointerMotionMask | \
668 EnterWindowMask | LeaveWindowMask
670 //---------------------------------------------------------------------------
672 // Grab the mouse. Returns true on success
674 bool LockProcess::grabMouse()
676 int rv
= XGrabPointer( QX11Info::display(), QApplication::desktop()->winId(),
677 True
, GRABEVENTS
, GrabModeAsync
, GrabModeAsync
, None
,
678 QCursor(Qt::BlankCursor
).handle(), CurrentTime
);
680 return (rv
== GrabSuccess
);
683 //---------------------------------------------------------------------------
685 // Grab keyboard and mouse. Returns true on success.
687 bool LockProcess::grabInput()
689 XSync(QX11Info::display(), False
);
705 XUngrabKeyboard(QX11Info::display(), CurrentTime
);
715 //---------------------------------------------------------------------------
717 // Release mouse an keyboard grab.
719 void LockProcess::ungrabInput()
721 XUngrabKeyboard(QX11Info::display(), CurrentTime
);
722 XUngrabPointer(QX11Info::display(), CurrentTime
);
726 //---------------------------------------------------------------------------
728 // Start the screen saver.
730 bool LockProcess::startSaver()
732 if (!child_saver
&& !grabInput())
734 kWarning(1204) << "LockProcess::startSaver() grabInput() failed!!!!" ;
742 QSocketNotifier
*notifier
= new QSocketNotifier(mParent
, QSocketNotifier::Read
, this);
743 connect(notifier
, SIGNAL( activated (int)), SLOT( quitSaver()));
748 setCursor( Qt::BlankCursor
);
751 XSync(QX11Info::display(), False
);
753 setVRoot( winId(), winId() );
759 //---------------------------------------------------------------------------
761 // Stop the screen saver.
763 void LockProcess::stopSaver()
765 kDebug(1204) << "LockProcess: stopping saver";
773 KDisplayManager().setLock( false );
775 const char *out
= "GOAWAY!";
776 for (QList
<int>::ConstIterator it
= child_sockets
.constBegin(); it
!= child_sockets
.constEnd(); ++it
)
777 write(*it
, out
, sizeof(out
));
782 QVariant
LockProcess::getConf(void *ctx
, const char *key
, const QVariant
&dflt
)
784 LockProcess
*that
= (LockProcess
*)ctx
;
785 QString fkey
= QLatin1String( key
) + '=';
786 for (QStringList::ConstIterator it
= that
->mPluginOptions
.constBegin();
787 it
!= that
->mPluginOptions
.constEnd(); ++it
)
788 if ((*it
).startsWith( fkey
))
789 return (*it
).mid( fkey
.length() );
793 void LockProcess::cantLock( const QString
&txt
)
795 msgBox( 0, QMessageBox::Critical
, i18n("Will not lock the session, as unlocking would be impossible:\n") + txt
);
798 #if 0 // placeholders for later
799 i18n("Cannot start <i>kcheckpass</i>.");
800 i18n("<i>kcheckpass</i> is unable to operate. Possibly it is not setuid root.");
803 //---------------------------------------------------------------------------
805 // Make the screen saver password protected.
807 bool LockProcess::startLock()
809 if (loadGreetPlugin()) {
811 KDisplayManager().setLock(true);
818 bool LockProcess::loadGreetPlugin()
820 if (greetPlugin
.library
) {
821 //we were locked once before, so all the plugin loading's done already
822 //FIXME should I be unloading the plugin on unlock instead?
825 for (QStringList::ConstIterator it
= mPlugins
.constBegin(); it
!= mPlugins
.constEnd(); ++it
) {
826 GreeterPluginHandle plugin
;
827 KLibrary
*lib
= new KLibrary( (*it
)[0] == '/' ? *it
: "kgreet_" + *it
);
828 if (lib
->fileName().isEmpty()) {
829 kWarning(1204) << "GreeterPlugin " << *it
<< " does not exist" ;
834 kWarning(1204) << "Cannot load GreeterPlugin " << *it
<< " (" << lib
->fileName() << ")" ;
838 plugin
.library
= lib
;
839 plugin
.info
= (KGreeterPluginInfo
*)lib
->resolveSymbol( "kgreeterplugin_info" );
841 kWarning(1204) << "GreeterPlugin " << *it
<< " (" << lib
->fileName() << ") is no valid greet widget plugin" ;
846 if (plugin
.info
->method
&& !mMethod
.isEmpty() && mMethod
!= plugin
.info
->method
) {
847 kDebug(1204) << "GreeterPlugin " << *it
<< " (" << lib
->fileName() << ") serves " << plugin
.info
->method
<< ", not " << mMethod
;
852 if (!plugin
.info
->init( mMethod
, getConf
, this )) {
853 kDebug(1204) << "GreeterPlugin " << *it
<< " (" << lib
->fileName() << ") refuses to serve " << mMethod
;
858 kDebug(1204) << "GreeterPlugin " << *it
<< " (" << plugin
.info
->method
<< ", " << plugin
.info
->name
<< ") loaded";
859 greetPlugin
= plugin
;
862 cantLock( i18n("No appropriate greeter plugin configured.") );
866 //---------------------------------------------------------------------------
870 bool LockProcess::startHack()
872 kDebug(1204) << "Starting hack:" << mSaverExec
;
874 if (mSaverExec
.isEmpty() || mForbidden
)
880 QHash
<QChar
, QString
> keyMap
;
881 keyMap
.insert('w', QString::number(winId()));
882 mHackProc
<< KShell::splitArgs(KMacroExpander::expandMacrosShellQuote(mSaverExec
, keyMap
));
885 if (mHackProc
.waitForStarted())
887 #ifdef HAVE_SETPRIORITY
888 setpriority(PRIO_PROCESS
, mHackProc
.pid(), mPriority
);
897 //---------------------------------------------------------------------------
899 void LockProcess::stopHack()
901 if (mHackProc
.state() != QProcess::NotRunning
)
903 mHackProc
.terminate();
904 if (!mHackProc
.waitForFinished(10000))
911 //---------------------------------------------------------------------------
913 void LockProcess::hackExited()
915 // Hack exited while we're supposed to be saving the screen.
916 // Make sure the saver window is black.
917 XSetWindowBackground(QX11Info::display(), winId(), 0);
918 XClearWindow(QX11Info::display(), winId());
921 bool LockProcess::startPlasma()
923 if (!mPlasmaEnabled
) {
928 mSuppressUnlock
.start(mSuppressUnlockTimeout
);
929 XChangeActivePointerGrab(QX11Info::display(), GRABEVENTS
,
930 QCursor(Qt::ArrowCursor
).handle(), CurrentTime
);
933 kDebug() << "looking for plasma-overlay";
935 //try to get it, in case it's already running somehow
936 //FIXME I don't like hardcoded strings
937 //mPlasmaDBus = new QDBusInterface("org.kde.plasma-overlay", "/MainApplication", QString(),
938 mPlasmaDBus
= new org::kde::plasmaoverlay::App("org.kde.plasma-overlay", "/App",
939 QDBusConnection::sessionBus(), this);
940 //FIXME this might-already-be-running stuff seems really really Wrong.
943 if (mPlasmaDBus
->isValid()) {
944 kDebug() << "weird, plasma-overlay is already running";
945 mPlasmaDBus
->call(QDBus::NoBlock
, "setup", mSetupMode
);
949 kDebug () << "...not found" << "starting plasma-overlay";
953 connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString
, QString
, QString
)),
954 this, SLOT(newService(QString
, QString
, QString
)));
956 KProcess
*plasmaProc
= new KProcess
;
957 plasmaProc
->setProgram("plasma-overlay");
959 *plasmaProc
<< "--setup";
962 //make sure it goes away when it's done (and not before)
963 connect(plasmaProc
, SIGNAL(finished(int,QProcess::ExitStatus
)), plasmaProc
, SLOT(deleteLater()));
966 kDebug() << "process begun";
968 //plasma gets 15 seconds to load, or we assume it failed
969 QTimer::singleShot(15 * 1000, this, SLOT(checkPlasma()));
973 void LockProcess::checkPlasma()
975 if (!mPlasmaEnabled
) {
976 kDebug() << "You're Doing It Wrong!";
979 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
980 //hooray, looks like it started ok
981 kDebug() << "success!";
982 //...but just in case, make sure we're not waiting on it
987 kDebug() << "ohnoes. plasma = teh fail.";
991 bool LockProcess::isPlasmaValid()
993 //FIXME I'm assuming that if it's valid, calls will succeed. so if that's not the case we'll
994 //need to change things so that plasma's disabled properly if it fails
995 //damn. isValid is not quite enough. a call may still fail, and then we need to bail.
996 if (!(mPlasmaEnabled
&& mPlasmaDBus
)) {
997 return false; //no plasma, at least not yet
999 if (mPlasmaDBus
->isValid()) {
1002 //oh crap, it ran away on us.
1007 void LockProcess::disablePlasma()
1010 mPlasmaEnabled
= false;
1012 mSuppressUnlock
.stop(); //FIXME we might need to start the lock timer ala deactivatePlasma()
1013 //actually we could be lazy and just call deactivatePlasma() TODO check that this'll really work
1018 void LockProcess::stopPlasma()
1020 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
1021 mPlasmaDBus
->call(QDBus::NoBlock
, "quit");
1023 kDebug() << "cannot stop plasma-overlay";
1027 void LockProcess::newService(QString name
, QString oldOwner
, QString newOwner
)
1030 if (name
!= "org.kde.plasma-overlay") {
1035 if (newOwner
.isEmpty()) {
1036 kDebug() << "plasma ran away?";
1039 kDebug() << "I'm confused!!";
1044 kDebug() << "plasma! yaay!";
1045 mPlasmaDBus
= new org::kde::plasmaoverlay::App(name
, "/App",
1046 QDBusConnection::sessionBus(), this);
1048 //XXX this isn't actually used any more iirc
1049 connect(mPlasmaDBus
, SIGNAL(hidden()), SLOT(unSuppressUnlock()));
1051 if (!mDialogs
.isEmpty()) {
1052 //whoops, activation probably failed earlier
1053 mPlasmaDBus
->call(QDBus::NoBlock
, "setActive", true);
1057 void LockProcess::deactivatePlasma()
1059 if (isPlasmaValid()) {
1060 mPlasmaDBus
->call(QDBus::NoBlock
, "setActive", false);
1062 if (!mLocked
&& mLockGrace
>=0) {
1063 QTimer::singleShot(mLockGrace
, this, SLOT(startLock())); //this is only ok because any activity will quit
1067 void LockProcess::lockPlasma()
1069 if (isPlasmaValid()) {
1070 mPlasmaDBus
->call(QDBus::NoBlock
, "lock");
1074 void LockProcess::unSuppressUnlock()
1076 //note: suppressing unlock also now means suppressing quit-on-activity
1077 //maybe some var renaming is in order.
1078 mSuppressUnlock
.stop();
1081 void LockProcess::quit()
1083 mSuppressUnlock
.stop();
1084 if (!mLocked
|| checkPass()) {
1089 void LockProcess::suspend()
1091 if( !mSuspended
&& mHackProc
.state() == QProcess::Running
)
1093 ::kill(mHackProc
.pid(), SIGSTOP
);
1094 QApplication::syncX();
1095 mSavedScreen
= QPixmap::grabWindow( winId());
1100 void LockProcess::resume( bool force
)
1102 if( !force
&& (!mDialogs
.isEmpty() || !mVisibility
))
1103 return; // no resuming with dialog visible or when not visible
1104 if( mSuspended
&& mHackProc
.state() == QProcess::Running
)
1106 XForceScreenSaver(QX11Info::display(), ScreenSaverReset
);
1108 p
.drawPixmap( 0, 0, mSavedScreen
);
1110 mSavedScreen
= QPixmap();
1111 QApplication::syncX();
1112 ::kill(mHackProc
.pid(), SIGCONT
);
1117 //---------------------------------------------------------------------------
1119 // Show the password dialog
1120 // This is called only in the master process
1122 bool LockProcess::checkPass()
1124 killTimer(mAutoLogoutTimerId
);
1126 if (isPlasmaValid()) {
1127 mPlasmaDBus
->call(QDBus::NoBlock
, "setActive", true);
1130 PasswordDlg
passDlg( this, &greetPlugin
);
1131 int ret
= execDialog( &passDlg
);
1133 if (isPlasmaValid()) {
1134 if (ret
== QDialog::Rejected
) {
1135 mSuppressUnlock
.start(mSuppressUnlockTimeout
);
1136 } else if (ret
== TIMEOUT_CODE
) {
1137 mPlasmaDBus
->call(QDBus::NoBlock
, "setActive", false);
1141 XWindowAttributes rootAttr
;
1142 XGetWindowAttributes(QX11Info::display(), QX11Info::appRootWindow(), &rootAttr
);
1143 if(( rootAttr
.your_event_mask
& SubstructureNotifyMask
) == 0 )
1145 kWarning() << "ERROR: Something removed SubstructureNotifyMask from the root window!!!" ;
1146 XSelectInput( QX11Info::display(), QX11Info::appRootWindow(),
1147 SubstructureNotifyMask
| rootAttr
.your_event_mask
);
1150 return ret
== QDialog::Accepted
;
1153 bool LockProcess::checkPass(const QString
&reason
)
1156 //we were never locked... how can we unlock?!
1157 //if anyone finds a use case for checking the password while unlocked, they'll have to load
1158 //the greetplugin n'stuff
1161 PasswordDlg
passDlg(this, &greetPlugin
, reason
);
1162 int ret
= execDialog( &passDlg
);
1165 //FIXME do we need to copy&paste that SubstructureNotifyMask code above?
1166 if (ret
== QDialog::Accepted
) {
1167 //we don't quit on a custom checkpass, but we do unlock
1168 //so that the user doesn't have to type their password twice
1170 KDisplayManager().setLock(false);
1171 //FIXME while suppressUnlock *should* always be running, if it isn't
1172 //(say if someone's doing things they shouldn't with dbus) then it won't get started by this
1173 //which means that a successful unlock will never re-lock
1174 //in fact, the next bit of activity would lead to the screensaver quitting.
1175 //possible solutions:
1176 //-treat this function like activity: quit if already unlocked, ensure suppress is started
1177 //if we're locked and the dialog's rejected
1178 //-return true if already unlocked, without doing anything, same as above if locked
1179 //-let it quit, and tell people not to do such silly things :P
1185 static void fakeFocusIn( WId window
)
1187 // We have keyboard grab, so this application will
1188 // get keyboard events even without having focus.
1189 // Fake FocusIn to make Qt realize it has the active
1190 // window, so that it will correctly show cursor in the dialog.
1192 memset(&ev
, 0, sizeof(ev
));
1193 ev
.xfocus
.display
= QX11Info::display();
1194 ev
.xfocus
.type
= FocusIn
;
1195 ev
.xfocus
.window
= window
;
1196 ev
.xfocus
.mode
= NotifyNormal
;
1197 ev
.xfocus
.detail
= NotifyAncestor
;
1198 XSendEvent( QX11Info::display(), window
, False
, NoEventMask
, &ev
);
1201 bool LockProcess::eventFilter(QObject
*o
, QEvent
*e
)
1203 if (e
->type() == QEvent::Resize
) {
1204 QWidget
*w
= static_cast<QWidget
*>(o
);
1205 mFrames
.value(w
)->resize(w
->size());
1210 int LockProcess::execDialog( QDialog
*dlg
)
1212 QFrame
*winFrame
= new QFrame( dlg
);
1213 winFrame
->setFrameStyle( QFrame::WinPanel
| QFrame::Raised
);
1214 winFrame
->setLineWidth( 2 );
1216 mFrames
.insert(dlg
, winFrame
);
1217 dlg
->installEventFilter(this);
1221 QRect rect
= dlg
->geometry();
1222 rect
.moveCenter(KGlobalSettings::desktopGeometry(QCursor::pos()).center());
1223 dlg
->move( rect
.topLeft() );
1225 if (mDialogs
.isEmpty())
1228 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
1229 QCursor(Qt::ArrowCursor
).handle(), CurrentTime
);
1231 mDialogs
.prepend( dlg
);
1232 fakeFocusIn( dlg
->winId());
1233 int rt
= dlg
->exec();
1234 int pos
= mDialogs
.indexOf( dlg
);
1236 mDialogs
.remove( pos
);
1237 if( mDialogs
.isEmpty() ) {
1242 dlg
->removeEventFilter(this);
1243 mFrames
.remove(dlg
);
1248 void LockProcess::preparePopup()
1250 QWidget
*dlg
= (QWidget
*)sender();
1251 mDialogs
.prepend( dlg
);
1252 fakeFocusIn( dlg
->winId() );
1255 void LockProcess::cleanupPopup()
1257 QWidget
*dlg
= (QWidget
*)sender();
1259 int pos
= mDialogs
.indexOf( dlg
);
1260 mDialogs
.remove( pos
);
1264 void LockProcess::updateFocus()
1266 if (mDialogs
.isEmpty()) {
1267 if (mForeignInputWindows
.isEmpty()) {
1268 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
1269 QCursor(Qt::BlankCursor
).handle(), CurrentTime
);
1271 fakeFocusIn(mForeignInputWindows
.first());
1274 fakeFocusIn(mDialogs
.first()->winId());
1278 //---------------------------------------------------------------------------
1282 bool LockProcess::x11Event(XEvent
*event
)
1285 switch (event
->type
)
1288 if (!mDialogs
.isEmpty() && event
->xbutton
.window
== event
->xbutton
.root
) {
1289 //kDebug() << "close" << mDialogs.first()->effectiveWinId();
1290 KDialog
*dlg
= qobject_cast
<KDialog
*>(mDialogs
.first());
1292 //kDebug() << "casting success";
1299 if (mBusy
|| !mDialogs
.isEmpty()) {
1300 //kDebug() << "busy";
1301 //FIXME shouldn't we be resetting some timers?
1305 //something happened. do we quit, ask for a password or forward it to plasma?
1306 //if we're supposed to be forwarding, we check that there's actually a plasma window up
1307 //so that the user isn't trapped if plasma crashes or is slow to load.
1308 //however, if plasma started in setup mode, we don't want to let anything happen until
1309 //it has a chance to load.
1310 //note: mSetupMode should end when we either get a winid or hit the checkPlasma timeout
1311 if (mSuppressUnlock
.isActive() && (mSetupMode
|| !mForeignInputWindows
.isEmpty())) {
1312 mSuppressUnlock
.start(); //help, help, I'm being suppressed!
1313 } else if (!mLocked
|| checkPass()) {
1316 return true; //it's better not to forward any input while quitting, right?
1318 if (mAutoLogoutTimerId
) // we need to restart the auto logout countdown
1320 killTimer(mAutoLogoutTimerId
);
1321 mAutoLogoutTimerId
= startTimer(mAutoLogoutTimeout
* 1000);
1327 case VisibilityNotify
:
1328 if( event
->xvisibility
.window
== winId())
1329 { // mVisibility == false means the screensaver is not visible at all
1330 // e.g. when switched to text console
1331 // ...or when plasma's over it non-compositely?
1332 // hey, this gives me free "suspend saver when plasma obscures it"
1333 mVisibility
= !(event
->xvisibility
.state
== VisibilityFullyObscured
);
1335 mSuspendTimer
.start(2000);
1336 kDebug() << "fully obscured";
1338 kDebug() << "not fully obscured";
1339 mSuspendTimer
.stop();
1342 if (mForeignWindows
.isEmpty() && event
->xvisibility
.state
!= VisibilityUnobscured
) {
1343 kDebug() << "no plasma; saver obscured";
1346 } else if (!mForeignWindows
.isEmpty() && event
->xvisibility
.window
== mForeignWindows
.last() &&
1347 event
->xvisibility
.state
!= VisibilityUnobscured
) {
1348 //FIXME now that we have several plasma winids this doesn't feel valid
1349 //but I don't know what to do about it!
1350 kDebug() << "plasma obscured!";
1355 case ConfigureNotify
: // from SubstructureNotifyMask on the root window
1356 if(event
->xconfigure
.event
== QX11Info::appRootWindow()) {
1357 //kDebug() << "ConfigureNotify:";
1358 //the stacking order changed, so let's change the stacking order!
1362 case MapNotify
: // from SubstructureNotifyMask on the root window
1363 if( event
->xmap
.event
== QX11Info::appRootWindow()) {
1364 kDebug() << "MapNotify:" << event
->xmap
.window
;
1365 WindowType type
= windowType(event
->xmap
.window
);
1366 if (type
!= IgnoreWindow
) {
1367 if (mForeignWindows
.contains(event
->xmap
.window
)) {
1368 kDebug() << "uhoh! duplicate!";
1370 //ordered youngest-on-top
1371 mForeignWindows
.prepend(event
->xmap
.window
);
1373 if (type
& InputWindow
) {
1374 kDebug() << "input window";
1375 if (mForeignInputWindows
.contains(event
->xmap
.window
)) {
1376 kDebug() << "uhoh! duplicate again"; //never happens
1378 //ordered youngest-on-top
1379 mForeignInputWindows
.prepend(event
->xmap
.window
);
1380 fakeFocusIn(event
->xmap
.window
);
1382 mSetupMode
= false; //no more waiting for plasma
1389 if (event
->xmap
.event
== QX11Info::appRootWindow()) {
1390 kDebug() << "UnmapNotify:" << event
->xunmap
.window
;
1391 mForeignWindows
.removeAll(event
->xunmap
.window
);
1392 if (mForeignInputWindows
.removeAll(event
->xunmap
.window
)) {
1398 // We have grab with the grab window being the root window.
1399 // This results in key events being sent to the root window,
1400 // but they should be sent to the dialog if it's visible.
1401 // It could be solved by setFocus() call, but that would mess
1402 // the focus after this process exits.
1403 // Qt seems to be quite hard to persuade to redirect the event,
1404 // so let's simply dupe it with correct destination window,
1405 // and ignore the original one.
1406 if (!mDialogs
.isEmpty()) {
1407 if ((event
->type
== KeyPress
|| event
->type
== KeyRelease
) &&
1408 event
->xkey
.window
!= mDialogs
.first()->winId()) {
1409 //kDebug() << "forward to dialog";
1410 XEvent ev2
= *event
;
1411 ev2
.xkey
.window
= ev2
.xkey
.subwindow
= mDialogs
.first()->winId();
1412 qApp
->x11ProcessEvent( &ev2
);
1415 } else if (!mForeignInputWindows
.isEmpty()) {
1416 //when there are no dialogs, forward some events to plasma
1417 switch (event
->type
) {
1424 //kDebug() << "forward to plasma";
1425 XEvent ev2
= *event
;
1426 ev2
.xkey
.window
= ev2
.xkey
.subwindow
= mForeignInputWindows
.first();
1427 XSendEvent(QX11Info::display(), ev2
.xkey
.window
, False
, NoEventMask
, &ev2
);
1438 LockProcess::WindowType
LockProcess::windowType(WId id
)
1440 Atom tag
= XInternAtom(QX11Info::display(), "_KDE_SCREENSAVER_OVERRIDE", False
);
1443 unsigned long nitems
, remaining
;
1444 unsigned char *data
= 0;
1445 Display
*display
= QX11Info::display();
1447 int result
= XGetWindowProperty(display
, id
, tag
, 0, 1, False
, tag
, &actualType
,
1448 &actualFormat
, &nitems
, &remaining
, &data
);
1450 //kDebug() << (result == Success) << (actualType == tag);
1451 WindowType type
= IgnoreWindow
;
1452 if (result
== Success
&& actualType
== tag
) {
1453 if (nitems
!= 1 || actualFormat
!= 8) {
1454 kDebug() << "malformed property";
1456 kDebug() << "i can haz plasma window?" << data
[0];
1458 case 0: //FIXME magic numbers
1459 type
= SimpleWindow
;
1465 type
= DefaultWindow
;
1474 /* if (result != Success) {
1477 if (actualType == tag) {
1480 //managed windows will have a pesky frame we have to bypass
1481 XWindowAttributes attr;
1482 XGetWindowAttributes(display, id, &attr);
1483 if (!attr.override_redirect) {
1484 //check the real client window
1485 if (Window client = XmuClientWindow(display, id)) {
1486 result = XGetWindowProperty(display, client, tag, 0, 0, False, tag, &actualType,
1487 &actualFormat, &nitems, &remaining, &data);
1488 kDebug() << (result == Success) << (actualType == tag);
1492 return (result == Success) && (actualType == tag);
1498 void LockProcess::stayOnTop()
1500 if(!(mDialogs
.isEmpty() && mForeignWindows
.isEmpty()))
1502 // this restacking is written in a way so that
1503 // if the stacking positions actually don't change,
1504 // all restacking operations will be no-op,
1505 // and no ConfigureNotify will be generated,
1506 // thus avoiding possible infinite loops
1507 Window
* stack
= new Window
[ mDialogs
.count() + mForeignWindows
.count() + 1 ];
1509 if (!mDialogs
.isEmpty()) {
1510 XRaiseWindow( QX11Info::display(), mDialogs
.first()->winId()); // raise topmost
1511 // and stack others below it
1512 for( QVector
< QWidget
* >::ConstIterator it
= mDialogs
.constBegin();
1513 it
!= mDialogs
.constEnd();
1515 stack
[ count
++ ] = (*it
)->winId();
1517 XRaiseWindow( QX11Info::display(), mForeignWindows
.first()); // raise topmost
1519 //now the plasma stuff below the dialogs
1520 foreach (const WId w
, mForeignWindows
) {
1523 //finally, the saver window
1524 stack
[ count
++ ] = winId();
1525 XRestackWindows( x11Info().display(), stack
, count
);
1526 //kDebug() << "restacked" << count;
1529 XRaiseWindow(QX11Info::display(), winId());
1533 void LockProcess::checkDPMSActive()
1538 DPMSInfo(QX11Info::display(), &state
, &on
);
1539 //kDebug() << "checkDPMSActive " << on << " " << state;
1540 if (state
== DPMSModeStandby
|| state
== DPMSModeSuspend
|| state
== DPMSModeOff
)
1543 } else if ( mSuspended
)
1550 #if defined(HAVE_XF86MISC) && defined(HAVE_XF86MISCSETGRABKEYSSTATE)
1551 // see http://cvsweb.xfree86.org/cvsweb/xc/programs/Xserver/hw/xfree86/common/xf86Events.c#rev3.113
1552 // This allows enabling the "Allow{Deactivate/Closedown}Grabs" options in XF86Config,
1553 // and kscreenlocker will still lock the session.
1554 static enum { Unknown
, Yes
, No
} can_do_xf86_lock
= Unknown
;
1555 void LockProcess::lockXF86()
1557 if( can_do_xf86_lock
== Unknown
)
1560 if( XF86MiscQueryVersion( QX11Info::display(), &major
, &minor
)
1561 && (major
> 0 || minor
>= 5) )
1562 can_do_xf86_lock
= Yes
;
1564 can_do_xf86_lock
= No
;
1566 if( can_do_xf86_lock
!= Yes
)
1568 if( mRestoreXF86Lock
)
1570 if( XF86MiscSetGrabKeysState( QX11Info::display(), False
) != MiscExtGrabStateSuccess
)
1573 mRestoreXF86Lock
= true;
1576 void LockProcess::unlockXF86()
1578 if( can_do_xf86_lock
!= Yes
)
1580 if( !mRestoreXF86Lock
)
1582 XF86MiscSetGrabKeysState( QX11Info::display(), True
);
1583 mRestoreXF86Lock
= false;
1586 void LockProcess::lockXF86()
1590 void LockProcess::unlockXF86()
1595 void LockProcess::msgBox( QWidget
*parent
, QMessageBox::Icon type
, const QString
&txt
)
1597 QDialog
box( parent
, Qt::X11BypassWindowManagerHint
);
1598 box
.setModal( true );
1600 QLabel
*label1
= new QLabel( &box
);
1601 label1
->setPixmap( QMessageBox::standardIcon( type
) );
1602 QLabel
*label2
= new QLabel( txt
, &box
);
1603 KPushButton
*button
= new KPushButton( KStandardGuiItem::ok(), &box
);
1604 button
->setDefault( true );
1605 button
->setSizePolicy( QSizePolicy( QSizePolicy::Preferred
, QSizePolicy::Preferred
) );
1606 connect( button
, SIGNAL( clicked() ), &box
, SLOT( accept() ) );
1608 QGridLayout
*grid
= new QGridLayout( &box
);
1609 grid
->setSpacing( 10 );
1610 grid
->addWidget( label1
, 0, 0, Qt::AlignCenter
);
1611 grid
->addWidget( label2
, 0, 1, Qt::AlignCenter
);
1612 grid
->addWidget( button
, 1, 0, 1, 2, Qt::AlignCenter
);
1617 #include "lockprocess.moc"