summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIgor V. Kovalenko <igor.v.kovalenko@gmail.com>2020-12-25 17:43:15 +0300
committerIgor V. Kovalenko <igor.v.kovalenko@gmail.com>2021-02-26 14:03:20 +0300
commitd66137f9a8751ac8598c9a014a27cd72072756f2 (patch)
treee06a13373ce27818991468ac9e52ae73716b98af /src
parentc3efbcea55767d0a7e67e7884d1f16cd0a4401c5 (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.cc73
-rw-r--r--src/cardwidget.h17
-rw-r--r--src/devicewidget.h2
-rw-r--r--src/mainwindow.cc57
-rw-r--r--src/mainwindow.h4
-rw-r--r--src/pavucontrol.cc154
-rw-r--r--src/pavucontrol.glade37
-rw-r--r--src/pavucontrol.h7
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">&lt;b&gt;Codec:&lt;/b&gt;</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