// Copyright (C) 2022 ~ 2022 Deepin Technology Co., Ltd. // SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: LGPL-3.0-or-later #include "tray_model.h" #include "tray_monitor.h" #include "indicatortrayitem.h" #include "indicatorplugin.h" #include "quicksettingcontroller.h" #include "pluginsiteminterface.h" #include "docksettings.h" #include "platformutils.h" #include #include #include #include #include #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(DockSettings::instance(), &DockSettings::quickPluginsChanged, this, &TrayModel::onSettingChanged); m_fixedTrayNames = DockSettings::instance()->getTrayItemsOnDock(); 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(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(); 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) & ~Qt::ItemIsSelectable; } 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 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; } } DockSettings::instance()->updateTrayItemsOnDock(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 QStringList &value) { // 先将其转换为任务栏上的图标列表 m_fixedTrayNames = value; } 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; }