mirror of
https://github.com/polybar/polybar.git
synced 2024-11-11 13:50:56 -05:00
wip(pulseaudio): create pulseaudio backend
This commit is contained in:
parent
6ed4838738
commit
81913cf181
9 changed files with 497 additions and 7 deletions
|
@ -7,6 +7,7 @@ checklib(ENABLE_CURL "pkg-config" libcurl)
|
||||||
checklib(ENABLE_I3 "binary" i3)
|
checklib(ENABLE_I3 "binary" i3)
|
||||||
checklib(ENABLE_MPD "pkg-config" libmpdclient)
|
checklib(ENABLE_MPD "pkg-config" libmpdclient)
|
||||||
checklib(ENABLE_NETWORK "cmake" Libiw)
|
checklib(ENABLE_NETWORK "cmake" Libiw)
|
||||||
|
checklib(ENABLE_PULSEAUDIO "pkg-config" libpulse)
|
||||||
checklib(WITH_XRM "pkg-config" xcb-xrm)
|
checklib(WITH_XRM "pkg-config" xcb-xrm)
|
||||||
checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12")
|
checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12")
|
||||||
checklib(WITH_XCURSOR "pkg-config" "xcb-cursor")
|
checklib(WITH_XCURSOR "pkg-config" "xcb-cursor")
|
||||||
|
@ -28,6 +29,7 @@ option(ENABLE_I3 "Enable i3 support" ON)
|
||||||
option(ENABLE_MPD "Enable mpd support" ON)
|
option(ENABLE_MPD "Enable mpd support" ON)
|
||||||
option(ENABLE_NETWORK "Enable network support" ON)
|
option(ENABLE_NETWORK "Enable network support" ON)
|
||||||
option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON)
|
option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON)
|
||||||
|
option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON)
|
||||||
|
|
||||||
option(WITH_XRANDR "xcb-randr support" ON)
|
option(WITH_XRANDR "xcb-randr support" ON)
|
||||||
option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON)
|
option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON)
|
||||||
|
|
|
@ -11,6 +11,7 @@ querylib(ENABLE_ALSA "pkg-config" alsa libs dirs)
|
||||||
querylib(ENABLE_CURL "pkg-config" libcurl libs dirs)
|
querylib(ENABLE_CURL "pkg-config" libcurl libs dirs)
|
||||||
querylib(ENABLE_MPD "pkg-config" libmpdclient libs dirs)
|
querylib(ENABLE_MPD "pkg-config" libmpdclient libs dirs)
|
||||||
querylib(ENABLE_NETWORK "cmake" Libiw libs dirs)
|
querylib(ENABLE_NETWORK "cmake" Libiw libs dirs)
|
||||||
|
querylib(ENABLE_PULSEAUDIO "pkg-config" libpulse libs dirs)
|
||||||
|
|
||||||
querylib(WITH_XCOMPOSITE "pkg-config" xcb-composite libs dirs)
|
querylib(WITH_XCOMPOSITE "pkg-config" xcb-composite libs dirs)
|
||||||
querylib(WITH_XDAMAGE "pkg-config" xcb-damage libs dirs)
|
querylib(WITH_XDAMAGE "pkg-config" xcb-damage libs dirs)
|
||||||
|
|
|
@ -18,6 +18,7 @@ colored_option(" curl" ENABLE_CURL)
|
||||||
colored_option(" i3" ENABLE_I3)
|
colored_option(" i3" ENABLE_I3)
|
||||||
colored_option(" mpd" ENABLE_MPD)
|
colored_option(" mpd" ENABLE_MPD)
|
||||||
colored_option(" network" ENABLE_NETWORK)
|
colored_option(" network" ENABLE_NETWORK)
|
||||||
|
colored_option(" pulseaudio" ENABLE_PULSEAUDIO)
|
||||||
|
|
||||||
message(STATUS " X extensions:")
|
message(STATUS " X extensions:")
|
||||||
colored_option(" xcb-randr" WITH_XRANDR)
|
colored_option(" xcb-randr" WITH_XRANDR)
|
||||||
|
|
73
include/adapters/pulseaudio.hpp
Normal file
73
include/adapters/pulseaudio.hpp
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <pulse/pulseaudio.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
#include "errors.hpp"
|
||||||
|
|
||||||
|
#include "utils/math.hpp"
|
||||||
|
// fwd
|
||||||
|
struct pa_context;
|
||||||
|
struct pa_threaded_mainloop;
|
||||||
|
struct pa_cvolume;
|
||||||
|
typedef struct pa_context pa_context;
|
||||||
|
typedef struct pa_threaded_mainloop pa_threaded_mainloop;
|
||||||
|
|
||||||
|
POLYBAR_NS
|
||||||
|
|
||||||
|
DEFINE_ERROR(pulseaudio_error);
|
||||||
|
|
||||||
|
class pulseaudio {
|
||||||
|
// events to add to our queue
|
||||||
|
enum class evtype { NEW = 0, CHANGE, REMOVE };
|
||||||
|
using queue = std::queue<evtype>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit pulseaudio(string&& sink_name);
|
||||||
|
~pulseaudio();
|
||||||
|
|
||||||
|
pulseaudio(const pulseaudio& o) = delete;
|
||||||
|
pulseaudio& operator=(const pulseaudio& o) = delete;
|
||||||
|
|
||||||
|
const string& get_name();
|
||||||
|
|
||||||
|
bool wait(int timeout = -1);
|
||||||
|
int process_events();
|
||||||
|
|
||||||
|
int get_volume();
|
||||||
|
void set_volume(float percentage);
|
||||||
|
void set_mute(bool mode);
|
||||||
|
void toggle_mute();
|
||||||
|
bool is_muted();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata);
|
||||||
|
static void get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int is_last, void *userdata);
|
||||||
|
static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata);
|
||||||
|
static void simple_callback(pa_context *context, int success, void *userdata);
|
||||||
|
static void get_default_sink_callback(pa_context *context, const pa_server_info *info, void *userdata);
|
||||||
|
static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata);
|
||||||
|
static void context_state_callback(pa_context *context, void *userdata);
|
||||||
|
|
||||||
|
// used for temporary callback results
|
||||||
|
pa_cvolume cv;
|
||||||
|
bool muted;
|
||||||
|
bool exists;
|
||||||
|
|
||||||
|
pa_context* m_context{nullptr};
|
||||||
|
pa_threaded_mainloop* m_mainloop{nullptr};
|
||||||
|
|
||||||
|
queue m_events;
|
||||||
|
|
||||||
|
// specified sink name
|
||||||
|
string spec_s_name;
|
||||||
|
// sink currently in use
|
||||||
|
string s_name;
|
||||||
|
// default sink name
|
||||||
|
string def_s_name;
|
||||||
|
uint32_t s_index{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
POLYBAR_NS_END
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
#include "adapters/pulseaudio.hpp"
|
||||||
#include "modules/meta/event_module.hpp"
|
#include "modules/meta/event_module.hpp"
|
||||||
#include "modules/meta/input_handler.hpp"
|
#include "modules/meta/input_handler.hpp"
|
||||||
|
|
||||||
|
@ -11,6 +12,7 @@ namespace alsa {
|
||||||
class mixer;
|
class mixer;
|
||||||
class control;
|
class control;
|
||||||
}
|
}
|
||||||
|
//class pulseaudio;
|
||||||
|
|
||||||
namespace modules {
|
namespace modules {
|
||||||
enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE };
|
enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE };
|
||||||
|
@ -18,6 +20,7 @@ namespace modules {
|
||||||
|
|
||||||
using mixer_t = shared_ptr<alsa::mixer>;
|
using mixer_t = shared_ptr<alsa::mixer>;
|
||||||
using control_t = shared_ptr<alsa::control>;
|
using control_t = shared_ptr<alsa::control>;
|
||||||
|
using pulseaudio_t = shared_ptr<pulseaudio>;
|
||||||
|
|
||||||
class volume_module : public event_module<volume_module>, public input_handler {
|
class volume_module : public event_module<volume_module>, public input_handler {
|
||||||
public:
|
public:
|
||||||
|
@ -56,8 +59,10 @@ namespace modules {
|
||||||
|
|
||||||
map<mixer, mixer_t> m_mixer;
|
map<mixer, mixer_t> m_mixer;
|
||||||
map<control, control_t> m_ctrl;
|
map<control, control_t> m_ctrl;
|
||||||
int m_headphoneid{0};
|
pulseaudio_t m_pulseaudio;
|
||||||
bool m_mapped{false};
|
|
||||||
|
//int m_headphoneid{0};
|
||||||
|
//bool m_mapped{false};
|
||||||
atomic<bool> m_muted{false};
|
atomic<bool> m_muted{false};
|
||||||
atomic<bool> m_headphones{false};
|
atomic<bool> m_headphones{false};
|
||||||
atomic<int> m_volume{0};
|
atomic<int> m_volume{0};
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#cmakedefine01 ENABLE_NETWORK
|
#cmakedefine01 ENABLE_NETWORK
|
||||||
#cmakedefine01 ENABLE_I3
|
#cmakedefine01 ENABLE_I3
|
||||||
#cmakedefine01 ENABLE_CURL
|
#cmakedefine01 ENABLE_CURL
|
||||||
|
#cmakedefine01 ENABLE_PULSEAUDIO
|
||||||
|
|
||||||
#cmakedefine01 WITH_XRANDR
|
#cmakedefine01 WITH_XRANDR
|
||||||
#cmakedefine01 WITH_XRENDER
|
#cmakedefine01 WITH_XRENDER
|
||||||
|
@ -99,12 +100,13 @@ const auto version_details = [](const std::vector<std::string>& args) {
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const auto print_build_info = [](bool extended = false) {
|
const auto print_build_info = [](bool extended = false) {
|
||||||
printf("%s %s\n\n", APP_NAME, APP_VERSION);
|
printf("%s %s\n\n", APP_NAME, APP_VERSION);
|
||||||
printf("Features: %calsa %ccurl %ci3 %cmpd %cnetwork\n",
|
printf("Features: %calsa %ccurl %ci3 %cmpd %cnetwork %cpulseaudio\n",
|
||||||
(ENABLE_ALSA ? '+' : '-'),
|
(ENABLE_ALSA ? '+' : '-'),
|
||||||
(ENABLE_CURL ? '+' : '-'),
|
(ENABLE_CURL ? '+' : '-'),
|
||||||
(ENABLE_I3 ? '+' : '-'),
|
(ENABLE_I3 ? '+' : '-'),
|
||||||
(ENABLE_MPD ? '+' : '-'),
|
(ENABLE_MPD ? '+' : '-'),
|
||||||
(ENABLE_NETWORK ? '+' : '-'));
|
(ENABLE_NETWORK ? '+' : '-'),
|
||||||
|
(ENABLE_PULSEAUDIO ? '+' : '-'));
|
||||||
if (extended) {
|
if (extended) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("X extensions: %crandr (%cmonitors) %crender %cdamage %csync %ccomposite %cxkb %cxrm %cxcursor\n",
|
printf("X extensions: %crandr (%cmonitors) %crender %cdamage %csync %ccomposite %cxkb %cxrm %cxcursor\n",
|
||||||
|
|
|
@ -28,6 +28,9 @@ if(NOT ENABLE_I3)
|
||||||
list(REMOVE_ITEM files modules/i3.cpp)
|
list(REMOVE_ITEM files modules/i3.cpp)
|
||||||
list(REMOVE_ITEM files utils/i3.cpp)
|
list(REMOVE_ITEM files utils/i3.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
if(NOT ENABLE_PULSEAUDIO)
|
||||||
|
list(REMOVE_ITEM files adapters/pulseaudio.cpp)
|
||||||
|
endif()
|
||||||
if(NOT WITH_XRANDR)
|
if(NOT WITH_XRANDR)
|
||||||
list(REMOVE_ITEM files x11/extensions/randr.cpp)
|
list(REMOVE_ITEM files x11/extensions/randr.cpp)
|
||||||
endif()
|
endif()
|
||||||
|
|
355
src/adapters/pulseaudio.cpp
Normal file
355
src/adapters/pulseaudio.cpp
Normal file
|
@ -0,0 +1,355 @@
|
||||||
|
#include "adapters/pulseaudio.hpp"
|
||||||
|
|
||||||
|
// TODO possibly move all the callback functions to lambda functions
|
||||||
|
// also maybe use pa_operation_unref(op)
|
||||||
|
// create base volume backend class (mixer/control, pulseaudio inherits from base class)
|
||||||
|
// use index instead of name internally?
|
||||||
|
POLYBAR_NS
|
||||||
|
|
||||||
|
/* Multichannel volumes:
|
||||||
|
* use pa_cvolume_max(), and pa_cvolume_scale()
|
||||||
|
*
|
||||||
|
* see https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/WritingVolumeControlUIs/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct pulseaudio object
|
||||||
|
*/
|
||||||
|
pulseaudio::pulseaudio(string&& sink_name) : spec_s_name(sink_name) {
|
||||||
|
m_mainloop = pa_threaded_mainloop_new();
|
||||||
|
if (!m_mainloop) {
|
||||||
|
throw pulseaudio_error("Could not create pulseaudio threaded mainloop.");
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
|
||||||
|
m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "polybar");
|
||||||
|
if (!m_context) {
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
pa_threaded_mainloop_free(m_mainloop);
|
||||||
|
throw pulseaudio_error("Could not create pulseaudio context.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_context_set_state_callback(m_context, context_state_callback, this);
|
||||||
|
|
||||||
|
if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
|
||||||
|
pa_context_disconnect(m_context);
|
||||||
|
pa_context_unref(m_context);
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
pa_threaded_mainloop_free(m_mainloop);
|
||||||
|
throw pulseaudio_error("Could not connect pulseaudio context.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_threaded_mainloop_start(m_mainloop) < 0) {
|
||||||
|
pa_context_disconnect(m_context);
|
||||||
|
pa_context_unref(m_context);
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
pa_threaded_mainloop_free(m_mainloop);
|
||||||
|
throw pulseaudio_error("Could not start pulseaudio mainloop.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
if (pa_context_get_state(m_context) != PA_CONTEXT_READY) {
|
||||||
|
//goto error;
|
||||||
|
throw pulseaudio_error("Could not connect to pulseaudio server.");
|
||||||
|
}
|
||||||
|
|
||||||
|
pa_operation* op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
if (exists)
|
||||||
|
s_name = sink_name;
|
||||||
|
else {
|
||||||
|
op = pa_context_get_server_info(m_context, get_default_sink_callback, this);
|
||||||
|
if (!op) {
|
||||||
|
throw pulseaudio_error("Failed to get pulseaudio server info.");
|
||||||
|
}
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
s_name = def_s_name;
|
||||||
|
// get the sink index
|
||||||
|
op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
op = pa_context_subscribe(m_context, static_cast<pa_subscription_mask_t>(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER),
|
||||||
|
simple_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
pa_context_set_subscribe_callback(m_context, subscribe_callback, this);
|
||||||
|
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct pulseaudio
|
||||||
|
*/
|
||||||
|
pulseaudio::~pulseaudio() {
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
pa_threaded_mainloop_stop(m_mainloop);
|
||||||
|
pa_context_disconnect(m_context);
|
||||||
|
pa_context_unref(m_context);
|
||||||
|
pa_threaded_mainloop_free(m_mainloop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sink name
|
||||||
|
*/
|
||||||
|
const string& pulseaudio::get_name() {
|
||||||
|
return s_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for events (timeout in ms)
|
||||||
|
*/
|
||||||
|
bool pulseaudio::wait(int timeout) {
|
||||||
|
// TODO wait for specified timeout
|
||||||
|
(void) timeout;
|
||||||
|
return m_events.size() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process queued pulseaudio events
|
||||||
|
*/
|
||||||
|
int pulseaudio::process_events() {
|
||||||
|
int ret = m_events.size();
|
||||||
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
pa_operation *o{nullptr};
|
||||||
|
// clear the queue
|
||||||
|
while (!m_events.empty()) {
|
||||||
|
switch (m_events.front()) {
|
||||||
|
// try to get specified sink
|
||||||
|
case evtype::NEW:
|
||||||
|
o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this);
|
||||||
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
if (exists)
|
||||||
|
s_name = spec_s_name;
|
||||||
|
break;
|
||||||
|
// get volume
|
||||||
|
case evtype::CHANGE:
|
||||||
|
o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this);
|
||||||
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
break;
|
||||||
|
// get default sink
|
||||||
|
case evtype::REMOVE:
|
||||||
|
o = pa_context_get_server_info(m_context, get_default_sink_callback, this);
|
||||||
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
o = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), sink_info_callback, this);
|
||||||
|
while (pa_operation_get_state(o) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_events.pop();
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get volume in percentage
|
||||||
|
*/
|
||||||
|
int pulseaudio::get_volume() {
|
||||||
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE) {
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
// alternatively, user pa_cvolume_avg_mask() to average selected channels
|
||||||
|
//return math_util::percentage(pa_cvolume_avg(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM);
|
||||||
|
return math_util::percentage(pa_cvolume_max(&cv), PA_VOLUME_MUTED, PA_VOLUME_NORM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set volume to given percentage
|
||||||
|
*/
|
||||||
|
//void pulseaudio::set_volume(int delta_perc) {
|
||||||
|
void pulseaudio::set_volume(float percentage) {
|
||||||
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), get_sink_volume_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
pa_volume_t vol = math_util::percentage_to_value<pa_volume_t>(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM);
|
||||||
|
pa_cvolume_scale(&cv, vol);
|
||||||
|
op = pa_context_set_sink_volume_by_name(m_context, s_name.c_str(), &cv, simple_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set mute state
|
||||||
|
*/
|
||||||
|
void pulseaudio::set_mute(bool mode) {
|
||||||
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
pa_operation *op = pa_context_set_sink_mute_by_name(m_context, s_name.c_str(), mode, simple_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle mute state
|
||||||
|
*/
|
||||||
|
void pulseaudio::toggle_mute() {
|
||||||
|
set_mute(!is_muted());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current mute state
|
||||||
|
*/
|
||||||
|
bool pulseaudio::is_muted() {
|
||||||
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
pa_operation *op = pa_context_get_sink_info_by_name(m_context, s_name.c_str(), check_mute_callback, this);
|
||||||
|
while (pa_operation_get_state(op) != PA_OPERATION_DONE)
|
||||||
|
pa_threaded_mainloop_wait(m_mainloop);
|
||||||
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
return muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when getting current mute state
|
||||||
|
*/
|
||||||
|
void pulseaudio::check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) {
|
||||||
|
if (eol < 0) {
|
||||||
|
throw pulseaudio_error("Failed to get sink information: " + string{pa_strerror(pa_context_errno(context))});
|
||||||
|
}
|
||||||
|
if (eol)
|
||||||
|
return;
|
||||||
|
pulseaudio* This = static_cast<pulseaudio *>(userdata);
|
||||||
|
if (info)
|
||||||
|
This->muted = info->mute;
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when getting volume
|
||||||
|
*/
|
||||||
|
void pulseaudio::get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) {
|
||||||
|
if (eol < 0) {
|
||||||
|
throw pulseaudio_error("Failed to get sink information: " + string{pa_strerror(pa_context_errno(context))});
|
||||||
|
}
|
||||||
|
if (eol)
|
||||||
|
return;
|
||||||
|
//pa_assert(info);
|
||||||
|
pulseaudio* This = static_cast<pulseaudio *>(userdata);
|
||||||
|
if (info)
|
||||||
|
This->cv = info->volume;
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when subscribing to changes
|
||||||
|
*/
|
||||||
|
void pulseaudio::subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
|
||||||
|
pulseaudio *This = static_cast<pulseaudio *>(userdata);
|
||||||
|
switch(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
|
||||||
|
case PA_SUBSCRIPTION_EVENT_SINK:
|
||||||
|
switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
|
||||||
|
case PA_SUBSCRIPTION_EVENT_NEW:
|
||||||
|
// if using the default sink, check if the new sink matches our specified sink
|
||||||
|
if (This->s_name == This->def_s_name && This->spec_s_name != This->def_s_name) {
|
||||||
|
printf("NEW\n");
|
||||||
|
This->m_events.emplace(evtype::NEW);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PA_SUBSCRIPTION_EVENT_CHANGE:
|
||||||
|
if (idx == PA_INVALID_INDEX) {
|
||||||
|
throw pulseaudio_error("Invalid index given: " + string{pa_strerror(pa_context_errno(context))});
|
||||||
|
} else if (idx == This->s_index) {
|
||||||
|
printf("CHANGE\n");
|
||||||
|
This->m_events.emplace(evtype::CHANGE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PA_SUBSCRIPTION_EVENT_REMOVE:
|
||||||
|
if (idx == This->s_index) {
|
||||||
|
printf("REMOVE\n");
|
||||||
|
This->m_events.emplace(evtype::REMOVE);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
/*
|
||||||
|
case PA_SUBSCRIPTION_EVENT_SERVER:
|
||||||
|
switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
|
||||||
|
case PA_SUBSCRIPTION_EVENT_CHANGE:
|
||||||
|
// default sink changed but no one cares
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple callback to check for success
|
||||||
|
*/
|
||||||
|
void pulseaudio::simple_callback(pa_context *context, int success, void *userdata) {
|
||||||
|
if (!success)
|
||||||
|
throw pulseaudio_error("Something failed: %s" + string{pa_strerror(pa_context_errno(context))});
|
||||||
|
pulseaudio *This = static_cast<pulseaudio *>(userdata);
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when getting default sink name
|
||||||
|
*/
|
||||||
|
void pulseaudio::get_default_sink_callback(pa_context *context, const pa_server_info *info, void *userdata) {
|
||||||
|
pulseaudio *This = static_cast<pulseaudio *>(userdata);
|
||||||
|
if (!info) {
|
||||||
|
throw pulseaudio_error("Failed to get server information: %s" + string{pa_strerror(pa_context_errno(context))});
|
||||||
|
} else {
|
||||||
|
This->s_name = info->default_sink_name;
|
||||||
|
This->def_s_name = info->default_sink_name;
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when getting sink info & existence
|
||||||
|
*/
|
||||||
|
void pulseaudio::sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata) {
|
||||||
|
(void) context;
|
||||||
|
pulseaudio *This = static_cast<pulseaudio *>(userdata);
|
||||||
|
if (eol < 0) {
|
||||||
|
//throw pulseaudio_error("Failed to get server information: " + string{pa_strerror(pa_context_errno(context))});
|
||||||
|
This->exists = false;
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (eol)
|
||||||
|
return;
|
||||||
|
if (!info) {
|
||||||
|
This->exists = false;
|
||||||
|
} else {
|
||||||
|
This->exists = true;
|
||||||
|
This->s_index = info->index;
|
||||||
|
}
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback when context state changes
|
||||||
|
*/
|
||||||
|
void pulseaudio::context_state_callback(pa_context *context, void *userdata) {
|
||||||
|
pulseaudio* This = static_cast<pulseaudio *>(userdata);
|
||||||
|
switch (pa_context_get_state(context)) {
|
||||||
|
case PA_CONTEXT_READY:
|
||||||
|
case PA_CONTEXT_TERMINATED:
|
||||||
|
case PA_CONTEXT_FAILED:
|
||||||
|
pa_threaded_mainloop_signal(This->m_mainloop, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PA_CONTEXT_UNCONNECTED:
|
||||||
|
case PA_CONTEXT_CONNECTING:
|
||||||
|
case PA_CONTEXT_AUTHORIZING:
|
||||||
|
case PA_CONTEXT_SETTING_NAME:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
POLYBAR_NS_END
|
|
@ -2,6 +2,7 @@
|
||||||
#include "adapters/alsa/control.hpp"
|
#include "adapters/alsa/control.hpp"
|
||||||
#include "adapters/alsa/generic.hpp"
|
#include "adapters/alsa/generic.hpp"
|
||||||
#include "adapters/alsa/mixer.hpp"
|
#include "adapters/alsa/mixer.hpp"
|
||||||
|
#include "adapters/pulseaudio.hpp"
|
||||||
#include "drawtypes/label.hpp"
|
#include "drawtypes/label.hpp"
|
||||||
#include "drawtypes/progressbar.hpp"
|
#include "drawtypes/progressbar.hpp"
|
||||||
#include "drawtypes/ramp.hpp"
|
#include "drawtypes/ramp.hpp"
|
||||||
|
@ -20,6 +21,7 @@ namespace modules {
|
||||||
|
|
||||||
volume_module::volume_module(const bar_settings& bar, string name_) : event_module<volume_module>(bar, move(name_)) {
|
volume_module::volume_module(const bar_settings& bar, string name_) : event_module<volume_module>(bar, move(name_)) {
|
||||||
// Load configuration values
|
// Load configuration values
|
||||||
|
/*
|
||||||
m_mapped = m_conf.get(name(), "mapped", m_mapped);
|
m_mapped = m_conf.get(name(), "mapped", m_mapped);
|
||||||
|
|
||||||
auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s);
|
auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s);
|
||||||
|
@ -66,6 +68,10 @@ namespace modules {
|
||||||
} catch (const control_error& err) {
|
} catch (const control_error& err) {
|
||||||
throw module_error(err.what());
|
throw module_error(err.what());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
auto sink_name = m_conf.get(name(), "sink", ""s);
|
||||||
|
//m_pulseaudio = new pulseaudio_t::element_type{move(sink_name)};
|
||||||
|
m_pulseaudio = factory_util::unique<pulseaudio>(move(sink_name));
|
||||||
|
|
||||||
// Add formats and elements
|
// Add formats and elements
|
||||||
m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
|
m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
|
||||||
|
@ -87,13 +93,18 @@ namespace modules {
|
||||||
}
|
}
|
||||||
|
|
||||||
void volume_module::teardown() {
|
void volume_module::teardown() {
|
||||||
|
/*
|
||||||
m_mixer.clear();
|
m_mixer.clear();
|
||||||
m_ctrl.clear();
|
m_ctrl.clear();
|
||||||
|
*/
|
||||||
|
//m_pulseaudio.clear();
|
||||||
|
m_pulseaudio.reset();
|
||||||
snd_config_update_free_global();
|
snd_config_update_free_global();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool volume_module::has_event() {
|
bool volume_module::has_event() {
|
||||||
// Poll for mixer and control events
|
// Poll for mixer and control events
|
||||||
|
/*
|
||||||
try {
|
try {
|
||||||
if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) {
|
if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -110,12 +121,19 @@ namespace modules {
|
||||||
} catch (const alsa_exception& e) {
|
} catch (const alsa_exception& e) {
|
||||||
m_log.err("%s: %s", name(), e.what());
|
m_log.err("%s: %s", name(), e.what());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
if (m_pulseaudio->wait(25))
|
||||||
|
return true;
|
||||||
|
} catch (const pulseaudio_error& e) {
|
||||||
|
m_log.err("%s: %s", name(), e.what());
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool volume_module::update() {
|
bool volume_module::update() {
|
||||||
// Consume pending events
|
// Consume pending events
|
||||||
|
/*
|
||||||
if (m_mixer[mixer::MASTER]) {
|
if (m_mixer[mixer::MASTER]) {
|
||||||
m_mixer[mixer::MASTER]->process_events();
|
m_mixer[mixer::MASTER]->process_events();
|
||||||
}
|
}
|
||||||
|
@ -128,12 +146,15 @@ namespace modules {
|
||||||
if (m_ctrl[control::HEADPHONE]) {
|
if (m_ctrl[control::HEADPHONE]) {
|
||||||
m_ctrl[control::HEADPHONE]->process_events();
|
m_ctrl[control::HEADPHONE]->process_events();
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
m_pulseaudio->process_events();
|
||||||
|
|
||||||
// Get volume, mute and headphone state
|
// Get volume, mute and headphone state
|
||||||
m_volume = 100;
|
m_volume = 100;
|
||||||
m_muted = false;
|
m_muted = false;
|
||||||
m_headphones = false;
|
m_headphones = false;
|
||||||
|
|
||||||
|
/*
|
||||||
try {
|
try {
|
||||||
if (m_mixer[mixer::MASTER]) {
|
if (m_mixer[mixer::MASTER]) {
|
||||||
m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f
|
m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f
|
||||||
|
@ -164,6 +185,15 @@ namespace modules {
|
||||||
} catch (const alsa_exception& err) {
|
} catch (const alsa_exception& err) {
|
||||||
m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what());
|
m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what());
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
if (m_pulseaudio) {
|
||||||
|
m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f;
|
||||||
|
m_muted = m_muted || m_pulseaudio->is_muted();
|
||||||
|
}
|
||||||
|
} catch (const pulseaudio_error& err) {
|
||||||
|
m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what());
|
||||||
|
}
|
||||||
|
|
||||||
// Replace label tokens
|
// Replace label tokens
|
||||||
if (m_label_volume) {
|
if (m_label_volume) {
|
||||||
|
@ -222,11 +252,12 @@ namespace modules {
|
||||||
return false;
|
return false;
|
||||||
} else if (cmd.compare(0, 3, EVENT_PREFIX) != 0) {
|
} else if (cmd.compare(0, 3, EVENT_PREFIX) != 0) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!m_mixer[mixer::MASTER]) {
|
//} else if (!m_mixer[mixer::MASTER]) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
/*
|
||||||
vector<mixer_t> mixers;
|
vector<mixer_t> mixers;
|
||||||
bool headphones{m_headphones};
|
bool headphones{m_headphones};
|
||||||
|
|
||||||
|
@ -266,6 +297,23 @@ namespace modules {
|
||||||
mixer->process_events();
|
mixer->process_events();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
if (m_pulseaudio && !m_pulseaudio->get_name().empty()) {
|
||||||
|
if (cmd.compare(0, strlen(EVENT_TOGGLE_MUTE), EVENT_TOGGLE_MUTE) == 0) {
|
||||||
|
printf("toggling mute\n");
|
||||||
|
m_pulseaudio->toggle_mute();
|
||||||
|
} else if (cmd.compare(0, strlen(EVENT_VOLUME_UP), EVENT_VOLUME_UP) == 0) {
|
||||||
|
// cap above 100 (~150)?
|
||||||
|
m_pulseaudio->set_volume(math_util::cap<float>(m_pulseaudio->get_volume() + 5, 0, 100));
|
||||||
|
} else if (cmd.compare(0, strlen(EVENT_VOLUME_DOWN), EVENT_VOLUME_DOWN) == 0) {
|
||||||
|
m_pulseaudio->set_volume(math_util::cap<float>(m_pulseaudio->get_volume() - 5, 0, 100));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (m_pulseaudio->wait(0)) {
|
||||||
|
m_pulseaudio->process_events();
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (const exception& err) {
|
} catch (const exception& err) {
|
||||||
m_log.err("%s: Failed to handle command (%s)", name(), err.what());
|
m_log.err("%s: Failed to handle command (%s)", name(), err.what());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue