// 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. // An idiomatic C++ interface to (parts of) PulseAudio. #ifndef PAVMON_PULSE_H_ #define PAVMON_PULSE_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace pulse { // The base exception type for all exceptions thrown by this library. class Error final : public std::exception { public: static Error InFunction(std::string function_name) noexcept { return Error(function_name + " failed"); } explicit Error(std::string what) noexcept : what_(std::move(what)) {} Error(const Error&) = default; Error& operator=(const Error&) = default; Error(Error&&) noexcept = default; Error& operator=(Error&&) noexcept = default; const char* what() const noexcept override { return what_.c_str(); } private: std::string what_; }; // A simple key-value store. // // This class is thread-compatible. class PropertyList final { public: explicit PropertyList(); PropertyList(const PropertyList&); PropertyList& operator=(const PropertyList& other); PropertyList(PropertyList&& other) noexcept : list_(nullptr) { *this = std::move(other); } PropertyList& operator=(PropertyList&& other) noexcept { swap(*this, other); return *this; } ~PropertyList() noexcept; // Adds or overwrites a mapping. Useful keys are #defined in // . void Set(const char key[], std::string_view value); pa_proplist* get() noexcept { return list_; } friend void swap(PropertyList& left, PropertyList& right) noexcept { using ::std::swap; swap(left.list_, right.list_); } private: pa_proplist* list_; }; // A main event loop implementation based on poll(2). // // This class is thread-compatible. class PollMainLoop final { public: explicit PollMainLoop(); PollMainLoop(PollMainLoop&& other) noexcept : loop_(nullptr), api_(nullptr), handling_signals_(false) { *this = std::move(other); } PollMainLoop& operator=(PollMainLoop&& other) noexcept { swap(*this, other); return *this; } ~PollMainLoop() noexcept; // Configures process signals to be delivered to the event loop. void HandleSignals(); // Undoes a HandleSignals call. void StopHandlingSignals() noexcept { pa_signal_done(); handling_signals_ = false; } // Runs the main loop. This function does not return until somebody invokes // Quit or an error occurs; it returns the argument to Quit. int Run(); // Instructs the main loop to stop and return the specified code. void Quit(int retval) noexcept { api_->quit(api_, retval); } pa_mainloop* get() noexcept { return loop_; } friend void swap(PollMainLoop& left, PollMainLoop& right) noexcept { using ::std::swap; swap(left.loop_, right.loop_); swap(left.api_, right.api_); swap(left.handling_signals_, right.handling_signals_); } private: pa_mainloop* loop_; pa_mainloop_api* api_; bool handling_signals_; }; // An RAII object to control signal handling in the event loop. Call // HandleSignals() on the event loop and then create one of these for each // signal you'd like to handle. // // This class is thread-compatible. class SignalEventSource final { public: // Starts handling a particular signal. Calls the specified callback whenever // that signal is received. explicit SignalEventSource(int signal, std::function); SignalEventSource(SignalEventSource&& other) noexcept : source_(nullptr) { *this = std::move(other); } SignalEventSource& operator=(SignalEventSource&& other) noexcept { swap(*this, other); return *this; } ~SignalEventSource() noexcept; friend void swap(SignalEventSource& left, SignalEventSource& right) noexcept { using ::std::swap; swap(left.source_, right.source_); } private: pa_signal_event* source_; }; // A connection to a PulseAudio server. // // This class is thread-compatible. class Context final { public: explicit Context(const char application_name[], PropertyList&, PollMainLoop&); Context(Context&& other) noexcept : ctx_(nullptr) { *this = std::move(other); } Context& operator=(Context&& other) noexcept { swap(*this, other); return *this; } ~Context() noexcept; // Connects to the default PulseAudio server. Set options by bitwise-ORing // flags defined in . Calls the specified callback each time the // state of the connection changes. void Connect( pa_context_flags_t, std::function on_state_change) noexcept; // Disconnects from the server. void Disconnect() noexcept { pa_context_disconnect(ctx_); state_callback_ = nullptr; } // Requests information about a sink specified by name. Calls on_failure if // the request fails; calls on_data if it succeeds. Each callback will only be // called once. void GetSinkInfo( const char name[], std::function on_failure, std::function on_data) const noexcept; // Subscribes to some subset of server event notifications (see // for allowable mask values and event types). Calls on_failure once if the // request fails. If the request succeeds, calls on_data once every time an // event occurs. void Subscribe(pa_subscription_mask_t, std::function on_failure, std::function on_event) const noexcept; friend void swap(Context& left, Context& right) noexcept { using ::std::swap; swap(left.ctx_, right.ctx_); swap(left.state_callback_, right.state_callback_); swap(left.subscribe_callback_, right.subscribe_callback_); } private: pa_context* ctx_; // These are pointers so the underlying std::function is always // at the same address, even if this object gets moved. std::unique_ptr> state_callback_; mutable std::unique_ptr> subscribe_callback_; }; } // namespace pulse #endif // PAVMON_PULSE_H_