Skip to content

Conversation

@BLumia
Copy link
Member

@BLumia BLumia commented Dec 25, 2025

此提交也是对后续 Tray SelectionOwner 支持相关提交的预先准备。

Log:

Summary by Sourcery

Replace the custom XCB event thread with Qt’s native event filtering and integrate X11 event dispatch into the main event loop for the tray subsystem.

Enhancements:

  • Make Util a QObject and wire XCB file descriptor into Qt’s event loop using QSocketNotifier and event dispatcher hooks.
  • Introduce a centralized X11 event dispatch helper and remove the dedicated XcbThread implementation.
  • Add a native event filter to XembedProtocol to handle specific X11 events (e.g. leave notify) through Qt’s nativeEventFilter API.
  • Add a helper to query X11 atoms from the display and introduce categorized logging for tray utilities.

此提交也是对后续 Tray SelectionOwner 支持相关提交的预先准备。

Log:
@BLumia BLumia requested a review from tsic404 December 25, 2025 08:16
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 25, 2025

Reviewer's Guide

Route X11 tray handling through Qt’s native event filter instead of a custom XCB thread by making the tray Util a QObject that integrates the XCB connection into the Qt event loop and having XembedProtocol implement QAbstractNativeEventFilter for relevant X11 events, while cleaning up the old thread-based implementation and improving logging and atom utilities.

Sequence diagram for X11 tray events via Qt native event filter

sequenceDiagram
    participant X11 as X11_Server
    participant XCB as xcb_connection_t
    participant Util as Util
    participant QSN as QSocketNotifier
    participant QED as QAbstractEventDispatcher
    participant XEP as XembedProtocol

    X11->>XCB: X11 event available
    XCB-->>Util: Readable on X11 fd
    Util->>QSN: QSocketNotifier activated
    QSN->>Util: activated(fd)
    Util->>Util: dispatchEvents(Poll)
    loop dispatch all pending events
        Util->>XCB: xcb_poll_for_event
        XCB-->>Util: xcb_generic_event_t*
        Util->>QED: filterNativeEvent("xcb_generic_event_t", event)
        alt XembedProtocol handles event
            QED->>XEP: nativeEventFilter(eventType, message, result)
            XEP->>XEP: handle leave_notify
            XEP->>Util: setX11WindowInputShape(window, QSize(0,0))
        else other filters or default handling
            QED->>QED: other native event processing
        end
        Util->>Util: free(event)
    end
    Util->>XCB: xcb_flush

    QED->>Util: aboutToBlock()
    Util->>Util: dispatchEvents(EventQueue)

    QED->>Util: awake()
    Util->>Util: dispatchEvents(EventQueue)
Loading

Class diagram for updated Util and XembedProtocol

