|
#!/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) |