card: implement bluetooth profile codec selection
Part-of: <https://gitlab.freedesktop.org/pulseaudio/pavucontrol/-/merge_requests/54>
This commit is contained in:
parent
c3efbcea55
commit
d66137f9a8
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
// 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
|
||||
|
|
|
@ -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;
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue