#include "dbus.hpp" #include "log.hpp" #include "backend.hpp" #include #include #include #ifdef DBUS_ENABLED MprisAPI::MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api) : AdaptorInterfaces(connection, sdbus::ObjectPath{objectPath}) , dbus_api(dbus_api) , connection(connection) { registerAdaptor(); connection.enterEventLoopAsync(); } void MprisAPI::Quit() { dbus_api->Quit(); } void MprisAPI::Pause() { dbus_api->Paused(true); } void MprisAPI::PlayPause() { dbus_api->TogglePause(); } void MprisAPI::Stop() { dbus_api->Stop(); } void MprisAPI::Play() { dbus_api->Paused(false); } void MprisAPI::Seek(const int64_t &offset) { double value = offset; value /= 1000000; dbus_api->Position(value); } void MprisAPI::SetPosition(const sdbus::ObjectPath &TrackId, const int64_t &offset) { if (TrackId == playing_track_id) { Seek(offset); } } void MprisAPI::OpenUri(const std::string &Uri) { dbus_api->Start(Uri, true); } std::string MprisAPI::PlaybackStatus() { if (dbus_api->IsStopped()) { return "Stopped"; } else { return dbus_api->Paused() ? "Paused" : "Playing"; } } double MprisAPI::Rate() { return dbus_api->Speed() * dbus_api->Tempo(); } void MprisAPI::Rate(const double &value) { dbus_api->Speed(value / dbus_api->Tempo()); } std::map MprisAPI::Metadata() { std::map output; if (!dbus_api->IsStopped()) { output["mpris:length"] = sdbus::Variant{(int64_t)(dbus_api->Length() * 1000000)}; output["mpris:trackid"] = sdbus::Variant{playing_track_id}; output["xesam:title"] = sdbus::Variant{dbus_api->FileTitle()}; output["xesam:url"] = sdbus::Variant{dbus_api->FilePath()}; } else { output["mpris:trackid"] = sdbus::Variant{empty_track_id}; } return output; } double MprisAPI::Volume() { return dbus_api->Volume(); } void MprisAPI::Volume(const double &value) { dbus_api->Volume(value); } int64_t MprisAPI::Position() { return dbus_api->Position() * 1000000; } bool MprisAPI::CanPlay() { return !dbus_api->IsStopped(); } bool MprisAPI::CanPause() { return !dbus_api->IsStopped(); } bool MprisAPI::CanSeek() { return !dbus_api->IsStopped() && dbus_api->Length() > 0; } std::vector MprisAPI::GetTracksMetadata(const std::vector &TrackIds) { std::vector output; if (!dbus_api->IsStopped()) { output.push_back(Metadata()); auto streams = dbus_api->playback->get_streams(); int i = 0; for (auto stream : streams) { std::map meta; meta["mpris:trackid"] = sdbus::Variant{fmt::format("{}{}", streamPrefix, i)}; meta["xesam:title"] = sdbus::Variant{stream.name}; i++; } } return output; } void MprisAPI::AddTrack(const std::string &Uri, const track_id_t &AfterTrack, const bool &SetAsCurrent) { return; } void MprisAPI::RemoveTrack(const track_id_t &TrackId) { return; } void MprisAPI::GoTo(const track_id_t &TrackId) { if (TrackId.starts_with(streamPrefix)) { std::string stream = TrackId.substr(streamPrefix.length()); int streamIdx = std::stoi(stream); dbus_api->PlayStream((uint32_t)streamIdx); } return; } std::vector MprisAPI::Tracks() { std::vector output; if (dbus_api->IsStopped()) { return output; } output.push_back(playing_track_id); auto streams = dbus_api->playback->get_streams(); int i = 0; for (auto stream : streams) { output.push_back(sdbus::ObjectPath{fmt::format("{}{}", streamPrefix, i)}); i++; } return output; } bool MprisAPI::CanEditTracks() { return false; } MprisAPI::~MprisAPI() { connection.leaveEventLoop(); unregisterAdaptor(); } #endif #ifndef DBUS_ENABLED DBusAPI::DBusAPI(Playback *playback, bool daemon) : playback(playback) { } #else DBusAPI::DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon) : AdaptorInterfaces(connection, sdbus::ObjectPath{objectPath}) , daemon(daemon) , playback(playback) , connection(connection) { registerAdaptor(); playback->register_handle(this); auto mprisConnection = sdbus::createSessionBusConnection(sdbus::ServiceName{"org.mpris.MediaPlayer2.Looper"}); auto &mprisConRef = *mprisConnection.release(); mpris = new MprisAPI(mprisConRef, "/org/mpris/MediaPlayer2", this); threadFunc = std::thread([this]() { while (!threadExitFlag.load()) { Update(); std::this_thread::yield(); } }); connection.enterEventLoopAsync(); } #endif const char *DBusAPI::objectPath = "/com/complecwaft/looper"; const char *DBusAPI::busName = "com.complecwaft.looper"; DBusAPI *DBusAPI::Create(Playback *playback, bool daemon) { #ifdef DBUS_ENABLED auto connection = sdbus::createSessionBusConnection(sdbus::ServiceName{busName}); auto &con_ref = *connection.release(); return new DBusAPI(playback, con_ref, objectPath, daemon); #else return new DBusAPI(playback, daemon); #endif } #ifdef DBUS_ENABLED double DBusAPI::Position() { return playback->GetPosition(); } void DBusAPI::Position(const double &value) { playback->Seek(value); } double DBusAPI::Length() { return playback->GetLength(); } std::string DBusAPI::FilePath() { return playback->get_current_file().value_or(""); } std::string DBusAPI::FileTitle() { return playback->get_current_title().value_or(""); } void DBusAPI::Update() { uint16_t signals = playback->handle_signals(PlaybackSignalErrorOccurred|PlaybackSignalFileChanged|PlaybackSignalPaused|PlaybackSignalPitchChanged|PlaybackSignalResumed|PlaybackSignalSeeked|PlaybackSignalSpeedChanged|PlaybackSignalStarted|PlaybackSignalStopped|PlaybackSignalTempoChanged, this); if (signals & PlaybackSignalErrorOccurred) { auto error = playback->GetError(this).value_or("Unknown error."); emitErrorOccurred(error, error); mpris->sendPropertiesChanged(mpris->playerInterface, {"PlaybackStatus", "CanSeek", "CanPlay", "CanPause", "Metadata"}); } if (signals & PlaybackSignalFileChanged) { emitFileChanged(playback->get_current_file().value_or(""), playback->get_current_title().value_or("")); mpris->emitTrackMetadataChanged(mpris->playing_track_id, mpris->Metadata()); mpris->sendPropertiesChanged(mpris->playerInterface, {"Position", "Metadata"}); } if (signals & (PlaybackSignalPaused|PlaybackSignalResumed)) { emitPauseChanged(playback->IsPaused()); mpris->sendPropertiesChanged(mpris->playerInterface, {"PlaybackStatus"}); } if (signals & PlaybackSignalStopped) { emitStopped(); emitPauseChanged(playback->IsPaused()); mpris->emitTrackRemoved(mpris->playing_track_id); mpris->sendPropertiesChanged(mpris->playerInterface, {"PlaybackStatus", "CanSeek", "CanPlay", "CanPause", "Metadata"}); } if (signals & PlaybackSignalPitchChanged) { emitPitchChanged(playback->GetPitch()); } if (signals & PlaybackSignalTempoChanged) { emitTempoChanged(playback->GetTempo()); mpris->sendPropertiesChanged(mpris->playerInterface, {"Rate"}); } if (signals & PlaybackSignalSpeedChanged) { emitSpeedChanged(playback->GetSpeed()); mpris->sendPropertiesChanged(mpris->playerInterface, {"Rate"}); } if (signals & PlaybackSignalSeeked) { emitSeeked(playback->GetPosition()); mpris->emitSeeked(playback->GetPosition() * 1000000); mpris->sendPropertiesChanged(mpris->playerInterface, {"Position"}); } if (signals & PlaybackSignalStarted) { emitPlaybackEngineStarted(); mpris->emitTrackAdded(mpris->Metadata(), mpris->empty_track_id); mpris->sendPropertiesChanged(mpris->playerInterface, {"CanSeek", "CanPlay", "CanPause", "Position", "Metadata", "PlaybackStatus"}); } } void DBusAPI::Activate(const std::map &platform_data) { // TODO: Raise window? } void DBusAPI::Open(const std::vector& uris, const std::map &platform_data) { if (uris.size() > 0) { Start(uris[0], true); } } void DBusAPI::ActivateAction(const std::string &action_name, const std::vector& parameter, const std::map &platform_data) { // Nothing yet. } std::string uriToPath(const std::string &uri) { const static std::string file_prefix = "file:"; std::string tmp = uri; if (tmp.starts_with(file_prefix)) { tmp = tmp.substr(file_prefix.length()); return tmp; } return uri; } void DBusAPI::Start(const std::string &path, const bool &isUri) { StartWithStreamIndex(path, isUri, 0); } void DBusAPI::StartWithStreamIndex(const std::string &path, const bool &isUri, const uint32_t &streamIndex) { if (isUri) { StartWithStreamIndex(uriToPath(path), false, streamIndex); } else { playback->Start(path, (int)streamIndex); } } void DBusAPI::Load(const std::string &path, const bool &isUri) { if (isUri) { Load(uriToPath(path), false); } else { playback->Load(path); } } void DBusAPI::Stop() { playback->Stop(); } void DBusAPI::TogglePause() { playback->Pause(); } double DBusAPI::Speed() { return playback->GetSpeed(); } void DBusAPI::Speed(const double &value) { playback->SetSpeed(value); } double DBusAPI::Tempo() { return playback->GetTempo(); } void DBusAPI::Tempo(const double &value) { playback->SetTempo(value); } double DBusAPI::Pitch() { return playback->GetPitch(); } void DBusAPI::Pitch(const double &value) { playback->SetPitch(value); } double DBusAPI::Volume() { return playback->GetVolume(); } void DBusAPI::Volume(const double &value) { playback->SetVolume(value); } bool DBusAPI::Paused() { return playback->IsPaused(); } void DBusAPI::Paused(const bool &value) { if (value != playback->IsPaused()) { playback->Pause(); } } bool DBusAPI::IsStopped() { return playback->IsStopped(); } bool DBusAPI::IsDaemon() { return daemon; } std::string DBusAPI::CreateHandle() { size_t idx = handle_idx++; size_t hash = std::hash()(idx); size_t rand = (size_t)rand_engine(); size_t rand_hash = std::hash()(rand); std::string value = fmt::format("{}:{}:{}", idx, hash, rand_hash); handles[value] = (void*)&value; playback->register_handle(handles[value]); return value; } void DBusAPI::ClearHandle(const std::string &handle) { if (handles.contains(handle)) { playback->unregister_handle(handles[handle]); handles.erase(handle); } } std::deque *DBusAPI::get_errors_by_handle(const std::string &handle) { if (handles.contains(handle)) { auto *v_handle = handles[handle]; if (playback->errors_occurred.contains(v_handle)) { return &playback->errors_occurred[v_handle]; } } return nullptr; } std::string DBusAPI::PopFront(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { if (errors_list->empty()) { return ""; } std::string output = errors_list->front(); errors_list->pop_front(); return output; } return ""; } std::string DBusAPI::PopBack(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { if (errors_list->empty()) { return ""; } std::string output = errors_list->back(); errors_list->pop_back(); return output; } return ""; } std::string DBusAPI::PeekFront(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { if (errors_list->empty()) { return ""; } std::string output = errors_list->front(); return output; } return ""; } void DBusAPI::Quit() { UIBackend::running_ui_backend->QuitHandler(); } std::string DBusAPI::PeekBack(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { if (errors_list->empty()) { return ""; } std::string output = errors_list->back(); return output; } return ""; } uint32_t DBusAPI::GetCount(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { return errors_list->size(); } return 0; } bool DBusAPI::IsEmpty(const std::string& handle) { return GetCount(handle) == 0; } void DBusAPI::Clear(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { errors_list->clear(); } } std::vector DBusAPI::PeekAll(const std::string &handle) { auto *errors_list = get_errors_by_handle(handle); if (errors_list != nullptr) { std::vector output; for (auto error : (*errors_list)) { output.push_back(error); } return output; } return std::vector(); } std::vector DBusAPI::GetAllAndClear(const std::string &handle) { auto output = PeekAll(handle); Clear(handle); return output; } uint32_t DBusAPI::StreamIdx() { return playback->get_current_stream(); } std::vector> DBusAPI::GetStreams() { std::vector> output; auto streams = playback->get_streams(); for (auto stream : streams) { sdbus::Struct tmp(stream.length, stream.name, stream.id); output.push_back(tmp); } return output; } void DBusAPI::PlayStream(const uint32_t &idx) { playback->play_stream((int)idx); } #endif DBusAPI::~DBusAPI() { #ifdef DBUS_ENABLED threadExitFlag.store(true); threadFunc.detach(); connection.leaveEventLoop(); unregisterAdaptor(); #endif } bool DBusAPISender::isOnlyInstance() { #ifdef DBUS_ENABLED bool output; try { auto *tmp = Create(); output = tmp == nullptr; delete tmp; return output; } catch (sdbus::Error) { return true; } #else return true; #endif } #ifdef DBUS_ENABLED std::optional DBusAPISender::get_current_file() { if (IsStopped()) { return {}; } else { return FilePath(); } } std::optional DBusAPISender::get_current_title() { if (IsStopped()) { return {}; } else { return FileTitle(); } } void DBusAPISender::onPlaybackEngineStarted() { set_signal(PlaybackSignalStarted); } void DBusAPISender::onFileChanged(const std::string &FilePath, const std::string &Title) { set_signal(PlaybackSignalFileChanged); } void DBusAPISender::onSpeedChanged(const double &new_speed) { set_signal(PlaybackSignalSpeedChanged); } void DBusAPISender::onTempoChanged(const double &new_tempo) { set_signal(PlaybackSignalTempoChanged); } void DBusAPISender::onPitchChanged(const double &new_pitch) { set_signal(PlaybackSignalPitchChanged); } void DBusAPISender::onPauseChanged(const bool &now_paused) { set_signal(now_paused ? PlaybackSignalPaused : PlaybackSignalResumed); } void DBusAPISender::onStopped() { set_signal(PlaybackSignalStopped); } void DBusAPISender::onErrorOccurred(const std::string &error_desc, const std::string &error_type) { set_signal(PlaybackSignalErrorOccurred); } void DBusAPISender::onSeeked(const double &to_position) { set_signal(PlaybackSignalSeeked); } double DBusAPISender::GetPosition() { return ProxyInterfaces::Position(); } double DBusAPISender::GetLength() { return ProxyInterfaces::Length(); } void DBusAPISender::Seek(double position) { ProxyInterfaces::Position(position); } void DBusAPISender::Start(std::string filePath, int streamIdx) { ProxyInterfaces::Start(filePath, streamIdx); } void DBusAPISender::Load(std::string filePath) { ProxyInterfaces::Load(filePath, false); } int DBusAPISender::get_current_stream() { return (int)ProxyInterfaces::StreamIdx(); } std::vector DBusAPISender::get_streams() { std::vector output; auto input = ProxyInterfaces::GetStreams(); for (auto stream : input) { PlaybackStream tmp; tmp.length = stream.get<0>(); tmp.name = stream.get<1>(); tmp.id = stream.get<2>(); output.push_back(tmp); } return output; } bool DBusAPISender::IsPaused() { return ProxyInterfaces::Paused(); } void DBusAPISender::Pause() { ProxyInterfaces::TogglePause(); } void DBusAPISender::Stop() { ProxyInterfaces::Stop(); } bool DBusAPISender::IsStopped() { return ProxyInterfaces::IsStopped(); } void DBusAPISender::SetTempo(float tempo) { ProxyInterfaces::Tempo(tempo); } void DBusAPISender::SetPitch(float pitch) { ProxyInterfaces::Pitch(pitch); } void DBusAPISender::SetSpeed(float speed) { ProxyInterfaces::Speed(speed); } void DBusAPISender::SetVolume(float volume) { ProxyInterfaces::Volume(volume); } float DBusAPISender::GetTempo() { return (float)ProxyInterfaces::Tempo(); } float DBusAPISender::GetPitch() { return (float)ProxyInterfaces::Pitch(); } float DBusAPISender::GetSpeed() { return (float)ProxyInterfaces::Speed(); } float DBusAPISender::GetVolume() { return (float)ProxyInterfaces::Volume(); } void DBusAPISender::Update() { } #endif DBusAPISender *DBusAPISender::Create() { #ifdef DBUS_ENABLED try { auto connection = sdbus::createSessionBusConnection(); auto &con_ref = *connection.release(); auto *output = new DBusAPISender(con_ref, DBusAPI::busName, DBusAPI::objectPath); return output; } catch (sdbus::Error error) { DEBUG.writefln("sdbus::Error: %s: %s", error.getName().c_str(), error.getMessage().c_str()); return nullptr; } #else return nullptr; #endif } #ifdef DBUS_ENABLED DBusAPISender::DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath) : ProxyInterfaces(connection, sdbus::BusName{busName}, sdbus::ObjectPath{objectPath}) { registerProxy(); DEBUG.writeln("Pinging DBus API to check for its existance."); Ping(); DEBUG.writeln("Requesting handle..."); handle = CreateHandle(); DEBUG.writeln("Preparing updates for UI frontend..."); if (IsStopped()) { set_signal(PlaybackSignalStopped); } else { filePath = ProxyInterfaces::FilePath(); title = ProxyInterfaces::FileTitle(); length = ProxyInterfaces::Length(); set_signal(PlaybackSignalStarted|PlaybackSignalSeeked|PlaybackSignalFileChanged); } if (IsPaused()) { set_signal(PlaybackSignalPaused); } else { set_signal(PlaybackSignalResumed); } set_signal(PlaybackSignalSpeedChanged|PlaybackSignalPitchChanged|PlaybackSignalTempoChanged); connection.enterEventLoopAsync(); } DBusAPISender::~DBusAPISender() { DEBUG.writeln("Requesting handle deletion..."); ClearHandle(handle); unregisterProxy(); } #endif