/* * Copyright (C) 2022 ~ 2022 Deepin Technology Co., Ltd. * * Author: donghualin * * Maintainer: donghualin * * 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 . */ #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 #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); }); } 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; removeRows(m_dragModelIndex.row(), 1, QModelIndex()); dropInsert(newPos); emit QAbstractItemModel::dataChanged(m_dragModelIndex, m_dropModelIndex); } void TrayModel::dropInsert(int newPos) { beginInsertRows(QModelIndex(), newPos, newPos); WinInfo name = m_dragInfo; m_winInfos.insert(newPos, name); // 更新输入法的位置 endInsertRows(); } void TrayModel::clearDragDropIndex() { const QModelIndex startIndex = m_dragModelIndex; const QModelIndex endIndex = m_dropModelIndex; m_dragModelIndex = m_dropModelIndex = QModelIndex(); Q_EMIT requestRefreshEditor(); emit QAbstractItemModel::dataChanged(startIndex, endIndex); emit QAbstractItemModel::dataChanged(endIndex, startIndex); } void TrayModel::setDragingIndex(const QModelIndex index) { m_dragModelIndex = index; m_dropModelIndex = index; Q_EMIT requestRefreshEditor(); 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 (const WinInfo &winInfo : m_winInfos) { if (winInfo.type == TrayIconType::ExpandIcon) 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::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); Q_UNUSED(parent); 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; } 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(); } 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); 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_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); } 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::onSniTrayAdded(const QString &servicePath) { if (!sniCanExport(servicePath)) return; bool typeWriting = isTypeWriting(servicePath); int citxIndex = -1; for (int i = 0; i < m_winInfos.size(); i++) { WinInfo info = m_winInfos[i]; if (info.servicePath == servicePath) return; if (typeWriting && info.isTypeWriting) citxIndex = i; } beginInsertRows(QModelIndex(), rowCount(), rowCount()); WinInfo info; info.type = Sni; info.key = "sni:" + servicePath; info.itemKey = sniItemKey(servicePath); info.servicePath = servicePath; info.isTypeWriting = typeWriting; // 是否为输入法 if (typeWriting) { if (citxIndex < 0) { m_winInfos.append(info); } else { // 如果输入法在指定位置,则将输入法移动到指定位置 m_winInfos[citxIndex] = info; QTimer::singleShot(150, this, [ = ] { // 对比需要变化的图标 emit requestUpdateWidget({ citxIndex }); }); } } else { m_winInfos.append(info); Q_EMIT rowCountChanged(); } endInsertRows(); } 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.key == itemKey) return; } beginInsertRows(QModelIndex(), rowCount(), rowCount()); WinInfo info; info.type = Incicator; info.key = itemKey; info.itemKey = IndicatorTrayItem::toIndicatorKey(indicatorName); m_winInfos.append(info); 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); 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 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 (indicatorCanExport(indicatorName)) onIndicatorAdded(indicatorName); else onIndicatorRemoved(indicatorName); } QList 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.key == 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; } beginInsertRows(QModelIndex(), rowCount(), rowCount()); m_winInfos.append(info); endInsertRows(); 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.swap(index, i); endResetModel(); return; } } beginInsertRows(QModelIndex(), index, index); m_winInfos.insert(index, info); 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; }