wip: sniproxy to traymanager1
This commit is contained in:
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"type": "gdb",
|
||||||
|
"request": "launch",
|
||||||
|
"target": "./build/xembed-traymanager-proxy",
|
||||||
|
"cwd": "${workspaceRoot}",
|
||||||
|
"env": {
|
||||||
|
"DISPLAY": ":0",
|
||||||
|
"QT_LOGGING_RULES": "dde.*.debug=true;",
|
||||||
|
},
|
||||||
|
"valuesFormatting": "prettyPrinters",
|
||||||
|
"preLaunchTask": "Build (Debug)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
.vscode/tasks.json
vendored
Normal file
19
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||||
|
// for the documentation about the tasks.json format
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "Config",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cmake",
|
||||||
|
"args": ["-Bbuild", ".", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON", "-DCMAKE_BUILD_TYPE=Debug"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build (Debug)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "cmake",
|
||||||
|
"args": ["--build", "build", "-j4"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
project(xembed-sni-proxy)
|
project(xembed-traymanager-proxy)
|
||||||
|
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON) # ensure adapter class can include related header
|
||||||
|
|
||||||
find_package(Qt6 6.8 CONFIG REQUIRED COMPONENTS DBus)
|
find_package(Qt6 6.8 CONFIG REQUIRED COMPONENTS DBus)
|
||||||
find_package(ECM REQUIRED NO_MODULE)
|
find_package(ECM REQUIRED NO_MODULE)
|
||||||
@@ -41,33 +42,35 @@ set(XCB_LIBS
|
|||||||
set(XEMBED_SNI_PROXY_SOURCES
|
set(XEMBED_SNI_PROXY_SOURCES
|
||||||
main.cpp
|
main.cpp
|
||||||
fdoselectionmanager.cpp fdoselectionmanager.h
|
fdoselectionmanager.cpp fdoselectionmanager.h
|
||||||
snidbus.cpp snidbus.h
|
traymanager1.cpp traymanager1.h
|
||||||
sniproxy.cpp
|
traymanagerproxy.cpp traymanagerproxy.h
|
||||||
xtestsender.cpp xtestsender.h
|
xtestsender.cpp xtestsender.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES org.kde.StatusNotifierItem.xml
|
set_source_files_properties(
|
||||||
sniproxy.h SNIProxy)
|
${CMAKE_CURRENT_SOURCE_DIR}/org.deepin.dde.TrayManager1.xml
|
||||||
|
PROPERTIES INCLUDE traylist.h
|
||||||
|
CLASSNAME TrayManager
|
||||||
|
)
|
||||||
|
|
||||||
set(statusnotifierwatcher_xml org.kde.StatusNotifierWatcher.xml)
|
qt_add_dbus_adaptor(XEMBED_SNI_PROXY_SOURCES org.deepin.dde.TrayManager1.xml traymanager1.h TrayManager1)
|
||||||
qt_add_dbus_interface(XEMBED_SNI_PROXY_SOURCES ${statusnotifierwatcher_xml} statusnotifierwatcher_interface)
|
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(XEMBED_SNI_PROXY_SOURCES HEADER debug.h
|
ecm_qt_declare_logging_category(XEMBED_SNI_PROXY_SOURCES HEADER debug.h
|
||||||
IDENTIFIER SNIPROXY
|
IDENTIFIER SNIPROXY
|
||||||
CATEGORY_NAME kde.xembedsniproxy
|
CATEGORY_NAME dde.xembedsniproxy
|
||||||
DEFAULT_SEVERITY Info
|
DEFAULT_SEVERITY Info
|
||||||
DESCRIPTION "xembed sni proxy"
|
DESCRIPTION "xembed sni proxy"
|
||||||
EXPORT PLASMAWORKSPACE
|
EXPORT PLASMAWORKSPACE
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(xembedsniproxy ${XEMBED_SNI_PROXY_SOURCES})
|
add_executable(xembed-traymanager-proxy ${XEMBED_SNI_PROXY_SOURCES})
|
||||||
set_property(TARGET xembedsniproxy PROPERTY AUTOMOC ON)
|
set_property(TARGET xembed-traymanager-proxy PROPERTY AUTOMOC ON)
|
||||||
|
|
||||||
|
|
||||||
set_package_properties(XCB PROPERTIES TYPE REQUIRED)
|
set_package_properties(XCB PROPERTIES TYPE REQUIRED)
|
||||||
|
|
||||||
|
|
||||||
target_link_libraries(xembedsniproxy
|
target_link_libraries(xembed-traymanager-proxy
|
||||||
Qt::Core
|
Qt::Core
|
||||||
Qt::DBus
|
Qt::DBus
|
||||||
KF6::WindowSystem
|
KF6::WindowSystem
|
||||||
@@ -75,7 +78,7 @@ target_link_libraries(xembedsniproxy
|
|||||||
X11::Xtst
|
X11::Xtst
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS xembedsniproxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
install(TARGETS xembed-traymanager-proxy ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||||
install(FILES xembedsniproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
|
install(FILES xembedsniproxy.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR})
|
||||||
|
|
||||||
ecm_install_configured_files(INPUT plasma-xembedsniproxy.service.in @ONLY DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR})
|
ecm_install_configured_files(INPUT plasma-xembedsniproxy.service.in @ONLY DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR})
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
|
||||||
#include <KSelectionOwner>
|
#include <KSelectionOwner>
|
||||||
|
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
#include <xcb/xcb_atom.h>
|
#include <xcb/xcb_atom.h>
|
||||||
#include <xcb/xcb_event.h>
|
#include <xcb/xcb_event.h>
|
||||||
|
|
||||||
#include "../c_ptr.h"
|
#include "traymanager1.h"
|
||||||
#include "sniproxy.h"
|
#include "traymanagerproxy.h"
|
||||||
#include "xcbutils.h"
|
#include "xcbutils.h"
|
||||||
|
|
||||||
#define SYSTEM_TRAY_REQUEST_DOCK 0
|
#define SYSTEM_TRAY_REQUEST_DOCK 0
|
||||||
@@ -31,7 +32,7 @@ FdoSelectionManager::FdoSelectionManager()
|
|||||||
, m_x11Interface(qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
|
, m_x11Interface(qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
|
||||||
, m_selectionOwner(new KSelectionOwner(Xcb::atoms->selectionAtom, -1, this))
|
, m_selectionOwner(new KSelectionOwner(Xcb::atoms->selectionAtom, -1, this))
|
||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "starting";
|
qDebug(SNIPROXY) << "starting";
|
||||||
|
|
||||||
// we may end up calling QCoreApplication::quit() in this method, at which point we need the event loop running
|
// 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);
|
QTimer::singleShot(0, this, &FdoSelectionManager::init);
|
||||||
@@ -132,18 +133,18 @@ bool FdoSelectionManager::nativeEventFilter(const QByteArray &eventType, void *m
|
|||||||
}
|
}
|
||||||
} else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
|
} else if (responseType == m_damageEventBase + XCB_DAMAGE_NOTIFY) {
|
||||||
const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable;
|
const auto damagedWId = reinterpret_cast<xcb_damage_notify_event_t *>(ev)->drawable;
|
||||||
const auto sniProxy = m_proxies.value(damagedWId);
|
const auto tmProxy = m_proxies.value(damagedWId);
|
||||||
if (sniProxy) {
|
if (tmProxy) {
|
||||||
sniProxy->update();
|
tmProxy->update();
|
||||||
xcb_damage_subtract(m_x11Interface->connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE);
|
xcb_damage_subtract(m_x11Interface->connection(), m_damageWatches[damagedWId], XCB_NONE, XCB_NONE);
|
||||||
}
|
}
|
||||||
} else if (responseType == XCB_CONFIGURE_REQUEST) {
|
} else if (responseType == XCB_CONFIGURE_REQUEST) {
|
||||||
const auto event = reinterpret_cast<xcb_configure_request_event_t *>(ev);
|
const auto event = reinterpret_cast<xcb_configure_request_event_t *>(ev);
|
||||||
const auto sniProxy = m_proxies.value(event->window);
|
const auto tmProxy = m_proxies.value(event->window);
|
||||||
if (sniProxy) {
|
if (tmProxy) {
|
||||||
// The embedded window tries to move or resize. Ignore move, handle resize only.
|
// The embedded window tries to move or resize. Ignore move, handle resize only.
|
||||||
if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
|
if ((event->value_mask & XCB_CONFIG_WINDOW_WIDTH) || (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
|
||||||
sniProxy->resizeWindow(event->width, event->height);
|
tmProxy->resizeWindow(event->width, event->height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,7 +161,13 @@ void FdoSelectionManager::dock(xcb_window_t winId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (addDamageWatch(winId)) {
|
if (addDamageWatch(winId)) {
|
||||||
m_proxies[winId] = new SNIProxy(winId, this);
|
auto proxy = new TrayManagerProxy(winId, this);
|
||||||
|
m_proxies[winId] = proxy;
|
||||||
|
|
||||||
|
// Register with TrayManager1 if available
|
||||||
|
if (m_trayManager) {
|
||||||
|
m_trayManager->registerIcon(winId, proxy);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +178,12 @@ void FdoSelectionManager::undock(xcb_window_t winId)
|
|||||||
if (!m_proxies.contains(winId)) {
|
if (!m_proxies.contains(winId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unregister from TrayManager1 if available
|
||||||
|
if (m_trayManager) {
|
||||||
|
m_trayManager->unregisterIcon(winId);
|
||||||
|
}
|
||||||
|
|
||||||
m_proxies[winId]->deleteLater();
|
m_proxies[winId]->deleteLater();
|
||||||
m_proxies.remove(winId);
|
m_proxies.remove(winId);
|
||||||
}
|
}
|
||||||
@@ -179,6 +192,7 @@ void FdoSelectionManager::onClaimedOwnership()
|
|||||||
{
|
{
|
||||||
qCDebug(SNIPROXY) << "Manager selection claimed";
|
qCDebug(SNIPROXY) << "Manager selection claimed";
|
||||||
|
|
||||||
|
initTrayManager();
|
||||||
setSystemTrayVisual();
|
setSystemTrayVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,3 +238,26 @@ void FdoSelectionManager::setSystemTrayVisual()
|
|||||||
|
|
||||||
xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_selectionOwner->ownerWindow(), Xcb::atoms->visualAtom, XCB_ATOM_VISUALID, 32, 1, &trayVisual);
|
xcb_change_property(c, XCB_PROP_MODE_REPLACE, m_selectionOwner->ownerWindow(), Xcb::atoms->visualAtom, XCB_ATOM_VISUALID, 32, 1, &trayVisual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FdoSelectionManager::initTrayManager()
|
||||||
|
{
|
||||||
|
// Create and register the TrayManager1 DBus interface
|
||||||
|
if (!m_trayManager) {
|
||||||
|
m_trayManager = new TrayManager1(this);
|
||||||
|
|
||||||
|
// Export the object on DBus
|
||||||
|
QDBusConnection::sessionBus().registerObject(
|
||||||
|
QStringLiteral("/org/deepin/dde/TrayManager1"),
|
||||||
|
m_trayManager,
|
||||||
|
QDBusConnection::ExportAdaptors
|
||||||
|
);
|
||||||
|
|
||||||
|
// Request the service name
|
||||||
|
QDBusConnection::sessionBus().registerService(
|
||||||
|
QStringLiteral("org.deepin.dde.TrayManager1")
|
||||||
|
);
|
||||||
|
|
||||||
|
qCDebug(SNIPROXY) << "TrayManager1 DBus interface registered";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
class KSelectionOwner;
|
class KSelectionOwner;
|
||||||
class SNIProxy;
|
class TrayManagerProxy;
|
||||||
|
class TrayManager1;
|
||||||
|
|
||||||
class FdoSelectionManager : public QObject, public QAbstractNativeEventFilter
|
class FdoSelectionManager : public QObject, public QAbstractNativeEventFilter
|
||||||
{
|
{
|
||||||
@@ -39,12 +40,14 @@ private:
|
|||||||
void dock(xcb_window_t embed_win);
|
void dock(xcb_window_t embed_win);
|
||||||
void undock(xcb_window_t client);
|
void undock(xcb_window_t client);
|
||||||
void setSystemTrayVisual();
|
void setSystemTrayVisual();
|
||||||
|
void initTrayManager();
|
||||||
|
|
||||||
QNativeInterface::QX11Application *m_x11Interface = nullptr;
|
QNativeInterface::QX11Application *m_x11Interface = nullptr;
|
||||||
|
TrayManager1 *m_trayManager = nullptr;
|
||||||
|
|
||||||
uint8_t m_damageEventBase;
|
uint8_t m_damageEventBase;
|
||||||
|
|
||||||
QHash<xcb_window_t, u_int32_t> m_damageWatches;
|
QHash<xcb_window_t, u_int32_t> m_damageWatches;
|
||||||
QHash<xcb_window_t, SNIProxy *> m_proxies;
|
QHash<xcb_window_t, TrayManagerProxy *> m_proxies;
|
||||||
KSelectionOwner *m_selectionOwner;
|
KSelectionOwner *m_selectionOwner;
|
||||||
};
|
};
|
||||||
|
|||||||
8
main.cpp
8
main.cpp
@@ -10,7 +10,6 @@
|
|||||||
#include "fdoselectionmanager.h"
|
#include "fdoselectionmanager.h"
|
||||||
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#include "snidbus.h"
|
|
||||||
#include "xcbutils.h"
|
#include "xcbutils.h"
|
||||||
|
|
||||||
#ifdef None
|
#ifdef None
|
||||||
@@ -44,18 +43,13 @@ int main(int argc, char **argv)
|
|||||||
QGuiApplication app(argc, argv);
|
QGuiApplication app(argc, argv);
|
||||||
|
|
||||||
if (!KWindowSystem::isPlatformX11()) {
|
if (!KWindowSystem::isPlatformX11()) {
|
||||||
qFatal("xembed-sni-proxy is only useful XCB. Aborting");
|
qFatal("xembed-traymanager-proxy requires X11. Aborting");
|
||||||
}
|
}
|
||||||
|
|
||||||
app.setQuitOnLastWindowClosed(false);
|
app.setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
qDBusRegisterMetaType<KDbusImageStruct>();
|
|
||||||
qDBusRegisterMetaType<KDbusImageVector>();
|
|
||||||
qDBusRegisterMetaType<KDbusToolTipStruct>();
|
|
||||||
|
|
||||||
Xcb::atoms = new Xcb::Atoms();
|
Xcb::atoms = new Xcb::Atoms();
|
||||||
|
|
||||||
// KDBusService service(KDBusService::Unique);
|
|
||||||
FdoSelectionManager manager;
|
FdoSelectionManager manager;
|
||||||
|
|
||||||
auto rc = app.exec();
|
auto rc = app.exec();
|
||||||
|
|||||||
38
org.deepin.dde.TrayManager1.xml
Normal file
38
org.deepin.dde.TrayManager1.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||||
|
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||||
|
<node name="/org/deepin/dde/TrayManager1">
|
||||||
|
<interface name="org.deepin.dde.TrayManager1">
|
||||||
|
<!-- Properties -->
|
||||||
|
<property name="TrayIcons" type="au" access="read">
|
||||||
|
<annotation name="org.qtproject.QtDBus.QtTypeName" value="TrayList"/>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<!-- Methods -->
|
||||||
|
<method name="Manage">
|
||||||
|
<arg type="b" name="ok" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="GetName">
|
||||||
|
<arg type="u" name="win" direction="in"/>
|
||||||
|
<arg type="s" name="name" direction="out"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<method name="EnableNotification">
|
||||||
|
<arg type="u" name="win" direction="in"/>
|
||||||
|
<arg type="b" name="enabled" direction="in"/>
|
||||||
|
</method>
|
||||||
|
|
||||||
|
<!-- Signals -->
|
||||||
|
<signal name="Added">
|
||||||
|
<arg type="u" name="id"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="Removed">
|
||||||
|
<arg type="u" name="id"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="Changed">
|
||||||
|
<arg type="u" name="id"/>
|
||||||
|
</signal>
|
||||||
|
<signal name="Inited"/>
|
||||||
|
</interface>
|
||||||
|
</node>
|
||||||
131
snidbus.cpp
131
snidbus.cpp
@@ -1,131 +0,0 @@
|
|||||||
/*
|
|
||||||
SNI DBus Serialisers
|
|
||||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2009 Marco Martin <notmart@gmail.com>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "snidbus.h"
|
|
||||||
|
|
||||||
#include <QSysInfo>
|
|
||||||
#include <QtEndian>
|
|
||||||
|
|
||||||
// mostly copied from KStatusNotiferItemDbus.cpps from knotification
|
|
||||||
|
|
||||||
KDbusImageStruct::KDbusImageStruct() = default;
|
|
||||||
|
|
||||||
KDbusImageStruct::KDbusImageStruct(const QImage &image)
|
|
||||||
{
|
|
||||||
width = image.size().width();
|
|
||||||
height = image.size().height();
|
|
||||||
if (image.format() == QImage::Format_ARGB32) {
|
|
||||||
data = QByteArray((char *)image.bits(), image.sizeInBytes());
|
|
||||||
} else {
|
|
||||||
QImage image32 = image.convertToFormat(QImage::Format_ARGB32);
|
|
||||||
data = QByteArray((char *)image32.bits(), image32.sizeInBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
// swap to network byte order if we are little endian
|
|
||||||
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
|
|
||||||
auto *uintBuf = (quint32 *)data.data();
|
|
||||||
for (uint i = 0; i < data.size() / sizeof(quint32); ++i) {
|
|
||||||
*uintBuf = qToBigEndian(*uintBuf);
|
|
||||||
++uintBuf;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshall the ImageStruct data into a D-BUS argument
|
|
||||||
const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon)
|
|
||||||
{
|
|
||||||
argument.beginStructure();
|
|
||||||
argument << icon.width;
|
|
||||||
argument << icon.height;
|
|
||||||
argument << icon.data;
|
|
||||||
argument.endStructure();
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the ImageStruct data from the D-BUS argument
|
|
||||||
const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon)
|
|
||||||
{
|
|
||||||
qint32 width;
|
|
||||||
qint32 height;
|
|
||||||
QByteArray data;
|
|
||||||
|
|
||||||
argument.beginStructure();
|
|
||||||
argument >> width;
|
|
||||||
argument >> height;
|
|
||||||
argument >> data;
|
|
||||||
argument.endStructure();
|
|
||||||
|
|
||||||
icon.width = width;
|
|
||||||
icon.height = height;
|
|
||||||
icon.data = data;
|
|
||||||
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshall the ImageVector data into a D-BUS argument
|
|
||||||
const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector)
|
|
||||||
{
|
|
||||||
argument.beginArray(qMetaTypeId<KDbusImageStruct>());
|
|
||||||
for (int i = 0; i < iconVector.size(); ++i) {
|
|
||||||
argument << iconVector[i];
|
|
||||||
}
|
|
||||||
argument.endArray();
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the ImageVector data from the D-BUS argument
|
|
||||||
const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector)
|
|
||||||
{
|
|
||||||
argument.beginArray();
|
|
||||||
iconVector.clear();
|
|
||||||
|
|
||||||
while (!argument.atEnd()) {
|
|
||||||
KDbusImageStruct element;
|
|
||||||
argument >> element;
|
|
||||||
iconVector.append(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
argument.endArray();
|
|
||||||
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshall the ToolTipStruct data into a D-BUS argument
|
|
||||||
const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip)
|
|
||||||
{
|
|
||||||
argument.beginStructure();
|
|
||||||
argument << toolTip.icon;
|
|
||||||
argument << toolTip.image;
|
|
||||||
argument << toolTip.title;
|
|
||||||
argument << toolTip.subTitle;
|
|
||||||
argument.endStructure();
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the ToolTipStruct data from the D-BUS argument
|
|
||||||
const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip)
|
|
||||||
{
|
|
||||||
QString icon;
|
|
||||||
KDbusImageVector image;
|
|
||||||
QString title;
|
|
||||||
QString subTitle;
|
|
||||||
|
|
||||||
argument.beginStructure();
|
|
||||||
argument >> icon;
|
|
||||||
argument >> image;
|
|
||||||
argument >> title;
|
|
||||||
argument >> subTitle;
|
|
||||||
argument.endStructure();
|
|
||||||
|
|
||||||
toolTip.icon = icon;
|
|
||||||
toolTip.image = image;
|
|
||||||
toolTip.title = title;
|
|
||||||
toolTip.subTitle = subTitle;
|
|
||||||
|
|
||||||
return argument;
|
|
||||||
}
|
|
||||||
41
snidbus.h
41
snidbus.h
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
SNI Dbus serialisers
|
|
||||||
Copyright 2015 <davidedmundson@kde.org> David Edmundson
|
|
||||||
|
|
||||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QDBusArgument>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QList>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
// Custom message type for DBus
|
|
||||||
struct KDbusImageStruct {
|
|
||||||
KDbusImageStruct();
|
|
||||||
KDbusImageStruct(const QImage &image);
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
QByteArray data;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef QList<KDbusImageStruct> KDbusImageVector;
|
|
||||||
|
|
||||||
struct KDbusToolTipStruct {
|
|
||||||
QString icon;
|
|
||||||
KDbusImageVector image;
|
|
||||||
QString title;
|
|
||||||
QString subTitle;
|
|
||||||
};
|
|
||||||
|
|
||||||
const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &icon);
|
|
||||||
const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &icon);
|
|
||||||
|
|
||||||
const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &iconVector);
|
|
||||||
const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &iconVector);
|
|
||||||
|
|
||||||
const QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &toolTip);
|
|
||||||
const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &toolTip);
|
|
||||||
635
sniproxy.cpp
635
sniproxy.cpp
@@ -1,635 +0,0 @@
|
|||||||
/*
|
|
||||||
Holds one embedded window, registers as DBus entry
|
|
||||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "sniproxy.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <span>
|
|
||||||
#include <xcb/xcb_atom.h>
|
|
||||||
#include <xcb/xcb_event.h>
|
|
||||||
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include <QBitmap>
|
|
||||||
|
|
||||||
#include <KWindowInfo>
|
|
||||||
#include <KWindowSystem>
|
|
||||||
#include <netwm.h>
|
|
||||||
|
|
||||||
#include "statusnotifieritemadaptor.h"
|
|
||||||
#include "statusnotifierwatcher_interface.h"
|
|
||||||
|
|
||||||
#include "../c_ptr.h"
|
|
||||||
#include "debug.h"
|
|
||||||
#include "xcbutils.h"
|
|
||||||
#include "xtestsender.h"
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
|
|
||||||
// #define VISUAL_DEBUG
|
|
||||||
|
|
||||||
#define SNI_WATCHER_SERVICE_NAME "org.kde.StatusNotifierWatcher"
|
|
||||||
#define SNI_WATCHER_PATH "/StatusNotifierWatcher"
|
|
||||||
|
|
||||||
#ifdef Status
|
|
||||||
typedef Status XStatus;
|
|
||||||
#undef Status
|
|
||||||
typedef XStatus Status;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static uint16_t s_embedSize = 32; // max size of window to embed. We no longer resize the embedded window as Chromium acts stupidly.
|
|
||||||
static unsigned int XEMBED_VERSION = 0;
|
|
||||||
|
|
||||||
int SNIProxy::s_serviceCount = 0;
|
|
||||||
|
|
||||||
void xembed_message_send(xcb_window_t towin, long message, long d1, long d2, long d3)
|
|
||||||
{
|
|
||||||
xcb_client_message_event_t ev;
|
|
||||||
|
|
||||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
|
||||||
ev.window = towin;
|
|
||||||
ev.format = 32;
|
|
||||||
ev.data.data32[0] = XCB_CURRENT_TIME;
|
|
||||||
ev.data.data32[1] = message;
|
|
||||||
ev.data.data32[2] = d1;
|
|
||||||
ev.data.data32[3] = d2;
|
|
||||||
ev.data.data32[4] = d3;
|
|
||||||
ev.type = Xcb::atoms->xembedAtom;
|
|
||||||
xcb_send_event(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection(), false, towin, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool checkWindowOrDescendantWantButtonEvents(xcb_window_t window)
|
|
||||||
{
|
|
||||||
auto connection = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
|
|
||||||
auto waCookie = xcb_get_window_attributes(connection, window);
|
|
||||||
UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(connection, waCookie, nullptr));
|
|
||||||
if (windowAttributes && windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (windowAttributes && windowAttributes->do_not_propagate_mask & XCB_EVENT_MASK_BUTTON_PRESS) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
auto treeCookie = xcb_query_tree(connection, window);
|
|
||||||
UniqueCPointer<xcb_query_tree_reply_t> tree(xcb_query_tree_reply(connection, treeCookie, nullptr));
|
|
||||||
if (!tree) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::span<xcb_window_t> children(xcb_query_tree_children(tree.get()), xcb_query_tree_children_length(tree.get()));
|
|
||||||
return std::ranges::any_of(children, &checkWindowOrDescendantWantButtonEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
SNIProxy::SNIProxy(xcb_window_t wid, QObject *parent)
|
|
||||||
: QObject(parent)
|
|
||||||
,
|
|
||||||
// Work round a bug in our SNIWatcher with multiple SNIs per connection.
|
|
||||||
// there is an undocumented feature that you can register an SNI by path, however it doesn't detect an object on a service being removed, only the entire
|
|
||||||
// service closing instead lets use one DBus connection per SNI
|
|
||||||
m_dbus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, QStringLiteral("XembedSniProxy%1").arg(s_serviceCount++)))
|
|
||||||
, m_x11Interface(qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
|
|
||||||
, m_windowId(wid)
|
|
||||||
, m_injectMode(Direct)
|
|
||||||
{
|
|
||||||
// create new SNI
|
|
||||||
new StatusNotifierItemAdaptor(this);
|
|
||||||
m_dbus.registerObject(QStringLiteral("/StatusNotifierItem"), this);
|
|
||||||
|
|
||||||
auto statusNotifierWatcher =
|
|
||||||
new org::kde::StatusNotifierWatcher(QStringLiteral(SNI_WATCHER_SERVICE_NAME), QStringLiteral(SNI_WATCHER_PATH), QDBusConnection::sessionBus(), this);
|
|
||||||
auto reply = statusNotifierWatcher->RegisterStatusNotifierItem(m_dbus.baseService());
|
|
||||||
reply.waitForFinished();
|
|
||||||
if (reply.isError()) {
|
|
||||||
qCWarning(SNIPROXY) << "could not register SNI:" << reply.error().message();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
|
|
||||||
// create a container window
|
|
||||||
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
|
||||||
m_containerWid = xcb_generate_id(c);
|
|
||||||
uint32_t values[3];
|
|
||||||
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
|
||||||
values[0] = screen->black_pixel; // draw a solid background so the embedded icon doesn't get garbage in it
|
|
||||||
values[1] = true; // bypass wM
|
|
||||||
values[2] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
|
|
||||||
xcb_create_window(c, /* connection */
|
|
||||||
XCB_COPY_FROM_PARENT, /* depth */
|
|
||||||
m_containerWid, /* window Id */
|
|
||||||
screen->root, /* parent window */
|
|
||||||
0,
|
|
||||||
0, /* x, y */
|
|
||||||
s_embedSize,
|
|
||||||
s_embedSize, /* width, height */
|
|
||||||
0, /* border_width */
|
|
||||||
XCB_WINDOW_CLASS_INPUT_OUTPUT, /* class */
|
|
||||||
screen->root_visual, /* visual */
|
|
||||||
mask,
|
|
||||||
values); /* masks */
|
|
||||||
|
|
||||||
/*
|
|
||||||
We need the window to exist and be mapped otherwise the child won't render it's contents
|
|
||||||
|
|
||||||
We also need it to exist in the right place to get the clicks working as GTK will check sendEvent locations to see if our window is in the right place.
|
|
||||||
So even though our contents are drawn via compositing we still put this window in the right place
|
|
||||||
|
|
||||||
Set opacity to 0 just to make sure this container never appears
|
|
||||||
And set the input region to null so everything just clicks through
|
|
||||||
*/
|
|
||||||
|
|
||||||
setActiveForInput(false);
|
|
||||||
|
|
||||||
#ifndef VISUAL_DEBUG
|
|
||||||
|
|
||||||
NETWinInfo wm(c, m_containerWid, screen->root, NET::Properties(), NET::Properties2());
|
|
||||||
wm.setOpacity(0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
xcb_flush(c);
|
|
||||||
|
|
||||||
xcb_map_window(c, m_containerWid);
|
|
||||||
|
|
||||||
xcb_reparent_window(c, wid, m_containerWid, 0, 0);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Render the embedded window offscreen
|
|
||||||
*/
|
|
||||||
xcb_composite_redirect_window(c, wid, XCB_COMPOSITE_REDIRECT_MANUAL);
|
|
||||||
|
|
||||||
/* we grab the window, but also make sure it's automatically reparented back
|
|
||||||
* to the root window if we should die.
|
|
||||||
*/
|
|
||||||
xcb_change_save_set(c, XCB_SET_MODE_INSERT, wid);
|
|
||||||
|
|
||||||
// tell client we're embedding it
|
|
||||||
xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, 0, m_containerWid, XEMBED_VERSION);
|
|
||||||
|
|
||||||
// move window we're embedding
|
|
||||||
const uint32_t windowMoveConfigVals[2] = {0, 0};
|
|
||||||
|
|
||||||
xcb_configure_window(c, wid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals);
|
|
||||||
|
|
||||||
QSize clientWindowSize = calculateClientWindowSize();
|
|
||||||
|
|
||||||
// show the embedded window otherwise nothing happens
|
|
||||||
xcb_map_window(c, wid);
|
|
||||||
|
|
||||||
xcb_clear_area(c, 0, wid, 0, 0, clientWindowSize.width(), clientWindowSize.height());
|
|
||||||
|
|
||||||
xcb_flush(c);
|
|
||||||
|
|
||||||
// guess which input injection method to use
|
|
||||||
// we can either send an X event to the client or XTest
|
|
||||||
// some don't support direct X events (GTK3/4), and some don't support XTest because reasons
|
|
||||||
// note also some clients might not have the XTest extension. We may as well assume it does and just fail to send later.
|
|
||||||
|
|
||||||
// we query if the client selected button presses in the event mask
|
|
||||||
// if the client does supports that we send directly, otherwise we'll use xtest
|
|
||||||
auto waCookie = xcb_get_window_attributes(c, wid);
|
|
||||||
UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr));
|
|
||||||
if (!checkWindowOrDescendantWantButtonEvents(wid)) {
|
|
||||||
m_injectMode = XTest;
|
|
||||||
}
|
|
||||||
|
|
||||||
// there's no damage event for the first paint, and sometimes it's not drawn immediately
|
|
||||||
// not ideal, but it works better than nothing
|
|
||||||
// test with xchat before changing
|
|
||||||
QTimer::singleShot(500, this, &SNIProxy::update);
|
|
||||||
}
|
|
||||||
|
|
||||||
SNIProxy::~SNIProxy()
|
|
||||||
{
|
|
||||||
xcb_destroy_window(m_x11Interface->connection(), m_containerWid);
|
|
||||||
QDBusConnection::disconnectFromBus(m_dbus.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::update()
|
|
||||||
{
|
|
||||||
QImage image = getImageNonComposite();
|
|
||||||
if (image.isNull()) {
|
|
||||||
qCDebug(SNIPROXY) << "No xembed icon for" << m_windowId << Title();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int w = image.width();
|
|
||||||
int h = image.height();
|
|
||||||
|
|
||||||
m_pixmap = QPixmap::fromImage(std::move(image));
|
|
||||||
if (w > s_embedSize || h > s_embedSize) {
|
|
||||||
qCDebug(SNIPROXY) << "Scaling pixmap of window" << m_windowId << Title() << "from w*h" << w << h;
|
|
||||||
m_pixmap = m_pixmap.scaled(s_embedSize, s_embedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
|
||||||
}
|
|
||||||
Q_EMIT NewIcon();
|
|
||||||
Q_EMIT NewToolTip();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::resizeWindow(const uint16_t width, const uint16_t height) const
|
|
||||||
{
|
|
||||||
auto connection = m_x11Interface->connection();
|
|
||||||
|
|
||||||
uint16_t widthNormalized = std::min(width, s_embedSize);
|
|
||||||
uint16_t heighNormalized = std::min(height, s_embedSize);
|
|
||||||
|
|
||||||
const uint32_t windowSizeConfigVals[2] = {widthNormalized, heighNormalized};
|
|
||||||
xcb_configure_window(connection, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, windowSizeConfigVals);
|
|
||||||
|
|
||||||
xcb_flush(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize SNIProxy::calculateClientWindowSize() const
|
|
||||||
{
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
|
|
||||||
auto cookie = xcb_get_geometry(c, m_windowId);
|
|
||||||
UniqueCPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(c, cookie, nullptr));
|
|
||||||
|
|
||||||
QSize clientWindowSize;
|
|
||||||
if (clientGeom) {
|
|
||||||
clientWindowSize = QSize(clientGeom->width, clientGeom->height);
|
|
||||||
}
|
|
||||||
// if the window is a clearly stupid size resize to be something sensible
|
|
||||||
// this is needed as chromium and such when resized just fill the icon with transparent space and only draw in the middle
|
|
||||||
// however KeePass2 does need this as by default the window size is 273px wide and is not transparent
|
|
||||||
// use an arbitrary heuristic to make sure icons are always sensible
|
|
||||||
if (clientWindowSize.isEmpty() || clientWindowSize.width() > s_embedSize || clientWindowSize.height() > s_embedSize) {
|
|
||||||
qCDebug(SNIPROXY) << "Resizing window" << m_windowId << Title() << "from w*h" << clientWindowSize;
|
|
||||||
|
|
||||||
resizeWindow(s_embedSize, s_embedSize);
|
|
||||||
|
|
||||||
clientWindowSize = QSize(s_embedSize, s_embedSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
return clientWindowSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sni_cleanup_xcb_image(void *data)
|
|
||||||
{
|
|
||||||
xcb_image_destroy(static_cast<xcb_image_t *>(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SNIProxy::isTransparentImage(const QImage &image) const
|
|
||||||
{
|
|
||||||
int w = image.width();
|
|
||||||
int h = image.height();
|
|
||||||
|
|
||||||
// check for the center and sub-center pixels first and avoid full image scan
|
|
||||||
if (!(qAlpha(image.pixel(w >> 1, h >> 1)) + qAlpha(image.pixel(w >> 2, h >> 2)) == 0))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// skip scan altogether if sub-center pixel found to be opaque
|
|
||||||
// and break out from the outer loop too on full scan
|
|
||||||
for (int x = 0; x < w; ++x) {
|
|
||||||
for (int y = 0; y < h; ++y) {
|
|
||||||
if (qAlpha(image.pixel(x, y))) {
|
|
||||||
// Found an opaque pixel.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage SNIProxy::getImageNonComposite() const
|
|
||||||
{
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
|
|
||||||
QSize clientWindowSize = calculateClientWindowSize();
|
|
||||||
|
|
||||||
xcb_image_t *image = xcb_image_get(c, m_windowId, 0, 0, clientWindowSize.width(), clientWindowSize.height(), 0xFFFFFFFF, XCB_IMAGE_FORMAT_Z_PIXMAP);
|
|
||||||
|
|
||||||
// Don't hook up cleanup yet, we may use a different QImage after all
|
|
||||||
QImage naiveConversion;
|
|
||||||
if (image) {
|
|
||||||
naiveConversion = QImage(image->data, image->width, image->height, QImage::Format_ARGB32);
|
|
||||||
} else {
|
|
||||||
qCDebug(SNIPROXY) << "Skip NULL image returned from xcb_image_get() for" << m_windowId << Title();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isTransparentImage(naiveConversion)) {
|
|
||||||
QImage elaborateConversion = QImage(convertFromNative(image));
|
|
||||||
|
|
||||||
// Update icon only if it is at least partially opaque.
|
|
||||||
// This is just a workaround for X11 bug: xembed icon may suddenly
|
|
||||||
// become transparent for a one or few frames. Reproducible at least
|
|
||||||
// with WINE applications.
|
|
||||||
if (isTransparentImage(elaborateConversion)) {
|
|
||||||
qCDebug(SNIPROXY) << "Skip transparent xembed icon for" << m_windowId << Title();
|
|
||||||
return {};
|
|
||||||
} else
|
|
||||||
return elaborateConversion;
|
|
||||||
} else {
|
|
||||||
// Now we are sure we can eventually delete the xcb_image_t with this version
|
|
||||||
return {image->data, image->width, image->height, image->stride, QImage::Format_ARGB32, sni_cleanup_xcb_image, image};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage SNIProxy::convertFromNative(xcb_image_t *xcbImage) const
|
|
||||||
{
|
|
||||||
QImage::Format format = QImage::Format_Invalid;
|
|
||||||
|
|
||||||
switch (xcbImage->depth) {
|
|
||||||
case 1:
|
|
||||||
format = QImage::Format_MonoLSB;
|
|
||||||
break;
|
|
||||||
case 16:
|
|
||||||
format = QImage::Format_RGB16;
|
|
||||||
break;
|
|
||||||
case 24:
|
|
||||||
format = QImage::Format_RGB32;
|
|
||||||
break;
|
|
||||||
case 30: {
|
|
||||||
// Qt doesn't have a matching image format. We need to convert manually
|
|
||||||
auto *pixels = reinterpret_cast<quint32 *>(xcbImage->data);
|
|
||||||
for (uint i = 0; i < (xcbImage->size / 4); i++) {
|
|
||||||
int r = (pixels[i] >> 22) & 0xff;
|
|
||||||
int g = (pixels[i] >> 12) & 0xff;
|
|
||||||
int b = (pixels[i] >> 2) & 0xff;
|
|
||||||
|
|
||||||
pixels[i] = qRgba(r, g, b, 0xff);
|
|
||||||
}
|
|
||||||
// fall through, Qt format is still Format_ARGB32_Premultiplied
|
|
||||||
Q_FALLTHROUGH();
|
|
||||||
}
|
|
||||||
case 32:
|
|
||||||
format = QImage::Format_ARGB32_Premultiplied;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return {}; // we don't know
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage image(xcbImage->data, xcbImage->width, xcbImage->height, xcbImage->stride, format, sni_cleanup_xcb_image, xcbImage);
|
|
||||||
|
|
||||||
if (image.isNull()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format == QImage::Format_RGB32 && xcbImage->bpp == 32) {
|
|
||||||
QImage m = image.createHeuristicMask();
|
|
||||||
QPixmap p = QPixmap::fromImage(std::move(image));
|
|
||||||
p.setMask(QBitmap::fromImage(std::move(m)));
|
|
||||||
image = p.toImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
// work around an abort in QImage::color
|
|
||||||
if (image.format() == QImage::Format_MonoLSB) {
|
|
||||||
image.setColorCount(2);
|
|
||||||
image.setColor(0, QColor(Qt::white).rgb());
|
|
||||||
image.setColor(1, QColor(Qt::black).rgb());
|
|
||||||
}
|
|
||||||
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Wine is using XWindow Shape Extension for transparent tray icons.
|
|
||||||
We need to find first clickable point starting from top-left.
|
|
||||||
*/
|
|
||||||
QPoint SNIProxy::calculateClickPoint() const
|
|
||||||
{
|
|
||||||
QSize clientSize = calculateClientWindowSize();
|
|
||||||
QPoint clickPoint = QPoint(clientSize.width() / 2, clientSize.height() / 2);
|
|
||||||
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
|
|
||||||
// request extent to check if shape has been set
|
|
||||||
xcb_shape_query_extents_cookie_t extentsCookie = xcb_shape_query_extents(c, m_windowId);
|
|
||||||
// at the same time make the request for rectangles (even if this request isn't needed)
|
|
||||||
xcb_shape_get_rectangles_cookie_t rectaglesCookie = xcb_shape_get_rectangles(c, m_windowId, XCB_SHAPE_SK_BOUNDING);
|
|
||||||
|
|
||||||
UniqueCPointer<xcb_shape_query_extents_reply_t> extentsReply(xcb_shape_query_extents_reply(c, extentsCookie, nullptr));
|
|
||||||
UniqueCPointer<xcb_shape_get_rectangles_reply_t> rectanglesReply(xcb_shape_get_rectangles_reply(c, rectaglesCookie, nullptr));
|
|
||||||
|
|
||||||
if (!extentsReply || !rectanglesReply || !extentsReply->bounding_shaped) {
|
|
||||||
return clickPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
xcb_rectangle_t *rectangles = xcb_shape_get_rectangles_rectangles(rectanglesReply.get());
|
|
||||||
if (!rectangles) {
|
|
||||||
return clickPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QImage image = getImageNonComposite();
|
|
||||||
|
|
||||||
double minLength = sqrt(pow(image.height(), 2) + pow(image.width(), 2));
|
|
||||||
const int nRectangles = xcb_shape_get_rectangles_rectangles_length(rectanglesReply.get());
|
|
||||||
for (int i = 0; i < nRectangles; ++i) {
|
|
||||||
double length = sqrt(pow(rectangles[i].x, 2) + pow(rectangles[i].y, 2));
|
|
||||||
if (length < minLength) {
|
|
||||||
minLength = length;
|
|
||||||
clickPoint = QPoint(rectangles[i].x, rectangles[i].y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(SNIPROXY) << "Click point:" << clickPoint;
|
|
||||||
return clickPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::setActiveForInput(bool active) const
|
|
||||||
{
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
if (active) {
|
|
||||||
xcb_rectangle_t rectangle;
|
|
||||||
rectangle.x = 0;
|
|
||||||
rectangle.y = 0;
|
|
||||||
rectangle.width = s_embedSize;
|
|
||||||
rectangle.height = s_embedSize;
|
|
||||||
xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, 0, m_containerWid, 0, 0, 1, &rectangle);
|
|
||||||
|
|
||||||
const uint32_t stackData[] = {XCB_STACK_MODE_ABOVE};
|
|
||||||
xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackData);
|
|
||||||
} else {
|
|
||||||
xcb_rectangle_t rectangle;
|
|
||||||
rectangle.x = 0;
|
|
||||||
rectangle.y = 0;
|
|
||||||
rectangle.width = 0;
|
|
||||||
rectangle.height = 0;
|
|
||||||
xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, 0, m_containerWid, 0, 0, 1, &rectangle);
|
|
||||||
|
|
||||||
const uint32_t stackData[] = {XCB_STACK_MODE_BELOW};
|
|
||||||
xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_STACK_MODE, stackData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//____________properties__________
|
|
||||||
|
|
||||||
QString SNIProxy::Category() const
|
|
||||||
{
|
|
||||||
return QStringLiteral("ApplicationStatus");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SNIProxy::Id() const
|
|
||||||
{
|
|
||||||
const auto title = Title();
|
|
||||||
// we always need /some/ ID so if no window title exists, just use the winId.
|
|
||||||
if (title.isEmpty()) {
|
|
||||||
return QString::number(m_windowId);
|
|
||||||
}
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
KDbusImageVector SNIProxy::IconPixmap() const
|
|
||||||
{
|
|
||||||
KDbusImageStruct dbusImage(m_pixmap.toImage());
|
|
||||||
return KDbusImageVector() << dbusImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SNIProxy::ItemIsMenu() const
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SNIProxy::Status() const
|
|
||||||
{
|
|
||||||
return QStringLiteral("Active");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SNIProxy::Title() const
|
|
||||||
{
|
|
||||||
KWindowInfo window(m_windowId, NET::WMName);
|
|
||||||
return window.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
int SNIProxy::WindowId() const
|
|
||||||
{
|
|
||||||
return m_windowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
//____________actions_____________
|
|
||||||
|
|
||||||
void SNIProxy::Activate(int x, int y)
|
|
||||||
{
|
|
||||||
sendClick(XCB_BUTTON_INDEX_1, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::SecondaryActivate(int x, int y)
|
|
||||||
{
|
|
||||||
sendClick(XCB_BUTTON_INDEX_2, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::ContextMenu(int x, int y)
|
|
||||||
{
|
|
||||||
sendClick(XCB_BUTTON_INDEX_3, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::Scroll(int delta, const QString &orientation)
|
|
||||||
{
|
|
||||||
if (orientation == QLatin1String("vertical")) {
|
|
||||||
sendClick(delta > 0 ? XCB_BUTTON_INDEX_4 : XCB_BUTTON_INDEX_5, 0, 0);
|
|
||||||
} else {
|
|
||||||
sendClick(delta > 0 ? 6 : 7, 0, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SNIProxy::sendClick(uint8_t mouseButton, int x, int y)
|
|
||||||
{
|
|
||||||
// it's best not to look at this code
|
|
||||||
// GTK doesn't like send_events and double checks the mouse position matches where the window is and is top level
|
|
||||||
// in order to solve this we move the embed container over to where the mouse is then replay the event using send_event
|
|
||||||
// if patching, test with xchat + xchat context menus
|
|
||||||
|
|
||||||
// note x,y are not actually where the mouse is, but the plasmoid
|
|
||||||
// ideally we should make this match the plasmoid hit area
|
|
||||||
|
|
||||||
qCDebug(SNIPROXY) << "Received click" << mouseButton << "with passed x*y" << x << y;
|
|
||||||
|
|
||||||
auto c = m_x11Interface->connection();
|
|
||||||
|
|
||||||
auto cookieSize = xcb_get_geometry(c, m_windowId);
|
|
||||||
UniqueCPointer<xcb_get_geometry_reply_t> clientGeom(xcb_get_geometry_reply(c, cookieSize, nullptr));
|
|
||||||
|
|
||||||
if (!clientGeom) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*qCDebug(SNIPROXY) << "samescreen" << pointer->same_screen << endl
|
|
||||||
<< "root x*y" << pointer->root_x << pointer->root_y << endl
|
|
||||||
<< "win x*y" << pointer->win_x << pointer->win_y;*/
|
|
||||||
|
|
||||||
// move our window so the mouse is within its geometry
|
|
||||||
uint32_t configVals[2] = {0, 0};
|
|
||||||
const QPoint clickPoint = calculateClickPoint();
|
|
||||||
|
|
||||||
if (mouseButton >= XCB_BUTTON_INDEX_4) {
|
|
||||||
// scroll event, take pointer position
|
|
||||||
auto cookie = xcb_query_pointer(c, m_windowId);
|
|
||||||
UniqueCPointer<xcb_query_pointer_reply_t> pointer(xcb_query_pointer_reply(c, cookie, nullptr));
|
|
||||||
configVals[0] = pointer->root_x;
|
|
||||||
configVals[1] = pointer->root_y;
|
|
||||||
} else {
|
|
||||||
configVals[0] = static_cast<uint32_t>(x - clickPoint.x());
|
|
||||||
configVals[1] = static_cast<uint32_t>(y - clickPoint.y());
|
|
||||||
}
|
|
||||||
xcb_configure_window(c, m_containerWid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, configVals);
|
|
||||||
|
|
||||||
setActiveForInput(true);
|
|
||||||
|
|
||||||
if (qgetenv("XDG_SESSION_TYPE") == "wayland") {
|
|
||||||
xcb_warp_pointer(c, XCB_NONE, m_windowId, 0, 0, 0, 0, clickPoint.x(), clickPoint.y());
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse down
|
|
||||||
if (m_injectMode == Direct) {
|
|
||||||
auto *event = new xcb_button_press_event_t;
|
|
||||||
memset(event, 0x00, sizeof(xcb_button_press_event_t));
|
|
||||||
event->response_type = XCB_BUTTON_PRESS;
|
|
||||||
event->event = m_windowId;
|
|
||||||
event->time = XCB_CURRENT_TIME;
|
|
||||||
event->same_screen = 1;
|
|
||||||
event->root = DefaultRootWindow(m_x11Interface->display());
|
|
||||||
event->root_x = x;
|
|
||||||
event->root_y = y;
|
|
||||||
event->event_x = static_cast<int16_t>(clickPoint.x());
|
|
||||||
event->event_y = static_cast<int16_t>(clickPoint.y());
|
|
||||||
event->child = 0;
|
|
||||||
event->state = 0;
|
|
||||||
event->detail = mouseButton;
|
|
||||||
|
|
||||||
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, (char *)event);
|
|
||||||
delete event;
|
|
||||||
} else {
|
|
||||||
sendXTestPressed(m_x11Interface->display(), mouseButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
// mouse up
|
|
||||||
if (m_injectMode == Direct) {
|
|
||||||
auto *event = new xcb_button_release_event_t;
|
|
||||||
memset(event, 0x00, sizeof(xcb_button_release_event_t));
|
|
||||||
event->response_type = XCB_BUTTON_RELEASE;
|
|
||||||
event->event = m_windowId;
|
|
||||||
event->time = XCB_CURRENT_TIME;
|
|
||||||
event->same_screen = 1;
|
|
||||||
event->root = DefaultRootWindow(m_x11Interface->display());
|
|
||||||
event->root_x = x;
|
|
||||||
event->root_y = y;
|
|
||||||
event->event_x = static_cast<int16_t>(clickPoint.x());
|
|
||||||
event->event_y = static_cast<int16_t>(clickPoint.y());
|
|
||||||
event->child = 0;
|
|
||||||
event->state = 0;
|
|
||||||
event->detail = mouseButton;
|
|
||||||
|
|
||||||
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, (char *)event);
|
|
||||||
delete event;
|
|
||||||
} else {
|
|
||||||
sendXTestReleased(m_x11Interface->display(), mouseButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_injectMode == Direct) {
|
|
||||||
setActiveForInput(false);
|
|
||||||
} else {
|
|
||||||
// delayed because on xwayland with the new libei path it will go to XWayland
|
|
||||||
// then kwin, then back to X
|
|
||||||
// we need to delay slightly until that happens
|
|
||||||
if (qgetenv("XDG_SESSION_TYPE") == QByteArrayLiteral("wayland")) {
|
|
||||||
QTimer::singleShot(300, this, [this]() {
|
|
||||||
setActiveForInput(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setActiveForInput(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
153
sniproxy.h
153
sniproxy.h
@@ -1,153 +0,0 @@
|
|||||||
/*
|
|
||||||
Holds one embedded window, registers as DBus entry
|
|
||||||
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
|
||||||
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
|
||||||
|
|
||||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QDBusArgument>
|
|
||||||
#include <QDBusConnection>
|
|
||||||
#include <QDBusObjectPath>
|
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPoint>
|
|
||||||
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_image.h>
|
|
||||||
|
|
||||||
#include "snidbus.h"
|
|
||||||
|
|
||||||
class SNIProxy : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(QString Category READ Category)
|
|
||||||
Q_PROPERTY(QString Id READ Id)
|
|
||||||
Q_PROPERTY(QString Title READ Title)
|
|
||||||
Q_PROPERTY(QString Status READ Status)
|
|
||||||
Q_PROPERTY(int WindowId READ WindowId)
|
|
||||||
Q_PROPERTY(bool ItemIsMenu READ ItemIsMenu)
|
|
||||||
Q_PROPERTY(KDbusImageVector IconPixmap READ IconPixmap)
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit SNIProxy(xcb_window_t wid, QObject *parent = nullptr);
|
|
||||||
~SNIProxy() override;
|
|
||||||
|
|
||||||
void update();
|
|
||||||
void resizeWindow(const uint16_t width, const uint16_t height) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the category of the application associated to this item
|
|
||||||
* @see Category
|
|
||||||
*/
|
|
||||||
QString Category() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the id of this item
|
|
||||||
*/
|
|
||||||
QString Id() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the title of this item
|
|
||||||
*/
|
|
||||||
QString Title() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The status of this item
|
|
||||||
* @see Status
|
|
||||||
*/
|
|
||||||
QString Status() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The id of the main window of the application that controls the item
|
|
||||||
*/
|
|
||||||
int WindowId() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The item only support the context menu, the visualization should prefer sending ContextMenu() instead of Activate()
|
|
||||||
*/
|
|
||||||
bool ItemIsMenu() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a serialization of the icon data
|
|
||||||
*/
|
|
||||||
KDbusImageVector IconPixmap() const;
|
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
// interaction
|
|
||||||
/**
|
|
||||||
* Shows the context menu associated to this item
|
|
||||||
* at the desired screen position
|
|
||||||
*/
|
|
||||||
void ContextMenu(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows the main widget and try to position it on top
|
|
||||||
* of the other windows, if the widget is already visible, hide it.
|
|
||||||
*/
|
|
||||||
void Activate(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The user activated the item in an alternate way (for instance with middle mouse button, this depends from the systray implementation)
|
|
||||||
*/
|
|
||||||
void SecondaryActivate(int x, int y);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform this item that the mouse wheel was used on its representation
|
|
||||||
*/
|
|
||||||
void Scroll(int delta, const QString &orientation);
|
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
/**
|
|
||||||
* Inform the systemtray that the own main icon has been changed,
|
|
||||||
* so should be reloaded
|
|
||||||
*/
|
|
||||||
void NewIcon();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the systemtray that there is a new icon to be used as overlay
|
|
||||||
*/
|
|
||||||
void NewOverlayIcon();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the systemtray that the requesting attention icon
|
|
||||||
* has been changed, so should be reloaded
|
|
||||||
*/
|
|
||||||
void NewAttentionIcon();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inform the systemtray that something in the tooltip has been changed
|
|
||||||
*/
|
|
||||||
void NewToolTip();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signal the new status when it has been changed
|
|
||||||
* @see Status
|
|
||||||
*/
|
|
||||||
void NewStatus(const QString &status);
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum InjectMode {
|
|
||||||
Direct,
|
|
||||||
XTest,
|
|
||||||
};
|
|
||||||
|
|
||||||
QSize calculateClientWindowSize() const;
|
|
||||||
void sendClick(uint8_t mouseButton, int x, int y);
|
|
||||||
QImage getImageNonComposite() const;
|
|
||||||
bool isTransparentImage(const QImage &image) const;
|
|
||||||
QImage convertFromNative(xcb_image_t *xcbImage) const;
|
|
||||||
QPoint calculateClickPoint() const;
|
|
||||||
void setActiveForInput(bool active) const;
|
|
||||||
|
|
||||||
QDBusConnection m_dbus;
|
|
||||||
QNativeInterface::QX11Application *m_x11Interface = nullptr;
|
|
||||||
xcb_window_t m_windowId;
|
|
||||||
xcb_window_t m_containerWid;
|
|
||||||
static int s_serviceCount;
|
|
||||||
QPixmap m_pixmap;
|
|
||||||
InjectMode m_injectMode;
|
|
||||||
};
|
|
||||||
7
traylist.h
Normal file
7
traylist.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
typedef QList<quint32> TrayList;
|
||||||
96
traymanager1.cpp
Normal file
96
traymanager1.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
Deepin DDE TrayManager1 implementation
|
||||||
|
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "traymanager1.h"
|
||||||
|
#include "traymanagerproxy.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "traymanager1adaptor.h"
|
||||||
|
|
||||||
|
TrayManager1::TrayManager1(QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_adaptor(new TrayManager1Adaptor(this))
|
||||||
|
{
|
||||||
|
qCDebug(SNIPROXY) << "TrayManager1 created";
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayManager1::~TrayManager1()
|
||||||
|
{
|
||||||
|
qCDebug(SNIPROXY) << "TrayManager1 destroyed";
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManager1::registerIcon(xcb_window_t win, TrayManagerProxy *proxy)
|
||||||
|
{
|
||||||
|
if (m_icons.contains(win)) {
|
||||||
|
qCWarning(SNIPROXY) << "Icon already registered:" << win;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_icons[win] = proxy;
|
||||||
|
qCDebug(SNIPROXY) << "Icon registered:" << win << "name:" << proxy->name();
|
||||||
|
|
||||||
|
Q_EMIT Added(static_cast<uint32_t>(win));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManager1::unregisterIcon(xcb_window_t win)
|
||||||
|
{
|
||||||
|
if (!m_icons.contains(win)) {
|
||||||
|
qCWarning(SNIPROXY) << "Icon not found for removal:" << win;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_icons.remove(win);
|
||||||
|
qCDebug(SNIPROXY) << "Icon unregistered:" << win;
|
||||||
|
|
||||||
|
Q_EMIT Removed(static_cast<uint32_t>(win));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManager1::notifyIconChanged(xcb_window_t win)
|
||||||
|
{
|
||||||
|
if (!m_icons.contains(win)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qCDebug(SNIPROXY) << "Icon changed:" << win;
|
||||||
|
Q_EMIT Changed(static_cast<uint32_t>(win));
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayList TrayManager1::trayIcons() const
|
||||||
|
{
|
||||||
|
qDebug() << "trayIcons:" << m_icons.keys();
|
||||||
|
TrayList result;
|
||||||
|
for (xcb_window_t win : m_icons.keys()) {
|
||||||
|
result << static_cast<uint>(win);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayManagerProxy *TrayManager1::iconProxy(xcb_window_t win) const
|
||||||
|
{
|
||||||
|
return m_icons.value(win, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DBus method implementations
|
||||||
|
bool TrayManager1::Manage()
|
||||||
|
{
|
||||||
|
qCDebug(SNIPROXY) << "Manage() called via DBus";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TrayManager1::GetName(uint32_t win)
|
||||||
|
{
|
||||||
|
auto proxy = m_icons.value(static_cast<xcb_window_t>(win), nullptr);
|
||||||
|
if (proxy) {
|
||||||
|
return proxy->name();
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManager1::EnableNotification(uint32_t win, bool enabled)
|
||||||
|
{
|
||||||
|
auto proxy = m_icons.value(static_cast<xcb_window_t>(win), nullptr);
|
||||||
|
if (proxy) {
|
||||||
|
qCDebug(SNIPROXY) << "EnableNotification for" << win << "=" << enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
83
traymanager1.h
Normal file
83
traymanager1.h
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
Deepin DDE TrayManager1 implementation
|
||||||
|
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QDBusContext>
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
class TrayManagerProxy;
|
||||||
|
|
||||||
|
typedef QList<uint> TrayList;
|
||||||
|
|
||||||
|
class TrayManager1Adaptor;
|
||||||
|
/**
|
||||||
|
* @brief TrayManager1 implements the org.deepin.dde.TrayManager1 DBus interface
|
||||||
|
*
|
||||||
|
* This class manages all embedded X11 tray icons and exposes them via DBus.
|
||||||
|
* It maintains a list of TrayManagerProxy objects and emits signals when
|
||||||
|
* icons are added, removed, or changed.
|
||||||
|
*/
|
||||||
|
class TrayManager1 : public QObject, protected QDBusContext
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_CLASSINFO("D-Bus Interface", "org.deepin.dde.TrayManager1")
|
||||||
|
Q_PROPERTY(TrayList TrayIcons READ trayIcons)
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TrayManager1(QObject *parent = nullptr);
|
||||||
|
~TrayManager1() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a new tray icon with the manager
|
||||||
|
* @param win Window ID of the embedded tray icon
|
||||||
|
* @param proxy Pointer to the TrayManagerProxy managing this icon
|
||||||
|
*/
|
||||||
|
void registerIcon(xcb_window_t win, TrayManagerProxy *proxy);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unregister a tray icon
|
||||||
|
* @param win Window ID of the icon
|
||||||
|
*/
|
||||||
|
void unregisterIcon(xcb_window_t win);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Notify that an icon has changed
|
||||||
|
* @param win Window ID of the icon
|
||||||
|
*/
|
||||||
|
void notifyIconChanged(xcb_window_t win);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return List of all registered tray icon window IDs
|
||||||
|
*/
|
||||||
|
TrayList trayIcons() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Pointer to TrayManagerProxy for the given window, or nullptr
|
||||||
|
*/
|
||||||
|
TrayManagerProxy *iconProxy(xcb_window_t win) const;
|
||||||
|
|
||||||
|
public Q_SLOTS:
|
||||||
|
// DBus methods
|
||||||
|
bool Manage();
|
||||||
|
QString GetName(uint32_t win);
|
||||||
|
void EnableNotification(uint32_t win, bool enabled);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
// DBus signals
|
||||||
|
void Added(uint32_t id);
|
||||||
|
void Removed(uint32_t id);
|
||||||
|
void Changed(uint32_t id);
|
||||||
|
void Inited();
|
||||||
|
|
||||||
|
private:
|
||||||
|
TrayManager1Adaptor * m_adaptor;
|
||||||
|
QHash<xcb_window_t, TrayManagerProxy *> m_icons;
|
||||||
|
};
|
||||||
302
traymanagerproxy.cpp
Normal file
302
traymanagerproxy.cpp
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
/*
|
||||||
|
Xembed Tray Manager Proxy - holds one embedded window
|
||||||
|
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
||||||
|
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
||||||
|
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "traymanagerproxy.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <xcb/xcb_atom.h>
|
||||||
|
#include <xcb/xcb_event.h>
|
||||||
|
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QBitmap>
|
||||||
|
#include <QDBusConnection>
|
||||||
|
#include <QDBusPendingCall>
|
||||||
|
#include <QDBusPendingCallWatcher>
|
||||||
|
|
||||||
|
#include <KWindowInfo>
|
||||||
|
#include <KWindowSystem>
|
||||||
|
#include <netwm.h>
|
||||||
|
|
||||||
|
#include "xcbutils.h"
|
||||||
|
#include "xtestsender.h"
|
||||||
|
#include "c_ptr.h"
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef Status
|
||||||
|
typedef Status XStatus;
|
||||||
|
#undef Status
|
||||||
|
typedef XStatus Status;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static uint16_t s_embedSize = 32;
|
||||||
|
static unsigned int XEMBED_VERSION = 0;
|
||||||
|
|
||||||
|
void xembed_message_send(xcb_window_t towin, long message, long d1, long d2, long d3)
|
||||||
|
{
|
||||||
|
xcb_client_message_event_t ev;
|
||||||
|
|
||||||
|
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||||
|
ev.window = towin;
|
||||||
|
ev.format = 32;
|
||||||
|
ev.data.data32[0] = XCB_CURRENT_TIME;
|
||||||
|
ev.data.data32[1] = message;
|
||||||
|
ev.data.data32[2] = d1;
|
||||||
|
ev.data.data32[3] = d2;
|
||||||
|
ev.data.data32[4] = d3;
|
||||||
|
ev.type = Xcb::atoms->xembedAtom;
|
||||||
|
xcb_send_event(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection(), false, towin, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool checkWindowOrDescendantWantButtonEvents(xcb_window_t window)
|
||||||
|
{
|
||||||
|
auto connection = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection();
|
||||||
|
auto waCookie = xcb_get_window_attributes(connection, window);
|
||||||
|
UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(connection, waCookie, nullptr));
|
||||||
|
if (windowAttributes && windowAttributes->all_event_masks & XCB_EVENT_MASK_BUTTON_PRESS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (windowAttributes && windowAttributes->do_not_propagate_mask & XCB_EVENT_MASK_BUTTON_PRESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto treeCookie = xcb_query_tree(connection, window);
|
||||||
|
UniqueCPointer<xcb_query_tree_reply_t> tree(xcb_query_tree_reply(connection, treeCookie, nullptr));
|
||||||
|
if (!tree) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::span<xcb_window_t> children(xcb_query_tree_children(tree.get()), xcb_query_tree_children_length(tree.get()));
|
||||||
|
return std::ranges::any_of(children, &checkWindowOrDescendantWantButtonEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayManagerProxy::TrayManagerProxy(xcb_window_t wid, QObject *parent)
|
||||||
|
: QObject(parent)
|
||||||
|
, m_x11Interface(qGuiApp->nativeInterface<QNativeInterface::QX11Application>())
|
||||||
|
, m_windowId(wid)
|
||||||
|
, m_injectMode(Direct)
|
||||||
|
{
|
||||||
|
m_name = getWindowName();
|
||||||
|
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
|
||||||
|
// create a container window
|
||||||
|
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
||||||
|
m_containerWid = xcb_generate_id(c);
|
||||||
|
uint32_t values[3];
|
||||||
|
uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
|
||||||
|
values[0] = screen->black_pixel;
|
||||||
|
values[1] = true;
|
||||||
|
values[2] = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;
|
||||||
|
xcb_create_window(c,
|
||||||
|
XCB_COPY_FROM_PARENT,
|
||||||
|
m_containerWid,
|
||||||
|
screen->root,
|
||||||
|
0, 0,
|
||||||
|
s_embedSize, s_embedSize,
|
||||||
|
0,
|
||||||
|
XCB_WINDOW_CLASS_INPUT_OUTPUT,
|
||||||
|
screen->root_visual,
|
||||||
|
mask,
|
||||||
|
values);
|
||||||
|
|
||||||
|
setActiveForInput(false);
|
||||||
|
|
||||||
|
NETWinInfo wm(c, m_containerWid, screen->root, NET::Properties(), NET::Properties2());
|
||||||
|
wm.setOpacity(0);
|
||||||
|
|
||||||
|
xcb_flush(c);
|
||||||
|
xcb_map_window(c, m_containerWid);
|
||||||
|
|
||||||
|
xcb_reparent_window(c, wid, m_containerWid, 0, 0);
|
||||||
|
|
||||||
|
// Render the embedded window offscreen
|
||||||
|
xcb_composite_redirect_window(c, wid, XCB_COMPOSITE_REDIRECT_MANUAL);
|
||||||
|
|
||||||
|
xcb_change_save_set(c, XCB_SET_MODE_INSERT, wid);
|
||||||
|
|
||||||
|
// tell client we're embedding it
|
||||||
|
xembed_message_send(wid, XEMBED_EMBEDDED_NOTIFY, 0, m_containerWid, XEMBED_VERSION);
|
||||||
|
|
||||||
|
// move window we're embedding
|
||||||
|
const uint32_t windowMoveConfigVals[2] = {0, 0};
|
||||||
|
xcb_configure_window(c, wid, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, windowMoveConfigVals);
|
||||||
|
|
||||||
|
QSize clientWindowSize = calculateClientWindowSize();
|
||||||
|
|
||||||
|
// show the embedded window
|
||||||
|
xcb_map_window(c, wid);
|
||||||
|
xcb_clear_area(c, 0, wid, 0, 0, clientWindowSize.width(), clientWindowSize.height());
|
||||||
|
xcb_flush(c);
|
||||||
|
|
||||||
|
// guess which input injection method to use
|
||||||
|
auto waCookie = xcb_get_window_attributes(c, wid);
|
||||||
|
UniqueCPointer<xcb_get_window_attributes_reply_t> windowAttributes(xcb_get_window_attributes_reply(c, waCookie, nullptr));
|
||||||
|
if (!checkWindowOrDescendantWantButtonEvents(wid)) {
|
||||||
|
m_injectMode = XTest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First update after delay
|
||||||
|
QTimer::singleShot(500, this, &TrayManagerProxy::update);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayManagerProxy::~TrayManagerProxy()
|
||||||
|
{
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
xcb_destroy_window(c, m_containerWid);
|
||||||
|
xcb_flush(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TrayManagerProxy::getWindowName() const
|
||||||
|
{
|
||||||
|
auto connection = m_x11Interface->connection();
|
||||||
|
KWindowInfo info(m_windowId, NET::WMName | NET::WMIconName);
|
||||||
|
return info.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManagerProxy::update()
|
||||||
|
{
|
||||||
|
QImage newImage = getImageNonComposite();
|
||||||
|
|
||||||
|
if (!newImage.isNull() && newImage != m_lastImage) {
|
||||||
|
m_lastImage = newImage;
|
||||||
|
// Icon image changed, could trigger update on TrayManager1
|
||||||
|
// This would be handled at a higher level
|
||||||
|
}
|
||||||
|
|
||||||
|
QTimer::singleShot(100, this, &TrayManagerProxy::update);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManagerProxy::resizeWindow(const uint16_t width, const uint16_t height) const
|
||||||
|
{
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
const uint32_t values[] = {width, height};
|
||||||
|
xcb_configure_window(c, m_windowId, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
|
||||||
|
xcb_flush(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize TrayManagerProxy::calculateClientWindowSize() const
|
||||||
|
{
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
auto geoCookie = xcb_get_geometry(c, m_windowId);
|
||||||
|
UniqueCPointer<xcb_get_geometry_reply_t> geo(xcb_get_geometry_reply(c, geoCookie, nullptr));
|
||||||
|
if (geo) {
|
||||||
|
return QSize(geo->width, geo->height);
|
||||||
|
}
|
||||||
|
return QSize(s_embedSize, s_embedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManagerProxy::sendClick(uint8_t mouseButton, int x, int y)
|
||||||
|
{
|
||||||
|
if (m_injectMode == XTest) {
|
||||||
|
// Use XTest helper functions defined in xtestsender.h
|
||||||
|
sendXTestPressed(m_x11Interface->display(), mouseButton);
|
||||||
|
sendXTestReleased(m_x11Interface->display(), mouseButton);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
xcb_button_press_event_t pressEvent{};
|
||||||
|
memset(&pressEvent, 0, sizeof(pressEvent));
|
||||||
|
pressEvent.response_type = XCB_BUTTON_PRESS;
|
||||||
|
pressEvent.event = m_windowId;
|
||||||
|
pressEvent.time = XCB_CURRENT_TIME;
|
||||||
|
pressEvent.same_screen = 1;
|
||||||
|
pressEvent.root = DefaultRootWindow(m_x11Interface->display());
|
||||||
|
pressEvent.root_x = x;
|
||||||
|
pressEvent.root_y = y;
|
||||||
|
pressEvent.event_x = static_cast<int16_t>(x);
|
||||||
|
pressEvent.event_y = static_cast<int16_t>(y);
|
||||||
|
pressEvent.child = 0;
|
||||||
|
pressEvent.state = 0;
|
||||||
|
pressEvent.detail = mouseButton;
|
||||||
|
|
||||||
|
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_PRESS, reinterpret_cast<const char *>(&pressEvent));
|
||||||
|
|
||||||
|
xcb_button_release_event_t releaseEvent{};
|
||||||
|
memset(&releaseEvent, 0, sizeof(releaseEvent));
|
||||||
|
releaseEvent.response_type = XCB_BUTTON_RELEASE;
|
||||||
|
releaseEvent.event = m_windowId;
|
||||||
|
releaseEvent.time = XCB_CURRENT_TIME;
|
||||||
|
releaseEvent.same_screen = 1;
|
||||||
|
releaseEvent.root = DefaultRootWindow(m_x11Interface->display());
|
||||||
|
releaseEvent.root_x = x;
|
||||||
|
releaseEvent.root_y = y;
|
||||||
|
releaseEvent.event_x = static_cast<int16_t>(x);
|
||||||
|
releaseEvent.event_y = static_cast<int16_t>(y);
|
||||||
|
releaseEvent.child = 0;
|
||||||
|
releaseEvent.state = 0;
|
||||||
|
releaseEvent.detail = mouseButton;
|
||||||
|
|
||||||
|
xcb_send_event(c, false, m_windowId, XCB_EVENT_MASK_BUTTON_RELEASE, reinterpret_cast<const char *>(&releaseEvent));
|
||||||
|
xcb_flush(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage TrayManagerProxy::getImageNonComposite() const
|
||||||
|
{
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
|
||||||
|
auto geoCookie = xcb_get_geometry(c, m_windowId);
|
||||||
|
UniqueCPointer<xcb_get_geometry_reply_t> geo(xcb_get_geometry_reply(c, geoCookie, nullptr));
|
||||||
|
if (!geo) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
// Use xcb_image_get directly on the drawable. Use UINT32_MAX for plane mask.
|
||||||
|
xcb_image_t *xcbimg = xcb_image_get(c, m_windowId, 0, 0, geo->width, geo->height, UINT32_MAX, XCB_IMAGE_FORMAT_Z_PIXMAP);
|
||||||
|
if (!xcbimg) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertFromNative(xcbimg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrayManagerProxy::isTransparentImage(const QImage &image) const
|
||||||
|
{
|
||||||
|
if (image.format() != QImage::Format_ARGB32) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QRgb *data = reinterpret_cast<const QRgb *>(image.bits());
|
||||||
|
for (int i = 0; i < image.width() * image.height(); ++i) {
|
||||||
|
if (qAlpha(data[i]) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage TrayManagerProxy::convertFromNative(xcb_image_t *xcbImage) const
|
||||||
|
{
|
||||||
|
if (!xcbImage) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage qimage(xcbImage->width, xcbImage->height, QImage::Format_ARGB32);
|
||||||
|
memcpy(qimage.bits(), xcbImage->data, qimage.sizeInBytes());
|
||||||
|
|
||||||
|
return qimage;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPoint TrayManagerProxy::calculateClickPoint() const
|
||||||
|
{
|
||||||
|
return QPoint(s_embedSize / 2, s_embedSize / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TrayManagerProxy::setActiveForInput(bool active) const
|
||||||
|
{
|
||||||
|
auto c = m_x11Interface->connection();
|
||||||
|
auto screen = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
|
||||||
|
|
||||||
|
xcb_rectangle_t rect = {0, 0, 0, 0};
|
||||||
|
if (active) {
|
||||||
|
rect.width = s_embedSize;
|
||||||
|
rect.height = s_embedSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_shape_rectangles(c, XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED, m_containerWid, 0, 0, 1, &rect);
|
||||||
|
xcb_flush(c);
|
||||||
|
}
|
||||||
62
traymanagerproxy.h
Normal file
62
traymanagerproxy.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
Xembed Tray Manager Proxy - holds one embedded window
|
||||||
|
SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
|
||||||
|
SPDX-FileCopyrightText: 2019 Konrad Materka <materka@gmail.com>
|
||||||
|
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QPixmap>
|
||||||
|
#include <QPoint>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_image.h>
|
||||||
|
|
||||||
|
class TrayManagerProxy : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TrayManagerProxy(xcb_window_t wid, QObject *parent = nullptr);
|
||||||
|
~TrayManagerProxy() override;
|
||||||
|
|
||||||
|
void update();
|
||||||
|
void resizeWindow(const uint16_t width, const uint16_t height) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the window id of this item
|
||||||
|
*/
|
||||||
|
uint32_t windowId() const { return m_windowId; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the name/title of this item
|
||||||
|
*/
|
||||||
|
QString name() const { return m_name; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum InjectMode {
|
||||||
|
Direct,
|
||||||
|
XTest,
|
||||||
|
};
|
||||||
|
|
||||||
|
QSize calculateClientWindowSize() const;
|
||||||
|
void sendClick(uint8_t mouseButton, int x, int y);
|
||||||
|
QImage getImageNonComposite() const;
|
||||||
|
bool isTransparentImage(const QImage &image) const;
|
||||||
|
QImage convertFromNative(xcb_image_t *xcbImage) const;
|
||||||
|
QPoint calculateClickPoint() const;
|
||||||
|
void setActiveForInput(bool active) const;
|
||||||
|
QString getWindowName() const;
|
||||||
|
|
||||||
|
QNativeInterface::QX11Application *m_x11Interface = nullptr;
|
||||||
|
xcb_window_t m_windowId;
|
||||||
|
xcb_window_t m_containerWid;
|
||||||
|
uint32_t m_damageId = 0;
|
||||||
|
InjectMode m_injectMode;
|
||||||
|
QString m_name;
|
||||||
|
QImage m_lastImage;
|
||||||
|
};
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
||||||
#include "../c_ptr.h"
|
#include "c_ptr.h"
|
||||||
#include <X11/Xlib.h>
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
/** XEMBED messages */
|
/** XEMBED messages */
|
||||||
|
|||||||
Reference in New Issue
Block a user