27 #include <pulse/pulseaudio.h>
28 #include <pulse/glib-mainloop.h>
31 #include <QStringList>
32 #include <QRegularExpression>
47 typedef std::shared_ptr<pa_glib_mainloop> MainLoopPtr;
48 MainLoopPtr make_main_loop()
52 pa_glib_mainloop_new(g_main_context_default()),
53 [](pa_glib_mainloop *ml)
55 pa_glib_mainloop_free(ml);
60 typedef std::shared_ptr<pa_context> ContextPtr;
61 ContextPtr make_context(MainLoopPtr main_loop)
65 pa_context_new(pa_glib_mainloop_get_api(main_loop.get()),
"MediaHubPulseContext"),
70 void set_state_callback(ContextPtr ctxt, pa_context_notify_cb_t cb,
void* cookie)
72 pa_context_set_state_callback(ctxt.get(), cb, cookie);
75 void set_subscribe_callback(ContextPtr ctxt, pa_context_subscribe_cb_t cb,
void* cookie)
77 pa_context_set_subscribe_callback(ctxt.get(), cb, cookie);
80 void throw_if_not_connected(ContextPtr ctxt)
82 if (pa_context_get_state(ctxt.get()) != PA_CONTEXT_READY )
throw std::logic_error
84 "Attempted to issue a call against pulseaudio via a non-connected context."
88 void get_server_info_async(ContextPtr ctxt, pa_server_info_cb_t cb,
void* cookie)
90 throw_if_not_connected(ctxt);
91 pa_operation_unref(pa_context_get_server_info(ctxt.get(), cb, cookie));
94 void subscribe_to_events(ContextPtr ctxt, pa_subscription_mask mask)
96 throw_if_not_connected(ctxt);
97 pa_operation_unref(pa_context_subscribe(ctxt.get(), mask,
nullptr,
nullptr));
100 void get_index_of_sink_by_name_async(ContextPtr ctxt,
const QString &name, pa_sink_info_cb_t cb,
void* cookie)
102 throw_if_not_connected(ctxt);
103 pa_operation_unref(pa_context_get_sink_info_by_name(ctxt.get(), qUtf8Printable(name), cb, cookie));
106 void get_sink_info_by_index_async(ContextPtr ctxt, std::int32_t index, pa_sink_info_cb_t cb,
void* cookie)
108 throw_if_not_connected(ctxt);
109 pa_operation_unref(pa_context_get_sink_info_by_index(ctxt.get(), index, cb, cookie));
112 void connect_async(ContextPtr ctxt)
114 pa_context_connect(ctxt.get(),
nullptr,
static_cast<pa_context_flags_t
>(PA_CONTEXT_NOAUTOSPAWN | PA_CONTEXT_NOFAIL),
nullptr);
117 bool is_port_available_on_sink(
const pa_sink_info* info,
const QRegularExpression& port_pattern)
122 for (std::uint32_t i = 0; i < info->n_ports; i++)
124 if (info->ports[i]->available == PA_PORT_AVAILABLE_NO ||
125 info->ports[i]->available == PA_PORT_AVAILABLE_UNKNOWN)
128 if (port_pattern.match(QString(info->ports[i]->name)).hasMatch())
141 if (
auto thiz =
static_cast<Private*
>(cookie))
145 if (thiz->context.get() != ctxt)
148 switch (pa_context_get_state(ctxt))
150 case PA_CONTEXT_READY:
151 thiz->on_context_ready();
153 case PA_CONTEXT_FAILED:
154 thiz->on_context_failed();
166 if (
auto thiz =
static_cast<Private*
>(cookie))
170 if (thiz->context.get() != ctxt)
173 if ((ev & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK)
174 thiz->on_sink_event_with_index(idx);
183 if (
auto thiz =
static_cast<Private*
>(cookie))
187 if (thiz->context.get() != ctxt)
190 thiz->on_query_for_active_sink_finished(si);
199 if (
auto thiz =
static_cast<Private*
>(cookie))
203 if (thiz->context.get() != ctxt)
206 thiz->on_query_for_primary_sink_finished(si);
215 if (
auto thiz =
static_cast<Private*
>(cookie))
219 if (thiz->context.get() != ctxt)
222 thiz->on_query_for_server_info_finished(si);
227 const QStringList &outputPortPatterns,
228 Reporter::Ptr reporter,
238 q->setOutputState(audio::OutputState::Speaker);
239 for (
const auto& pattern : outputPortPatterns)
241 outputs.emplace_back(QRegularExpression(pattern),
242 audio::OutputState::Speaker);
254 MH_DEBUG(
"Connection state for port changed to: %d", state);
255 std::get<1>(element) = state;
256 q->setOutputState(state);
265 pa::subscribe_to_events(
context, PA_SUBSCRIPTION_MASK_SINK);
320 MH_INFO(
"Checking if port is available -> %d",
321 pa::is_port_available_on_sink(info, std::get<0>(element)));
322 const bool available = pa::is_port_available_on_sink(info, std::get<0>(element));
331 state = audio::OutputState::Speaker;
333 state = audio::OutputState::External;
338 std::set<Reporter::Port> known_ports;
339 for (std::uint32_t i = 0; i < info->n_ports; i++)
341 bool is_monitored =
false;
344 is_monitored |= std::get<0>(element).match(info->ports[i]->name).hasMatch();
346 known_ports.insert(Reporter::Port
348 info->ports[i]->name,
349 info->ports[i]->description,
350 info->ports[i]->available == PA_PORT_AVAILABLE_YES,
361 m_reporter->query_for_sink_info_finished(info->name, info->index, known_ports);
368 if (not info->default_sink_name)
375 if (info->default_sink_name != std::get<1>(
active_sink))
383 m_reporter->query_for_default_sink_finished(info->default_sink_name);
393 std::vector<std::tuple<QRegularExpression, audio::OutputState>>
outputs;
398 std::set<audio::PulseAudioOutputObserver::Reporter::Port>
m_knownPorts;
404 return name == rhs.
name;
409 return name < rhs.
name;
412 audio::PulseAudioOutputObserver::Reporter::~Reporter()
416 void audio::PulseAudioOutputObserver::Reporter::connected_to_pulse_audio()
420 void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_failed()
424 void audio::PulseAudioOutputObserver::Reporter::query_for_default_sink_finished(
const std::string&)
428 void audio::PulseAudioOutputObserver::Reporter::query_for_sink_info_finished(
const std::string&, std::uint32_t,
const std::set<Port>&)
432 void audio::PulseAudioOutputObserver::Reporter::sink_event_with_index(std::uint32_t)
440 const QStringList &outputPortPatterns,
444 d(new Private(sink, outputPortPatterns, reporter, this))
455 return d->m_knownPorts;