looper/dbus.cpp

621 lines
19 KiB
C++

#include "dbus.hpp"
#include "log.hpp"
#include "backend.hpp"
#include <random>
#include <fmt/core.h>
#include <fmt/format.h>
#ifdef DBUS_ENABLED
MprisAPI::MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api)
: AdaptorInterfaces(connection, OBJECT_PATH(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<std::string, sdbus::Variant> MprisAPI::Metadata() {
std::map<std::string, sdbus::Variant> output;
if (!dbus_api->IsStopped()) {
output["mpris:length"] = VARIANT((int64_t)(dbus_api->Length() * 1000000));
output["mpris:trackid"] = VARIANT(playing_track_id);
output["xesam:title"] = VARIANT(dbus_api->FileTitle());
output["xesam:url"] = VARIANT(dbus_api->FilePath());
} else {
output["mpris:trackid"] = 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<meta_t> MprisAPI::GetTracksMetadata(const std::vector<track_id_t> &TrackIds) {
std::vector<meta_t> 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<std::string, sdbus::Variant> meta;
meta["mpris:trackid"] = VARIANT(fmt::format("{}{}", streamPrefix, i));
meta["xesam:title"] = 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<track_id_t> MprisAPI::Tracks() {
std::vector<track_id_t> 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(OBJECT_PATH(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, OBJECT_PATH(objectPath))
, daemon(daemon)
, playback(playback)
, connection(connection)
{
registerAdaptor();
playback->register_handle(this);
auto mprisConnection = sdbus::createSessionBusConnection(SERVICE_NAME("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<std::string, sdbus::Variant> &platform_data) {
// TODO: Raise window?
}
void DBusAPI::Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant> &platform_data) {
if (uris.size() > 0) {
Start(uris[0], true);
}
}
void DBusAPI::ActivateAction(const std::string &action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant> &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<size_t>()(idx);
size_t rand = (size_t)rand_engine();
size_t rand_hash = std::hash<size_t>()(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<std::string> *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<std::string> DBusAPI::PeekAll(const std::string &handle) {
auto *errors_list = get_errors_by_handle(handle);
if (errors_list != nullptr) {
std::vector<std::string> output;
for (auto error : (*errors_list)) {
output.push_back(error);
}
return output;
}
return std::vector<std::string>();
}
std::vector<std::string> 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<sdbus::Struct<double, std::string, int32_t>> DBusAPI::GetStreams() {
std::vector<sdbus::Struct<double, std::string, int32_t>> output;
auto streams = playback->get_streams();
for (auto stream : streams) {
sdbus::Struct<double, std::string, int32_t> 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<std::string> DBusAPISender::get_current_file() {
if (IsStopped()) {
return {};
} else {
return FilePath();
}
}
std::optional<std::string> 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<PlaybackStream> DBusAPISender::get_streams() {
std::vector<PlaybackStream> 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, BUS_NAME(busName), OBJECT_PATH(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