Update DBus API

This commit is contained in:
Zachary Hall 2024-04-09 10:15:05 -07:00
parent 57cb111358
commit b84fec3175
27 changed files with 2502 additions and 329 deletions

View file

@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(SDL_MIXER_X_STATIC ON CACHE BOOL "")
set(SDL_MIXER_X_SHARED OFF CACHE BOOL "")
set(DOWNLOAD_AUDIO_CODECS_DEPENDENCY ON CACHE BOOL "")
set(DOWNLOAD_AUDIO_CODECS_DEPENDENCY OFF CACHE BOOL "")
set(AUDIO_CODECS_BUILD_LOCAL_SDL2 OFF CACHE BOOL "" FORCE)
set(MIXERX_LGPL ON CACHE BOOL "" FORCE)
set(USE_MIDI ON CACHE BOOL "" FORCE)
@ -30,6 +30,7 @@ find_package(PkgConfig)
add_subdirectory(subprojects/jsoncpp)
find_package(SDL2 REQUIRED)
find_package(sdbus-c++ REQUIRED)
include(GNUInstallDirs)
add_subdirectory(subprojects/SDL-Mixer-X)
add_subdirectory(subprojects/vgmstream)
@ -136,18 +137,18 @@ if (NOT DISABLE_GTK_UI)
list(APPEND ENABLED_UIS "gtk")
endif()
execute_process(COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/gen_ui_backend_inc.py ${ENABLED_UIS})
prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ backend_glue.cpp main.cpp)
prefix_all(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ backend_glue.cpp main.cpp daemon_backend.cpp proxy_backend.cpp)
add_executable(looper ${SOURCES})
add_dependencies(looper looper_assets ${UI_BACKENDS})
find_program(ASCLI_EXE NAMES "appstreamcli" NO_CACHE)
if(${ASCLI_EXE} STREQUAL "ASCLIEXE-NOTFOUND")
message("Cannot verify Appstream Metadata.")
else()
add_test(NAME "verify appstream metadata" COMMAND ${ASCLI_EXE} validate --no-net --pedantic "assets/com.experimentalcraft.Looper.metainfo.xml")
add_test(NAME "verify appstream metadata" COMMAND ${ASCLI_EXE} validate --no-net --pedantic "assets/com.complecwaft.Looper.metainfo.xml")
endif()
target_link_libraries(looper PRIVATE liblooper libvgmstream jsoncpp ${UI_BACKENDS})
target_link_libraries(looper PUBLIC liblooper libvgmstream jsoncpp ${UI_BACKENDS})
install(TARGETS looper ${EXTRA_LIBS})
install(FILES assets/icon.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps/)
install(FILES assets/looper.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES assets/com.experimentalcraft.Looper.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(FILES assets/com.complecwaft.Looper.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES assets/com.complecwaft.Looper.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo)
install(DIRECTORY assets/translations/ TYPE LOCALE PATTERN "*" EXCLUDE PATTERN "looper.pot")

106
assets/app.dbus.xml Normal file
View file

@ -0,0 +1,106 @@
<node name="/com/complecwaft/Looper">
<interface name='org.freedesktop.Application'>
<method name='Activate'>
<arg type='a{sv}' name='platform_data' direction='in'/>
</method>
<method name='Open'>
<arg type='as' name='uris' direction='in'/>
<arg type='a{sv}' name='platform_data' direction='in'/>
</method>
<method name='ActivateAction'>
<arg type='s' name='action_name' direction='in'/>
<arg type='av' name='parameter' direction='in'/>
<arg type='a{sv}' name='platform_data' direction='in'/>
</method>
</interface>
<interface name="com.complecwaft.Looper">
<method name="CreateHandle">
<arg type='s' name='new_handle' direction='out' />
</method>
<method name="ClearHandle">
<arg type='s' name='handle' direction='in' />
</method>
<method name="Start">
<arg type='s' name='path' direction='in' />
</method>
<method name="StartByURI">
<arg type='s' name='uri' direction='in' />
</method>
<method name="Quit" />
<method name="Stop" />
<method name="TogglePause" />
<signal name="PlaybackEngineStarted" />
<signal name="SpeedChanged">
<arg name="new_speed" type="d" />
</signal>
<signal name="TempoChanged">
<arg name="new_tempo" type="d" />
</signal>
<signal name="PitchChanged">
<arg name="new_pitch" type="d" />
</signal>
<signal name="PauseChanged">
<arg name="now_paused" type="b" />
</signal>
<signal name="Stopped" />
<signal name="ErrorOccurred">
<arg name="error_desc" type="s" />
<arg name="error_type" type="s" />
</signal>
<signal name="Seeked">
<arg name="to_position" type="d" />
</signal>
<signal name="FileChanged">
<arg name="path" type="s" />
<arg name="title" type="s" />
</signal>
<property name="FilePath" type="s" access="read" />
<property name="FileTitle" type="s" access="read" />
<property name="Position" type="d" access="readwrite" />
<property name="Length" type="d" access="read" />
<property name="Speed" type="d" access="readwrite" />
<property name="Tempo" type="d" access="readwrite" />
<property name="Pitch" type="d" access="readwrite" />
<property name="Volume" type="d" access="readwrite" />
<property name="Paused" type="b" access="readwrite" />
<property name="IsStopped" type="b" access="read" />
<property name="IsDaemon" type="b" access="read" />
</interface>
<interface name="com.complecwaft.Looper.Errors" >
<method name="PopFront">
<arg name="handle" direction="in" type="s" />
<arg name="error" direction="out" type="s" />
</method>
<method name="PopBack">
<arg name="handle" direction="in" type="s" />
<arg name="error" direction="out" type="s" />
</method>
<method name="PeekFront">
<arg name="handle" direction="in" type="s" />
<arg name="error" direction="out" type="s" />
</method>
<method name="PeekBack">
<arg name="handle" direction="in" type="s" />
<arg name="error" direction="out" type="s" />
</method>
<method name="GetCount">
<arg name="handle" direction="in" type="s" />
<arg name="count" direction="out" type="u" />
</method>
<method name="IsEmpty">
<arg name="handle" direction="in" type="s" />
<arg name="empty" direction="out" type="b" />
</method>
<method name="Clear">
<arg name="handle" direction="in" type="s" />
</method>
<method name="PeekAll">
<arg name="handle" direction="in" type="s" />
<arg name="errors" direction="out" type="as" />
</method>
<method name="GetAllAndClear">
<arg name="handle" direction="in" type="s" />
<arg name="errors" direction="out" type="as" />
</method>
</interface>
</node>

View file

@ -0,0 +1,28 @@
[Desktop Entry]
Version=1.5
Type=Application
Name=Looper
Comment=An audio player that can properly loop audio files
GenericName=Looping audio player
Exec=looper -n %f
Icon=looper
StartupWMClass=looper;com.complecwaft.Looper;com.complecwaft.Looper.GTK
MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;
Categories=Audio;AudioVideo;
Terminal=false
SingleMainWindow=true
StartupNotify=false
Actions=Open;Daemon;Quit;
Hidden=false
PrefersNonDefaultGPU=false
[Desktop Action Open]
Name=Open Window
Icon=window-new
Exec=looper
[Desktop Action Quit]
Name=Quit Looper
Icon=application-exit
Exec=looper -q
[Desktop Action Daemon]
Name=Start Daemon
Exec=looper -d -n

View file

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>com.experimentalcraft.NekoPlayer</id>
<id>com.complecwaft.Looper</id>
<developer_name>Catmeow72</developer_name>
<name>Neko Player</name>
<name>Looper</name>
<summary>An audio player that can play back files with seamless loops using their metadata.</summary>
<metadata_license>MIT</metadata_license>
<project_license>MIT AND LGPL-2.1-only AND Zlib</project_license>
<project_license>MIT OR GPL-3.0-or-later</project_license>
<content_rating type="oars-1.1" />
<description>

View file

@ -0,0 +1,224 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_stub_adaptor_hpp__adaptor__H__
#define __sdbuscpp__dbus_stub_adaptor_hpp__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
class Application_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.Application";
protected:
Application_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("Activate").onInterface(INTERFACE_NAME).withInputParamNames("platform_data").implementedAs([this](const std::map<std::string, sdbus::Variant>& platform_data){ return this->Activate(platform_data); });
object_->registerMethod("Open").onInterface(INTERFACE_NAME).withInputParamNames("uris", "platform_data").implementedAs([this](const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data){ return this->Open(uris, platform_data); });
object_->registerMethod("ActivateAction").onInterface(INTERFACE_NAME).withInputParamNames("action_name", "parameter", "platform_data").implementedAs([this](const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data){ return this->ActivateAction(action_name, parameter, platform_data); });
}
Application_adaptor(const Application_adaptor&) = delete;
Application_adaptor& operator=(const Application_adaptor&) = delete;
Application_adaptor(Application_adaptor&&) = default;
Application_adaptor& operator=(Application_adaptor&&) = default;
~Application_adaptor() = default;
private:
virtual void Activate(const std::map<std::string, sdbus::Variant>& platform_data) = 0;
virtual void Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data) = 0;
virtual void ActivateAction(const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data) = 0;
private:
sdbus::IObject* object_;
};
}} // namespaces
namespace com {
namespace complecwaft {
class Looper_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper";
protected:
Looper_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("CreateHandle").onInterface(INTERFACE_NAME).withOutputParamNames("new_handle").implementedAs([this](){ return this->CreateHandle(); });
object_->registerMethod("ClearHandle").onInterface(INTERFACE_NAME).withInputParamNames("handle").implementedAs([this](const std::string& handle){ return this->ClearHandle(handle); });
object_->registerMethod("Start").onInterface(INTERFACE_NAME).withInputParamNames("path").implementedAs([this](const std::string& path){ return this->Start(path); });
object_->registerMethod("StartByURI").onInterface(INTERFACE_NAME).withInputParamNames("uri").implementedAs([this](const std::string& uri){ return this->StartByURI(uri); });
object_->registerMethod("Quit").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Quit(); });
object_->registerMethod("Stop").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Stop(); });
object_->registerMethod("TogglePause").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->TogglePause(); });
object_->registerSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME);
object_->registerSignal("SpeedChanged").onInterface(INTERFACE_NAME).withParameters<double>("new_speed");
object_->registerSignal("TempoChanged").onInterface(INTERFACE_NAME).withParameters<double>("new_tempo");
object_->registerSignal("PitchChanged").onInterface(INTERFACE_NAME).withParameters<double>("new_pitch");
object_->registerSignal("PauseChanged").onInterface(INTERFACE_NAME).withParameters<bool>("now_paused");
object_->registerSignal("Stopped").onInterface(INTERFACE_NAME);
object_->registerSignal("ErrorOccurred").onInterface(INTERFACE_NAME).withParameters<std::string, std::string>("error_desc", "error_type");
object_->registerSignal("Seeked").onInterface(INTERFACE_NAME).withParameters<double>("to_position");
object_->registerSignal("FileChanged").onInterface(INTERFACE_NAME).withParameters<std::string, std::string>("path", "title");
object_->registerProperty("FilePath").onInterface(INTERFACE_NAME).withGetter([this](){ return this->FilePath(); });
object_->registerProperty("FileTitle").onInterface(INTERFACE_NAME).withGetter([this](){ return this->FileTitle(); });
object_->registerProperty("Position").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Position(); }).withSetter([this](const double& value){ this->Position(value); });
object_->registerProperty("Length").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Length(); });
object_->registerProperty("Speed").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Speed(); }).withSetter([this](const double& value){ this->Speed(value); });
object_->registerProperty("Tempo").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Tempo(); }).withSetter([this](const double& value){ this->Tempo(value); });
object_->registerProperty("Pitch").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Pitch(); }).withSetter([this](const double& value){ this->Pitch(value); });
object_->registerProperty("Volume").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Volume(); }).withSetter([this](const double& value){ this->Volume(value); });
object_->registerProperty("Paused").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Paused(); }).withSetter([this](const bool& value){ this->Paused(value); });
object_->registerProperty("IsStopped").onInterface(INTERFACE_NAME).withGetter([this](){ return this->IsStopped(); });
object_->registerProperty("IsDaemon").onInterface(INTERFACE_NAME).withGetter([this](){ return this->IsDaemon(); });
}
Looper_adaptor(const Looper_adaptor&) = delete;
Looper_adaptor& operator=(const Looper_adaptor&) = delete;
Looper_adaptor(Looper_adaptor&&) = default;
Looper_adaptor& operator=(Looper_adaptor&&) = default;
~Looper_adaptor() = default;
public:
void emitPlaybackEngineStarted()
{
object_->emitSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME);
}
void emitSpeedChanged(const double& new_speed)
{
object_->emitSignal("SpeedChanged").onInterface(INTERFACE_NAME).withArguments(new_speed);
}
void emitTempoChanged(const double& new_tempo)
{
object_->emitSignal("TempoChanged").onInterface(INTERFACE_NAME).withArguments(new_tempo);
}
void emitPitchChanged(const double& new_pitch)
{
object_->emitSignal("PitchChanged").onInterface(INTERFACE_NAME).withArguments(new_pitch);
}
void emitPauseChanged(const bool& now_paused)
{
object_->emitSignal("PauseChanged").onInterface(INTERFACE_NAME).withArguments(now_paused);
}
void emitStopped()
{
object_->emitSignal("Stopped").onInterface(INTERFACE_NAME);
}
void emitErrorOccurred(const std::string& error_desc, const std::string& error_type)
{
object_->emitSignal("ErrorOccurred").onInterface(INTERFACE_NAME).withArguments(error_desc, error_type);
}
void emitSeeked(const double& to_position)
{
object_->emitSignal("Seeked").onInterface(INTERFACE_NAME).withArguments(to_position);
}
void emitFileChanged(const std::string& path, const std::string& title)
{
object_->emitSignal("FileChanged").onInterface(INTERFACE_NAME).withArguments(path, title);
}
private:
virtual std::string CreateHandle() = 0;
virtual void ClearHandle(const std::string& handle) = 0;
virtual void Start(const std::string& path) = 0;
virtual void StartByURI(const std::string& uri) = 0;
virtual void Quit() = 0;
virtual void Stop() = 0;
virtual void TogglePause() = 0;
private:
virtual std::string FilePath() = 0;
virtual std::string FileTitle() = 0;
virtual double Position() = 0;
virtual void Position(const double& value) = 0;
virtual double Length() = 0;
virtual double Speed() = 0;
virtual void Speed(const double& value) = 0;
virtual double Tempo() = 0;
virtual void Tempo(const double& value) = 0;
virtual double Pitch() = 0;
virtual void Pitch(const double& value) = 0;
virtual double Volume() = 0;
virtual void Volume(const double& value) = 0;
virtual bool Paused() = 0;
virtual void Paused(const bool& value) = 0;
virtual bool IsStopped() = 0;
virtual bool IsDaemon() = 0;
private:
sdbus::IObject* object_;
};
}} // namespaces
namespace com {
namespace complecwaft {
namespace Looper {
class Errors_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper.Errors";
protected:
Errors_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("PopFront").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PopFront(handle); });
object_->registerMethod("PopBack").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PopBack(handle); });
object_->registerMethod("PeekFront").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PeekFront(handle); });
object_->registerMethod("PeekBack").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("error").implementedAs([this](const std::string& handle){ return this->PeekBack(handle); });
object_->registerMethod("GetCount").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("count").implementedAs([this](const std::string& handle){ return this->GetCount(handle); });
object_->registerMethod("IsEmpty").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("empty").implementedAs([this](const std::string& handle){ return this->IsEmpty(handle); });
object_->registerMethod("Clear").onInterface(INTERFACE_NAME).withInputParamNames("handle").implementedAs([this](const std::string& handle){ return this->Clear(handle); });
object_->registerMethod("PeekAll").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("errors").implementedAs([this](const std::string& handle){ return this->PeekAll(handle); });
object_->registerMethod("GetAllAndClear").onInterface(INTERFACE_NAME).withInputParamNames("handle").withOutputParamNames("errors").implementedAs([this](const std::string& handle){ return this->GetAllAndClear(handle); });
}
Errors_adaptor(const Errors_adaptor&) = delete;
Errors_adaptor& operator=(const Errors_adaptor&) = delete;
Errors_adaptor(Errors_adaptor&&) = default;
Errors_adaptor& operator=(Errors_adaptor&&) = default;
~Errors_adaptor() = default;
private:
virtual std::string PopFront(const std::string& handle) = 0;
virtual std::string PopBack(const std::string& handle) = 0;
virtual std::string PeekFront(const std::string& handle) = 0;
virtual std::string PeekBack(const std::string& handle) = 0;
virtual uint32_t GetCount(const std::string& handle) = 0;
virtual bool IsEmpty(const std::string& handle) = 0;
virtual void Clear(const std::string& handle) = 0;
virtual std::vector<std::string> PeekAll(const std::string& handle) = 0;
virtual std::vector<std::string> GetAllAndClear(const std::string& handle) = 0;
private:
sdbus::IObject* object_;
};
}}} // namespaces
#endif

