File indexing completed on 2024-12-29 05:17:37
0001 /* 0002 * Copyright 2023 David Edmundson <davidedmundson@kde.org> 0003 * 0004 * SPDX-License-Identifier: LicenseRef-KDE-Accepted-GPL 0005 * SPDX-FileCopyrightText: 2023 David Edmundson <kde@davidedmundson.co.uk> 0006 * SPDX-FileCopyrightText: 2023 Aleix Pol <aleixpol@kde.org> 0007 */ 0008 0009 #include "x11recordingnotifier.h" 0010 0011 #include <cstdio> 0012 #include <cstdlib> 0013 #include <xcb/xcb.h> 0014 #include <xcb/xproto.h> 0015 #include <xcb/composite.h> 0016 #include <xcb/xcb_event.h> 0017 #include <xcb/xcbext.h> 0018 0019 #include <xcb/record.h> 0020 #include <cstring> 0021 0022 #include <QScopedPointer> 0023 #include <QDebug> 0024 #include <QSocketNotifier> 0025 #include <QScopeGuard> 0026 0027 struct XCBResponse 0028 { 0029 ~XCBResponse(); 0030 0031 xcb_record_enable_context_reply_t *reply = nullptr; 0032 xcb_generic_error_t *error = nullptr; 0033 }; 0034 0035 XCBResponse::~XCBResponse() { 0036 std::free(this->reply); 0037 std::free(this->error); 0038 0039 this->reply = nullptr; 0040 this->error = nullptr; 0041 } 0042 0043 X11RecordingNotifier::X11RecordingNotifier(WId window, QObject *parent) 0044 : QObject(parent) 0045 , m_windowId(window) 0046 { 0047 // we use a separate connection as the X11 recording API blocks is super weird 0048 // and we get multiple replies to a request rather than events 0049 m_connection = xcb_connect(nullptr, nullptr); 0050 auto c = m_connection; 0051 xcb_generic_error_t *error; 0052 0053 if (!c) { 0054 qWarning("Error to open local display. Auto activation will fail!\n"); 0055 return; 0056 } 0057 0058 int compositeExtensionOpCode = -1; 0059 { 0060 xcb_query_extension_cookie_t cookie = xcb_query_extension(c, strlen("Composite"), "Composite"); 0061 QScopedPointer<xcb_query_extension_reply_t, QScopedPointerPodDeleter> reply(xcb_query_extension_reply(c, cookie, nullptr)); 0062 compositeExtensionOpCode = reply->major_opcode; 0063 } 0064 0065 // check the xcb_record extension exists 0066 { 0067 auto cookie = xcb_record_query_version(c, 0, 0); 0068 QScopedPointer<xcb_record_query_version_reply_t, QScopedPointerPodDeleter> reply(xcb_record_query_version_reply(c, cookie, &error)); 0069 if (!reply) { 0070 qWarning() << ("Failed to create recording context"); 0071 } else { 0072 } 0073 } 0074 0075 xcb_record_range_t range = {}; 0076 range.ext_requests.major.first = compositeExtensionOpCode; 0077 range.ext_requests.major.last = compositeExtensionOpCode; 0078 range.ext_requests.minor.first = XCB_COMPOSITE_REDIRECT_WINDOW; 0079 range.ext_requests.minor.last = XCB_COMPOSITE_UNREDIRECT_SUBWINDOWS; 0080 range.client_died = true; 0081 xcb_record_client_spec_t spec = XCB_RECORD_CS_ALL_CLIENTS; 0082 0083 m_recordingContext = xcb_generate_id(c); 0084 auto cookie = xcb_record_create_context_checked(c, m_recordingContext, 0, 1, 1, &spec, &range); 0085 auto err = xcb_request_check(c, cookie); 0086 if (err) { 0087 qWarning() << ("Failed to create recording context"); 0088 } 0089 0090 auto enableCookie = xcb_record_enable_context(c, m_recordingContext).sequence; 0091 xcb_flush(c); 0092 0093 auto notifier = new QSocketNotifier(xcb_get_file_descriptor(c), QSocketNotifier::Read, this); 0094 connect(notifier, &QSocketNotifier::activated, this, [this, enableCookie] { 0095 xcb_generic_event_t *event = nullptr; 0096 auto c = m_connection; 0097 while ((event = xcb_poll_for_event(m_connection))) { 0098 std::free(event); 0099 } 0100 0101 XCBResponse record; 0102 while (enableCookie && xcb_poll_for_reply(c, enableCookie, (void **)&record.reply, &record.error)) { 0103 // xcb_poll_for_reply may set both reply and error to null if connection has error. 0104 // break if xcb_connection has error, no point to continue anyway. 0105 if (xcb_connection_has_error(c)) { 0106 break; 0107 } 0108 0109 if (record.error) { 0110 break; 0111 } 0112 0113 if (!record.reply) { 0114 continue; 0115 } 0116 0117 handleNewRecord(*record.reply); 0118 record = XCBResponse(); 0119 } 0120 }); 0121 } 0122 0123 X11RecordingNotifier::~X11RecordingNotifier() 0124 { 0125 if (m_recordingContext) { 0126 xcb_record_free_context(m_connection, m_recordingContext); 0127 } 0128 if (m_connection) { 0129 xcb_disconnect(m_connection); 0130 } 0131 } 0132 0133 bool X11RecordingNotifier::isRedirected() const 0134 { 0135 return !m_redirectionCount.isEmpty(); 0136 } 0137 0138 void X11RecordingNotifier::handleNewRecord(xcb_record_enable_context_reply_t &reply) 0139 { 0140 const bool wasRedirected = isRedirected(); 0141 auto cleanup = qScopeGuard([wasRedirected, this] { 0142 if (isRedirected() != wasRedirected) { 0143 Q_EMIT isRedirectedChanged(); 0144 } 0145 }); 0146 0147 if (reply.category == 3) { 0148 m_redirectionCount.remove(reply.xid_base); 0149 return; 0150 } 0151 0152 if (reply.category != 1) { 0153 return; 0154 } 0155 0156 xcb_composite_redirect_window_request_t *request = reinterpret_cast<xcb_composite_redirect_window_request_t *>(xcb_record_enable_context_data(&reply)); 0157 0158 if (!request) { 0159 return; 0160 } 0161 0162 if (request->window != m_windowId) { 0163 return; 0164 } 0165 0166 uint32_t caller = reply.xid_base; 0167 switch(request->minor_opcode) { 0168 case XCB_COMPOSITE_REDIRECT_WINDOW: 0169 case XCB_COMPOSITE_REDIRECT_SUBWINDOWS: 0170 m_redirectionCount[caller]++; 0171 break; 0172 case XCB_COMPOSITE_UNREDIRECT_WINDOW: 0173 case XCB_COMPOSITE_UNREDIRECT_SUBWINDOWS: 0174 if (m_redirectionCount[caller]-- == 0) { 0175 m_redirectionCount.remove(caller); 0176 } 0177 break; 0178 default: 0179 break; 0180 } 0181 }