summaryrefslogtreecommitdiff
path: root/src/bt.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2025-10-20 21:29:01 +0200
committerJoel Klinghed <the_jk@spawned.biz>2025-10-20 21:29:01 +0200
commite8dc8edad7cdf194091f0479b70b154e872f57ef (patch)
tree5431680ce100812b9b3bea32a7847e7dcfdcf29d /src/bt.cc
parent0687ec31d1d75500beaee0e983ebf73d7c4517f7 (diff)
bt & main: Add optional player for device
Diffstat (limited to 'src/bt.cc')
-rw-r--r--src/bt.cc288
1 files changed, 286 insertions, 2 deletions
diff --git a/src/bt.cc b/src/bt.cc
index 5675df2..ad05a87 100644
--- a/src/bt.cc
+++ b/src/bt.cc
@@ -3,6 +3,7 @@
#include "cfg.hh"
#include "logger.hh"
#include "looper.hh"
+#include "sdbuscpp_no_throw_helper.hh"
#include <chrono>
#include <cstddef>
@@ -40,11 +41,27 @@ sdbus::Message& operator<<(sdbus::Message& msg, const Void& /* ignored */) {
return msg;
}
+struct MediaPlayerTrack {
+ std::string Title;
+ std::string Artist;
+ std::string Album;
+ std::string Genre;
+ uint32_t NumberOfTracks;
+ uint32_t Duration;
+ std::string ImgHandle;
+};
+
} // namespace
template <>
struct sdbus::signature_of<Void> : sdbus::signature_of<void> {};
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-function"
+SDBUSCPP_REGISTER_STRUCT_NO_THROW(MediaPlayerTrack, Title, Artist, Album, Genre,
+ NumberOfTracks, Duration, ImgHandle);
+#pragma clang diagnostic pop
+
namespace bt {
namespace {
@@ -71,9 +88,19 @@ class IManager {
virtual void update_adapter(sdbus::ObjectPath const& path) = 0;
virtual void update_device(sdbus::ObjectPath const& path) = 0;
+ virtual void update_player(sdbus::ObjectPath const& path) = 0;
+
+ virtual void add_player(
+ sdbus::ObjectPath const& path,
+ std::map<sdbus::PropertyName, sdbus::Variant> const& properties) = 0;
+ virtual void remove_player(sdbus::ObjectPath const& path) = 0;
+ [[nodiscard]]
virtual std::vector<Device*> get_devices(
- sdbus::ObjectPath const& adapter_path) = 0;
+ sdbus::ObjectPath const& adapter_path) const = 0;
+
+ [[nodiscard]]
+ virtual Player* get_player(sdbus::ObjectPath const& player_path) const = 0;
virtual void agent_request_pincode(
sdbus::ObjectPath const& device,
@@ -104,6 +131,7 @@ class IManager {
const sdbus::ServiceName kService{"org.bluez"};
const sdbus::InterfaceName kDeviceInterface{"org.bluez.Device1"};
const sdbus::InterfaceName kAdapterInterface{"org.bluez.Adapter1"};
+const sdbus::InterfaceName kMediaPlayerInterface{"org.bluez.MediaPlayer1"};
const sdbus::InterfaceName kAgentInterface{"org.bluez.Agent1"};
const sdbus::InterfaceName kAgentManagerInterface{"org.bluez.AgentManager1"};
const sdbus::InterfaceName kPropertiesInterface{
@@ -195,6 +223,10 @@ class ObjectManagerProxy {
if (it != interfaces_and_properties.end()) {
manager_.add_agent_manager(object_path);
}
+ it = interfaces_and_properties.find(kMediaPlayerInterface);
+ if (it != interfaces_and_properties.end()) {
+ manager_.add_player(object_path, it->second);
+ }
}
void interfaces_removed(sdbus::ObjectPath const& object_path,
@@ -209,6 +241,8 @@ class ObjectManagerProxy {
manager_.remove_adapter(object_path);
} else if (interface == kAgentManagerInterface) {
manager_.remove_agent_manager(object_path);
+ } else if (interface == kMediaPlayerInterface) {
+ manager_.remove_player(object_path);
}
}
}
@@ -331,10 +365,24 @@ class DeviceProxy : public BaseProxy, public Device {
}
[[nodiscard]]
+ Player* player() const override {
+ return manager_.get_player(primary_player_);
+ }
+
+ [[nodiscard]]
sdbus::ObjectPath const& adapter() const {
return adapter_;
}
+ [[nodiscard]]
+ sdbus::ObjectPath const& primary_player() const {
+ return primary_player_;
+ }
+
+ void set_primary_player(sdbus::ObjectPath const& path) {
+ primary_player_ = path;
+ }
+
private:
void update(size_t field, sdbus::Variant const& value) override {
switch (field) {
@@ -376,6 +424,7 @@ class DeviceProxy : public BaseProxy, public Device {
bool connected_{false};
bool paired_{false};
sdbus::ObjectPath adapter_;
+ sdbus::ObjectPath primary_player_;
};
class AdapterProxy : public BaseProxy, public Adapter {
@@ -550,6 +599,157 @@ class AdapterProxy : public BaseProxy, public Adapter {
uint32_t pairable_timeout_{0};
};
+inline bool isActive(Player::Status status) {
+ switch (status) {
+ case Player::Status::kPlaying:
+ case Player::Status::kForwardSeek:
+ case Player::Status::kReverseSeek:
+ return true;
+ case Player::Status::kStopped:
+ case Player::Status::kPaused:
+ case Player::Status::kError:
+ return false;
+ }
+ std::unreachable();
+}
+
+class MediaPlayerProxy : public BaseProxy, public Player {
+ private:
+ static inline const std::map<sdbus::PropertyName, size_t> kNames{
+ {sdbus::PropertyName{"Status"}, 0},
+ {sdbus::PropertyName{"Track"}, 1},
+ {sdbus::PropertyName{"Device"}, 2},
+ {sdbus::PropertyName{"Name"}, 3},
+ };
+
+ public:
+ MediaPlayerProxy(
+ IManager& manager, sdbus::IConnection& connection,
+ sdbus::ObjectPath const& path,
+ std::map<sdbus::PropertyName, sdbus::Variant> const& properties)
+ : BaseProxy(connection, path, kMediaPlayerInterface, kNames),
+ manager_(manager) {
+ init(properties);
+ }
+
+ [[nodiscard]]
+ Status status() const override {
+ return status_;
+ }
+
+ [[nodiscard]]
+ std::string const& track_title() const override {
+ return track_.Title;
+ }
+
+ [[nodiscard]]
+ std::string const& track_artist() const override {
+ return track_.Artist;
+ }
+
+ [[nodiscard]]
+ std::string const& track_album() const override {
+ return track_.Album;
+ }
+
+ void play() override {
+ switch (status_) {
+ case Status::kPlaying:
+ return;
+ case Status::kStopped:
+ case Status::kPaused:
+ case Status::kForwardSeek:
+ case Status::kReverseSeek:
+ case Status::kError:
+ break;
+ }
+
+ getProxy()
+ .callMethodAsync("Play")
+ .onInterface(kMediaPlayerInterface)
+ .uponReplyInvoke([this](std::optional<sdbus::Error> err) {
+ if (err.has_value()) {
+ manager_.logger().err(std::format("{}: Play {}: {}", name_,
+ std::string(err->getName()),
+ err->getMessage()));
+ }
+ });
+ }
+
+ void pause() override {
+ switch (status_) {
+ case Status::kStopped:
+ case Status::kPaused:
+ return;
+ case Status::kPlaying:
+ case Status::kForwardSeek:
+ case Status::kReverseSeek:
+ case Status::kError:
+ break;
+ }
+
+ getProxy()
+ .callMethodAsync("Pause")
+ .onInterface(kMediaPlayerInterface)
+ .uponReplyInvoke([this](std::optional<sdbus::Error> err) {
+ if (err.has_value()) {
+ manager_.logger().err(std::format("{}: Pause {}: {}", name_,
+ std::string(err->getName()),
+ err->getMessage()));
+ }
+ });
+ }
+
+ [[nodiscard]]
+ sdbus::ObjectPath const& device() const {
+ return device_;
+ }
+
+ private:
+ void update(size_t field, sdbus::Variant const& value) override {
+ switch (field) {
+ case 0: {
+ auto tmp = value.get<std::string>();
+ if (tmp == "playing") {
+ status_ = Status::kPlaying;
+ } else if (tmp == "stopped") {
+ status_ = Status::kStopped;
+ } else if (tmp == "paused") {
+ status_ = Status::kPaused;
+ } else if (tmp == "forward-seek") {
+ status_ = Status::kForwardSeek;
+ } else if (tmp == "reverse-seek") {
+ status_ = Status::kReverseSeek;
+ } else {
+ status_ = Status::kError;
+ }
+ break;
+ }
+ case 1:
+ track_ = value.get<MediaPlayerTrack>();
+ break;
+ case 2:
+ device_ = value.get<sdbus::ObjectPath>();
+ break;
+ case 3:
+ name_ = value.get<std::string>();
+ break;
+ default:
+ std::unreachable();
+ }
+ }
+
+ void notify_updated() override {
+ manager_.update_player(getProxy().getObjectPath());
+ }
+
+ IManager& manager_;
+ Status status_{Status::kError};
+ MediaPlayerTrack track_{};
+ sdbus::ObjectPath device_;
+ std::string name_;
+};
+
class AgentManagerProxy {
public:
AgentManagerProxy(sdbus::IConnection& connection,
@@ -799,6 +999,20 @@ class ManagerImpl : public Manager, public IManager, looper::Hook {
}
}
+ void add_player(sdbus::ObjectPath const& path,
+ std::map<sdbus::PropertyName, sdbus::Variant> const&
+ properties) override {
+ logger_.dbg(std::format("Add player: {}", std::string(path)));
+ auto [it, inserted] = players_.emplace(
+ path,
+ std::make_unique<MediaPlayerProxy>(*this, *conn_, path, properties));
+
+ auto it2 = devices_.find(it->second->device());
+ if (it2 != devices_.end()) {
+ update_device_player(*it2->second);
+ }
+ }
+
void add_agent_manager(sdbus::ObjectPath const& path) override {
logger_.dbg(std::format("Add agent manager: {}", std::string(path)));
if (agent_manager_proxy_)
@@ -835,6 +1049,19 @@ class ManagerImpl : public Manager, public IManager, looper::Hook {
}
}
+ void remove_player(sdbus::ObjectPath const& path) override {
+ logger_.dbg(std::format("Remove player: {}", std::string(path)));
+ auto it = players_.find(path);
+ if (it == players_.end())
+ return;
+
+ auto it2 = devices_.find(it->second->device());
+ players_.erase(it);
+ if (it2 != devices_.end()) {
+ update_device_player(*it2->second);
+ }
+ }
+
void remove_adapter(sdbus::ObjectPath const& path) override {
logger_.dbg(std::format("Remove adapter: {}", std::string(path)));
auto it = adapters_.find(path);
@@ -877,8 +1104,26 @@ class ManagerImpl : public Manager, public IManager, looper::Hook {
}
}
+ void update_player(sdbus::ObjectPath const& path) override {
+ logger_.dbg(std::format("Update player: {}", std::string(path)));
+
+ auto it = players_.find(path);
+ if (it == players_.end())
+ return;
+
+ auto it2 = devices_.find(it->second->device());
+ if (it2 != devices_.end()) {
+ update_device_player(*it2->second);
+
+ if (it2->second->player() == it->second.get()) {
+ delegate_.updated_player(*it2->second, *it->second);
+ }
+ }
+ }
+
+ [[nodiscard]]
std::vector<Device*> get_devices(
- sdbus::ObjectPath const& adapter_path) override {
+ sdbus::ObjectPath const& adapter_path) const override {
std::vector<Device*> ret;
ret.reserve(devices_.size());
for (auto& pair : devices_) {
@@ -890,6 +1135,17 @@ class ManagerImpl : public Manager, public IManager, looper::Hook {
}
[[nodiscard]]
+ Player* get_player(sdbus::ObjectPath const& player_path) const override {
+ if (player_path.empty())
+ return nullptr;
+
+ auto it = players_.find(player_path);
+ if (it == players_.end())
+ return nullptr;
+ return it->second.get();
+ }
+
+ [[nodiscard]]
logger::Logger& logger() override {
return logger_;
}
@@ -1033,6 +1289,33 @@ class ManagerImpl : public Manager, public IManager, looper::Hook {
delegate_.new_adapter(nullptr);
}
+ void update_device_player(DeviceProxy& device) {
+ if (device.adapter() != primary_adapter_)
+ return;
+
+ sdbus::ObjectPath primary_player;
+ for (auto& pair : players_) {
+ if (pair.second->device() == device.getProxy().getObjectPath()) {
+ if (primary_player.empty() || isActive(pair.second->status())) {
+ primary_player = pair.first;
+ if (isActive(pair.second->status()))
+ break;
+ }
+ }
+ }
+
+ if (device.primary_player() == primary_player)
+ return;
+
+ device.set_primary_player(primary_player);
+
+ if (primary_player.empty()) {
+ delegate_.removed_player(device);
+ } else {
+ delegate_.added_player(device, *device.player());
+ }
+ }
+
logger::Logger& logger_;
cfg::Config const& cfg_;
looper::Looper& looper_;
@@ -1043,6 +1326,7 @@ class ManagerImpl : public Manager, public IManager, looper::Hook {
std::unique_ptr<AgentObject> agent_;
std::map<sdbus::ObjectPath, std::unique_ptr<DeviceProxy>> devices_;
std::map<sdbus::ObjectPath, std::unique_ptr<AdapterProxy>> adapters_;
+ std::map<sdbus::ObjectPath, std::unique_ptr<MediaPlayerProxy>> players_;
sdbus::ObjectPath primary_adapter_;
};