dde-dock/plugins/tray/trayplugin.cpp
2023-02-16 15:08:28 +08:00

642 lines
19 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (C) 2011 ~ 2018 Deepin Technology Co., Ltd.
// SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "trayplugin.h"
#include "fashiontray/fashiontrayitem.h"
#include "snitraywidget.h"
#include "utils.h"
#include "../widgets/tipswidget.h"
#include <QDir>
#include <QWindow>
#include <QWidget>
#include <QX11Info>
#include <QtConcurrent>
#include <QFuture>
#include <QFutureWatcher>
#include <xcb/xcb_icccm.h>
#include <X11/Xlib.h>
#define PLUGIN_ENABLED_KEY "enable"
#define FASHION_MODE_TRAYS_SORTED "fashion-mode-trays-sorted"
#define SNI_WATCHER_SERVICE "org.kde.StatusNotifierWatcher"
#define SNI_WATCHER_PATH "/StatusNotifierWatcher"
#define REGISTERTED_WAY_IS_SNI 1
#define REGISTERTED_WAY_IS_XEMBED 2
using org::kde::StatusNotifierWatcher;
using namespace Dock;
TrayPlugin::TrayPlugin(QObject *parent)
: QObject(parent)
, m_pluginLoaded(false)
, xcb_connection(nullptr)
, m_display(nullptr)
{
if (Utils::IS_WAYLAND_DISPLAY) {
int screenp = 0;
xcb_connection = xcb_connect(qgetenv("DISPLAY"), &screenp);
m_display = XOpenDisplay(nullptr);
}
}
const QString TrayPlugin::pluginName() const
{
return "tray";
}
void TrayPlugin::init(PluginProxyInterface *proxyInter)
{
// transfex config
QSettings settings("deepin", "dde-dock-shutdown");
if (QFile::exists(settings.fileName())) {
proxyInter->saveValue(this, "enable", settings.value("enable", true));
QFile::remove(settings.fileName());
}
m_proxyInter = proxyInter;
if (pluginIsDisable()) {
qDebug() << "hide tray from config disable!!";
return;
}
if (m_pluginLoaded) {
return;
}
m_pluginLoaded = true;
m_trayInter = new DBusTrayManager(this);
m_sniWatcher = new StatusNotifierWatcher(SNI_WATCHER_SERVICE, SNI_WATCHER_PATH, QDBusConnection::sessionBus(), this);
m_fashionItem = new FashionTrayItem(this);
m_systemTraysController = new SystemTraysController(this);
m_refreshXEmbedItemsTimer = new QTimer(this);
m_refreshSNIItemsTimer = new QTimer(this);
m_refreshXEmbedItemsTimer->setInterval(0);
m_refreshXEmbedItemsTimer->setSingleShot(true);
m_refreshSNIItemsTimer->setInterval(0);
m_refreshSNIItemsTimer->setSingleShot(true);
connect(m_systemTraysController, &SystemTraysController::pluginItemAdded, this, &TrayPlugin::addTrayWidget);
connect(m_systemTraysController, &SystemTraysController::pluginItemRemoved, this, [ = ](const QString & itemKey) { trayRemoved(itemKey); });
m_trayInter->Manage();
switchToMode(displayMode());
// 加载snixem自定义indicator协议以及其他托盘插件
QTimer::singleShot(0, this, &TrayPlugin::loadIndicator);
QTimer::singleShot(0, this, &TrayPlugin::initSNI);
QTimer::singleShot(0, this, &TrayPlugin::initXEmbed);
}
bool TrayPlugin::pluginIsDisable()
{
// NOTE(justforlxz): local config
QSettings enableSetting("deepin", "dde-dock");
enableSetting.beginGroup("tray");
if (!enableSetting.value("enable", true).toBool()) {
return true;
}
if (!m_proxyInter)
return true;
return !m_proxyInter->getValue(this, PLUGIN_ENABLED_KEY, true).toBool();
}
void TrayPlugin::displayModeChanged(const Dock::DisplayMode mode)
{
Q_UNUSED(mode);
if (pluginIsDisable()) {
return;
}
switchToMode(displayMode());
}
void TrayPlugin::positionChanged(const Dock::Position position)
{
if (pluginIsDisable()) {
return;
}
m_fashionItem->setDockPosition(position);
}
QWidget *TrayPlugin::itemWidget(const QString &itemKey)
{
if (itemKey == FASHION_MODE_ITEM_KEY) {
return m_fashionItem;
}
return m_trayMap.value(itemKey);
}
QWidget *TrayPlugin::itemTipsWidget(const QString &itemKey)
{
Q_UNUSED(itemKey);
return nullptr;
}
QWidget *TrayPlugin::itemPopupApplet(const QString &itemKey)
{
Q_UNUSED(itemKey);
return nullptr;
}
int TrayPlugin::itemSortKey(const QString &itemKey)
{
// 如果是系统托盘图标则调用内部插件的相应接口
if (isSystemTrayItem(itemKey)) {
return m_systemTraysController->systemTrayItemSortKey(itemKey);
}
const int defaultSort = 0;
AbstractTrayWidget *const trayWidget = m_trayMap.value(itemKey, nullptr);
if (trayWidget == nullptr) {
return defaultSort;
}
const QString key = QString("pos_%1_%2").arg(trayWidget->itemKeyForConfig()).arg(Dock::Efficient);
return m_proxyInter->getValue(this, key, defaultSort).toInt();
}
void TrayPlugin::setSortKey(const QString &itemKey, const int order)
{
if (displayMode() == Dock::DisplayMode::Fashion && !traysSortedInFashionMode()) {
m_proxyInter->saveValue(this, FASHION_MODE_TRAYS_SORTED, true);
}
// 如果是系统托盘图标则调用内部插件的相应接口
if (isSystemTrayItem(itemKey)) {
return m_systemTraysController->setSystemTrayItemSortKey(itemKey, order);
}
AbstractTrayWidget *const trayWidget = m_trayMap.value(itemKey, nullptr);
if (trayWidget == nullptr) {
return;
}
const QString key = QString("pos_%1_%2").arg(trayWidget->itemKeyForConfig()).arg(Dock::Efficient);
m_proxyInter->saveValue(this, key, order);
}
void TrayPlugin::refreshIcon(const QString &itemKey)
{
if (itemKey == FASHION_MODE_ITEM_KEY) {
for (auto trayWidget : m_trayMap.values()) {
if (trayWidget) {
trayWidget->updateIcon();
}
}
return;
}
AbstractTrayWidget *const trayWidget = m_trayMap.value(itemKey);
if (trayWidget) {
trayWidget->updateIcon();
}
}
void TrayPlugin::pluginSettingsChanged()
{
if (pluginIsDisable()) {
return;
}
if (displayMode() == Dock::DisplayMode::Fashion) {
m_fashionItem->onPluginSettingsChanged();
m_fashionItem->clearTrayWidgets();
m_fashionItem->setTrayWidgets(m_trayMap);
}
}
Dock::Position TrayPlugin::dockPosition() const
{
return position();
}
bool TrayPlugin::traysSortedInFashionMode()
{
return m_proxyInter->getValue(this, FASHION_MODE_TRAYS_SORTED, false).toBool();
}
void TrayPlugin::saveValue(const QString &itemKey, const QString &key, const QVariant &value)
{
// 如果是系统托盘图标则调用内部插件的相应接口
if (isSystemTrayItem(itemKey)) {
return m_systemTraysController->saveValueSystemTrayItem(itemKey, key, value);
}
m_proxyInter->saveValue(this, key, value);
}
const QVariant TrayPlugin::getValue(const QString &itemKey, const QString &key, const QVariant &fallback)
{
// 如果是系统托盘图标则调用内部插件的相应接口
if (isSystemTrayItem(itemKey)) {
return m_systemTraysController->getValueSystemTrayItem(itemKey, key, fallback);
}
return m_proxyInter->getValue(this, key, fallback);
}
bool TrayPlugin::isSystemTrayItem(const QString &itemKey)
{
AbstractTrayWidget *const trayWidget = m_trayMap.value(itemKey, nullptr);
if (trayWidget && trayWidget->trayTyep() == AbstractTrayWidget::TrayType::SystemTray) {
return true;
}
return false;
}
QString TrayPlugin::itemKeyOfTrayWidget(AbstractTrayWidget *trayWidget)
{
QString itemKey;
if (displayMode() == Dock::DisplayMode::Fashion) {
itemKey = FASHION_MODE_ITEM_KEY;
} else {
itemKey = m_trayMap.key(trayWidget);
}
return itemKey;
}
Dock::DisplayMode TrayPlugin::displayMode()
{
return Dock::DisplayMode::Fashion;
}
void TrayPlugin::initXEmbed()
{
connect(m_refreshXEmbedItemsTimer, &QTimer::timeout, this, &TrayPlugin::xembedItemsChanged);
connect(m_trayInter, &DBusTrayManager::TrayIconsChanged, this, [ = ] {m_refreshXEmbedItemsTimer->start();});
connect(m_trayInter, &DBusTrayManager::Changed, this, &TrayPlugin::xembedItemChanged);
m_refreshXEmbedItemsTimer->start();
}
/**
* @brief TrayPlugin::initSNI
* @note 初始化监听信号绑定
*/
void TrayPlugin::initSNI()
{
connect(m_refreshSNIItemsTimer, &QTimer::timeout, this, &TrayPlugin::sniItemsChanged);
connect(m_sniWatcher, &StatusNotifierWatcher::StatusNotifierItemRegistered, this, [ = ] {m_refreshSNIItemsTimer->start();});
connect(m_sniWatcher, &StatusNotifierWatcher::StatusNotifierItemUnregistered, this, [ = ] {m_refreshSNIItemsTimer->start();});
m_refreshSNIItemsTimer->start();
}
/**
* @brief TrayPlugin::sniItemsChanged
* @note 移除关闭的item,插入新增item
*/
void TrayPlugin::sniItemsChanged()
{
const QStringList &itemServicePaths = m_sniWatcher->registeredStatusNotifierItems();
QStringList sinTrayKeyList;
for (auto item : itemServicePaths) {
sinTrayKeyList << SNITrayWidget::toSNIKey(item);
}
for (auto itemKey : m_trayMap.keys()) {
if (!sinTrayKeyList.contains(itemKey) && SNITrayWidget::isSNIKey(itemKey)) {
m_registertedPID.take(m_trayMap[itemKey]->getOwnerPID());
trayRemoved(itemKey);
}
}
const QList<QString> &passiveSNIKeyList = m_passiveSNITrayMap.keys();
for (auto itemKey : passiveSNIKeyList) {
if (!sinTrayKeyList.contains(itemKey) && SNITrayWidget::isSNIKey(itemKey)) {
m_passiveSNITrayMap.take(itemKey)->deleteLater();
}
}
for (int i = 0; i < sinTrayKeyList.size(); ++i) {
uint pid = SNITrayWidget::servicePID(itemServicePaths.at(i));
if (m_registertedPID.value(pid, REGISTERTED_WAY_IS_SNI) == REGISTERTED_WAY_IS_SNI) {
traySNIAdded(sinTrayKeyList.at(i), itemServicePaths.at(i));
m_registertedPID.insert(pid, REGISTERTED_WAY_IS_SNI);
}
}
}
void TrayPlugin::xembedItemsChanged()
{
QList<quint32> winidList = m_trayInter->trayIcons();
QStringList newlyAddedTrayKeyList;
QStringList allKeytary;
QList<quint32> newlyAddedWindowID;
for (auto winid : winidList) {
uint pid = XEmbedTrayWidget::getWindowPID(winid);
allKeytary << XEmbedTrayWidget::toXEmbedKey(winid);
if (m_registertedPID.value(pid, REGISTERTED_WAY_IS_XEMBED) == REGISTERTED_WAY_IS_XEMBED) {
m_registertedPID.insert(pid, REGISTERTED_WAY_IS_XEMBED);
newlyAddedWindowID << winid;
newlyAddedTrayKeyList << XEmbedTrayWidget::toXEmbedKey(winid);
}
}
for (auto tray : m_trayMap.keys()) {
if (!allKeytary.contains(tray) && XEmbedTrayWidget::isXEmbedKey(tray)) {
m_registertedPID.take(m_trayMap[tray]->getOwnerPID());
trayRemoved(tray);
}
}
for (int i = 0; i < newlyAddedTrayKeyList.size(); ++i) {
trayXEmbedAdded(newlyAddedTrayKeyList.at(i), newlyAddedWindowID.at(i));
}
}
void TrayPlugin::addTrayWidget(const QString &itemKey, AbstractTrayWidget *trayWidget)
{
if (!trayWidget) {
return;
}
if (m_trayMap.contains(itemKey) || m_trayMap.values().contains(trayWidget)) {
return;
}
m_trayMap.insert(itemKey, trayWidget);
if (displayMode() == Dock::Efficient) {
m_proxyInter->itemAdded(this, itemKey);
} else {
m_proxyInter->itemAdded(this, FASHION_MODE_ITEM_KEY);
m_fashionItem->trayWidgetAdded(itemKey, trayWidget);
}
connect(trayWidget, &AbstractTrayWidget::requestWindowAutoHide, this, &TrayPlugin::onRequestWindowAutoHide, Qt::UniqueConnection);
connect(trayWidget, &AbstractTrayWidget::requestRefershWindowVisible, this, &TrayPlugin::onRequestRefershWindowVisible, Qt::UniqueConnection);
}
void TrayPlugin::trayXEmbedAdded(const QString &itemKey, quint32 winId)
{
if (m_trayMap.contains(itemKey) || !XEmbedTrayWidget::isXEmbedKey(itemKey)) {
return;
}
if (!Utils::SettingValue("com.deepin.dde.dock.module.systemtray", QByteArray(), "enable", false).toBool())
return;
AbstractTrayWidget *trayWidget = Utils::IS_WAYLAND_DISPLAY ? new XEmbedTrayWidget(winId, xcb_connection, m_display) : new XEmbedTrayWidget(winId);
if (trayWidget->isValid())
addTrayWidget(itemKey, trayWidget);
else {
qDebug() << "-- invalid tray windowid" << winId;
}
}
void TrayPlugin::traySNIAdded(const QString &itemKey, const QString &sniServicePath)
{
QFutureWatcher<bool> *watcher = new QFutureWatcher<bool>();
// 开线程去处理添加任务
connect(watcher, &QFutureWatcher<int>::finished, this, [=] {
watcher->deleteLater();
if (!watcher->result()) {
return;
}
SNITrayWidget *trayWidget = new SNITrayWidget(sniServicePath);
// TODO(lxz): 在future里已经对dbus进行过检查了这里应该不需要再次检查。
if (!trayWidget->isValid())
return;
std::lock_guard<std::mutex> lock(m_sniMutex);
if (trayWidget->status() == SNITrayWidget::ItemStatus::Passive) {
m_passiveSNITrayMap.insert(itemKey, trayWidget);
} else {
addTrayWidget(itemKey, trayWidget);
}
connect(trayWidget, &SNITrayWidget::statusChanged, this, &TrayPlugin::onSNIItemStatusChanged);
});
// Start the computation.
QFuture<bool> future = QtConcurrent::run([=]() -> bool {
{
std::lock_guard<std::mutex> lock(m_sniMutex);
if (m_trayMap.contains(itemKey) || !SNITrayWidget::isSNIKey(itemKey) || m_passiveSNITrayMap.contains(itemKey)) {
return false;
}
}
if (!Utils::SettingValue("com.deepin.dde.dock.module.systemtray", QByteArray(), "enable", false).toBool())
return false;
if (sniServicePath.startsWith("/") || !sniServicePath.contains("/")) {
qDebug() << "SNI service path invalid";
return false;
}
// NOTE(lxz): The data from the sni daemon is interface/methd
// e.g. org.kde.StatusNotifierItem-1741-1/StatusNotifierItem
const QStringList list = sniServicePath.split("/");
const QString sniServerName = list.first();
if (sniServerName.isEmpty()) {
qWarning() << "SNI service error: " << sniServerName;
return false;
}
// 1、确保服务有效
QDBusInterface sniItemDBus(sniServerName, "/" + list.last());
if (!sniItemDBus.isValid()) {
qDebug() << "sni dbus service error : " << sniServerName;
return false;
}
// 部分服务虽然有效但是在dbus总线上只能看到服务其他信息都无法获取,这里通过Ping进行二次确认
// 参考: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces
// 2、通过Ping接口确认服务是否正常
QDBusInterface peerInter(sniServerName, "/" + list.last(), "org.freedesktop.DBus.Peer");
QDBusReply<void> reply = peerInter.call("Ping");
if (!reply.isValid())
return false;
return true;
});
watcher->setFuture(future);
}
void TrayPlugin::trayIndicatorAdded(const QString &itemKey, const QString &indicatorName)
{
if (m_trayMap.contains(itemKey) || !IndicatorTrayWidget::isIndicatorKey(itemKey)) {
return;
}
if (!Utils::SettingValue("com.deepin.dde.dock.module.systemtray", QByteArray(), "enable", false).toBool())
return;
IndicatorTray *indicatorTray = nullptr;
if (!m_indicatorMap.keys().contains(indicatorName)) {
indicatorTray = new IndicatorTray(indicatorName, this);
m_indicatorMap[indicatorName] = indicatorTray;
} else {
indicatorTray = m_indicatorMap[itemKey];
}
connect(indicatorTray, &IndicatorTray::delayLoaded,
indicatorTray, [ = ]() {
addTrayWidget(itemKey, indicatorTray->widget());
}, Qt::UniqueConnection);
connect(indicatorTray, &IndicatorTray::removed, this, [ = ] {
trayRemoved(itemKey);
indicatorTray->removeWidget();
}, Qt::UniqueConnection);
}
void TrayPlugin::trayRemoved(const QString &itemKey, const bool deleteObject)
{
if (!m_trayMap.contains(itemKey)) {
return;
}
AbstractTrayWidget *widget = m_trayMap.take(itemKey);
if (displayMode() == Dock::Efficient) {
m_proxyInter->itemRemoved(this, itemKey);
} else {
m_fashionItem->trayWidgetRemoved(widget);
}
// only delete tray object when it is a tray of applications
// set the parent of the tray object to avoid be deconstructed by parent(DockItem/PluginsItem/TrayPluginsItem)
if (widget->trayTyep() == AbstractTrayWidget::TrayType::SystemTray) {
widget->setParent(nullptr);
} else if (deleteObject) {
widget->deleteLater();
}
}
void TrayPlugin::xembedItemChanged(quint32 winId)
{
QString itemKey = XEmbedTrayWidget::toXEmbedKey(winId);
if (!m_trayMap.contains(itemKey)) {
return;
}
m_trayMap.value(itemKey)->updateIcon();
}
void TrayPlugin::switchToMode(const Dock::DisplayMode mode)
{
if (!m_proxyInter)
return;
if (mode == Dock::Fashion) {
for (auto itemKey : m_trayMap.keys()) {
m_proxyInter->itemRemoved(this, itemKey);
}
if (m_trayMap.isEmpty()) {
m_proxyInter->itemRemoved(this, FASHION_MODE_ITEM_KEY);
} else {
m_fashionItem->setTrayWidgets(m_trayMap);
m_proxyInter->itemAdded(this, FASHION_MODE_ITEM_KEY);
}
} else {
m_fashionItem->clearTrayWidgets();
m_proxyInter->itemRemoved(this, FASHION_MODE_ITEM_KEY);
for (auto itemKey : m_trayMap.keys()) {
m_proxyInter->itemAdded(this, itemKey);
}
}
}
void TrayPlugin::onRequestWindowAutoHide(const bool autoHide)
{
const QString &itemKey = itemKeyOfTrayWidget(static_cast<AbstractTrayWidget *>(sender()));
if (itemKey.isEmpty()) {
return;
}
m_proxyInter->requestWindowAutoHide(this, itemKey, autoHide);
}
void TrayPlugin::onRequestRefershWindowVisible()
{
const QString &itemKey = itemKeyOfTrayWidget(static_cast<AbstractTrayWidget *>(sender()));
if (itemKey.isEmpty()) {
return;
}
m_proxyInter->requestRefreshWindowVisible(this, itemKey);
}
void TrayPlugin::onSNIItemStatusChanged(SNITrayWidget::ItemStatus status)
{
SNITrayWidget *trayWidget = static_cast<SNITrayWidget *>(sender());
if (!trayWidget) {
return;
}
QString itemKey;
do {
itemKey = m_trayMap.key(trayWidget);
if (!itemKey.isEmpty()) {
break;
}
itemKey = m_passiveSNITrayMap.key(trayWidget);
if (itemKey.isEmpty()) {
qDebug() << "Error! not found the status changed SNI tray!";
return;
}
} while (false);
switch (status) {
case SNITrayWidget::Passive: {
m_passiveSNITrayMap.insert(itemKey, trayWidget);
trayRemoved(itemKey, false);
break;
}
case SNITrayWidget::Active:
case SNITrayWidget::NeedsAttention: {
m_passiveSNITrayMap.remove(itemKey);
addTrayWidget(itemKey, trayWidget);
break;
}
default:
break;
}
}
void TrayPlugin::loadIndicator()
{
QDir indicatorConfDir("/etc/dde-dock/indicator");
for (const QFileInfo &fileInfo : indicatorConfDir.entryInfoList({"*.json"}, QDir::Files | QDir::NoDotAndDotDot)) {
const QString &indicatorName = fileInfo.baseName();
trayIndicatorAdded(IndicatorTrayWidget::toIndicatorKey(indicatorName), indicatorName);
}
}