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