Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sending DDC/CI commands to monitor on Windows using Python?

I would like to have my monitors controlled from Windows (simple stuff such as changing the input source), but cannot find a way of sending DDC/CI commands from Python...

Any clue about a library or method that could help here?

like image 728
ronszon Avatar asked May 16 '13 12:05

ronszon


People also ask

What is Ddcutil?

ddcutil is a Linux program for managing monitor settings, such as brightness, color levels, and input source.

What is DDC CI Dell?

This package contains the software application that allows users to configure their Dell monitor settings such as Brightness, Contrast and Preset Modes. This application requires Display Data Channel Command Interface (DDC/CI) to work. Dell monitors support DDC/CI on VGA, DVI, DP and HDMI inputs.


2 Answers

This is easily possible using the windows monitor API. I don't think there are any Python bindings out there and pywin32 doesn't contain those functions. However, using ctypes to call them is not that hard.

Here's an example that switches the monitor to soft-off and then back on; it should be pretty easy to adapt it to changing the input source etc. The only complicated part is getting the handles for the physical monitors after all:

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 SOFT-OFF, wait for the user to press return and then back to ON
for handle in _iter_physical_monitors():
    set_vcp_feature(handle, 0xd6, 0x04)
    raw_input()
    set_vcp_feature(handle, 0xd6, 0x01)
like image 52
ThiefMaster Avatar answered Oct 25 '22 00:10

ThiefMaster


It's your lucky day, you don't have to deal with those ctypes anymore. Somebody actually made a python package that does this for you and exposes a simple api. It's called monitorcontrol.

Here's a quick example of setting luminance on all monitors:

from monitorcontrol import get_monitors

for monitor in get_monitors():
    with monitor:
        monitor.set_luminance(50)
like image 22
Michał Wesołowski Avatar answered Oct 25 '22 00:10

Michał Wesołowski