File indexing completed on 2024-05-19 05:38:36
0001 /* 0002 SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 #include "mpris2model.h" 0008 0009 #include <QUrl> 0010 0011 #include "mpris2filterproxymodel.h" 0012 #include "mpris2sourcemodel.h" 0013 #include "multiplexermodel.h" 0014 #include "playercontainer.h" 0015 0016 Mpris2Model::Mpris2Model(QObject *parent) 0017 : QConcatenateTablesProxyModel(parent) 0018 , m_multiplexerModel(MultiplexerModel::self()) 0019 , m_mprisModel(Mpris2FilterProxyModel::self()) 0020 { 0021 addSourceModel(m_multiplexerModel.get()); 0022 addSourceModel(m_mprisModel.get()); 0023 0024 connect(this, &QConcatenateTablesProxyModel::dataChanged, this, &Mpris2Model::onDataChanged); 0025 connect(this, &QConcatenateTablesProxyModel::rowsAboutToBeRemoved, this, &Mpris2Model::onRowsAboutToBeRemoved); 0026 connect(this, &QConcatenateTablesProxyModel::rowsRemoved, this, &Mpris2Model::onRowsRemoved); 0027 connect(this, &QConcatenateTablesProxyModel::rowsInserted, this, &Mpris2Model::onRowsInserted); 0028 0029 if (rowCount() > 0) { 0030 m_currentPlayer = index(m_currentIndex, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0031 Q_EMIT currentPlayerChanged(); 0032 } 0033 } 0034 0035 Mpris2Model::~Mpris2Model() 0036 { 0037 } 0038 0039 QHash<int, QByteArray> Mpris2Model::roleNames() const 0040 { 0041 return m_mprisModel->roleNames(); 0042 } 0043 0044 unsigned Mpris2Model::currentIndex() const 0045 { 0046 return m_currentIndex; 0047 } 0048 0049 void Mpris2Model::setCurrentIndex(unsigned _index) 0050 { 0051 if (m_currentIndex == _index) { 0052 return; 0053 } 0054 0055 if (rowCount() > 0) { 0056 m_currentIndex = std::clamp<unsigned>(_index, 0, rowCount() - 1); 0057 m_currentPlayer = index(m_currentIndex, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0058 } else { 0059 m_currentIndex = 0; 0060 m_currentPlayer = nullptr; 0061 } 0062 Q_EMIT currentPlayerChanged(); 0063 Q_EMIT currentIndexChanged(); 0064 } 0065 0066 PlayerContainer *Mpris2Model::currentPlayer() const 0067 { 0068 return m_currentPlayer; 0069 } 0070 0071 PlayerContainer *Mpris2Model::playerForLauncherUrl(const QUrl &launcherUrl, unsigned pid) const 0072 { 0073 if (launcherUrl.isEmpty()) { 0074 return nullptr; 0075 } 0076 0077 // MPRIS spec explicitly mentions that "DesktopEntry" is with .desktop extension trimmed 0078 // Moreover, remove URL parameters, like wmClass (part after the question mark) 0079 QStringList result = launcherUrl.toString().split(QLatin1Char('/')); 0080 if (result.empty()) { 0081 return nullptr; 0082 } 0083 0084 result = result.crbegin()->split(QLatin1Char('?')); 0085 if (result.empty()) { 0086 return nullptr; 0087 } 0088 0089 QString &desktopFileName = result.begin()->remove(QLatin1String(".desktop")); 0090 if (desktopFileName.contains(QLatin1String("applications:"))) { 0091 desktopFileName.remove(0, 13); 0092 } 0093 0094 // Find in PID 0095 QModelIndex idx, fallbackIdx; 0096 for (int i = 0, size = m_mprisModel->rowCount(); i < size; ++i) { 0097 QModelIndex _idx = m_mprisModel->index(i, 0); 0098 if (_idx.data(Mpris2SourceModel::InstancePidRole).toUInt() == pid) { 0099 idx = _idx; 0100 break; 0101 } else if (pid > 0 && _idx.data(Mpris2SourceModel::KDEPidRole).toUInt() == pid) { 0102 idx = _idx; 0103 break; 0104 } else if (_idx.data(Mpris2SourceModel::DesktopEntryRole).toString() == desktopFileName) { 0105 fallbackIdx = _idx; 0106 } 0107 } 0108 0109 if (idx.isValid()) { 0110 return idx.data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0111 } else if (fallbackIdx.isValid()) { 0112 // If PID match fails, return fallbackSource. 0113 return fallbackIdx.data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0114 } else { 0115 return nullptr; 0116 } 0117 } 0118 0119 void Mpris2Model::onRowsInserted(const QModelIndex &, int first, int) 0120 { 0121 if (first == 0) { 0122 Q_ASSERT_X(m_currentIndex == 0, Q_FUNC_INFO, qUtf8Printable(QString::number(m_currentIndex))); 0123 m_currentPlayer = index(0, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0124 Q_EMIT currentPlayerChanged(); 0125 } 0126 } 0127 0128 void Mpris2Model::onRowsAboutToBeRemoved(const QModelIndex &, int first, int) 0129 { 0130 if (m_currentIndex < static_cast<unsigned>(first)) { 0131 return; 0132 } 0133 unsigned currentIndex = m_currentIndex; 0134 const PlayerContainer *const oldPlayer = m_currentPlayer; 0135 if (currentIndex == static_cast<unsigned>(first)) { 0136 currentIndex = 0; // Reset to Multiplexer 0137 } 0138 if (rowCount() - 1 /* Multiplexer */ >= 2 /* At least two players */) { 0139 m_currentPlayer = index(currentIndex, 0).data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0140 } else { 0141 m_currentPlayer = nullptr; 0142 } 0143 if (oldPlayer != m_currentPlayer) { 0144 Q_EMIT currentPlayerChanged(); 0145 } 0146 0147 // m_currentIndex is updated in onRowsRemoved(...) 0148 } 0149 0150 void Mpris2Model::onRowsRemoved(const QModelIndex &, int first, int) 0151 { 0152 if (m_currentIndex < static_cast<unsigned>(first)) { 0153 return; 0154 } 0155 if (m_currentIndex == static_cast<unsigned>(first)) { 0156 m_currentIndex = 0; // Reset to Multiplexer 0157 } else if (m_currentIndex > 0) { 0158 --m_currentIndex; 0159 } 0160 Q_EMIT currentIndexChanged(); 0161 } 0162 0163 void Mpris2Model::onDataChanged(const QModelIndex &topLeft, const QModelIndex &, const QList<int> &roles) 0164 { 0165 if (m_currentIndex != static_cast<unsigned>(topLeft.row()) || (!roles.empty() && !roles.contains(Mpris2SourceModel::ContainerRole))) { 0166 return; 0167 } 0168 m_currentPlayer = topLeft.data(Mpris2SourceModel::ContainerRole).value<PlayerContainer *>(); 0169 Q_EMIT currentPlayerChanged(); 0170 } 0171 0172 #include "moc_mpris2model.cpp"