diff options
author | Igor V. Kovalenko <igor.v.kovalenko@gmail.com> | 2020-12-25 17:43:15 +0300 |
---|---|---|
committer | Igor V. Kovalenko <igor.v.kovalenko@gmail.com> | 2021-02-26 14:03:20 +0300 |
commit | d66137f9a8751ac8598c9a014a27cd72072756f2 (patch) | |
tree | e06a13373ce27818991468ac9e52ae73716b98af /src | |
parent | c3efbcea55767d0a7e67e7884d1f16cd0a4401c5 (diff) |
card: implement bluetooth profile codec selection
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pavucontrol/-/merge_requests/54>
Diffstat (limited to 'src')
-rw-r--r-- | src/cardwidget.cc | 73 | ||||
-rw-r--r-- | src/cardwidget.h | 17 | ||||
-rw-r--r-- | src/devicewidget.h | 2 | ||||
-rw-r--r-- | src/mainwindow.cc | 57 | ||||
-rw-r--r-- | src/mainwindow.h | 4 | ||||
-rw-r--r-- | src/pavucontrol.cc | 154 | ||||
-rw-r--r-- | src/pavucontrol.glade | 37 | ||||
-rw-r--r-- | src/pavucontrol.h | 7 |
8 files changed, 336 insertions, 15 deletions
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<Gtk::Builder> 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<Glib::ustring,Glib::ustring> > profiles; + // each entry in profiles is a pair of profile name and profile description + std::vector<std::pair<Glib::ustring, Glib::ustring>> profiles; std::map<Glib::ustring, PortInfo> ports; Glib::ustring activeProfile; bool hasSinks; bool hasSources; + // each entry in codecs is a pair of codec name and codec description + std::vector<std::pair<Glib::ustring, Glib::ustring>> 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<Gtk::ListStore> treeModel; + Glib::RefPtr<Gtk::ListStore> profileListStore; + + ModelColumns codecModel; + + Gtk::ComboBox *codecList; + Glib::RefPtr<Gtk::ListStore> 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<Glib::ustring,Glib::ustring> > ports; + std::vector<std::pair<Glib::ustring, Glib::ustring>> 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 <pulse/message-params.h> +#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<Glib::ustring,Glib::ustring>(profileIt->name, desc)); + w->profiles.push_back(std::pair<Glib::ustring, Glib::ustring>(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<std::string, std::string>& 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<Glib::ustring, Glib::ustring>(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<pa_sink_port_info>::iterator i = port_priorities.begin(); i != port_priorities.end(); ++i) - w->ports.push_back(std::pair<Glib::ustring,Glib::ustring>(i->name, i->description)); + w->ports.push_back(std::pair<Glib::ustring, Glib::ustring>(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<pa_source_port_info>::iterator i = port_priorities.begin(); i != port_priorities.end(); ++i) - w->ports.push_back(std::pair<Glib::ustring,Glib::ustring>(i->name, i->description)); + w->ports.push_back(std::pair<Glib::ustring, Glib::ustring>(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 <pulse/ext-device-restore.h> #endif +#include <unordered_map> + 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<std::string, std::string>& 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 <pulse/pulseaudio.h> #include <pulse/ext-stream-restore.h> #include <pulse/ext-device-manager.h> +#ifdef HAVE_PULSE_MESSAGING_API +#include <pulse/message-params.h> +#endif #include <canberra-gtk.h> @@ -42,6 +45,12 @@ #include "mainwindow.h" #include "pavuapplication.h" +#include <unordered_map> +#include <utility> +#include <memory> + +using WindowAndCardName = std::pair<MainWindow*, std::string>; + 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<WindowAndCardName>(reinterpret_cast<WindowAndCardName*>(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<std::string, std::string> 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<WindowAndCardName>(reinterpret_cast<WindowAndCardName*>(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<typename U> 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<WindowAndCardName>(reinterpret_cast<WindowAndCardName*>(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<std::string, std::string> 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<MainWindow*>(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 @@ <property name="position">1</property> </packing> </child> + <child> + <object class="GtkHBox" id="codecBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel" id="label51"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes"><b>Codec:</b></property> + <property name="use_markup">True</property> + <property name="xalign">0</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkComboBox" id="codecList"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> </object> <packing> <property name="expand">False</property> 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 <string> + #include <signal.h> #include <string.h> @@ -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 |