aab259b826
If increasing would exceed the volume, set it to exactly the limit instead. Also, if channels have different volumes, ensure that the higher channel does not exceed the volume. fixes #354
169 lines
6.1 KiB
Python
169 lines
6.1 KiB
Python
# pylint: disable=C0111,R0903
|
|
|
|
"""Displays volume and mute status and controls for PulseAudio devices. Use wheel up and down to change volume, left click mutes, right click opens pavucontrol.
|
|
|
|
Aliases: pasink (use this to control output instead of input), pasource
|
|
|
|
Parameters:
|
|
* pulseaudio.autostart: If set to "true" (default is "false"), automatically starts the pulseaudio daemon if it is not running
|
|
* pulseaudio.percent_change: How much to change volume by when scrolling on the module (default is 2%)
|
|
* pulseaudio.limit: Upper limit for setting the volume (default is 0%, which means "no limit")
|
|
Note: If the left and right channels have different volumes, the limit might not be reached exactly.
|
|
|
|
Requires the following executable:
|
|
* pulseaudio
|
|
* pactl
|
|
* pavucontrol
|
|
* pacmd
|
|
"""
|
|
|
|
import re
|
|
|
|
import bumblebee.util
|
|
import bumblebee.input
|
|
import bumblebee.output
|
|
import bumblebee.engine
|
|
|
|
ALIASES = ["pasink", "pasource"]
|
|
|
|
class Module(bumblebee.engine.Module):
|
|
def __init__(self, engine, config):
|
|
super(Module, self).__init__(engine, config,
|
|
bumblebee.output.Widget(full_text=self.volume)
|
|
)
|
|
try:
|
|
if bumblebee.util.asbool(self.parameter("autostart", False)):
|
|
bumblebee.util.execute("pulseaudio --start")
|
|
except Exception:
|
|
pass
|
|
|
|
self._change = 2
|
|
self._change = int(self.parameter("percent_change", "2%").strip("%"))
|
|
if self._change < 0 or self._change > 100:
|
|
self._change = 2
|
|
|
|
self._limit = 0
|
|
self._limit = int(self.parameter("limit", "0%").strip("%s"))
|
|
if self._limit < 0:
|
|
self._limit = 0
|
|
|
|
self._left = 0
|
|
self._right = 0
|
|
self._mono = 0
|
|
self._mute = False
|
|
self._failed = False
|
|
self._channel = "sink" if self.name == "pasink" else "source"
|
|
|
|
self._patterns = [
|
|
{"expr": "name:", "callback": (lambda line: False)},
|
|
{"expr": "muted:", "callback": (lambda line: self.mute(False if " no" in line.lower() else True))},
|
|
{"expr": "volume:", "callback": self.getvolume},
|
|
]
|
|
|
|
engine.input.register_callback(self, button=bumblebee.input.RIGHT_MOUSE, cmd="pavucontrol")
|
|
|
|
events = [
|
|
{"type": "mute", "action": self.toggle, "button": bumblebee.input.LEFT_MOUSE},
|
|
{"type": "volume", "action": self.increase_volume, "button": bumblebee.input.WHEEL_UP},
|
|
{"type": "volume", "action": self.decrease_volume, "button": bumblebee.input.WHEEL_DOWN},
|
|
]
|
|
|
|
for event in events:
|
|
engine.input.register_callback(self, button=event["button"], cmd=event["action"])
|
|
|
|
def set_volume(self, amount):
|
|
bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format(
|
|
self._channel, "volume", self._channel.upper(), amount))
|
|
|
|
def increase_volume(self, event):
|
|
if self._limit > 0: # we need to check the limit
|
|
left = int(self._left)
|
|
right = int(self._right)
|
|
if left + self._change >= self._limit or right + self._change >= self._limit:
|
|
if left == right:
|
|
# easy case, just set to limit
|
|
self.set_volume("{}%".format(self._limit))
|
|
return
|
|
else:
|
|
# don't adjust anymore, since i don't know how to update only one channel
|
|
return
|
|
|
|
self.set_volume("+{}%".format(self._change))
|
|
|
|
def decrease_volume(self, event):
|
|
self.set_volume("-{}%".format(self._change))
|
|
|
|
def toggle(self, event):
|
|
bumblebee.util.execute("pactl set-{}-{} @DEFAULT_{}@ {}".format(
|
|
self._channel, "mute", self._channel.upper(), "toggle"))
|
|
|
|
def mute(self, value):
|
|
self._mute = value
|
|
|
|
def getvolume(self, line):
|
|
if "mono" in line:
|
|
m = re.search(r'mono:.*\s*\/\s*(\d+)%', line)
|
|
if m:
|
|
self._mono = m.group(1)
|
|
else:
|
|
m = re.search(r'left:.*\s*\/\s*(\d+)%.*right:.*\s*\/\s*(\d+)%', line)
|
|
if m:
|
|
self._left = m.group(1)
|
|
self._right = m.group(2)
|
|
return True
|
|
|
|
def _default_device(self):
|
|
output = bumblebee.util.execute("pacmd stat")
|
|
pattern = "Default sink name: " if self.name == "pasink" else "Default source name: "
|
|
for line in output.split("\n"):
|
|
if line.startswith(pattern):
|
|
return line.replace(pattern, "")
|
|
return "n/a"
|
|
|
|
def volume(self, widget):
|
|
if self._failed == True:
|
|
return "n/a"
|
|
if int(self._mono) > 0:
|
|
return "{}%".format(self._mono)
|
|
elif self._left == self._right:
|
|
return "{}%".format(self._left)
|
|
else:
|
|
return "{}%/{}%".format(self._left, self._right)
|
|
|
|
def update(self, widgets):
|
|
try:
|
|
self._failed = False
|
|
channel = "sinks" if self.name == "pasink" else "sources"
|
|
device = self._default_device()
|
|
|
|
result = bumblebee.util.execute("pacmd list-{}".format(channel))
|
|
found = False
|
|
|
|
for line in result.split("\n"):
|
|
if "<"+device+">" in line:
|
|
found = True
|
|
continue
|
|
if found is False:
|
|
continue
|
|
for pattern in self._patterns:
|
|
if not pattern["expr"] in line:
|
|
continue
|
|
if pattern["callback"](line) is False and found == True:
|
|
return
|
|
except Exception:
|
|
self._failed = True
|
|
if bumblebee.util.asbool(self.parameter("autostart", False)):
|
|
try:
|
|
bumblebee.util.execute("pulseaudio --start")
|
|
self.update(widgets)
|
|
except Exception:
|
|
pass
|
|
|
|
def state(self, widget):
|
|
if self._mute:
|
|
return ["warning", "muted"]
|
|
if int(self._left) > int(100):
|
|
return ["critical", "unmuted"]
|
|
return ["unmuted"]
|
|
|
|
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
|