File indexing completed on 2024-11-10 04:56:36

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