File indexing completed on 2024-05-19 05:38:37

0001 /*
0002     SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "multiplexermodel.h"
0008 
0009 #include <KLocalizedString>
0010 
0011 #include "mpris2sourcemodel.h"
0012 #include "multiplexer.h"
0013 #include "multiplexermodel.h"
0014 #include "playercontainer.h"
0015 
0016 std::shared_ptr<MultiplexerModel> MultiplexerModel::self()
0017 {
0018     static std::weak_ptr<MultiplexerModel> s_model;
0019     if (s_model.expired()) {
0020         auto ptr = std::make_shared<MultiplexerModel>();
0021         s_model = ptr;
0022         return ptr;
0023     }
0024 
0025     return s_model.lock();
0026 }
0027 
0028 MultiplexerModel::MultiplexerModel(QObject *parent)
0029     : QAbstractListModel(parent)
0030     , m_multiplexer(Multiplexer::self())
0031 {
0032     updateActivePlayer();
0033     /*
0034         # Why is Qt::QueuedConnection used?
0035         If there are only one player remaining and the last player is closed:
0036         1. Mpris2SourceModel::rowsRemoved -> Mpris2FilterProxyModel::rowsRemoved
0037         3. Mpris2FilterProxyModel::rowsRemoved -> Multiplexer::onRowsRemoved
0038         4. In Multiplexer::onRowsRemoved, activePlayerIndex is updated -> Multiplexer::activePlayerIndexChanged
0039         5. Multiplexer::activePlayerIndexChanged -> **Qt::QueuedConnection**, so MultiplexerModel::updateActivePlayer() is not called in this context。
0040             ⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️⚠️
0041             Can't call updateActivePlayer() in the current context because updateActivePlayer() will emit rowsRemoved, which confuses Mpris2Model
0042             because at this moment Mpris2FilterProxyModel::rowsRemoved is not finished!!! Without Qt::QueuedConnection there will be a CRASH!
0043             💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣💣
0044         6. After Mpris2SourceModel::rowsRemoved, the container is deleted -> PlayerContainer::destroyed
0045         7. PlayerContainer::destroyed -> MultiplexerModel::updateActivePlayer
0046         8. MultiplexerModel::updateActivePlayer -> (no player now) MultiplexerModel::rowsRemoved
0047         9. The queued connection now calls MultiplexerModel::updateActivePlayer(), but there is no player now so it just simply returns.
0048 
0049         # Why is this connection needed?
0050         If there is more than one player, and the active player changes to another player:
0051         1. activePlayerIndex is updated -> Multiplexer::activePlayerIndexChanged
0052         2. Multiplexer::activePlayerIndexChanged -> **Qt::QueuedConnection**, so MultiplexerModel::updateActivePlayer() is not called in this context
0053         3. The queued connection now calls MultiplexerModel::updateActivePlayer(), and (m_multiplexer->activePlayer().value() != m_activePlayer) is satisfied,
0054            so a new player becomes the starred player.
0055      */
0056     connect(m_multiplexer.get(), &Multiplexer::activePlayerIndexChanged, this, &MultiplexerModel::updateActivePlayer, Qt::QueuedConnection);
0057 }
0058 
0059 MultiplexerModel::~MultiplexerModel()
0060 {
0061 }
0062 
0063 QVariant MultiplexerModel::data(const QModelIndex &index, int role) const
0064 {
0065     if (!checkIndex(index, CheckIndexOption::IndexIsValid) || !m_activePlayer) {
0066         return {};
0067     }
0068 
0069     switch (role) {
0070     case Mpris2SourceModel::IdentityRole:
0071         return i18nc("@action:button", "Choose player automatically");
0072     case Mpris2SourceModel::IconNameRole:
0073         return QStringLiteral("emblem-favorite");
0074     default:
0075         return Mpris2SourceModel::dataFromPlayer(m_activePlayer, role);
0076     }
0077 }
0078 
0079 int MultiplexerModel::rowCount(const QModelIndex &parent) const
0080 {
0081     if (parent.isValid()) {
0082         return 0;
0083     }
0084     return m_activePlayer ? 1 : 0;
0085 }
0086 
0087 QHash<int, QByteArray> MultiplexerModel::roleNames() const
0088 {
0089     return Mpris2SourceModel::self()->roleNames();
0090 }
0091 
0092 void MultiplexerModel::updateActivePlayer()
0093 {
0094     if (m_activePlayer && !m_multiplexer->activePlayer().value()) {
0095         beginRemoveRows({}, 0, 0);
0096         disconnect(m_activePlayer, nullptr, this, nullptr);
0097         m_activePlayer = nullptr;
0098         endRemoveRows();
0099     } else if (m_multiplexer->activePlayer().value() != m_activePlayer) {
0100         if (!m_activePlayer) {
0101             beginInsertRows({}, 0, 0);
0102             m_activePlayer = m_multiplexer->activePlayer().value();
0103             endInsertRows();
0104         } else {
0105             disconnect(m_activePlayer, nullptr, this, nullptr);
0106             m_activePlayer = m_multiplexer->activePlayer().value();
0107             Q_EMIT dataChanged(index(0, 0), index(0, 0));
0108         }
0109 
0110         connect(m_activePlayer, &QObject::destroyed, this, &MultiplexerModel::updateActivePlayer);
0111 
0112         // Property bindings
0113         connect(m_activePlayer, &AbstractPlayerContainer::canControlChanged, this, [this] {
0114             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanControlRole});
0115         });
0116         connect(m_activePlayer, &AbstractPlayerContainer::trackChanged, this, [this] {
0117             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::TrackRole});
0118         });
0119         connect(m_activePlayer, &AbstractPlayerContainer::canGoNextChanged, this, [this] {
0120             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanGoNextRole});
0121         });
0122         connect(m_activePlayer, &AbstractPlayerContainer::canGoPreviousChanged, this, [this] {
0123             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanGoPreviousRole});
0124         });
0125         connect(m_activePlayer, &AbstractPlayerContainer::canPlayChanged, this, [this] {
0126             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanPlayRole});
0127         });
0128         connect(m_activePlayer, &AbstractPlayerContainer::canPauseChanged, this, [this] {
0129             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanPauseRole});
0130         });
0131         connect(m_activePlayer, &AbstractPlayerContainer::canStopChanged, this, [this] {
0132             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanStopRole});
0133         });
0134         connect(m_activePlayer, &AbstractPlayerContainer::canSeekChanged, this, [this] {
0135             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanSeekRole});
0136         });
0137         connect(m_activePlayer, &AbstractPlayerContainer::loopStatusChanged, this, [this] {
0138             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::LoopStatusRole});
0139         });
0140         connect(m_activePlayer, &AbstractPlayerContainer::playbackStatusChanged, this, [this] {
0141             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::PlaybackStatusRole});
0142         });
0143         connect(m_activePlayer, &AbstractPlayerContainer::positionChanged, this, [this] {
0144             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::PositionRole});
0145         });
0146         connect(m_activePlayer, &AbstractPlayerContainer::rateChanged, this, [this] {
0147             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::RateRole});
0148         });
0149         connect(m_activePlayer, &AbstractPlayerContainer::shuffleChanged, this, [this] {
0150             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::ShuffleRole});
0151         });
0152         connect(m_activePlayer, &AbstractPlayerContainer::volumeChanged, this, [this] {
0153             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::VolumeRole});
0154         });
0155         connect(m_activePlayer, &AbstractPlayerContainer::artUrlChanged, this, [this] {
0156             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::ArtUrlRole});
0157         });
0158         connect(m_activePlayer, &AbstractPlayerContainer::artistChanged, this, [this] {
0159             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::ArtistRole});
0160         });
0161         connect(m_activePlayer, &AbstractPlayerContainer::albumChanged, this, [this] {
0162             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::AlbumRole});
0163         });
0164         connect(m_activePlayer, &AbstractPlayerContainer::lengthChanged, this, [this] {
0165             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::LengthRole});
0166         });
0167         connect(m_activePlayer, &AbstractPlayerContainer::canQuitChanged, this, [this] {
0168             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanQuitRole});
0169         });
0170         connect(m_activePlayer, &AbstractPlayerContainer::canRaiseChanged, this, [this] {
0171             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanRaiseRole});
0172         });
0173         connect(m_activePlayer, &AbstractPlayerContainer::canSetFullscreenChanged, this, [this] {
0174             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::CanSetFullscreenRole});
0175         });
0176         connect(m_activePlayer, &AbstractPlayerContainer::desktopEntryChanged, this, [this] {
0177             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::DesktopEntryRole, Mpris2SourceModel::IconNameRole});
0178         });
0179         connect(m_activePlayer, &AbstractPlayerContainer::identityChanged, this, [this] {
0180             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::IdentityRole});
0181         });
0182         connect(m_activePlayer, &AbstractPlayerContainer::kdePidChanged, this, [this] {
0183             Q_EMIT dataChanged(index(0, 0), index(0, 0), {Mpris2SourceModel::KDEPidRole});
0184         });
0185     }
0186 }
0187 
0188 #include "moc_multiplexermodel.cpp"