316
assets/dbus_stub_proxy.hpp Normal file
View file

@ -0,0 +1,316 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__dbus_stub_proxy_hpp__proxy__H__
#define __sdbuscpp__dbus_stub_proxy_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace freedesktop {
class Application_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.freedesktop.Application";
protected:
Application_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
Application_proxy(const Application_proxy&) = delete;
Application_proxy& operator=(const Application_proxy&) = delete;
Application_proxy(Application_proxy&&) = default;
Application_proxy& operator=(Application_proxy&&) = default;
~Application_proxy() = default;
public:
void Activate(const std::map<std::string, sdbus::Variant>& platform_data)
{
proxy_->callMethod("Activate").onInterface(INTERFACE_NAME).withArguments(platform_data);
}
void Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data)
{
proxy_->callMethod("Open").onInterface(INTERFACE_NAME).withArguments(uris, platform_data);
}
void ActivateAction(const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data)
{
proxy_->callMethod("ActivateAction").onInterface(INTERFACE_NAME).withArguments(action_name, parameter, platform_data);
}
private:
sdbus::IProxy* proxy_;
};
}} // namespaces
namespace com {
namespace complecwaft {
class Looper_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper";
protected:
Looper_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
proxy_->uponSignal("PlaybackEngineStarted").onInterface(INTERFACE_NAME).call([this](){ this->onPlaybackEngineStarted(); });
proxy_->uponSignal("SpeedChanged").onInterface(INTERFACE_NAME).call([this](const double& new_speed){ this->onSpeedChanged(new_speed); });
proxy_->uponSignal("TempoChanged").onInterface(INTERFACE_NAME).call([this](const double& new_tempo){ this->onTempoChanged(new_tempo); });
proxy_->uponSignal("PitchChanged").onInterface(INTERFACE_NAME).call([this](const double& new_pitch){ this->onPitchChanged(new_pitch); });
proxy_->uponSignal("PauseChanged").onInterface(INTERFACE_NAME).call([this](const bool& now_paused){ this->onPauseChanged(now_paused); });
proxy_->uponSignal("Stopped").onInterface(INTERFACE_NAME).call([this](){ this->onStopped(); });
proxy_->uponSignal("ErrorOccurred").onInterface(INTERFACE_NAME).call([this](const std::string& error_desc, const std::string& error_type){ this->onErrorOccurred(error_desc, error_type); });
proxy_->uponSignal("Seeked").onInterface(INTERFACE_NAME).call([this](const double& to_position){ this->onSeeked(to_position); });
proxy_->uponSignal("FileChanged").onInterface(INTERFACE_NAME).call([this](const std::string& path, const std::string& title){ this->onFileChanged(path, title); });
}
Looper_proxy(const Looper_proxy&) = delete;
Looper_proxy& operator=(const Looper_proxy&) = delete;
Looper_proxy(Looper_proxy&&) = default;
Looper_proxy& operator=(Looper_proxy&&) = default;
~Looper_proxy() = default;
virtual void onPlaybackEngineStarted() = 0;
virtual void onSpeedChanged(const double& new_speed) = 0;
virtual void onTempoChanged(const double& new_tempo) = 0;
virtual void onPitchChanged(const double& new_pitch) = 0;
virtual void onPauseChanged(const bool& now_paused) = 0;
virtual void onStopped() = 0;
virtual void onErrorOccurred(const std::string& error_desc, const std::string& error_type) = 0;
virtual void onSeeked(const double& to_position) = 0;
virtual void onFileChanged(const std::string& path, const std::string& title) = 0;
public:
std::string CreateHandle()
{
std::string result;
proxy_->callMethod("CreateHandle").onInterface(INTERFACE_NAME).storeResultsTo(result);
return result;
}
void ClearHandle(const std::string& handle)
{
proxy_->callMethod("ClearHandle").onInterface(INTERFACE_NAME).withArguments(handle);
}
void Start(const std::string& path)
{
proxy_->callMethod("Start").onInterface(INTERFACE_NAME).withArguments(path);
}
void StartByURI(const std::string& uri)
{
proxy_->callMethod("StartByURI").onInterface(INTERFACE_NAME).withArguments(uri);
}
void Quit()
{
proxy_->callMethod("Quit").onInterface(INTERFACE_NAME);
}
void Stop()
{
proxy_->callMethod("Stop").onInterface(INTERFACE_NAME);
}
void TogglePause()
{
proxy_->callMethod("TogglePause").onInterface(INTERFACE_NAME);
}
public:
std::string FilePath()
{
return proxy_->getProperty("FilePath").onInterface(INTERFACE_NAME);
}
std::string FileTitle()
{
return proxy_->getProperty("FileTitle").onInterface(INTERFACE_NAME);
}
double Position()
{
return proxy_->getProperty("Position").onInterface(INTERFACE_NAME);
}
void Position(const double& value)
{
proxy_->setProperty("Position").onInterface(INTERFACE_NAME).toValue(value);
}
double Length()
{
return proxy_->getProperty("Length").onInterface(INTERFACE_NAME);
}
double Speed()
{
return proxy_->getProperty("Speed").onInterface(INTERFACE_NAME);
}
void Speed(const double& value)
{
proxy_->setProperty("Speed").onInterface(INTERFACE_NAME).toValue(value);
}
double Tempo()
{
return proxy_->getProperty("Tempo").onInterface(INTERFACE_NAME);
}
void Tempo(const double& value)
{
proxy_->setProperty("Tempo").onInterface(INTERFACE_NAME).toValue(value);
}
double Pitch()
{
return proxy_->getProperty("Pitch").onInterface(INTERFACE_NAME);
}
void Pitch(const double& value)
{
proxy_->setProperty("Pitch").onInterface(INTERFACE_NAME).toValue(value);
}
double Volume()
{
return proxy_->getProperty("Volume").onInterface(INTERFACE_NAME);
}
void Volume(const double& value)
{
proxy_->setProperty("Volume").onInterface(INTERFACE_NAME).toValue(value);
}
bool Paused()
{
return proxy_->getProperty("Paused").onInterface(INTERFACE_NAME);
}
void Paused(const bool& value)
{
proxy_->setProperty("Paused").onInterface(INTERFACE_NAME).toValue(value);
}
bool IsStopped()
{
return proxy_->getProperty("IsStopped").onInterface(INTERFACE_NAME);
}
bool IsDaemon()
{
return proxy_->getProperty("IsDaemon").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}} // namespaces
namespace com {
namespace complecwaft {
namespace Looper {
class Errors_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "com.complecwaft.Looper.Errors";
protected:
Errors_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
Errors_proxy(const Errors_proxy&) = delete;
Errors_proxy& operator=(const Errors_proxy&) = delete;
Errors_proxy(Errors_proxy&&) = default;
Errors_proxy& operator=(Errors_proxy&&) = default;
~Errors_proxy() = default;
public:
std::string PopFront(const std::string& handle)
{
std::string result;
proxy_->callMethod("PopFront").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::string PopBack(const std::string& handle)
{
std::string result;
proxy_->callMethod("PopBack").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::string PeekFront(const std::string& handle)
{
std::string result;
proxy_->callMethod("PeekFront").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::string PeekBack(const std::string& handle)
{
std::string result;
proxy_->callMethod("PeekBack").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
uint32_t GetCount(const std::string& handle)
{
uint32_t result;
proxy_->callMethod("GetCount").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
bool IsEmpty(const std::string& handle)
{
bool result;
proxy_->callMethod("IsEmpty").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
void Clear(const std::string& handle)
{
proxy_->callMethod("Clear").onInterface(INTERFACE_NAME).withArguments(handle);
}
std::vector<std::string> PeekAll(const std::string& handle)
{
std::vector<std::string> result;
proxy_->callMethod("PeekAll").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
std::vector<std::string> GetAllAndClear(const std::string& handle)
{
std::vector<std::string> result;
proxy_->callMethod("GetAllAndClear").onInterface(INTERFACE_NAME).withArguments(handle).storeResultsTo(result);
return result;
}
private:
sdbus::IProxy* proxy_;
};
}}} // namespaces
#endif

View file

@ -1,10 +0,0 @@
[Desktop Entry]
Type=Application
Name=Looper
Comment=An audio player that can properly loop audio files
GenericName=Looping audio player
Exec=looper %f
Icon=looper
MimeType=audio/x-wav;audio/ogg;audio/x-vorbis+ogg;audio/x-opus+ogg;audio/mpeg;audio/flac;audio/xm;audio/x-mod;
StartupWMClass=looper
Categories=Audio;AudioVideo;

81
assets/mpris.dbus.xml Normal file
View file

@ -0,0 +1,81 @@
<node name="/org/mpris/MediaPlayer2">
<interface name="org.mpris.MediaPlayer2">
<method name="Raise" />
<method name="Quit" />
<property name="CanRaise" type="b" access="read" />
<property name="CanQuit" type="b" access="read" />
<property name="HasTrackList" type="b" access="read" />
<property name="Identity" type="s" access="read" />
<property name="SupportedUriSchemes" type="as" access="read" />
<property name="SupportedMimeTypes" type="as" access="read" />
</interface>
<interface name="org.mpris.MediaPlayer2.Player">
<method name="Next" />
<method name="Previous" />
<method name="Pause" />
<method name="PlayPause" />
<method name="Stop" />
<method name="Play" />
<method name="Seek">
<arg name="Offset" type="x" direction="in" />
</method>
<method name="SetPosition">
<arg name="TrackId" type="o" direction="in" />
<arg name="Position" type="x" direction="in" />
</method>
<method name="OpenUri">
<arg name="Uri" type="s" direction="in" />
</method>
<signal name="Seeked">
<arg name="Position" type="x" />
</signal>
<property name="PlaybackStatus" type="s" access="read" />
<property name="Rate" type="d" access="readwrite" />
<property name="Metadata" type="a{sv}" access="read" />
<property name="Volume" type="d" access="readwrite" />
<property name="Position" type="x" access="read" />
<property name="MinimumRate" type="d" access="read" />
<property name="MaximumRate" type="d" access="read" />
<property name="CanGoNext" type="b" access="read" />
<property name="CanGoPrevious" type="b" access="read" />
<property name="CanPlay" type="b" access="read" />
<property name="CanPause" type="b" access="read" />
<property name="CanSeek" type="b" access="read" />
<property name="CanControl" type="b" access="read" />
</interface>
<interface name="org.mpris.MediaPlayer2.TrackList">
<method name="GetTracksMetadata">
<arg name="TrackIds" direction="in" type="ao" />
<arg name="Metadata" direction="out" type="aa{sv}" />
</method>
<method name="AddTrack">
<arg name="Uri" type="s" direction="in" />
<arg name="AfterTrack" type="o" direction="in" />
<arg name="SetAsCurrent" type="b" direction="in" />
</method>
<method name="RemoveTrack">
<arg name="TrackId" type="o" direction="in" />
</method>
<method name="GoTo">
<arg name="TrackId" type="o" direction="in" />
</method>
<signal name="TrackListReplaced">
<arg name="Tracks" type="ao" />
<arg name="CurrentTrack" type="o" />
</signal>
<signal name="TrackAdded">
<arg name="Metadata" type="a{sv}" />
<arg name="AfterTrack" type="o" />
</signal>
<signal name="TrackRemoved">
<arg name="TrackId" type="o" />
</signal>
<signal name="TrackMetadataChanged">
<arg name="TrackId" type="o" />
<arg name="Metadata" type="a{sv}" />
</signal>
<property name="Tracks" type="ao" access="read" />
<property name="CanEditTracks" type="b" access="read" />
</interface>
</node>

View file

@ -0,0 +1,214 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__mpris_stub_adaptor_hpp__adaptor__H__
#define __sdbuscpp__mpris_stub_adaptor_hpp__adaptor__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace mpris {
class MediaPlayer2_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2";
protected:
MediaPlayer2_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("Raise").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Raise(); });
object_->registerMethod("Quit").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Quit(); });
object_->registerProperty("CanRaise").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanRaise(); });
object_->registerProperty("CanQuit").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanQuit(); });
object_->registerProperty("HasTrackList").onInterface(INTERFACE_NAME).withGetter([this](){ return this->HasTrackList(); });
object_->registerProperty("Identity").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Identity(); });
object_->registerProperty("SupportedUriSchemes").onInterface(INTERFACE_NAME).withGetter([this](){ return this->SupportedUriSchemes(); });
object_->registerProperty("SupportedMimeTypes").onInterface(INTERFACE_NAME).withGetter([this](){ return this->SupportedMimeTypes(); });
}
MediaPlayer2_adaptor(const MediaPlayer2_adaptor&) = delete;
MediaPlayer2_adaptor& operator=(const MediaPlayer2_adaptor&) = delete;
MediaPlayer2_adaptor(MediaPlayer2_adaptor&&) = default;
MediaPlayer2_adaptor& operator=(MediaPlayer2_adaptor&&) = default;
~MediaPlayer2_adaptor() = default;
private:
virtual void Raise() = 0;
virtual void Quit() = 0;
private:
virtual bool CanRaise() = 0;
virtual bool CanQuit() = 0;
virtual bool HasTrackList() = 0;
virtual std::string Identity() = 0;
virtual std::vector<std::string> SupportedUriSchemes() = 0;
virtual std::vector<std::string> SupportedMimeTypes() = 0;
private:
sdbus::IObject* object_;
};
}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class Player_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.Player";
protected:
Player_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("Next").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Next(); });
object_->registerMethod("Previous").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Previous(); });
object_->registerMethod("Pause").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Pause(); });
object_->registerMethod("PlayPause").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->PlayPause(); });
object_->registerMethod("Stop").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Stop(); });
object_->registerMethod("Play").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->Play(); });
object_->registerMethod("Seek").onInterface(INTERFACE_NAME).withInputParamNames("Offset").implementedAs([this](const int64_t& Offset){ return this->Seek(Offset); });
object_->registerMethod("SetPosition").onInterface(INTERFACE_NAME).withInputParamNames("TrackId", "Position").implementedAs([this](const sdbus::ObjectPath& TrackId, const int64_t& Position){ return this->SetPosition(TrackId, Position); });
object_->registerMethod("OpenUri").onInterface(INTERFACE_NAME).withInputParamNames("Uri").implementedAs([this](const std::string& Uri){ return this->OpenUri(Uri); });
object_->registerSignal("Seeked").onInterface(INTERFACE_NAME).withParameters<int64_t>("Position");
object_->registerProperty("PlaybackStatus").onInterface(INTERFACE_NAME).withGetter([this](){ return this->PlaybackStatus(); });
object_->registerProperty("Rate").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Rate(); }).withSetter([this](const double& value){ this->Rate(value); });
object_->registerProperty("Metadata").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Metadata(); });
object_->registerProperty("Volume").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Volume(); }).withSetter([this](const double& value){ this->Volume(value); });
object_->registerProperty("Position").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Position(); });
object_->registerProperty("MinimumRate").onInterface(INTERFACE_NAME).withGetter([this](){ return this->MinimumRate(); });
object_->registerProperty("MaximumRate").onInterface(INTERFACE_NAME).withGetter([this](){ return this->MaximumRate(); });
object_->registerProperty("CanGoNext").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanGoNext(); });
object_->registerProperty("CanGoPrevious").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanGoPrevious(); });
object_->registerProperty("CanPlay").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanPlay(); });
object_->registerProperty("CanPause").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanPause(); });
object_->registerProperty("CanSeek").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanSeek(); });
object_->registerProperty("CanControl").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanControl(); });
}
Player_adaptor(const Player_adaptor&) = delete;
Player_adaptor& operator=(const Player_adaptor&) = delete;
Player_adaptor(Player_adaptor&&) = default;
Player_adaptor& operator=(Player_adaptor&&) = default;
~Player_adaptor() = default;
public:
void emitSeeked(const int64_t& Position)
{
object_->emitSignal("Seeked").onInterface(INTERFACE_NAME).withArguments(Position);
}
private:
virtual void Next() = 0;
virtual void Previous() = 0;
virtual void Pause() = 0;
virtual void PlayPause() = 0;
virtual void Stop() = 0;
virtual void Play() = 0;
virtual void Seek(const int64_t& Offset) = 0;
virtual void SetPosition(const sdbus::ObjectPath& TrackId, const int64_t& Position) = 0;
virtual void OpenUri(const std::string& Uri) = 0;
private:
virtual std::string PlaybackStatus() = 0;
virtual double Rate() = 0;
virtual void Rate(const double& value) = 0;
virtual std::map<std::string, sdbus::Variant> Metadata() = 0;
virtual double Volume() = 0;
virtual void Volume(const double& value) = 0;
virtual int64_t Position() = 0;
virtual double MinimumRate() = 0;
virtual double MaximumRate() = 0;
virtual bool CanGoNext() = 0;
virtual bool CanGoPrevious() = 0;
virtual bool CanPlay() = 0;
virtual bool CanPause() = 0;
virtual bool CanSeek() = 0;
virtual bool CanControl() = 0;
private:
sdbus::IObject* object_;
};
}}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class TrackList_adaptor
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.TrackList";
protected:
TrackList_adaptor(sdbus::IObject& object)
: object_(&object)
{
object_->registerMethod("GetTracksMetadata").onInterface(INTERFACE_NAME).withInputParamNames("TrackIds").withOutputParamNames("Metadata").implementedAs([this](const std::vector<sdbus::ObjectPath>& TrackIds){ return this->GetTracksMetadata(TrackIds); });
object_->registerMethod("AddTrack").onInterface(INTERFACE_NAME).withInputParamNames("Uri", "AfterTrack", "SetAsCurrent").implementedAs([this](const std::string& Uri, const sdbus::ObjectPath& AfterTrack, const bool& SetAsCurrent){ return this->AddTrack(Uri, AfterTrack, SetAsCurrent); });
object_->registerMethod("RemoveTrack").onInterface(INTERFACE_NAME).withInputParamNames("TrackId").implementedAs([this](const sdbus::ObjectPath& TrackId){ return this->RemoveTrack(TrackId); });
object_->registerMethod("GoTo").onInterface(INTERFACE_NAME).withInputParamNames("TrackId").implementedAs([this](const sdbus::ObjectPath& TrackId){ return this->GoTo(TrackId); });
object_->registerSignal("TrackListReplaced").onInterface(INTERFACE_NAME).withParameters<std::vector<sdbus::ObjectPath>, sdbus::ObjectPath>("Tracks", "CurrentTrack");
object_->registerSignal("TrackAdded").onInterface(INTERFACE_NAME).withParameters<std::map<std::string, sdbus::Variant>, sdbus::ObjectPath>("Metadata", "AfterTrack");
object_->registerSignal("TrackRemoved").onInterface(INTERFACE_NAME).withParameters<sdbus::ObjectPath>("TrackId");
object_->registerSignal("TrackMetadataChanged").onInterface(INTERFACE_NAME).withParameters<sdbus::ObjectPath, std::map<std::string, sdbus::Variant>>("TrackId", "Metadata");
object_->registerProperty("Tracks").onInterface(INTERFACE_NAME).withGetter([this](){ return this->Tracks(); });
object_->registerProperty("CanEditTracks").onInterface(INTERFACE_NAME).withGetter([this](){ return this->CanEditTracks(); });
}
TrackList_adaptor(const TrackList_adaptor&) = delete;
TrackList_adaptor& operator=(const TrackList_adaptor&) = delete;
TrackList_adaptor(TrackList_adaptor&&) = default;
TrackList_adaptor& operator=(TrackList_adaptor&&) = default;
~TrackList_adaptor() = default;
public:
void emitTrackListReplaced(const std::vector<sdbus::ObjectPath>& Tracks, const sdbus::ObjectPath& CurrentTrack)
{
object_->emitSignal("TrackListReplaced").onInterface(INTERFACE_NAME).withArguments(Tracks, CurrentTrack);
}
void emitTrackAdded(const std::map<std::string, sdbus::Variant>& Metadata, const sdbus::ObjectPath& AfterTrack)
{
object_->emitSignal("TrackAdded").onInterface(INTERFACE_NAME).withArguments(Metadata, AfterTrack);
}
void emitTrackRemoved(const sdbus::ObjectPath& TrackId)
{
object_->emitSignal("TrackRemoved").onInterface(INTERFACE_NAME).withArguments(TrackId);
}
void emitTrackMetadataChanged(const sdbus::ObjectPath& TrackId, const std::map<std::string, sdbus::Variant>& Metadata)
{
object_->emitSignal("TrackMetadataChanged").onInterface(INTERFACE_NAME).withArguments(TrackId, Metadata);
}
private:
virtual std::vector<std::map<std::string, sdbus::Variant>> GetTracksMetadata(const std::vector<sdbus::ObjectPath>& TrackIds) = 0;
virtual void AddTrack(const std::string& Uri, const sdbus::ObjectPath& AfterTrack, const bool& SetAsCurrent) = 0;
virtual void RemoveTrack(const sdbus::ObjectPath& TrackId) = 0;
virtual void GoTo(const sdbus::ObjectPath& TrackId) = 0;
private:
virtual std::vector<sdbus::ObjectPath> Tracks() = 0;
virtual bool CanEditTracks() = 0;
private:
sdbus::IObject* object_;
};
}}} // namespaces
#endif

