extended pulseaudio module

* added possibility to show currently selected default device in the
  statusbar (default: off)
* allows to override the left mouse button click with a different
  action (e.g open popup menu to change the current default device)
This commit is contained in:
Bernhard B 2022-03-24 21:19:52 +01:00
parent 7932af712e
commit 82fa347f2c

View file

@ -11,6 +11,20 @@ Parameters:
Note: If the left and right channels have different volumes, the limit might not be reached exactly. Note: If the left and right channels have different volumes, the limit might not be reached exactly.
* pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango; * pulseaudio.showbars: 1 for showing volume bars, requires --markup=pango;
0 for not showing volume bars (default) 0 for not showing volume bars (default)
* pulseaudio.showdevicename: If set to 'true' (default is 'false'), the currently selected default device is shown.
Per default, the sink/source name returned by "pactl list sinks short" is used as display name.
As this name is usually not particularly nice (e.g "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo"),
its possible to map the name to more a user friendly name.
e.g to map "alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" to the name "Headset", add the following
bumblebee-status config entry: pulseaudio.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=Headset
Furthermore its possible to specify individual (unicode) icons for all sinks/sources. e.g in order to use the icon 🎧 for the
"alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo" sink, add the following bumblebee-status config entry:
pulseaudio.icon.alsa_output.usb-Logitech_Logitech_USB_Headset-00.analog-stereo=🎧
* Per default a left mouse button click mutes/unmutes the device. In case you want to open a dropdown menu to change the current
default device add the following config entry to your bumblebee-status config: pulseaudio.left-click=select_default_device_popup
Requires the following executable: Requires the following executable:
* pulseaudio * pulseaudio
@ -20,6 +34,7 @@ Requires the following executable:
import re import re
import logging import logging
import functools
import core.module import core.module
import core.widget import core.widget
@ -28,11 +43,12 @@ import core.input
import util.cli import util.cli
import util.graph import util.graph
import util.format import util.format
import util.popup
class Module(core.module.Module): class Module(core.module.Module):
def __init__(self, config, theme, channel): def __init__(self, config, theme, channel):
super().__init__(config, theme, core.widget.Widget(self.volume)) super().__init__(config, theme, core.widget.Widget(self.display))
if util.format.asbool(self.parameter("autostart", False)): if util.format.asbool(self.parameter("autostart", False)):
util.cli.execute("pulseaudio --start", ignore_errors=True) util.cli.execute("pulseaudio --start", ignore_errors=True)
@ -48,7 +64,11 @@ class Module(core.module.Module):
self._mute = False self._mute = False
self._failed = False self._failed = False
self._channel = channel self._channel = channel
self.__selected_default_device = None
self._showbars = util.format.asbool(self.parameter("showbars", 0)) self._showbars = util.format.asbool(self.parameter("showbars", 0))
self.__show_device_name = util.format.asbool(
self.parameter("showdevicename", False)
)
self._patterns = [ self._patterns = [
{"expr": "Name:", "callback": (lambda line: False)}, {"expr": "Name:", "callback": (lambda line: False)},
@ -138,19 +158,19 @@ class Module(core.module.Module):
logging.error("no pulseaudio device found") logging.error("no pulseaudio device found")
return "n/a" return "n/a"
def volume(self, widget): def display(self, widget):
if self._failed == True: if self._failed == True:
return "n/a" return "n/a"
vol = None
if int(self._mono) > 0: if int(self._mono) > 0:
vol = "{}%".format(self._mono) vol = "{}%".format(self._mono)
if self._showbars: if self._showbars:
vol = "{} {}".format(vol, util.graph.hbar(float(self._mono))) vol = "{} {}".format(vol, util.graph.hbar(float(self._mono)))
return vol
elif self._left == self._right: elif self._left == self._right:
vol = "{}%".format(self._left) vol = "{}%".format(self._left)
if self._showbars: if self._showbars:
vol = "{} {}".format(vol, util.graph.hbar(float(self._left))) vol = "{} {}".format(vol, util.graph.hbar(float(self._left)))
return vol
else: else:
vol = "{}%/{}%".format(self._left, self._right) vol = "{}%/{}%".format(self._left, self._right)
if self._showbars: if self._showbars:
@ -159,19 +179,31 @@ class Module(core.module.Module):
util.graph.hbar(float(self._left)), util.graph.hbar(float(self._left)),
util.graph.hbar(float(self._right)), util.graph.hbar(float(self._right)),
) )
return vol
output = vol
if self.__show_device_name:
friendly_name = self.parameter(
self.__selected_default_device, self.__selected_default_device
)
icon = self.parameter("icon." + self.__selected_default_device, "")
output = (
icon + " " + friendly_name + " | " + vol
if icon != ""
else friendly_name + " | " + vol
)
return output
def update(self): def update(self):
try: try:
self._failed = False self._failed = False
channel = "sinks" if self._channel == "sink" else "sources" channel = "sinks" if self._channel == "sink" else "sources"
device = self._default_device() self.__selected_default_device = self._default_device()
result = util.cli.execute("pactl list {}".format(channel)) result = util.cli.execute("pactl list {}".format(channel))
found = False found = False
for line in result.split("\n"): for line in result.split("\n"):
if "Name: {}".format(device) in line: if "Name: {}".format(self.__selected_default_device) in line:
found = True found = True
continue continue
if found is False: if found is False:
@ -189,6 +221,29 @@ class Module(core.module.Module):
else: else:
raise e raise e
def __on_sink_selected(self, sink_name):
util.cli.execute("pactl set-default-{} {}".format(self._channel, sink_name))
def select_default_device_popup(self, widget):
channel = "sinks" if self._channel == "sink" else "sources"
result = util.cli.execute("pactl list {} short".format(channel))
menu = util.popup.menu()
lines = result.splitlines()
for line in lines:
info = line.split("\t")
try:
friendly_name = self.parameter(info[1], info[1])
menu.add_menuitem(
friendly_name,
callback=functools.partial(self.__on_sink_selected, info[1]),
)
except:
logging.exception("Couldn't parse {}".format(channel))
pass
menu.show(widget)
def state(self, widget): def state(self, widget):
if self._mute: if self._mute:
return ["warning", "muted"] return ["warning", "muted"]