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
),
137 mOpenGLVisual(false),
140 mUseBlankOnly(useBlankOnly
),
143 mRestoreXF86Lock(false),
145 mAutoLogoutTimerId(0)
147 setObjectName("save window");
150 new LockProcessAdaptor(this);
151 QDBusConnection::sessionBus().registerService("org.kde.krunner_lock");
152 QDBusConnection::sessionBus().registerObject("/LockProcess", this);
154 kapp
->installX11EventFilter(this);
156 // Get root window size
157 XWindowAttributes rootAttr
;
159 XGetWindowAttributes(QX11Info::display(), RootWindow(QX11Info::display(),
160 info
.screen()), &rootAttr
);
161 kapp
->desktop(); // make Qt set its event mask on the root window first
162 XSelectInput( QX11Info::display(), QX11Info::appRootWindow(),
163 SubstructureNotifyMask
| rootAttr
.your_event_mask
);
164 #ifdef CHECK_XSELECTINPUT
165 check_xselectinput
= true;
167 setGeometry(0, 0, rootAttr
.width
, rootAttr
.height
);
169 // virtual root property
170 gXA_VROOT
= XInternAtom (QX11Info::display(), "__SWM_VROOT", False
);
171 gXA_SCREENSAVER_VERSION
= XInternAtom (QX11Info::display(), "_SCREENSAVER_VERSION", False
);
173 connect(&mHackProc
, SIGNAL(finished(int, QProcess::ExitStatus
)),
176 mSuspendTimer
.setSingleShot(true);
177 connect(&mSuspendTimer
, SIGNAL(timeout()), SLOT(suspend()));
180 QString::fromLatin1( ::getenv( "XDM_MANAGED" )).split(QChar(','), QString::SkipEmptyParts
);
181 for (QStringList::ConstIterator it
= dmopt
.begin(); it
!= dmopt
.end(); ++it
)
182 if ((*it
).startsWith("method="))
183 mMethod
= (*it
).mid(7);
191 DPMSInfo(QX11Info::display(), &state
, &on
);
194 connect(&mCheckDPMS
, SIGNAL(timeout()), SLOT(checkDPMSActive()));
195 // we can save CPU if we stop it as quickly as possible
196 // but we waste CPU if we check too often -> so take 10s
197 mCheckDPMS
.start(10000);
202 greetPlugin
.library
= 0;
204 mSuppressUnlock
.setSingleShot(true);
205 connect(&mSuppressUnlock
, SIGNAL(timeout()), SLOT(deactivatePlasma()));
208 //---------------------------------------------------------------------------
210 // Destructor - usual cleanups.
212 LockProcess::~LockProcess()
214 if (greetPlugin
.library
) {
215 if (greetPlugin
.info
->done
)
216 greetPlugin
.info
->done();
217 greetPlugin
.library
->unload();
221 static int signal_pipe
[2];
223 static void sigterm_handler(int)
226 ::write( signal_pipe
[1], &tmp
, 1);
229 static void sighup_handler(int)
232 ::write( signal_pipe
[1], &tmp
, 1);
235 void LockProcess::timerEvent(QTimerEvent
*ev
)
237 if (ev
->timerId() == mAutoLogoutTimerId
)
239 killTimer(mAutoLogoutTimerId
);
240 AutoLogout
autologout(this);
241 execDialog(&autologout
);
245 void LockProcess::setupSignals()
247 struct sigaction act
;
249 act
.sa_handler
=SIG_IGN
;
250 sigemptyset(&(act
.sa_mask
));
251 sigaddset(&(act
.sa_mask
), SIGINT
);
253 sigaction(SIGINT
, &act
, 0L);
255 act
.sa_handler
=SIG_IGN
;
256 sigemptyset(&(act
.sa_mask
));
257 sigaddset(&(act
.sa_mask
), SIGQUIT
);
259 sigaction(SIGQUIT
, &act
, 0L);
260 // exit cleanly on SIGTERM
261 act
.sa_handler
= sigterm_handler
;
262 sigemptyset(&(act
.sa_mask
));
263 sigaddset(&(act
.sa_mask
), SIGTERM
);
265 sigaction(SIGTERM
, &act
, 0L);
266 // SIGHUP forces lock
267 act
.sa_handler
= sighup_handler
;
268 sigemptyset(&(act
.sa_mask
));
269 sigaddset(&(act
.sa_mask
), SIGHUP
);
271 sigaction(SIGHUP
, &act
, 0L);
274 QSocketNotifier
* notif
= new QSocketNotifier(signal_pipe
[0], QSocketNotifier::Read
, this);
275 connect( notif
, SIGNAL(activated(int)), SLOT(signalPipeSignal()));
279 void LockProcess::signalPipeSignal()
282 ::read( signal_pipe
[0], &tmp
, 1);
285 else if( tmp
== 'H' ) {
291 //---------------------------------------------------------------------------
292 bool LockProcess::lock()
295 // In case of a forced lock we don't react to events during
296 // the dead-time to give the screensaver some time to activate.
297 // That way we don't accidentally show the password dialog before
298 // the screensaver kicks in because the user moved the mouse after
299 // selecting "lock screen", that looks really untidy.
303 QTimer::singleShot(1000, this, SLOT(slotDeadTimePassed()));
311 //---------------------------------------------------------------------------
312 void LockProcess::slotDeadTimePassed()
317 //---------------------------------------------------------------------------
318 bool LockProcess::defaultSave()
323 QTimer::singleShot(mLockGrace
, this, SLOT(startLock()));
329 bool LockProcess::startSetup()
331 if (!mPlasmaEnabled
) {
332 return defaultSave();
338 //plasma startup will handle the suppressunlock bit
340 //---------------------------------------------------------------------------
341 bool LockProcess::dontLock()
347 //---------------------------------------------------------------------------
348 void LockProcess::quitSaver()
354 //---------------------------------------------------------------------------
356 // Read and apply configuration.
358 void LockProcess::configure()
360 // the configuration is stored in krunner's config file
361 if( KScreenSaverSettings::lock() ) {
362 mLockGrace
= KScreenSaverSettings::lockGrace();
365 else if (mLockGrace
> 300000)
366 mLockGrace
= 300000; // 5 minutes, keep the value sane
371 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
) {
931 mSuppressUnlock
.start(mSuppressUnlockTimeout
);
932 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
933 QCursor(Qt::ArrowCursor
).handle(), CurrentTime
);
935 kDebug() << "looking for plasma-overlay";
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 mPlasmaDBus
= new org::kde::plasmaoverlay::App("org.kde.plasma-overlay", "/App",
941 QDBusConnection::sessionBus(), this);
942 //FIXME this might-already-be-running stuff seems really really Wrong.
944 if (mPlasmaDBus
->isValid()) {
945 kDebug() << "weird, plasma-overlay is already running";
946 //FIXME I'd like to just kill it off or ignore it
947 //maybe it should *not* be a kuniqueapp?
948 //FIXME oh, and if it's already visible then we won't get a mapnotify for it
949 //and then it won't be in our foreign window lists and so stuff will break
951 mPlasmaDBus
->call(QDBus::NoBlock
, "activate");
952 //it'll be locked because we haven't put plasma in setup mode. doh.
953 //FIXME make a dbus call to change that
955 mPlasmaDBus
->call(QDBus::NoBlock
, "deactivate");
959 kDebug () << "...not found";
962 kDebug() << "starting plasma-overlay";
963 connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString
, QString
,
965 SLOT(newService(QString
, QString
, QString
)));
966 mPlasmaProc
.setProgram("plasma-overlay");
968 mPlasmaProc
<< "--setup";
971 kDebug() << "process begun";
972 //plasma gets 15 seconds to load, or we assume it failed
973 QTimer::singleShot(15 * 1000, this, SLOT(checkPlasma()));
977 void LockProcess::checkPlasma()
979 if (!mPlasmaEnabled
) {
980 kDebug() << "You're Doing It Wrong!";
983 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
984 //hooray, looks like it started ok
985 kDebug() << "success!";
986 //...but just in case, make sure we're not waiting on it
991 kDebug() << "ohnoes. plasma = teh fail.";
995 bool LockProcess::isPlasmaValid()
997 //FIXME I'm assuming that if it's valid, calls will succeed. so if that's not the case we'll
998 //need to change things so that plasma's disabled properly if it fails
999 //damn. isValid is not quite enough. a call may still fail, and then we need to bail.
1000 if (!(mPlasmaEnabled
&& mPlasmaDBus
)) {
1001 return false; //no plasma, at least not yet
1003 if (mPlasmaDBus
->isValid()) {
1006 //oh crap, it ran away on us.
1011 void LockProcess::disablePlasma()
1014 mPlasmaEnabled
= false;
1016 mSuppressUnlock
.stop(); //FIXME we might need to start the lock timer ala deactivatePlasma()
1017 //actually we could be lazy and just call deactivatePlasma() TODO check that this'll really work
1022 void LockProcess::stopPlasma()
1024 if (mPlasmaDBus
&& mPlasmaDBus
->isValid()) {
1025 mPlasmaDBus
->call(QDBus::NoBlock
, "quit");
1027 kDebug() << "cannot stop plasma-overlay";
1031 void LockProcess::newService(QString name
, QString oldOwner
, QString newOwner
)
1034 if (name
!= "org.kde.plasma-overlay") {
1039 if (newOwner
.isEmpty()) {
1040 kDebug() << "plasma ran away?";
1043 kDebug() << "I'm confused!!";
1048 kDebug() << "plasma! yaay!";
1049 mPlasmaDBus
= new org::kde::plasmaoverlay::App(name
, "/App",
1050 QDBusConnection::sessionBus(), this);
1052 //XXX this isn't actually used any more iirc
1053 connect(mPlasmaDBus
, SIGNAL(hidden()), SLOT(unSuppressUnlock()));
1055 if (!mDialogs
.isEmpty()) {
1056 //whoops, activation probably failed earlier
1057 mPlasmaDBus
->call(QDBus::NoBlock
, "activate");
1061 void LockProcess::deactivatePlasma()
1063 if (isPlasmaValid()) {
1064 mPlasmaDBus
->call(QDBus::NoBlock
, "deactivate");
1066 if (!mLocked
&& mLockGrace
>=0) {
1067 QTimer::singleShot(mLockGrace
, this, SLOT(startLock())); //this is only ok because any activity will quit
1071 void LockProcess::lockPlasma()
1073 if (isPlasmaValid()) {
1074 mPlasmaDBus
->call(QDBus::NoBlock
, "lock");
1078 void LockProcess::unSuppressUnlock()
1080 //note: suppressing unlock also now means suppressing quit-on-activity
1081 //maybe some var renaming is in order.
1082 mSuppressUnlock
.stop();
1085 void LockProcess::quit()
1087 mSuppressUnlock
.stop();
1088 if (!mLocked
|| checkPass()) {
1093 void LockProcess::suspend()
1095 if( !mSuspended
&& mHackProc
.state() == QProcess::Running
)
1097 ::kill(mHackProc
.pid(), SIGSTOP
);
1098 QApplication::syncX();
1099 mSavedScreen
= QPixmap::grabWindow( winId());
1104 void LockProcess::resume( bool force
)
1106 if( !force
&& (!mDialogs
.isEmpty() || !mVisibility
))
1107 return; // no resuming with dialog visible or when not visible
1108 if( mSuspended
&& mHackProc
.state() == QProcess::Running
)
1110 XForceScreenSaver(QX11Info::display(), ScreenSaverReset
);
1112 p
.drawPixmap( 0, 0, mSavedScreen
);
1114 mSavedScreen
= QPixmap();
1115 QApplication::syncX();
1116 ::kill(mHackProc
.pid(), SIGCONT
);
1121 //---------------------------------------------------------------------------
1123 // Show the password dialog
1124 // This is called only in the master process
1126 bool LockProcess::checkPass()
1128 killTimer(mAutoLogoutTimerId
);
1130 if (isPlasmaValid()) {
1131 mPlasmaDBus
->call(QDBus::NoBlock
, "activate");
1134 PasswordDlg
passDlg( this, &greetPlugin
);
1135 int ret
= execDialog( &passDlg
);
1137 if (isPlasmaValid()) {
1138 if (ret
== QDialog::Rejected
) {
1139 mSuppressUnlock
.start(mSuppressUnlockTimeout
);
1140 } else if (ret
== TIMEOUT_CODE
) {
1141 mPlasmaDBus
->call(QDBus::NoBlock
, "deactivate");
1145 XWindowAttributes rootAttr
;
1146 XGetWindowAttributes(QX11Info::display(), QX11Info::appRootWindow(), &rootAttr
);
1147 if(( rootAttr
.your_event_mask
& SubstructureNotifyMask
) == 0 )
1149 kWarning() << "ERROR: Something removed SubstructureNotifyMask from the root window!!!" ;
1150 XSelectInput( QX11Info::display(), QX11Info::appRootWindow(),
1151 SubstructureNotifyMask
| rootAttr
.your_event_mask
);
1154 return ret
== QDialog::Accepted
;
1157 bool LockProcess::checkPass(const QString
&reason
)
1159 PasswordDlg
passDlg(this, &greetPlugin
, reason
);
1160 int ret
= execDialog( &passDlg
);
1163 //FIXME do we need to copy&paste that SubstructureNotifyMask code above?
1164 if (ret
== QDialog::Accepted
) {
1165 //we don't quit on a custom checkpass, but we do unlock
1166 //so that the user doesn't have to type their password twice
1168 KDisplayManager().setLock(false);
1169 //FIXME while suppressUnlock *should* always be running, if it isn't
1170 //(say if someone's doing things they shouldn't with dbus) then it won't get started by this
1171 //which means that a successful unlock will never re-lock
1172 //in fact, the next bit of activity would lead to the screensaver quitting.
1173 //possible solutions:
1174 //-treat this function like activity: quit if already unlocked, ensure suppress is started
1175 //if we're locked and the dialog's rejected
1176 //-return true if already unlocked, without doing anything, same as above if locked
1177 //-let it quit, and tell people not to do such silly things :P
1183 static void fakeFocusIn( WId window
)
1185 // We have keyboard grab, so this application will
1186 // get keyboard events even without having focus.
1187 // Fake FocusIn to make Qt realize it has the active
1188 // window, so that it will correctly show cursor in the dialog.
1190 memset(&ev
, 0, sizeof(ev
));
1191 ev
.xfocus
.display
= QX11Info::display();
1192 ev
.xfocus
.type
= FocusIn
;
1193 ev
.xfocus
.window
= window
;
1194 ev
.xfocus
.mode
= NotifyNormal
;
1195 ev
.xfocus
.detail
= NotifyAncestor
;
1196 XSendEvent( QX11Info::display(), window
, False
, NoEventMask
, &ev
);
1199 bool LockProcess::eventFilter(QObject
*o
, QEvent
*e
)
1201 if (e
->type() == QEvent::Resize
) {
1202 QWidget
*w
= static_cast<QWidget
*>(o
);
1203 mFrames
.value(w
)->resize(w
->size());
1208 int LockProcess::execDialog( QDialog
*dlg
)
1210 QFrame
*winFrame
= new QFrame( dlg
);
1211 winFrame
->setFrameStyle( QFrame::WinPanel
| QFrame::Raised
);
1212 winFrame
->setLineWidth( 2 );
1214 mFrames
.insert(dlg
, winFrame
);
1215 dlg
->installEventFilter(this);
1219 QRect rect
= dlg
->geometry();
1220 rect
.moveCenter(KGlobalSettings::desktopGeometry(QCursor::pos()).center());
1221 dlg
->move( rect
.topLeft() );
1223 if (mDialogs
.isEmpty())
1226 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
1227 QCursor(Qt::ArrowCursor
).handle(), CurrentTime
);
1229 mDialogs
.prepend( dlg
);
1230 fakeFocusIn( dlg
->winId());
1231 int rt
= dlg
->exec();
1232 int pos
= mDialogs
.indexOf( dlg
);
1234 mDialogs
.remove( pos
);
1235 if( mDialogs
.isEmpty() ) {
1240 dlg
->removeEventFilter(this);
1241 mFrames
.remove(dlg
);
1246 void LockProcess::preparePopup()
1248 QWidget
*dlg
= (QWidget
*)sender();
1249 mDialogs
.prepend( dlg
);
1250 fakeFocusIn( dlg
->winId() );
1253 void LockProcess::cleanupPopup()
1255 QWidget
*dlg
= (QWidget
*)sender();
1257 int pos
= mDialogs
.indexOf( dlg
);
1258 mDialogs
.remove( pos
);
1262 void LockProcess::updateFocus()
1264 if (mDialogs
.isEmpty()) {
1265 if (mForeignInputWindows
.isEmpty()) {
1266 XChangeActivePointerGrab( QX11Info::display(), GRABEVENTS
,
1267 QCursor(Qt::BlankCursor
).handle(), CurrentTime
);
1269 fakeFocusIn(mForeignInputWindows
.first());
1272 fakeFocusIn(mDialogs
.first()->winId());
1276 //---------------------------------------------------------------------------
1280 bool LockProcess::x11Event(XEvent
*event
)
1283 switch (event
->type
)
1286 if (!mDialogs
.isEmpty() && event
->xbutton
.window
== event
->xbutton
.root
) {
1287 //kDebug() << "close" << mDialogs.first()->effectiveWinId();
1288 KDialog
*dlg
= qobject_cast
<KDialog
*>(mDialogs
.first());
1290 //kDebug() << "casting success";
1297 if (mBusy
|| !mDialogs
.isEmpty()) {
1298 //kDebug() << "busy";
1299 //FIXME shouldn't we be resetting some timers?
1303 //something happened. do we quit, ask for a password or forward it to plasma?
1304 //if we're supposed to be forwarding, we check that there's actually a plasma window up
1305 //so that the user isn't trapped if plasma crashes or is slow to load.
1306 //however, if plasma started in setup mode, we don't want to let anything happen until
1307 //it has a chance to load.
1308 //note: mSetupMode should end when we either get a winid or hit the checkPlasma timeout
1309 if (mSuppressUnlock
.isActive() && (mSetupMode
|| !mForeignInputWindows
.isEmpty())) {
1310 mSuppressUnlock
.start(); //help, help, I'm being suppressed!
1311 } else if (!mLocked
|| checkPass()) {
1314 return true; //it's better not to forward any input while quitting, right?
1316 if (mAutoLogoutTimerId
) // we need to restart the auto logout countdown
1318 killTimer(mAutoLogoutTimerId
);
1319 mAutoLogoutTimerId
= startTimer(mAutoLogoutTimeout
* 1000);
1325 case VisibilityNotify
:
1326 if( event
->xvisibility
.window
== winId())
1327 { // mVisibility == false means the screensaver is not visible at all
1328 // e.g. when switched to text console
1329 // ...or when plasma's over it non-compositely?
1330 // hey, this gives me free "suspend saver when plasma obscures it"
1331 mVisibility
= !(event
->xvisibility
.state
== VisibilityFullyObscured
);
1333 mSuspendTimer
.start(2000);
1334 kDebug() << "fully obscured";
1336 kDebug() << "not fully obscured";
1337 mSuspendTimer
.stop();
1340 if (mForeignWindows
.isEmpty() && event
->xvisibility
.state
!= VisibilityUnobscured
) {
1341 kDebug() << "no plasma; saver obscured";
1344 } else if (!mForeignWindows
.isEmpty() && event
->xvisibility
.window
== mForeignWindows
.last() &&
1345 event
->xvisibility
.state
!= VisibilityUnobscured
) {
1346 //FIXME now that we have several plasma winids this doesn't feel valid
1347 //but I don't know what to do about it!
1348 kDebug() << "plasma obscured!";
1353 case ConfigureNotify
: // from SubstructureNotifyMask on the root window
1354 if(event
->xconfigure
.event
== QX11Info::appRootWindow()) {
1355 //kDebug() << "ConfigureNotify:";
1356 //the stacking order changed, so let's change the stacking order!
1360 case MapNotify
: // from SubstructureNotifyMask on the root window
1361 if( event
->xmap
.event
== QX11Info::appRootWindow()) {
1362 kDebug() << "MapNotify:" << event
->xmap
.window
;
1363 WindowType type
= windowType(event
->xmap
.window
);
1364 //TODO get the view id here
1365 if (type
!= IgnoreWindow
) {
1366 if (mForeignWindows
.contains(event
->xmap
.window
)) {
1367 kDebug() << "uhoh! duplicate!";
1369 //ordered youngest-on-top
1370 mForeignWindows
.prepend(event
->xmap
.window
);
1372 if (type
& InputWindow
) {
1373 kDebug() << "input window";
1374 if (mForeignInputWindows
.contains(event
->xmap
.window
)) {
1375 kDebug() << "uhoh! duplicate again"; //never happens
1377 //ordered youngest-on-top
1378 mForeignInputWindows
.prepend(event
->xmap
.window
);
1379 fakeFocusIn(event
->xmap
.window
);
1381 mSetupMode
= false; //no more waiting for plasma
1388 if (event
->xmap
.event
== QX11Info::appRootWindow()) {
1389 kDebug() << "UnmapNotify:" << event
->xunmap
.window
;
1390 mForeignWindows
.removeAll(event
->xunmap
.window
);
1391 if (mForeignInputWindows
.removeAll(event
->xunmap
.window
)) {
1397 // We have grab with the grab window being the root window.
1398 // This results in key events being sent to the root window,
1399 // but they should be sent to the dialog if it's visible.
1400 // It could be solved by setFocus() call, but that would mess
1401 // the focus after this process exits.
1402 // Qt seems to be quite hard to persuade to redirect the event,
1403 // so let's simply dupe it with correct destination window,
1404 // and ignore the original one.
1405 if (!mDialogs
.isEmpty()) {
1406 if ((event
->type
== KeyPress
|| event
->type
== KeyRelease
) &&
1407 event
->xkey
.window
!= mDialogs
.first()->winId()) {
1408 //kDebug() << "forward to dialog";
1409 XEvent ev2
= *event
;
1410 ev2
.xkey
.window
= ev2
.xkey
.subwindow
= mDialogs
.first()->winId();
1411 qApp
->x11ProcessEvent( &ev2
);
1414 } else if (!mForeignInputWindows
.isEmpty()) {
1415 //when there are no dialogs, forward some events to plasma
1416 switch (event
->type
) {
1423 //kDebug() << "forward to plasma";
1424 XEvent ev2
= *event
;
1425 ev2
.xkey
.window
= ev2
.xkey
.subwindow
= mForeignInputWindows
.first();
1426 XSendEvent(QX11Info::display(), ev2
.xkey
.window
, False
, NoEventMask
, &ev2
);
1437 LockProcess::WindowType
LockProcess::windowType(WId id
)
1439 Atom tag
= XInternAtom(QX11Info::display(), "_KDE_SCREENSAVER_OVERRIDE", False
);
1442 unsigned long nitems
, remaining
;
1443 unsigned char *data
= 0;
1444 Display
*display
= QX11Info::display();
1446 int result
= XGetWindowProperty(display
, id
, tag
, 0, 1, False
, tag
, &actualType
,
1447 &actualFormat
, &nitems
, &remaining
, &data
);
1449 //kDebug() << (result == Success) << (actualType == tag);
1450 WindowType type
= IgnoreWindow
;
1451 if (result
== Success
&& actualType
== tag
) {
1452 if (nitems
!= 1 || actualFormat
!= 8) {
1453 kDebug() << "malformed property";
1455 kDebug() << "i can haz plasma window?" << data
[0];
1457 case 0: //FIXME magic numbers
1458 type
= SimpleWindow
;
1464 type
= DefaultWindow
;
1473 /* if (result != Success) {
1476 if (actualType == tag) {
1479 //managed windows will have a pesky frame we have to bypass
1480 XWindowAttributes attr;
1481 XGetWindowAttributes(display, id, &attr);
1482 if (!attr.override_redirect) {
1483 //check the real client window
1484 if (Window client = XmuClientWindow(display, id)) {
1485 result = XGetWindowProperty(display, client, tag, 0, 0, False, tag, &actualType,
1486 &actualFormat, &nitems, &remaining, &data);
1487 kDebug() << (result == Success) << (actualType == tag);
1491 return (result == Success) && (actualType == tag);
1497 void LockProcess::stayOnTop()
1499 if(!(mDialogs
.isEmpty() && mForeignWindows
.isEmpty()))
1501 // this restacking is written in a way so that
1502 // if the stacking positions actually don't change,
1503 // all restacking operations will be no-op,
1504 // and no ConfigureNotify will be generated,
1505 // thus avoiding possible infinite loops
1506 Window
* stack
= new Window
[ mDialogs
.count() + mForeignWindows
.count() + 1 ];
1508 if (!mDialogs
.isEmpty()) {
1509 XRaiseWindow( QX11Info::display(), mDialogs
.first()->winId()); // raise topmost
1510 // and stack others below it
1511 for( QVector
< QWidget
* >::ConstIterator it
= mDialogs
.begin();
1512 it
!= mDialogs
.end();
1514 stack
[ count
++ ] = (*it
)->winId();
1516 XRaiseWindow( QX11Info::display(), mForeignWindows
.first()); // raise topmost
1518 //now the plasma stuff below the dialogs
1519 foreach (const WId w
, mForeignWindows
) {
1522 //finally, the saver window
1523 stack
[ count
++ ] = winId();
1524 XRestackWindows( x11Info().display(), stack
, count
);
1525 //kDebug() << "restacked" << count;
1528 XRaiseWindow(QX11Info::display(), winId());
1532 void LockProcess::checkDPMSActive()
1537 DPMSInfo(QX11Info::display(), &state
, &on
);
1538 //kDebug() << "checkDPMSActive " << on << " " << state;
1539 if (state
== DPMSModeStandby
|| state
== DPMSModeSuspend
|| state
== DPMSModeOff
)
1542 } else if ( mSuspended
)
1549 #if defined(HAVE_XF86MISC) && defined(HAVE_XF86MISCSETGRABKEYSSTATE)
1550 // see http://cvsweb.xfree86.org/cvsweb/xc/programs/Xserver/hw/xfree86/common/xf86Events.c#rev3.113
1551 // This allows enabling the "Allow{Deactivate/Closedown}Grabs" options in XF86Config,
1552 // and krunner_lock will still lock the session.
1553 static enum { Unknown
, Yes
, No
} can_do_xf86_lock
= Unknown
;
1554 void LockProcess::lockXF86()
1556 if( can_do_xf86_lock
== Unknown
)
1559 if( XF86MiscQueryVersion( QX11Info::display(), &major
, &minor
)
1560 && (major
> 0 || minor
>= 5) )
1561 can_do_xf86_lock
= Yes
;
1563 can_do_xf86_lock
= No
;
1565 if( can_do_xf86_lock
!= Yes
)
1567 if( mRestoreXF86Lock
)
1569 if( XF86MiscSetGrabKeysState( QX11Info::display(), False
) != MiscExtGrabStateSuccess
)
1572 mRestoreXF86Lock
= true;
1575 void LockProcess::unlockXF86()
1577 if( can_do_xf86_lock
!= Yes
)
1579 if( !mRestoreXF86Lock
)
1581 XF86MiscSetGrabKeysState( QX11Info::display(), True
);
1582 mRestoreXF86Lock
= false;
1585 void LockProcess::lockXF86()
1589 void LockProcess::unlockXF86()
1594 void LockProcess::msgBox( QWidget
*parent
, QMessageBox::Icon type
, const QString
&txt
)
1596 QDialog
box( parent
, Qt::X11BypassWindowManagerHint
);
1597 box
.setModal( true );
1599 QLabel
*label1
= new QLabel( &box
);
1600 label1
->setPixmap( QMessageBox::standardIcon( type
) );
1601 QLabel
*label2
= new QLabel( txt
, &box
);
1602 KPushButton
*button
= new KPushButton( KStandardGuiItem::ok(), &box
);
1603 button
->setDefault( true );
1604 button
->setSizePolicy( QSizePolicy( QSizePolicy::Preferred
, QSizePolicy::Preferred
) );
1605 connect( button
, SIGNAL( clicked() ), &box
, SLOT( accept() ) );
1607 QGridLayout
*grid
= new QGridLayout( &box
);
1608 grid
->setSpacing( 10 );
1609 grid
->addWidget( label1
, 0, 0, Qt::AlignCenter
);
1610 grid
->addWidget( label2
, 0, 1, Qt::AlignCenter
);
1611 grid
->addWidget( button
, 1, 0, 1, 2, Qt::AlignCenter
);
1616 #include "lockprocess.moc"