diff --git a/configure.ac b/configure.ac index d63bb39..2ae549f 100644 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,9 @@ fi AC_SUBST(PULSE_LIBS) AC_SUBST(PULSE_CFLAGS) +AC_CHECK_LIB(pulse, pa_context_send_message_to_object, [HAVE_PULSE_MESSAGING_API=yes], [HAVE_PULSE_MESSAGING_API=no]) +AS_IF([test "x$HAVE_PULSE_MESSAGING_API" = "xyes"], AC_DEFINE([HAVE_PULSE_MESSAGING_API], 1, [Have messaging API.])) + # If using GCC specifiy some additional parameters if test "x$GCC" = "xyes" ; then CFLAGS="$CFLAGS -pipe -Wall -W -Wno-unused-parameter" diff --git a/src/cardwidget.cc b/src/cardwidget.cc index bcd53fa..a9e4727 100644 --- a/src/cardwidget.cc +++ b/src/cardwidget.cc @@ -33,12 +33,22 @@ CardWidget::CardWidget(BaseObjectType* cobject, const Glib::RefPtr x->get_widget("cardNameLabel", nameLabel); x->get_widget("profileList", profileList); x->get_widget("cardIconImage", iconImage); + x->get_widget("codecBox", codecBox); + x->get_widget("codecList", codecList); - treeModel = Gtk::ListStore::create(profileModel); - profileList->set_model(treeModel); + profileListStore = Gtk::ListStore::create(profileModel); + profileList->set_model(profileListStore); profileList->pack_start(profileModel.desc); profileList->signal_changed().connect( sigc::mem_fun(*this, &CardWidget::onProfileChange)); + + codecBox->hide(); + + codecListStore = Gtk::ListStore::create(codecModel); + codecList->set_model(codecListStore); + codecList->pack_start(codecModel.desc); + + codecList->signal_changed().connect( sigc::mem_fun(*this, &CardWidget::onCodecChange)); } CardWidget* CardWidget::create() { @@ -51,22 +61,41 @@ CardWidget* CardWidget::create() { void CardWidget::prepareMenu() { - int idx = 0; - int active_idx = -1; + int active_idx; - treeModel->clear(); + profileListStore->clear(); + active_idx = -1; /* Fill the ComboBox's Tree Model */ for (uint32_t i = 0; i < profiles.size(); ++i) { - Gtk::TreeModel::Row row = *(treeModel->append()); + Gtk::TreeModel::Row row = *(profileListStore->append()); row[profileModel.name] = profiles[i].first; row[profileModel.desc] = profiles[i].second; if (profiles[i].first == activeProfile) - active_idx = idx; - idx++; + active_idx = i; } if (active_idx >= 0) profileList->set_active(active_idx); + + codecListStore->clear(); + active_idx = -1; + /* Fill the ComboBox's Tree Model */ + for (uint32_t i = 0; i < codecs.size(); ++i) { + Gtk::TreeModel::Row row = *(codecListStore->append()); + row[codecModel.name] = codecs[i].first; + row[codecModel.desc] = codecs[i].second; + if (codecs[i].first == activeCodec) + active_idx = i; + } + + if (active_idx >= 0) + codecList->set_active(active_idx); + + /* unhide codec box */ + if (codecs.size()) + codecBox->show(); + else + codecBox->hide(); } void CardWidget::onProfileChange() { @@ -93,3 +122,31 @@ void CardWidget::onProfileChange() { } } } + +void CardWidget::onCodecChange() { + if (updating) + return; + +#ifdef HAVE_PULSE_MESSAGING_API + Gtk::TreeModel::iterator iter = codecList->get_active(); + if (iter) + { + Gtk::TreeModel::Row row = *iter; + if (row) + { + pa_operation* o; + Glib::ustring codec_id = row[codecModel.name]; + + std::string codec_message = "{" + std::string(codec_id) + "}"; + + if (!(o = pa_context_send_message_to_object(get_context(), card_bluez_message_handler_path(pulse_card_name).c_str(), + "switch-codec", codec_message.c_str(), NULL, NULL))) { + show_error(_("pa_context_set_card_profile_by_index() failed")); + return; + } + + pa_operation_unref(o); + } + } +#endif +} diff --git a/src/cardwidget.h b/src/cardwidget.h index 821aae5..98201a4 100644 --- a/src/cardwidget.h +++ b/src/cardwidget.h @@ -43,19 +43,27 @@ public: Gtk::Menu menu; Gtk::Image *iconImage; Glib::ustring name; + std::string pulse_card_name; + Gtk::Box *codecBox; uint32_t index; bool updating; - std::vector< std::pair > profiles; + // each entry in profiles is a pair of profile name and profile description + std::vector> profiles; std::map ports; Glib::ustring activeProfile; bool hasSinks; bool hasSources; + // each entry in codecs is a pair of codec name and codec description + std::vector> codecs; + Glib::ustring activeCodec; + void prepareMenu(); protected: virtual void onProfileChange(); + virtual void onCodecChange(); /* Tree model columns */ class ModelColumns : public Gtk::TreeModel::ColumnRecord @@ -72,7 +80,12 @@ protected: ModelColumns profileModel; Gtk::ComboBox *profileList; - Glib::RefPtr treeModel; + Glib::RefPtr profileListStore; + + ModelColumns codecModel; + + Gtk::ComboBox *codecList; + Glib::RefPtr codecListStore; }; #endif diff --git a/src/devicewidget.h b/src/devicewidget.h index 862834d..cb13b61 100644 --- a/src/devicewidget.h +++ b/src/devicewidget.h @@ -68,7 +68,7 @@ public: virtual void executeVolumeUpdate(); virtual void setBaseVolume(pa_volume_t v); - std::vector< std::pair > ports; + std::vector> ports; Glib::ustring activePort; void prepareMenu(); diff --git a/src/mainwindow.cc b/src/mainwindow.cc index f9cd16e..34013e7 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -35,6 +35,10 @@ #include "i18n.h" +#if defined(HAVE_PULSE_MESSAGING_API) +#include +#endif + /* Used for profile sorting */ struct profile_prio_compare { bool operator() (const pa_card_profile_info2& lhs, const pa_card_profile_info2& rhs) const { @@ -373,6 +377,8 @@ void MainWindow::updateCard(const pa_card_info &info) { w->updating = true; + w->pulse_card_name = info.name; + description = pa_proplist_gets(info.proplist, PA_PROP_DEVICE_DESCRIPTION); w->name = description ? description : info.name; gchar *txt; @@ -431,7 +437,7 @@ void MainWindow::updateCard(const pa_card_info &info) { if (!profileIt->available) desc += _(" (unavailable)"); - w->profiles.push_back(std::pair(profileIt->name, desc)); + w->profiles.push_back(std::pair(profileIt->name, desc)); } w->activeProfile = info.active_profile ? info.active_profile->name : ""; @@ -475,6 +481,51 @@ void MainWindow::updateCard(const pa_card_info &info) { w->updating = false; } +void MainWindow::updateCardCodecs(const std::string& card_name, const std::unordered_map& codecs) { + CardWidget *w = NULL; + + for (auto c : cardWidgets) { + if (card_name.compare(c.second->pulse_card_name) == 0) + w = c.second; + } + + if (!w) + return; + + w->updating = true; + + w->codecs.clear(); + + /* insert profiles */ + for (auto e : codecs) { + w->codecs.push_back(std::pair(e.first, e.second)); + } + + w->prepareMenu(); + + w->updating = false; +} + +void MainWindow::setActiveCodec(const std::string& card_name, const std::string& codec) { + CardWidget *w = NULL; + + for (auto c : cardWidgets) { + if (card_name.compare(c.second->pulse_card_name) == 0) + w = c.second; + } + + if (!w) + return; + + w->updating = true; + + w->activeCodec = codec; + + w->prepareMenu(); + + w->updating = false; +} + bool MainWindow::updateSink(const pa_sink_info &info) { SinkWidget *w; bool is_new = false; @@ -525,7 +576,7 @@ bool MainWindow::updateSink(const pa_sink_info &info) { w->ports.clear(); for (std::set::iterator i = port_priorities.begin(); i != port_priorities.end(); ++i) - w->ports.push_back(std::pair(i->name, i->description)); + w->ports.push_back(std::pair(i->name, i->description)); w->activePort = info.active_port ? info.active_port->name : ""; @@ -692,7 +743,7 @@ void MainWindow::updateSource(const pa_source_info &info) { w->ports.clear(); for (std::set::iterator i = port_priorities.begin(); i != port_priorities.end(); ++i) - w->ports.push_back(std::pair(i->name, i->description)); + w->ports.push_back(std::pair(i->name, i->description)); w->activePort = info.active_port ? info.active_port->name : ""; diff --git a/src/mainwindow.h b/src/mainwindow.h index b56cf73..50f3985 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -29,6 +29,8 @@ class MainWindow; # include #endif +#include + class CardWidget; class SinkWidget; class SourceWidget; @@ -54,6 +56,8 @@ public: #if HAVE_EXT_DEVICE_RESTORE_API void updateDeviceInfo(const pa_ext_device_restore_info &info); #endif + void updateCardCodecs(const std::string& card_name, const std::unordered_map& codecs); + void setActiveCodec(const std::string& card_name, const std::string& codec); void removeCard(uint32_t index); void removeSink(uint32_t index); diff --git a/src/pavucontrol.cc b/src/pavucontrol.cc index bcdde8e..0d74ee8 100644 --- a/src/pavucontrol.cc +++ b/src/pavucontrol.cc @@ -25,6 +25,9 @@ #include #include #include +#ifdef HAVE_PULSE_MESSAGING_API +#include +#endif #include @@ -42,6 +45,12 @@ #include "mainwindow.h" #include "pavuapplication.h" +#include +#include +#include + +using WindowAndCardName = std::pair; + static pa_context* context = NULL; static pa_mainloop_api* api = NULL; static int n_outstanding = 0; @@ -70,7 +79,145 @@ static void dec_outstanding(MainWindow *w) { } } -void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) { +#ifdef HAVE_PULSE_MESSAGING_API + +std::string card_bluez_message_handler_path(const std::string& name) { + return "/card/" + name + "/bluez"; +} + +static void context_bluetooth_card_codec_list_cb(pa_context *c, int success, char *response, void *userdata) { + auto u = std::unique_ptr(reinterpret_cast(userdata)); + + if (!success) + return; + + void *state = NULL; + char *codec_list; + char *handler_struct; + int err; + + if (pa_message_params_read_raw(response, &codec_list, &state) <= 0) { + show_error(_("list-codecs message response could not be parsed correctly")); + return; + } + + std::unordered_map codecs; + + state = NULL; + while ((err = pa_message_params_read_raw(codec_list, &handler_struct, &state)) > 0) { + void *state2 = NULL; + const char *path; + const char *description; + + if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) { + err = -1; + break; + } + if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) { + err = -1; + break; + } + + codecs[path] = description; + } + + if (err < 0) { + show_error(_("list-codecs message response could not be parsed correctly")); + codecs.clear(); + return; + } + + u->first->updateCardCodecs(u->second, codecs); +} + +static void context_bluetooth_card_active_codec_cb(pa_context *c, int success, char *response, void *userdata) { + auto u = std::unique_ptr(reinterpret_cast(userdata)); + + if (!success) + return; + + void *state = NULL; + const char *name; + + if (pa_message_params_read_string(response, &name, &state) <= 0) { + show_error(_("get-codec message response could not be parsed correctly")); + return; + } + + u->first->setActiveCodec(u->second, name); +} + +template void send_message(pa_context *c, const char *target, const char *request, pa_context_string_cb_t cb, const U& u) +{ + auto send_message_userdata = new U(u); + + pa_operation *o = pa_context_send_message_to_object(c, target, request, NULL, cb, send_message_userdata); + + if (!o) { + delete send_message_userdata; + show_error(_("pa_context_send_message_to_object() failed")); + return; + } + pa_operation_unref(o); +} + +static void context_message_handlers_cb(pa_context *c, int success, char *response, void *userdata) { + auto u = std::unique_ptr(reinterpret_cast(userdata)); + + if (!success) + return; + + void *state = NULL; + char *handler_list; + char *handler_struct; + int err; + + if (pa_message_params_read_raw(response, &handler_list, &state) <= 0) { + show_error(_("list-handlers message response could not be parsed correctly")); + return; + } + + std::unordered_map message_handler_map; + + state = NULL; + while ((err = pa_message_params_read_raw(handler_list, &handler_struct, &state)) > 0) { + void *state2 = NULL; + const char *path; + const char *description; + + if (pa_message_params_read_string(handler_struct, &path, &state2) <= 0) { + err = -1; + break; + } + if (pa_message_params_read_string(handler_struct, &description, &state2) <= 0) { + err = -1; + break; + } + + message_handler_map[path] = description; + } + + if (err < 0) { + show_error(_("list-handlers message response could not be parsed correctly")); + message_handler_map.clear(); + return; + } + + /* only send requests if card bluez message handler is registered */ + auto e = message_handler_map.find(card_bluez_message_handler_path(u->second)); + + if (e != message_handler_map.end()) { + + /* get-codec: retrieve active codec name */ + send_message(c, e->first.c_str(), "get-codec", context_bluetooth_card_active_codec_cb, *u); + + /* list-codecs: retrieve list of codecs */ + send_message(c, e->first.c_str(), "list-codecs", context_bluetooth_card_codec_list_cb, *u); + } +} +#endif + +void card_cb(pa_context *c, const pa_card_info *i, int eol, void *userdata) { MainWindow *w = static_cast(userdata); if (eol < 0) { @@ -87,6 +234,11 @@ void card_cb(pa_context *, const pa_card_info *i, int eol, void *userdata) { } w->updateCard(*i); + +#ifdef HAVE_PULSE_MESSAGING_API + /* initiate requesting bluetooth codec list */ + send_message(c, "/core", "list-handlers", context_message_handlers_cb, WindowAndCardName(w, i->name)); +#endif } #if HAVE_EXT_DEVICE_RESTORE_API diff --git a/src/pavucontrol.glade b/src/pavucontrol.glade index ed83b04..e5cf31a 100644 --- a/src/pavucontrol.glade +++ b/src/pavucontrol.glade @@ -171,6 +171,43 @@ 1 + + + True + False + 6 + + + True + False + <b>Codec:</b> + True + 0 + + + False + True + 0 + + + + + True + False + + + True + True + 1 + + + + + False + False + 2 + + False diff --git a/src/pavucontrol.h b/src/pavucontrol.h index a5ecb03..7bf7f49 100644 --- a/src/pavucontrol.h +++ b/src/pavucontrol.h @@ -21,6 +21,8 @@ #ifndef pavucontrol_h #define pavucontrol_h +#include + #include #include @@ -75,4 +77,9 @@ pa_context* get_context(void); void show_error(const char *txt); MainWindow* pavucontrol_get_window(pa_glib_mainloop *m, bool maximize, bool retry, int tab_number); + +#ifdef HAVE_PULSE_MESSAGING_API +std::string card_bluez_message_handler_path(const std::string& name); +#endif + #endif