// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "pulse.h" namespace { constexpr std::string_view kShortUsage = "Usage: pavmon SINK_NAME\n"; constexpr std::string_view kHelp = R"( Monitor the volume level of a PulseAudio sink, printing a new line each time it changes. Options: --help display this help and exit --version display version information and exit )"; constexpr std::string_view kAskForHelp = "Try \"pavmon --help\" for more information.\n"; constexpr std::string_view kVersionInfo = R"(pavmon 1.0.0 Copyright 2022 Benjamin Barenblat Licensed under the Apache License, Version 2.0 )"; enum { kHelpLongOption = 128, kVersionLongOption, }; void GetAndPrintVolume(const std::string& sink_name, pulse::PollMainLoop& loop, pulse::Context& ctx) noexcept { ctx.GetSinkInfo( sink_name.c_str(), [&]() noexcept { std::clog << "pavmon: failed to get volume" << std::endl; loop.Quit(1); }, [&](const pa_sink_info& info) noexcept { if (info.mute) { std::cout << "muted ("; } std::cout << std::fixed << std::setprecision(1) << pa_sw_volume_to_dB(pa_cvolume_avg(&info.volume)) << " dB"; if (info.mute) { std::cout << ')'; } std::cout << std::endl; }); } void SubscribeToEvents(const std::string& sink_name, pulse::PollMainLoop& loop, pulse::Context& ctx) noexcept { ctx.Subscribe( PA_SUBSCRIPTION_MASK_SINK, [&]() noexcept { std::clog << "pavmon: failed to subscribe to events" << std::endl; loop.Quit(1); }, [&](pa_subscription_event_type_t) noexcept { GetAndPrintVolume(sink_name, loop, ctx); }); } } // namespace int main(int argc, char* argv[]) { std::locale loc(""); std::locale::global(loc); std::cin.imbue(loc); std::cout.imbue(loc); std::cerr.imbue(loc); std::clog.imbue(loc); std::wcin.imbue(loc); std::wcout.imbue(loc); std::wcerr.imbue(loc); std::wclog.imbue(loc); std::ios_base::sync_with_stdio(false); static option long_options[] = { {"help", no_argument, nullptr, kHelpLongOption}, {"version", no_argument, nullptr, kVersionLongOption}, {nullptr, 0, nullptr, 0}, }; while (true) { int c = getopt_long(argc, argv, "", long_options, /*longindex=*/nullptr); if (c == -1) { break; } switch (c) { case kHelpLongOption: std::cout << kShortUsage << kHelp; return 0; case kVersionLongOption: std::cout << kVersionInfo; return 0; case '?': std::clog << kAskForHelp; return 1; default: std::clog << "Internal error; please report.\n"; return 1; } } if (optind != argc - 1) { std::clog << kShortUsage << kAskForHelp; return 1; } std::string sink_name = argv[1]; pulse::PollMainLoop loop; loop.HandleSignals(); auto quit_on_signal = [&]() noexcept { loop.Quit(0); }; pulse::SignalEventSource quit_on_sigterm(SIGTERM, quit_on_signal); pulse::SignalEventSource quit_on_sigint(SIGINT, quit_on_signal); pulse::PropertyList proplist; proplist.Set(PA_PROP_APPLICATION_NAME, "PulseAudio volume monitor"); proplist.Set(PA_PROP_APPLICATION_ID, "pavmon"); proplist.Set(PA_PROP_APPLICATION_VERSION, "1.0.0"); proplist.Set(PA_PROP_APPLICATION_PROCESS_ID, std::to_string(getpid())); if (const char* user = getlogin(); user != nullptr) { proplist.Set(PA_PROP_APPLICATION_PROCESS_USER, user); } // TODO(bbarenblat@gmail.com): More of these? pulse::Context ctx("PulseAudio volume meter", proplist, loop); ctx.Connect(PA_CONTEXT_NOFAIL, [&](pa_context_state_t state) noexcept { switch (state) { case PA_CONTEXT_UNCONNECTED: case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: GetAndPrintVolume(sink_name, loop, ctx); SubscribeToEvents(sink_name, loop, ctx); break; case PA_CONTEXT_TERMINATED: loop.Quit(0); break; case PA_CONTEXT_FAILED: default: std::clog << "pavmon: failed to connect to PulseAudio" << std::endl; loop.Quit(1); break; } }); return loop.Run(); }