mirror of
https://github.com/noctalia-dev/noctalia-shell.git
synced 2026-05-11 17:08:27 +08:00
Merge pull request #2656 from Mathew-D/v5
fix(media): MPRIS change to async
This commit is contained in:
+625
-449
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
@@ -11,8 +13,10 @@
|
||||
#include <vector>
|
||||
|
||||
namespace sdbus {
|
||||
class Error;
|
||||
class IObject;
|
||||
class IProxy;
|
||||
class Variant;
|
||||
} // namespace sdbus
|
||||
|
||||
class SessionBus;
|
||||
@@ -98,16 +102,24 @@ private:
|
||||
void syncSignals(const std::optional<MprisPlayerInfo>& previousActive);
|
||||
void registerBusSignals();
|
||||
void discoverPlayers();
|
||||
void scheduleDiscoveryDrain();
|
||||
void scheduleStartupRediscovery();
|
||||
void scheduleRecoveryDiscovery();
|
||||
void addOrRefreshPlayer(const std::string& busName);
|
||||
void applyPlayerSnapshot(const std::string& busName, const MprisPlayerInfo& info, bool hadPositionSignal);
|
||||
void refreshPlayerPosition(const std::string& busName, bool notifyChange);
|
||||
void applyPositionSample(const std::string& busName, int64_t rawPositionUs, bool notifyChange);
|
||||
void removePlayer(const std::string& busName);
|
||||
[[nodiscard]] MprisPlayerInfo readPlayerInfo(sdbus::IProxy& proxy, const std::string& busName) const;
|
||||
[[nodiscard]] MprisPlayerInfo
|
||||
readPlayerInfoFromProperties(const std::string& busName, const std::map<std::string, sdbus::Variant>& rootProps,
|
||||
const std::map<std::string, sdbus::Variant>& playerProps) const;
|
||||
[[nodiscard]] MprisPlayerInfo projectedPlayerInfo(const MprisPlayerInfo& player) const;
|
||||
[[nodiscard]] std::int64_t projectedPositionUs(const MprisPlayerInfo& player) const;
|
||||
[[nodiscard]] std::optional<std::string> chooseActivePlayer() const;
|
||||
[[nodiscard]] bool isBlacklisted(const MprisPlayerInfo& player) const;
|
||||
std::function<void(std::optional<sdbus::Error>)> makeAsyncReplyHandler(std::string op, std::string busName);
|
||||
std::function<void(std::optional<sdbus::Error>)> makeAsyncReplyHandler(std::string op, std::string busName,
|
||||
std::string_view method);
|
||||
[[nodiscard]] bool callPlayerMethod(const std::string& busName, const char* methodName);
|
||||
[[nodiscard]] bool canInvoke(const MprisPlayerInfo& player, const char* methodName) const;
|
||||
|
||||
@@ -141,6 +153,7 @@ private:
|
||||
[[nodiscard]] std::tuple<bool, std::string, std::vector<std::string>> onGetPlayerPreferences() const;
|
||||
|
||||
SessionBus& m_bus;
|
||||
std::shared_ptr<void> m_aliveGuard = std::make_shared<int>(0);
|
||||
std::unique_ptr<sdbus::IObject> m_controlObject;
|
||||
std::unique_ptr<sdbus::IProxy> m_dbusProxy;
|
||||
std::unordered_map<std::string, std::unique_ptr<sdbus::IProxy>> m_playerProxies;
|
||||
@@ -161,6 +174,7 @@ private:
|
||||
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_lastPropertiesUpdate;
|
||||
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_lastPlayingUpdate;
|
||||
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_lastStrongMetadataUpdate;
|
||||
std::deque<std::string> m_pendingDiscoveryBusNames;
|
||||
std::string m_lastActivePlayer;
|
||||
std::string m_lastEmittedActivePlayer;
|
||||
std::optional<std::string> m_pinnedPlayerPreference;
|
||||
@@ -169,4 +183,5 @@ private:
|
||||
std::function<void()> m_changeCallback;
|
||||
int m_startupRediscoveryPassesRemaining = 4;
|
||||
bool m_recoveryDiscoveryScheduled = false;
|
||||
bool m_discoveryDrainScheduled = false;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "shell/control_center/media_tab.h"
|
||||
|
||||
#include "core/deferred_call.h"
|
||||
#include "core/log.h"
|
||||
#include "dbus/mpris/mpris_art.h"
|
||||
#include "dbus/mpris/mpris_service.h"
|
||||
@@ -71,7 +72,7 @@ MediaTab::MediaTab(MprisService* mpris, HttpClient* httpClient, PipeWireSpectrum
|
||||
: m_mpris(mpris), m_httpClient(httpClient), m_spectrum(spectrum), m_wayland(wayland),
|
||||
m_renderContext(renderContext) {}
|
||||
|
||||
MediaTab::~MediaTab() = default;
|
||||
MediaTab::~MediaTab() { m_aliveGuard.reset(); }
|
||||
|
||||
void MediaTab::openPlayerMenu() {
|
||||
if (m_playerMenuPopup == nullptr || m_mpris == nullptr || m_playerMenuButton == nullptr) {
|
||||
@@ -270,17 +271,19 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
m_pendingSeekBusName = seekBusName;
|
||||
m_pendingSeekUs = targetUs;
|
||||
m_pendingSeekUntil = now + std::chrono::milliseconds(3000);
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
|
||||
bool seekIssued = false;
|
||||
if (!seekBusName.empty()) {
|
||||
seekIssued = m_mpris->setPosition(seekBusName, targetUs);
|
||||
} else {
|
||||
seekIssued = m_mpris->setPositionActive(targetUs);
|
||||
}
|
||||
if (!seekIssued) {
|
||||
// Keep the thumb stable briefly even if transport seek dispatch races.
|
||||
m_pendingSeekUntil = now + std::chrono::milliseconds(750);
|
||||
}
|
||||
DeferredCall::callLater([this, aliveGuard, seekBusName, targetUs]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!seekBusName.empty()) {
|
||||
(void)m_mpris->setPosition(seekBusName, targetUs);
|
||||
} else {
|
||||
(void)m_mpris->setPositionActive(targetUs);
|
||||
}
|
||||
PanelManager::instance().refresh();
|
||||
});
|
||||
});
|
||||
m_progressSlider = progress.get();
|
||||
mediaStack->addChild(std::move(progress));
|
||||
@@ -305,13 +308,16 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
repeat->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
|
||||
repeat->setRadius(Style::radiusLg * scale);
|
||||
repeat->setOnClick([this]() {
|
||||
if (m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto current = m_mpris->loopStatusActive().value_or("None");
|
||||
const std::string next = current == "None" ? "Playlist" : (current == "Playlist" ? "Track" : "None");
|
||||
m_mpris->setLoopStatusActive(next);
|
||||
PanelManager::instance().refresh();
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
const auto current = m_mpris->loopStatusActive().value_or("None");
|
||||
const std::string next = current == "None" ? "Playlist" : (current == "Playlist" ? "Track" : "None");
|
||||
(void)m_mpris->setLoopStatusActive(next);
|
||||
PanelManager::instance().refresh();
|
||||
});
|
||||
});
|
||||
m_repeatButton = repeat.get();
|
||||
controls->addChild(std::move(repeat));
|
||||
@@ -324,10 +330,14 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
previous->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
|
||||
previous->setRadius(Style::radiusLg * scale);
|
||||
previous->setOnClick([this]() {
|
||||
if (m_mpris != nullptr) {
|
||||
m_mpris->previousActive();
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
(void)m_mpris->previousActive();
|
||||
PanelManager::instance().refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
m_prevButton = previous.get();
|
||||
controls->addChild(std::move(previous));
|
||||
@@ -340,10 +350,14 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
playPause->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
|
||||
playPause->setRadius(Style::radiusLg * scale);
|
||||
playPause->setOnClick([this]() {
|
||||
if (m_mpris != nullptr) {
|
||||
m_mpris->playPauseActive();
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
(void)m_mpris->playPauseActive();
|
||||
PanelManager::instance().refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
m_playPauseButton = playPause.get();
|
||||
controls->addChild(std::move(playPause));
|
||||
@@ -356,10 +370,14 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
next->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
|
||||
next->setRadius(Style::radiusLg * scale);
|
||||
next->setOnClick([this]() {
|
||||
if (m_mpris != nullptr) {
|
||||
m_mpris->nextActive();
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
(void)m_mpris->nextActive();
|
||||
PanelManager::instance().refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
m_nextButton = next.get();
|
||||
controls->addChild(std::move(next));
|
||||
@@ -372,11 +390,15 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
shuffle->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
|
||||
shuffle->setRadius(Style::radiusLg * scale);
|
||||
shuffle->setOnClick([this]() {
|
||||
if (m_mpris != nullptr) {
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
const bool enabled = m_mpris->shuffleActive().value_or(false);
|
||||
m_mpris->setShuffleActive(!enabled);
|
||||
(void)m_mpris->setShuffleActive(!enabled);
|
||||
PanelManager::instance().refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
m_shuffleButton = shuffle.get();
|
||||
controls->addChild(std::move(shuffle));
|
||||
@@ -419,17 +441,21 @@ std::unique_ptr<Flex> MediaTab::create() {
|
||||
if (m_wayland != nullptr && m_renderContext != nullptr) {
|
||||
m_playerMenuPopup = std::make_unique<ContextMenuPopup>(*m_wayland, *m_renderContext);
|
||||
m_playerMenuPopup->setOnActivate([this](const ContextMenuControlEntry& entry) {
|
||||
if (m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (entry.id == 0) {
|
||||
m_mpris->clearPinnedPlayerPreference();
|
||||
} else {
|
||||
const std::size_t idx = static_cast<std::size_t>(entry.id - 1);
|
||||
if (idx < m_playerBusNames.size()) {
|
||||
m_mpris->setPinnedPlayerPreference(m_playerBusNames[idx]);
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard, entry]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (entry.id == 0) {
|
||||
m_mpris->clearPinnedPlayerPreference();
|
||||
} else {
|
||||
const std::size_t idx = static_cast<std::size_t>(entry.id - 1);
|
||||
if (idx < m_playerBusNames.size()) {
|
||||
m_mpris->setPinnedPlayerPreference(m_playerBusNames[idx]);
|
||||
}
|
||||
}
|
||||
PanelManager::instance().refresh();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -603,7 +629,15 @@ void MediaTab::setActive(bool active) {
|
||||
// Pull a fresh snapshot (including Position) when the tab opens so the
|
||||
// progress slider starts at the current playback position.
|
||||
m_positionSampleAt = {};
|
||||
m_mpris->refreshPlayers();
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
m_mpris->refreshPlayers();
|
||||
PanelManager::instance().requestUpdateOnly();
|
||||
PanelManager::instance().requestRedraw();
|
||||
});
|
||||
m_lastMprisRefreshAttempt = std::chrono::steady_clock::now();
|
||||
}
|
||||
}
|
||||
@@ -676,11 +710,15 @@ void MediaTab::refresh(Renderer& renderer) {
|
||||
if (shouldRetryMpris) {
|
||||
m_lastMprisRefreshAttempt = now;
|
||||
kLog.debug("media tab retrying mpris discovery players={} active={}", players.size(), active.has_value());
|
||||
m_mpris->refreshPlayers();
|
||||
players = m_mpris->listPlayers();
|
||||
active = m_mpris->activePlayer();
|
||||
kLog.debug("media tab refresh after retry players={} active={} active_bus=\"{}\"", players.size(),
|
||||
active.has_value(), active.has_value() ? active->busName : std::string{});
|
||||
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
|
||||
DeferredCall::callLater([this, aliveGuard]() {
|
||||
if (aliveGuard.expired() || m_mpris == nullptr) {
|
||||
return;
|
||||
}
|
||||
m_mpris->refreshPlayers();
|
||||
PanelManager::instance().requestUpdateOnly();
|
||||
PanelManager::instance().requestRedraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,11 @@ private:
|
||||
|
||||
void openPlayerMenu();
|
||||
|
||||
// Guard token for deferred callbacks that run on the next main-loop tick.
|
||||
// Callbacks capture a weak_ptr so they can detect destruction without
|
||||
// relying on a raw this pointer staying valid.
|
||||
std::shared_ptr<void> m_aliveGuard = std::make_shared<int>(0);
|
||||
|
||||
MprisService* m_mpris = nullptr;
|
||||
HttpClient* m_httpClient = nullptr;
|
||||
PipeWireSpectrum* m_spectrum = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user