Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace pacmd calls with pactl to support Pipewire [final version] #836

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 27 additions & 25 deletions i3pystatus/pulseaudio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import re
import subprocess

Expand Down Expand Up @@ -46,7 +45,7 @@ class PulseAudio(Module, ColorRangeModule):
("format_muted", "optional format string to use when muted"),
("format_selected", "string used to mark this sink if selected"),
"muted", "unmuted",
"color_muted", "color_unmuted",
"color_error", "color_muted", "color_unmuted",
("step", "percentage to increment volume on scroll"),
("sink", "sink name to use, None means pulseaudio default"),
("move_sink_inputs", "Move all sink inputs when we change the default sink"),
Expand All @@ -64,6 +63,7 @@ class PulseAudio(Module, ColorRangeModule):
format_selected = " 🗸"
currently_muted = False
has_amixer = False
color_error = "#FF0000"
color_muted = "#FF0000"
color_unmuted = "#FFFFFF"
vertical_bar_glyphs = None
Expand Down Expand Up @@ -120,8 +120,7 @@ def current_sink(self):
if self.sink is not None:
return self.sink

self.sinks = subprocess.check_output(['pactl', 'list', 'short', 'sinks'],
universal_newlines=True).splitlines()
self.sinks = self._call_pactl(["list", "short", "sinks"]).splitlines()
bestsink = None
state = 'DEFAULT'
for sink in self.sinks:
Expand Down Expand Up @@ -191,19 +190,17 @@ def sink_info_cb(self, context, sink_info_p, eol, _):
output_format = self.format

if self.bar_type == 'vertical':
volume_bar = make_vertical_bar(volume_percent, self.vertical_bar_width, glyphs=self.vertical_bar_glyphs)
volume_bar = make_vertical_bar(volume_percent, self.vertical_bar_width,
glyphs=self.vertical_bar_glyphs)
elif self.bar_type == 'horizontal':
volume_bar = make_bar(volume_percent)
else:
raise Exception("bar_type must be 'vertical' or 'horizontal'")

selected = ""
dump = subprocess.check_output("pacmd dump".split(), universal_newlines=True)
for line in dump.split("\n"):
if line.startswith("set-default-sink"):
default_sink = line.split()[1]
if default_sink == self.current_sink:
selected = self.format_selected
default_sink = self._call_pactl(["get-default-sink"]).strip()
if default_sink == self.current_sink:
selected = self.format_selected

self.output = {
"color": color,
Expand All @@ -223,28 +220,33 @@ def sink_info_cb(self, context, sink_info_p, eol, _):
def change_sink(self):
sinks = list(s.split()[1] for s in self.sinks)
if self.sink is None:
next_sink = sinks[(sinks.index(self.current_sink) + 1) %
len(sinks)]
next_sink = sinks[(sinks.index(self.current_sink) + 1) % len(sinks)]
else:
next_sink = self.current_sink

if self.move_sink_inputs:
sink_inputs = subprocess.check_output("pacmd list-sink-inputs".split(),
universal_newlines=True)
sink_inputs = self._call_pactl(["list-sink-inputs"])
for input_index in re.findall(r'index:\s+(\d+)', sink_inputs):
command = "pacmd move-sink-input {} {}".format(input_index, next_sink)

# Not all applications can be moved and pulseaudio, and when
# this fail pacmd print error messaging
with open(os.devnull, 'w') as devnull:
subprocess.call(command.split(), stdout=devnull)
Comment on lines -237 to -240
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the code came from #552. Since this PR uses the method _call_pactl instead that internally uses subprocess.check_output, the messages on stdout that could come from pactl will always be discarded.

subprocess.call("pacmd set-default-sink {}".format(next_sink).split())
pactl_args = ["move-sink-input", input_index, next_sink]
self._call_pactl(pactl_args)
self._call_pactl(["set-default-sink", next_sink])

def _call_pactl(self, pactl_arguments):
try:
output = subprocess.check_output(
["pactl"] + [str(arg) for arg in pactl_arguments],
universal_newlines=True)
return output
except Exception:
self.logger.exception("Error while executing pactl")
self.output = {"color": self.color_error, "full_text": "Error while executing pactl"}
self.send_output()

def switch_mute(self):
subprocess.call(['pactl', '--', 'set-sink-mute', self.current_sink, "toggle"])
self._call_pactl(["--", "set-sink-mute", self.current_sink, "toggle"])

def increase_volume(self):
subprocess.call(['pactl', '--', 'set-sink-volume', self.current_sink, "+%s%%" % self.step])
self._call_pactl(["--", "set-sink-volume", self.current_sink, f"+{self.step}%"])

def decrease_volume(self):
subprocess.call(['pactl', '--', 'set-sink-volume', self.current_sink, "-%s%%" % self.step])
self._call_pactl(["--", "set-sink-volume", self.current_sink, f"-{self.step}%"])