classDiagram
    class QObject
    class QAbstractNativeEventFilter
    class AbstractTrayProtocol

    class Util {
        +static Util* instance()
        +xcb_atom_t getAtomByName(QString name)
        +QString getNameByAtom(xcb_atom_t atom)
        +xcb_atom_t getAtomFromDisplay(const char* name)
        +void moveX11Window(xcb_window_t window, uint32_t x, uint32_t y)
        +void setX11WindowSize(xcb_window_t window, QSize size)
        +void setX11WindowInputShape(xcb_window_t window, QSize size)
        +xcb_connection_t* getX11Connection()
        +_XDisplay* getDisplay()
        +xcb_ewmh_connection_t* getEwmhConnection()
        +xcb_window_t getRootWindow()
        +bool isTransparentImage(QImage image)
        +QImage convertFromNative(xcb_image_t* image)
        +xcb_image_t* convertToNative(const QImage& image)
        +xcb_atom_t internAtom(const QString& name)
        +void deleteWindowPixmap(xcb_pixmap_t pixmap)
        +void deleteWindowCursor(xcb_cursor_t cursor)
        +void destroyX11Window(xcb_window_t window)
        +void destroyX11SubWindows(xcb_window_t window)
        +void setX11WindowVisible(xcb_window_t window, bool visible)
        +void setX11WindowProperty(xcb_window_t window, xcb_atom_t property, xcb_atom_t type, uint32_t format, QByteArray data)
        +QByteArray getX11WindowProperty(xcb_window_t window, xcb_atom_t property)
        +void sendX11ClientMessage(xcb_window_t window, xcb_atom_t type, QVector~uint32_t~ data)
        +void sendX11TrayNotify(xcb_window_t window, xcb_atom_t message, QVector~uint32_t~ data)
        +void sendX11TrayRequestDock(xcb_window_t window)
        +void sendX11TrayBeginMessage(xcb_window_t window, uint32_t size)
        +void sendX11TrayCancelMessage(xcb_window_t window)
        +void sendX11TrayMessageData(xcb_window_t window, const QByteArray& data)
        +void setX11WindowGeometry(xcb_window_t window, QRect rect)
        +void setX11WindowStrutPartial(xcb_window_t window, const QVector~uint32_t~& values)
        +void setX11WindowSkipTaskbar(xcb_window_t window, bool skip)
        +void setX11WindowSkipPager(xcb_window_t window, bool skip)
        +void setX11WindowSticky(xcb_window_t window, bool sticky)
        +void setX11WindowBelow(xcb_window_t window, bool below)
        +void setX11WindowAbove(xcb_window_t window, bool above)
        +void setX11WindowTypeDock(xcb_window_t window)
        +void setX11WindowTypeNormal(xcb_window_t window)
        +void setX11WindowDesktop(xcb_window_t window)
        +void setX11WindowStrut(xcb_window_t window, const QVector~uint32_t~& values)
        +void setX11WindowIconPixmap(xcb_window_t window, xcb_pixmap_t pixmap)
        +void setX11WindowClass(xcb_window_t window, const QByteArray& name)
        +void setX11WindowNetWmName(xcb_window_t window, const QString& name)
        +void setX11WindowInputShape(xcb_window_t window, int x, int y, int width, int height)
        +void clearX11WindowInputShape(xcb_window_t window)
        +void setX11WindowShapeRectangles(xcb_window_t window, const QVector~QRect~& rects)
        +void setX11WindowOpacity(xcb_window_t window, double opacity)
        +void setX11WindowXEmbedInfo(xcb_window_t window, uint32_t flags, uint32_t version)
        +xcb_window_t createX11Window(int x, int y, int width, int height, uint32_t eventMask)
        +xcb_window_t createTransparentX11Window(int x, int y, int width, int height, uint32_t eventMask)
        +xcb_pixmap_t createX11PixmapFromImage(const QImage& image)
        +xcb_cursor_t createX11CursorFromImage(const QImage& image)
        +void dispatchEvents(DispatchEventsMode mode)
        -Util()
        -~Util()
        -xcb_connection_t* m_x11connection
        -xcb_ewmh_connection_t m_ewmh
        -xcb_window_t m_rootWindow
        -_XDisplay* m_display
        -QSet~QString~ m_currentIds
        <<enum>> DispatchEventsMode
        DispatchEventsMode Poll
        DispatchEventsMode EventQueue
    }

    class XembedProtocol {
        +XembedProtocol(QObject* parent)
        +~XembedProtocol()
        +void onTrayIconsChanged()
        +void onTrayIconAdded(QString id)
        +void onTrayIconRemoved(QString id)
        +void setManager(WindowId manager)
        +void registerTrayIcon(WindowId window)
        +void unregisterTrayIcon(WindowId window)
        +void updateTrayIconGeometry(WindowId window, QRect geometry)
        +void handleTrayMessage(WindowId window, QByteArray message)
        +void handleTraySelectionCleared()
        +void handleTrayManagerDestroyed()
        +void activateWindow(WindowId window)
        +void sendClick(WindowId window, QPoint pos)
        +void sendDoubleClick(WindowId window, QPoint pos)
        +void sendContextMenu(WindowId window, QPoint pos)
        +void sendScroll(WindowId window, QPoint pos, int delta, int orientation)
        +void sendKey(WindowId window, int key, int modifiers)
        +void focusIn(WindowId window)
        +void focusOut(WindowId window)
        +bool nativeEventFilter(QByteArray eventType, void* message, qintptr* result)
        -QSharedPointer~TrayManager~ m_trayManager
        -QSet~WindowId~ m_registedItem
    }

    Util --|> QObject
    XembedProtocol --|> AbstractTrayProtocol
    XembedProtocol --|> QAbstractNativeEventFilter

    class XcbThread {
        +XcbThread(xcb_connection_t* connection)
        +~XcbThread()
        +void run()
        -xcb_connection_t* m_connection
    }

    XcbThread ..x Util : removed_dependency
