File indexing completed on 2025-04-27 11:33:01

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0006     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0007     SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "x11_standalone_windowselector.h"
0012 #include "cursor.h"
0013 #include "unmanaged.h"
0014 #include "utils/xcbutils.h"
0015 #include "workspace.h"
0016 #include "x11window.h"
0017 // XLib
0018 #include <X11/Xutil.h>
0019 #include <X11/cursorfont.h>
0020 #include <fixx11h.h>
0021 // XCB
0022 #include <xcb/xcb_keysyms.h>
0023 
0024 namespace KWin
0025 {
0026 
0027 WindowSelector::WindowSelector()
0028     : X11EventFilter(QVector<int>{
0029         XCB_BUTTON_PRESS,
0030         XCB_BUTTON_RELEASE,
0031         XCB_MOTION_NOTIFY,
0032         XCB_ENTER_NOTIFY,
0033         XCB_LEAVE_NOTIFY,
0034         XCB_KEY_PRESS,
0035         XCB_KEY_RELEASE,
0036         XCB_FOCUS_IN,
0037         XCB_FOCUS_OUT,
0038     })
0039     , m_active(false)
0040 {
0041 }
0042 
0043 WindowSelector::~WindowSelector()
0044 {
0045 }
0046 
0047 void WindowSelector::start(std::function<void(KWin::Window *)> callback, const QByteArray &cursorName)
0048 {
0049     if (m_active) {
0050         callback(nullptr);
0051         return;
0052     }
0053 
0054     m_active = activate(cursorName);
0055     if (!m_active) {
0056         callback(nullptr);
0057         return;
0058     }
0059     m_callback = callback;
0060 }
0061 
0062 void WindowSelector::start(std::function<void(const QPoint &)> callback)
0063 {
0064     if (m_active) {
0065         callback(QPoint(-1, -1));
0066         return;
0067     }
0068 
0069     m_active = activate();
0070     if (!m_active) {
0071         callback(QPoint(-1, -1));
0072         return;
0073     }
0074     m_pointSelectionFallback = callback;
0075 }
0076 
0077 bool WindowSelector::activate(const QByteArray &cursorName)
0078 {
0079     xcb_cursor_t cursor = createCursor(cursorName);
0080 
0081     xcb_connection_t *c = connection();
0082     UniqueCPtr<xcb_grab_pointer_reply_t> grabPointer(xcb_grab_pointer_reply(c, xcb_grab_pointer_unchecked(c, false, rootWindow(), XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW, XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_WINDOW_NONE, cursor, XCB_TIME_CURRENT_TIME), nullptr));
0083     if (!grabPointer || grabPointer->status != XCB_GRAB_STATUS_SUCCESS) {
0084         return false;
0085     }
0086     const bool grabbed = grabXKeyboard();
0087     if (grabbed) {
0088         grabXServer();
0089     } else {
0090         xcb_ungrab_pointer(connection(), XCB_TIME_CURRENT_TIME);
0091     }
0092     return grabbed;
0093 }
0094 
0095 xcb_cursor_t WindowSelector::createCursor(const QByteArray &cursorName)
0096 {
0097     if (cursorName.isEmpty()) {
0098         return Cursors::self()->mouse()->x11Cursor(Qt::CrossCursor);
0099     }
0100     xcb_cursor_t cursor = Cursors::self()->mouse()->x11Cursor(cursorName);
0101     if (cursor != XCB_CURSOR_NONE) {
0102         return cursor;
0103     }
0104     if (cursorName == QByteArrayLiteral("pirate")) {
0105         // special handling for font pirate cursor
0106         static xcb_cursor_t kill_cursor = XCB_CURSOR_NONE;
0107         if (kill_cursor != XCB_CURSOR_NONE) {
0108             return kill_cursor;
0109         }
0110         // fallback on font
0111         xcb_connection_t *c = connection();
0112         const xcb_font_t cursorFont = xcb_generate_id(c);
0113         xcb_open_font(c, cursorFont, strlen("cursor"), "cursor");
0114         cursor = xcb_generate_id(c);
0115         xcb_create_glyph_cursor(c, cursor, cursorFont, cursorFont,
0116                                 XC_pirate, /* source character glyph */
0117                                 XC_pirate + 1, /* mask character glyph */
0118                                 0, 0, 0, 0, 0, 0); /* r b g r b g */
0119         kill_cursor = cursor;
0120     }
0121     return cursor;
0122 }
0123 
0124 void WindowSelector::processEvent(xcb_generic_event_t *event)
0125 {
0126     if (event->response_type == XCB_BUTTON_RELEASE) {
0127         xcb_button_release_event_t *buttonEvent = reinterpret_cast<xcb_button_release_event_t *>(event);
0128         handleButtonRelease(buttonEvent->detail, buttonEvent->child);
0129     } else if (event->response_type == XCB_KEY_PRESS) {
0130         xcb_key_press_event_t *keyEvent = reinterpret_cast<xcb_key_press_event_t *>(event);
0131         handleKeyPress(keyEvent->detail, keyEvent->state);
0132     }
0133 }
0134 
0135 bool WindowSelector::event(xcb_generic_event_t *event)
0136 {
0137     if (!m_active) {
0138         return false;
0139     }
0140     processEvent(event);
0141 
0142     return true;
0143 }
0144 
0145 void WindowSelector::handleButtonRelease(xcb_button_t button, xcb_window_t window)
0146 {
0147     if (button == XCB_BUTTON_INDEX_3) {
0148         cancelCallback();
0149         release();
0150         return;
0151     }
0152     if (button == XCB_BUTTON_INDEX_1 || button == XCB_BUTTON_INDEX_2) {
0153         if (m_callback) {
0154             selectWindowId(window);
0155         } else if (m_pointSelectionFallback) {
0156             m_pointSelectionFallback(Cursors::self()->mouse()->pos());
0157         }
0158         release();
0159         return;
0160     }
0161 }
0162 
0163 void WindowSelector::handleKeyPress(xcb_keycode_t keycode, uint16_t state)
0164 {
0165     xcb_key_symbols_t *symbols = xcb_key_symbols_alloc(connection());
0166     xcb_keysym_t kc = xcb_key_symbols_get_keysym(symbols, keycode, 0);
0167     int mx = 0;
0168     int my = 0;
0169     const bool returnPressed = (kc == XK_Return) || (kc == XK_space);
0170     const bool escapePressed = (kc == XK_Escape);
0171     if (kc == XK_Left) {
0172         mx = -10;
0173     }
0174     if (kc == XK_Right) {
0175         mx = 10;
0176     }
0177     if (kc == XK_Up) {
0178         my = -10;
0179     }
0180     if (kc == XK_Down) {
0181         my = 10;
0182     }
0183     if (state & XCB_MOD_MASK_CONTROL) {
0184         mx /= 10;
0185         my /= 10;
0186     }
0187     Cursors::self()->mouse()->setPos(Cursors::self()->mouse()->pos() + QPoint(mx, my));
0188     if (returnPressed) {
0189         if (m_callback) {
0190             selectWindowUnderPointer();
0191         } else if (m_pointSelectionFallback) {
0192             m_pointSelectionFallback(Cursors::self()->mouse()->pos());
0193         }
0194     }
0195     if (returnPressed || escapePressed) {
0196         if (escapePressed) {
0197             cancelCallback();
0198         }
0199         release();
0200     }
0201     xcb_key_symbols_free(symbols);
0202 }
0203 
0204 void WindowSelector::selectWindowUnderPointer()
0205 {
0206     Xcb::Pointer pointer(rootWindow());
0207     if (!pointer.isNull() && pointer->child != XCB_WINDOW_NONE) {
0208         selectWindowId(pointer->child);
0209     }
0210 }
0211 
0212 void WindowSelector::release()
0213 {
0214     ungrabXKeyboard();
0215     xcb_ungrab_pointer(connection(), XCB_TIME_CURRENT_TIME);
0216     ungrabXServer();
0217     m_active = false;
0218     m_callback = std::function<void(KWin::Window *)>();
0219     m_pointSelectionFallback = std::function<void(const QPoint &)>();
0220 }
0221 
0222 void WindowSelector::selectWindowId(xcb_window_t window_to_select)
0223 {
0224     if (window_to_select == XCB_WINDOW_NONE) {
0225         m_callback(nullptr);
0226         return;
0227     }
0228     xcb_window_t window = window_to_select;
0229     X11Window *client = nullptr;
0230     while (true) {
0231         client = Workspace::self()->findClient(Predicate::FrameIdMatch, window);
0232         if (client) {
0233             break; // Found the client
0234         }
0235         Xcb::Tree tree(window);
0236         if (window == tree->root) {
0237             // We didn't find the client, probably an override-redirect window
0238             break;
0239         }
0240         window = tree->parent; // Go up
0241     }
0242     if (client) {
0243         m_callback(client);
0244     } else {
0245         m_callback(Workspace::self()->findUnmanaged(window_to_select));
0246     }
0247 }
0248 
0249 void WindowSelector::cancelCallback()
0250 {
0251     if (m_callback) {
0252         m_callback(nullptr);
0253     } else if (m_pointSelectionFallback) {
0254         m_pointSelectionFallback(QPoint(-1, -1));
0255     }
0256 }
0257 
0258 } // namespace