# HG changeset patch # User drewp@bigasterisk.com # Date 1677907493 28800 # Node ID 04c8a1b3976b43848bb5652eee7a89416de5ee46 # Parent 05bdded21d387ef5f0309d724aebae6758142c33 volume r/w on linux+osx diff -r 05bdded21d38 -r 04c8a1b3976b pdm.lock --- a/pdm.lock Fri Mar 03 18:33:19 2023 -0800 +++ b/pdm.lock Fri Mar 03 21:24:53 2023 -0800 @@ -53,6 +53,14 @@ summary = "Internationalized Domain Names in Applications (IDNA)" [[package]] +name = "osascript" +version = "2020.12.3" +summary = "" +dependencies = [ + "runcmd", +] + +[[package]] name = "prometheus-client" version = "0.16.0" requires_python = ">=3.6" @@ -65,12 +73,34 @@ summary = "Cross-platform lib for process and system monitoring in Python." [[package]] +name = "pulsectl" +version = "22.3.2" +summary = "Python high-level interface and ctypes-based bindings for PulseAudio (libpulse)" + +[[package]] +name = "pulsectl-asyncio" +version = "1.0.0" +requires_python = ">=3.6" +summary = "Asyncio frontend for the pulsectl Python bindings of libpulse" +dependencies = [ + "pulsectl<=22.3.2,>=21.10.2", +] + +[[package]] name = "pycparser" version = "2.21" requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" summary = "C parser in Python" [[package]] +name = "runcmd" +version = "2020.12.3" +summary = "" +dependencies = [ + "psutil", +] + +[[package]] name = "sniffio" version = "1.3.0" requires_python = ">=3.7" @@ -114,7 +144,7 @@ [metadata] lock_version = "4.0" -content_hash = "sha256:37aab9383d44d97e9e9296176d348df2527022c22eda65680036ee05e71d5bae" +content_hash = "sha256:154a0840707c5c174819c62d9d6fe17c300e92fada090c6125686011e9ca2bc8" [metadata.files] "anyio 3.6.2" = [ @@ -206,6 +236,9 @@ {url = "https://files.pythonhosted.org/packages/8b/e1/43beb3d38dba6cb420cefa297822eac205a277ab43e5ba5d5c46faf96438/idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, {url = "https://files.pythonhosted.org/packages/fc/34/3030de6f1370931b9dbb4dad48f6ab1015ab1d32447850b9fc94e60097be/idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, ] +"osascript 2020.12.3" = [ + {url = "https://files.pythonhosted.org/packages/15/21/36f43fde613eefa23e8f569a4020964c5417cd8fad7b069383717d2a57b0/osascript-2020.12.3.tar.gz", hash = "sha256:36d3f30fb6c43c868c70d1fb06e4c970844a5cbe0a481b9fbd5059398916e069"}, +] "prometheus-client 0.16.0" = [ {url = "https://files.pythonhosted.org/packages/5b/8e/6a546e439b4366ab9eab0a736876eb1e1916dd93b4a1fa560ef711d24f8c/prometheus_client-0.16.0-py3-none-any.whl", hash = "sha256:0836af6eb2c8f4fed712b2f279f6c0a8bbab29f9f4aa15276b91c7cb0d1616ab"}, {url = "https://files.pythonhosted.org/packages/d0/55/9e34c73e1e490b105b4cd13d08497b1f7cb086a260e4161b7b7c2928b196/prometheus_client-0.16.0.tar.gz", hash = "sha256:a03e35b359f14dd1630898543e2120addfdeacd1a6069c1367ae90fd93ad3f48"}, @@ -226,10 +259,21 @@ {url = "https://files.pythonhosted.org/packages/a5/73/35cea01aad1baf901c915dc95ea33a2f271c8ff8cf2f1c73b7f591f1bdf1/psutil-5.9.4-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:54d5b184728298f2ca8567bf83c422b706200bcbbfafdc06718264f9393cfeb7"}, {url = "https://files.pythonhosted.org/packages/ec/be/b8df2071eda861e65a1b2cec35770bb1f4523737e84a10aa41c53e39e9bc/psutil-5.9.4-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6b92c532979bafc2df23ddc785ed116fced1f492ad90a6830cf24f4d1ea27d24"}, ] +"pulsectl 22.3.2" = [ + {url = "https://files.pythonhosted.org/packages/38/b5/7fb041271e9db6ad54a74eecbc84f8c9a9faf696c267b63dfc9fbbf63d49/pulsectl-22.3.2.tar.gz", hash = "sha256:cc174e1ceebd4e6222c5b78f4f415f12e80753c9ab75ab354159b4cb5950b084"}, + {url = "https://files.pythonhosted.org/packages/e7/dc/53454b4d9f52f53aa5d95b0c5aa1adaf80e324f760e9086db4e2de2dab24/pulsectl-22.3.2-py2.py3-none-any.whl", hash = "sha256:6fded96a8e6a9ac6c387ed3a9bdb1afb20d80256b0c9df4b98b58461d74ba0c4"}, +] +"pulsectl-asyncio 1.0.0" = [ + {url = "https://files.pythonhosted.org/packages/02/98/b5618e70db2279c9d7f604283df21eadd0d0a5e878bc5a0b9fd85e14fc96/pulsectl-asyncio-1.0.0.tar.gz", hash = "sha256:46ef1573a17fb064196e8d8f0d9031a9377f717cde26769d97959ce4b9395af0"}, + {url = "https://files.pythonhosted.org/packages/41/4d/b973e74ed465735fc230089cc9167152f692d7f6221ffccf35a3b3156e7f/pulsectl_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:5842fe428da9ede62077469f15dedd7c19f1e0085a641f22e2266449fd394dd2"}, +] "pycparser 2.21" = [ {url = "https://files.pythonhosted.org/packages/5e/0b/95d387f5f4433cb0f53ff7ad859bd2c6051051cebbb564f139a999ab46de/pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, {url = "https://files.pythonhosted.org/packages/62/d5/5f610ebe421e85889f2e55e33b7f9a6795bd982198517d912eb1c76e1a53/pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, ] +"runcmd 2020.12.3" = [ + {url = "https://files.pythonhosted.org/packages/41/b7/08c18bc2944c0443aa77672e5610e861c6946f93f247bef0aa8d7737b248/runcmd-2020.12.3.tar.gz", hash = "sha256:1f2652c2e51404bb0faaa83610f23fdafd4c246b46e361a5dd6d4334d013fc24"}, +] "sniffio 1.3.0" = [ {url = "https://files.pythonhosted.org/packages/c3/a0/5dba8ed157b0136607c7f2151db695885606968d1fae123dc3391e0cfdbf/sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {url = "https://files.pythonhosted.org/packages/cd/50/d49c388cae4ec10e8109b1b833fd265511840706808576df3ada99ecb0ac/sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, diff -r 05bdded21d38 -r 04c8a1b3976b pyproject.toml --- a/pyproject.toml Fri Mar 03 18:33:19 2023 -0800 +++ b/pyproject.toml Fri Mar 03 21:24:53 2023 -0800 @@ -12,6 +12,8 @@ "background-loop>=1.7.0", "starlette-exporter>=0.15.1", "xprintidle>=0.2", + "pulsectl-asyncio>=1.0.0", + "osascript>=2020.12.3", ] requires-python = ">=3.10" license = {text = "MIT"} diff -r 05bdded21d38 -r 04c8a1b3976b racc.py --- a/racc.py Fri Mar 03 18:33:19 2023 -0800 +++ b/racc.py Fri Mar 03 21:24:53 2023 -0800 @@ -16,8 +16,10 @@ import sys if sys.implementation._multiarch == 'darwin': import idle_osx as idle + import volume_osx as volume elif 'linux' in sys.implementation._multiarch: import idle_linux as idle + import volume_linux as volume else: raise NotImplementedError(repr(sys.implementation)) @@ -61,8 +63,9 @@ RACC_IDLE.labels(host=hostname).set(idle.get_idle_seconds()) -def root(req: Request) -> HTMLResponse: - return HTMLResponse(f'''controls for {hostname}''') +async def root(req: Request) -> HTMLResponse: + vol = await volume.get_volume() + return HTMLResponse(f'''controls for {hostname} whose volume is {vol}''') def main(): diff -r 05bdded21d38 -r 04c8a1b3976b volume_linux.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/volume_linux.py Fri Mar 03 21:24:53 2023 -0800 @@ -0,0 +1,30 @@ +from pulsectl_asyncio import PulseAsync + +_pulse = None + + +async def _init(): + global _pulse + if _pulse is not None: + return + _pulse = PulseAsync('racc') + await _pulse.connect() + + +async def _get_sink(): + [sink] = [ + s for s in (await _pulse.sink_list()) + if s.description == 'Built-in Audio Analog Stereo' + ] + return sink + + +async def get_volume(): + await _init() + return (await _get_sink()).volume.value_flat + + +async def set_volume(v: float): + await _init() + sync = await _get_sink() + await _pulse.volume_set_all_chans(sink, v) diff -r 05bdded21d38 -r 04c8a1b3976b volume_osx.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/volume_osx.py Fri Mar 03 21:24:53 2023 -0800 @@ -0,0 +1,11 @@ +from osascript import osascript + + +async def get_volume(): + out = osascript('output volume of (get volume settings)') + return int(out[1]) / 100 + + +async def set_volume(v: float): + i = round(v * 100) + osascript(f"set volume output volume {i}")