home · contact · privacy
Add pulseaudio and a tool for setting mute, volume; refactor with backlight script.
authorPlom Heller <plom@plomlompom.com>
Thu, 17 Sep 2026 02:41:51 +0000 (04:41 +0200)
committerPlom Heller <plom@plomlompom.com>
Thu, 17 Sep 2026 02:41:51 +0000 (04:41 +0200)
home/user/.local/bin/backlight
home/user/.local/bin/lib/__init__.py [new file with mode: 0644]
home/user/.local/bin/lib/argparsing.py [new file with mode: 0644]
home/user/.local/bin/vol [new file with mode: 0755]
to_install/t490s

index 6273512ec829852dfa9409d79c0d18b9d471e051..33b2826dc647dbef048b3730fc1586c75406f5d0 100755 (executable)
@@ -1,33 +1,23 @@
 #!/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)
diff --git a/home/user/.local/bin/lib/__init__.py b/home/user/.local/bin/lib/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/home/user/.local/bin/lib/argparsing.py b/home/user/.local/bin/lib/argparsing.py
new file mode 100644 (file)
index 0000000..f1bb54c
--- /dev/null
@@ -0,0 +1,60 @@
+'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)
diff --git a/home/user/.local/bin/vol b/home/user/.local/bin/vol
new file mode 100755 (executable)
index 0000000..a1f70b3
--- /dev/null
@@ -0,0 +1,51 @@
+#!/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 ''))
index cff3404f6d53848078c297be42d6b7dc6a400268..e13553dbc92bfb6eb4514895a2995fa81f274783 100644 (file)
@@ -21,4 +21,6 @@ vim-gtk3
 sway
 wmenu
 xwayland
+# multimedia
+pulseaudio
 # last line ignored