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"