Add switch-pa-sink

This script can be used to switch the pulseaudio sink of an application
to the next sink in the list of sinks. The sink is found by querying the
currently focused window from i3 and retrieving the PID via _NET_WM_PID
attribute using xprop. If that PID doesn't directly have sink attached,
the script searches for children of this PID having a sink and switches
those. This is useful to switch e.g. an mpv running inside a terminal.
This commit is contained in:
MasterofJOKers 2022-01-26 00:00:57 +01:00
parent 12c3ac9c05
commit e558da4427
3 changed files with 67 additions and 0 deletions

17
switch-pa-sink/setup.cfg Normal file
View File

@ -0,0 +1,17 @@
[metadata]
name = switch-pa-sink
version = 0.1
description = Find the pulseaudio sink of currently active window in i3 and switch it to the next pulseaudio sink
[options]
scripts = switch-pa-sink
install_requires =
i3ipc
psutil
pulsectl
[flake8]
max-line-length = 120
exclude = .git,__pycache__,*.egg-info,*lib/python*
ignore = E241,E741,W503,W504

4
switch-pa-sink/setup.py Normal file
View File

@ -0,0 +1,4 @@
import setuptools
setuptools.setup()

46
switch-pa-sink/switch-pa-sink Executable file
View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
import subprocess
import i3ipc
import psutil
import pulsectl
def main():
i3 = i3ipc.Connection()
window = i3.get_tree().find_focused()
cmd = ['xprop', '-id', str(window.window), '_NET_WM_PID']
o = subprocess.run(cmd, encoding='utf-8', capture_output=True)
# _NET_WM_PID(CARDINAL) = 418472
pid = int(o.stdout.strip().split(' = ')[1])
pulse = pulsectl.Pulse('Focused sink switcher')
sink_input_by_pid = {}
for i in pulse.sink_input_list():
sink_pid = int(i.proplist['application.process.id'])
sink_input_by_pid[sink_pid] = i
if pid in sink_input_by_pid:
sink_input = sink_input_by_pid[pid]
else:
# try the childprocesses of our process, because this might be a
# terminal having started an mpv or something
p = psutil.Process(pid)
for child in p.children(recursive=True):
if child.pid in sink_input_by_pid:
sink_input = sink_input_by_pid[child.pid]
break
else:
msg = 'Did not find a pulse client for pid {} or its children.'
raise RuntimeError(msg.format(pid))
sink_index_list = [s.index for s in pulse.sink_list()]
old_sink = sink_input.sink
old_sink_index = sink_index_list.index(old_sink)
new_sink_index = sink_index_list[(old_sink_index + 1) % len(sink_index_list)]
pulse.sink_input_move(sink_input.index, new_sink_index)
if __name__ == '__main__':
main()