306
assets/mpris_stub_proxy.hpp Normal file
View file

@ -0,0 +1,306 @@
/*
* This file was automatically generated by sdbus-c++-xml2cpp; DO NOT EDIT!
*/
#ifndef __sdbuscpp__mpris_stub_proxy_hpp__proxy__H__
#define __sdbuscpp__mpris_stub_proxy_hpp__proxy__H__
#include <sdbus-c++/sdbus-c++.h>
#include <string>
#include <tuple>
namespace org {
namespace mpris {
class MediaPlayer2_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2";
protected:
MediaPlayer2_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
}
MediaPlayer2_proxy(const MediaPlayer2_proxy&) = delete;
MediaPlayer2_proxy& operator=(const MediaPlayer2_proxy&) = delete;
MediaPlayer2_proxy(MediaPlayer2_proxy&&) = default;
MediaPlayer2_proxy& operator=(MediaPlayer2_proxy&&) = default;
~MediaPlayer2_proxy() = default;
public:
void Raise()
{
proxy_->callMethod("Raise").onInterface(INTERFACE_NAME);
}
void Quit()
{
proxy_->callMethod("Quit").onInterface(INTERFACE_NAME);
}
public:
bool CanRaise()
{
return proxy_->getProperty("CanRaise").onInterface(INTERFACE_NAME);
}
bool CanQuit()
{
return proxy_->getProperty("CanQuit").onInterface(INTERFACE_NAME);
}
bool HasTrackList()
{
return proxy_->getProperty("HasTrackList").onInterface(INTERFACE_NAME);
}
std::string Identity()
{
return proxy_->getProperty("Identity").onInterface(INTERFACE_NAME);
}
std::vector<std::string> SupportedUriSchemes()
{
return proxy_->getProperty("SupportedUriSchemes").onInterface(INTERFACE_NAME);
}
std::vector<std::string> SupportedMimeTypes()
{
return proxy_->getProperty("SupportedMimeTypes").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class Player_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.Player";
protected:
Player_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
proxy_->uponSignal("Seeked").onInterface(INTERFACE_NAME).call([this](const int64_t& Position){ this->onSeeked(Position); });
}
Player_proxy(const Player_proxy&) = delete;
Player_proxy& operator=(const Player_proxy&) = delete;
Player_proxy(Player_proxy&&) = default;
Player_proxy& operator=(Player_proxy&&) = default;
~Player_proxy() = default;
virtual void onSeeked(const int64_t& Position) = 0;
public:
void Next()
{
proxy_->callMethod("Next").onInterface(INTERFACE_NAME);
}
void Previous()
{
proxy_->callMethod("Previous").onInterface(INTERFACE_NAME);
}
void Pause()
{
proxy_->callMethod("Pause").onInterface(INTERFACE_NAME);
}
void PlayPause()
{
proxy_->callMethod("PlayPause").onInterface(INTERFACE_NAME);
}
void Stop()
{
proxy_->callMethod("Stop").onInterface(INTERFACE_NAME);
}
void Play()
{
proxy_->callMethod("Play").onInterface(INTERFACE_NAME);
}
void Seek(const int64_t& Offset)
{
proxy_->callMethod("Seek").onInterface(INTERFACE_NAME).withArguments(Offset);
}
void SetPosition(const sdbus::ObjectPath& TrackId, const int64_t& Position)
{
proxy_->callMethod("SetPosition").onInterface(INTERFACE_NAME).withArguments(TrackId, Position);
}
void OpenUri(const std::string& Uri)
{
proxy_->callMethod("OpenUri").onInterface(INTERFACE_NAME).withArguments(Uri);
}
public:
std::string PlaybackStatus()
{
return proxy_->getProperty("PlaybackStatus").onInterface(INTERFACE_NAME);
}
double Rate()
{
return proxy_->getProperty("Rate").onInterface(INTERFACE_NAME);
}
void Rate(const double& value)
{
proxy_->setProperty("Rate").onInterface(INTERFACE_NAME).toValue(value);
}
std::map<std::string, sdbus::Variant> Metadata()
{
return proxy_->getProperty("Metadata").onInterface(INTERFACE_NAME);
}
double Volume()
{
return proxy_->getProperty("Volume").onInterface(INTERFACE_NAME);
}
void Volume(const double& value)
{
proxy_->setProperty("Volume").onInterface(INTERFACE_NAME).toValue(value);
}
int64_t Position()
{
return proxy_->getProperty("Position").onInterface(INTERFACE_NAME);
}
double MinimumRate()
{
return proxy_->getProperty("MinimumRate").onInterface(INTERFACE_NAME);
}
double MaximumRate()
{
return proxy_->getProperty("MaximumRate").onInterface(INTERFACE_NAME);
}
bool CanGoNext()
{
return proxy_->getProperty("CanGoNext").onInterface(INTERFACE_NAME);
}
bool CanGoPrevious()
{
return proxy_->getProperty("CanGoPrevious").onInterface(INTERFACE_NAME);
}
bool CanPlay()
{
return proxy_->getProperty("CanPlay").onInterface(INTERFACE_NAME);
}
bool CanPause()
{
return proxy_->getProperty("CanPause").onInterface(INTERFACE_NAME);
}
bool CanSeek()
{
return proxy_->getProperty("CanSeek").onInterface(INTERFACE_NAME);
}
bool CanControl()
{
return proxy_->getProperty("CanControl").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}}} // namespaces
namespace org {
namespace mpris {
namespace MediaPlayer2 {
class TrackList_proxy
{
public:
static constexpr const char* INTERFACE_NAME = "org.mpris.MediaPlayer2.TrackList";
protected:
TrackList_proxy(sdbus::IProxy& proxy)
: proxy_(&proxy)
{
proxy_->uponSignal("TrackListReplaced").onInterface(INTERFACE_NAME).call([this](const std::vector<sdbus::ObjectPath>& Tracks, const sdbus::ObjectPath& CurrentTrack){ this->onTrackListReplaced(Tracks, CurrentTrack); });
proxy_->uponSignal("TrackAdded").onInterface(INTERFACE_NAME).call([this](const std::map<std::string, sdbus::Variant>& Metadata, const sdbus::ObjectPath& AfterTrack){ this->onTrackAdded(Metadata, AfterTrack); });
proxy_->uponSignal("TrackRemoved").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& TrackId){ this->onTrackRemoved(TrackId); });
proxy_->uponSignal("TrackMetadataChanged").onInterface(INTERFACE_NAME).call([this](const sdbus::ObjectPath& TrackId, const std::map<std::string, sdbus::Variant>& Metadata){ this->onTrackMetadataChanged(TrackId, Metadata); });
}
TrackList_proxy(const TrackList_proxy&) = delete;
TrackList_proxy& operator=(const TrackList_proxy&) = delete;
TrackList_proxy(TrackList_proxy&&) = default;
TrackList_proxy& operator=(TrackList_proxy&&) = default;
~TrackList_proxy() = default;
virtual void onTrackListReplaced(const std::vector<sdbus::ObjectPath>& Tracks, const sdbus::ObjectPath& CurrentTrack) = 0;
virtual void onTrackAdded(const std::map<std::string, sdbus::Variant>& Metadata, const sdbus::ObjectPath& AfterTrack) = 0;
virtual void onTrackRemoved(const sdbus::ObjectPath& TrackId) = 0;
virtual void onTrackMetadataChanged(const sdbus::ObjectPath& TrackId, const std::map<std::string, sdbus::Variant>& Metadata) = 0;
public:
std::vector<std::map<std::string, sdbus::Variant>> GetTracksMetadata(const std::vector<sdbus::ObjectPath>& TrackIds)
{
std::vector<std::map<std::string, sdbus::Variant>> result;
proxy_->callMethod("GetTracksMetadata").onInterface(INTERFACE_NAME).withArguments(TrackIds).storeResultsTo(result);
return result;
}
void AddTrack(const std::string& Uri, const sdbus::ObjectPath& AfterTrack, const bool& SetAsCurrent)
{
proxy_->callMethod("AddTrack").onInterface(INTERFACE_NAME).withArguments(Uri, AfterTrack, SetAsCurrent);
}
void RemoveTrack(const sdbus::ObjectPath& TrackId)
{
proxy_->callMethod("RemoveTrack").onInterface(INTERFACE_NAME).withArguments(TrackId);
}
void GoTo(const sdbus::ObjectPath& TrackId)
{
proxy_->callMethod("GoTo").onInterface(INTERFACE_NAME).withArguments(TrackId);
}
public:
std::vector<sdbus::ObjectPath> Tracks()
{
return proxy_->getProperty("Tracks").onInterface(INTERFACE_NAME);
}
bool CanEditTracks()
{
return proxy_->getProperty("CanEditTracks").onInterface(INTERFACE_NAME);
}
private:
sdbus::IProxy* proxy_;
};
}}} // namespaces
#endif

