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