Skip to content

Instantly share code, notes, and snippets.

@kherge
Last active July 9, 2020 16:12
Show Gist options
  • Save kherge/2d77d647e2a2bb2178ae66217b81a42f to your computer and use it in GitHub Desktop.
Save kherge/2d77d647e2a2bb2178ae66217b81a42f to your computer and use it in GitHub Desktop.
Change input source through data display channel (DDC).

Change Input Source

A simple command line tool that uses ddccontrol to find named input sources and change to them.

Usage

cis -d *U3818DW* -s usb-c

There are two options that are needed:

  • -d, --display — The name of the display. Wildcards are supported for partial matches.
  • -s, --source — The exact name of the input source to switch to.

Requirements

#!/usr/bin/env python3
import argparse
import re
import subprocess
import sys
####################################################################################################
# Matches the end of the detected list.
detectEndRE = re.compile(r"^Reading EDID")
# Matches the device path for the display.
deviceRE = re.compile(r"^\s*-\s*Device:\s*(.+)\s*")
# Matches the start of display information.
infoRE = re.compile(r"^=\s*(.+)\s*")
# Matches the end of input sources.
inputEndRE = re.compile(r"^\s*supported")
# Matches an input source.
inputSourceRE = re.compile(r"^\s*>\s*id=(.+) - name=USB-C, value=(.+)\s*")
# Matches the start of input sources.
inputStartRE = re.compile(r"^\s*>\s*id=inputsource.+address=(0x\d{2}).+")
# Matches the name for the display.
nameRE = re.compile(r"^\s*Monitor Name:\s*(.+)\s*")
def get_inputs(debug):
"""Queries for available displays and their source inputs."""
# Probe for all devices.
raw = subprocess\
.check_output(['ddccontrol', '-p'])\
.decode('ascii')\
.split('\n')
device = None
inputs = {}
name = None
# Collect device information.
while raw:
line = raw.pop(0)
if debug:
print('Checking line: ' + line)
# Check for device path.
match = deviceRE.match(line)
if match:
if debug:
print('Device path matched')
device = match.group(1)
# Check for device name.
match = nameRE.match(line)
if match:
if debug:
print('Device name matched');
name = match.group(1)
# Add to input dictionary.
if device and name:
if debug:
print('Adding device info')
inputs[name] = {
'device': device,
'sources': {}
}
device = None
name = None
# Check if end of detect list.
if detectEndRE.match(line):
if debug:
print('End of device probe')
break
name = None
inList = False
# Collect device input sources.
while raw:
line = raw.pop(0)
if debug:
print('Checking line: ' + line)
# Check for start of device information.
match = infoRE.match(line)
if match:
if debug:
print('Device info start matched')
name = match.group(1)
if name:
# Check for source inputs start.
match = inputStartRE.match(line)
if match:
if debug:
print('Device input source address matched')
inputs[name]['address'] = match.group(1)
inList = True
if inList:
# Check for source inputs end.
match = inputEndRE.match(line)
if match:
if debug:
print('Device input source list end matched')
name = None
# Check for source input.
match = inputSourceRE.match(line)
if match:
if debug:
print('Device input source matched')
inputs[name]['sources'][match.group(1)] = match.group(2)
return inputs
def set_input(device, address, value):
"""Sets the active source input."""
subprocess.check_output(['ddccontrol', '-r', address, '-w', value, device])
####################################################################################################
# Parse the command line arguments.
parser = argparse.ArgumentParser(description='Change input source through ddccontrol.')
parser.add_argument(
'-d', '--display',
dest='display',
required=True,
help='The name of the display (wildcard, *, supported).'
)
parser.add_argument(
'-s', '--source',
dest='source',
required=True,
help='The name of the source.'
)
parser.add_argument(
'-v', '--verbose',
action='store_true',
dest='verbose',
help='Enables verbose output.'
)
args = parser.parse_args()
# Alert the user.
alertScript = '(echo 33; sleep 2; echo 66; sleep 1; echo 90; sleep 1; echo 100) | zenity --progress --title="Change Input Source" --text="Switching to ' + args.source + ' on ' + args.display + '..." --percentage=0 --auto-close'
subprocess.Popen(['nohup', 'bash', '-c', alertScript, '&'], close_fds=True)
# Get available display information.
displays = get_inputs(debug=args.verbose)
# Find first matching display
displayName = re.compile(args.display.replace('*', '(?:.*)'))
for display in displays.keys():
if displayName.match(display):
if displays[display]['sources'].get(args.source):
set_input(
displays[display]['device'],
displays[display]['address'],
displays[display]['sources'][args.source]
)
else:
print(args.source + ": no such input source", file=sys.stderr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment