Switch monitors via menu
When I was working I was constantly having to hit the buttons on one or more of my three monitors to change inputs between my work laptop, home PC and other devices. Figured there's gotta be a better way.
Instead of having to manually push buttons on monitors to switch inputs I wanted a menu allowing me to switch via a keyboard shortcut. Would also be great to power on and off monitors if possible.
Via some googling I came across the mention of ddc (DDC/CI (Display Data Channel Command Interface) tools to switch monitor inputs and other monitor related tasks from command line.
Originally tried ddccontrol but it is outdated and possibly a dead project (last update 2006) and requires this library file for details on monitors. Didn't like that. Then I found ddcutil which is being actively developed and doesn't require stupid library files.
Whats ddcutil you ask?
ddcutil is a Linux program for managing monitor settings, such as brightness, color levels, and input source. Generally speaking, any setting that can be changed by pressing buttons on the monitor can be modified by ddcutil.
This looks like exactly what I need.
I attempted installing ddcutil from Debian packages via Opensuse Build Services (OBS) but it required some weird pci.ds package but that would break pciutils on MX Linux. Went to the source which is also much newer. Prerequisites are on the site if you run into any problems.
$ git clone https://github.com/rockowitz/ddcutil.git $ cd ddcutil SUDO to root # ./autogen.sh # ./configure # make # make install
Now that its installed I can query my monitors. I used the --nousb option as I don't have USB monitors. The monitors are hooked up in order from left to right. 1,2,3.
❯ ddcutil --nousb detect Display 1 I2C bus: /dev/i2c-4 EDID synopsis: Mfg id: DELL Model: DELL P2719H Product code: 453434 Serial number: XXXXXXXXX Binary serial number: 0 (0x00000000) Manufacture year: 2016, Week: 30 VCP version: 2.2 Display 2 I2C bus: /dev/i2c-5 EDID synopsis: Mfg id: DELL Model: DELL S3220DGF Product code: 53492 Serial number: 6P7N4W2 Binary serial number: XXXXXXXXXXXX Manufacture year: 2020, Week: 15 VCP version: 2.1 Display 3 I2C bus: /dev/i2c-6 EDID synopsis: Mfg id: DELL Model: DELL P2719H Product code: 453434 Serial number: XXXXXXXXXXXXX Binary serial number: 0 (0x00000000) Manufacture year: 2016, Week: 30 VCP version: 2.2
Now I want to see the capabilities of my monitors. Going to look at monitor 3 (far right)
$ ddcutil capabilities --display 3 --verbose
Produces a lot of information but I think what I want is the Input Source
Feature: 60 (Input Source) Values: 01: VGA-1 03: DVI-1 0f: DisplayPort-1
I wanted to test switching the monitor via the physical buttons and see the difference. While connected to Display Port
❯ ddcutil --display 3 getvcp 60 VCP code 0x60 (Input Source ): DisplayPort-1 (sl=0x0f)
After switching to DVI
❯ ddcutil --display 3 getvcp 60 VCP code 0x60 (Input Source ): DVI-1 (sl=0x03)
So I know DP1 is 0x0f and DVI is 0x03. That is actually shown above too. Sweet.
This switches monitors back and forth without touching the monitor
❯ ddcutil --display 3 setvcp 60 0x03 ❯ ddcutil --display 3 setvcp 60 0x0f
Checking my Center monitor the numbers are a little different:
$ ddcutil capabilities --display 1 <CUT> Feature: 60 (Input Source) Values: 0f: DisplayPort-1 11: HDMI-1 12: HDMI-2
Create 6 scripts to change the two monitors inputs and 2 scripts to power off and on the monitors. Again...probably a cleaner way to do this but it works for me.
I'll use a simple naming convention of LEFT_* or RIGHT_* with the different inputs. e.g. LEFT_DP.sh, LEFT_DVI.sh and LEFT_VGA.sh.
❯ cat LEFT_DVI.sh #!/bin/sh ddcutil --display 1 setvcp 60 0x03 ❯ cat LEFT_DP.sh #!/bin/sh ddcutil --display 1 setvcp 60 0x0f ❯ cat LEFT_VGA.sh #!/bin/sh ddcutil --display 1 setvcp 60 0x01
Do the same for RIGHT monitor.
Create two scripts for powering off and on the monitors. You'll notice that I can turn off all 3 monitors and only turn on 2. That's because I need to be able to see menu to turn on all the monitors. So I simply hit the button on my left monitor and then use rofi menu to power on the middle and right one. Hope that makes ense.
Lets look at Feature D6 (ddcutil capabilities --display # --verbose)
Feature: D6 (Power mode) Values (unparsed): 01 02 03 04 05 Values ( parsed): 01: DPM: On, DPMS: Off 02: DPM: Off, DPMS: Standby 03: DPM: Off, DPMS: Suspend 04: DPM: Off, DPMS: Off 05: Write only value to turn off display
For Center Monitor
Feature: D6 (Power mode) Values (unparsed): 01 04 05 Values ( parsed):01: DPM: On, DPMS: Off 04: DPM: Off, DPMS: Off 05: Write only value to turn off display
❯ cat monitors_on.sh #!/bin/sh ddcutil --nousb --display 2 setvcp D6 0x01 ddcutil --nousb --display 3 setvcp D6 0x01 ❯ cat monitors_off.sh #!/bin/sh ddcutil --nousb --display 1 setvcp D6 0x04 ddcutil --nousb --display 2 setvcp D6 0x04 ddcutil --nousb --display 3 setvcp D6 0x04
You can try each script above and make sure it works for you.
To make things easier (and prettier) I created a custom menu with rofi to do this:
$ sudo apt install rofi
Note: The script is a little messy and there's probably a more "correct" and cleaner way to do this. Let me know if you have any feedback. For my second (Center Dell 32") I don't currently have a need to switch inputs as it always stays on my main PC via Display Port. Change $USERNAME to your username.
#!/usr/bin/python3 # This script will allow me to switch one at a time or both at a time screens my Dell 27" left and right monitors inputs and turn off all monitors and turn on the center and right one. # Utilizing DDCUTILS - https://www.ddcutil.com # Notes # Dell Main Monitor - Display 2 # Inputs # 0f: DisplayPort-1 # 11: HDMI-1 # 12: HDMI-2 # Right Dell Monitor - Display 3 # Left Dell Monitor - Display 1 # Inputs # 01: VGA-1 # 03: DVI-1 # 0f: DisplayPort-1 import rofi_menu class Display1(rofi_menu.Menu): prompt = "Screen1 Inputs" items = [ rofi_menu.BackItem(), rofi_menu.ShellItem("VGA", "/home/$USERNAME/bin/LEFT_VGA.sh"), rofi_menu.ShellItem("DisplayPort", "/home/$USERNAME/bin/LEFT_DP.sh"), rofi_menu.ShellItem("DVI", "/home/$USERNAME/bin/LEFT_DVI.sh"), ] class Display3(rofi_menu.Menu): prompt = "Screen3 Inputs" items = [ rofi_menu.BackItem(), rofi_menu.ShellItem("VGA", "/home/$USERNAME/bin/RIGHT_VGA.sh"), rofi_menu.ShellItem("DisplayPort", "/home/$USERNAME/bin/RIGHT_DP.sh"), rofi_menu.ShellItem("DVI", "/home/$USERNAME/bin/RIGHT_DVI.sh"), ] class Power(rofi_menu.Menu): prompt = "Monitor Power" items = [ rofi_menu.BackItem(), rofi_menu.ShellItem("On", "/home/$USERNAME/bin/monitors_on.sh"), rofi_menu.ShellItem("Off", "/home/$USERNAME/bin/monitors_off.sh"), ] class MainMenu(rofi_menu.Menu): prompt = "Menu" items = [ rofi_menu.BackItem(), rofi_menu.NestedMenu("Screen1 Inputs >", Display1()), rofi_menu.NestedMenu("Screen3 Inputs >", Display3()), rofi_menu.NestedMenu("Monitor Power >", Power()), ] if __name__ == "__main__": rofi_menu.run(MainMenu())
To test you can run:
rofi -modi mymenu:/home/$USERNAME/bin/screen_menu.py -show displaymenu
I'm using I3wm so I'll map a keyboard shortcut to bring up this particular rofi menu. I already have $mod+d to bring up rofi's main menu.
$ nano ~/.config/i3/config
Add the following line (all one line). Change $USERNAME to your username obviously
bindsym $mod+Ctrl+d exec rofi -modi mymenu:/home/$USERNAME/bin/screen_menu.py -show mymenu
Hope this helps. Let me know if you have any feedback or comments.