summaryrefslogtreecommitdiff
path: root/pavmon.cc
diff options
context:
space:
mode:
authorGravatar Benjamin Barenblat <bbarenblat@gmail.com>2022-01-27 11:10:37 -0500
committerGravatar Benjamin Barenblat <bbarenblat@gmail.com>2022-01-27 11:10:37 -0500
commit61c9951dae64e79cbc1983098b5fd52317578103 (patch)
treec8d4c2c4fb7eacd660dccbde09b6cc216e2ccee3 /pavmon.cc
pavmon, a simple tool to monitor the volume of a PulseAudio sinkHEADmain
Diffstat (limited to 'pavmon.cc')
-rw-r--r--pavmon.cc184
1 files changed, 184 insertions, 0 deletions
diff --git a/pavmon.cc b/pavmon.cc
new file mode 100644
index 0000000..df639e4
--- /dev/null
+++ b/pavmon.cc
@@ -0,0 +1,184 @@
+// 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 <getopt.h>
+#include <pulse/def.h>
+#include <pulse/error.h>
+#include <pulse/introspect.h>
+#include <pulse/volume.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <iomanip>
+#include <ios>
+#include <iostream>
+#include <locale>
+#include <string>
+#include <string_view>
+
+#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();
+}