|
| 1 | +/* |
| 2 | + Registers as a embed container |
| 3 | + SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org> |
| 4 | + SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com> |
| 5 | + SPDX-FileCopyrightText: 2025 Wang Zichong <wangzichong@deepin.org> |
| 6 | +
|
| 7 | + SPDX-License-Identifier: LGPL-2.1-or-later |
| 8 | +*/ |
| 9 | +#include "fdoselectionmanager.h" |
| 10 | + |
| 11 | +#include <QTimer> |
| 12 | +#include <QDBusConnection> |
| 13 | +#include <QLoggingCategory> |
| 14 | + |
| 15 | +#include <KSelectionOwner> |
| 16 | + |
| 17 | +#include <xcb/composite.h> |
| 18 | +#include <xcb/damage.h> |
| 19 | +#include <xcb/xcb_atom.h> |
| 20 | +#include <xcb/xcb_event.h> |
| 21 | + |
| 22 | +#include "traymanager1.h" |
| 23 | +#include "c_ptr.h" |
| 24 | +#include "util.h" |
| 25 | + |
| 26 | +using Util = tray::Util; |
| 27 | + |
| 28 | +#define SYSTEM_TRAY_REQUEST_DOCK 0 |
| 29 | +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 |
| 30 | +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 |
| 31 | + |
| 32 | +Q_LOGGING_CATEGORY(SELECTIONMGR, "org.deepin.dde.trayloader.selectionmgr") |
| 33 | + |
| 34 | +FdoSelectionManager::FdoSelectionManager(QObject *parent) |
| 35 | + : QObject(parent) |
| 36 | + , m_selectionOwner(new KSelectionOwner(UTIL->getAtomFromDisplay("_NET_SYSTEM_TRAY"), UTIL->getX11Connection(), UTIL->getRootWindow(), this)) |
| 37 | +{ |
| 38 | + qDebug(SELECTIONMGR) << "starting"; |
| 39 | + |
| 40 | + // we may end up calling QCoreApplication::quit() in this method, at which point we need the event loop running |
| 41 | + QTimer::singleShot(0, this, &FdoSelectionManager::init); |
| 42 | +} |
| 43 | + |
| 44 | +FdoSelectionManager::~FdoSelectionManager() |
| 45 | +{ |
| 46 | + qCDebug(SELECTIONMGR) << "closing"; |
| 47 | + m_selectionOwner->release(); |
| 48 | +} |
| 49 | + |
| 50 | +void FdoSelectionManager::init() |
| 51 | +{ |
| 52 | + // load damage extension |
| 53 | + xcb_connection_t *c = Util::instance()->getX11Connection(); |
| 54 | + xcb_prefetch_extension_data(c, &xcb_damage_id); |
| 55 | + const auto *reply = xcb_get_extension_data(c, &xcb_damage_id); |
| 56 | + if (reply && reply->present) { |
| 57 | + m_damageEventBase = reply->first_event; |
| 58 | + xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION); |
| 59 | + } else { |
| 60 | + // no XDamage means |
| 61 | + qCCritical(SELECTIONMGR) << "could not load damage extension."; |
| 62 | + } |
| 63 | + |
| 64 | + qApp->installNativeEventFilter(this); |
| 65 | + |
| 66 | + connect(m_selectionOwner, &KSelectionOwner::claimedOwnership, this, &FdoSelectionManager::onClaimedOwnership); |
| 67 | + connect(m_selectionOwner, &KSelectionOwner::failedToClaimOwnership, this, &FdoSelectionManager::onFailedToClaimOwnership); |
| 68 | + connect(m_selectionOwner, &KSelectionOwner::lostOwnership, this, &FdoSelectionManager::onLostOwnership); |
| 69 | + m_selectionOwner->claim(true); |
| 70 | + |
| 71 | + connect(m_trayManager, &TrayManager1::reclainRequested, this, [this](){ |
| 72 | + m_selectionOwner->claim(true); |
| 73 | + }); |
| 74 | +} |
| 75 | + |
| 76 | +bool FdoSelectionManager::addDamageWatch(xcb_window_t client) |
| 77 | +{ |
| 78 | + qCDebug(SELECTIONMGR) << "adding damage watch for " << client; |
| 79 | + |
| 80 | + xcb_connection_t *c = Util::instance()->getX11Connection(); |
| 81 | + const auto attribsCookie = xcb_get_window_attributes_unchecked(c, client); |
| 82 | + |
| 83 | + const auto damageId = xcb_generate_id(c); |
| 84 | + m_damageWatches[client] = damageId; |
| 85 | + xcb_damage_create(c, damageId, client, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); |
| 86 | + |
| 87 | + xcb_generic_error_t *error = nullptr; |
| 88 | + UniqueCPointer<xcb_get_window_attributes_reply_t> attr(xcb_get_window_attributes_reply(c, attribsCookie, &error)); |
| 89 | + UniqueCPointer<xcb_generic_error_t> getAttrError(error); |
| 90 | + uint32_t events = XCB_EVENT_MASK_STRUCTURE_NOTIFY; |
| 91 | + if (attr) { |
| 92 | + events = events | attr->your_event_mask; |
| 93 | + } |
| 94 | + // if window is already gone, there is no need to handle it. |
| 95 | + if (getAttrError && getAttrError->error_code == XCB_WINDOW) { |
| 96 | + return false; |
| 97 | + } |
| 98 | + // the event mask will not be removed again. We cannot track whether another component also needs STRUCTURE_NOTIFY (e.g. KWindowSystem). |
| 99 | + // if we would remove the event mask again, other areas will break. |
| 100 | + const auto changeAttrCookie = xcb_change_window_attributes_checked(c, client, XCB_CW_EVENT_MASK, &events); |
| 101 | + UniqueCPointer<xcb_generic_error_t> changeAttrError(xcb_request_check(c, changeAttrCookie)); |
| 102 | + // if window is gone by this point, it will be caught by eventFilter, so no need to check later errors. |
| 103 | + if (changeAttrError && changeAttrError->error_code == XCB_WINDOW) { |
| 104 | + return false; |
| 105 | + } |
| 106 | + |
| 107 | + return true; |
| 108 | +} |
| 109 | + |
| 110 | +bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) |
| 111 | +{ |
| 112 | + Q_UNUSED(result) |
| 113 | + |
| 114 | + if (eventType != "xcb_generic_event_t") { |
| 115 | + return false; |
| 116 | + } |
| 117 | + |
| 118 | + auto *ev = static_cast<xcb_generic_event_t *>(message); |
| 119 | + |
| 120 | + const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev); |
| 121 | + if (responseType == XCB_CLIENT_MESSAGE) { |
| 122 | + const auto ce = reinterpret_cast<xcb_client_message_event_t *>(ev); |
| 123 | + if (ce->type == UTIL->getAtomByName("_NET_SYSTEM_TRAY_OPCODE")) { |
| 124 | + switch (ce->data.data32[1]) { |
| 125 | + case SYSTEM_TRAY_REQUEST_DOCK: |
| 126 | + dock(ce->data.data32[2]); |
| 127 | + return true; |
| 128 | + } |
| 129 | + } |
| 130 | + } else if (responseType == XCB_UNMAP_NOTIFY) { |
| 131 | + // const auto unmappedWId = reinterpret_cast<xcb_unmap_notify_event_t *>(ev)->window; |
| 132 | + // if (m_proxies.contains(unmappedWId)) { |
| 133 | + // undock(unmappedWId); |
| 134 | + // } |
| 135 | + } else if (responseType == XCB_DESTROY_NOTIFY) { |
| 136 | + const auto destroyedWId = reinterpret_cast<xcb_destroy_notify_event_t *>(ev)->window; |
| 137 | + if (m_trayManager->haveIcon(destroyedWId)) { |
| 138 | + undock(destroyedWId); |
| 139 | + } |
| 140 | + } else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) { |
| 141 | + const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable; |
| 142 | + m_trayManager->notifyIconChanged(damagedWId); |
| 143 | + } else if (responseType == XCB_CONFIGURE_REQUEST) { |
| 144 | + // const auto event = reinterpret_cast<xcb_configure_request_event_t *>(ev); |
| 145 | + // const auto tmProxy = m_proxies.value(event->window); |
| 146 | + // if (tmProxy) { |
| 147 | + // // The embedded window tries to move or resize. Ignore move, handle resize only. |
| 148 | + // if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) { |
| 149 | + // tmProxy->resizeWindow(event->width, event->height); |
| 150 | + // } |
| 151 | + // } |
| 152 | + } |
| 153 | + |
| 154 | + return false; |
| 155 | +} |
| 156 | + |
| 157 | +void FdoSelectionManager::dock(xcb_window_t winId) |
| 158 | +{ |
| 159 | + Q_CHECK_PTR(m_trayManager); |
| 160 | + qCDebug(SELECTIONMGR) << "trying to dock window " << winId; |
| 161 | + |
| 162 | + if (m_trayManager->haveIcon(winId)) { |
| 163 | + return; |
| 164 | + } |
| 165 | + |
| 166 | + if (addDamageWatch(winId)) { |
| 167 | + // Register with TrayManager1 if available |
| 168 | + m_trayManager->registerIcon(winId); |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +void FdoSelectionManager::undock(xcb_window_t winId) |
| 173 | +{ |
| 174 | + Q_CHECK_PTR(m_trayManager); |
| 175 | + qCDebug(SELECTIONMGR) << "trying to undock window " << winId; |
| 176 | + |
| 177 | + if (m_trayManager->haveIcon(winId)) { |
| 178 | + return; |
| 179 | + } |
| 180 | + |
| 181 | + // Unregister from TrayManager1 if available |
| 182 | + m_trayManager->unregisterIcon(winId); |
| 183 | + |
| 184 | + // m_proxies[winId]->deleteLater(); |
| 185 | + // m_proxies.remove(winId); |
| 186 | +} |
| 187 | + |
| 188 | +void FdoSelectionManager::onClaimedOwnership() |
| 189 | +{ |
| 190 | + qCDebug(SELECTIONMGR) << "Manager selection claimed"; |
| 191 | + |
| 192 | + initTrayManager(); |
| 193 | + setSystemTrayVisual(); |
| 194 | +} |
| 195 | + |
| 196 | +void FdoSelectionManager::onFailedToClaimOwnership() |
| 197 | +{ |
| 198 | + qCWarning(SELECTIONMGR) << "failed to claim ownership of Systray Manager"; |
| 199 | +} |
| 200 | + |
| 201 | +void FdoSelectionManager::onLostOwnership() |
| 202 | +{ |
| 203 | + qCWarning(SELECTIONMGR) << "lost ownership of Systray Manager"; |
| 204 | +} |
| 205 | + |
| 206 | +void FdoSelectionManager::setSystemTrayVisual() |
| 207 | +{ |
| 208 | + xcb_connection_t *c = Util::instance()->getX11Connection(); |
| 209 | + auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data; |
| 210 | + auto trayVisual = screen->root_visual; |
| 211 | + xcb_depth_iterator_t depth_iterator = xcb_screen_allowed_depths_iterator(screen); |
| 212 | + xcb_depth_t *depth = nullptr; |
| 213 | + |
| 214 | + while (depth_iterator.rem) { |
| 215 | + if (depth_iterator.data->depth == 32) { |
| 216 | + depth = depth_iterator.data; |
| 217 | + break; |
| 218 | + } |
| 219 | + xcb_depth_next(&depth_iterator); |
| 220 | + } |
| 221 | + |
| 222 | + if (depth) { |
| 223 | + xcb_visualtype_iterator_t visualtype_iterator = xcb_depth_visuals_iterator(depth); |
| 224 | + while (visualtype_iterator.rem) { |
| 225 | + xcb_visualtype_t *visualtype = visualtype_iterator.data; |
| 226 | + if (visualtype->_class == XCB_VISUAL_CLASS_TRUE_COLOR) { |
| 227 | + trayVisual = visualtype->visual_id; |
| 228 | + break; |
| 229 | + } |
| 230 | + xcb_visualtype_next(&visualtype_iterator); |
| 231 | + } |
| 232 | + } |
| 233 | + |
| 234 | + xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_selectionOwner->ownerWindow(), UTIL->getAtomByName("_NET_SYSTEM_TRAY_VISUAL"), XCB_ATOM_VISUALID, 32, 1, &trayVisual); |
| 235 | +} |
| 236 | + |
| 237 | +void FdoSelectionManager::initTrayManager() |
| 238 | +{ |
| 239 | + // Create and register the TrayManager1 DBus interface |
| 240 | + if (!m_trayManager) { |
| 241 | + m_trayManager = new TrayManager1(this); |
| 242 | + |
| 243 | + // Export the object on DBus |
| 244 | + QDBusConnection::sessionBus().registerObject( |
| 245 | + QStringLiteral("/org/deepin/dde/TrayManager1"), |
| 246 | + m_trayManager, |
| 247 | + QDBusConnection::ExportAdaptors |
| 248 | + ); |
| 249 | + |
| 250 | + // Request the service name |
| 251 | + QDBusConnection::sessionBus().registerService( |
| 252 | + QStringLiteral("org.deepin.dde.TrayManager1") |
| 253 | + ); |
| 254 | + |
| 255 | + qCDebug(SELECTIONMGR) << "TrayManager1 DBus interface registered"; |
| 256 | + } |
| 257 | +} |
| 258 | + |
0 commit comments