View file

@ -4,11 +4,21 @@ import sys
import subprocess
import os.path as path
import shutil
from shutil import which
from glob import glob
olddir = os.curdir
print("Entering assets directory to begin asset conversion...")
os.chdir(path.realpath(path.dirname(__file__)))
ASSETS = []
WARNINGS: dict[str, bool] = {}
def warn(id: str, message: str, WARNINGS = WARNINGS) -> None:
warned_already: bool = False
try:
warned_already = WARNINGS[id]
except KeyError:
pass
if not warned_already:
print("WARNING: %s" % message)
def run_btcc(args: list[str], outpath: str):
with open(outpath + ".h", "wt+") as f:
actual_args = ["./btcc"]
@ -44,6 +54,15 @@ def add_css(file: str, output: str):
CSS_FILE = output + ".h"
print("Adding CSS file '%s' (C identifier '%s') from file '%s'" % (file, output, CSS_FILE))
add_basic(file, output)
def add_dbus(file: str, output_basename: str, adaptor: str|None = None, proxy: str|None = None, WARNINGS = WARNINGS):
if adaptor == None:
adaptor = output_basename + '_adaptor.hpp'
if proxy == None:
proxy = output_basename + '_proxy.hpp'
if which('sdbus-c++-xml2cpp') is not None:
subprocess.call(['sdbus-c++-xml2cpp', file, '--adaptor=' + adaptor, '--proxy=' + proxy])
else:
warn("Not generating DBus API stubs.")
compile_program("../backends/ui/imgui/imgui/misc/fonts/binary_to_compressed_c.cpp", "btcc")
for i in glob("Noto_Sans/*.ttf"):
add_font(i)
@ -69,6 +88,8 @@ add_license("licenses/cli11.txt", "cli11")
add_license("licenses/TomlPlusPlus.txt", "tomlplusplus")
add_license("../backends/ui/imgui/IconFontCppHeaders/licence.txt", "icnfntcpphdrs")
add_css("gtk-frontend.css", "gtk_frontend_css")
add_dbus('app.dbus.xml', 'dbus_stub')
add_dbus('mpris.dbus.xml', 'mpris_stub')
def finalize(output: str, ASSETS = ASSETS):
print("Writing a header including all previous asset headers and writing it to '%s'..." % output)
with open(output, "wt+") as f:

