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 }