A better xrandr command-line experience
Tags: linux, x11, xmonad, bash
Published: 2023-10-19 21:36:25

---
title: A better xrandr command-line experience
published: true
date: 2020-10-11 00:00:00 UTC
tags: #linux, #x11, #xmonad, #bash
canonical_url: https://work.lisk.in/2020/10/11/xrandr-ux.html
---

Can we make a small improvement to [xrandr](https://manpages.debian.org/unstable/x11-xserver-utils/xrandr.1.en.html) command-line user experience so that extra tools like [arandr](https://christian.amsuess.com/tools/arandr/) (GUI for xrandr) or [autorandr](https://github.com/phillipberndt/autorandr) become unnecessary for some people (like me)? Yes, I think that making the `--output` option a bit more powerful goes a long way, and lets me cover most use-cases with just four shell functions/aliases.

```bash
function layout-vertical {
  xrandr-smart --output 'eDP-*' --auto \
               --output '!(eDP-*)' --auto --above 'eDP-*'
}
function layout-horizontal {
  xrandr-smart --output 'eDP-*' --auto \
               --output '!(eDP-*)' --auto --right-of 'eDP-*'
}
function layout-clone {
  xrandr-smart --output 'eDP-*' --auto \
               --output '!(eDP-*)' --auto --same-as 'eDP-*'
}
function layout-extonly {
  xrandr-smart --output '!(eDP-*)' --auto
}

```
<figcaption>functions/aliases I wish I could have</figcaption>

### Table of Contents
- [TL;DR](#tldr)
- [What? Why?](#what-why)
- [Behind the scenes](#behind-the-scenes)
- [Limitations](#limitations)
- [Future work](#future-work)

### TL;DR

Grab it here: [release branch](https://github.com/liskin/dotfiles/tree/standalone/xrandr-smart), [tarball](https://github.com/liskin/dotfiles/archive/standalone/xrandr-smart.tar.gz).

### What? Why?

The way monitor layouts work for most people with mainstream operating systems or desktop environments (like [GNOME](https://www.gnome.org/gnome-3/)) is this: you connect an external monitor for the first time, your desktop expands to this monitor, then you can change its position or turn it off in the settings, and then this setup is remembered so that you don’t need to do it again the next time you connect it. People with non-mainstream [X11 window managers](https://en.wikipedia.org/wiki/X_window_manager) (like [xmonad](https://xmonad.org/), [i3](https://i3wm.org/), [awesomewm](https://awesomewm.org/), [fluxbox](http://fluxbox.org/)) can get a similar experience: [arandr](https://christian.amsuess.com/tools/arandr/) being the “settings UI” and [autorandr](https://github.com/phillipberndt/autorandr) handling the initial expansion, saving and restoring.

Still, many people don’t know about these tools, and just use plain xrandr, and then look a bit too nerdy when trying to connect to a projector at some meetup or conference. I’ve got to admit I used to be one of them: I had a script to handle external monitors at home/work, but connecting anything else was so unusual that I didn’t bother writing a script, and had to do it manually, and feel bad about myself afterwards.

This year I finally decided to do something about it. Not a big deal, right? Just adopt autorandr and be done with it. But the script is massive (1500 lines of code including various workarounds for X11 and driver bugs that are already fixed) and the format of its state/configuration files is undocumented, and that’s a bit of a red flag as I like to keep this stuff cleaned up and in [version control](https://en.wikipedia.org/wiki/Version_control). So I tried to think of something simpler.

The simple solution I came up with is to extend xrandr to **allow [shell globs](https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html) as output names** and **disable unspecified outputs**.

So instead of having several scripts like

<figcaption><a href="https://github.com/liskin/dotfiles/blob/ca010423335ae885ff620e60ed37186b12354cc8/bin/layout-home-cz-hdmi">layout-home-cz-hdmi</a></figcaption>
```bash
xrandr \
    --output HDMI-2 --mode 1920x1200 --pos 0x0 --dpi 96 \
    --output eDP-1 --mode 1920x1080 --pos 0x1200 --dpi 96 --primary
```

<figcaption><a href="https://github.com/liskin/dotfiles/blob/ca010423335ae885ff620e60ed37186b12354cc8/bin/layout-home-uk-dock">layout-home-uk-dock</a></figcaption>
```bash
xrandr \
    --output DP-2-1 --mode 1920x1080 --pos 0x0 --dpi 96 \
    --output eDP-1 --mode 1920x1080 --pos 0x1080 --dpi 96 --primary
```

now I just have

<figcaption><a href="https://github.com/liskin/dotfiles/blob/74fed5fca5c2f414a588d2e02880c968eb224615/bin/layout-vertical">layout-vertical</a></figcaption>
```bash
xrandr-smart \
    --output 'eDP-*' --auto --dpi 96 --primary \
    --output '!(eDP-*)' --auto --above 'eDP-*' --dpi 96
```

and I think that’s beautiful.

The source for the `xrandr-smart` script as described is [in my dotfiles monorepo](https://github.com/liskin/dotfiles/blob/7a713c40892da5ed3eed162ad271ff5f90f76e9c/bin/xrandr-smart), but the best way to obtain the most recent version of it is to use the [standalone/xrandr-smart branch](https://github.com/liskin/dotfiles/tree/standalone/xrandr-smart), which can also be downloaded as [a tarball](https://github.com/liskin/dotfiles/archive/standalone/xrandr-smart.tar.gz).

### Behind the scenes

[`xrandr-smart`](https://github.com/liskin/dotfiles/blob/7a713c40892da5ed3eed162ad271ff5f90f76e9c/bin/xrandr-smart) invokes the [`xrandr-auto-find` function](https://github.com/liskin/dotfiles/blob/7a713c40892da5ed3eed162ad271ff5f90f76e9c/bin/xrandr-smart#L76) which resolves output globs (if the globs match nothing or more than one output, it fails) and invokes the [`xrandr-auto-off` function](https://github.com/liskin/dotfiles/blob/7a713c40892da5ed3eed162ad271ff5f90f76e9c/bin/xrandr-smart#L51)to disable all other unspecific outputs.

As an example, `layout-vertical` might translate to:

```bash
xrandr-auto-off \
    --output eDP-1 --auto --dpi 96 --primary \
    --output HDMI-1 --auto --above eDP-1 --dpi 96
↓
xrandr \
    --output eDP-1 --auto --dpi 96 --primary \
    --output HDMI-1 --auto --above eDP-1 --dpi 96 \
    --output DP-1 --off \
    --output DP-2 --off \
    --output HDMI-2 --off
```

And that’s all there is to it, really.

### Limitations

There are two significant limitations compared to [GNOME](https://www.gnome.org/gnome-3/) and [autorandr](https://github.com/phillipberndt/autorandr):

1. The generic `layout-horizontal`, `layout-vertical` scripts can only support the laptop panel and one external monitor. In reality, this isn’t a problem as triple head setups usual need [fine-tuned positioning](https://github.com/liskin/dotfiles/blob/74fed5fca5c2f414a588d2e02880c968eb224615/bin/layout-work2-dock#L6-L8) anyway.

2. We need extra code to support layout saving and (possibly automatic) restoring. Turns out that’s just a few lines: [`layout-auto`](https://github.com/liskin/dotfiles/blob/7a713c40892da5ed3eed162ad271ff5f90f76e9c/bin/layout-auto), [keybindings](https://github.com/liskin/dotfiles/blob/7a713c40892da5ed3eed162ad271ff5f90f76e9c/.xmonad/xmonad.hs#L89-L90)<sup id="fnref:not-auto" role="doc-noteref"><a href="#fn:not-auto">1</a></sup>.

### Future work

- We could additionally use [parse-edid](https://manpages.debian.org/unstable/read-edid/parse-edid.1.en.html) to get monitor vendor and model and match against that as well. This could make `xrandr-smart` useful even in triple-head and non-laptop setups.

* * *

1. Yeah, I invoke this manually using a Fn-key combo. There’s an endless stream of bugs in the kernel, X server and GPU drivers, plus the occassional security issue in a screensaver, so I feel safer to just invoke it manually when I think everything is settled down. [↩](#fnref:not-auto)