File indexing completed on 2024-11-24 05:00:47

0001 /*
0002     SPDX-FileCopyrightText: 2013 Alexander Mezin <mezin.alexander@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "xrecordkeyboardmonitor.h"
0008 #include "c_ptr.h"
0009 
0010 #include <cstdlib>
0011 #include <limits>
0012 #include <memory>
0013 
0014 #include <X11/Xlib.h>
0015 #include <xcb/xcbext.h>
0016 
0017 XRecordKeyboardMonitor::XRecordKeyboardMonitor(Display *display)
0018     : m_connection(xcb_connect(XDisplayString(display), nullptr))
0019     , m_modifiersPressed(0)
0020     , m_keysPressed(0)
0021 {
0022     if (!m_connection) {
0023         return;
0024     }
0025 
0026     xcb_get_modifier_mapping_cookie_t modmapCookie = xcb_get_modifier_mapping(m_connection);
0027 
0028     m_context = xcb_generate_id(m_connection);
0029     xcb_record_range_t range;
0030     memset(&range, 0, sizeof(range));
0031     range.device_events.first = XCB_KEY_PRESS;
0032     range.device_events.last = XCB_KEY_RELEASE;
0033     xcb_record_client_spec_t cs = XCB_RECORD_CS_ALL_CLIENTS;
0034     xcb_record_create_context(m_connection, m_context, 0, 1, 1, &cs, &range);
0035     xcb_flush(m_connection);
0036 
0037     std::unique_ptr<xcb_get_modifier_mapping_reply_t, CDeleter> modmap(xcb_get_modifier_mapping_reply(m_connection, modmapCookie, nullptr));
0038     if (!modmap) {
0039         return;
0040     }
0041 
0042     int nModifiers = xcb_get_modifier_mapping_keycodes_length(modmap.get());
0043     xcb_keycode_t *modifiers = xcb_get_modifier_mapping_keycodes(modmap.get());
0044     m_modifier.fill(false, std::numeric_limits<xcb_keycode_t>::max() + 1);
0045     for (xcb_keycode_t *i = modifiers; i < modifiers + nModifiers; i++) {
0046         m_modifier[*i] = true;
0047     }
0048     m_ignore.fill(false, std::numeric_limits<xcb_keycode_t>::max() + 1);
0049     for (xcb_keycode_t *i = modifiers; i < modifiers + modmap->keycodes_per_modifier; i++) {
0050         m_ignore[*i] = true;
0051     }
0052     m_pressed.fill(false, std::numeric_limits<xcb_keycode_t>::max() + 1);
0053 
0054     m_cookie = xcb_record_enable_context(m_connection, m_context);
0055     xcb_flush(m_connection);
0056 
0057     m_notifier = new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this);
0058     connect(m_notifier, &QSocketNotifier::activated, this, &XRecordKeyboardMonitor::processNextReply);
0059     m_notifier->setEnabled(true);
0060 }
0061 
0062 XRecordKeyboardMonitor::~XRecordKeyboardMonitor()
0063 {
0064     if (!m_connection) {
0065         return;
0066     }
0067 
0068     xcb_record_disable_context(m_connection, m_context);
0069     xcb_record_free_context(m_connection, m_context);
0070     xcb_disconnect(m_connection);
0071 }
0072 
0073 void XRecordKeyboardMonitor::processNextReply()
0074 {
0075     xcb_generic_event_t *event;
0076     while ((event = xcb_poll_for_event(m_connection))) {
0077         std::free(event);
0078     }
0079 
0080     void *reply = nullptr;
0081     xcb_generic_error_t *error = nullptr;
0082     while (m_cookie.sequence && xcb_poll_for_reply(m_connection, m_cookie.sequence, &reply, &error)) {
0083         // xcb_poll_for_reply may set both reply and error to null if connection has error.
0084         // break if xcb_connection has error, no point to continue anyway.
0085         if (xcb_connection_has_error(m_connection)) {
0086             break;
0087         }
0088 
0089         if (error) {
0090             std::free(error);
0091             break;
0092         }
0093 
0094         if (!reply) {
0095             continue;
0096         }
0097 
0098         std::unique_ptr<xcb_record_enable_context_reply_t, CDeleter> data(reinterpret_cast<xcb_record_enable_context_reply_t *>(reply));
0099         process(data.get());
0100     }
0101 }
0102 
0103 void XRecordKeyboardMonitor::process(xcb_record_enable_context_reply_t *reply)
0104 {
0105     bool prevActivity = activity();
0106 
0107     xcb_key_press_event_t *events = reinterpret_cast<xcb_key_press_event_t *>(xcb_record_enable_context_data(reply));
0108     int nEvents = xcb_record_enable_context_data_length(reply) / sizeof(xcb_key_press_event_t);
0109     bool wasActivity = prevActivity;
0110     for (xcb_key_press_event_t *e = events; e < events + nEvents; e++) {
0111         if (e->response_type != XCB_KEY_PRESS && e->response_type != XCB_KEY_RELEASE) {
0112             continue;
0113         }
0114 
0115         if (m_ignore[e->detail]) {
0116             continue;
0117         }
0118 
0119         bool pressed = (e->response_type == XCB_KEY_PRESS);
0120         if (m_pressed[e->detail] == pressed) {
0121             continue;
0122         }
0123         m_pressed[e->detail] = pressed;
0124 
0125         int &counter = m_modifier[e->detail] ? m_modifiersPressed : m_keysPressed;
0126         if (pressed) {
0127             counter++;
0128         } else {
0129             counter--;
0130         }
0131 
0132         wasActivity = wasActivity || activity();
0133     }
0134 
0135     if (!prevActivity && activity()) {
0136         Q_EMIT keyboardActivityStarted();
0137     } else if (!activity() && wasActivity) {
0138         Q_EMIT keyboardActivityFinished();
0139     }
0140 }