summaryrefslogtreecommitdiff
path: root/pulse.cc
diff options
context:
space:
mode:
Diffstat (limited to 'pulse.cc')
-rw-r--r--pulse.cc360
1 files changed, 360 insertions, 0 deletions
diff --git a/pulse.cc b/pulse.cc
new file mode 100644
index 0000000..ebe72b9
--- /dev/null
+++ b/pulse.cc
@@ -0,0 +1,360 @@
+// Copyright 2022 Benjamin Barenblat
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pulse.h"
+
+#include <assert.h>
+#include <pulse/context.h>
+#include <pulse/def.h>
+#include <pulse/error.h>
+#include <pulse/introspect.h>
+#include <pulse/mainloop-api.h>
+#include <pulse/mainloop-signal.h>
+#include <pulse/mainloop.h>
+#include <pulse/operation.h>
+#include <pulse/proplist.h>
+#include <pulse/subscribe.h>
+
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <utility>
+
+namespace pulse {
+
+// Adapters for libpulse callbacks.
+//
+// libpulse presents a callback-driven C API--callbacks must be C function
+// pointers, and each callback can be passed a user-controlled void*. This isn't
+// a great API for a C++ library, though, so this library accepts std::functions
+// and pass them by pointer to statically defined callbacks.
+//
+// Even though these functions are defined in an anonymous namespace, the extern
+// "C" means they need "static" to get the desired internal linkage.
+
+namespace {
+
+using SignalCallback = std::function<void()>;
+
+extern "C" {
+
+static void CallSignalCallback(pa_mainloop_api*, pa_signal_event*,
+ int /*signal*/, void* real_callback) noexcept {
+ assert(real_callback != nullptr);
+ (*reinterpret_cast<SignalCallback*>(real_callback))();
+}
+
+static void DeleteSignalCallback(pa_mainloop_api*, pa_signal_event*,
+ void* real_callback) noexcept {
+ assert(real_callback != nullptr);
+ delete reinterpret_cast<SignalCallback*>(real_callback);
+}
+
+} // extern "C"
+
+using ContextNotifyCallback = std::function<void(pa_context_state_t)>;
+
+extern "C" {
+
+static void CallContextNotifyCallback(pa_context* ctx,
+ void* real_callback) noexcept {
+ assert(real_callback != nullptr);
+ (*reinterpret_cast<ContextNotifyCallback*>(real_callback))(
+ pa_context_get_state(ctx));
+}
+
+} // extern "C"
+
+struct SinkInfoCallbacks {
+ std::function<void()> on_failure;
+ std::function<void(const pa_sink_info&)> on_data;
+};
+
+extern "C" {
+
+static void CallOrDeleteSinkInfoCallbacks(pa_context*, const pa_sink_info* info,
+ int status,
+ void* real_callbacks) noexcept {
+ assert(real_callbacks != nullptr);
+ auto* sink_info_callbacks =
+ reinterpret_cast<SinkInfoCallbacks*>(real_callbacks);
+
+ if (status == 0) {
+ // The RPC succeeded, and info contains sink information. Tell the user.
+ assert(info != nullptr);
+ sink_info_callbacks->on_data(*info);
+ } else {
+ // No sink information came through...
+ if (status < 0) {
+ // ...because of an error. Tell the user.
+ sink_info_callbacks->on_failure();
+ } else {
+ // ...because no more data is available from the PulseAudio server. The
+ // user doesn't need to hear about that.
+ }
+
+ // In any case, no more data are coming, so don't hang onto the callbacks
+ // anymore.
+ delete sink_info_callbacks;
+ }
+}
+
+} // extern "C"
+
+struct RpcResultCallbacks {
+ std::function<void()> on_failure;
+ std::function<void()> on_success;
+};
+
+extern "C" {
+
+static void CallAndDeleteRpcResultCallbacks(pa_context*, int success,
+ void* real_callbacks) noexcept {
+ assert(real_callbacks != nullptr);
+ auto* rpc_result_callbacks =
+ reinterpret_cast<RpcResultCallbacks*>(real_callbacks);
+ if (success) {
+ rpc_result_callbacks->on_success();
+ } else {
+ rpc_result_callbacks->on_failure();
+ }
+ delete rpc_result_callbacks;
+}
+
+} // extern "C"
+
+using SubscribeCallback = std::function<void(pa_subscription_event_type_t)>;
+
+extern "C" {
+
+static void CallSubscribeCallback(pa_context*,
+ pa_subscription_event_type_t type,
+ uint32_t /*index*/,
+ void* real_callback) noexcept {
+ assert(real_callback != nullptr);
+ (*reinterpret_cast<SubscribeCallback*>(real_callback))(type);
+}
+
+} // extern "C"
+
+} // namespace
+
+PropertyList::PropertyList() : list_(pa_proplist_new()) {
+ if (list_ == nullptr) {
+ throw Error::InFunction("pa_proplist_new");
+ }
+}
+
+PropertyList::PropertyList(const PropertyList& other)
+ : list_(pa_proplist_copy(other.list_)) {
+ if (list_ == nullptr) {
+ throw Error::InFunction("pa_proplist_copy");
+ }
+}
+
+PropertyList& PropertyList::operator=(const PropertyList& other) {
+ if (this != &other) {
+ PropertyList other2(other);
+ swap(*this, other2);
+ }
+ return *this;
+}
+
+PropertyList::~PropertyList() noexcept {
+ if (list_ != nullptr) {
+ pa_proplist_free(list_);
+ }
+}
+
+void PropertyList::Set(const char key[], std::string_view value) {
+ if (pa_proplist_set(list_, key, value.data(), value.size())) {
+ throw Error::InFunction("pa_proplist_set");
+ }
+}
+
+PollMainLoop::PollMainLoop()
+ : loop_(pa_mainloop_new()), handling_signals_(false) {
+ if (loop_ == nullptr) {
+ throw Error::InFunction("pa_mainloop_new");
+ }
+ api_ = pa_mainloop_get_api(loop_);
+}
+
+PollMainLoop::~PollMainLoop() noexcept {
+ if (handling_signals_) {
+ StopHandlingSignals();
+ }
+ if (loop_ != nullptr) {
+ pa_mainloop_free(loop_);
+ }
+}
+
+void PollMainLoop::HandleSignals() {
+ if (pa_signal_init(api_)) {
+ throw Error::InFunction("pa_signal_init");
+ }
+ handling_signals_ = true;
+}
+
+int PollMainLoop::Run() {
+ int r;
+ if (pa_mainloop_run(loop_, &r) < 0) {
+ throw Error::InFunction("pa_mainloop_run");
+ }
+ return r;
+}
+
+SignalEventSource::SignalEventSource(int signal,
+ std::function<void()> real_callback) {
+ // Construct source_ using CallSignalCallback as the C callback and
+ // real_callback as the real callback. real_callback is going to get passed by
+ // pointer, so that address needs to continue to point to real_callback until
+ // pa_signal_free. Happily, libpulse provides a mechanism to run an arbitrary
+ // callback during pa_signal_free, so move the callback onto the heap and
+ // delete it during pa_signal_free. This function is still responsible for
+ // deleting it if pa_signal_new fails, though, so stash it inside a unique_ptr
+ // and release the unique_ptr if pa_signal_new succeeds.
+ auto heap_callback =
+ std::make_unique<SignalCallback>(std::move(real_callback));
+ source_ = pa_signal_new(signal, &CallSignalCallback, heap_callback.get());
+ if (source_ == nullptr) {
+ throw Error::InFunction("pa_signal_new");
+ }
+ pa_signal_set_destroy(source_, &DeleteSignalCallback);
+ heap_callback.release();
+}
+
+SignalEventSource::~SignalEventSource() noexcept {
+ if (source_ != nullptr) {
+ pa_signal_free(source_);
+ }
+}
+
+Context::Context(const char application_name[], PropertyList& proplist,
+ PollMainLoop& loop)
+ : ctx_(pa_context_new_with_proplist(pa_mainloop_get_api(loop.get()),
+ application_name, proplist.get())) {
+ if (ctx_ == nullptr) {
+ throw Error::InFunction("pa_context_new_with_proplist");
+ }
+}
+
+Context::~Context() noexcept {
+ if (state_callback_ != nullptr) {
+ Disconnect();
+ }
+ if (ctx_ != nullptr) {
+ pa_context_unref(ctx_);
+ }
+}
+
+void Context::Connect(
+ pa_context_flags_t flags,
+ std::function<void(pa_context_state_t)> on_state_change) noexcept {
+ // Set Up CallContextNotifyCallback to call on_state_change. on_state_change
+ // is going to get passed by pointer, so its address needs to continue to
+ // point to on_state_change until pa_context_disconnect. Saving the callback
+ // as a member ensures it stays alive long enough, but it doesn't ensure
+ // pointer stability; to get that, save it on the heap and keep a unique_ptr
+ // to it as a member.
+ state_callback_ =
+ std::make_unique<ContextNotifyCallback>(std::move(on_state_change));
+ pa_context_set_state_callback(ctx_, &CallContextNotifyCallback,
+ state_callback_.get());
+ if (pa_context_connect(ctx_, /*server=*/nullptr, flags,
+ /*spawn_api=*/nullptr)) {
+ // The RPC never even went out. libpulse won't call the callback in this
+ // case, so do it manually.
+ (*state_callback_)(PA_CONTEXT_FAILED);
+
+ // The callback will never be called again at this point, and hanging onto
+ // it could cause resource leakage (e.g., if it captured a std::shared_ptr).
+ // Get rid of it.
+ state_callback_ = nullptr;
+ }
+}
+
+void Context::GetSinkInfo(
+ const char name[], std::function<void()> on_failure,
+ std::function<void(const pa_sink_info&)> on_data) const noexcept {
+ // Set up CallOrDeleteSinkInfoCallbacks to call on_failure or on_data as
+ // appropriate. These callbacks are going to get passed by pointer, so they
+ // need to live on the heap. If constructing the get_sink_info_by_name RPC
+ // fails, though, libpulse won't call on_failure; to allow calling it
+ // manually, stash the callbacks inside a unique_ptr and release the
+ // unique_ptr if the RPC actually gets dispatched.
+ auto callbacks = std::make_unique<SinkInfoCallbacks>();
+ callbacks->on_failure = std::move(on_failure);
+ callbacks->on_data = std::move(on_data);
+ pa_operation* op = pa_context_get_sink_info_by_name(
+ ctx_, name, &CallOrDeleteSinkInfoCallbacks, callbacks.get());
+ if (op == nullptr) {
+ // The RPC never even went out. libpulse won't call on_failure in this case,
+ // so do it manually.
+ callbacks->on_failure();
+ } else {
+ pa_operation_unref(op); // The RPC handle is no longer needed.
+
+ // The RPC is running, which means CallOrDeleteSinkInfoCallbacks will be
+ // called. (In fact, it's possible that CallOrDeleteSinkInfoCallbacks has
+ // already been called and the callbacks struct has already been deleted!)
+ // Stop managing the callbacks struct.
+ callbacks.release();
+ }
+}
+
+void Context::Subscribe(
+ pa_subscription_mask_t desired_events, std::function<void()> on_failure,
+ std::function<void(pa_subscription_event_type_t)> on_event) const noexcept {
+ // Set up CallSubscribeCallback to call on_event. This callback is going to
+ // get passed by pointer, so its address needs to continue to point to
+ // on_event until pa_context_disconnect. Saving the callback as a member
+ // ensures it stays alive long enough, but it doesn't ensure pointer
+ // stability; to get that, save it on the heap and keep a unique_ptr to it as
+ // a member.
+ subscribe_callback_ =
+ std::make_unique<SubscribeCallback>(std::move(on_event));
+ pa_context_set_subscribe_callback(ctx_, &CallSubscribeCallback,
+ subscribe_callback_.get());
+
+ // Set up CallAndDeleteRpcResultCallbacks to handle the result of the
+ // subscribe RPC. The real callbacks struct is going to get passed by pointer,
+ // so it needs to live on the heap. If constructing the subscribe RPC fails,
+ // though, libpulse won't call on_failure; to allow calling it manually, stash
+ // the callbacks inside a unique_ptr and release the unique_ptr if the RPC
+ // actually gets dispatched.
+ auto subscribe_completion_callbacks = std::make_unique<RpcResultCallbacks>();
+ subscribe_completion_callbacks->on_failure = std::move(on_failure);
+ // We don't need to tell the user the RPC succeded; the event callback can
+ // tell them that.
+ subscribe_completion_callbacks->on_success = []() noexcept {};
+ pa_operation* op = pa_context_subscribe(ctx_, desired_events,
+ &CallAndDeleteRpcResultCallbacks,
+ subscribe_completion_callbacks.get());
+ if (op == nullptr) {
+ // The RPC never even went out. libpulse won't call
+ // CallAndDeleteRpcResultCallbacks in this case, call on_failure manually.
+ subscribe_completion_callbacks->on_failure();
+ } else {
+ pa_operation_unref(op); // The RPC handle is no longer needed.
+
+ // The RPC is running, which means CallAndDeleteRpcResultCallbacks will be
+ // called. (In fact, it's possible that CallAndDeleteRpcResultCallbacks has
+ // already been called and the result callbacks struct has already been
+ // deleted!) Stop managing the result callbacks struct.
+ subscribe_completion_callbacks.release();
+ }
+}
+
+} // namespace pulse