Loading

File-Level Changes

Change Details Files
Integrate the XCB connection into the Qt main event loop via Util and remove the dedicated XcbThread.
  • Make Util inherit from QObject and initialize it accordingly
  • Create a QSocketNotifier on the XCB connection file descriptor to trigger X11 event dispatching when readable
  • Hook into QAbstractEventDispatcher aboutToBlock and awake signals to drain queued X11 events
  • Implement Util::dispatchEvents to poll XCB events and forward them to Qt’s native event dispatcher
  • Remove the XcbThread member from Util and delete xcbthread.cpp/.h
plugins/application-tray/util.cpp
plugins/application-tray/util.h
plugins/application-tray/xcbthread.cpp
plugins/application-tray/xcbthread.h
Handle specific X11 events for tray windows through a native event filter in XembedProtocol.
  • Make XembedProtocol implement QAbstractNativeEventFilter
  • Install the native event filter in XembedProtocol’s constructor using qApp->installNativeEventFilter(this)
  • Implement nativeEventFilter to process xcb_generic_event_t events, detect XCB_LEAVE_NOTIFY, and adjust the X11 window input shape via Util
plugins/application-tray/xembedprotocolhandler.cpp
plugins/application-tray/xembedprotocolhandler.h
Extend tray Util helpers and switch logging to QLoggingCategory.
  • Replace QDebug logging includes with QLoggingCategory and define a TRAYUTIL logging category
  • Add Util::getAtomFromDisplay to resolve atoms using the X11 display
  • Include additional XCB headers such as xcb_atom.h to support new helpers
plugins/application-tray/util.cpp
plugins/application-tray/util.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepin-ci-robot
Copy link

deepin pr auto review

我来分析一下这段代码的改进:

  1. 架构改进:
  • 移除了 XcbThread 类,改用 Qt 的事件系统来处理 X11 事件,这是一个很好的改进。原来的实现使用了独立的线程处理 X11 事件,可能会带来线程同步的问题。新实现通过 QSocketNotifier 和 QAbstractNativeEventFilter 来处理事件,更加符合 Qt 的事件驱动模型。
  1. 代码质量改进:
  • 添加了日志系统(Q_LOGGING_CATEGORY),便于调试和问题追踪
  • 使用了更现代的 C++11 语法,如 enum class
  • 添加了更多的错误检查,比如在 dispatchEvents 中检查 connection 是否为空
  • 代码结构更清晰,将事件处理逻辑集中到了一起
  1. 性能优化:
  • 使用 QSocketNotifier 监听 X11 连接的文件描述符,比原来的轮询方式更高效
  • 通过 aboutToBlock 和 awake 信号确保事件及时处理
  • 避免了线程切换的开销
  1. 安全性改进:
  • 使用了更安全的事件处理方式,避免了线程间共享数据可能带来的问题
  • 在事件处理中添加了类型检查和错误处理

建议的进一步改进:

  1. 在 Util::dispatchEvents 中:
void Util::dispatchEvents(DispatchEventsMode mode)
{
    xcb_connection_t *connection = m_x11connection;
    if (!connection) {
        qCWarning(TRAYUTIL, "Attempting to dispatch X11 events with no connection");
        return;
    }

    auto pollEventFunc = mode == DispatchEventsMode::Poll ? xcb_poll_for_event : xcb_poll_for_queued_event;
    
    // 添加事件计数限制,防止事件处理阻塞
    int eventCount = 0;
    const int maxEvents = 100; // 设置最大处理事件数

    while (xcb_generic_event_t *event = pollEventFunc(connection)) {
        if (++eventCount >= maxEvents) {
            qCWarning(TRAYUTIL, "Too many events pending, processing later");
            break;
        }

        qintptr result = 0;
        QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
        if (!dispatcher) {
            qCWarning(TRAYUTIL, "No event dispatcher available");
            free(event);
            break;
        }

        dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
        free(event);
    }

    if (eventCount > 0) {
        xcb_flush(connection);
    }
}
  1. 在 XembedProtocol::nativeEventFilter 中:
bool XembedProtocol::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
{
    Q_UNUSED(result)

    if (eventType != "xcb_generic_event_t") {
        return false;
    }

    if (!message) {
        qCWarning(TRAYUTIL, "Received null event message");
        return false;
    }

    auto *ev = static_cast<xcb_generic_event_t *>(message);
    if (!ev) {
        return false;
    }

    const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
    if (responseType == XCB_LEAVE_NOTIFY) {
        xcb_leave_notify_event_t *lE = reinterpret_cast<xcb_leave_notify_event_t*>(ev);
        if (lE && lE->event) {
            UTIL->setX11WindowInputShape(lE->event, QSize(0, 0));
            return true;
        }
    }

    return false;
}
  1. 在 Util 构造函数中:
Util::Util()
    : QObject()
{
    m_x11connection = xcb_connect(nullptr, nullptr);
    if (!m_x11connection) {
        qCCritical(TRAYUTIL, "Failed to connect to X server");
        return;
    }

    m_display = XOpenDisplay("");
    if (!m_display) {
        qCCritical(TRAYUTIL, "Failed to open X display");
        xcb_disconnect(m_x11connection);
        m_x11connection = nullptr;
        return;
    }

    // ... 其余代码
}

这些改进主要增加了:

  1. 更完善的错误处理和空指针检查
  2. 事件处理数量限制,防止事件处理阻塞
  3. 更详细的日志输出
  4. 更安全的类型转换
  5. 资源清理的保证

这些改进能够提高代码的健壮性和可靠性,同时保持良好的性能。

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In Util::dispatchEvents, consider guarding against a null QAbstractEventDispatcher from QCoreApplication::eventDispatcher() before calling filterNativeEvent to avoid potential crashes during early startup or shutdown.
  • The new Util::getAtomFromDisplay helper is slightly confusing given the existing getAtomByName(QString); it may be clearer to either delegate directly to getAtomByName with a QString name or rename/adjust the implementation to better reflect what xcb_atom_name_by_screen returns.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In Util::dispatchEvents, consider guarding against a null QAbstractEventDispatcher from QCoreApplication::eventDispatcher() before calling filterNativeEvent to avoid potential crashes during early startup or shutdown.
- The new Util::getAtomFromDisplay helper is slightly confusing given the existing getAtomByName(QString); it may be clearer to either delegate directly to getAtomByName with a QString name or rename/adjust the implementation to better reflect what xcb_atom_name_by_screen returns.

## Individual Comments