View file

@ -8,57 +8,87 @@ std::map<std::string, UIBackend*> UIBackend::backends;
std::string UIBackend::get_id() {
return "";
}
int UIBackend::run(std::vector<std::string> realArgs, int argc, char **argv) {
#ifdef GLIB_ENABLED
g_set_application_name("Looper")
#endif
args = realArgs;
UIBackend *UIBackend::running_ui_backend = nullptr;
void UIBackend::init_playback() {
if (multi_instance) {
playback = new PlaybackInstance();
} else {
playback = Playback::Create(&daemon_found);
if (playback == nullptr) {
throw 1;
}
}
}
void UIBackend::init_libportal() {
#ifdef GLIB_ENABLED
g_set_application_name("Looper");
#endif
}
void UIBackend::setup_playback_args() {
if (speed_set) {
playback->SetSpeed(new_speed);
}
if (tempo_set) {
playback->SetTempo(new_tempo);
}
if (pitch_set) {
playback->SetPitch(new_pitch);
}
if (args.size() > 0) {
playback->Start(args[0]);
}
if (!daemon_found && playback->is_proxy()) {
throw 0;
}
}
bool UIBackend::parse_args(std::vector<std::string> realArgs, int argc, char **argv) {
args = realArgs;
CLI::App app{DESCRIPTION};
std::string filename = "";
app.allow_extras();
double new_speed = 1.0;
double new_tempo = 1.0;
double new_pitch = 1.0;
auto speed_opt = app.add_option("-s,--speed", new_speed, "Set the initial speed of the playback.")->default_val(1.0);
auto tempo_opt = app.add_option("-t,--tempo", new_tempo, "Set the initial tempo of the playback.")->default_val(1.0);
auto pitch_opt = app.add_option("-p,--pitch", new_pitch, "Set the initial pitch of the playback.")->default_val(1.0);
bool multi_instance = false;
std::vector<CLI::Option*> options;
options.push_back(speed_opt);
options.push_back(tempo_opt);
options.push_back(pitch_opt);
if (allow_multi_instance()) {
auto multi_instance_opt = app.add_flag<bool>("-m,--multi-instance", multi_instance, "Disables the DBus api to allow multiple instances.");
options.push_back(multi_instance_opt);
}
try {
app.parse(args);
} catch (const CLI::ParseError &e) {
throw app.exit(e);
}
playback->SetSpeed(new_speed);
playback->SetTempo(new_tempo);
playback->SetPitch(new_pitch);
args = app.remaining();
if (!multi_instance) {
DBusAPISender sender;
if (!sender.isOnlyInstance()) {
speed_set = !speed_opt->empty();
tempo_set = !tempo_opt->empty();
pitch_set = !pitch_opt->empty();
if (args.size() > 0) {
sender.playFile(args[0]);
}
if (!speed_opt->empty()) {
sender.setSpeed(new_speed);
}
if (!tempo_opt->empty()) {
sender.setTempo(new_tempo);
}
if (!pitch_opt->empty()) {
sender.setPitch(new_pitch);
}
throw 0;
return true;
} else {
dbus_api = new DBusAPI(playback);
for (auto opt : options) {
if (!opt->empty()) {
return true;
}
} else {
dbus_api = nullptr;
}
if (args.size() > 0) {
playback->Start(args[0]);
}
return false;
}
void UIBackend::init_dbus() {
if (!multi_instance && !playback->is_proxy()) {
dbus_api = DBusAPI::Create(playback);
}
}
int UIBackend::run(std::vector<std::string> realArgs, int argc, char **argv) {
init_libportal();
parse_args(realArgs, argc, argv);
init_playback();
setup_playback_args();
init_dbus();
return 0;
}
void UIBackend::deinit_backends() {

View file

@ -3,28 +3,53 @@
#include <string>
#include <map>
#include <optional>
#include <atomic>
#include "playback.h"
#include "dbus.hpp"
class UIBackend {
protected:
std::vector<std::string> args;
Playback *playback;
std::atomic_bool exit_flag = false;
double new_speed = 1.0;
double new_tempo = 1.0;
double new_pitch = 1.0;
bool speed_set = false;
bool tempo_set = false;
bool pitch_set = false;
bool multi_instance = false;
bool daemon_found = false;
public:
DBusAPI *dbus_api;
inline virtual bool allow_multi_instance() {
return true;
}
virtual std::string get_id();
virtual std::string get_name();
UIBackend() = default;
virtual void add_licenses() {
/// @brief A hook to add any licenses of software packages used by the UI backend.
inline virtual void add_licenses() {
// Don't add any here, but leave this specified. That way, licenses specific to UI frontends are only added per UI frontend.
}
void init_libportal();
void init_playback();
void setup_playback_args();
void init_dbus();
bool parse_args(std::vector<std::string> realArgs, int argc, char **argv);
/// @brief The main loop of the UI. Be sure to call @ref UIBackend::run and be prepared for it to throw an integer exit code that needs to escape your implementation.
virtual int run(std::vector<std::string> realArgs, int argc, char **argv);
static std::map<std::string, UIBackend*> backends;
static inline std::optional<UIBackend*> get_backend(std::string id) {
if (backends.contains(id)) {
return backends[id];
} else {
return {};
/// @brief A hook that is called when the D-Bus API receives a request to exit the program.
inline virtual void QuitHandler() {
// Set a flag for loops managed by the UI backend.
exit_flag.store(true);
}
static std::map<std::string, UIBackend*> backends;
template<class T>
static inline std::string get_backend_id() {
UIBackend *backend = new T();
auto output = backend->get_id();
delete backend;
return output;
}
static inline UIBackend* get_first_backend() {
if (backends.empty()) {
@ -32,9 +57,7 @@ class UIBackend {
}
return (*backends.begin()).second;
}
template<class T>
static void register_backend() {
UIBackend *backend = new T();
static void register_backend(UIBackend *backend) {
std::string backend_id = backend->get_id();
if (backends.contains(backend_id)) { // Guard against potential memory leak due to reassigning a new pointer without deallocating the previous one
delete backend;
@ -42,7 +65,47 @@ class UIBackend {
}
backends[backend_id] = backend;
}
template<class T>
static void register_backend() {
UIBackend *backend = new T();
backend->register_self();
}
static inline void unregister_backend(std::string id) {
if (backends.contains(id)) {
auto backend = backends[id];
delete backend;
backends.erase(id);
}
}
inline void unregister_self() {
UIBackend::unregister_backend(get_id());
}
inline void register_self() {
UIBackend::register_backend(this);
}
template<class T>
static void unregister_backend() {
unregister_backend(get_backend_id<T>());
}
static void deinit_backends();
static inline std::optional<UIBackend*> get_backend(std::string id) {
if (backends.contains(id)) {
return backends[id];
} else {
return {};
}
}
template<class T>
static inline std::optional<T*> get_backend() {
auto id = get_backend_id<T>();
auto output = get_backend(id);
if (output.has_value()) {
return (T*)output.value();
} else {
return {};
}
}
static UIBackend *running_ui_backend;
virtual ~UIBackend();
};
void init_backends();

View file

@ -14,7 +14,7 @@ int GtkBackend::run(std::vector<std::string> realArgs, int argc, char **argv) {
return ret;
}
Glib::set_application_name("Looper");
auto app = Gtk::Application::create("com.experimentalcraft.Looper.GTK", Gio::Application::Flags::NON_UNIQUE);
auto app = Gtk::Application::create("com.complecwaft.Looper.GTK", Gio::Application::Flags::NON_UNIQUE);
char *gtk_frontend_css = (char*)malloc(gtk_frontend_css_size + 1);
memcpy(gtk_frontend_css, gtk_frontend_css_data, gtk_frontend_css_size);
gtk_frontend_css[gtk_frontend_css_size] = '\0';
@ -32,10 +32,16 @@ int GtkBackend::run(std::vector<std::string> realArgs, int argc, char **argv) {
auto *win = new MainWindow(playback, app);
win->present();
});
app_ptr = app.get();
ret = app->run(argc, argv);
app_ptr = nullptr;
return ret;
}
void GtkBackend::QuitHandler() {
if (app_ptr != nullptr) {
((Gtk::Application*)app_ptr)->quit();
}
}
void GtkBackend::add_licenses() {
auto &license_data = get_license_data();
auto gtkmm = LicenseData("GtkMM", "lgpl-2.1");

View file

@ -4,9 +4,11 @@
#include <vector>
#define GTK_FRONTEND
class GtkBackend : public UIBackend {
void *app_ptr;
public:
std::string get_id() override;
std::string get_name() override;
void add_licenses() override;
void QuitHandler() override;
int run(std::vector<std::string> realArgs, int argc, char **argv) override;
};

View file

@ -361,6 +361,9 @@ void MainLoop::GuiFunction() {
// Make sure to not load the file unnecessarily.
fileDialog.ClearSelected();
}
if (exit_flag.load()) {
done = true;
}
}
void MainLoop::LoadFile(std::string file) {
playback->Start(file);
@ -408,6 +411,12 @@ std::string ImGuiUIBackend::get_id() {
std::string ImGuiUIBackend::get_name() {
return "Dear ImGui frontend";
}
void ImGuiUIBackend::QuitHandler() {
if (main_loop == nullptr) {
return;
}
((MainLoop*)main_loop)->exit_flag.store(true);
}
// Main code
int ImGuiUIBackend::run(std::vector<std::string> realArgs, int argc, char** argv)
{
@ -418,6 +427,7 @@ int ImGuiUIBackend::run(std::vector<std::string> realArgs, int argc, char** argv
MainLoop loop;
loop.playback = playback;
loop.args = args;
main_loop = &loop;
return loop.Run();
}
void ImGuiUIBackend::add_licenses() {

View file

@ -33,6 +33,7 @@
#include "../libs/emscripten/emscripten_mainloop_stub.h"
#endif
#include "../../../backend.hpp"
#include "ui_backend.hpp"
using namespace std::filesystem;
using namespace std::numbers;
using std::string;
@ -50,6 +51,8 @@ class MainLoop : public RendererBackend {
bool stopped = true;
std::vector<UIBackend*> backends;
UIBackend *cur_backend;
friend class ImGuiUIBackend;
std::atomic_bool exit_flag;
public:
Playback *playback;
vector<std::string> args;

View file

@ -6,6 +6,8 @@ class ImGuiUIBackend : public UIBackend {
public:
std::string get_id() override;
std::string get_name() override;
void QuitHandler() override;
void *main_loop;
int run(std::vector<std::string> realArgs, int argc, char **argv) override;
void add_licenses() override;
ImGuiUIBackend() = default;

29
daemon_backend.cpp Normal file
View file

@ -0,0 +1,29 @@
#include "daemon_backend.hpp"
#include "log.hpp"
#include <thread>
#include <chrono>
using namespace std::literals::chrono_literals;
std::string DaemonGlueBackend::get_id() {
return "daemon";
}
std::string DaemonGlueBackend::get_name() {
return "Daemon Backend";
}
bool DaemonGlueBackend::allow_multi_instance() {
return false;
}
int DaemonGlueBackend::run(std::vector<std::string> realArgs, int argc, char **argv) {
parse_args(realArgs, argc, argv);
playback = Playback::Create(nullptr, true);
if (playback == nullptr) {
ERROR.writeln("Cannot start daemon with existing instance running.");
return 1;
}
setup_playback_args();
dbus_api = DBusAPI::Create(playback, true);
exit_flag.store(false);
while (!exit_flag.load()) {
std::this_thread::sleep_for(100ms);
}
return 0;
}

9
daemon_backend.hpp Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include "backend.hpp"
class DaemonGlueBackend : public UIBackend {
public:
bool allow_multi_instance() override;
std::string get_id() override;
std::string get_name() override;
int run(std::vector<std::string> realArgs, int argc, char **argv) override;
};

665
dbus.cpp
View file

@ -1,173 +1,518 @@
#include "dbus.hpp"
#include "log.hpp"
const char *DBusAPI::serviceName = "com.experimentalcraft.looper";
const char *DBusAPI::objectPath = "/com/experimentalcraft/looper";
const char *DBusAPI::interfaceName = "com.experimentalcraft.Looper";
DBusAPI::DBusAPI(Playback *playback) {
this->playback = playback;
connection = sdbus::createSessionBusConnection(serviceName);
api = sdbus::createObject(*connection, objectPath);
api->registerMethod(interfaceName, "open", "s", "", [this](sdbus::MethodCall call) {
std::string filePath;
call >> filePath;
this->playback->Start(filePath);
});
api->registerSignal(interfaceName, "playbackEngineStarted", "");
api->registerSignal(interfaceName, "speedChanged", "d");
api->registerSignal(interfaceName, "tempoChanged", "d");
api->registerSignal(interfaceName, "pitchChanged", "d");
api->registerSignal(interfaceName, "pauseChanged", "b");
api->registerSignal(interfaceName, "stopped", "");
api->registerSignal(interfaceName, "errorOccurred", "s");
api->registerSignal(interfaceName, "seeked", "d");
api->registerSignal(interfaceName, "fileChanged", "s");
api->registerProperty(interfaceName, "speed", "d", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->GetSpeed();
}, [this](sdbus::PropertySetCall set_call) {
double newValue;
set_call >> newValue;
this->playback->SetSpeed(newValue);
});
api->registerProperty(interfaceName, "tempo", "d", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->GetTempo();
}, [this](sdbus::PropertySetCall set_call) {
double newValue;
set_call >> newValue;
this->playback->SetTempo(newValue);
});
api->registerProperty(interfaceName, "pitch", "d", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->GetPitch();
}, [this](sdbus::PropertySetCall set_call) {
double newValue;
set_call >> newValue;
this->playback->SetPitch(newValue);
});
api->registerProperty(interfaceName, "volume", "d", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->GetVolume();
}, [this](sdbus::PropertySetCall set_call) {
double newValue;
set_call >> newValue;
this->playback->SetVolume(newValue);
});
api->registerMethod(interfaceName, "stop", "", "", [this](sdbus::MethodCall call) {
this->playback->Stop();
});
api->registerMethod(interfaceName, "togglePause", "", "", [this](sdbus::MethodCall call) {
this->playback->Pause();
});
api->registerProperty(interfaceName, "paused", "b", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->IsPaused();
}, [this](sdbus::PropertySetCall call) {
bool shouldBePaused;
call >> shouldBePaused;
this->playback->SetPaused(shouldBePaused);
});
api->registerProperty(interfaceName, "stopped", "b", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->IsStopped();
});
api->registerMethod(interfaceName, "errorExists", "", "b", [this](sdbus::MethodCall call) {
auto reply = call.createReply();
reply << this->playback->ErrorExists();
});
api->registerMethod(interfaceName, "getLastError", "", "s", [this](sdbus::MethodCall call) {
auto error = this->playback->GetError();
if (error.has_value()) {
auto reply = call.createReply();
reply << error.value();
#include "backend.hpp"
#include <random>
MprisAPI::MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api)
: AdaptorInterfaces(connection, std::move(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) {
Seek(offset);
}
void MprisAPI::OpenUri(const std::string &Uri) {
dbus_api->StartByURI(Uri);
}
std::string MprisAPI::PlaybackStatus() {
if (dbus_api->IsStopped()) {
return "Stopped";
} else {
auto errorReply = call.createErrorReply(sdbus::createError(0, "No error found."));
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"] = (int64_t)(dbus_api->Length() * 1000000);
output["mpris:trackid"] = playing_track_id;
output["xesam:title"] = dbus_api->FileTitle();
output["xesam:url"] = dbus_api->FilePath();
} else {
output["mpris:trackid"] = 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());
}
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) {
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);
return output;
}
bool MprisAPI::CanEditTracks() {
return false;
}
MprisAPI::~MprisAPI() {
unregisterAdaptor();
}
DBusAPI::DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon)
: AdaptorInterfaces(connection, std::move(objectPath))
, daemon(daemon)
, playback(playback)
, connection(connection)
{
registerAdaptor();
playback->register_handle(this);
auto mprisConnection = sdbus::createSessionBusConnection("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();
}
});
api->registerProperty(interfaceName, "position", "d", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->GetPosition();
}, [this](sdbus::PropertySetCall call) {
double value;
call >> value;
this->playback->Seek(value);
});
api->registerProperty(interfaceName, "length", "d", [this](sdbus::PropertyGetReply reply) {
reply << this->playback->GetLength();
});
api->registerProperty(interfaceName, "current_file_path", "s", [this](sdbus::PropertyGetReply reply) {
auto file_maybe = this->playback->get_current_file();
if (file_maybe.has_value()) {
reply << file_maybe.value();
} else {
reply << "";
connection.enterEventLoopAsync();
}
});
api->registerProperty(interfaceName, "current_file_title", "s", [this](sdbus::PropertyGetReply reply) {
auto title_maybe = this->playback->get_current_title();
if (title_maybe.has_value()) {
reply << title_maybe.value();
} else {
reply << "";
const char *DBusAPI::objectPath = "/com/complecwaft/Looper";
const char *DBusAPI::busName = "com.complecwaft.Looper";
DBusAPI *DBusAPI::Create(Playback *playback, bool daemon) {
auto connection = sdbus::createSessionBusConnection(busName);
auto &con_ref = *connection.release();
return new DBusAPI(playback, con_ref, objectPath, daemon);
}
});
api->finishRegistration();
connection->enterEventLoopAsync();
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) {
StartByURI(uris[0]);
}
}
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.
}
void DBusAPI::Start(const std::string &path) {
playback->Start(path);
}
void DBusAPI::StartByURI(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());
Start(tmp);
}
}
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 = std::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;
}
DBusAPI::~DBusAPI() {
threadExitFlag.store(true);
threadFunc.join();
unregisterAdaptor();
}
bool DBusAPISender::isOnlyInstance() {
return !connected;
}
void DBusAPISender::playFile(std::string file) {
if (connected) {
auto method = proxy->createMethodCall(DBusAPI::interfaceName, "open");
method.dontExpectReply();
method << file;
proxy->callMethod(method);
}
}
void DBusAPISender::setPitch(double value) {
if (connected) {
proxy->setProperty("pitch").onInterface(DBusAPI::interfaceName).toValue(value);
}
}
void DBusAPISender::setSpeed(double value) {
if (connected) {
proxy->setProperty("speed").onInterface(DBusAPI::interfaceName).toValue(value);
}
}
void DBusAPISender::setTempo(double value) {
if (connected) {
proxy->setProperty("tempo").onInterface(DBusAPI::interfaceName).toValue(value);
}
}
double DBusAPISender::getPitch() {
if (connected) {
return proxy->getProperty("pitch").onInterface(DBusAPI::interfaceName);
} else {
return -1.0;
}
}
double DBusAPISender::getTempo() {
if (connected) {
return proxy->getProperty("tempo").onInterface(DBusAPI::interfaceName);
} else {
return -1.0;
}
}
double DBusAPISender::getSpeed() {
if (connected) {
return proxy->getProperty("speed").onInterface(DBusAPI::interfaceName);
} else {
return -1.0;
}
}
DBusAPISender::DBusAPISender() {
DEBUG.writeln("Checking and connecting to main instance...");
bool output;
try {
connection = sdbus::createSessionBusConnection();
proxy = sdbus::createProxy(*connection, DBusAPI::serviceName, DBusAPI::objectPath);
proxy->finishRegistration();
{
auto method = proxy->createMethodCall("org.freedesktop.DBus.Peer", "Ping");
proxy->callMethod(method);
}
DEBUG.writeln("Main instance found.");
auto *tmp = Create();
output = tmp == nullptr;
delete tmp;
return output;
} catch (sdbus::Error) {
connected = false;
DEBUG.writeln("Main instance not found.");
return true;
}
}
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) {
ProxyInterfaces::Start(filePath);
}
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() {
}
DBusAPISender *DBusAPISender::Create() {
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;
}
}
DBusAPISender::DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath)
: ProxyInterfaces(connection, std::move(busName), std::move(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();
}

246
dbus.hpp
View file

@ -1,31 +1,231 @@
#pragma once
#include <sdbus-c++/sdbus-c++.h>
#include <sdbus-c++/StandardInterfaces.h>
#include "assets/dbus_stub_adaptor.hpp"
#include "assets/dbus_stub_proxy.hpp"
#include "assets/mpris_stub_adaptor.hpp"
#include "playback.h"
class DBusAPI {
friend class DBusAPISender;
static const char *serviceName;
#include <mutex>
#include <optional>
#include <random>
#include <thread>
class DBusAPI;
class MprisAPI : public sdbus::AdaptorInterfaces<org::mpris::MediaPlayer2_adaptor, org::mpris::MediaPlayer2::Player_adaptor, org::mpris::MediaPlayer2::TrackList_adaptor, sdbus::Properties_adaptor> {
friend class DBusAPI;
DBusAPI *dbus_api;
std::string curPlayingObjectPath;
sdbus::IConnection &connection;
const std::string mainInterface = "org.mpris.MediaPlayer2";
const std::string playerInterface = "org.mpris.MediaPlayer2.Player";
const std::string trackInterface = "org.mpris.MediaPlayer2.TrackList";
inline void sendPropertiesChanged(const std::string interface, const std::initializer_list<std::string> properties) {
std::vector<std::string> property_vec;
for (auto property : properties) {
property_vec.push_back(property);
}
emitPropertiesChangedSignal(interface, property_vec);
}
public:
#define meta_t std::map<std::string, sdbus::Variant>
#define track_id_t sdbus::ObjectPath
const sdbus::ObjectPath playing_track_id = "/com/complecwaft/Looper/PlayingTrack";
const sdbus::ObjectPath empty_track_id = "/org/mpris/MediaPlayer2/TrackList/NoTrack";
inline void Raise() override { }
void Quit() override;
inline std::string Identity() override {
return "Looper";
}
inline std::vector<std::string> SupportedUriSchemes() override {
std::vector<std::string> output;
output.push_back("file:");
return output;
}
inline std::vector<std::string> SupportedMimeTypes() override {
std::vector<std::string> output;
output.push_back("audio/*");
return output;
}
inline void Next() override { }
inline void Previous() override { }
void Pause() override;
void PlayPause() override;
void Stop() override;
void Play() override;
void Seek(const int64_t &offset) override;
void SetPosition(const track_id_t &TrackId, const int64_t &Position) override;
void OpenUri(const std::string &Uri) override;
std::string PlaybackStatus() override;
double Rate() override;
void Rate(const double &value) override;
meta_t Metadata() override;
double Volume() override;
void Volume(const double& value) override;
int64_t Position() override;
inline double MinimumRate() override {
return 0.25;
}
inline double MaximumRate() override {
return 4.0;
}
inline bool CanGoNext() override {
return false;
}
inline bool CanGoPrevious() override {
return false;
}
bool CanPlay() override;
bool CanPause() override;
bool CanSeek() override;
inline bool CanControl() override {
return true;
}
inline bool CanRaise() override {
return false;
}
inline bool CanQuit() override {
return true;
}
inline bool HasTrackList() override {
return true;
}
std::vector<meta_t> GetTracksMetadata(const std::vector<track_id_t> &TrackIds) override;
void AddTrack(const std::string &Uri, const track_id_t &AfterTrack, const bool &SetAsCurrent) override;
void RemoveTrack(const track_id_t &TrackId) override;
void GoTo(const track_id_t &TrackId) override;
std::vector<track_id_t> Tracks() override;
bool CanEditTracks() override;
MprisAPI(sdbus::IConnection &connection, std::string objectPath, DBusAPI *dbus_api);
~MprisAPI();
};
class DBusAPI : public sdbus::AdaptorInterfaces<com::complecwaft::Looper_adaptor, com::complecwaft::Looper::Errors_adaptor, org::freedesktop::Application_adaptor> {
std::map<std::string, void*> handles;
size_t handle_idx = 0;
MprisAPI *mpris;
std::minstd_rand rand_engine;
std::deque<std::string> *get_errors_by_handle(const std::string &handle);
bool daemon;
std::atomic_bool threadExitFlag = false;
std::thread threadFunc;
sdbus::IConnection &connection;
public:
static const char *objectPath;
static const char *interfaceName;
std::unique_ptr<sdbus::IConnection> connection;
std::unique_ptr<sdbus::IObject> api;
public:
static const char *busName;
void Activate(const std::map<std::string, sdbus::Variant>& platform_data) override;
void Open(const std::vector<std::string>& uris, const std::map<std::string, sdbus::Variant>& platform_data) override;
void ActivateAction(const std::string& action_name, const std::vector<sdbus::Variant>& parameter, const std::map<std::string, sdbus::Variant>& platform_data) override;
// com.experimentalcraft.Looper
// Properties
std::string FilePath() override;
std::string FileTitle() override;
double Speed() override;
void Speed(const double &value) override;
double Tempo() override;
void Tempo(const double& value) override;
double Pitch() override;
void Pitch(const double& value) override;
double Volume() override;
void Volume(const double& value) override;
double Position() override;
void Position(const double &value) override;
double Length() override;
// Pausing and Resuming
bool Paused() override;
void Paused(const bool& value) override;
void TogglePause() override;
// Starting new Files
void Start(const std::string& path) override;
void StartByURI(const std::string& uri) override;
// Playback stopping
void Stop() override;
bool IsStopped() override;
// Handles
std::string CreateHandle() override;
void ClearHandle(const std::string& handle) override;
// com.experimentalcraft.Looper.Errors
// Popping from the queue
std::string PopFront(const std::string &handle) override;
std::string PopBack(const std::string &handle) override;
// Peeking at the values
std::string PeekFront(const std::string &handle) override;
std::string PeekBack(const std::string &handle) override;
// Getting the amount of unread errors.
uint32_t GetCount(const std::string &handle) override;
bool IsEmpty(const std::string& handle) override;
// Getting and Clearing Errors.
void Clear(const std::string &handle) override;
std::vector<std::string> PeekAll(const std::string &handle) override;
std::vector<std::string> GetAllAndClear(const std::string &handle) override;
bool IsDaemon() override;
void Quit() override;
// API
Playback *playback;
DBusAPI(Playback *playback);
void Update();
DBusAPI(Playback *playback, sdbus::IConnection &connection, std::string objectPath, bool daemon);
~DBusAPI();
static DBusAPI *Create(Playback *playback, bool daemon = false);
};
class DBusAPISender {
std::unique_ptr<sdbus::IProxy> proxy;
std::unique_ptr<sdbus::IConnection> connection;
bool connected = true;
class DBusAPISender : public Playback, public sdbus::ProxyInterfaces<com::complecwaft::Looper_proxy, com::complecwaft::Looper::Errors_proxy, org::freedesktop::Application_proxy, sdbus::Peer_proxy> {
// Cache
double length, pitch, speed, tempo, volume;
bool stopped, paused;
std::string filePath;
std::string title;
std::mutex cacheMutex;
optional<std::string> last_error;
// Handle for error handling.
std::string handle;
public:
void playFile(std::string file);
void setPitch(double pitch);
void setTempo(double tempo);
void setSpeed(double speed);
void setVolume(double volume);
double getPitch();
double getTempo();
double getSpeed();
double getVolume();
bool isOnlyInstance();
DBusAPISender();
// Public API for creating this object, and checking if it is needed.
/// @brief Checks if this is the only instance, by attempting creation and immediately deleting the created object.
/// @returns true, if this is the only instance, false otherwise.
static bool isOnlyInstance();
/// @brief Creates a proxy playback engine, if possible.
/// @returns A proxy to the main instance of the playback engine, or nullptr if there is none.
static DBusAPISender *Create();
// Signals. Protected so that they aren't seen as a proper API
protected:
void onPlaybackEngineStarted() override;
void onSpeedChanged(const double &new_speed) override;
void onTempoChanged(const double &new_tempo) override;
void onPitchChanged(const double &new_pitch) override;
void onPauseChanged(const bool &now_paused) override;
void onStopped() override;
void onErrorOccurred(const std::string &error_desc, const std::string &error_type) override;
void onSeeked(const double &to_position) override;
void onFileChanged(const std::string &path, const std::string &title) override;
// Playback API. This is the API to be used by UI frontends.
public:
std::optional<std::string> get_current_file() override;
std::optional<std::string> get_current_title() override;
inline bool is_proxy() override {
return true;
}
double GetPosition() override;
double GetLength() override;
void Seek(double position) override;
void Start(std::string filePath) override;
bool IsPaused() override;
void Pause() override;
void Stop() override;
void Update();
bool IsStopped() override;
void SetTempo(float tempo) override;
void SetPitch(float pitch) override;
void SetSpeed(float speed) override;
void SetVolume(float volume) override;
float GetTempo() override;
float GetPitch() override;
float GetSpeed() override;
float GetVolume() override;
// Constructors and destructors.
// The constructor is protected because there is a different API that should be used for creation.
protected:
DBusAPISender(sdbus::IConnection &connection, std::string busName, std::string objectPath);
public:
~DBusAPISender();
};

View file

@ -1,5 +1,7 @@
#include "options.hpp"
#include "backend.hpp"
#include "daemon_backend.hpp"
#include "proxy_backend.hpp"
#include "log.hpp"
#include "thirdparty/CLI11.hpp"
#include "data.h"
@ -23,8 +25,15 @@ int main(int argc, char **argv) {
int log_level;
std::string ui_backend_option = "";
bool full_help = false;
bool daemonize = false;
bool disable_gui = false;
bool quit = false;
bool open_window = false;
app.add_option("-l, --log-level", LogStream::log_level, "Sets the minimum log level to display in the logs.");
app.add_option("-u, --ui-backend", ui_backend_option, "Specifies which UI backend to use.");
app.add_flag("-d, --daemon", daemonize, "Daemonizes the program.");
app.add_flag("-n, --no-gui", disable_gui, "Don't open the GUI when there is a daemon and there are settings or commands for it. Ignored in daemon mode, or when no changes in state are issued.");
app.add_flag("-q, --quit", quit, "Quits an existing instance.");
try {
app.parse(args);
} catch (const CLI::ParseError &e) {
@ -34,11 +43,25 @@ int main(int argc, char **argv) {
exit(app.exit(e));
}
}
if (daemonize) {
ui_backend_option = "daemon";
}
args.clear();
args = app.remaining(false);
int new_argc = args.size();
char **new_argv = (char**)malloc(new_argc * sizeof(char*));
init_logging();
if (quit) {
DBusAPISender *sender = DBusAPISender::Create();
if (sender != nullptr) {
sender->Quit();
} else {
ERROR.writeln("Did not find existing instance to quit.");
return 1;
}
return 0;
}
{
auto looper_mit = LicenseData("Looper (MIT)", "MIT");
auto looper_gpl = LicenseData("Looper (GPL)", "GPL-3.0-or-later");
@ -73,9 +96,26 @@ int main(int argc, char **argv) {
}
DEBUG.writeln("Initializing frontends...");
init_backends();
DEBUG.writeln("Loaded frontends: ");
ProxyGlueBackend *proxy_backend = nullptr;
if ((disable_gui && !daemonize) || quit) {
if (!DBusAPISender::isOnlyInstance()) {
UIBackend::register_backend<ProxyGlueBackend>();
if (disable_gui) {
proxy_backend = UIBackend::get_backend<ProxyGlueBackend>().value_or(nullptr);
}
}
if (quit && proxy_backend == nullptr) {
proxy_backend = UIBackend::get_backend<ProxyGlueBackend>().value_or(nullptr);
}
}
if (daemonize) {
UIBackend::register_backend<DaemonGlueBackend>();
}
for (auto kv : UIBackend::backends) {
kv.second->add_licenses();
}
DEBUG.writeln("Loaded frontends: ");
for (auto kv : UIBackend::backends) {
DEBUG.writefln(" - '%s'", kv.first.c_str());
}
DEBUG.writeln("Loading options file...");
@ -88,12 +128,26 @@ int main(int argc, char **argv) {
return -1;
} else {
DEBUG.writefln("Using backend: '%s'...", backend->get_id().c_str());
UIBackend::running_ui_backend = backend;
if (full_help) {
args.clear();
args.push_back("--help");
}
try {
if (proxy_backend != nullptr && !quit) {
if (!proxy_backend->run(args, new_argc, new_argv)) {
throw 0;
}
proxy_backend = nullptr;
}
if (!quit) {
UIBackend::unregister_backend<ProxyGlueBackend>();
}
output = backend->run(args, new_argc, new_argv);
if (quit && proxy_backend != nullptr) {
proxy_backend->quitDaemon();
proxy_backend->unregister_self();
}
} catch (int return_code) {
if (full_help) {
std::string helpstr = app.help();

View file

@ -10,6 +10,7 @@
#endif
#include "log.hpp"
#include <filesystem>
#include "dbus.hpp"
using namespace std::chrono;
size_t CalculateBufSize(SDL_AudioSpec *obtained, double seconds, double max_seconds, size_t samples_override = 0) {
return ((((samples_override == 0) ? obtained->samples : samples_override) * std::min(seconds, max_seconds)) + 1) * sizeof(SAMPLETYPE) * obtained->channels;
@ -69,9 +70,7 @@ Mix_Music *PlaybackInstance::Load(const char *file) {
Mix_Music *output = Mix_LoadMUS(file);
if (output == nullptr) {
ERROR.writefln("Error loading music '%s': %s", file, Mix_GetError());
error_mutex.lock();
errors.emplace("Error loading music!");
error_mutex.unlock();
set_error("Error loading music!");
return nullptr;
}
Mix_PlayMusicStream(output, -1);
@ -129,9 +128,7 @@ void PlaybackInstance::ThreadFunc() {
if (!SDL_WasInit(SDL_INIT_AUDIO)) {
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
ERROR.writefln("Error initializing SDL: '%s'", SDL_GetError());
error_mutex.lock();
errors.emplace("Failed to initialize SDL!");
error_mutex.unlock();
set_error("Failed to initialize SDL!");
return;
}
}
@ -152,9 +149,7 @@ void PlaybackInstance::ThreadFunc() {
Mix_Init(MIX_INIT_FLAC|MIX_INIT_MID|MIX_INIT_MOD|MIX_INIT_MP3|MIX_INIT_OGG|MIX_INIT_OPUS|MIX_INIT_WAVPACK);
if ((device = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE|SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) {
ERROR.writefln("Error opening audio device: '%s'", SDL_GetError());
error_mutex.lock();
errors.emplace("Failed to open audio device!");
error_mutex.unlock();
set_error("Failed to open audio device!");
running = false;
break;
}
@ -171,9 +166,7 @@ void PlaybackInstance::ThreadFunc() {
buf = (Uint8*)malloc(new_bufsize);
if (buf == nullptr) {
ERROR.writeln("Failed to allocate memory for playback!");
error_mutex.lock();
errors.emplace("Failed to allocate memory for playback!");
error_mutex.unlock();
set_error("Failed to allocate memory for playback!");
set_signal(PlaybackSignalErrorOccurred);
running = false;
break;
@ -234,9 +227,7 @@ void PlaybackInstance::ThreadFunc() {
buf = (Uint8*)realloc((void*)buf, correct_buf_size);
if (buf == nullptr) {
ERROR.writes("Failed to allocate memory for playback!");
error_mutex.lock();
errors.emplace("Failed to allocate memory for playback!");
error_mutex.unlock();
set_error("Failed to allocate memory for playback!");
set_signal(PlaybackSignalErrorOccurred);
running = false;
break;
@ -359,38 +350,55 @@ void PlaybackInstance::Update() {
bool PlaybackInstance::IsStopped() {
return !running;
}
optional<std::string> PlaybackInstance::GetError() {
if (ErrorExists()) {
error_mutex.lock();
std::string error = errors.back();
errors.pop();
error_mutex.unlock();
return error;
} else {
return {};
}
}
bool PlaybackInstance::ErrorExists() {
error_mutex.lock();
bool output = !errors.empty();
error_mutex.unlock();
return output;
}
void PlaybackInstance::set_signal(uint16_t signal) {
void Playback::set_signal(uint16_t signal) {
signal_mutex.lock();
signals_occurred |= signal;
for (auto &kv : signals_occurred) {
kv.second |= signal;
}
signal_mutex.unlock();
}
uint16_t PlaybackInstance::handle_signals(uint16_t signals) {
uint16_t Playback::handle_signals(uint16_t signals, void *handle) {
if (signal_mutex.try_lock()) {
uint16_t output = signals_occurred & signals;
signals_occurred &= ~output;
if (!signals_occurred.contains(handle)) {
signals_occurred[handle] = PlaybackSignalNone;
}
uint16_t output = signals_occurred[handle];
signals_occurred[handle] &= ~output;
signal_mutex.unlock();
return output;
} else {
return PlaybackSignalNone;
}
}
void Playback::register_handle(void *handle) {
signal_mutex.lock();
if (!signals_occurred.contains(handle)) {
signals_occurred[handle] = PlaybackSignalNone;
}
signal_mutex.unlock();
error_mutex.lock();
if (!errors_occurred.contains(handle)) {
std::deque<std::string> new_value;
for (size_t i = 0; i < errors.size(); i++) {
new_value.push_back(errors[i]);
}
errors_occurred[handle] = new_value;
}
error_mutex.unlock();
}
void Playback::unregister_handle(void *handle) {
signal_mutex.lock();
if (signals_occurred.contains(handle)) {
signals_occurred.erase(handle);
}
signal_mutex.unlock();
error_mutex.lock();
if (errors_occurred.contains(handle)) {
errors_occurred.erase(handle);
}
error_mutex.unlock();
}
void PlaybackInstance::SetTempo(float tempo) {
this->tempo = tempo;
Update();
@ -419,3 +427,32 @@ float PlaybackInstance::GetSpeed() {
float PlaybackInstance::GetVolume() {
return volume;
}
void Playback::set_error(std::string desc) {
error_mutex.lock();
errors.push_back(desc);
for (auto &kv : errors_occurred) {
kv.second.push_front(desc);
}
error_mutex.unlock();
set_signal(PlaybackSignalErrorOccurred);
}
Playback *Playback::Create(bool *daemon_found, bool daemon) {
auto *dbus_proxy = DBusAPISender::Create();
if (dbus_proxy != nullptr) {
if (daemon_found != nullptr) {
*daemon_found = dbus_proxy->IsDaemon();
}
DEBUG.writefln("DBus proxy daemon found: %s", *daemon_found ? "true" : "false");
if (daemon) {
delete dbus_proxy;
return nullptr;
} else {
return dbus_proxy;
}
}
if (daemon_found != nullptr) {
*daemon_found = false;
}
DEBUG.writeln("Creating new playback instance.");
return new PlaybackInstance();
}

View file

@ -11,109 +11,166 @@
#include <optional>
#include <vector>
#include <queue>
#include <deque>
#include <map>
using namespace soundtouch;
using std::span;
using std::optional;
using std::vector;
using std::queue;
using std::deque;
enum {
/// @brief No signals have occurred.
PlaybackSignalNone = 0,
/// @brief The file was changed. Recheck the properties of the file because they are likely different.
PlaybackSignalFileChanged = 1 << 0,
/// @brief The speed was changed.
PlaybackSignalSpeedChanged = 1 << 1,
/// @brief The speed was changed.
PlaybackSignalTempoChanged = 1 << 2,
/// @brief The speed was changed.
PlaybackSignalPitchChanged = 1 << 3,
/// @brief Playback was paused. If @ref PlaybackSignalResumed has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
PlaybackSignalPaused = 1 << 4,
/// @brief Playback was resumed. If @ref PlaybackSignalPaused has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
PlaybackSignalResumed = 1 << 5,
/// @brief Playback was stopped entirely. If @ref PlaybackSignalStarted has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
PlaybackSignalStopped = 1 << 6,
/// @brief An error occurred and playback has likely (but not necessarily) stopped. Call @ref Playback::GetError for details.
PlaybackSignalErrorOccurred = 1 << 7,
/// @brief Playback was seeked by the @ref Playback::Seek function
PlaybackSignalSeeked = 1 << 8,
/// @brief Playback has started. If @ref PlaybackSignalStopped has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
PlaybackSignalStarted = 1 << 9
};
/// @brief Playback handler base class.
class Playback {
protected:
friend class DBusAPI;
/// @brief The handle-specific signal state.
std::map<void*, uint16_t> signals_occurred;
/// @brief The global error state for initializing handle-specific state.
std::vector<std::string> errors;
/// @brief The handle-specific error state.
std::map<void*, std::deque<std::string>> errors_occurred;
/// @brief The mutex for signals
std::mutex signal_mutex;
/// @brief The mutex for errors
std::mutex error_mutex;
/// @brief Signals other code with the specified signal(s). Use with PlaybackSignal* enum variants.
void set_signal(uint16_t signal);
/// @brief Signals an error, and pushes details to other code.
void set_error(std::string desc);
public:
inline virtual bool is_proxy() {
return false;
}
inline Playback() {};
inline virtual ~Playback() {}
/// @brief Gets the current file, if any.
/// @returns No value if there is no file playing, or the path to the file if there is a file playing.
inline virtual std::optional<std::string> get_current_file() {
return {};
}
/// @brief Gets the current file's title, if any, for display to the user.
/// @returns No value if there is no file playing. If there is a file playing, a string is returned. The contents of the string depend on whether or not the file has a title tag. If so, the string contains the contents of that tag. Otherwise, the string contains the stem of the file's path.
inline virtual std::optional<std::string> get_current_title() {
return {};
}
/// @brief Gets the position of the playing file.
inline virtual double GetPosition() {
return 0.0;
}
/// @brief Gets the length of the file. If the length is less than or equal to zero, the file can be considered to have an unknown length.
inline virtual double GetLength() {
return 0.0;
}
/// @brief Sets the playback position into the current file.
inline virtual void Seek(double position) {}
/// @brief Plays a new file.
inline virtual void Start(std::string filePath) {}
/// @brief Checks whether or not the file is paused.
/// @returns true if playback is paused, false otherwise
inline virtual bool IsPaused() {
return true;
}
/// @brief Toggles the pause state of the playback.
inline virtual void Pause() {}
/// @brief Stops all playback.
inline virtual void Stop() {}
/// @brief Checks whether or not playback is stopped.
/// @returns true if playback is stopped, false otherwise.
inline virtual bool IsStopped() {
return true;
}
/// @brief Sets the tempo multiplier of the playback.
inline virtual void SetTempo(float tempo) {}
/// @brief Sets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
inline virtual void SetSpeed(float speed) {}
/// @brief Sets the pitch multiplier of the playback
inline virtual void SetPitch(float pitch) {}
/// @brief Sets the volume of the playback, as a percentage
inline virtual void SetVolume(float volume) {}
/// @brief Gets the tempo multiplier of the playback.
inline virtual float GetTempo() {
return 1.0;
}
/// @brief Gets the speed multiplier (Both speed and pitch, but this is a separate property) of the playback.
inline virtual float GetSpeed() {
return 1.0;
}
/// @brief Gets the pitch multiplier of the playback.
inline virtual float GetPitch() {
return 1.0;
}
/// @brief Gets the volume of the playback, as a percentage
inline virtual float GetVolume() {
return 1.0;
}
inline virtual uint16_t handle_signals(uint16_t signal) {
return 0;
/// @brief Returns and clears signals, using a registered handle if specified.
/// @param signal The signal(s) to check for and clear.
/// @param handle The handle, which was specified to @ref Playback::register_handle , or nullptr.
uint16_t handle_signals(uint16_t signal, void *handle = nullptr);
/// @brief Registers a handle for usage in handle_signals and GetError.
void register_handle(void *handle);
/// @brief Unregisters a handle that is no longer needed.
void unregister_handle(void *handle);
/// @brief Gets the last error from the playback engine, or no value if there was none.
/// @param handle The handle as registered with @ref Playback::register_handle
inline virtual optional<std::string> GetError(void *handle) {
std::optional<std::string> output = {};
if (ErrorExists(handle)) {
error_mutex.lock();
if (errors_occurred.contains(handle)) {
output = errors_occurred[handle].back();
errors_occurred[handle].pop_back();
}
inline virtual optional<std::string> GetError() {
return {};
error_mutex.unlock();
}
inline virtual bool ErrorExists() {
return false;
return output;
}
/// @brief Checks if an unread error has occurred.
/// @param handle The handle as registered with @ref Playback::register_handle
inline virtual bool ErrorExists(void *handle) {
bool output = false;
error_mutex.lock();
if (errors_occurred.contains(handle)) {
output = !errors_occurred[handle].empty();
}
error_mutex.unlock();
return output;
}
/// @brief Helper function to set the paused status via the @ref Playback::IsPaused and @ref Playback::Pause APIs.
/// @param paused The new pause state.
inline virtual void SetPaused(bool paused) {
if (IsPaused() != paused) {
Pause();
}
}
static Playback *Create(bool *daemon_found, bool daemon = false);
};
class DBusAPISender;
class PlaybackRemote : public Playback {
private:
DBusAPISender *sender;
public:
double GetPosition() override;
double GetLength() override;
void Seek(double position) override;
void Start(std::string filePath) override;
bool IsPaused() override;
void Pause() override;
void Stop() override;
void Update();
bool IsStopped() override;
void SetTempo(float tempo) override;
void SetPitch(float pitch) override;
void SetSpeed(float speed) override;
void SetVolume(float volume) override;
float GetTempo() override;
float GetPitch() override;
float GetSpeed() override;
float GetVolume() override;
uint16_t handle_signals(uint16_t signal) override;
optional<std::string> GetError() override;
bool ErrorExists() override;
PlaybackRemote();
};
class PlaybackInstance : public Playback {
private:
std::string filePath;
@ -152,9 +209,6 @@ private:
std::mutex current_file_mutex;
std::optional<std::string> current_file;
std::optional<std::string> current_title;
uint16_t signals_occurred = PlaybackSignalNone;
std::mutex signal_mutex;
void set_signal(uint16_t signal);
float prev_pitch, prev_speed, prev_tempo;
public:
@ -162,7 +216,9 @@ public:
~PlaybackInstance() override;
std::optional<std::string> get_current_file() override;
std::optional<std::string> get_current_title() override;
inline bool is_proxy() override {
return false;
}
double GetPosition() override;
double GetLength() override;
void Seek(double position) override;
@ -191,7 +247,4 @@ public:
float MinSpeed = 0.25;
float MinPitch = 0.25;
float MinTempo = 0.25;
uint16_t handle_signals(uint16_t signal) override;
optional<std::string> GetError() override;
bool ErrorExists() override;
};

32
proxy_backend.cpp Normal file
View file

@ -0,0 +1,32 @@
#include "proxy_backend.hpp"
#include "log.hpp"
#include <thread>
#include <chrono>
using namespace std::literals::chrono_literals;
std::string ProxyGlueBackend::get_id() {
return "proxy";
}
std::string ProxyGlueBackend::get_name() {
return "Proxy Backend";
}
bool ProxyGlueBackend::allow_multi_instance() {
return false;
}
int ProxyGlueBackend::run(std::vector<std::string> realArgs, int argc, char **argv) {
bool commands_specified = parse_args(realArgs, argc, argv);
if (!commands_specified) {
DEBUG.writeln("No commands specified.");
return 1;
}
sender = DBusAPISender::Create();
if (sender == nullptr) {
ERROR.writeln("Existing instance required to send commands.");
return 1;
}
playback = (Playback*)sender;
setup_playback_args();
return 0;
}
void ProxyGlueBackend::quitDaemon() {
((DBusAPISender*)playback)->Quit();
}

11
proxy_backend.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include "backend.hpp"
class ProxyGlueBackend : public UIBackend {
DBusAPISender *sender;
public:
bool allow_multi_instance() override;
std::string get_id() override;
std::string get_name() override;
void quitDaemon();
int run(std::vector<std::string> realArgs, int argc, char **argv) override;
};