File indexing completed on 2024-04-28 13:21:12
0001 /* 0002 SPDX-FileCopyrightText: 1999 Martin R. Jones <mjones@kde.org> 0003 SPDX-FileCopyrightText: 2002 Luboš Luňák <l.lunak@kde.org> 0004 SPDX-FileCopyrightText: 2003 Oswald Buddenhagen <ossi@kde.org> 0005 SPDX-FileCopyrightText: 2008 Chani Armitage <chanika@gmail.com> 0006 SPDX-FileCopyrightText: 2011 Martin Gräßlin <mgraesslin@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 #include "x11locker.h" 0011 #include "globalaccel.h" 0012 // KDE 0013 // Qt 0014 #include <QApplication> 0015 #include <QScreen> 0016 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0017 #include <private/qtx11extras_p.h> 0018 #else 0019 #include <QDesktopWidget> 0020 #include <QX11Info> 0021 #endif 0022 // X11 0023 #include <X11/Xatom.h> 0024 #include <xcb/xcb.h> 0025 0026 #include <kscreenlocker_logging.h> 0027 0028 static Window gVRoot = 0; 0029 static Window gVRootData = 0; 0030 static Atom gXA_VROOT; 0031 static Atom gXA_SCREENSAVER_VERSION; 0032 0033 namespace ScreenLocker 0034 { 0035 X11Locker::X11Locker(QObject *parent) 0036 : AbstractLocker(parent) 0037 , QAbstractNativeEventFilter() 0038 , m_focusedLockWindow(XCB_WINDOW_NONE) 0039 { 0040 initialize(); 0041 } 0042 0043 X11Locker::~X11Locker() 0044 { 0045 qApp->removeNativeEventFilter(this); 0046 } 0047 0048 void X11Locker::initialize() 0049 { 0050 qApp->installNativeEventFilter(this); 0051 0052 XWindowAttributes rootAttr; 0053 XGetWindowAttributes(QX11Info::display(), QX11Info::appRootWindow(), &rootAttr); 0054 XSelectInput(QX11Info::display(), QX11Info::appRootWindow(), SubstructureNotifyMask | rootAttr.your_event_mask); 0055 // Get root window size 0056 updateGeo(); 0057 0058 // virtual root property 0059 gXA_VROOT = XInternAtom(QX11Info::display(), "__SWM_VROOT", False); 0060 gXA_SCREENSAVER_VERSION = XInternAtom(QX11Info::display(), "_SCREENSAVER_VERSION", False); 0061 0062 // read the initial information about all toplevel windows 0063 Window r, p; 0064 Window *real; 0065 unsigned nreal; 0066 if (XQueryTree(QX11Info::display(), QX11Info::appRootWindow(), &r, &p, &real, &nreal) && real != nullptr) { 0067 for (unsigned i = 0; i < nreal; ++i) { 0068 XWindowAttributes winAttr; 0069 if (XGetWindowAttributes(QX11Info::display(), real[i], &winAttr)) { 0070 WindowInfo info; 0071 info.window = real[i]; 0072 info.viewable = (winAttr.map_state == IsViewable); 0073 m_windowInfo.append(info); // ordered bottom to top 0074 } 0075 } 0076 XFree(real); 0077 } 0078 0079 // monitor for screen geometry changes 0080 connect(qGuiApp, &QGuiApplication::screenAdded, this, [this](QScreen *screen) { 0081 connect(screen, &QScreen::geometryChanged, this, &X11Locker::updateGeo); 0082 updateGeo(); 0083 }); 0084 connect(qGuiApp, &QGuiApplication::screenRemoved, this, &X11Locker::updateGeo); 0085 const auto screens = QGuiApplication::screens(); 0086 for (auto *screen : screens) { 0087 connect(screen, &QScreen::geometryChanged, this, &X11Locker::updateGeo); 0088 } 0089 } 0090 0091 void X11Locker::showLockWindow() 0092 { 0093 m_background->hide(); 0094 0095 // Some xscreensaver hacks check for this property 0096 const char *version = "KDE 4.0"; 0097 0098 XChangeProperty(QX11Info::display(), 0099 m_background->winId(), 0100 gXA_SCREENSAVER_VERSION, 0101 XA_STRING, 0102 8, 0103 PropModeReplace, 0104 (unsigned char *)version, 0105 strlen(version)); 0106 0107 qCDebug(KSCREENLOCKER) << "Lock window Id: " << m_background->winId(); 0108 0109 m_background->setPosition(0, 0); 0110 XSync(QX11Info::display(), False); 0111 0112 setVRoot(m_background->winId(), m_background->winId()); 0113 } 0114 0115 //--------------------------------------------------------------------------- 0116 // 0117 // Hide the screen locker window 0118 // 0119 void X11Locker::hideLockWindow() 0120 { 0121 Q_EMIT userActivity(); 0122 m_background->hide(); 0123 m_background->lower(); 0124 removeVRoot(m_background->winId()); 0125 XDeleteProperty(QX11Info::display(), m_background->winId(), gXA_SCREENSAVER_VERSION); 0126 if (gVRoot) { 0127 unsigned long vroot_data[1] = {gVRootData}; 0128 XChangeProperty(QX11Info::display(), gVRoot, gXA_VROOT, XA_WINDOW, 32, PropModeReplace, (unsigned char *)vroot_data, 1); 0129 gVRoot = 0; 0130 } 0131 XSync(QX11Info::display(), False); 0132 m_allowedWindows.clear(); 0133 } 0134 0135 //--------------------------------------------------------------------------- 0136 static int ignoreXError(Display *, XErrorEvent *) 0137 { 0138 return 0; 0139 } 0140 0141 //--------------------------------------------------------------------------- 0142 // 0143 // Save the current virtual root window 0144 // 0145 void X11Locker::saveVRoot() 0146 { 0147 Window rootReturn, parentReturn, *children; 0148 unsigned int numChildren; 0149 Window root = QX11Info::appRootWindow(); 0150 0151 gVRoot = 0; 0152 gVRootData = 0; 0153 0154 int (*oldHandler)(Display *, XErrorEvent *); 0155 oldHandler = XSetErrorHandler(ignoreXError); 0156 0157 if (XQueryTree(QX11Info::display(), root, &rootReturn, &parentReturn, &children, &numChildren)) { 0158 for (unsigned int i = 0; i < numChildren; i++) { 0159 Atom actual_type; 0160 int actual_format; 0161 unsigned long nitems, bytesafter; 0162 unsigned char *newRoot = nullptr; 0163 0164 if ((XGetWindowProperty(QX11Info::display(), 0165 children[i], 0166 gXA_VROOT, 0167 0, 0168 1, 0169 False, 0170 XA_WINDOW, 0171 &actual_type, 0172 &actual_format, 0173 &nitems, 0174 &bytesafter, 0175 &newRoot) 0176 == Success) 0177 && newRoot) { 0178 gVRoot = children[i]; 0179 Window *dummy = (Window *)newRoot; 0180 gVRootData = *dummy; 0181 XFree((char *)newRoot); 0182 break; 0183 } 0184 } 0185 if (children) { 0186 XFree((char *)children); 0187 } 0188 } 0189 0190 XSetErrorHandler(oldHandler); 0191 } 0192 0193 //--------------------------------------------------------------------------- 0194 // 0195 // Set the virtual root property 0196 // 0197 void X11Locker::setVRoot(Window win, Window vr) 0198 { 0199 if (gVRoot) { 0200 removeVRoot(gVRoot); 0201 } 0202 0203 unsigned long rw = QX11Info::appRootWindow(); 0204 unsigned long vroot_data[1] = {vr}; 0205 0206 Window rootReturn, parentReturn, *children; 0207 unsigned int numChildren; 0208 Window top = win; 0209 while (1) { 0210 if (!XQueryTree(QX11Info::display(), top, &rootReturn, &parentReturn, &children, &numChildren)) { 0211 return; 0212 } 0213 if (children) { 0214 XFree((char *)children); 0215 } 0216 if (parentReturn == rw) { 0217 break; 0218 } else { 0219 top = parentReturn; 0220 } 0221 } 0222 0223 XChangeProperty(QX11Info::display(), top, gXA_VROOT, XA_WINDOW, 32, PropModeReplace, (unsigned char *)vroot_data, 1); 0224 } 0225 0226 //--------------------------------------------------------------------------- 0227 // 0228 // Remove the virtual root property 0229 // 0230 void X11Locker::removeVRoot(Window win) 0231 { 0232 XDeleteProperty(QX11Info::display(), win, gXA_VROOT); 0233 } 0234 0235 void X11Locker::fakeFocusIn(WId window) 0236 { 0237 if (window == m_focusedLockWindow) { 0238 return; 0239 } 0240 0241 // We have keyboard grab, so this application will 0242 // get keyboard events even without having focus. 0243 // Fake FocusIn to make Qt realize it has the active 0244 // window, so that it will correctly show cursor in the dialog. 0245 XEvent ev; 0246 memset(&ev, 0, sizeof(ev)); 0247 ev.xfocus.display = QX11Info::display(); 0248 ev.xfocus.type = FocusIn; 0249 ev.xfocus.window = window; 0250 ev.xfocus.mode = NotifyNormal; 0251 ev.xfocus.detail = NotifyAncestor; 0252 XSendEvent(QX11Info::display(), window, False, NoEventMask, &ev); 0253 XFlush(QX11Info::display()); 0254 0255 m_focusedLockWindow = window; 0256 } 0257 0258 template<typename T> 0259 void coordFromEvent(xcb_generic_event_t *event, int *x, int *y) 0260 { 0261 T *e = reinterpret_cast<T *>(event); 0262 *x = e->event_x; 0263 *y = e->event_y; 0264 } 0265 0266 template<typename T> 0267 void sendEvent(xcb_generic_event_t *event, xcb_window_t target, int x, int y) 0268 { 0269 T e = *(reinterpret_cast<T *>(event)); 0270 e.event = target; 0271 e.child = target; 0272 e.event_x = x; 0273 e.event_y = y; 0274 xcb_send_event(QX11Info::connection(), false, target, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&e)); 0275 } 0276 0277 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0278 bool X11Locker::nativeEventFilter(const QByteArray &eventType, void *message, long int *) 0279 #else 0280 bool X11Locker::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *) 0281 #endif 0282 { 0283 if (eventType != QByteArrayLiteral("xcb_generic_event_t")) { 0284 return false; 0285 } 0286 xcb_generic_event_t *event = reinterpret_cast<xcb_generic_event_t *>(message); 0287 const uint8_t responseType = event->response_type & ~0x80; 0288 if (globalAccel() && responseType == XCB_KEY_PRESS) { 0289 if (globalAccel()->checkKeyPress(reinterpret_cast<xcb_key_press_event_t *>(event))) { 0290 Q_EMIT userActivity(); 0291 return true; 0292 } 0293 } 0294 bool ret = false; 0295 switch (responseType) { 0296 case XCB_BUTTON_PRESS: 0297 case XCB_BUTTON_RELEASE: 0298 case XCB_KEY_PRESS: 0299 case XCB_KEY_RELEASE: 0300 case XCB_MOTION_NOTIFY: 0301 Q_EMIT userActivity(); 0302 if (!m_lockWindows.isEmpty()) { 0303 int x = 0; 0304 int y = 0; 0305 if (responseType == XCB_KEY_PRESS || responseType == XCB_KEY_RELEASE) { 0306 coordFromEvent<xcb_key_press_event_t>(event, &x, &y); 0307 } else if (responseType == XCB_BUTTON_PRESS || responseType == XCB_BUTTON_RELEASE) { 0308 coordFromEvent<xcb_button_press_event_t>(event, &x, &y); 0309 } else if (responseType == XCB_MOTION_NOTIFY) { 0310 coordFromEvent<xcb_motion_notify_event_t>(event, &x, &y); 0311 } 0312 Window root_return; 0313 int x_return, y_return; 0314 unsigned int width_return, height_return, border_width_return, depth_return; 0315 for (WId window : qAsConst(m_lockWindows)) { 0316 if (XGetGeometry(QX11Info::display(), 0317 window, 0318 &root_return, 0319 &x_return, 0320 &y_return, 0321 &width_return, 0322 &height_return, 0323 &border_width_return, 0324 &depth_return) 0325 && (x >= x_return && x <= x_return + (int)width_return) && (y >= y_return && y <= y_return + (int)height_return)) { 0326 // We need to do our own focus handling (see comment in fakeFocusIn). 0327 // For now: Focus on clicks inside the window 0328 if (responseType == XCB_BUTTON_PRESS) { 0329 fakeFocusIn(window); 0330 } 0331 const int targetX = x - x_return; 0332 const int targetY = y - y_return; 0333 if (responseType == XCB_KEY_PRESS || responseType == XCB_KEY_RELEASE) { 0334 sendEvent<xcb_key_press_event_t>(event, window, targetX, targetY); 0335 } else if (responseType == XCB_BUTTON_PRESS || responseType == XCB_BUTTON_RELEASE) { 0336 sendEvent<xcb_button_press_event_t>(event, window, targetX, targetY); 0337 } else if (responseType == XCB_MOTION_NOTIFY) { 0338 sendEvent<xcb_motion_notify_event_t>(event, window, targetX, targetY); 0339 } 0340 break; 0341 } 0342 } 0343 ret = true; 0344 } 0345 break; 0346 case XCB_CONFIGURE_NOTIFY: { // from SubstructureNotifyMask on the root window 0347 xcb_configure_notify_event_t *xc = reinterpret_cast<xcb_configure_notify_event_t *>(event); 0348 if (xc->event == QX11Info::appRootWindow()) { 0349 int index = findWindowInfo(xc->window); 0350 if (index >= 0) { 0351 int index2 = xc->above_sibling ? findWindowInfo(xc->above_sibling) : 0; 0352 if (index2 < 0) { 0353 qCDebug(KSCREENLOCKER) << "Unknown above for ConfigureNotify"; 0354 } else { // move just above the other window 0355 if (index2 < index) { 0356 ++index2; 0357 } 0358 m_windowInfo.move(index, index2); 0359 } 0360 } else { 0361 qCDebug(KSCREENLOCKER) << "Unknown toplevel for ConfigureNotify"; 0362 } 0363 // kDebug() << "ConfigureNotify:"; 0364 // the stacking order changed, so let's change the stacking order again to what we want 0365 stayOnTop(); 0366 ret = true; 0367 } 0368 break; 0369 } 0370 case XCB_MAP_NOTIFY: { // from SubstructureNotifyMask on the root window 0371 xcb_map_notify_event_t *xm = reinterpret_cast<xcb_map_notify_event_t *>(event); 0372 if (xm->event == QX11Info::appRootWindow()) { 0373 qCDebug(KSCREENLOCKER) << "MapNotify:" << xm->window; 0374 int index = findWindowInfo(xm->window); 0375 if (index >= 0) { 0376 m_windowInfo[index].viewable = true; 0377 } else { 0378 qCDebug(KSCREENLOCKER) << "Unknown toplevel for MapNotify"; 0379 } 0380 if (m_allowedWindows.contains(xm->window)) { 0381 if (m_lockWindows.contains(xm->window)) { 0382 qCDebug(KSCREENLOCKER) << "uhoh! duplicate!"; 0383 } else { 0384 if (!m_background->isVisible()) { 0385 // not yet shown and we have a lock window, so we show our own window 0386 m_background->show(); 0387 } 0388 m_lockWindows.prepend(xm->window); 0389 fakeFocusIn(xm->window); 0390 } 0391 } 0392 if (xm->window == m_background->winId()) { 0393 m_background->update(); 0394 Q_EMIT lockWindowShown(); 0395 return false; 0396 } 0397 stayOnTop(); 0398 ret = true; 0399 } 0400 break; 0401 } 0402 case XCB_UNMAP_NOTIFY: { 0403 xcb_unmap_notify_event_t *xu = reinterpret_cast<xcb_unmap_notify_event_t *>(event); 0404 if (xu->event == QX11Info::appRootWindow()) { 0405 qCDebug(KSCREENLOCKER) << "UnmapNotify:" << xu->window; 0406 int index = findWindowInfo(xu->window); 0407 if (index >= 0) { 0408 m_windowInfo[index].viewable = false; 0409 } else { 0410 qCDebug(KSCREENLOCKER) << "Unknown toplevel for MapNotify"; 0411 } 0412 m_lockWindows.removeAll(xu->window); 0413 if (m_focusedLockWindow == xu->event && !m_lockWindows.empty()) { 0414 // The currently focused window vanished, just focus the first one in the list 0415 fakeFocusIn(m_lockWindows[0]); 0416 } 0417 ret = true; 0418 } 0419 break; 0420 } 0421 case XCB_CREATE_NOTIFY: { 0422 xcb_create_notify_event_t *xc = reinterpret_cast<xcb_create_notify_event_t *>(event); 0423 if (xc->parent == QX11Info::appRootWindow()) { 0424 qCDebug(KSCREENLOCKER) << "CreateNotify:" << xc->window; 0425 int index = findWindowInfo(xc->window); 0426 if (index >= 0) { 0427 qCDebug(KSCREENLOCKER) << "Already existing toplevel for CreateNotify"; 0428 } else { 0429 WindowInfo info; 0430 info.window = xc->window; 0431 info.viewable = false; 0432 m_windowInfo.append(info); 0433 } 0434 ret = true; 0435 } 0436 break; 0437 } 0438 case XCB_DESTROY_NOTIFY: { 0439 xcb_destroy_notify_event_t *xd = reinterpret_cast<xcb_destroy_notify_event_t *>(event); 0440 if (xd->event == QX11Info::appRootWindow()) { 0441 int index = findWindowInfo(xd->window); 0442 if (index >= 0) { 0443 m_windowInfo.removeAt(index); 0444 } else { 0445 qCDebug(KSCREENLOCKER) << "Unknown toplevel for DestroyNotify"; 0446 } 0447 ret = true; 0448 } 0449 break; 0450 } 0451 case XCB_REPARENT_NOTIFY: { 0452 xcb_reparent_notify_event_t *xr = reinterpret_cast<xcb_reparent_notify_event_t *>(event); 0453 if (xr->event == QX11Info::appRootWindow() && xr->parent != QX11Info::appRootWindow()) { 0454 int index = findWindowInfo(xr->window); 0455 if (index >= 0) { 0456 m_windowInfo.removeAt(index); 0457 } else { 0458 qCDebug(KSCREENLOCKER) << "Unknown toplevel for ReparentNotify away"; 0459 } 0460 } else if (xr->parent == QX11Info::appRootWindow()) { 0461 int index = findWindowInfo(xr->window); 0462 if (index >= 0) { 0463 qCDebug(KSCREENLOCKER) << "Already existing toplevel for ReparentNotify"; 0464 } else { 0465 WindowInfo info; 0466 info.window = xr->window; 0467 info.viewable = false; 0468 m_windowInfo.append(info); 0469 } 0470 } 0471 break; 0472 } 0473 case XCB_CIRCULATE_NOTIFY: { 0474 xcb_circulate_notify_event_t *xc = reinterpret_cast<xcb_circulate_notify_event_t *>(event); 0475 if (xc->event == QX11Info::appRootWindow()) { 0476 int index = findWindowInfo(xc->window); 0477 if (index >= 0) { 0478 m_windowInfo.move(index, xc->place == PlaceOnTop ? m_windowInfo.size() - 1 : 0); 0479 } else { 0480 qCDebug(KSCREENLOCKER) << "Unknown toplevel for CirculateNotify"; 0481 } 0482 } 0483 break; 0484 } 0485 } 0486 return ret; 0487 } 0488 0489 int X11Locker::findWindowInfo(Window w) 0490 { 0491 for (int i = 0; i < m_windowInfo.size(); ++i) { 0492 if (m_windowInfo[i].window == w) { 0493 return i; 0494 } 0495 } 0496 return -1; 0497 } 0498 0499 void X11Locker::stayOnTop() 0500 { 0501 // this restacking is written in a way so that 0502 // if the stacking positions actually don't change, 0503 // all restacking operations will be no-op, 0504 // and no ConfigureNotify will be generated, 0505 // thus avoiding possible infinite loops 0506 QVector<Window> stack(m_lockWindows.count() + 1); 0507 int count = 0; 0508 for (WId w : qAsConst(m_lockWindows)) { 0509 stack[count++] = w; 0510 } 0511 // finally, the lock window 0512 stack[count++] = m_background->winId(); 0513 // do the actual restacking if needed 0514 XRaiseWindow(QX11Info::display(), stack[0]); 0515 if (count > 1) { 0516 XRestackWindows(QX11Info::display(), stack.data(), count); 0517 } 0518 XFlush(QX11Info::display()); 0519 } 0520 0521 void X11Locker::updateGeo() 0522 { 0523 QRect geometry; 0524 const auto screens = QGuiApplication::screens(); 0525 for (auto *screen : screens) { 0526 geometry |= screen->geometry(); 0527 } 0528 m_background->setGeometry(geometry); 0529 m_background->update(); 0530 } 0531 0532 void X11Locker::addAllowedWindow(quint32 window) 0533 { 0534 m_allowedWindows << window; 0535 // test whether it's to show 0536 const int index = findWindowInfo(window); 0537 if (index == -1 || !m_windowInfo[index].viewable) { 0538 return; 0539 } 0540 if (m_lockWindows.contains(window)) { 0541 qCDebug(KSCREENLOCKER) << "uhoh! duplicate!"; 0542 } else { 0543 if (!m_background->isVisible()) { 0544 // not yet shown and we have a lock window, so we show our own window 0545 m_background->show(); 0546 } 0547 0548 if (m_lockWindows.empty()) { 0549 // Make sure to focus the first window 0550 m_focusedLockWindow = XCB_WINDOW_NONE; 0551 fakeFocusIn(window); 0552 } 0553 0554 m_lockWindows.prepend(window); 0555 stayOnTop(); 0556 } 0557 } 0558 0559 }