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 }