#!/usr/bin/env python3
-'Simple wrapper for manipulating backlight device brightness file(s).'
-from argparse import ArgumentError, ArgumentParser
+'Wrapper for manipulating backlight device brightness file(s).'
from pathlib import Path
+from lib.argparsing import ArgParser
+
+APP_DESC = 'Set backlight strength.'
FILENAME_MAX = 'max_brightness'
FILENAME_TARGET = 'brightness'
PATH_BACKLIGHTS = '/sys/class/backlight'
-PERCENTAGE_MIN = 0
-PERCENTAGE_MAX = 100
-APP_DESC = 'Set backlight strength.'
+PERCENTAGE_MAX = 100
ARG_NAME = 'strength'
ARG_HELP = f'percentage of possible maximum (default: {PERCENTAGE_MAX})'
-ARG_ERR_RANGE = f'must be >={PERCENTAGE_MIN}, <={PERCENTAGE_MAX}'
if __name__ == '__main__':
- parser = ArgumentParser(description=APP_DESC)
- arg = parser.add_argument(
- ARG_NAME,
- metavar=ARG_NAME.upper(),
- type=int,
- nargs='?',
- default=PERCENTAGE_MAX,
- help=ARG_HELP
- )
- args = parser.parse_args()
- target_percentage = getattr(args, ARG_NAME)
- if not PERCENTAGE_MIN <= target_percentage <= PERCENTAGE_MAX:
- parser.error(str(ArgumentError(arg, ARG_ERR_RANGE)))
+ parser = ArgParser(APP_DESC)
+ parser.add_arg_ranged_int(ARG_NAME, PERCENTAGE_MAX,
+ help=ARG_HELP, default=PERCENTAGE_MAX)
+ target_percentage = getattr(parser.args, ARG_NAME)
for path_backlight in Path(PATH_BACKLIGHTS).iterdir():
path_max = Path(path_backlight).joinpath(FILENAME_MAX)
path_target = Path(path_backlight).joinpath(FILENAME_TARGET)
--- /dev/null
+'Some conveniences around argument parsing.'
+from argparse import Action, ArgumentParser, ArgumentError, Namespace
+from typing import Optional
+
+_INT_MIN = 0
+
+
+class ArgParser:
+ 'Wrapper around argparse.ArgumentParser.'
+ _args: Optional[Namespace] = None
+
+ def __init__(
+ self,
+ app_desc: str
+ ) -> None:
+ self._parser = ArgumentParser(description=app_desc)
+
+ def add_arg(
+ self,
+ arg_name,
+ *args,
+ **kwargs
+ ) -> Action:
+ 'Public access to ._parser.add_argument().'
+ return self._parser.add_argument(arg_name, *args, **kwargs)
+
+ @property
+ def args(
+ self
+ ) -> Namespace:
+ 'Result of ._parser.parse_args(), running same if not done yet.'
+ if self._args is None:
+ self._args = self._parser.parse_args()
+ return self._args
+
+ def add_arg_ranged_int(
+ self,
+ arg_name: str,
+ int_max: int,
+ **kwargs
+ ) -> None:
+ 'Add argument for int ranged >= _INT_MIN, <= int_max.'
+ def int_ranged(
+ arg: str
+ ) -> int:
+ err = ArgumentError(
+ action,
+ f'must be int >={_INT_MIN}, <={int_max}')
+ try:
+ arg_int = int(arg)
+ except ValueError as exc:
+ raise err from exc
+ if not _INT_MIN <= arg_int <= int_max:
+ raise err
+ return arg_int
+ action = self.add_arg(arg_name,
+ metavar=arg_name.upper(),
+ type=int_ranged,
+ nargs='?',
+ **kwargs)
--- /dev/null
+#!/usr/bin/env python3
+'Wrapper for manipulating audio device muteness and volume.'
+from subprocess import CompletedProcess, run as subprocess_run
+from lib.argparsing import ArgParser
+
+APP_DESC = 'Set audio device volume, muteness.'
+
+VOL_AT_100 = 65536
+DEVICES = (('sink', 'speaker', '@DEFAULT_SINK@'),
+ ('source', 'microphone', '@DEFAULT_SOURCE@'))
+
+PERCENTAGE_MAX = 150
+ARG_NAME = 'volume'
+ARG_HELP = 'percentage of possible maximum; if omitted: toggle muteness'
+
+if __name__ == '__main__':
+ parser = ArgParser(APP_DESC)
+ parser.add_arg_ranged_int(ARG_NAME, PERCENTAGE_MAX, help=ARG_HELP)
+ parser.add_arg('--mic',
+ action='store_true',
+ help='manipulate microphone rather than speaker')
+ dev_type, dev_name, dev_target = DEVICES[int(parser.args.mic)]
+ target_percentage = getattr(parser.args, ARG_NAME)
+
+ def pactl(
+ verb: str,
+ target: str,
+ bonus_arg='',
+ **run_kwargs
+ ) -> CompletedProcess:
+ 'Run pactl command via subprocess_run.'
+ return subprocess_run(
+ ['pactl', f'{verb}-{dev_type}-{target}', dev_target
+ ] + ([bonus_arg] if bonus_arg else []),
+ check=True,
+ **run_kwargs)
+
+ toks = pactl('get', 'mute', capture_output=True, text=True
+ ).stdout.strip().split()
+ assert (len(toks) == 2
+ and toks[0] == 'Mute:'
+ and toks[1] in {'yes', 'no'}), toks
+ was_mute = toks[1] == 'yes'
+ if target_percentage is None:
+ pactl('set', 'mute', str(int(not was_mute)))
+ print(f'{dev_name} muteness toggled {"off" if was_mute else "on"}.')
+ else:
+ target_abs = int((target_percentage / 100) * VOL_AT_100)
+ pactl('set', 'volume', str(target_abs))
+ print(f'{dev_name} volume set to {target_percentage}% ({target_abs})'
+ + (' (muted)' if was_mute else ''))
sway
wmenu
xwayland
+# multimedia
+pulseaudio
# last line ignored