Change screen input programatically on Linux
Tags: linux, kvm, howto
Published: 2024-02-20 17:09:34

I have a keyboard/mouse/screen switch to connect 2 PCs: one running Linux and one running Windows.

When I connect all the ports, it works, but every time I switch to the Windows laptop, the screen reconfigures, my windows are moved, and it annoys me. The switch behaves as if I had physically unplugged/replugged the screen, and Windows feels compelled to adjust the entire layout!

To fix this issue, I removed the screen connection from the switch and connected both computers to the screen's 2 sources (HDMI 1 and 2): okay, Windows doesn't rearrange all my windows when I switch to it, but now I have to switch both on the switch to get my keyboard and mouse, and I also have to manually change the screen source from HDMI 1 to 2. And vice versa when I go back to my Linux!

I needed a way to switch the screen source at the same time as I switched the switch. Like, something that detects that I'm switching and voila, changes the screen source for me.

Is that possible on Linux?

Well yes, it is.

Under Linux, we have a utility called `ddcutil` which allows us to "talk" to the screen and send it commands (like changing the source).

Here's the command to change the source:

```sh
sudo ddcutil setvcp 60 x
```

where x corresponds to the hexadecimal value of the source, and 60 corresponds to the source change functionality.

According to the results of `ddcutil capabilities`, I was supposed to use values 11 for HDMI 1, 12 for HDMI 2, and 0F for the display port.

```sh
sudo ddcutil capabilities
...
   Feature: 60 (Input Source)
      Values:
         11: HDMI-1
         12: HDMI-2
         0f: DisplayPort-1
...
```
_(I omitted other information provided by the command to focus only on the screen source)_

In reality, values 11 and 12 didn't do anything. I tested all the x0f values and down, until I got a reaction from the screen.

And for me, it was respectively 5 and 6 for HDMI 1 and HDMI 2.

So, to switch to HDMI 1, I had to do

```sh
sudo ddcutil setvcp 60 x5
```

and for HDMI 2:

```sh
sudo ddcutil setvcp 60 x6
```

To prepare for the next steps, I create 2 script files with these commands.

The first one, which I name `set-hdmi1.sh`, is the script that changes the screen source to HDMI 1:

```sh
#!/bin/sh
# set input source to HDMI 1
sudo ddcutil setvcp 60 x5 --display 2
```

I don't forget to make this script executable:

```sh
chmod a+x /usr/local/bin/set-hdmi1.sh
```

The second one, `set-hdmi2.sh`, is the script that changes the screen source to HDMI 2:

```sh
#!/bin/sh
# set input source to HDMI 2
sudo ddcutil setvcp 60 x6 --display 2
```

And I make it executable:

```sh
chmod a+x /usr/local/bin/set-hdmi2.sh
```

Okay, now that it works, I need to detect the keyboard switch.

Linux allows reacting to hardware changes and launching its own actions, thanks to udev. For example, we can take action if we plug or unplug a keyboard.

For this, we need to target the right hardware, and that involves finding its identifiers. The command `lsusb` lists all devices connected via USB to my Linux, but I'm only interested in keyboards, hence the filter by the `grep` command.

```sh
lsusb | grep -i key
Bus 001 Device 039: ID 04d9:0295 Holtek Semiconductor, Inc. USB-HID Keyboard
```

The important values are right after the ID:

- 04d9 identifies the vendor
- 0295 the product

Then, I create a new udev rules file, for example, `99-kvm.rules` in the folder `/etc/udev/rules.d` with the following content:

```sh
ACTION=="remove", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="0295", RUN+="/usr/local/bin/set-hdmi2.sh"
ACTION=="add", ATTRS{idVendor}=="04d9", ATTRS{idProduct}=="0295", RUN+="/usr/local/bin/set-hdmi1.sh"
```

- ACTION allows me to react, either to the removal of the keyboard (action 'remove', I switch to HDMI 2), or to its insertion (action 'add', I switch to HDMI 1).

- Then, I identify my hardware using the values noted above, for the attributes `idVendor` and `idProduct`.

- Finally, I execute the right script.

It's almost done: if we want these rules to take effect immediately (rather than at the next reboot), we can run the following commands:

```sh
sudo udevadm control --reload-rules && sudo udevadm trigger
```

And there you go! Now, I press the keyboard switch to change the screen source, and Windows stays calm! Thank you, Linux!

Note: Next time I change the KVM, I'll get a basic one that only handles keyboard and mouse :-)