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

583 lines
20 KiB

/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 1999 Martin R. Jones <mjones@kde.org>
Copyright (C) 2002 Luboš Luňák <l.lunak@kde.org>
Copyright (C) 2003 Oswald Buddenhagen <ossi@kde.org>
Copyright (C) 2008 Chani Armitage <chanika@gmail.com>
Copyright (C) 2011 Martin Gräßlin <mgraesslin@kde.org>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "lockwindow.h"
#include "autologout.h"
#include "ksldapp.h"
// KDE
#include <KApplication>
#include <KDebug>
// Qt
#include <QTimer>
#include <QPointer>
#include <QDesktopWidget>
#include <QPainter>
#include <QX11Info>
// X11
#include <X11/Xatom.h>
#include <fixx11h.h>
#include <xcb/xcb.h>
static Window gVRoot = 0;
static Window gVRootData = 0;
static Atom gXA_VROOT;
static Atom gXA_SCREENSAVER_VERSION;
//#define CHECK_XSELECTINPUT
#ifdef CHECK_XSELECTINPUT
#include <dlfcn.h>
static bool check_xselectinput = false;
extern "C"
int XSelectInput( Display* dpy, Window w, long e )
{
typedef int (*ptr)(Display*, Window, long);
static ptr fun = NULL;
if( fun == NULL )
fun = (ptr)dlsym( RTLD_NEXT, "XSelectInput" );
if( check_xselectinput && w == DefaultRootWindow( dpy ))
kDebug() << kBacktrace();
return fun( dpy, w, e );
}
#endif
namespace ScreenLocker
{
LockWindow::LockWindow()
: QWidget(nullptr, Qt::X11BypassWindowManagerHint)
, QAbstractNativeEventFilter()
, m_autoLogoutTimer(new QTimer(this))
{
initialize();
}
LockWindow::~LockWindow()
{
qApp->removeNativeEventFilter(this);
}
void LockWindow::initialize()
{
qApp->installNativeEventFilter(this);
XWindowAttributes rootAttr;
XGetWindowAttributes(QX11Info::display(), QX11Info::appRootWindow(), &rootAttr);
QApplication::desktop(); // make Qt set its event mask on the root window first
#ifdef CHECK_XSELECTINPUT
check_xselectinput = true;
#endif
XSelectInput( QX11Info::display(), QX11Info::appRootWindow(),
SubstructureNotifyMask | rootAttr.your_event_mask );
// Get root window size
updateGeo();
// virtual root property
gXA_VROOT = XInternAtom (QX11Info::display(), "__SWM_VROOT", False);
gXA_SCREENSAVER_VERSION = XInternAtom (QX11Info::display(), "_SCREENSAVER_VERSION", False);
// read the initial information about all toplevel windows
Window r, p;
Window* real;
unsigned nreal;
if( XQueryTree( QX11Info::display(), QX11Info::appRootWindow(), &r, &p, &real, &nreal )
&& real != NULL ) {
for( unsigned i = 0; i < nreal; ++i ) {
XWindowAttributes winAttr;
if (XGetWindowAttributes(QX11Info::display(), real[ i ], &winAttr)) {
WindowInfo info;
info.window = real[ i ];
info.viewable = ( winAttr.map_state == IsViewable );
m_windowInfo.append( info ); // ordered bottom to top
}
}
XFree( real );
}
m_autoLogoutTimer->setSingleShot(true);
connect(m_autoLogoutTimer, SIGNAL(timeout()), SLOT(autoLogoutTimeout()));
connect(QApplication::desktop(), SIGNAL(resized(int)), SLOT(updateGeo()));
connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), SLOT(updateGeo()));
}
void LockWindow::showLockWindow()
{
hide();
// Some xscreensaver hacks check for this property
const char *version = "KDE 4.0";
XChangeProperty (QX11Info::display(), winId(),
gXA_SCREENSAVER_VERSION, XA_STRING, 8, PropModeReplace,
(unsigned char *) version, strlen(version));
XSetWindowAttributes attr;
// Qt doesn't want to set our background, so let's just use black
attr.background_pixel = 0;
attr.event_mask = KeyPressMask | ButtonPressMask | PointerMotionMask |
VisibilityChangeMask | ExposureMask;
XChangeWindowAttributes(QX11Info::display(), winId(),
CWEventMask | CWBackPixel, &attr);
kDebug() << "Lock window Id: " << winId();
move(0, 0);
XSync(QX11Info::display(), False);
setVRoot( winId(), winId() );
if (KSldApp::self()->autoLogoutTimeout()) {
m_autoLogoutTimer->start(KSldApp::self()->autoLogoutTimeout());
}
}
//---------------------------------------------------------------------------
//
// Hide the screen locker window
//
void LockWindow::hideLockWindow()
{
if (m_autoLogoutTimer->isActive()) {
m_autoLogoutTimer->stop();
}
emit userActivity();
hide();
lower();
removeVRoot(winId());
XDeleteProperty(QX11Info::display(), winId(), gXA_SCREENSAVER_VERSION);
if ( gVRoot ) {
unsigned long vroot_data[1] = { gVRootData };
XChangeProperty(QX11Info::display(), gVRoot, gXA_VROOT, XA_WINDOW, 32,
PropModeReplace, (unsigned char *)vroot_data, 1);
gVRoot = 0;
}
XSync(QX11Info::display(), False);
}
//---------------------------------------------------------------------------
static int ignoreXError(Display *, XErrorEvent *)
{
return 0;
}
//---------------------------------------------------------------------------
//
// Save the current virtual root window
//
void LockWindow::saveVRoot()
{
Window rootReturn, parentReturn, *children;
unsigned int numChildren;
Window root = QX11Info::appRootWindow();
gVRoot = 0;
gVRootData = 0;
int (*oldHandler)(Display *, XErrorEvent *);
oldHandler = XSetErrorHandler(ignoreXError);
if (XQueryTree(QX11Info::display(), root, &rootReturn, &parentReturn,
&children, &numChildren))
{
for (unsigned int i = 0; i < numChildren; i++)
{
Atom actual_type;
int actual_format;
unsigned long nitems, bytesafter;
unsigned char *newRoot = 0;
if ((XGetWindowProperty(QX11Info::display(), children[i], gXA_VROOT, 0, 1,
False, XA_WINDOW, &actual_type, &actual_format, &nitems, &bytesafter,
&newRoot) == Success) && newRoot)
{
gVRoot = children[i];
Window *dummy = (Window*)newRoot;
gVRootData = *dummy;
XFree ((char*) newRoot);
break;
}
}
if (children)
{
XFree((char *)children);
}
}
XSetErrorHandler(oldHandler);
}
//---------------------------------------------------------------------------
//
// Set the virtual root property
//
void LockWindow::setVRoot(Window win, Window vr)
{
if (gVRoot)
removeVRoot(gVRoot);
unsigned long rw = QX11Info::appRootWindow();
unsigned long vroot_data[1] = { vr };
Window rootReturn, parentReturn, *children;
unsigned int numChildren;
Window top = win;
while (1) {
if (!XQueryTree(QX11Info::display(), top , &rootReturn, &parentReturn,
&children, &numChildren))
return;
if (children)
XFree((char *)children);
if (parentReturn == rw) {
break;
} else
top = parentReturn;
}
XChangeProperty(QX11Info::display(), top, gXA_VROOT, XA_WINDOW, 32,
PropModeReplace, (unsigned char *)vroot_data, 1);
}
//---------------------------------------------------------------------------
//
// Remove the virtual root property
//
void LockWindow::removeVRoot(Window win)
{
XDeleteProperty (QX11Info::display(), win, gXA_VROOT);
}
static void fakeFocusIn( WId window )
{
// We have keyboard grab, so this application will
// get keyboard events even without having focus.
// Fake FocusIn to make Qt realize it has the active
// window, so that it will correctly show cursor in the dialog.
XEvent ev;
memset(&ev, 0, sizeof(ev));
ev.xfocus.display = QX11Info::display();
ev.xfocus.type = FocusIn;
ev.xfocus.window = window;
ev.xfocus.mode = NotifyNormal;
ev.xfocus.detail = NotifyAncestor;
XSendEvent( QX11Info::display(), window, False, NoEventMask, &ev );
}
template< typename T>
void coordFromEvent(xcb_generic_event_t *event, int *x, int *y)
{
T *e = reinterpret_cast<T*>(event);
*x = e->event_x;
*y = e->event_y;
}
template<typename T>
void sendEvent(xcb_generic_event_t *event, xcb_window_t target, int x, int y)
{
T e = *(reinterpret_cast<T*>(event));
e.event = target;
e.child = target;
e.event_x = x;
e.event_y = y;
xcb_send_event(QX11Info::connection(), false, target, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char*>(&e));
}
bool LockWindow::nativeEventFilter(const QByteArray &eventType, void *message, long int *)
{
if (eventType != QByteArrayLiteral("xcb_generic_event_t")) {
return false;
}
xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t*>(message);
const uint8_t responseType = event->response_type & ~0x80;
bool ret = false;
switch (responseType) {
case XCB_BUTTON_PRESS:
case XCB_BUTTON_RELEASE:
case XCB_KEY_PRESS:
case XCB_KEY_RELEASE:
case XCB_MOTION_NOTIFY:
if (KSldApp::self()->isGraceTime()) {
KSldApp::self()->unlock();
return true;
}
if (m_autoLogoutTimer->isActive()) {
m_autoLogoutTimer->start(KSldApp::self()->autoLogoutTimeout());
}
emit userActivity();
if (!m_lockWindows.isEmpty()) {
int x = 0;
int y = 0;
if (responseType == XCB_KEY_PRESS || responseType == XCB_KEY_RELEASE) {
coordFromEvent<xcb_key_press_event_t>(event, &x, &y);
} else if (responseType == XCB_BUTTON_PRESS || responseType == XCB_BUTTON_RELEASE) {
coordFromEvent<xcb_button_press_event_t>(event, &x, &y);
} else if (responseType == XCB_MOTION_NOTIFY) {
coordFromEvent<xcb_motion_notify_event_t>(event, &x, &y);
}
Window root_return;
int x_return, y_return;
unsigned int width_return, height_return, border_width_return, depth_return;
foreach (WId window, m_lockWindows) {
if (XGetGeometry(QX11Info::display(), window, &root_return,
&x_return, &y_return,
&width_return, &height_return,
&border_width_return, &depth_return)
&&
(x>=x_return && x<=x_return+(int)width_return)
&&
(y>=y_return && y<=y_return+(int)height_return) ) {
const int targetX = x - x_return;
const int targetY = y - y_return;
if (responseType == XCB_KEY_PRESS || responseType == XCB_KEY_RELEASE) {
sendEvent<xcb_key_press_event_t>(event, window, targetX, targetY);
} else if (responseType == XCB_BUTTON_PRESS || responseType == XCB_BUTTON_RELEASE) {
sendEvent<xcb_button_press_event_t>(event, window, targetX, targetY);
} else if (responseType == XCB_MOTION_NOTIFY) {
sendEvent<xcb_motion_notify_event_t>(event, window, targetX, targetY);
}
break;
}
}
ret = true;
}
break;
case XCB_CONFIGURE_NOTIFY: { // from SubstructureNotifyMask on the root window
xcb_configure_notify_event_t *xc = reinterpret_cast<xcb_configure_notify_event_t*>(event);
if (xc->event == QX11Info::appRootWindow()) {
int index = findWindowInfo( xc->window );
if( index >= 0 ) {
int index2 = xc->above_sibling ? findWindowInfo( xc->above_sibling ) : 0;
if( index2 < 0 )
kDebug(1204) << "Unknown above for ConfigureNotify";
else { // move just above the other window
if( index2 < index )
++index2;
m_windowInfo.move( index, index2 );
}
} else
kDebug(1204) << "Unknown toplevel for ConfigureNotify";
//kDebug() << "ConfigureNotify:";
//the stacking order changed, so let's change the stacking order again to what we want
stayOnTop();
ret = true;
}
break;
}
case XCB_MAP_NOTIFY: { // from SubstructureNotifyMask on the root window
xcb_map_notify_event_t *xm = reinterpret_cast<xcb_map_notify_event_t*>(event);
if (xm->event == QX11Info::appRootWindow()) {
kDebug(1204) << "MapNotify:" << xm->window;
int index = findWindowInfo( xm->window );
if( index >= 0 )
m_windowInfo[ index ].viewable = true;
else
kDebug(1204) << "Unknown toplevel for MapNotify";
if (isLockWindow(xm->window)) {
if (m_lockWindows.contains(xm->window)) {
kDebug() << "uhoh! duplicate!";
} else {
if (!isVisible()) {
// not yet shown and we have a lock window, so we show our own window
show();
setCursor(Qt::ArrowCursor);
}
m_lockWindows.prepend(xm->window);
fakeFocusIn(xm->window);
}
}
stayOnTop();
ret = true;
}
break;
}
case XCB_UNMAP_NOTIFY: {
xcb_unmap_notify_event_t *xu = reinterpret_cast<xcb_unmap_notify_event_t*>(event);
if (xu->event == QX11Info::appRootWindow()) {
kDebug(1204) << "UnmapNotify:" << xu->window;
int index = findWindowInfo( xu->window );
if( index >= 0 )
m_windowInfo[ index ].viewable = false;
else
kDebug(1204) << "Unknown toplevel for MapNotify";
m_lockWindows.removeAll(xu->event);
ret = true;
}
break;
}
case XCB_CREATE_NOTIFY: {
xcb_create_notify_event_t *xc = reinterpret_cast<xcb_create_notify_event_t*>(event);
if (xc->parent == QX11Info::appRootWindow()) {
kDebug() << "CreateNotify:" << xc->window;
int index = findWindowInfo( xc->window );
if( index >= 0 )
kDebug() << "Already existing toplevel for CreateNotify";
else {
WindowInfo info;
info.window = xc->window;
info.viewable = false;
m_windowInfo.append( info );
}
ret = true;
}
break;
}
case XCB_DESTROY_NOTIFY: {
xcb_destroy_notify_event_t *xd = reinterpret_cast<xcb_destroy_notify_event_t *>(event);
if (xd->event == QX11Info::appRootWindow()) {
int index = findWindowInfo( xd->window );
if( index >= 0 )
m_windowInfo.removeAt( index );
else
kDebug() << "Unknown toplevel for DestroyNotify";
ret = true;
}
break;
}
case XCB_REPARENT_NOTIFY: {
xcb_reparent_notify_event_t *xr = reinterpret_cast<xcb_reparent_notify_event_t*>(event);
if (xr->event == QX11Info::appRootWindow() && xr->parent != QX11Info::appRootWindow()) {
int index = findWindowInfo( xr->window );
if( index >= 0 )
m_windowInfo.removeAt( index );
else
kDebug() << "Unknown toplevel for ReparentNotify away";
} else if (xr->parent == QX11Info::appRootWindow()) {
int index = findWindowInfo( xr->window );
if( index >= 0 )
kDebug() << "Already existing toplevel for ReparentNotify";
else {
WindowInfo info;
info.window = xr->window;
info.viewable = false;
m_windowInfo.append( info );
}
}
break;
}
case XCB_CIRCULATE_NOTIFY: {
xcb_circulate_notify_event_t *xc = reinterpret_cast<xcb_circulate_notify_event_t*>(event);
if (xc->event == QX11Info::appRootWindow()) {
int index = findWindowInfo( xc->window );
if( index >= 0 ) {
m_windowInfo.move( index, xc->place == PlaceOnTop ? m_windowInfo.size() - 1 : 0 );
} else
kDebug() << "Unknown toplevel for CirculateNotify";
}
break;
}
}
return ret;
}
int LockWindow::findWindowInfo(Window w)
{
for( int i = 0;
i < m_windowInfo.size();
++i )
if( m_windowInfo[ i ].window == w )
return i;
return -1;
}
void LockWindow::stayOnTop()
{
// this restacking is written in a way so that
// if the stacking positions actually don't change,
// all restacking operations will be no-op,
// and no ConfigureNotify will be generated,
// thus avoiding possible infinite loops
QVector< Window > stack( m_lockWindows.count() + 1 );
int count = 0;
foreach( WId w, m_lockWindows )
stack[ count++ ] = w;
// finally, the lock window
stack[ count++ ] = winId();
// do the actual restacking if needed
XRaiseWindow( QX11Info::display(), stack[ 0 ] );
if( count > 1 )
XRestackWindows( QX11Info::display(), stack.data(), count );
}
bool LockWindow::isLockWindow(Window id)
{
Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False);
Atom actualType;
int actualFormat;
unsigned long nitems, remaining;
unsigned char *data = 0;
Display *display = QX11Info::display();
int result = XGetWindowProperty(display, id, tag, 0, 1, False, tag, &actualType,
&actualFormat, &nitems, &remaining, &data);
bool lockWindow = false;
if (result == Success && actualType == tag) {
lockWindow = true;
}
if (data) {
XFree(data);
}
return lockWindow;
}
void LockWindow::autoLogoutTimeout()
{
QDesktopWidget *desktop = QApplication::desktop();
QRect screenRect;
if (desktop->screenCount() > 1) {
screenRect = desktop->screenGeometry(desktop->screenNumber(QCursor::pos()));
} else {
screenRect = desktop->screenGeometry();
}
QPointer<AutoLogout> dlg = new AutoLogout(this);
dlg->adjustSize();
QRect rect = dlg->geometry();
rect.moveCenter(screenRect.center());
dlg->move(rect.topLeft());
Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False);
XChangeProperty(QX11Info::display(), dlg->winId(), tag, tag, 32, PropModeReplace, 0, 0);
dlg->exec();
delete dlg;
// start the timer again - only if the window is still shown
if (isVisible()) {
m_autoLogoutTimer->start(KSldApp::self()->autoLogoutTimeout());
}
}
void LockWindow::updateGeo()
{
QDesktopWidget *desktop = QApplication::desktop();
setGeometry(desktop->geometry());
}
void LockWindow::paintEvent(QPaintEvent* )
{
QPainter p(this);
p.setBrush(QBrush(Qt::black));
p.drawRect(geometry());
}
}