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"