dde-dock/frame/window/tray/tray_model.cpp
donghualin 077d6f056f fix: 修复从任务栏拖出托盘应用后托盘不弹出的问题
1.当图标只剩下一个的时候,遍历获取展开图标错误
2.在系统图标删除之前,需要将内置的插件返回的widget的parent设置为nullptr,防止在系统图标删除的时候把插件的widget给删除了
3、在拖动图标的过程中,不从原来的系统中删除原来的图标,再释放后,再将原来的图标删除

Log:
Influence: 从任务栏拖动微信或企业微信,观察托盘是否弹出
Bug: https://pms.uniontech.com/bug-view-171497.html
Change-Id: Iacbfe3406112e92a68d268beaaea3c1a3c3afe7c
2022-12-02 13:51:58 +08:00

817 lines
23 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) 2022 ~ 2022 Deepin Technology Co., Ltd.
*
* Author: donghualin <donghualin@uniontech.com>
*
* Maintainer: donghualin <donghualin@uniontech.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "tray_model.h"
#include "tray_monitor.h"
#include "indicatortrayitem.h"
#include "indicatorplugin.h"
#include "quicksettingcontroller.h"
#include "pluginsiteminterface.h"
#include "settingconfig.h"
#include "platformutils.h"
#include <QMimeData>
#include <QIcon>
#include <QDebug>
#include <QAbstractItemModel>
#include <QDBusInterface>
#define TRAY_DRAG_FALG "tray_drag"
#define DOCKQUICKTRAYNAME "Dock_Quick_Tray_Name"
TrayModel *TrayModel::getDockModel()
{
static TrayModel *model = nullptr;
if (!model) {
model = new TrayModel(false);
TrayModel *iconModel = getIconModel();
connect(iconModel, &TrayModel::rowsRemoved, model, [ = ] {
model->setExpandVisible(iconModel->rowCount() > 0);
});
connect(iconModel, &TrayModel::rowsInserted, model, [ = ] {
model->setExpandVisible(iconModel->rowCount() > 0);
});
connect(iconModel, &TrayModel::rowCountChanged, model, [ = ] {
model->setExpandVisible(iconModel->rowCount() > 0);
});
}
return model;
}
TrayModel *TrayModel::getIconModel()
{
static TrayModel model(true);
return &model;
}
TrayModel::TrayModel(bool isIconTray, QObject *parent)
: QAbstractListModel(parent)
, m_dragModelIndex(QModelIndex())
, m_dropModelIndex(QModelIndex())
, m_monitor(new TrayMonitor(this))
, m_isTrayIcon(isIconTray)
{
connect(m_monitor, &TrayMonitor::xEmbedTrayAdded, this, &TrayModel::onXEmbedTrayAdded);
connect(m_monitor, &TrayMonitor::xEmbedTrayRemoved, this, &TrayModel::onXEmbedTrayRemoved);
connect(m_monitor, &TrayMonitor::sniTrayAdded, this, &TrayModel::onSniTrayAdded);
connect(m_monitor, &TrayMonitor::sniTrayRemoved, this, &TrayModel::onSniTrayRemoved);
connect(m_monitor, &TrayMonitor::indicatorFounded, this, &TrayModel::onIndicatorFounded);
connect(m_monitor, &TrayMonitor::systemTrayAdded, this, &TrayModel::onSystemTrayAdded);
connect(m_monitor, &TrayMonitor::systemTrayRemoved, this, &TrayModel::onSystemTrayRemoved);
connect(m_monitor, &TrayMonitor::requestUpdateIcon, this, &TrayModel::requestUpdateIcon);
connect(SETTINGCONFIG, &SettingConfig::valueChanged, this, &TrayModel::onSettingChanged);
m_fixedTrayNames = SETTINGCONFIG->value(DOCKQUICKTRAYNAME).toStringList();
m_fixedTrayNames.removeDuplicates();
}
void TrayModel::dropSwap(int newPos)
{
if (!m_dragModelIndex.isValid())
return;
int row = m_dragModelIndex.row();
if (row < m_winInfos.size())
m_dragInfo = m_winInfos.takeAt(row);
WinInfo name = m_dragInfo;
m_winInfos.insert(newPos, name);
emit QAbstractItemModel::dataChanged(m_dragModelIndex, m_dropModelIndex);
requestRefreshEditor();
}
void TrayModel::clearDragDropIndex()
{
const QModelIndex startIndex = m_dragModelIndex;
const QModelIndex endIndex = m_dropModelIndex;
m_dragModelIndex = m_dropModelIndex = QModelIndex();
emit QAbstractItemModel::dataChanged(startIndex, endIndex);
emit QAbstractItemModel::dataChanged(endIndex, startIndex);
}
void TrayModel::setDragingIndex(const QModelIndex index)
{
m_dragModelIndex = index;
m_dropModelIndex = index;
emit QAbstractListModel::dataChanged(index, index);
}
void TrayModel::setDragDropIndex(const QModelIndex index)
{
if (m_dragModelIndex == index)
return;
m_dropModelIndex = index;
emit QAbstractListModel::dataChanged(m_dragModelIndex, index);
emit QAbstractListModel::dataChanged(index, m_dragModelIndex);
}
void TrayModel::setExpandVisible(bool visible, bool openExpand)
{
// 如果是托盘,不支持展开图标
if (m_isTrayIcon)
return;
if (visible) {
// 如果展开图标已经存在,则不添加,
for (WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon) {
winInfo.expand = openExpand;
return;
}
}
// 如果是任务栏图标,则添加托盘展开图标
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = TrayIconType::ExpandIcon;
info.expand = openExpand;
m_winInfos.insert(0, info); // 展开图标始终显示在第一个
endInsertRows();
Q_EMIT requestRefreshEditor();
Q_EMIT rowCountChanged();
} else {
// 如果隐藏,则直接从列表中移除
bool rowChanged = false;
beginResetModel();
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon) {
m_winInfos.removeOne(winInfo);
rowChanged = true;
}
}
endResetModel();
if (rowChanged)
Q_EMIT rowCountChanged();
}
}
void TrayModel::updateOpenExpand(bool openExpand)
{
for (WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon)
winInfo.expand = openExpand;
}
}
void TrayModel::setDragKey(const QString &key)
{
m_dragKey = key;
}
bool TrayModel::indexDragging(const QModelIndex &index) const
{
if (index.isValid() && index.data(Role::KeyRole).toString() == m_dragKey)
return true;
if (!m_dragModelIndex.isValid() || !m_dropModelIndex.isValid())
return false;
const int start = m_dragModelIndex.row();
const int end = m_dropModelIndex.row();
const int current = index.row();
return (start <= end && current >= start && current <= end)
|| (start >= end && current <= start && current >= end);
}
IndicatorTrayItem *TrayModel::indicatorWidget(const QString &indicatorName) const
{
QString indicatorKey = indicatorName;
indicatorKey = indicatorKey.remove(0, QString("indicator:").length());
if (m_indicatorMap.contains(indicatorKey))
return m_indicatorMap.value(indicatorKey)->widget();
return nullptr;
}
QMimeData *TrayModel::mimeData(const QModelIndexList &indexes) const
{
Q_ASSERT(indexes.size() == 1);
QMimeData *mime = new QMimeData;
mime->setData(TRAY_DRAG_FALG, QByteArray());
for (auto index : indexes) {
if (!index.isValid())
continue;
int itemIndex = index.row();
auto info = m_winInfos.at(itemIndex);
mime->setData("type", QByteArray::number(static_cast<int>(info.type)));
mime->setData("key", info.key.toLatin1());
mime->setData("itemKey", info.itemKey.toLatin1());
mime->setData("winId", QByteArray::number(info.winId));
mime->setData("servicePath", info.servicePath.toLatin1());
mime->setData("isTypeWritting", info.isTypeWriting ? "1" : "0");
mime->setData("expand", info.expand ? "1" : "0");
mime->setImageData(QVariant::fromValue((qulonglong)(info.pluginInter)));
//TODO 支持多个index的数据待支持
}
return mime;
}
QVariant TrayModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
int itemIndex = index.row();
const WinInfo &info = m_winInfos[itemIndex];
switch (role) {
case Role::TypeRole:
return info.type;
case Role::KeyRole:
return info.key;
case Role::WinIdRole:
return info.winId;
case Role::ServiceRole:
return info.servicePath;
case Role::PluginInterfaceRole:
return (qulonglong)(info.pluginInter);
case Role::ExpandRole:
return info.expand;
case Role::ItemKeyRole:
return info.itemKey;
case Role::Blank:
return indexDragging(index);
default:
return QVariant();
}
}
bool TrayModel::removeRows(int row, int count, const QModelIndex &parent)
{
Q_UNUSED(count);
if (m_winInfos.size() - 1 < row)
return false;
beginRemoveRows(parent, row, row);
m_dragInfo = m_winInfos.takeAt(row);
endRemoveRows();
Q_EMIT rowCountChanged();
return true;
}
bool TrayModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(action)
Q_UNUSED(row)
Q_UNUSED(column)
TrayIconType iconType = parent.data(TrayModel::Role::TypeRole).value<TrayIconType>();
if (iconType == TrayIconType::ExpandIcon)
return false;
return data->formats().contains(TRAY_DRAG_FALG);
}
Qt::ItemFlags TrayModel::flags(const QModelIndex &index) const
{
const Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index);
Q_EMIT requestOpenEditor(index);
return defaultFlags | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
int TrayModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_winInfos.size();
}
bool TrayModel::isIconTray()
{
return m_isTrayIcon;
}
bool TrayModel::hasExpand() const
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.type == TrayIconType::ExpandIcon)
return true;
}
return false;
}
bool TrayModel::isEmpty() const
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.type != TrayIconType::ExpandIcon)
return false;
}
return true;
}
void TrayModel::clear()
{
beginResetModel();
m_winInfos.clear();
endResetModel();
Q_EMIT rowCountChanged();
}
WinInfo TrayModel::getWinInfo(const QModelIndex &index)
{
int row = index.row();
if (row < 0 || row >= m_winInfos.size())
return WinInfo();
return m_winInfos[row];
}
void TrayModel::onXEmbedTrayAdded(quint32 winId)
{
if (!xembedCanExport(winId))
return;
for (const WinInfo &info : m_winInfos) {
if (info.winId == winId)
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = XEmbed;
info.key = "wininfo:" + QString::number(winId);
info.itemKey = xembedItemKey(winId);
info.winId = winId;
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onXEmbedTrayRemoved(quint32 winId)
{
for (auto info : m_winInfos) {
if (info.winId == winId) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
return;
}
}
}
QString TrayModel::fileNameByServiceName(const QString &serviceName) const
{
QStringList serviceInfo = serviceName.split("/");
if (serviceInfo.size() <= 0)
return QString();
QDBusInterface dbsInterface("org.freedesktop.DBus", "/org/freedesktop/DBus",
"org.freedesktop.DBus", QDBusConnection::sessionBus());
QDBusMessage msg = dbsInterface.call("GetConnectionUnixProcessID", serviceInfo[0] );
QList<QVariant> arguments = msg.arguments();
if (arguments.size() == 0)
return QString();
QVariant v = arguments.at(0);
uint pid = v.toUInt();
QString path = QString("/proc/%1/cmdline").arg(pid);
QFile file(path);
if (file.open(QIODevice::ReadOnly)) {
const QString fileName = file.readAll();
file.close();
return fileName;
}
return QString();
}
bool TrayModel::isTypeWriting(const QString &servicePath) const
{
const QString appFilePath = fileNameByServiceName(servicePath);
return (appFilePath.startsWith("/usr/bin/fcitx") || appFilePath.endsWith("chinime-qim"));
}
void TrayModel::saveConfig(int index, const WinInfo &winInfo)
{
if (m_isTrayIcon) {
// 如果是从任务栏将图标移动到托盘,就从配置中移除
if (!m_fixedTrayNames.contains(winInfo.itemKey))
return;
m_fixedTrayNames.removeOne(winInfo.itemKey);
} else {
// 如果是将图标从托盘移到任务栏上面,就增加到配置中
if (m_fixedTrayNames.contains(winInfo.itemKey))
return;
if (index >= 0 && index < m_fixedTrayNames.size()) {
m_fixedTrayNames.insert(index, winInfo.itemKey);
} else {
m_fixedTrayNames << winInfo.itemKey;
}
}
SETTINGCONFIG->setValue(DOCKQUICKTRAYNAME, m_fixedTrayNames);
}
void TrayModel::removeWinInfo(WinInfo winInfo)
{
for (const WinInfo &info : m_winInfos) {
if (winInfo == info) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
break;
}
}
}
bool TrayModel::inTrayConfig(const QString itemKey) const
{
if (m_isTrayIcon) {
// 如果是托盘区域,显示所有不在配置中的应用
return !m_fixedTrayNames.contains(itemKey);
}
// 如果是任务栏区域,显示所有在配置中的应用
return m_fixedTrayNames.contains(itemKey);
}
QString TrayModel::xembedItemKey(quint32 winId) const
{
return QString("embed:%1").arg(PlatformUtils::getAppNameForWindow(winId));
}
bool TrayModel::xembedCanExport(quint32 winId) const
{
return inTrayConfig(xembedItemKey(winId));
}
QString TrayModel::sniItemKey(const QString &servicePath) const
{
if (isTypeWriting(servicePath))
return "fcitx";
QString fileName = fileNameByServiceName(servicePath);
return QString("sni:%1").arg(fileName.mid(fileName.lastIndexOf("/") + 1));
}
bool TrayModel::sniCanExport(const QString &servicePath) const
{
return inTrayConfig(sniItemKey(servicePath));
}
bool TrayModel::indicatorCanExport(const QString &indicatorName) const
{
return inTrayConfig(IndicatorTrayItem::toIndicatorKey(indicatorName));
}
QString TrayModel::systemItemKey(const QString &pluginName) const
{
return QString("systemItem:%1").arg(pluginName);
}
bool TrayModel::systemItemCanExport(const QString &pluginName) const
{
return inTrayConfig(systemItemKey(pluginName));
}
void TrayModel::sortItems()
{
// 如果当前是展开托盘的内容,则无需排序
if (m_isTrayIcon)
return;
// 数据排列,展开按钮始终排在最前面,输入法始终排在最后面
WinInfos expandWin;
WinInfos inputMethodWin;
// 从列表中获取输入法和展开按钮
for (const WinInfo &winInfo : m_winInfos) {
switch (winInfo.type) {
case TrayIconType::ExpandIcon: {
expandWin << winInfo;
break;
}
case TrayIconType::Sni: {
if (winInfo.isTypeWriting)
inputMethodWin << winInfo;
break;
}
default:
break;
}
}
// 从列表中移除展开按钮
for (const WinInfo &winInfo : expandWin)
m_winInfos.removeOne(winInfo);
// 从列表中移除输入法
for (const WinInfo &winInfo : inputMethodWin)
m_winInfos.removeOne(winInfo);
// 将展开按钮添加到列表的最前面
for (int i = expandWin.size() - 1; i >= 0; i--)
m_winInfos.push_front(expandWin[i]);
// 将输入法添加到列表的最后面
for (int i = 0; i < inputMethodWin.size(); i++)
m_winInfos.push_back(inputMethodWin[i]);
}
void TrayModel::onSniTrayAdded(const QString &servicePath)
{
if (!sniCanExport(servicePath))
return;
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.servicePath == servicePath)
return;
}
bool typeWriting = isTypeWriting(servicePath);
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = Sni;
info.key = "sni:" + servicePath;
info.itemKey = sniItemKey(servicePath);
info.servicePath = servicePath;
info.isTypeWriting = typeWriting; // 是否为输入法
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onSniTrayRemoved(const QString &servicePath)
{
for (const WinInfo &info : m_winInfos) {
if (info.servicePath == servicePath) {
int index = m_winInfos.indexOf(info);
// 如果为输入法则无需立刻删除等100毫秒后再观察是否会删除输入法(因为在100毫秒内如果是切换输入法就会很快发送add信号)
if (info.isTypeWriting) {
QTimer::singleShot(100, this, [ servicePath, this ] {
for (WinInfo info : m_winInfos) {
if (info.servicePath == servicePath) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
}
}
});
} else {
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
}
break;
}
}
}
void TrayModel::onIndicatorFounded(const QString &indicatorName)
{
const QString &itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName);
if (exist(itemKey) || !IndicatorTrayItem::isIndicatorKey(itemKey))
return;
IndicatorPlugin *indicatorTray = nullptr;
if (!m_indicatorMap.keys().contains(indicatorName)) {
indicatorTray = new IndicatorPlugin(indicatorName, this);
m_indicatorMap[indicatorName] = indicatorTray;
} else {
indicatorTray = m_indicatorMap[itemKey];
}
connect(indicatorTray, &IndicatorPlugin::delayLoaded, indicatorTray, [ = ] {
onIndicatorAdded(indicatorName);
}, Qt::UniqueConnection);
connect(indicatorTray, &IndicatorPlugin::removed, this, [ = ] {
onIndicatorRemoved(indicatorName);
}, Qt::UniqueConnection);
}
void TrayModel::onIndicatorAdded(const QString &indicatorName)
{
if (!indicatorCanExport(indicatorName))
return;
const QString &itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName);
for (const WinInfo &info : m_winInfos) {
if (info.itemKey == itemKey)
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = Incicator;
info.key = itemKey;
info.itemKey = itemKey;
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onIndicatorRemoved(const QString &indicatorName)
{
const QString &itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName);
removeRow(itemKey);
}
void TrayModel::onSystemTrayAdded(PluginsItemInterface *itemInter)
{
if (!systemItemCanExport(itemInter->pluginName()))
return;
for (const WinInfo &info : m_winInfos) {
if (info.pluginInter == itemInter)
return;
}
beginInsertRows(QModelIndex(), rowCount(), rowCount());
WinInfo info;
info.type = SystemItem;
info.pluginInter = itemInter;
info.itemKey = systemItemKey(itemInter->pluginName());
m_winInfos.append(info);
sortItems();
endInsertRows();
Q_EMIT rowCountChanged();
}
void TrayModel::onSystemTrayRemoved(PluginsItemInterface *itemInter)
{
for (const WinInfo &info : m_winInfos) {
if (info.pluginInter != itemInter)
continue;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_winInfos.removeOne(info);
endInsertRows();
Q_EMIT rowCountChanged();
break;
}
}
void TrayModel::onSettingChanged(const QString &key, const QVariant &value)
{
if (key != DOCKQUICKTRAYNAME)
return;
// 先将其转换为任务栏上的图标列表
m_fixedTrayNames = value.toStringList();
// 依次获取所有的图盘图标判断当前图标是否可以保持在当前的view中
// 如果可以保留则添加到view上显示否则移除显示
QList<quint32> trayWinIds = m_monitor->trayWinIds();
for (quint32 trayId : trayWinIds) {
if (xembedCanExport(trayId))
onXEmbedTrayAdded(trayId);
else
onXEmbedTrayRemoved(trayId);
}
QStringList sniServices = m_monitor->sniServices();
for (const QString &sniService : sniServices) {
if (sniCanExport(sniService))
onSniTrayAdded(sniService);
else
onSniTrayRemoved(sniService);
}
QStringList indicators = m_monitor->indicatorNames();
for (const QString &indicatorName : indicators) {
if (!m_indicatorMap.contains(indicatorName))
continue;
IndicatorPlugin *plugin = m_indicatorMap[indicatorName];
if (!plugin->isLoaded())
continue;
if (indicatorCanExport(indicatorName))
onIndicatorAdded(indicatorName);
else
onIndicatorRemoved(indicatorName);
}
QList<PluginsItemInterface *> pluginItems = m_monitor->systemTrays();
for (PluginsItemInterface *plugin : pluginItems) {
if (systemItemCanExport(plugin->pluginName()))
onSystemTrayAdded(plugin);
else
onSystemTrayRemoved(plugin);
}
}
void TrayModel::removeRow(const QString &itemKey)
{
for (const WinInfo &info : m_winInfos) {
if (info.itemKey == itemKey) {
int index = m_winInfos.indexOf(info);
beginRemoveRows(QModelIndex(), index, index);
m_winInfos.removeOne(info);
endRemoveRows();
Q_EMIT rowCountChanged();
break;
}
}
}
void TrayModel::addRow(WinInfo info)
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.key == info.key)
return;
}
beginResetModel();
m_winInfos.append(info);
sortItems();
endResetModel();
Q_EMIT requestRefreshEditor();
Q_EMIT rowCountChanged();
}
void TrayModel::insertRow(int index, WinInfo info)
{
for (int i = 0; i < m_winInfos.size(); i++) {
const WinInfo &wininfo = m_winInfos[i];
if (wininfo.key == info.key) {
beginResetModel();
m_winInfos.swapItemsAt(index, i);
endResetModel();
return;
}
}
beginInsertRows(QModelIndex(), index, index);
m_winInfos.insert(index, info);
sortItems();
endInsertRows();
Q_EMIT requestRefreshEditor();
Q_EMIT rowCountChanged();
}
bool TrayModel::exist(const QString &itemKey)
{
for (const WinInfo &winInfo : m_winInfos) {
if (winInfo.key == itemKey)
return true;
}
return false;
}