### Comment 1
<location> `plugins/application-tray/util.cpp:52-57` </location>
<code_context>
+        return;
+    }
+
+    auto pollEventFunc = mode == DispatchEventsMode::Poll ? xcb_poll_for_event : xcb_poll_for_queued_event;
+
+    while (xcb_generic_event_t *event = pollEventFunc(connection)) {
+        qintptr result = 0;
+
+        QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
+        dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
+        free(event);
</code_context>

<issue_to_address>
**issue (bug_risk):** Guard against a null QAbstractEventDispatcher before calling filterNativeEvent.

`QCoreApplication::eventDispatcher()` may return `nullptr` during early startup or shutdown, which would cause a crash when calling `dispatcher->filterNativeEvent(...)`. Please add a null check and skip processing if no dispatcher is available.
</issue_to_address>

### Comment 2
<location> `plugins/application-tray/xembedprotocolhandler.cpp:35` </location>
<code_context>
     : AbstractTrayProtocol(parent)
     , m_trayManager(new TrayManager("org.deepin.dde.TrayManager1", "/org/deepin/dde/TrayManager1", QDBusConnection::sessionBus(), this))
 {
+    qApp->installNativeEventFilter(this);
+
     m_trayManager->Manage();
</code_context>

<issue_to_address>
**issue (bug_risk):** Remove the native event filter in the destructor to avoid a dangling pointer in QCoreApplication.

`QCoreApplication` stores a raw pointer to the installed `QAbstractNativeEventFilter`. If `XembedProtocol` is destroyed without unregistering, the app may still invoke `nativeEventFilter(...)` on a freed object. Add `qApp->removeNativeEventFilter(this);` in the destructor to prevent a use-after-free during shutdown or protocol replacement.
</issue_to_address>

### Comment 3
<location> `plugins/application-tray/util.cpp:185-187` </location>
<code_context>
     return name;
 }

+xcb_atom_t Util::getAtomFromDisplay(const char * name)
+{
+    return getAtomByName(xcb_atom_name_by_screen(name, DefaultScreen(getDisplay())));
+}
+
</code_context>

<issue_to_address>
**question:** Clarify the intent and correctness of getAtomFromDisplay’s Xlib/XCB interop and helper usage.

This currently mixes Xlib (`DefaultScreen(getDisplay())`) with an XCB helper, then forwards to `getAtomByName`. Please explain why this indirection is needed instead of calling `getAtomByName(QString::fromLatin1(name))` directly, and confirm that `xcb_atom_name_by_screen(...)` behaves as expected here (no extra round-trips, correct encoding/screen handling). If it’s equivalent, simplifying to the direct call would reduce Xlib/XCB interop complexity.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +52 to +57
auto pollEventFunc = mode == DispatchEventsMode::Poll ? xcb_poll_for_event : xcb_poll_for_queued_event;

while (xcb_generic_event_t *event = pollEventFunc(connection)) {
qintptr result = 0;

QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Guard against a null QAbstractEventDispatcher before calling filterNativeEvent.

QCoreApplication::eventDispatcher() may return nullptr during early startup or shutdown, which would cause a crash when calling dispatcher->filterNativeEvent(...). Please add a null check and skip processing if no dispatcher is available.

: AbstractTrayProtocol(parent)
, m_trayManager(new TrayManager("org.deepin.dde.TrayManager1", "/org/deepin/dde/TrayManager1", QDBusConnection::sessionBus(), this))
{
qApp->installNativeEventFilter(this);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Remove the native event filter in the destructor to avoid a dangling pointer in QCoreApplication.

QCoreApplication stores a raw pointer to the installed QAbstractNativeEventFilter. If XembedProtocol is destroyed without unregistering, the app may still invoke nativeEventFilter(...) on a freed object. Add qApp->removeNativeEventFilter(this); in the destructor to prevent a use-after-free during shutdown or protocol replacement.

Comment on lines +185 to +187
xcb_atom_t Util::getAtomFromDisplay(const char * name)
{
return getAtomByName(xcb_atom_name_by_screen(name, DefaultScreen(getDisplay())));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Clarify the intent and correctness of getAtomFromDisplay’s Xlib/XCB interop and helper usage.

This currently mixes Xlib (DefaultScreen(getDisplay())) with an XCB helper, then forwards to getAtomByName. Please explain why this indirection is needed instead of calling getAtomByName(QString::fromLatin1(name)) directly, and confirm that xcb_atom_name_by_screen(...) behaves as expected here (no extra round-trips, correct encoding/screen handling). If it’s equivalent, simplifying to the direct call would reduce Xlib/XCB interop complexity.

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: BLumia, tsic404

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@BLumia BLumia merged commit ccc873c into linuxdeepin:master Dec 25, 2025
9 of 10 checks passed
@BLumia BLumia deleted the nativeeventfilter branch December 25, 2025 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants