Skip to content

Commit 2fc9475

Browse files
Proxy Xembed icons to SNI
The goal of this project is to make xembed system trays available in Plasma. This is to allow legacy apps (xchat, pidgin, tuxguitar) etc. system trays[1] available in Plasma which only supports StatusNotifierItem [2]. Ideally we also want this to work in an xwayland session, making X system tray icons available even when plasmashell only has a wayland connection. REVIEW: 125655
1 parent 2b51174 commit 2fc9475

14 files changed

+1428
-0
lines changed

Diff for: CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ include(WriteBasicConfigVersionFile)
4343
include(CheckIncludeFiles)
4444
include(FeatureSummary)
4545
include(ECMOptionalAddSubdirectory)
46+
include(ECMQtDeclareLoggingCategory)
4647

4748
find_package(KF5Activities ${KF5_MIN_VERSION})
4849
set_package_properties(KF5Activities PROPERTIES DESCRIPTION "management of Plasma activities"
@@ -153,5 +154,7 @@ add_subdirectory(phonon)
153154
add_subdirectory(solidautoeject)
154155
add_subdirectory(drkonqi)
155156

157+
ecm_optional_add_subdirectory(xembed-sni-proxy)
158+
156159
add_subdirectory(soliduiserver)
157160
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

Diff for: xembed-sni-proxy/CMakeLists.txt

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
project(xembedsniproxy)
2+
3+
add_definitions(-DQT_NO_CAST_TO_ASCII
4+
-DQT_NO_CAST_FROM_ASCII
5+
-DQT_NO_URL_CAST_FROM_STRING
6+
-DQT_NO_CAST_FROM_BYTEARRAY)
7+
8+
find_package(XCB
9+
REQUIRED COMPONENTS
10+
XCB
11+
XFIXES
12+
DAMAGE
13+
COMPOSITE
14+
RANDR
15+
SHM
16+
UTIL
17+
IMAGE
18+
)
19+
20+
set(XCB_LIBS
21+
XCB::XCB
22+
XCB::XFIXES
23+
XCB::DAMAGE
24+
XCB::COMPOSITE
25+
XCB::RANDR
26+
XCB::SHM
27+
XCB::UTIL
28+
XCB::IMAGE
29+
)
30+
31+
32+
33+
set(XEMBED_SNI_PROXY_SOURCES
34+
main.cpp
35+
fdoselectionmanager.cpp
36+
snidbus.cpp
37+
sniproxy.cpp
38+
)
39+
40+
qt5_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES org.kde.StatusNotifierItem.xml
41+
sniproxy.h SNIProxy)
42+
43+
set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml)
44+
qt5_add_dbus_interface(XEMBED_SNI_PROXY_SOURCES ${statusnotifierwatcher_xml} statusnotifierwatcher_interface)
45+
46+
add_executable(xembedsniproxy ${XEMBED_SNI_PROXY_SOURCES})
47+
48+
49+
50+
set_package_properties(XCB PROPERTIES TYPE REQUIRED)
51+
52+
ecm_qt_declare_logging_category(xembedsniproxy HEADER debug.h
53+
IDENTIFIER SNIPROXY
54+
CATEGORY_NAME kde.xembedsniproxy
55+
DEFAULT_SEVERITY Info)
56+
57+
target_link_libraries(xembedsniproxy
58+
Qt5::Core
59+
Qt5::X11Extras
60+
Qt5::DBus
61+
KF5::WindowSystem
62+
${XCB_LIBS}
63+
)
64+
65+
install(TARGETS xembedsniproxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
66+
install(FILES xembedsniproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
67+

Diff for: xembed-sni-proxy/Readme.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
##XEmbed SNI Proxy
2+
3+
The goal of this project is to make xembed system trays available in Plasma.
4+
5+
This is to allow legacy apps (xchat, pidgin, tuxguitar) etc. system trays[1] available in Plasma which only supports StatusNotifierItem [2].
6+
7+
Ideally we also want this to work in an xwayland session, making X system tray icons available even when plasmashell only has a wayland connection.
8+
9+
This project should be portable onto all other DEs that speak SNI.
10+
11+
##How it works (in theory)
12+
13+
* We register a window as a system tray container
14+
* We render embeded windows composited offscreen
15+
* We render contents into an image and send this over DBus via the SNI protocol
16+
* XDamage events trigger a repaint
17+
* Activate and context menu events are replyed via X send event into the embedded container as left and right clicks
18+
19+
There are a few extra hacks in the real code to deal with some toolkits being awkward.
20+
21+
##Build instructions
22+
23+
cmake .
24+
make
25+
sudo make install
26+
27+
After building, run `xembedsniproxy`.
28+
29+
[1] http://standards.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
30+
[2] http://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/

Diff for: xembed-sni-proxy/fdoselectionmanager.cpp

+182
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Registers as a embed container
3+
* Copyright (C) 2015 <[email protected]> David Edmundson
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*
19+
*/
20+
#include "fdoselectionmanager.h"
21+
22+
#include "debug.h"
23+
24+
#include <QCoreApplication>
25+
#include <QHash>
26+
#include <QTimer>
27+
28+
#include <QTextDocument>
29+
#include <QX11Info>
30+
31+
#include <KWindowSystem>
32+
#include <KSelectionOwner>
33+
34+
#include <xcb/xcb.h>
35+
#include <xcb/xcb_atom.h>
36+
#include <xcb/xcb_event.h>
37+
#include <xcb/composite.h>
38+
#include <xcb/damage.h>
39+
40+
#include "xcbutils.h"
41+
#include "sniproxy.h"
42+
43+
#define SYSTEM_TRAY_REQUEST_DOCK 0
44+
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
45+
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
46+
47+
FdoSelectionManager::FdoSelectionManager():
48+
QObject(),
49+
m_selectionOwner(new KSelectionOwner(Xcb::atoms->selectionAtom, -1, this))
50+
{
51+
qCDebug(SNIPROXY) << "starting";
52+
53+
//load damage extension
54+
xcb_connection_t *c = QX11Info::connection();
55+
xcb_prefetch_extension_data(c, &xcb_damage_id);
56+
const auto *reply = xcb_get_extension_data(c, &xcb_damage_id);
57+
if (reply->present) {
58+
m_damageEventBase = reply->first_event;
59+
xcb_damage_query_version_unchecked(c, XCB_DAMAGE_MAJOR_VERSION, XCB_DAMAGE_MINOR_VERSION);
60+
} else {
61+
//no XDamage means
62+
qCCritical(SNIPROXY) << "could not load damage extension. Quitting";
63+
qApp->exit(-1);
64+
}
65+
66+
qApp->installNativeEventFilter(this);
67+
68+
m_selectionOwner->claim(false);
69+
connect(m_selectionOwner, &KSelectionOwner::claimedOwnership, this, &FdoSelectionManager::onClaimedOwnership);
70+
connect(m_selectionOwner, &KSelectionOwner::failedToClaimOwnership, this, &FdoSelectionManager::onFailedToClaimOwnership);
71+
connect(m_selectionOwner, &KSelectionOwner::lostOwnership, this, &FdoSelectionManager::onLostOwnership);
72+
}
73+
74+
FdoSelectionManager::~FdoSelectionManager()
75+
{
76+
qCDebug(SNIPROXY) << "closing";
77+
m_selectionOwner->release();
78+
}
79+
80+
void FdoSelectionManager::addDamageWatch(xcb_window_t client)
81+
{
82+
qCDebug(SNIPROXY) << "adding damage watch for " << client;
83+
84+
xcb_connection_t *c = QX11Info::connection();
85+
const auto attribsCookie = xcb_get_window_attributes_unchecked(c, client);
86+
87+
const auto damageId = xcb_generate_id(c);
88+
m_damageWatches[client] = damageId;
89+
xcb_damage_create(c, damageId, client, XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY);
90+
91+
QScopedPointer<xcb_get_window_attributes_reply_t, QScopedPointerPodDeleter> attr(xcb_get_window_attributes_reply(c, attribsCookie, Q_NULLPTR));
92+
uint32_t events = XCB_EVENT_MASK_STRUCTURE_NOTIFY;
93+
if (!attr.isNull()) {
94+
events = events | attr->your_event_mask;
95+
}
96+
// the event mask will not be removed again. We cannot track whether another component also needs STRUCTURE_NOTIFY (e.g. KWindowSystem).
97+
// if we would remove the event mask again, other areas will break.
98+
xcb_change_window_attributes(c, client, XCB_CW_EVENT_MASK, &events);
99+
100+
}
101+
102+
bool FdoSelectionManager::nativeEventFilter(const QByteArray& eventType, void* message, long int* result)
103+
{
104+
Q_UNUSED(result);
105+
106+
if (eventType != "xcb_generic_event_t") {
107+
return false;
108+
}
109+
110+
xcb_generic_event_t* ev = static_cast<xcb_generic_event_t *>(message);
111+
112+
const auto responseType = XCB_EVENT_RESPONSE_TYPE(ev);
113+
if (responseType == XCB_CLIENT_MESSAGE) {
114+
const auto ce = reinterpret_cast<xcb_client_message_event_t *>(ev);
115+
if (ce->type == Xcb::atoms->opcodeAtom) {
116+
switch (ce->data.data32[1]) {
117+
case SYSTEM_TRAY_REQUEST_DOCK:
118+
dock(ce->data.data32[2]);
119+
return true;
120+
}
121+
}
122+
} else if (responseType == XCB_UNMAP_NOTIFY) {
123+
const auto unmappedWId = reinterpret_cast<xcb_unmap_notify_event_t *>(ev)->window;
124+
if (m_proxies[unmappedWId]) {
125+
undock(unmappedWId);
126+
}
127+
} else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
128+
const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable;
129+
const auto sniProx = m_proxies[damagedWId];
130+
131+
Q_ASSERT(sniProx);
132+
133+
if(sniProx) {
134+
sniProx->update();
135+
xcb_damage_subtract(QX11Info::connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE);
136+
}
137+
}
138+
139+
return false;
140+
}
141+
142+
void FdoSelectionManager::dock(xcb_window_t winId)
143+
{
144+
qCDebug(SNIPROXY) << "trying to dock window " << winId;
145+
146+
if (m_proxies.contains(winId)) {
147+
return;
148+
}
149+
150+
addDamageWatch(winId);
151+
m_proxies[winId] = new SNIProxy(winId, this);
152+
}
153+
154+
void FdoSelectionManager::undock(xcb_window_t winId)
155+
{
156+
qCDebug(SNIPROXY) << "trying to undock window " << winId;
157+
158+
if (!m_proxies.contains(winId)) {
159+
return;
160+
}
161+
m_proxies[winId]->deleteLater();
162+
m_proxies.remove(winId);
163+
}
164+
165+
void FdoSelectionManager::onClaimedOwnership()
166+
{
167+
qCDebug(SNIPROXY) << "Manager selection claimed";
168+
}
169+
170+
void FdoSelectionManager::onFailedToClaimOwnership()
171+
{
172+
qCWarning(SNIPROXY) << "failed to claim ownership of Systray Manager";
173+
qApp->exit(-1);
174+
}
175+
176+
void FdoSelectionManager::onLostOwnership()
177+
{
178+
qCWarning(SNIPROXY) << "lost ownership of Systray Manager";
179+
qApp->exit(-1);
180+
}
181+
182+

Diff for: xembed-sni-proxy/fdoselectionmanager.h

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Registers as a embed container
3+
* Copyright (C) 2015 <[email protected]> David Edmundson
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 2.1 of the License, or (at your option) any later version.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*
19+
*/
20+
21+
#ifndef FDOSELECTIONMANAGER_H
22+
#define FDOSELECTIONMANAGER_H
23+
24+
#include <QObject>
25+
#include <QHash>
26+
#include <QAbstractNativeEventFilter>
27+
28+
#include <xcb/xcb.h>
29+
30+
class KSelectionOwner;
31+
class SNIProxy;
32+
33+
class FdoSelectionManager : public QObject, public QAbstractNativeEventFilter
34+
{
35+
Q_OBJECT
36+
37+
public:
38+
FdoSelectionManager();
39+
~FdoSelectionManager();
40+
41+
protected:
42+
bool nativeEventFilter(const QByteArray & eventType, void * message, long * result) Q_DECL_OVERRIDE;
43+
44+
private Q_SLOTS:
45+
void onClaimedOwnership();
46+
void onFailedToClaimOwnership();
47+
void onLostOwnership();
48+
49+
private:
50+
void addDamageWatch(xcb_window_t client);
51+
void dock(xcb_window_t embed_win);
52+
void undock(xcb_window_t client);
53+
54+
uint8_t m_damageEventBase;
55+
56+
QHash<xcb_window_t, u_int32_t> m_damageWatches;
57+
QHash<xcb_window_t, SNIProxy*> m_proxies;
58+
KSelectionOwner *m_selectionOwner;
59+
};
60+
61+
62+
#endif

0 commit comments

Comments
 (0)