Cycles audio/video output profiles (xrandr + pactl) on Xorg.
Project description
av-output-switcher
Cycles audio/video output profiles for lightweight Xorg window managers (it's built for a Fluxbox-style setup driven by keybinds).
A profile couples two things under one name:
- an xrandr display layout — which connected outputs are on, their resolution/refresh-rate/position, and which may act as primary; and
- a preferred PulseAudio sink (matched by regex via
pactl).
Applying a profile reconfigures the monitors and repoints default audio in
one shot. The --cycle-* commands are meant to be bound to keys: step through
valid profiles, flip the primary monitor, or rotate the default audio sink.
Platform: Xorg only. It shells out to
xrandrandxprop(X11) andpactl(PulseAudio/PipeWire's pulse shim). It does not work on Wayland.
Installation
$ uv tool install av-output-switcher
At runtime it shells out to these external commands, which must be on PATH:
xrandr— query and apply the display layout (X11)xprop— detect when the WM has settled after a layout change (X11)pactl— list sinks and switch the default audio output (PulseAudio, or PipeWire's pulse shim)
On ArchLinux these are the xorg-xrandr, xorg-xprop, and libpulse
packages.
Usage
Exactly one action per invocation:
$ av-output-switcher --state # show outputs, modes, positions
$ av-output-switcher --list-sinks # show audio sink names
$ av-output-switcher --list # list configured profile names
$ av-output-switcher --get-current-profile
$ av-output-switcher --primary-resolution # e.g. 2560x1440
$ av-output-switcher --profile NAME # apply a specific profile
$ av-output-switcher --default-profile # apply the best profile for what's
# currently connected
$ av-output-switcher --cycle-profile # advance to the next valid profile
$ av-output-switcher --cycle-primary # rotate the primary output
$ av-output-switcher --cycle-pactl-sink # rotate the default audio sink
Logging options (from sevaht-utility): -v/--verbose, -q/--quiet,
--debug, and --log-file FILE.
Example keybinds (Fluxbox ~/.fluxbox/keys)
Mod4 p :Exec av-output-switcher --cycle-profile
Mod4 o :Exec av-output-switcher --cycle-primary
Mod4 a :Exec av-output-switcher --cycle-pactl-sink
Configuration
Configuration lives in the platform user-config directory under
sevaht/av-output-switcher/:
- Linux:
~/.config/sevaht/av-output-switcher/
Create that directory and a profiles.json inside it. The rest of this
section walks through writing one from scratch.
Finding the values to put in a profile
You should never need to run xrandr or pactl yourself — the tool reports
every value a profile needs. These two commands work before any config exists.
Outputs, resolutions, refresh rates, positions, and the current primary
come from --state:
$ av-output-switcher --state
Screen 0: current 4480x1440
DP-1 connected primary 2560x1440+0+0 = [DEL] DELL S2721DGF (ABC123)
2560x1440 144.00*+ 120.00 60.00
1920x1080 144.00 60.00
HDMI-1 connected 1920x1080+2560+0 = [SAM] Samsung
1920x1080 60.00*+ 50.00
1280x720 60.00
eDP-1 disconnected
How to read it:
- The first token on each non-indented line is the output name (
DP-1,HDMI-1,eDP-1) — these are the keys underoutputsand the values forconnected_output_names. Onlyconnectedoutputs can be enabled. primarymarks the output currently acting as primary.2560x1440+0+0is the currentWIDTHxHEIGHT+X+Y: it gives both the resolution (width/height) and the position (x/y).- The indented lines list each supported resolution followed by its
available refresh rates.
*marks the rate currently in use and+marks the monitor's preferred mode, so2560x1440 144.00*+means 2560×1440 @ 144.00 Hz is both active and preferred. Pick a width/height and a refresh rate that appear together here.
Audio sink names come from --list-sinks (the * is the current
default):
$ av-output-switcher --list-sinks
* alsa_output.pci-0000_00_1f.3.analog-stereo
alsa_output.pci-0000_01_00.1.hdmi-stereo
alsa_output.usb-Generic_USB_Audio-00.analog-stereo
A profile selects audio with regexes matched against these names, so you can
either paste a full name or match a stable fragment (e.g. .*hdmi.*). Only
ALSA sinks are listed, since those are the only ones the tool can switch to.
Building a profile, step by step
Say --state shows DP-1 (2560×1440 @144) on the left and HDMI-1
(1920×1080 @60) to its right, and you want sound to come out over HDMI:
- Name the profile — add a key under
profiles(e.g."desk"). - Add each output you want on under
outputs, keyed by its name from--state. Any connected output you omit is turned off when the profile is applied. - Fill
resolutionandrefresh_ratefrom a*/+line in--state.refresh_rateis a string to preserve the exact decimal (e.g."144.00"). - Set
position— the top-left pixel coordinate of that output in the combined desktop. Put the leftmost output at{"x": 0, "y": 0}, then place the next one to its right atx= the sum of the widths to its left (2560here),y= 0 for a single top-aligned row. Stack vertically withyinstead. - Mark
primary_candidate: trueon outputs allowed to be primary (the first such output becomes primary when the profile is applied, and these are the outputs--cycle-primaryrotates through). - Set
pactl_sink_option_regexesto an ordered list of patterns from--list-sinks; the first one that matches a present sink becomes the default. Use[]to leave audio untouched.
That produces the "desk" profile in the example below.
profiles.json
{
"default_profiles": [
{
"connected_output_names": ["eDP-1"],
"profile_name": "laptop"
},
{
"connected_output_names": ["DP-1", "HDMI-1"],
"profile_name": "desk"
}
],
"profiles": {
"laptop": {
"pactl_sink_option_regexes": ["alsa_output\\..*pci.*analog.*"],
"outputs": {
"eDP-1": {
"configuration": {
"mode": {
"resolution": {"width": 1920, "height": 1080},
"refresh_rate": "60.00"
},
"position": {"x": 0, "y": 0}
},
"primary_candidate": true
}
}
},
"desk": {
"pactl_sink_option_regexes": ["alsa_output\\..*hdmi.*"],
"outputs": {
"DP-1": {
"configuration": {
"mode": {
"resolution": {"width": 2560, "height": 1440},
"refresh_rate": "144.00"
},
"position": {"x": 0, "y": 0}
},
"primary_candidate": true
},
"HDMI-1": {
"configuration": {
"mode": {
"resolution": {"width": 1920, "height": 1080},
"refresh_rate": "60.00"
},
"position": {"x": 2560, "y": 0}
},
"primary_candidate": false
}
}
}
}
}
Field reference
profiles— a map of profile name → profile. Applying a profile (via--profile,--default-profile, or--cycle-profile) enables exactly the listedoutputs; any connected output not listed is turned off.outputs— map of output name → output state. Required.configuration.mode.resolution—{"width", "height"}integers.configuration.mode.refresh_rate— refresh rate as a string (e.g."144.00"). Required; use a value shown by--state.configuration.position—{"x", "y"}integers, the output's top-left corner in the combined desktop.primary_candidate— optional, defaults tofalse. Outputs set totrueare eligible to be primary and are the set--cycle-primaryrotates through; the first one listed becomes primary on apply.
pactl_sink_option_regexes— ordered list of regexes matched against sink names from--list-sinks. On apply, the first pattern that matches a present sink becomes the default. Required key; use[]to leave audio alone.
default_profiles— optional list of rules for--default-profile. A rule (connected_output_names+profile_name) matches when all of its outputs are currently connected; the matching rule listing the most outputs wins. This is what picks the right profile automatically (e.g. at login, or when you dock/undock).
Hooks (optional)
If present and executable in the config directory, these scripts are run on changes:
on-profile-change— run after a profile is applied, with the profile name as$1(after waiting briefly for the WM to settle, detected via the root window pixmap).on-primary-output-change— run when the primary output changes, with the new output name as$1.
License
Unlicense — public domain.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file av_output_switcher-1.0.0.tar.gz.
File metadata
- Download URL: av_output_switcher-1.0.0.tar.gz
- Upload date:
- Size: 15.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc06d2a45845a904a1c67e282c34e88570f724557484428ce5949a4e8bbdf9f1
|
|
| MD5 |
8c0602d23aff80f1f84c283e6ec0d48d
|
|
| BLAKE2b-256 |
2566b732e60f49784b6da6f5bb64c42b538087ae5d3957cc367bc155832cd20e
|
Provenance
The following attestation bundles were made for av_output_switcher-1.0.0.tar.gz:
Publisher:
release.yml on sevaht/av-output-switcher
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
av_output_switcher-1.0.0.tar.gz -
Subject digest:
cc06d2a45845a904a1c67e282c34e88570f724557484428ce5949a4e8bbdf9f1 - Sigstore transparency entry: 1990760424
- Sigstore integration time:
-
Permalink:
sevaht/av-output-switcher@3746cf5c13331f5b1927519f3aedb2f01d9e089f -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/sevaht
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3746cf5c13331f5b1927519f3aedb2f01d9e089f -
Trigger Event:
push
-
Statement type:
File details
Details for the file av_output_switcher-1.0.0-py3-none-any.whl.
File metadata
- Download URL: av_output_switcher-1.0.0-py3-none-any.whl
- Upload date:
- Size: 18.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c6e194faf7ee1caf13b1989bf6d8a9da29099b8d5774317cb9db48adad87c6d6
|
|
| MD5 |
f780321afe95a07309ff7d9f7c69ab11
|
|
| BLAKE2b-256 |
1281d88bead005d0b05aceef60617677975dc77af97ee1fbc05d1ffd09645db8
|
Provenance
The following attestation bundles were made for av_output_switcher-1.0.0-py3-none-any.whl:
Publisher:
release.yml on sevaht/av-output-switcher
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
av_output_switcher-1.0.0-py3-none-any.whl -
Subject digest:
c6e194faf7ee1caf13b1989bf6d8a9da29099b8d5774317cb9db48adad87c6d6 - Sigstore transparency entry: 1990760524
- Sigstore integration time:
-
Permalink:
sevaht/av-output-switcher@3746cf5c13331f5b1927519f3aedb2f01d9e089f -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/sevaht
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3746cf5c13331f5b1927519f3aedb2f01d9e089f -
Trigger Event:
push
-
Statement type: