I recently stumbled upon this software: https://clickmonitorddc.bplaced.net/ And I wanted to find a way to programmatically change my default monitor's input source (from DP to HDMI and back) (On my first of two monitors).
I found this Sending DDC/CI commands to monitor on Windows using Python? detailing how to send ddc-ci commands through python.
Now this is all well and good, but the linked pdf file to the list of DDC commands has expired and I can't seem to figure out how I'd apply this to my specific case. Messing around has only resulted in me succesfully making my monitors go blank one after the other, but that's not really what I'm trying to accomplish.
I sadly do not have much of any attempts or code to share a part from the one in the linked post above.
Any help would be greatly appreciated.
After some testing with the code linked in my original, I managed to figure it out:
from ctypes import windll, byref, Structure, WinError, POINTER, WINFUNCTYPE
from ctypes.wintypes import BOOL, HMONITOR, HDC, RECT, LPARAM, DWORD, BYTE, WCHAR, HANDLE
_MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM)
class _PHYSICAL_MONITOR(Structure):
_fields_ = [('handle', HANDLE),
('description', WCHAR * 128)]
def _iter_physical_monitors(close_handles=True):
"""Iterates physical monitors.
The handles are closed automatically whenever the iterator is advanced.
This means that the iterator should always be fully exhausted!
If you want to keep handles e.g. because you need to store all of them and
use them later, set `close_handles` to False and close them manually."""
def callback(hmonitor, hdc, lprect, lparam):
monitors.append(HMONITOR(hmonitor))
return True
monitors = []
if not windll.user32.EnumDisplayMonitors(None, None, _MONITORENUMPROC(callback), None):
raise WinError('EnumDisplayMonitors failed')
for monitor in monitors:
# Get physical monitor count
count = DWORD()
if not windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count)):
raise WinError()
# Get physical monitor handles
physical_array = (_PHYSICAL_MONITOR * count.value)()
if not windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array):
raise WinError()
for physical in physical_array:
yield physical.handle
if close_handles:
if not windll.dxva2.DestroyPhysicalMonitor(physical.handle):
raise WinError()
def set_vcp_feature(monitor, code, value):
"""Sends a DDC command to the specified monitor.
See this link for a list of commands:
ftp://ftp.cis.nctu.edu.tw/pub/csie/Software/X11/private/VeSaSpEcS/VESA_Document_Center_Monitor_Interface/mccsV3.pdf
"""
if not windll.dxva2.SetVCPFeature(HANDLE(monitor), BYTE(code), DWORD(value)):
raise WinError()
# Switch to HDMI, wait for the user to press return and then back to DP
for handle in _iter_physical_monitors():
set_vcp_feature(handle, 0x60, 0x11) #Change input to HDMI
input()
set_vcp_feature(handle, 0x60, 0x0F) #Change input to DisplayPort
Turns out the vcp code for input commands is 0x60
, and from there the values can be determined quite easily, they are as followed:
0x01: D-sub/VGA, 0x03: DVI, 0x11 or 0x04 depending on the brand: HDMI, 0x0F: DisplayPort