#include "connection/dbus/mpris.hpp" #include #include #include #include "helpers/string.hpp" #include "giomm/dbusconnection.h" #include "giomm/dbusproxy.h" std::shared_ptr MprisController::getInstance() { static std::shared_ptr instance = std::shared_ptr(new MprisController()); return instance; } std::shared_ptr MprisController::createForPlayer(const std::string &bus_name) { return std::shared_ptr(new MprisController(bus_name)); } MprisController::MprisController() { connect_session_async(sigc::mem_fun(*this, &MprisController::on_bus_connected)); } MprisController::MprisController(const std::string &bus_name) : m_player_bus_name(bus_name) { connect_session_async(sigc::mem_fun(*this, &MprisController::on_bus_connected)); } std::vector MprisController::get_registered_players() const { return std::vector(registeredPlayers.begin(), registeredPlayers.end()); } sigc::signal &MprisController::signal_mpris_updated() { return mprisUpdatedSignal; } sigc::signal &MprisController::signal_playback_status_changed() { return playbackStatusChangedSignal; } sigc::signal &MprisController::signal_playback_position_changed() { return playbackPositionChangedSignal; } sigc::signal &MprisController::signal_can_seek_changed() { return canSeekChangedSignal; } sigc::signal &MprisController::signal_player_registered() { return playerRegisteredSignal; } sigc::signal &MprisController::signal_player_deregistered() { return playerDeregisteredSignal; } void MprisController::on_bus_connected(const Glib::RefPtr &result) { if (!result) { spdlog::error("DBus Connection Error: null async result"); return; } try { connection = Gio::DBus::Connection::get_finish(result); if (!m_dbus_proxy) { m_dbus_proxy = Gio::DBus::Proxy::create_sync( connection, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus"); if (m_dbus_proxy) { m_dbus_proxy->signal_signal().connect( sigc::mem_fun(*this, &MprisController::on_dbus_signal)); try { auto list_names_result = m_dbus_proxy->call_sync("ListNames"); auto names_variant = Glib::VariantBase::cast_dynamic< Glib::Variant>>(list_names_result.get_child(0)); for (const auto &name : names_variant.get()) { const std::string bus_name = name; if (bus_name.rfind("org.mpris.MediaPlayer2.", 0) == 0) { handle_player_registered(bus_name); } } } catch (const Glib::Error &ex) { spdlog::error("DBus ListNames Error: {}", ex.what()); } } } } catch (const Glib::Error &ex) { spdlog::error("DBus Connection Error: {}", ex.what()); } } void MprisController::on_dbus_signal(const Glib::ustring &, const Glib::ustring &signal_name, const Glib::VariantContainerBase ¶meters) { if (signal_name != "NameOwnerChanged") { return; } auto name_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(0)); auto old_owner_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(1)); auto new_owner_var = Glib::VariantBase::cast_dynamic>(parameters.get_child(2)); const std::string bus_name = name_var.get(); if (bus_name.rfind("org.mpris.MediaPlayer2.", 0) != 0) { return; } const bool had_owner = !old_owner_var.get().empty(); const bool has_owner = !new_owner_var.get().empty(); if (!had_owner && has_owner) { handle_player_registered(bus_name); } else if (had_owner && !has_owner) { handle_player_deregistered(bus_name); } } void MprisController::handle_player_registered(const std::string &bus_name) { spdlog::info("MPRIS player registered: {}", bus_name); registeredPlayers.insert(bus_name); if (bus_name == m_player_bus_name && !m_proxy && connection) { try { m_proxy = Gio::DBus::Proxy::create_sync( connection, m_player_bus_name, "/org/mpris/MediaPlayer2", "org.mpris.MediaPlayer2.Player"); if (m_proxy) { m_proxy->signal_properties_changed().connect( sigc::mem_fun(*this, &MprisController::on_properties_changed)); signalNotification(); emit_cached_playback_status(); emit_cached_position(); emit_cached_can_seek(); } } catch (const Glib::Error &ex) { spdlog::error("DBus Connection Error: {}", ex.what()); } } playerRegisteredSignal.emit(bus_name); } void MprisController::handle_player_deregistered(const std::string &bus_name) { spdlog::info("MPRIS player deregistered: {}", bus_name); registeredPlayers.erase(bus_name); if (bus_name == m_player_bus_name) { m_proxy.reset(); } playerDeregisteredSignal.emit(bus_name); } void MprisController::signalNotification() { if (!m_proxy) { return; } Glib::VariantBase metadata_var; m_proxy->get_cached_property(metadata_var, "Metadata"); if (!metadata_var) { spdlog::info("No metadata available."); return; } if (!metadata_var.is_of_type(Glib::VariantType("a{sv}"))) { spdlog::error("Unexpected metadata type."); return; } using MetadataMap = std::map; MetadataMap metadata_map; Glib::Variant variant_dict = Glib::VariantBase::cast_dynamic>(metadata_var); metadata_map = variant_dict.get(); std::string title, artwork_url, track_id; std::vector artist; if (metadata_map.count("xesam:title")) { const auto &title_base = metadata_map["xesam:title"]; if (title_base.is_of_type(Glib::VariantType("s"))) { auto title_var = Glib::VariantBase::cast_dynamic>(title_base); title = title_var.get(); } } if (metadata_map.count("xesam:artist")) { const auto &artist_var = metadata_map["xesam:artist"]; if (artist_var.is_of_type(Glib::VariantType("as"))) { auto artists = Glib::VariantBase::cast_dynamic>>(artist_var).get(); if (!artists.empty()) { for (const auto &a : artists) { artist.push_back(a); } } } else if (artist_var.is_of_type(Glib::VariantType("s"))) { auto artist_str = Glib::VariantBase::cast_dynamic>(artist_var); artist.push_back(artist_str.get()); } } if (metadata_map.count("mpris:artUrl")) { const auto &art_base = metadata_map["mpris:artUrl"]; if (art_base.is_of_type(Glib::VariantType("s"))) { auto art_var = Glib::VariantBase::cast_dynamic>(art_base); artwork_url = art_var.get(); } } if (metadata_map.count("mpris:trackid")) { const auto &track_base = metadata_map["mpris:trackid"]; if (track_base.is_of_type(Glib::VariantType("o"))) { auto track_var = Glib::VariantBase::cast_dynamic>(track_base); track_id = track_var.get(); } else if (track_base.is_of_type(Glib::VariantType("s"))) { auto track_var = Glib::VariantBase::cast_dynamic>(track_base); track_id = track_var.get(); } } int64_t length_us = 0; if (metadata_map.count("mpris:length")) { const auto &length_base = metadata_map["mpris:length"]; if (length_base.is_of_type(Glib::VariantType("x"))) { auto length_var = Glib::VariantBase::cast_dynamic>(length_base); length_us = static_cast(length_var.get()); } else if (length_base.is_of_type(Glib::VariantType("t"))) { auto length_var = Glib::VariantBase::cast_dynamic>(length_base); length_us = static_cast(length_var.get()); } } MprisPlayer2Message mpris; mpris.title = StringHelper::trimToSize(title, 30); mpris.artist = artist; mpris.artwork_url = artwork_url; mpris.track_id = track_id; mpris.play_pause = [this]() { this->toggle_play(); }; mpris.next = [this]() { this->next_song(); }; mpris.previous = [this]() { this->previous_song(); }; mpris.length_ms = length_us / 1000; // Convert microseconds to milliseconds mprisUpdatedSignal.emit(mpris); } void MprisController::emit_cached_playback_status() { if (!m_proxy) { return; } Glib::VariantBase status_var; m_proxy->get_cached_property(status_var, "PlaybackStatus"); if (!status_var || !status_var.is_of_type(Glib::VariantType("s"))) { return; } auto status = Glib::VariantBase::cast_dynamic>(status_var).get(); auto parsedStatusIt = playbackStatusMap.find(static_cast(status)); if (parsedStatusIt != playbackStatusMap.end()) { currentPlaybackStatus = parsedStatusIt->second; playbackStatusChangedSignal.emit(currentPlaybackStatus); } else { spdlog::error("Unknown playback status: {}", status.raw()); } } void MprisController::emit_cached_position() { if (!m_proxy) { return; } Glib::VariantBase position_var; m_proxy->get_cached_property(position_var, "Position"); if (!position_var || !position_var.is_of_type(Glib::VariantType("x"))) { return; } auto position = Glib::VariantBase::cast_dynamic>(position_var).get(); playbackPositionChangedSignal.emit(static_cast(position)); } void MprisController::emit_cached_can_seek() { if (!m_proxy) { return; } Glib::VariantBase can_seek_var; m_proxy->get_cached_property(can_seek_var, "CanSeek"); if (!can_seek_var || !can_seek_var.is_of_type(Glib::VariantType("b"))) { return; } auto can_seek = Glib::VariantBase::cast_dynamic>(can_seek_var).get(); canSeekChangedSignal.emit(can_seek); } void MprisController::on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties &changed_properties, const std::vector &) { if (changed_properties.find("Metadata") != changed_properties.end()) { signalNotification(); } if (changed_properties.find("PlaybackStatus") != changed_properties.end()) { auto status_var = changed_properties.at("PlaybackStatus"); if (status_var.is_of_type(Glib::VariantType("s"))) { auto status = Glib::VariantBase::cast_dynamic>(status_var).get(); spdlog::info("Playback Status changed to: {}", status.raw()); auto parsedStatusIt = playbackStatusMap.find(static_cast(status)); if (parsedStatusIt != playbackStatusMap.end()) { currentPlaybackStatus = parsedStatusIt->second; playbackStatusChangedSignal.emit(currentPlaybackStatus); } else { spdlog::error("Unknown playback status: {}", status.raw()); } } } if (changed_properties.find("Position") != changed_properties.end()) { auto position_var = changed_properties.at("Position"); if (position_var.is_of_type(Glib::VariantType("x"))) { auto position = Glib::VariantBase::cast_dynamic>(position_var).get(); spdlog::info("Position changed to: {}", position); playbackPositionChangedSignal.emit(static_cast(position)); } } if (changed_properties.find("CanSeek") != changed_properties.end()) { auto can_seek_var = changed_properties.at("CanSeek"); if (can_seek_var.is_of_type(Glib::VariantType("b"))) { auto can_seek = Glib::VariantBase::cast_dynamic>(can_seek_var).get(); canSeekChangedSignal.emit(can_seek); } } } void MprisController::previous_song() { if (m_proxy) { m_proxy->call("Previous"); } } void MprisController::toggle_play() { if (m_proxy) { m_proxy->call("PlayPause"); } } void MprisController::pause() { if (m_proxy) { m_proxy->call("Pause"); } } void MprisController::next_song() { if (m_proxy) { m_proxy->call("Next"); } } void MprisController::emit_seeked(int64_t position_us) { spdlog::debug("Seeking to position: {}", position_us); if (!m_proxy) { return; } try { Glib::VariantContainerBase params = Glib::VariantContainerBase::create_tuple( Glib::Variant::create(position_us)); m_proxy->call("Seek", params); } catch (const Glib::Error &ex) { spdlog::error("Error seeking: {}", ex.what()); } } void MprisController::set_position(const std::string &track_id, int64_t position_us) { if (!m_proxy || track_id.empty()) { return; } try { Glib::VariantContainerBase params = Glib::VariantContainerBase::create_tuple({ Glib::Variant::create(track_id), Glib::Variant::create(position_us) }); m_proxy->call("SetPosition", params); } catch (const Glib::Error &ex) { spdlog::error("Error setting position: {}", ex.what()); } }