Compare commits

...

6 Commits

16 changed files with 125 additions and 258 deletions

View File

@@ -10,12 +10,9 @@ find_package(Qt6 6.8 CONFIG REQUIRED COMPONENTS DBus)
find_package(ECM REQUIRED NO_MODULE)
find_package(PkgConfig REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(ECMQtDeclareLoggingCategory)
include(KDEInstallDirs)
include(ECMConfiguredInstall)
find_package(KF6 6.6 REQUIRED COMPONENTS
WindowSystem)
find_package(KF6WindowSystem 6.6 REQUIRED)
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11 xcb xcb-image xcb-damage xcb-composite xcb-xfixes xcb-util xcb-shape xtst xcb-xtest xcb-res xcb-ewmh)
@@ -24,24 +21,15 @@ set(XEMBED_SNI_PROXY_SOURCES
fdoselectionmanager.cpp fdoselectionmanager.h
traymanager1.cpp traymanager1.h
util.cpp util.h
xcbthread.cpp xcbthread.h
)
set_source_files_properties(
${CMAKE_CURRENT_SOURCE_DIR}/org.deepin.dde.TrayManager1.xml
PROPERTIES INCLUDE traylist.h
${CMAKE_CURRENT_SOURCE_DIR}/api/dbus/org.deepin.dde.TrayManager1.xml
PROPERTIES INCLUDE api/types/traylist.h
CLASSNAME TrayManager
)
qt_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES org.deepin.dde.TrayManager1.xml traymanager1.h TrayManager1)
ecm_qt_declare_logging_category(XEMBED_SNI_PROXY_SOURCES HEADER debug.h
IDENTIFIER SNIPROXY
CATEGORY_NAME dde.xembedsniproxy
DEFAULT_SEVERITY Info
DESCRIPTION "xembed sni proxy"
EXPORT PLASMAWORKSPACE
)
qt_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES api/dbus/org.deepin.dde.TrayManager1.xml traymanager1.h TrayManager1)
add_executable(xembed-traymanager-proxy ${XEMBED_SNI_PROXY_SOURCES})
set_property(TARGET xembed-traymanager-proxy PROPERTY AUTOMOC ON)
@@ -59,5 +47,3 @@ target_link_libraries(xembed-traymanager-proxy
install(TARGETS xembed-traymanager-proxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES xembedsniproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
ecm_install_configured_files(INPUT plasma-xembedsniproxy.service.in @ONLY DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR})

View File

@@ -9,6 +9,8 @@ This is to allow legacy apps (xchat, pidgin, tuxguitar) etc. system trays[1] ava
> Currently this project is a standalone project that provides Xembed tray information on TrayManager1 D-Bus, so `dde-tray-loader` could consume it and provide Xembed tray icons.
> This project will be integrated into `dde-tray-loader` project.
This tool can be used on a Wayland session with `wayland` QPA, `xcb` QPA can also be used but it's not mandatory, but either way it's required to have a X connection (via X11 or Xwayland).
## Build instructions
```shell

View File

@@ -2,19 +2,18 @@
Registers as a embed container
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
SPDX-FileCopyrightText: 2025 Wang Zichong <wangzichong@deepin.org>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "fdoselectionmanager.h"
#include "debug.h"
#include <QTimer>
#include <QDBusConnection>
#include <QLoggingCategory>
#include <KSelectionOwner>
#include <qassert.h>
#include <xcb/composite.h>
#include <xcb/damage.h>
#include <xcb/xcb_atom.h>
@@ -30,11 +29,13 @@ using Util = tray::Util;
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
FdoSelectionManager::FdoSelectionManager()
: QObject()
, m_selectionOwner(new KSelectionOwner(Util::instance()->getAtomFromDisplay("_NET_SYSTEM_TRAY"), -1, this))
Q_LOGGING_CATEGORY(SELECTIONMGR, "org.deepin.dde.trayloader.selectionmgr")
FdoSelectionManager::FdoSelectionManager(QObject *parent)
: QObject(parent)
, m_selectionOwner(new KSelectionOwner(UTIL->getAtomFromDisplay("_NET_SYSTEM_TRAY"), UTIL->getX11Connection(), UTIL->getRootWindow(), this))
{
qDebug(SNIPROXY) << "starting";
qCDebug(SELECTIONMGR) << "starting";
// we may end up calling QCoreApplication::quit() in this method, at which point we need the event loop running
QTimer::singleShot(0, this, &FdoSelectionManager::init);
@@ -42,7 +43,7 @@ FdoSelectionManager::FdoSelectionManager()
FdoSelectionManager::~FdoSelectionManager()
{
qCDebug(SNIPROXY) << "closing";
qCDebug(SELECTIONMGR) << "closing";
m_selectionOwner->release();
}
@@ -57,7 +58,7 @@ void FdoSelectionManager::init()
xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
} else {
// no XDamage means
qCCritical(SNIPROXY) << "could not load damage extension. Quitting";
qCCritical(SELECTIONMGR) << "could not load damage extension. Quitting";
qApp->exit(-1);
}
@@ -66,12 +67,16 @@ void FdoSelectionManager::init()
connect(m_selectionOwner, &KSelectionOwner::claimedOwnership, this, &FdoSelectionManager::onClaimedOwnership);
connect(m_selectionOwner, &KSelectionOwner::failedToClaimOwnership, this, &FdoSelectionManager::onFailedToClaimOwnership);
connect(m_selectionOwner, &KSelectionOwner::lostOwnership, this, &FdoSelectionManager::onLostOwnership);
m_selectionOwner->claim(false);
m_selectionOwner->claim(true);
connect(m_trayManager, &TrayManager1::reclainRequested, this, [this](){
m_selectionOwner->claim(true);
});
}
bool FdoSelectionManager::addDamageWatch(xcb_window_t client)
{
qCDebug(SNIPROXY) << "adding damage watch for " << client;
qCDebug(SELECTIONMGR) << "adding damage watch for " << client;
xcb_connection_t *c = Util::instance()->getX11Connection();
const auto attribsCookie = xcb_get_window_attributes_unchecked(c, client);
@@ -153,16 +158,13 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m
void FdoSelectionManager::dock(xcb_window_t winId)
{
Q_CHECK_PTR(m_trayManager);
qCDebug(SNIPROXY) << "trying to dock window " << winId;
qCDebug(SELECTIONMGR) << "trying to dock window " << winId;
if (m_trayManager->haveIcon(winId)) {
return;
}
if (addDamageWatch(winId)) {
// auto proxy = new TrayManagerProxy(winId, this);
// m_proxies[winId] = proxy;
// Register with TrayManager1 if available
m_trayManager->registerIcon(winId);
}
@@ -171,12 +173,13 @@ void FdoSelectionManager::dock(xcb_window_t winId)
void FdoSelectionManager::undock(xcb_window_t winId)
{
Q_CHECK_PTR(m_trayManager);
qCDebug(SNIPROXY) << "trying to undock window " << winId;
qCDebug(SELECTIONMGR) << "trying to undock window " << winId;
if (m_trayManager->haveIcon(winId)) {
if (!m_trayManager->haveIcon(winId)) {
qCDebug(SELECTIONMGR) << "failed to find winId to undock:" << winId;
return;
}
// Unregister from TrayManager1 if available
m_trayManager->unregisterIcon(winId);
@@ -186,7 +189,7 @@ void FdoSelectionManager::undock(xcb_window_t winId)
void FdoSelectionManager::onClaimedOwnership()
{
qCDebug(SNIPROXY) << "Manager selection claimed";
qCDebug(SELECTIONMGR) << "Manager selection claimed";
initTrayManager();
setSystemTrayVisual();
@@ -194,14 +197,12 @@ void FdoSelectionManager::onClaimedOwnership()
void FdoSelectionManager::onFailedToClaimOwnership()
{
qCWarning(SNIPROXY) << "failed to claim ownership of Systray Manager";
qApp->exit(-1);
qCWarning(SELECTIONMGR) << "failed to claim ownership of Systray Manager";
}
void FdoSelectionManager::onLostOwnership()
{
qCWarning(SNIPROXY) << "lost ownership of Systray Manager";
qApp->exit(-1);
qCWarning(SELECTIONMGR) << "lost ownership of Systray Manager";
}
void FdoSelectionManager::setSystemTrayVisual()
@@ -253,7 +254,7 @@ void FdoSelectionManager::initTrayManager()
QStringLiteral("org.deepin.dde.TrayManager1")
);
qCDebug(SNIPROXY) << "TrayManager1 DBus interface registered";
qCDebug(SELECTIONMGR) << "TrayManager1 DBus interface registered";
}
}

View File

@@ -23,7 +23,7 @@ class FdoSelectionManager : public QObject, public QAbstractNativeEventFilter
Q_OBJECT
public:
FdoSelectionManager();
FdoSelectionManager(QObject *parent = nullptr);
~FdoSelectionManager() override;
protected:

View File

@@ -9,7 +9,7 @@
#include "fdoselectionmanager.h"
#include "debug.h"
#include "util.h"
#ifdef None
#ifndef FIXX11H_None
@@ -25,18 +25,25 @@ inline constexpr XID None = XNone;
#include <KWindowSystem>
using Util = tray::Util;
int main(int argc, char **argv)
{
// the whole point of this is to interact with X, if we are in any other session, force trying to connect to X
// if the QPA can't load xcb, this app is useless anyway.
qputenv("QT_QPA_PLATFORM", "xcb");
// We will use the X connection managed by UTIL, we don't really care if the tool itself is
// running with xcb or wayland QPA.
// We'll use wayland QPA for testing on DDE, the following code uses dde-tray-loader's wayland
// display, thus we can ensure our X events are not from QPA.
qputenv("QT_QPA_PLATFORM", "wayland");
qputenv("WAYLAND_DISPLAY", "dockplugin");
qputenv("QT_WAYLAND_SHELL_INTEGRATION", "plugin-shell");
// qputenv("QT_QPA_PLATFORM", "xcb");
QGuiApplication::setDesktopSettingsAware(false);
QCoreApplication::setAttribute(Qt::AA_DisableSessionManager);
QGuiApplication app(argc, argv);
if (!KWindowSystem::isPlatformX11()) {
if (!UTIL->isXAvaliable()) {
qFatal("xembed-traymanager-proxy requires X11. Aborting");
}

View File

@@ -1,63 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<!-- This is a minimally cut down version of the interface only implementing the
methods and properties used by xembedsniproxy -->
<interface name="org.kde.StatusNotifierItem">
<property name="Category" type="s" access="read"/>
<property name="Id" type="s" access="read"/>
<property name="Title" type="s" access="read"/>
<property name="Status" type="s" access="read"/>
<property name="WindowId" type="i" access="read"/>
<property name="ItemIsMenu" type="b" access="read"/>
<property name="IconPixmap" type="(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="KDbusImageVector"/>
</property>
<!-- interaction: the systemtray wants the application to do something -->
<method name="ContextMenu">
<!-- we're passing the coordinates of the icon, so the app knows where to put the popup window -->
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Activate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="SecondaryActivate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Scroll">
<arg name="delta" type="i" direction="in"/>
<arg name="orientation" type="s" direction="in"/>
</method>
<!-- Signals: the client wants to change something in the status-->
<signal name="NewTitle">
</signal>
<signal name="NewIcon">
</signal>
<signal name="NewAttentionIcon">
</signal>
<signal name="NewOverlayIcon">
</signal>
<signal name="NewToolTip">
</signal>
<signal name="NewStatus">
<arg name="status" type="s"/>
</signal>
</interface>
</node>

View File

@@ -1,42 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierWatcher">
<!-- methods -->
<method name="RegisterStatusNotifierItem">
<arg name="service" type="s" direction="in"/>
</method>
<method name="RegisterStatusNotifierHost">
<arg name="service" type="s" direction="in"/>
</method>
<!-- properties -->
<property name="RegisteredStatusNotifierItems" type="as" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/>
</property>
<property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
<property name="ProtocolVersion" type="i" access="read"/>
<!-- signals -->
<signal name="StatusNotifierItemRegistered">
<arg type="s"/>
</signal>
<signal name="StatusNotifierItemUnregistered">
<arg type="s"/>
</signal>
<signal name="StatusNotifierHostRegistered">
</signal>
<signal name="StatusNotifierHostUnregistered">
</signal>
</interface>
</node>

View File

@@ -1,11 +0,0 @@
[Unit]
Description=Handle legacy xembed system tray icons
PartOf=graphical-session.target
After=plasma-core.target
[Service]
ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/xembedsniproxy
Restart=on-failure
Type=simple
Slice=background.slice
TimeoutSec=5sec

View File

@@ -1,35 +1,41 @@
/*
Deepin DDE TrayManager1 implementation
SPDX-License-Identifier: LGPL-2.1-or-later
*/
// Deepin DDE TrayManager1 implementation
//
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "traymanager1.h"
#include "debug.h"
#include "traymanager1adaptor.h"
#include "util.h"
#include <KWindowInfo>
#include <QGuiApplication>
#include <QLoggingCategory>
Q_LOGGING_CATEGORY(TRAYMGR, "org.deepin.dde.trayloader.traymgr")
TrayManager1::TrayManager1(QObject *parent)
: QObject(parent)
, m_adaptor(new TrayManager1Adaptor(this))
{
qCDebug(SNIPROXY) << "TrayManager1 created";
qCDebug(TRAYMGR) << "TrayManager1 created";
}
TrayManager1::~TrayManager1()
{
qCDebug(SNIPROXY) << "TrayManager1 destroyed";
qCDebug(TRAYMGR) << "TrayManager1 destroyed";
}
void TrayManager1::registerIcon(xcb_window_t win)
{
if (m_icons.contains(win)) {
qCWarning(SNIPROXY) << "Icon already registered:" << win;
qCWarning(TRAYMGR) << "Icon already registered:" << win;
return;
}
m_icons[win] = true;
qCDebug(SNIPROXY) << "Icon registered:" << win ;//<< "name:" << proxy->name();
qCDebug(TRAYMGR) << "Icon registered:" << win ;//<< "name:" << proxy->name();
Q_EMIT Added(static_cast<uint32_t>(win));
}
@@ -37,12 +43,12 @@ void TrayManager1::registerIcon(xcb_window_t win)
void TrayManager1::unregisterIcon(xcb_window_t win)
{
if (!m_icons.contains(win)) {
qCWarning(SNIPROXY) << "Icon not found for removal:" << win;
qCWarning(TRAYMGR) << "Icon not found for removal:" << win;
return;
}
m_icons.remove(win);
qCDebug(SNIPROXY) << "Icon unregistered:" << win;
qCDebug(TRAYMGR) << "Icon unregistered:" << win;
Q_EMIT Removed(static_cast<uint32_t>(win));
}
@@ -54,11 +60,11 @@ void TrayManager1::notifyIconChanged(xcb_window_t win)
}
if (!m_icons[win]) {
qCDebug(SNIPROXY) << "EnableNotification is false, not sending changed signal for:" << win;
qCDebug(TRAYMGR) << "EnableNotification is false, not sending changed signal for:" << win;
return;
}
qCDebug(SNIPROXY) << "Icon changed:" << win;
qCDebug(TRAYMGR) << "Icon changed:" << win;
Q_EMIT Changed(static_cast<uint32_t>(win));
}
@@ -80,15 +86,15 @@ bool TrayManager1::haveIcon(xcb_window_t win) const
// DBus method implementations
bool TrayManager1::Manage()
{
qCDebug(SNIPROXY) << "Manage() called via DBus";
qCDebug(TRAYMGR) << "Manage() called via DBus";
emit reclainRequested();
return true;
}
QString TrayManager1::GetName(uint32_t win)
{
auto connection = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
KWindowInfo info(win, NET::WMName | NET::WMIconName);
return info.name();
using Util = tray::Util;
return UTIL->getX11WindowName(win);
}
void TrayManager1::EnableNotification(uint32_t win, bool enabled)
@@ -99,5 +105,5 @@ void TrayManager1::EnableNotification(uint32_t win, bool enabled)
m_icons[win] = enabled;
qCDebug(SNIPROXY) << "EnableNotification for" << win << "=" << enabled;
qCDebug(TRAYMGR) << "EnableNotification for" << win << "=" << enabled;
}

View File

@@ -1,7 +1,8 @@
/*
Deepin DDE TrayManager1 implementation
SPDX-License-Identifier: LGPL-2.1-or-later
*/
// Deepin DDE TrayManager1 implementation
//
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -74,6 +75,8 @@ Q_SIGNALS:
void Changed(uint32_t id);
void Inited();
void reclainRequested();
private:
TrayManager1Adaptor * m_adaptor;
QHash<xcb_window_t, bool> m_icons; // <winid, enableNotify>

View File

@@ -2,16 +2,18 @@
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QDebug>
#include <QLoggingCategory>
#include "util.h"
#include "xcbthread.h"
#include <QSize>
#include <QPixmap>
#include <QBitmap>
#include <QFileInfo>
#include <QtGlobal>
#include <QSocketNotifier>
#include <QCoreApplication>
#include <QAbstractEventDispatcher>
#include <X11/Xlib.h>
@@ -23,6 +25,8 @@
#include <xcb/xproto.h>
#include <xcb/composite.h>
Q_LOGGING_CATEGORY(TRAYUTIL, "org.deepin.dde.trayloader.util")
namespace tray {
void clean_xcb_image(void *data)
{
@@ -37,7 +41,29 @@ Util* Util::instance()
return _instance;
}
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;
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);
}
xcb_flush(connection);
}
Util::Util()
: QObject()
{
m_x11connection = xcb_connect(nullptr, nullptr);
m_display = XOpenDisplay("");
@@ -51,8 +77,20 @@ Util::Util()
m_rootWindow = screen->root;
xcb_ewmh_init_atoms_replies(&m_ewmh, xcb_ewmh_init_atoms(m_x11connection, &m_ewmh), nullptr);
m_xcbThread = new XcbThread(m_x11connection);
m_xcbThread->start();
const int fd = xcb_get_file_descriptor(m_x11connection);
QSocketNotifier * qfd = new QSocketNotifier(fd, QSocketNotifier::Read, this);
connect(qfd, &QSocketNotifier::activated, this, [this](){
dispatchEvents(DispatchEventsMode::Poll);
});
QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, [this]() {
dispatchEvents(DispatchEventsMode::EventQueue);
});
connect(dispatcher, &QAbstractEventDispatcher::awake, this, [this]() {
dispatchEvents(DispatchEventsMode::EventQueue);
});
}
Util::~Util()

12
util.h
View File

@@ -8,6 +8,7 @@
#include <QImage>
#include <QSharedPointer>
#include <QSet>
#include <QObject>
#include <cstdint>
#include <sys/types.h>
@@ -20,8 +21,7 @@ struct _XDisplay;
namespace tray {
#define UTIL Util::instance()
class XcbThread;
class Util
class Util : public QObject
{
public:
@@ -59,6 +59,12 @@ private:
Util(const Util&) = delete;
Util& operator=(const Util&) = delete;
enum class DispatchEventsMode {
Poll,
EventQueue
};
void dispatchEvents(DispatchEventsMode mode);
bool isTransparentImage(const QImage &image);
QImage convertFromNative(xcb_image_t* image);
@@ -72,8 +78,6 @@ private:
_XDisplay *m_display;
QSet<QString> m_currentIds;
XcbThread *m_xcbThread;
};
}

View File

@@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "xcbthread.h"
#include "util.h"
namespace tray {
XcbThread::XcbThread(xcb_connection_t *connection, QObject *parent)
: QThread(parent)
, m_connection(connection)
{
}
XcbThread::~XcbThread()
{
}
void XcbThread::run()
{
if (!m_connection) {
return;
}
// The Xembed type tray needs to reset the xwindow state of the receiving event to the state of not receiving events after the mouse
// leaves. This thread is used to receive the leave event and apply the operation.
QScopedPointer<xcb_generic_event_t> event;
while (!isInterruptionRequested()) {
event.reset(xcb_wait_for_event(m_connection));
if (event) {
uint8_t responseType = event->response_type & ~0x80;
switch (responseType) {
case XCB_LEAVE_NOTIFY: {
xcb_leave_notify_event_t *lE = (xcb_leave_notify_event_t *)event.data();
UTIL->setX11WindowInputShape(lE->event, QSize(0, 0));
break;
}
}
}
}
}
}

View File

@@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QThread>
#include <xcb/xcb.h>
namespace tray {
class XcbThread : public QThread {
Q_OBJECT
public:
XcbThread(xcb_connection_t *connection, QObject *parent = nullptr);
~XcbThread();
void run() override;
private:
xcb_connection_t *m_connection;
};
}