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 krunner_lock, 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
);
124 const int TIMEOUT_CODE
= 2; //from PasswordDlg
126 //===========================================================================
128 // Screen saver handling process. Handles screensaver window,
129 // starting screensaver hacks, and password entry.f
131 LockProcess::LockProcess(bool child
, bool useBlankOnly
)
132 : QWidget(0L, Qt::X11BypassWindowManagerHint
),
136 mOpenGLVisual(false),
139 mUseBlankOnly(useBlankOnly
),
142 mRestoreXF86Lock(false),
146 setObjectName("save window");
149 new LockProcessAdaptor(this);
150 QDBusConnection::sessionBus().registerService("org.kde.krunner_lock");
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
.begin(); it
!= dmopt
.end(); ++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 if (!mPlasmaEnabled
) {
331 return defaultSave();
337 //plasma startup will handle the suppressunlock bit
339 //---------------------------------------------------------------------------
340 bool LockProcess::dontLock()
346 //---------------------------------------------------------------------------
347 void LockProcess::quitSaver()
353 //---------------------------------------------------------------------------
355 // Read and apply configuration.
357 void LockProcess::configure()
359 // the configuration is stored in krunner's config file
360 if( KScreenSaverSettings::lock() ) {
361 mLockGrace
= KScreenSaverSettings::lockGrace();
364 else if (mLockGrace
> 300000)
365 mLockGrace
= 300000; // 5 minutes, keep the value sane
370 if ( KScreenSaverSettings::autoLogout() ) {
372 mAutoLogoutTimeout
= KScreenSaverSettings::autoLogoutTimeout();
373 mAutoLogoutTimerId
= startTimer(mAutoLogoutTimeout
* 1000); // in milliseconds
377 mDPMSDepend
= KScreenSaverSettings::suspendWhenInvisible();
380 mPriority
= KScreenSaverSettings::priority();
381 if (mPriority
< 0) mPriority
= 0;
382 if (mPriority
> 19) mPriority
= 19;
384 mSaver
= KScreenSaverSettings::saver();
385 if (mSaver
.isEmpty() || mUseBlankOnly
) {
386 mSaver
= "kblank.desktop";
391 mPlasmaEnabled
= KScreenSaverSettings::plasmaEnabled();
393 mSuppressUnlockTimeout
= qMax(0, KScreenSaverSettings::timeout() * 1000);
394 mSuppressUnlockTimeout
= qMax(mSuppressUnlockTimeout
, 30 * 1000); //min. 30 secs FIXME is this a good idea?
396 mPlugins
= KScreenSaverSettings::pluginsUnlock();
397 if (mPlugins
.isEmpty()) {
398 mPlugins
<< "classic" << "generic";
400 mPluginOptions
= KScreenSaverSettings::pluginOptions();
403 //---------------------------------------------------------------------------
405 // Read the command line needed to run the screensaver given a .desktop file.
407 void LockProcess::readSaver()
409 if (!mSaver
.isEmpty())
411 QString entryName
= mSaver
;
412 if( entryName
.endsWith( ".desktop" ))
413 entryName
= entryName
.left( entryName
.length() - 8 ); // strip it
414 KService::List offers
= KServiceTypeTrader::self()->query( "ScreenSaver",
415 "DesktopEntryName == '" + entryName
.toLower() + '\'' );
416 if( offers
.count() == 0 )
418 kDebug(1204) << "Cannot find screesaver: " << mSaver
;
421 QString file
= KStandardDirs::locate("services", offers
.first()->entryPath());
423 bool opengl
= KAuthorized::authorizeKAction("opengl_screensavers");
424 bool manipulatescreen
= KAuthorized::authorizeKAction("manipulatescreen_screensavers");
425 KDesktopFile
config( file
);
426 KConfigGroup desktopGroup
= config
.desktopGroup();
427 if (!desktopGroup
.readEntry("X-KDE-Type").toUtf8().isEmpty())
429 QString saverType
= desktopGroup
.readEntry("X-KDE-Type").toUtf8();
430 QStringList saverTypes
= saverType
.split( ";");
431 for (int i
= 0; i
< saverTypes
.count(); i
++)
433 if ((saverTypes
[i
] == "ManipulateScreen") && !manipulatescreen
)
435 kDebug(1204) << "Screensaver is type ManipulateScreen and ManipulateScreen is forbidden";
438 if ((saverTypes
[i
] == "OpenGL") && !opengl
)
440 kDebug(1204) << "Screensaver is type OpenGL and OpenGL is forbidden";
443 if (saverTypes
[i
] == "OpenGL")
445 mOpenGLVisual
= true;
450 kDebug(1204) << "mForbidden: " << (mForbidden
? "true" : "false");
452 if (config
.hasActionGroup("Root"))
454 mSaverExec
= config
.actionGroup("Root").readPathEntry("Exec", QString());
459 //---------------------------------------------------------------------------
461 // Create a window to draw our screen saver on.
463 void LockProcess::createSaverWindow()
465 Visual
* visual
= CopyFromParent
;
466 int depth
= CopyFromParent
;
467 XSetWindowAttributes attrs
;
468 int flags
= CWOverrideRedirect
;
469 #ifdef HAVE_GLXCHOOSEVISUAL
470 // this code is (partially) duplicated in kdebase/workspace/kcontrol/screensaver
473 static int attribs
[][ 15 ] =
475 #define R GLX_RED_SIZE
476 #define G GLX_GREEN_SIZE
477 #define B GLX_BLUE_SIZE
478 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, GLX_STENCIL_SIZE
, 1, None
},
479 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, GLX_DOUBLEBUFFER
, GLX_STENCIL_SIZE
, 1, None
},
480 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, None
},
481 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, GLX_DOUBLEBUFFER
, None
},
482 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, GLX_STENCIL_SIZE
, 1, None
},
483 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, GLX_STENCIL_SIZE
, 1, None
},
484 { GLX_RGBA
, R
, 8, G
, 8, B
, 8, GLX_DEPTH_SIZE
, 8, None
},
485 { GLX_RGBA
, R
, 4, G
, 4, B
, 4, GLX_DEPTH_SIZE
, 4, None
},
486 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, GLX_STENCIL_SIZE
, 1, None
},
487 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, GLX_DOUBLEBUFFER
, None
},
488 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, GLX_STENCIL_SIZE
, 1, None
},
489 { GLX_RGBA
, GLX_DEPTH_SIZE
, 8, None
}
494 for( unsigned int i
= 0;
495 i
< sizeof( attribs
) / sizeof( attribs
[ 0 ] );
498 if( XVisualInfo
* info
= glXChooseVisual( x11Info().display(), x11Info().screen(), attribs
[ i
] ))
500 visual
= info
->visual
;
502 static Colormap colormap
= 0;
504 XFreeColormap( x11Info().display(), colormap
);
505 colormap
= XCreateColormap( x11Info().display(), RootWindow( x11Info().display(), x11Info().screen()), visual
, AllocNone
);
506 attrs
.colormap
= colormap
;
514 attrs
.override_redirect
= 1;
516 Window w
= XCreateWindow( x11Info().display(), RootWindow( x11Info().display(), x11Info().screen()),
517 x(), y(), width(), height(), 0, depth
, InputOutput
, visual
, flags
, &attrs
);
519 create( w
, false, true );
521 // Some xscreensaver hacks check for this property
522 const char *version
= "KDE 4.0";
523 XChangeProperty (QX11Info::display(), winId(),
524 gXA_SCREENSAVER_VERSION
, XA_STRING
, 8, PropModeReplace
,
525 (unsigned char *) version
, strlen(version
));
528 XSetWindowAttributes attr
;
529 attr
.event_mask
= KeyPressMask
| ButtonPressMask
| PointerMotionMask
|
530 VisibilityChangeMask
| ExposureMask
;
531 XChangeWindowAttributes(QX11Info::display(), winId(),
536 // set NoBackground so that the saver can capture the current
537 // screen state if necessary
538 setAttribute(Qt::WA_PaintOnScreen
, true);
539 setAttribute(Qt::WA_NoSystemBackground
, true);
540 setAttribute(Qt::WA_PaintOutsidePaintEvent
, true); // for bitBlt in resume()
542 setCursor( Qt::BlankCursor
);
544 kDebug(1204) << "Saver window Id: " << winId();
547 //---------------------------------------------------------------------------
549 // Hide the screensaver window
551 void LockProcess::hideSaverWindow()
555 removeVRoot(winId());
556 XDeleteProperty(QX11Info::display(), winId(), gXA_SCREENSAVER_VERSION
);
558 unsigned long vroot_data
[1] = { gVRootData
};
559 XChangeProperty(QX11Info::display(), gVRoot
, gXA_VROOT
, XA_WINDOW
, 32,
560 PropModeReplace
, (unsigned char *)vroot_data
, 1);
563 XSync(QX11Info::display(), False
);
566 //---------------------------------------------------------------------------
567 static int ignoreXError(Display
*, XErrorEvent
*)
572 //---------------------------------------------------------------------------
574 // Save the current virtual root window
576 void LockProcess::saveVRoot()
578 Window rootReturn
, parentReturn
, *children
;
579 unsigned int numChildren
;
581 Window root
= RootWindowOfScreen(ScreenOfDisplay(QX11Info::display(), info
.screen()));
586 int (*oldHandler
)(Display
*, XErrorEvent
*);
587 oldHandler
= XSetErrorHandler(ignoreXError
);
589 if (XQueryTree(QX11Info::display(), root
, &rootReturn
, &parentReturn
,
590 &children
, &numChildren
))
592 for (unsigned int i
= 0; i
< numChildren
; i
++)
596 unsigned long nitems
, bytesafter
;
597 unsigned char *newRoot
= 0;
599 if ((XGetWindowProperty(QX11Info::display(), children
[i
], gXA_VROOT
, 0, 1,
600 False
, XA_WINDOW
, &actual_type
, &actual_format
, &nitems
, &bytesafter
,
601 &newRoot
) == Success
) && newRoot
)
603 gVRoot
= children
[i
];
604 Window
*dummy
= (Window
*)newRoot
;
606 XFree ((char*) newRoot
);
612 XFree((char *)children
);
616 XSetErrorHandler(oldHandler
);
619 //---------------------------------------------------------------------------
621 // Set the virtual root property
623 void LockProcess::setVRoot(Window win
, Window vr
)
629 unsigned long rw
= RootWindowOfScreen(ScreenOfDisplay(QX11Info::display(), info
.screen()));
630 unsigned long vroot_data
[1] = { vr
};
632 Window rootReturn
, parentReturn
, *children
;
633 unsigned int numChildren
;
636 XQueryTree(QX11Info::display(), top
, &rootReturn
, &parentReturn
,
637 &children
, &numChildren
);
639 XFree((char *)children
);
640 if (parentReturn
== rw
) {
646 XChangeProperty(QX11Info::display(), top
, gXA_VROOT
, XA_WINDOW
, 32,
647 PropModeReplace
, (unsigned char *)vroot_data
, 1);
650 //---------------------------------------------------------------------------
652 // Remove the virtual root property
654 void LockProcess::removeVRoot(Window win
)
656 XDeleteProperty (QX11Info::display(), win
, gXA_VROOT
);
659 //---------------------------------------------------------------------------
661 // Grab the keyboard. Returns true on success
663 bool LockProcess::grabKeyboard()
665 int rv
= XGrabKeyboard( QX11Info::display(), QApplication::desktop()->winId(),
666 True
, GrabModeAsync
, GrabModeAsync
, CurrentTime
);
668 return (rv
== GrabSuccess
);
671 #define GRABEVENTS ButtonPressMask | ButtonReleaseMask | PointerMotionMask | \
672 EnterWindowMask | LeaveWindowMask
674 //---------------------------------------------------------------------------
676 // Grab the mouse. Returns true on success
678 bool LockProcess::grabMouse()
680 int rv
= XGrabPointer( QX11Info::display(), QApplication::desktop()->winId(),
681 True
, GRABEVENTS
, GrabModeAsync
, GrabModeAsync
, None
,
682 QCursor(Qt::BlankCursor
).handle(), CurrentTime
);
684 return (rv
== GrabSuccess
);
687 //---------------------------------------------------------------------------
689 // Grab keyboard and mouse. Returns true on success.
691 bool LockProcess::grabInput()
693 XSync(QX11Info::display(), False
);
709 XUngrabKeyboard(QX11Info::display(), CurrentTime
);
719 //---------------------------------------------------------------------------
721 // Release mouse an keyboard grab.
723 void LockProcess::ungrabInput()
725 XUngrabKeyboard(QX11Info::display(), CurrentTime
);
726 XUngrabPointer(QX11Info::display(), CurrentTime
);
730 //---------------------------------------------------------------------------
732 // Start the screen saver.
734 bool LockProcess::startSaver()
736 if (!child_saver
&& !grabInput())
738 kWarning(1204) << "LockProcess::startSaver() grabInput() failed!!!!" ;
746 QSocketNotifier
*notifier
= new QSocketNotifier(mParent
, QSocketNotifier::Read
, this);
747 connect(notifier
, SIGNAL( activated (int)), SLOT( quitSaver()));
752 setCursor( Qt::BlankCursor
);
755 XSync(QX11Info::display(), False
);
757 setVRoot( winId(), winId() );
763 //---------------------------------------------------------------------------
765 // Stop the screen saver.
767 void LockProcess::stopSaver()
769 kDebug(1204) << "LockProcess: stopping saver";
777 KDisplayManager().setLock( false );
779 const char *out
= "GOAWAY!";
780 for (QList
<int>::ConstIterator it
= child_sockets
.begin(); it
!= child_sockets
.end(); ++it
)
781 write(*it
, out
, sizeof(out
));
786 QVariant
LockProcess::getConf(void *ctx
, const char *key
, const QVariant
&dflt
)
788 LockProcess
*that
= (LockProcess
*)ctx
;
789 QString fkey
= QLatin1String( key
) + '=';
790 for (QStringList::ConstIterator it
= that
->mPluginOptions
.begin();
791 it
!= that
->mPluginOptions
.end(); ++it
)
792 if ((*it
).startsWith( fkey
))
793 return (*it
).mid( fkey
.length() );
797 void LockProcess::cantLock( const QString
&txt
)
799 msgBox( 0, QMessageBox::Critical
, i18n("Will not lock the session, as unlocking would be impossible:\n") + txt
);
802 #if 0 // placeholders for later
803 i18n("Cannot start <i>kcheckpass</i>.");
804 i18n("<i>kcheckpass</i> is unable to operate. Possibly it is not setuid root.");
807 //---------------------------------------------------------------------------
809 // Make the screen saver password protected.
811 bool LockProcess::startLock()
813 if (loadGreetPlugin()) {
815 KDisplayManager().setLock(true);
822 bool LockProcess::loadGreetPlugin()
824 if (greetPlugin
.library
) {
825 //we were locked once before, so all the plugin loading's done already
826 //FIXME should I be unloading the plugin on unlock instead?
829 for (QStringList::ConstIterator it
= mPlugins
.begin(); it
!= mPlugins
.end(); ++it
) {
830 GreeterPluginHandle plugin
;
831 KLibrary
*lib
= new KLibrary( (*it
)[0] == '/' ? *it
: "kgreet_" + *it
);
832 if (lib
->fileName().isEmpty()) {
833 kWarning(1204) << "GreeterPlugin " << *it
<< " does not exist" ;
838 kWarning(1204) << "Cannot load GreeterPlugin " << *it
<< " (" << lib
->fileName() << ")" ;
842 plugin
.library
= lib
;
843 plugin
.info
= (KGreeterPluginInfo
*)lib
->resolveSymbol( "kgreeterplugin_info" );
845 kWarning(1204) << "GreeterPlugin " << *it
<< " (" << lib
->fileName() << ") is no valid greet widget plugin" ;
850 if (plugin
.info
->method
&& !mMethod
.isEmpty() && mMethod
!= plugin
.info
->method
) {
851 kDebug(1204) << "GreeterPlugin " << *it
<< " (" << lib
->fileName() << ") serves " << plugin
.info
->method
<< ", not " << mMethod
;
856 if (!plugin
.info
->init( mMethod
, getConf
, this )) {
857 kDebug(1204) << "GreeterPlugin " << *it
<< " (" << lib
->fileName() << ") refuses to serve " << mMethod
;
862 kDebug(1204) << "GreeterPlugin " << *it
<< " (" << plugin
.info
->method
<< ", " << plugin
.info
->name
<< ") loaded";
863 greetPlugin
= plugin
;
866 cantLock( i18n("No appropriate greeter plugin configured.") );
870 //---------------------------------------------------------------------------
874 bool LockProcess::startHack()
876 kDebug(1204) << "Starting hack:" << mSaverExec
;
878 if (mSaverExec
.isEmpty() || mForbidden
)
884 QHash
<QChar
, QString
> keyMap
;
885 keyMap
.insert('w', QString::number(winId()));
886 mHackProc
<< KShell::splitArgs(KMacroExpander::expandMacrosShellQuote(mSaverExec
, keyMap
));
889 if (mHackProc
.waitForStarted())
891 #ifdef HAVE_SETPRIORITY
892 setpriority(PRIO_PROCESS
, mHackProc
.pid(), mPriority
);
901 //---------------------------------------------------------------------------
903 void LockProcess::stopHack()
905 if (mHackProc
.state() != QProcess::NotRunning
)
907 mHackProc
.terminate();
908 if (!mHackProc
.waitForFinished(10000))
915 //---------------------------------------------------------------------------
917 void LockProcess::hackExited()
919 // Hack exited while we're supposed to be saving the screen.
920 // Make sure the saver window is black.
921 XSetWindowBackground(QX11Info::display(), winId(), 0);
922 XClearWindow(QX11Info::display(), winId());
925 bool LockProcess::startPlasma()
927 if (!mPlasmaEnabled
) {
930 kDebug() << "starting plasma-overlay";
932 mSuppressUnlock
.start(mSuppressUnlockTimeout
);
933 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
934 QCursor(Qt::ArrowCursor
).handle(), CurrentTime
);
937 //try to get it, in case it's already running somehow
938 //FIXME I don't like hardcoded strings
939 mPlasmaDBus
= new QDBusInterface("org.kde.plasma-overlay", "/MainApplication", QString(),
940 QDBusConnection::sessionBus(), this);
942 if (mPlasmaDBus
->isValid()) {
943 kDebug() << "weird, plasma-overlay is already running";
944 connect(mPlasmaDBus
, SIGNAL(viewCreated(uint
)), SLOT(setPlasmaView(uint
)));
946 mPlasmaDBus
->call(QDBus::NoBlock
, "activate");
947 //it'll be locked because we haven't put plasma in setup mode. doh.
948 //FIXME make a dbus call to change that
950 mPlasmaDBus
->call(QDBus::NoBlock
, "deactivate");
952 mPlasmaDBus
->callWithCallback("viewWinId", QList
<QVariant
>(), this,
953 SLOT(setPlasmaView(uint
)));
958 connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString
, QString
,
960 SLOT(newService(QString
)));
961 mPlasmaProc
.setProgram("plasma-overlay");
963 mPlasmaProc
<< "--setup";
969 void LockProcess::stopPlasma()
971 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
972 mPlasmaDBus
->call(QDBus::NoBlock
, "quit");
974 kDebug() << "cannot stop plasma-overlay";
978 void LockProcess::newService(QString name
)
982 kDebug() << "can't happen"; //but it does.
985 if (name
!= "org.kde.plasma-overlay") {
989 kDebug() << "plasma! yaay!";
990 disconnect(QDBusConnection::sessionBus().interface(), 0, this, 0); //no need for you any more
991 //FIXME might we want to know if the interface goes away?
992 //FIXME that disconnect isn't working anyways! wtf
994 mPlasmaDBus
= new QDBusInterface(name
, "/MainApplication", QString(),
995 QDBusConnection::sessionBus(), this);
996 if (!mPlasmaDBus
->isValid()) {
997 kDebug() << "wtf! not valid!?"; //we're screwed now.
998 //FIXME delete it anyways?
1002 connect(mPlasmaDBus
, SIGNAL(hidden()), SLOT(unSuppressUnlock()));
1003 //TODO can we conect to this only when we don't have an ID?
1004 connect(mPlasmaDBus
, SIGNAL(viewCreated(uint
)), SLOT(setPlasmaView(uint
)));
1005 //kDebug() << "should be connected";
1006 //however, we may have connnected too *late*, so now we have to see if we can grab the winid
1008 mPlasmaDBus
->callWithCallback("viewWinId", QList
<QVariant
>(), this,
1009 SLOT(setPlasmaView(uint
)));
1011 if (!mDialogs
.isEmpty()) {
1012 //whoops, activation probably failed earlier
1013 mPlasmaDBus
->call(QDBus::NoBlock
, "activate");
1017 void LockProcess::deactivatePlasma()
1019 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
1020 mPlasmaDBus
->call(QDBus::NoBlock
, "deactivate");
1022 if (!mLocked
&& mLockGrace
>=0) {
1023 QTimer::singleShot(mLockGrace
, this, SLOT(startLock()));
1027 void LockProcess::lockPlasma()
1029 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
1030 mPlasmaDBus
->call(QDBus::NoBlock
, "lock");
1034 void LockProcess::unSuppressUnlock()
1036 //note: suppressing unlock also now means suppressing quit-on-activity
1037 //maybe some var renaming is in order.
1038 mSuppressUnlock
.stop();
1041 void LockProcess::quit()
1043 mSuppressUnlock
.stop();
1044 if (!mLocked
|| checkPass()) {
1049 void LockProcess::suspend()
1051 if( !mSuspended
&& mHackProc
.state() == QProcess::Running
)
1053 ::kill(mHackProc
.pid(), SIGSTOP
);
1054 QApplication::syncX();
1055 mSavedScreen
= QPixmap::grabWindow( winId());
1060 void LockProcess::resume( bool force
)
1062 if( !force
&& (!mDialogs
.isEmpty() || !mVisibility
))
1063 return; // no resuming with dialog visible or when not visible
1064 if( mSuspended
&& mHackProc
.state() == QProcess::Running
)
1066 XForceScreenSaver(QX11Info::display(), ScreenSaverReset
);
1068 p
.drawPixmap( 0, 0, mSavedScreen
);
1070 mSavedScreen
= QPixmap();
1071 QApplication::syncX();
1072 ::kill(mHackProc
.pid(), SIGCONT
);
1077 //---------------------------------------------------------------------------
1079 // Show the password dialog
1080 // This is called only in the master process
1082 bool LockProcess::checkPass()
1084 killTimer(mAutoLogoutTimerId
);
1086 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
1087 mPlasmaDBus
->call(QDBus::NoBlock
, "activate");
1090 PasswordDlg
passDlg( this, &greetPlugin
);
1091 int ret
= execDialog( &passDlg
);
1093 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
1094 if (ret
== QDialog::Rejected
) {
1095 mSuppressUnlock
.start(mSuppressUnlockTimeout
);
1096 } else if (ret
== TIMEOUT_CODE
) {
1097 mPlasmaDBus
->call(QDBus::NoBlock
, "deactivate");
1101 XWindowAttributes rootAttr
;
1102 XGetWindowAttributes(QX11Info::display(), QX11Info::appRootWindow(), &rootAttr
);
1103 if(( rootAttr
.your_event_mask
& SubstructureNotifyMask
) == 0 )
1105 kWarning() << "ERROR: Something removed SubstructureNotifyMask from the root window!!!" ;
1106 XSelectInput( QX11Info::display(), QX11Info::appRootWindow(),
1107 SubstructureNotifyMask
| rootAttr
.your_event_mask
);
1110 return ret
== QDialog::Accepted
;
1113 bool LockProcess::checkPass(const QString
&reason
)
1115 PasswordDlg
passDlg(this, &greetPlugin
, reason
);
1116 int ret
= execDialog( &passDlg
);
1119 //FIXME do we need to copy&paste that SubstructureNotifyMask code above?
1120 if (ret
== QDialog::Accepted
) {
1121 //we don't quit on a custom checkpass, but we do unlock
1122 //so that the user doesn't have to type their password twice
1124 KDisplayManager().setLock(false);
1125 //FIXME while suppressUnlock *should* always be running, if it isn't
1126 //(say if someone's doing things they shouldn't with dbus) then it won't get started by this
1127 //which means that a successful unlock will never re-lock
1128 //in fact, the next bit of activity would lead to the screensaver quitting.
1129 //possible solutions:
1130 //-treat this function like activity: quit if already unlocked, ensure suppress is started
1131 //if we're locked and the dialog's rejected
1132 //-return true if already unlocked, without doing anything, same as above if locked
1133 //-let it quit, and tell people not to do such silly things :P
1139 static void fakeFocusIn( WId window
)
1141 // We have keyboard grab, so this application will
1142 // get keyboard events even without having focus.
1143 // Fake FocusIn to make Qt realize it has the active
1144 // window, so that it will correctly show cursor in the dialog.
1146 memset(&ev
, 0, sizeof(ev
));
1147 ev
.xfocus
.display
= QX11Info::display();
1148 ev
.xfocus
.type
= FocusIn
;
1149 ev
.xfocus
.window
= window
;
1150 ev
.xfocus
.mode
= NotifyNormal
;
1151 ev
.xfocus
.detail
= NotifyAncestor
;
1152 XSendEvent( QX11Info::display(), window
, False
, NoEventMask
, &ev
);
1155 void LockProcess::setPlasmaView(uint id
)
1159 fakeFocusIn(mPlasmaView
);
1165 bool LockProcess::eventFilter(QObject
*o
, QEvent
*e
)
1167 if (e
->type() == QEvent::Resize
) {
1168 QWidget
*w
= static_cast<QWidget
*>(o
);
1169 mFrames
.value(w
)->resize(w
->size());
1174 int LockProcess::execDialog( QDialog
*dlg
)
1176 QFrame
*winFrame
= new QFrame( dlg
);
1177 winFrame
->setFrameStyle( QFrame::WinPanel
| QFrame::Raised
);
1178 winFrame
->setLineWidth( 2 );
1180 mFrames
.insert(dlg
, winFrame
);
1181 dlg
->installEventFilter(this);
1185 QRect rect
= dlg
->geometry();
1186 rect
.moveCenter(KGlobalSettings::desktopGeometry(QCursor::pos()).center());
1187 dlg
->move( rect
.topLeft() );
1189 if (mDialogs
.isEmpty())
1192 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
1193 QCursor(Qt::ArrowCursor
).handle(), CurrentTime
);
1195 mDialogs
.prepend( dlg
);
1196 fakeFocusIn( dlg
->winId());
1197 int rt
= dlg
->exec();
1198 int pos
= mDialogs
.indexOf( dlg
);
1200 mDialogs
.remove( pos
);
1201 if( mDialogs
.isEmpty() ) {
1202 //blank pointer + plasma = confused user
1203 //FIXME we need to fakefocusin plasma for the qactions to work
1204 //but we never seem to get a focus*out*
1205 //and what about the config dialogs?
1207 fakeFocusIn(mPlasmaView
);
1209 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
1210 QCursor(Qt::BlankCursor
).handle(), CurrentTime
);
1214 fakeFocusIn( mDialogs
.first()->winId());
1216 dlg
->removeEventFilter(this);
1217 mFrames
.remove(dlg
);
1222 void LockProcess::preparePopup()
1224 QWidget
*dlg
= (QWidget
*)sender();
1225 mDialogs
.prepend( dlg
);
1226 fakeFocusIn( dlg
->winId() );
1229 void LockProcess::cleanupPopup()
1231 QWidget
*dlg
= (QWidget
*)sender();
1233 int pos
= mDialogs
.indexOf( dlg
);
1234 mDialogs
.remove( pos
);
1235 fakeFocusIn( mDialogs
.first()->winId() );
1238 //---------------------------------------------------------------------------
1242 bool LockProcess::x11Event(XEvent
*event
)
1245 switch (event
->type
)
1248 if (!mDialogs
.isEmpty() && event
->xbutton
.window
== event
->xbutton
.root
) {
1249 //kDebug() << "close" << mDialogs.first()->effectiveWinId();
1250 KDialog
*dlg
= qobject_cast
<KDialog
*>(mDialogs
.first());
1252 //kDebug() << "casting success";
1259 if (mBusy
|| !mDialogs
.isEmpty())
1260 //FIXME shouldn't we be resetting some timers?
1263 //something happened. do we quit, ask for a password or forward it to plasma?
1264 if (mSuppressUnlock
.isActive()) {
1265 //help, help, I'm being suppressed!
1266 mSuppressUnlock
.start(); //reset the timeout
1267 } else if (!mLocked
|| checkPass()) {
1270 return true; //it's better not to forward any input while quitting, right?
1272 if (mAutoLogout
) // we need to restart the auto logout countdown
1274 killTimer(mAutoLogoutTimerId
);
1275 mAutoLogoutTimerId
= startTimer(mAutoLogoutTimeout
);
1281 case VisibilityNotify
:
1282 if( event
->xvisibility
.window
== winId())
1283 { // mVisibility == false means the screensaver is not visible at all
1284 // e.g. when switched to text console
1285 // ...or when plasma's over it non-compositely?
1286 // hey, this gives me free "suspend saver when plasma obscures it"
1287 mVisibility
= !(event
->xvisibility
.state
== VisibilityFullyObscured
);
1289 mSuspendTimer
.start(2000);
1290 kDebug() << "fully obscured";
1292 kDebug() << "not fully obscured";
1293 mSuspendTimer
.stop();
1296 if (mForeignWindows
.isEmpty() && event
->xvisibility
.state
!= VisibilityUnobscured
) {
1297 kDebug() << "no plasma; saver obscured";
1300 } else if (mPlasmaView
&& event
->xvisibility
.window
== mPlasmaView
&&
1301 event
->xvisibility
.state
!= VisibilityUnobscured
) {
1302 //FIXME now that we have several plasma winids this doesn't feel valid
1303 //but I don't know what to do about it!
1304 kDebug() << "plasma obscured!";
1309 case ConfigureNotify
: // from SubstructureNotifyMask on the root window
1310 if(event
->xconfigure
.event
== QX11Info::appRootWindow()) {
1311 //kDebug() << "ConfigureNotify:";
1312 //the stacking order changed, so let's change the stacking order!
1316 case MapNotify
: // from SubstructureNotifyMask on the root window
1317 if( event
->xmap
.event
== QX11Info::appRootWindow()) {
1318 kDebug() << "MapNotify:" << event
->xmap
.window
;
1319 WindowType type
= windowType(event
->xmap
.window
);
1320 //TODO get the view id here
1321 if (type
!= IgnoreWindow
) {
1322 if (mForeignWindows
.contains(event
->xmap
.window
)) {
1323 kDebug() << "uhoh! duplicate!";
1325 //ordered youngest-on-top
1326 mForeignWindows
.prepend(event
->xmap
.window
);
1328 if (type
& InputWindow
) {
1329 if (mForeignInputWindows
.contains(event
->xmap
.window
)) {
1330 kDebug() << "uhoh! duplicate again"; //never happens
1332 //ordered youngest-on-top
1333 mForeignInputWindows
.prepend(event
->xmap
.window
);
1341 if (event
->xmap
.event
== QX11Info::appRootWindow()) {
1342 kDebug() << "UnmapNotify:" << event
->xunmap
.window
;
1343 mForeignWindows
.removeAll(event
->xunmap
.window
);
1344 mForeignInputWindows
.removeAll(event
->xunmap
.window
);
1348 // We have grab with the grab window being the root window.
1349 // This results in key events being sent to the root window,
1350 // but they should be sent to the dialog if it's visible.
1351 // It could be solved by setFocus() call, but that would mess
1352 // the focus after this process exits.
1353 // Qt seems to be quite hard to persuade to redirect the event,
1354 // so let's simply dupe it with correct destination window,
1355 // and ignore the original one.
1356 if (!mDialogs
.isEmpty()) {
1357 if ((event
->type
== KeyPress
|| event
->type
== KeyRelease
) &&
1358 event
->xkey
.window
!= mDialogs
.first()->winId()) {
1359 //kDebug() << "forward to dialog";
1360 XEvent ev2
= *event
;
1361 ev2
.xkey
.window
= ev2
.xkey
.subwindow
= mDialogs
.first()->winId();
1362 qApp
->x11ProcessEvent( &ev2
);
1365 } else if (!mForeignInputWindows
.isEmpty()) {
1366 //when there are no dialogs, forward some events to plasma
1367 switch (event
->type
) {
1374 //kDebug() << "forward to plasma";
1375 XEvent ev2
= *event
;
1376 ev2
.xkey
.window
= ev2
.xkey
.subwindow
= mForeignInputWindows
.first();
1377 XSendEvent(QX11Info::display(), ev2
.xkey
.window
, False
, NoEventMask
, &ev2
);
1387 LockProcess::WindowType
LockProcess::windowType(WId id
)
1389 Atom tag
= XInternAtom(QX11Info::display(), "_KDE_SCREENSAVER_OVERRIDE", False
);
1392 unsigned long nitems
, remaining
;
1393 unsigned char *data
= 0;
1394 Display
*display
= QX11Info::display();
1396 int result
= XGetWindowProperty(display
, id
, tag
, 0, 1, False
, tag
, &actualType
,
1397 &actualFormat
, &nitems
, &remaining
, &data
);
1399 kDebug() << (result
== Success
) << (actualType
== tag
);
1400 WindowType type
= IgnoreWindow
;
1401 if (result
== Success
&& actualType
== tag
) {
1402 if (nitems
!= 1 || actualFormat
!= 8) {
1403 kDebug() << "malformed property";
1406 case 0: //FIXME magic numbers
1407 type
= SimpleWindow
;
1413 type
= DefaultWindow
;
1422 /* if (result != Success) {
1425 if (actualType == tag) {
1428 //managed windows will have a pesky frame we have to bypass
1429 XWindowAttributes attr;
1430 XGetWindowAttributes(display, id, &attr);
1431 if (!attr.override_redirect) {
1432 //check the real client window
1433 if (Window client = XmuClientWindow(display, id)) {
1434 result = XGetWindowProperty(display, client, tag, 0, 0, False, tag, &actualType,
1435 &actualFormat, &nitems, &remaining, &data);
1436 kDebug() << (result == Success) << (actualType == tag);
1440 return (result == Success) && (actualType == tag);
1446 void LockProcess::stayOnTop()
1448 if(!(mDialogs
.isEmpty() && mForeignWindows
.isEmpty()))
1450 // this restacking is written in a way so that
1451 // if the stacking positions actually don't change,
1452 // all restacking operations will be no-op,
1453 // and no ConfigureNotify will be generated,
1454 // thus avoiding possible infinite loops
1455 Window
* stack
= new Window
[ mDialogs
.count() + mForeignWindows
.count() + 1 ];
1457 if (!mDialogs
.isEmpty()) {
1458 XRaiseWindow( QX11Info::display(), mDialogs
.first()->winId()); // raise topmost
1459 // and stack others below it
1460 for( QVector
< QWidget
* >::ConstIterator it
= mDialogs
.begin();
1461 it
!= mDialogs
.end();
1463 stack
[ count
++ ] = (*it
)->winId();
1465 XRaiseWindow( QX11Info::display(), mForeignWindows
.first()); // raise topmost
1467 //now the plasma stuff below the dialogs
1468 foreach (const WId w
, mForeignWindows
) {
1471 /*if (mPlasmaView) {
1472 stack[count++] = mPlasmaView;
1473 kDebug() << "plasma on stack";
1475 //finally, the saver window
1476 stack
[ count
++ ] = winId();
1477 XRestackWindows( x11Info().display(), stack
, count
);
1478 //kDebug() << "restacked" << count;
1481 XRaiseWindow(QX11Info::display(), winId());
1485 void LockProcess::checkDPMSActive()
1490 DPMSInfo(QX11Info::display(), &state
, &on
);
1491 //kDebug() << "checkDPMSActive " << on << " " << state;
1492 if (state
== DPMSModeStandby
|| state
== DPMSModeSuspend
|| state
== DPMSModeOff
)
1495 } else if ( mSuspended
)
1502 #if defined(HAVE_XF86MISC) && defined(HAVE_XF86MISCSETGRABKEYSSTATE)
1503 // see http://cvsweb.xfree86.org/cvsweb/xc/programs/Xserver/hw/xfree86/common/xf86Events.c#rev3.113
1504 // This allows enabling the "Allow{Deactivate/Closedown}Grabs" options in XF86Config,
1505 // and krunner_lock will still lock the session.
1506 static enum { Unknown
, Yes
, No
} can_do_xf86_lock
= Unknown
;
1507 void LockProcess::lockXF86()
1509 if( can_do_xf86_lock
== Unknown
)
1512 if( XF86MiscQueryVersion( QX11Info::display(), &major
, &minor
)
1513 && (major
> 0 || minor
>= 5) )
1514 can_do_xf86_lock
= Yes
;
1516 can_do_xf86_lock
= No
;
1518 if( can_do_xf86_lock
!= Yes
)
1520 if( mRestoreXF86Lock
)
1522 if( XF86MiscSetGrabKeysState( QX11Info::display(), False
) != MiscExtGrabStateSuccess
)
1525 mRestoreXF86Lock
= true;
1528 void LockProcess::unlockXF86()
1530 if( can_do_xf86_lock
!= Yes
)
1532 if( !mRestoreXF86Lock
)
1534 XF86MiscSetGrabKeysState( QX11Info::display(), True
);
1535 mRestoreXF86Lock
= false;
1538 void LockProcess::lockXF86()
1542 void LockProcess::unlockXF86()
1547 void LockProcess::msgBox( QWidget
*parent
, QMessageBox::Icon type
, const QString
&txt
)
1549 QDialog
box( parent
, Qt::X11BypassWindowManagerHint
);
1550 box
.setModal( true );
1552 QLabel
*label1
= new QLabel( &box
);
1553 label1
->setPixmap( QMessageBox::standardIcon( type
) );
1554 QLabel
*label2
= new QLabel( txt
, &box
);
1555 KPushButton
*button
= new KPushButton( KStandardGuiItem::ok(), &box
);
1556 button
->setDefault( true );
1557 button
->setSizePolicy( QSizePolicy( QSizePolicy::Preferred
, QSizePolicy::Preferred
) );
1558 connect( button
, SIGNAL( clicked() ), &box
, SLOT( accept() ) );
1560 QGridLayout
*grid
= new QGridLayout( &box
);
1561 grid
->setSpacing( 10 );
1562 grid
->addWidget( label1
, 0, 0, Qt::AlignCenter
);
1563 grid
->addWidget( label2
, 0, 1, Qt::AlignCenter
);
1564 grid
->addWidget( button
, 1, 0, 1, 2, Qt::AlignCenter
);
1569 #include "lockprocess.moc"