File indexing completed on 2024-05-05 17:45:24
0001 /* 0002 Registers as a embed container 0003 SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org> 0004 SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com> 0005 0006 SPDX-License-Identifier: LGPL-2.1-or-later 0007 */ 0008 #include "fdoselectionmanager.h" 0009 0010 #include "debug.h" 0011 0012 #include <QCoreApplication> 0013 #include <QTimer> 0014 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0015 #include <private/qtx11extras_p.h> 0016 #else 0017 #include <QX11Info> 0018 #endif 0019 0020 #include <KSelectionOwner> 0021 0022 #include <xcb/composite.h> 0023 #include <xcb/damage.h> 0024 #include <xcb/xcb_atom.h> 0025 #include <xcb/xcb_event.h> 0026 0027 #include "../c_ptr.h" 0028 #include "sniproxy.h" 0029 #include "xcbutils.h" 0030 0031 #define SYSTEM_TRAY_REQUEST_DOCK 0 0032 #define SYSTEM_TRAY_BEGIN_MESSAGE 1 0033 #define SYSTEM_TRAY_CANCEL_MESSAGE 2 0034 0035 FdoSelectionManager::FdoSelectionManager() 0036 : QObject() 0037 , m_selectionOwner(new KSelectionOwner(Xcb::atoms->selectionAtom, -1, this)) 0038 { 0039 qCDebug(SNIPROXY) << "starting"; 0040 0041 // we may end up calling QCoreApplication::quit() in this method, at which point we need the event loop running 0042 QTimer::singleShot(0, this, &FdoSelectionManager::init); 0043 } 0044 0045 FdoSelectionManager::~FdoSelectionManager() 0046 { 0047 qCDebug(SNIPROXY) << "closing"; 0048 m_selectionOwner->release(); 0049 } 0050 0051 void FdoSelectionManager::init() 0052 { 0053 // load damage extension 0054 xcb_connection_t *c = QX11Info::connection(); 0055 xcb_prefetch_extension_data(c, &xcb_damage_id); 0056 const auto *reply = xcb_get_extension_data(c, &xcb_damage_id); 0057 if (reply && reply->present) { 0058 m_damageEventBase = reply->first_event; 0059 xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION); 0060 } else { 0061 // no XDamage means 0062 qCCritical(SNIPROXY) << "could not load damage extension. Quitting"; 0063 qApp->exit(-1); 0064 } 0065 0066 qApp->installNativeEventFilter(this); 0067 0068 connect(m_selectionOwner, &KSelectionOwner::claimedOwnership, this, &FdoSelectionManager::onClaimedOwnership); 0069 connect(m_selectionOwner, &KSelectionOwner::failedToClaimOwnership, this, &FdoSelectionManager::onFailedToClaimOwnership); 0070 connect(m_selectionOwner, &KSelectionOwner::lostOwnership, this, &FdoSelectionManager::onLostOwnership); 0071 m_selectionOwner->claim(false); 0072 } 0073 0074 bool FdoSelectionManager::addDamageWatch(xcb_window_t client) 0075 { 0076 qCDebug(SNIPROXY) << "adding damage watch for " << client; 0077 0078 xcb_connection_t *c = QX11Info::connection(); 0079 const auto attribsCookie = xcb_get_window_attributes_unchecked(c, client); 0080 0081 const auto damageId = xcb_generate_id(c); 0082 m_damageWatches[client] = damageId; 0083 xcb_damage_create(c, damageId, client, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); 0084 0085 xcb_generic_error_t *error = nullptr; 0086 UniqueCPointer<xcb_get_window_attributes_reply_t> attr(xcb_get_window_attributes_reply(c, attribsCookie, &error)); 0087 UniqueCPointer<xcb_generic_error_t> getAttrError(error); 0088 uint32_t events = XCB_EVENT_MASK_STRUCTURE_NOTIFY; 0089 if (attr) { 0090 events = events | attr->your_event_mask; 0091 } 0092 // if window is already gone, there is no need to handle it. 0093 if (getAttrError && getAttrError->error_code == XCB_WINDOW) { 0094 return false; 0095 } 0096 // the event mask will not be removed again. We cannot track whether another component also needs STRUCTURE_NOTIFY (e.g. KWindowSystem). 0097 // if we would remove the event mask again, other areas will break. 0098 const auto changeAttrCookie = xcb_change_window_attributes_checked(c, client, XCB_CW_EVENT_MASK, &events); 0099 UniqueCPointer<xcb_generic_error_t> changeAttrError(xcb_request_check(c, changeAttrCookie)); 0100 // if window is gone by this point, it will be caught by eventFilter, so no need to check later errors. 0101 if (changeAttrError && changeAttrError->error_code == XCB_WINDOW) { 0102 return false; 0103 } 0104 0105 return true; 0106 } 0107 0108 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0109 bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *message, long int *result) 0110 #else 0111 bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) 0112 #endif 0113 { 0114 Q_UNUSED(result) 0115 0116 if (eventType != "xcb_generic_event_t") { 0117 return false; 0118 } 0119 0120 xcb_generic_event_t *ev = static_cast<xcb_generic_event_t *>(message); 0121 0122 const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); 0123 if (responseType == XCB_CLIENT_MESSAGE) { 0124 const auto ce = reinterpret_cast<xcb_client_message_event_t *>(ev); 0125 if (ce->type == Xcb::atoms->opcodeAtom) { 0126 switch (ce->data.data32[1]) { 0127 case SYSTEM_TRAY_REQUEST_DOCK: 0128 dock(ce->data.data32[2]); 0129 return true; 0130 } 0131 } 0132 } else if (responseType == XCB_UNMAP_NOTIFY) { 0133 const auto unmappedWId = reinterpret_cast<xcb_unmap_notify_event_t *>(ev)->window; 0134 if (m_proxies.contains(unmappedWId)) { 0135 undock(unmappedWId); 0136 } 0137 } else if (responseType == XCB_DESTROY_NOTIFY) { 0138 const auto destroyedWId = reinterpret_cast<xcb_destroy_notify_event_t *>(ev)->window; 0139 if (m_proxies.contains(destroyedWId)) { 0140 undock(destroyedWId); 0141 } 0142 } else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) { 0143 const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable; 0144 const auto sniProxy = m_proxies.value(damagedWId); 0145 if (sniProxy) { 0146 sniProxy->update(); 0147 xcb_damage_subtract(QX11Info::connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE); 0148 } 0149 } else if (responseType == XCB_CONFIGURE_REQUEST) { 0150 const auto event = reinterpret_cast<xcb_configure_request_event_t *>(ev); 0151 const auto sniProxy = m_proxies.value(event->window); 0152 if (sniProxy) { 0153 // The embedded window tries to move or resize. Ignore move, handle resize only. 0154 if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { 0155 sniProxy->resizeWindow(event->width, event->height); 0156 } 0157 } 0158 } else if (responseType == XCB_VISIBILITY_NOTIFY) { 0159 const auto event = reinterpret_cast<xcb_visibility_notify_event_t *>(ev); 0160 // it's possible that something showed our container window, we have to hide it 0161 // workaround for BUG 357443: when KWin is restarted, container window is shown on top 0162 if (event->state == XCB_VISIBILITY_UNOBSCURED) { 0163 for (auto sniProxy : m_proxies.values()) { 0164 sniProxy->hideContainerWindow(event->window); 0165 } 0166 } 0167 } 0168 0169 return false; 0170 } 0171 0172 void FdoSelectionManager::dock(xcb_window_t winId) 0173 { 0174 qCDebug(SNIPROXY) << "trying to dock window " << winId; 0175 0176 if (m_proxies.contains(winId)) { 0177 return; 0178 } 0179 0180 if (addDamageWatch(winId)) { 0181 m_proxies[winId] = new SNIProxy(winId, this); 0182 } 0183 } 0184 0185 void FdoSelectionManager::undock(xcb_window_t winId) 0186 { 0187 qCDebug(SNIPROXY) << "trying to undock window " << winId; 0188 0189 if (!m_proxies.contains(winId)) { 0190 return; 0191 } 0192 m_proxies[winId]->deleteLater(); 0193 m_proxies.remove(winId); 0194 } 0195 0196 void FdoSelectionManager::onClaimedOwnership() 0197 { 0198 qCDebug(SNIPROXY) << "Manager selection claimed"; 0199 0200 setSystemTrayVisual(); 0201 } 0202 0203 void FdoSelectionManager::onFailedToClaimOwnership() 0204 { 0205 qCWarning(SNIPROXY) << "failed to claim ownership of Systray Manager"; 0206 qApp->exit(-1); 0207 } 0208 0209 void FdoSelectionManager::onLostOwnership() 0210 { 0211 qCWarning(SNIPROXY) << "lost ownership of Systray Manager"; 0212 qApp->exit(-1); 0213 } 0214 0215 void FdoSelectionManager::setSystemTrayVisual() 0216 { 0217 xcb_connection_t *c = QX11Info::connection(); 0218 auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; 0219 auto trayVisual = screen->root_visual; 0220 xcb_depth_iterator_t depth_iterator = xcb_screen_allowed_depths_iterator(screen); 0221 xcb_depth_t *depth = nullptr; 0222 0223 while (depth_iterator.rem) { 0224 if (depth_iterator.data->depth == 32) { 0225 depth = depth_iterator.data; 0226 break; 0227 } 0228 xcb_depth_next(&depth_iterator); 0229 } 0230 0231 if (depth) { 0232 xcb_visualtype_iterator_t visualtype_iterator = xcb_depth_visuals_iterator(depth); 0233 while (visualtype_iterator.rem) { 0234 xcb_visualtype_t *visualtype = visualtype_iterator.data; 0235 if (visualtype->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { 0236 trayVisual = visualtype->visual_id; 0237 break; 0238 } 0239 xcb_visualtype_next(&visualtype_iterator); 0240 } 0241 } 0242 0243 xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_selectionOwner->ownerWindow(), Xcb::atoms->visualAtom, XCB_ATOM_VISUALID, 32, 1, &trayVisual); 0244 }