Skip to main content

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 xrandr and xprop (X11) and pactl (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 under outputs and the values for connected_output_names. Only connected outputs can be enabled.
  • primary marks the output currently acting as primary.
  • 2560x1440+0+0 is the current WIDTHxHEIGHT+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, so 2560x1440 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:

  1. Name the profile — add a key under profiles (e.g. "desk").
  2. 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.
  3. Fill resolution and refresh_rate from a */+ line in --state. refresh_rate is a string to preserve the exact decimal (e.g. "144.00").
  4. 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 at x = the sum of the widths to its left (2560 here), y = 0 for a single top-aligned row. Stack vertically with y instead.
  5. Mark primary_candidate: true on outputs allowed to be primary (the first such output becomes primary when the profile is applied, and these are the outputs --cycle-primary rotates through).
  6. Set pactl_sink_option_regexes to 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 listed outputs; 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 to false. Outputs set to true are eligible to be primary and are the set --cycle-primary rotates 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

av_output_switcher-1.0.0.tar.gz (15.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

av_output_switcher-1.0.0-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

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

Hashes for av_output_switcher-1.0.0.tar.gz
Algorithm Hash digest
SHA256 cc06d2a45845a904a1c67e282c34e88570f724557484428ce5949a4e8bbdf9f1
MD5 8c0602d23aff80f1f84c283e6ec0d48d
BLAKE2b-256 2566b732e60f49784b6da6f5bb64c42b538087ae5d3957cc367bc155832cd20e

See more details on using hashes here.

Provenance

The following attestation bundles were made for av_output_switcher-1.0.0.tar.gz:

Publisher: release.yml on sevaht/av-output-switcher

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file av_output_switcher-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for av_output_switcher-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c6e194faf7ee1caf13b1989bf6d8a9da29099b8d5774317cb9db48adad87c6d6
MD5 f780321afe95a07309ff7d9f7c69ab11
BLAKE2b-256 1281d88bead005d0b05aceef60617677975dc77af97ee1fbc05d1ffd09645db8

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page