1 #include "modules/alsa.hpp"
2 #include "adapters/alsa/control.hpp"
3 #include "adapters/alsa/generic.hpp"
4 #include "adapters/alsa/mixer.hpp"
5 #include "drawtypes/label.hpp"
6 #include "drawtypes/progressbar.hpp"
7 #include "drawtypes/ramp.hpp"
8 #include "utils/math.hpp"
9 
10 #include "modules/meta/base.inl"
11 
12 #include "settings.hpp"
13 
14 POLYBAR_NS
15 
16 using namespace alsa;
17 
18 namespace modules {
19   template class module<alsa_module>;
20 
alsa_module(const bar_settings & bar,string name_)21   alsa_module::alsa_module(const bar_settings& bar, string name_) : event_module<alsa_module>(bar, move(name_)) {
22     // Load configuration values
23     m_mapped = m_conf.get(name(), "mapped", m_mapped);
24     m_interval = m_conf.get(name(), "interval", m_interval);
25 
26     auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s);
27     auto speaker_mixer_name = m_conf.get(name(), "speaker-mixer", ""s);
28     auto headphone_mixer_name = m_conf.get(name(), "headphone-mixer", ""s);
29 
30     // m_soundcard_name: Master Soundcard Name
31     // s_soundcard_name: Speaker Soundcard Name
32     // h_soundcard_name: Headphone Soundcard Name
33     auto m_soundcard_name = m_conf.get(name(), "master-soundcard", "default"s);
34     auto s_soundcard_name = m_conf.get(name(), "speaker-soundcard", "default"s);
35     auto h_soundcard_name = m_conf.get(name(), "headphone-soundcard", "default"s);
36 
37     if (!headphone_mixer_name.empty()) {
38       m_headphoneid = m_conf.get<decltype(m_headphoneid)>(name(), "headphone-id");
39     }
40 
41     if (string_util::compare(speaker_mixer_name, "master")) {
42       throw module_error("Master mixer is already defined");
43     }
44     if (string_util::compare(headphone_mixer_name, "master")) {
45       throw module_error("Master mixer is already defined");
46     }
47 
48     // Setup mixers
49     try {
50       if (!master_mixer_name.empty()) {
51         m_mixer[mixer::MASTER].reset(new mixer_t::element_type{move(master_mixer_name), move(m_soundcard_name)});
52       }
53       if (!speaker_mixer_name.empty()) {
54         m_mixer[mixer::SPEAKER].reset(new mixer_t::element_type{move(speaker_mixer_name), move(s_soundcard_name)});
55       }
56       if (!headphone_mixer_name.empty()) {
57         m_mixer[mixer::HEADPHONE].reset(new mixer_t::element_type{move(headphone_mixer_name), move(h_soundcard_name)});
58       }
59       if (m_mixer[mixer::HEADPHONE]) {
60         m_ctrl[control::HEADPHONE].reset(new control_t::element_type{m_headphoneid});
61       }
62       if (m_mixer.empty()) {
63         throw module_error("No configured mixers");
64       }
65     } catch (const mixer_error& err) {
66       throw module_error(err.what());
67     } catch (const control_error& err) {
68       throw module_error(err.what());
69     }
70 
71     // Add formats and elements
72     m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
73     m_formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});
74 
75     if (m_formatter->has(TAG_BAR_VOLUME)) {
76       m_bar_volume = load_progressbar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
77     }
78     if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
79       m_label_volume = load_optional_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%%");
80     }
81     if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
82       m_label_muted = load_optional_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%%");
83     }
84     if (m_formatter->has(TAG_RAMP_VOLUME)) {
85       m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME);
86       m_ramp_headphones = load_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false);
87     }
88   }
89 
teardown()90   void alsa_module::teardown() {
91     m_mixer.clear();
92     m_ctrl.clear();
93     snd_config_update_free_global();
94   }
95 
has_event()96   bool alsa_module::has_event() {
97     // Poll for mixer and control events
98     try {
99       if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) {
100         return true;
101       }
102       if (m_mixer[mixer::SPEAKER] && m_mixer[mixer::SPEAKER]->wait(25)) {
103         return true;
104       }
105       if (m_mixer[mixer::HEADPHONE] && m_mixer[mixer::HEADPHONE]->wait(25)) {
106         return true;
107       }
108       if (m_ctrl[control::HEADPHONE] && m_ctrl[control::HEADPHONE]->wait(25)) {
109         return true;
110       }
111     } catch (const alsa_exception& e) {
112       m_log.err("%s: %s", name(), e.what());
113     }
114 
115     return false;
116   }
117 
update()118   bool alsa_module::update() {
119     // Consume pending events
120     if (m_mixer[mixer::MASTER]) {
121       m_mixer[mixer::MASTER]->process_events();
122     }
123     if (m_mixer[mixer::SPEAKER]) {
124       m_mixer[mixer::SPEAKER]->process_events();
125     }
126     if (m_mixer[mixer::HEADPHONE]) {
127       m_mixer[mixer::HEADPHONE]->process_events();
128     }
129     if (m_ctrl[control::HEADPHONE]) {
130       m_ctrl[control::HEADPHONE]->process_events();
131     }
132 
133     // Get volume, mute and headphone state
134     m_volume = 100;
135     m_muted = false;
136     m_headphones = false;
137 
138     try {
139       if (m_mixer[mixer::MASTER]) {
140         m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f
141                                         : m_mixer[mixer::MASTER]->get_volume() / 100.0f);
142         m_muted = m_muted || m_mixer[mixer::MASTER]->is_muted();
143       }
144     } catch (const alsa_exception& err) {
145       m_log.err("%s: Failed to query master mixer (%s)", name(), err.what());
146     }
147 
148     try {
149       if (m_ctrl[control::HEADPHONE] && m_ctrl[control::HEADPHONE]->test_device_plugged()) {
150         m_headphones = true;
151         m_volume = m_volume * (m_mapped ? m_mixer[mixer::HEADPHONE]->get_normalized_volume() / 100.0f
152                                         : m_mixer[mixer::HEADPHONE]->get_volume() / 100.0f);
153         m_muted = m_muted || m_mixer[mixer::HEADPHONE]->is_muted();
154       }
155     } catch (const alsa_exception& err) {
156       m_log.err("%s: Failed to query headphone mixer (%s)", name(), err.what());
157     }
158 
159     try {
160       if (!m_headphones && m_mixer[mixer::SPEAKER]) {
161         m_volume = m_volume * (m_mapped ? m_mixer[mixer::SPEAKER]->get_normalized_volume() / 100.0f
162                                         : m_mixer[mixer::SPEAKER]->get_volume() / 100.0f);
163         m_muted = m_muted || m_mixer[mixer::SPEAKER]->is_muted();
164       }
165     } catch (const alsa_exception& err) {
166       m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what());
167     }
168 
169     // Replace label tokens
170     if (m_label_volume) {
171       m_label_volume->reset_tokens();
172       m_label_volume->replace_token("%percentage%", to_string(m_volume));
173     }
174 
175     if (m_label_muted) {
176       m_label_muted->reset_tokens();
177       m_label_muted->replace_token("%percentage%", to_string(m_volume));
178     }
179 
180     return true;
181   }
182 
get_format() const183   string alsa_module::get_format() const {
184     return m_muted ? FORMAT_MUTED : FORMAT_VOLUME;
185   }
186 
get_output()187   string alsa_module::get_output() {
188     // Get the module output early so that
189     // the format prefix/suffix also gets wrapper
190     // with the cmd handlers
191     string output{module::get_output()};
192 
193     if (m_handle_events) {
194       m_builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "");
195       m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
196       m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
197     }
198 
199     m_builder->append(output);
200 
201     return m_builder->flush();
202   }
203 
build(builder * builder,const string & tag) const204   bool alsa_module::build(builder* builder, const string& tag) const {
205     if (tag == TAG_BAR_VOLUME) {
206       builder->node(m_bar_volume->output(m_volume));
207     } else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones)) {
208       builder->node(m_ramp_volume->get_by_percentage(m_volume));
209     } else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones) {
210       builder->node(m_ramp_headphones->get_by_percentage(m_volume));
211     } else if (tag == TAG_LABEL_VOLUME) {
212       builder->node(m_label_volume);
213     } else if (tag == TAG_LABEL_MUTED) {
214       builder->node(m_label_muted);
215     } else {
216       return false;
217     }
218     return true;
219   }
220 
input(const string & action,const string &)221   bool alsa_module::input(const string& action, const string&) {
222     if (!m_handle_events) {
223       return false;
224     } else if (!m_mixer[mixer::MASTER]) {
225       return false;
226     }
227 
228     try {
229       vector<mixer_t> mixers;
230       bool headphones{m_headphones};
231 
232       if (m_mixer[mixer::MASTER] && !m_mixer[mixer::MASTER]->get_name().empty()) {
233         mixers.emplace_back(new mixer_t::element_type(
234             string{m_mixer[mixer::MASTER]->get_name()}, string{m_mixer[mixer::MASTER]->get_sound_card()}));
235       }
236       if (m_mixer[mixer::HEADPHONE] && !m_mixer[mixer::HEADPHONE]->get_name().empty() && headphones) {
237         mixers.emplace_back(new mixer_t::element_type(
238             string{m_mixer[mixer::HEADPHONE]->get_name()}, string{m_mixer[mixer::HEADPHONE]->get_sound_card()}));
239       }
240       if (m_mixer[mixer::SPEAKER] && !m_mixer[mixer::SPEAKER]->get_name().empty() && !headphones) {
241         mixers.emplace_back(new mixer_t::element_type(
242             string{m_mixer[mixer::SPEAKER]->get_name()}, string{m_mixer[mixer::SPEAKER]->get_sound_card()}));
243       }
244 
245       if (action == EVENT_TOGGLE) {
246         for (auto&& mixer : mixers) {
247           mixer->set_mute(m_muted || mixers[0]->is_muted());
248         }
249       } else if (action == EVENT_INC) {
250         for (auto&& mixer : mixers) {
251           m_mapped ? mixer->set_normalized_volume(math_util::cap<float>(mixer->get_normalized_volume() + m_interval, 0, 100))
252                    : mixer->set_volume(math_util::cap<float>(mixer->get_volume() + m_interval, 0, 100));
253         }
254       } else if (action == EVENT_DEC) {
255         for (auto&& mixer : mixers) {
256           m_mapped ? mixer->set_normalized_volume(math_util::cap<float>(mixer->get_normalized_volume() - m_interval, 0, 100))
257                    : mixer->set_volume(math_util::cap<float>(mixer->get_volume() - m_interval, 0, 100));
258         }
259       } else {
260         return false;
261       }
262 
263       for (auto&& mixer : mixers) {
264         if (mixer->wait(0)) {
265           mixer->process_events();
266         }
267       }
268     } catch (const exception& err) {
269       m_log.err("%s: Failed to handle command (%s)", name(), err.what());
270     }
271 
272     return true;
273   }
274 }
275 
276 POLYBAR_NS_END
277