mpris mostly works, still need to init the state though
This commit is contained in:
@@ -27,8 +27,8 @@ class Bar : public Gtk::Window {
|
||||
|
||||
Clock clock;
|
||||
Date date;
|
||||
WebWidget homeAssistant{"\ue88a", "Home Assistant", "https://home.rivercry.com"};
|
||||
ControlCenter controlCenter{"\ue88a", "Control Center"};
|
||||
WebWidget homeAssistant{"\uf024", "Home Assistant", "https://home.rivercry.com"};
|
||||
ControlCenter controlCenter{"\ue062", "Control Center"};
|
||||
|
||||
std::shared_ptr<TrayWidget> trayWidget = nullptr;
|
||||
std::shared_ptr<VolumeWidget> volumeWidget = nullptr;
|
||||
|
||||
@@ -16,6 +16,7 @@ struct MprisPlayer2Message {
|
||||
std::string title;
|
||||
std::vector<std::string> artist;
|
||||
std::string artwork_url;
|
||||
std::string track_id;
|
||||
int64_t length_ms;
|
||||
|
||||
std::function<void()> play_pause;
|
||||
|
||||
@@ -26,10 +26,13 @@ class MprisController {
|
||||
void next_song();
|
||||
void previous_song();
|
||||
void emit_seeked(int64_t position_us);
|
||||
void set_position(const std::string &track_id, int64_t position_us);
|
||||
|
||||
sigc::signal<void(const MprisPlayer2Message &)> &signal_mpris_updated();
|
||||
sigc::signal<void(PlaybackStatus)> &signal_playback_status_changed();
|
||||
sigc::signal<void(int64_t)> &signal_playback_position_changed();
|
||||
sigc::signal<void(const std::string &)> &signal_player_registered();
|
||||
sigc::signal<void(const std::string &)> &signal_player_deregistered();
|
||||
|
||||
private:
|
||||
MprisController();
|
||||
@@ -43,13 +46,22 @@ class MprisController {
|
||||
|
||||
Glib::RefPtr<Gio::DBus::Connection> m_connection;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> m_proxy;
|
||||
Glib::RefPtr<Gio::DBus::Proxy> m_dbus_proxy;
|
||||
std::string m_player_bus_name = "org.mpris.MediaPlayer2.spotify";
|
||||
|
||||
sigc::signal<void(const MprisPlayer2Message &)> mprisUpdatedSignal;
|
||||
sigc::signal<void(PlaybackStatus)> playbackStatusChangedSignal;
|
||||
sigc::signal<void(int64_t)> playbackPositionChangedSignal;
|
||||
sigc::signal<void(const std::string &)> playerRegisteredSignal;
|
||||
sigc::signal<void(const std::string &)> playerDeregisteredSignal;
|
||||
|
||||
void on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &result);
|
||||
void signalNotification();
|
||||
void on_dbus_signal(const Glib::ustring &sender_name,
|
||||
const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters);
|
||||
void handle_player_registered(const std::string &bus_name);
|
||||
void handle_player_deregistered(const std::string &bus_name);
|
||||
|
||||
// Called when the song changes
|
||||
void on_properties_changed(const Gio::DBus::Proxy::MapChangedProperties &changed_properties,
|
||||
|
||||
@@ -19,6 +19,8 @@ class MediaControlWidget : public Gtk::Box {
|
||||
int64_t currentPositionUs = 0;
|
||||
int64_t totalLengthUs = 0;
|
||||
sigc::connection seekTimerConnection;
|
||||
bool suppressSeekSignal = false;
|
||||
std::string currentTrackId;
|
||||
|
||||
void setCurrentPosition(int64_t position_us);
|
||||
void setTotalLength(int64_t length_us);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "gdkmm/monitor.h"
|
||||
#include "gtkmm/window.h"
|
||||
|
||||
#define DEFAULT_NOTIFICATION_TIMEOUT 5000
|
||||
#define DEFAULT_NOTIFICATION_TIMEOUT 6700
|
||||
|
||||
|
||||
class BaseNotification : public Gtk::Window {
|
||||
|
||||
@@ -21,6 +21,11 @@ window {
|
||||
font-family: var(--text-font);
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: var(--icon-font-material);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
popover {
|
||||
margin-top: 4px;
|
||||
font-family: var(--text-font);
|
||||
|
||||
@@ -5,6 +5,8 @@ Popover::Popover(const std::string icon, std::string name): Button(icon) {
|
||||
|
||||
set_name(name);
|
||||
|
||||
this->add_css_class("material-icons");
|
||||
|
||||
popover = new Gtk::Popover();
|
||||
popover->set_parent(*this);
|
||||
popover->set_autohide(true);
|
||||
|
||||
@@ -32,6 +32,14 @@ sigc::signal<void(int64_t)> &MprisController::signal_playback_position_changed()
|
||||
return playbackPositionChangedSignal;
|
||||
}
|
||||
|
||||
sigc::signal<void(const std::string &)> &MprisController::signal_player_registered() {
|
||||
return playerRegisteredSignal;
|
||||
}
|
||||
|
||||
sigc::signal<void(const std::string &)> &MprisController::signal_player_deregistered() {
|
||||
return playerDeregisteredSignal;
|
||||
}
|
||||
|
||||
void MprisController::on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &result) {
|
||||
if (!result) {
|
||||
spdlog::error("DBus Connection Error: null async result");
|
||||
@@ -40,22 +48,32 @@ void MprisController::on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &res
|
||||
try {
|
||||
m_connection = Gio::DBus::Connection::get_finish(result);
|
||||
|
||||
std::string player_bus_name = "org.mpris.MediaPlayer2.spotify";
|
||||
if (!m_dbus_proxy) {
|
||||
m_dbus_proxy = Gio::DBus::Proxy::create_sync(
|
||||
m_connection,
|
||||
"org.freedesktop.DBus",
|
||||
"/org/freedesktop/DBus",
|
||||
"org.freedesktop.DBus");
|
||||
|
||||
m_proxy = Gio::DBus::Proxy::create_sync(
|
||||
m_connection,
|
||||
player_bus_name, // The Bus Name
|
||||
"/org/mpris/MediaPlayer2", // The Object Path
|
||||
"org.mpris.MediaPlayer2.Player" // The Interface
|
||||
);
|
||||
if (m_dbus_proxy) {
|
||||
m_dbus_proxy->signal_signal().connect(
|
||||
sigc::mem_fun(*this, &MprisController::on_dbus_signal));
|
||||
|
||||
if (m_proxy) {
|
||||
spdlog::info("Connected to: {}", player_bus_name);
|
||||
try {
|
||||
auto list_names_result = m_dbus_proxy->call_sync("ListNames");
|
||||
auto names_variant = Glib::VariantBase::cast_dynamic<
|
||||
Glib::Variant<std::vector<Glib::ustring>>>(list_names_result.get_child(0));
|
||||
|
||||
signalNotification();
|
||||
|
||||
m_proxy->signal_properties_changed().connect(
|
||||
sigc::mem_fun(*this, &MprisController::on_properties_changed));
|
||||
for (const auto &name : names_variant.get()) {
|
||||
const std::string bus_name = name;
|
||||
if (bus_name.rfind("org.mpris.MediaPlayer2.", 0) == 0) {
|
||||
handle_player_registered(bus_name);
|
||||
}
|
||||
}
|
||||
} catch (const Glib::Error &ex) {
|
||||
spdlog::error("DBus ListNames Error: {}", ex.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (const Glib::Error &ex) {
|
||||
@@ -63,6 +81,66 @@ void MprisController::on_bus_connected(const Glib::RefPtr<Gio::AsyncResult> &res
|
||||
}
|
||||
}
|
||||
|
||||
void MprisController::on_dbus_signal(const Glib::ustring &,
|
||||
const Glib::ustring &signal_name,
|
||||
const Glib::VariantContainerBase ¶meters) {
|
||||
if (signal_name != "NameOwnerChanged") {
|
||||
return;
|
||||
}
|
||||
|
||||
auto name_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(parameters.get_child(0));
|
||||
auto old_owner_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(parameters.get_child(1));
|
||||
auto new_owner_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(parameters.get_child(2));
|
||||
|
||||
const std::string bus_name = name_var.get();
|
||||
if (bus_name.rfind("org.mpris.MediaPlayer2.", 0) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool had_owner = !old_owner_var.get().empty();
|
||||
const bool has_owner = !new_owner_var.get().empty();
|
||||
|
||||
if (!had_owner && has_owner) {
|
||||
handle_player_registered(bus_name);
|
||||
} else if (had_owner && !has_owner) {
|
||||
handle_player_deregistered(bus_name);
|
||||
}
|
||||
}
|
||||
|
||||
void MprisController::handle_player_registered(const std::string &bus_name) {
|
||||
spdlog::info("MPRIS player registered: {}", bus_name);
|
||||
|
||||
if (bus_name == m_player_bus_name && !m_proxy && m_connection) {
|
||||
try {
|
||||
m_proxy = Gio::DBus::Proxy::create_sync(
|
||||
m_connection,
|
||||
m_player_bus_name,
|
||||
"/org/mpris/MediaPlayer2",
|
||||
"org.mpris.MediaPlayer2.Player");
|
||||
|
||||
if (m_proxy) {
|
||||
m_proxy->signal_properties_changed().connect(
|
||||
sigc::mem_fun(*this, &MprisController::on_properties_changed));
|
||||
signalNotification();
|
||||
}
|
||||
} catch (const Glib::Error &ex) {
|
||||
spdlog::error("DBus Connection Error: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
playerRegisteredSignal.emit(bus_name);
|
||||
}
|
||||
|
||||
void MprisController::handle_player_deregistered(const std::string &bus_name) {
|
||||
spdlog::info("MPRIS player deregistered: {}", bus_name);
|
||||
|
||||
if (bus_name == m_player_bus_name) {
|
||||
m_proxy.reset();
|
||||
}
|
||||
|
||||
playerDeregisteredSignal.emit(bus_name);
|
||||
}
|
||||
|
||||
void MprisController::signalNotification() {
|
||||
if (!m_proxy) {
|
||||
return;
|
||||
@@ -89,7 +167,7 @@ void MprisController::signalNotification() {
|
||||
|
||||
metadata_map = variant_dict.get();
|
||||
|
||||
std::string title, artwork_url;
|
||||
std::string title, artwork_url, track_id;
|
||||
std::vector<std::string> artist;
|
||||
|
||||
if (metadata_map.count("xesam:title")) {
|
||||
@@ -124,6 +202,17 @@ void MprisController::signalNotification() {
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata_map.count("mpris:trackid")) {
|
||||
const auto &track_base = metadata_map["mpris:trackid"];
|
||||
if (track_base.is_of_type(Glib::VariantType("o"))) {
|
||||
auto track_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(track_base);
|
||||
track_id = track_var.get();
|
||||
} else if (track_base.is_of_type(Glib::VariantType("s"))) {
|
||||
auto track_var = Glib::VariantBase::cast_dynamic<Glib::Variant<Glib::ustring>>(track_base);
|
||||
track_id = track_var.get();
|
||||
}
|
||||
}
|
||||
|
||||
int64_t length_us = 0;
|
||||
if (metadata_map.count("mpris:length")) {
|
||||
const auto &length_base = metadata_map["mpris:length"];
|
||||
@@ -140,10 +229,11 @@ void MprisController::signalNotification() {
|
||||
mpris.title = StringHelper::trimToSize(title, 30);
|
||||
mpris.artist = artist;
|
||||
mpris.artwork_url = artwork_url;
|
||||
mpris.track_id = track_id;
|
||||
mpris.play_pause = [this]() { this->toggle_play(); };
|
||||
mpris.next = [this]() { this->next_song(); };
|
||||
mpris.previous = [this]() { this->previous_song(); };
|
||||
mpris.length_ms = length_us; // Convert microseconds to milliseconds
|
||||
mpris.length_ms = length_us / 1000; // Convert microseconds to milliseconds
|
||||
mprisUpdatedSignal.emit(mpris);
|
||||
}
|
||||
|
||||
@@ -199,6 +289,8 @@ void MprisController::next_song() {
|
||||
}
|
||||
|
||||
void MprisController::emit_seeked(int64_t position_us) {
|
||||
spdlog::debug("Seeking to position: {}", position_us);
|
||||
|
||||
if (!m_proxy) {
|
||||
return;
|
||||
}
|
||||
@@ -212,3 +304,20 @@ void MprisController::emit_seeked(int64_t position_us) {
|
||||
spdlog::error("Error seeking: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void MprisController::set_position(const std::string &track_id, int64_t position_us) {
|
||||
if (!m_proxy || track_id.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Glib::VariantContainerBase params = Glib::VariantContainerBase::create_tuple({
|
||||
Glib::Variant<Glib::DBusObjectPathString>::create(track_id),
|
||||
Glib::Variant<gint64>::create(position_us)
|
||||
});
|
||||
|
||||
m_proxy->call("SetPosition", params);
|
||||
} catch (const Glib::Error &ex) {
|
||||
spdlog::error("Error setting position: {}", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,10 +60,20 @@ MediaControlWidget::MediaControlWidget()
|
||||
this->seekBar.add_css_class("control-center-seek-bar");
|
||||
|
||||
this->seekBar.signal_value_changed().connect([this]() {
|
||||
if (this->suppressSeekSignal || this->totalLengthUs <= 0) {
|
||||
return;
|
||||
}
|
||||
double fraction = this->seekBar.get_value() / 100.0;
|
||||
int64_t new_position_us =
|
||||
static_cast<int64_t>(fraction * static_cast<double>(this->totalLengthUs));
|
||||
this->mprisController->emit_seeked(new_position_us - this->currentPositionUs); // in us
|
||||
if (new_position_us == this->currentPositionUs) {
|
||||
return;
|
||||
}
|
||||
if (!this->currentTrackId.empty()) {
|
||||
this->mprisController->set_position(this->currentTrackId, new_position_us);
|
||||
} else {
|
||||
this->mprisController->emit_seeked(new_position_us - this->currentPositionUs); // in us
|
||||
}
|
||||
this->resetSeekTimer(new_position_us);
|
||||
});
|
||||
|
||||
@@ -126,12 +136,13 @@ void MediaControlWidget::onSpotifyMprisUpdated(const MprisPlayer2Message &messag
|
||||
}
|
||||
this->artistLabel.set_text(artistText);
|
||||
this->titleLabel.set_text(message.title);
|
||||
this->currentTrackId = message.track_id;
|
||||
|
||||
if (auto texture = TextureCacheService::getInstance()->getTexture(message.artwork_url)) {
|
||||
this->backgroundImage.set_paintable(texture);
|
||||
}
|
||||
|
||||
this->setTotalLength(message.length_ms);
|
||||
this->setTotalLength(message.length_ms * 1000);
|
||||
this->setCurrentPosition(0);
|
||||
this->resetSeekTimer(0);
|
||||
}
|
||||
@@ -144,7 +155,9 @@ void MediaControlWidget::setCurrentPosition(int64_t position_us) {
|
||||
std::to_string(minutes) + ":" + (seconds < 10 ? "0" : "") + std::to_string(seconds));
|
||||
if (totalLengthUs > 0) {
|
||||
double fraction = static_cast<double>(currentPositionUs) / static_cast<double>(totalLengthUs);
|
||||
this->suppressSeekSignal = true;
|
||||
this->seekBar.set_value(fraction * 100);
|
||||
this->suppressSeekSignal = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,11 @@ SpotifyNotification::SpotifyNotification(uint64_t notificationId, std::shared_pt
|
||||
title_label->set_ellipsize(Pango::EllipsizeMode::END);
|
||||
|
||||
auto artistLabel = Gtk::make_managed<Gtk::Label>();
|
||||
artistLabel->set_text(StringHelper::trimToSize(mpris.artist[0], 30));
|
||||
if (!mpris.artist.empty()) {
|
||||
artistLabel->set_text(StringHelper::trimToSize(mpris.artist[0], 30));
|
||||
} else {
|
||||
artistLabel->set_text("Unknown Artist");
|
||||
}
|
||||
artistLabel->set_hexpand(true);
|
||||
artistLabel->set_halign(Gtk::Align::CENTER);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user