-
-
Save Fr6jDJF/6a2a451745527329e4a86bc3f29cf0da to your computer and use it in GitHub Desktop.
Python script for adjusting the settings of my monitor, by using DDC/CI
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Show and adjust display parameters on an Iiyama ProLite XB2483HSU-B1 monitor | |
Requirements: | |
- mentioned monitor (27' should also work) with DDC/CI setting on | |
- Windows Vista+ | |
- Python 3 | |
Copyright (C) 2015 Diego Fernández Gosende <dfgosende@gmail.com> | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License along | |
with this program. If not, see <http://www.gnu.org/licenses/gpl-3.0.html>. | |
""" | |
version = '0.1.0' | |
import ctypes | |
import ctypes.wintypes as wintypes | |
"""Capabilities string. mccs_ver and vcp code 'VCP Version' don't match | |
prot(monitor) | |
type(lcd) | |
model(W2413) | |
cmds(01 02 03 07 0C 4E F3 E3) | |
vcp(02 04 05 08 0B 0C 10 12 14(05 06 08 0B) 16 18 1A 52 AC AE B2 B6 C0 C6 C8 | |
C9 CC(02 03 04 05 06 09 0A 0D 12 14 1E) D6(01 05) DF 60(01 03 11) 62 8D) | |
mswhql(1) | |
mccs_ver(2.0) | |
asset_eep(32) | |
mpu_ver(01)) | |
""" | |
# Reference: VESA Monitor Control Command Set | |
vcp_codes = { | |
# ignore for scripting | |
'New Control Value': 0x02, | |
# pass a non-zero value | |
'Restore Factory Defaults': 0x04, | |
# pass a non-zero value | |
'Restore Factory Luminance/ Contrast Defaults': 0x05, | |
# pass a non-zero value, resets also luminance | |
'Restore Factory Color Defaults': 0x08, | |
# read-only, 50, see 'Color Temperature Request' | |
'Color Temperature Increment': 0x0B, | |
# 0-170, value to kelvin: 3000 + value * 'Color Temperature Increment' | |
# It just switches to the nearest 'Select Color Preset' | |
'Color Temperature Request': 0x0C, | |
# 0-100, called 'brightness' on the OSD | |
'Luminance': 0x10, | |
# 0-100 | |
'Contrast': 0x12, | |
# 0x05: 6500K, 0x06: 7500K, 0x08: 9300K, 0x0B: user | |
'Select Color Preset': 0x14, | |
# 0-100, it also changes the preset to 'user | |
'Video Gain (Drive): Red': 0x16, | |
# 0-100, it also changes the preset to 'user' | |
'Video Gain (Drive): Green': 0x18, | |
# 0-100, it also changes the preset to 'user' | |
'Video Gain (Drive): Blue': 0x1A, | |
# ignore for scripting | |
'Active Control': 0x52, | |
# read-only, Hz, it only returns two bytes instead of three | |
'Horizontal Frequency': 0xAC, | |
# read-only, 0.01 Hz | |
'Vertical Frequency': 0xAE, | |
# read-only, 0x01 -> Red / Green / Blue vertical stripe | |
'Flat Panel Sub-Pixel Layout': 0xB2, | |
# read-only, 0x03 -> LCD (active matrix) | |
'Display Technology Type': 0xB6, | |
# read-only, hours | |
'Display Usage Time': 0xC0, | |
# read-only, 0x6f, some id | |
'Application Enable Key': 0xC6, | |
# read-only, 0x7012 -> Novatek Microelectronics and some other info | |
'Display Controller Type': 0xC8, | |
# read-only, 0x01b3 -> v1.179 (OSD says v1.19 ?, ignore bits 5-7) | |
'Display Firmware Level': 0xC9, | |
# 0x02: English, 0x03: French, 0x04: German, 0x05: Italian, 0x06: Japanese, | |
# 0x09: Russian, 0x0A: Spanish, 0x0D: Chinese (simplified / Kantai), | |
# 0x12: Czech, 0x14: Dutch, 0x1E: Polish | |
'OSD Language': 0xCC, | |
# 0x01: power on, 0x05: power off | |
'Power Mode': 0xD6, | |
# read-only, 0x0201 -> MCCS v2.1 | |
'VCP Version': 0xDF, | |
# 0x01: D-sub, 0x03: DVI, 0x11: HDMI | |
'Input Source': 0x60, | |
# 0-100 | |
'Audio: Speaker Volume': 0x62, | |
# 0x01: mute the audio, 0x02: unmute the audio | |
'Audio Mute': 0x8D, | |
} | |
# Reference: https://msdn.microsoft.com/en-us/library/dd692982(v=vs.85).aspx | |
_PHYSICAL_MONITOR_DESCRIPTION_SIZE = 128 | |
_MONITORENUMPROC = ctypes.WINFUNCTYPE(wintypes.BOOL, | |
wintypes.HMONITOR, | |
wintypes.HDC, | |
ctypes.POINTER(wintypes.RECT), | |
wintypes.LPARAM) | |
class _PHYSICAL_MONITOR(ctypes.Structure): | |
_fields_ = [('hPhysicalMonitor', wintypes.HANDLE), | |
('szPhysicalMonitorDescription', | |
wintypes.WCHAR * _PHYSICAL_MONITOR_DESCRIPTION_SIZE)] | |
def get_displays(): | |
"""Get a list of the available displays""" | |
def MonitorEnumProc_callback(hmonitor, hdc, lprect, lparam): | |
monitors.append(hmonitor) | |
return True | |
monitors = [] | |
displays = [] | |
# get display monitors | |
if not ctypes.windll.user32.EnumDisplayMonitors(None, None, | |
_MONITORENUMPROC(MonitorEnumProc_callback), None): | |
raise ctypes.WinError() | |
# get physical monitors for each display monitor | |
for monitor in monitors: | |
monitor_number = wintypes.DWORD() | |
if not ctypes.windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR( | |
monitor, ctypes.byref(monitor_number)): | |
raise ctypes.WinError() | |
physical_monitor_array = (_PHYSICAL_MONITOR * monitor_number.value)() | |
if not ctypes.windll.dxva2.GetPhysicalMonitorsFromHMONITOR( | |
monitor, monitor_number, physical_monitor_array): | |
raise WinError() | |
for physical_monitor in physical_monitor_array: | |
displays.append(Display(physical_monitor.hPhysicalMonitor)) | |
return displays | |
class Display(): | |
def __init__(self, handle): | |
self._handle = handle | |
self._capabilities = None | |
@property | |
def capabilities(self): | |
if self._capabilities is None: | |
self._get_capabilities() | |
return self._capabilities | |
@property | |
def capabilities_raw(self): | |
if self._capabilities_raw is None: | |
self._get_capabilities() | |
return self._capabilities_raw | |
def _get_capabilities(self): | |
# this is slow | |
length = wintypes.DWORD() | |
if not ctypes.windll.dxva2.GetCapabilitiesStringLength( | |
self._handle, ctypes.byref(length)): | |
raise ctypes.WinError() | |
capabilities_string = (ctypes.c_char * length.value)() | |
if not ctypes.windll.dxva2.CapabilitiesRequestAndCapabilitiesReply( | |
self._handle, capabilities_string, length): | |
raise ctypes.WinError() | |
self._capabilities_raw = capabilities_string.value.decode('ascii') | |
self._capabilities = self.parse_capabilities_string( | |
self._capabilities_raw) | |
@classmethod | |
def parse_capabilities_string(cls, capabilities_string): | |
level = 0 | |
capabilities = {} | |
open_p = {} | |
close_p = {0: 0} | |
id = {} | |
for i, chr in enumerate(capabilities_string): | |
if chr == '(': | |
if i == 0: | |
close_p[0] = 1 | |
continue | |
open_p[level] = i | |
if level == 0: | |
id[0] = capabilities_string[close_p[0] + 1:i] | |
level += 1 | |
elif chr == ')': | |
level -= 1 | |
close_p[level] = i | |
if level == 0: | |
values = capabilities_string[open_p[0] + 1:i] | |
if id[0] == 'cmds': | |
values = values.split() | |
elif id[0] == 'vcp': | |
values = cls._parse_vcp_list(values) | |
capabilities[id[0]] = values | |
return capabilities | |
@staticmethod | |
def _parse_vcp_list(vcp_list): | |
vcp_dict = {} | |
open_p = 0 | |
for i, chr in enumerate(vcp_list): | |
if chr == '(': | |
open_p = i | |
code = vcp_list[i-2:i] | |
elif chr == ')': | |
vcp_dict[code] = vcp_list[open_p + 1:i].split() | |
elif chr.isspace() and vcp_list[i-1] != ')': | |
vcp_dict[vcp_list[i-2:i]] = None | |
return vcp_dict | |
def _get_vcf_feature_and_vcf_feature_reply(self, code): | |
"""Get current and maximun values for continuous VCP codes""" | |
current_value = wintypes.DWORD() | |
maximum_value = wintypes.DWORD() | |
if not ctypes.windll.dxva2.GetVCPFeatureAndVCPFeatureReply( | |
self._handle, wintypes.BYTE(code), None, | |
ctypes.byref(current_value), | |
ctypes.byref(maximum_value)): | |
raise ctypes.WinError() | |
return current_value.value, maximum_value.value | |
def _set_vcp_feature(self, code, value): | |
"""Set 'code' to 'value'""" | |
if not ctypes.windll.dxva2.SetVCPFeature(self._handle, | |
wintypes.BYTE(code), wintypes.DWORD(value)): | |
raise ctypes.WinError() | |
@property | |
def model(self): | |
return self.capabilities['model'] | |
_display_technology_types = { | |
0x01: 'CRT (shadow mask)', | |
0x02: 'CRT (aperture grill)', | |
0x03: 'LCD (active matrix)', | |
0x04: 'LCoS', | |
0x05: 'Plasma', | |
0x06: 'OLED', | |
0x07: 'EL', | |
0x08: 'Dynamic MEM e.g. DLP', | |
0x09: 'Static MEM e.g. iMOD', } | |
@property | |
def display_technology_type(self): | |
return self._display_technology_types.get( | |
self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Display Technology Type'])[0]) | |
_flat_panel_sub_pixel_layouts = { | |
0x00: 'Sub-pixel layout is not defined', | |
0x01: 'Red / Green / Blue vertical stripe', | |
0x02: 'Red / Green / Blue horizontal stripe', | |
0x03: 'Blue / Green / Red vertical stripe', | |
0x04: 'Blue/ Green / Red horizontal stripe', | |
0x05: 'Quad-pixel, a 2 x 2 sub-pixel structure with red at top left, ' | |
'blue at bottom right and green at top right and bottom left', | |
0x06: 'Quad-pixel, a 2 x 2 sub-pixel structure with red at bottom ' | |
'left, blue at top right and green at top left and bottom right', | |
0x07: 'Delta (triad)', | |
0x08: 'Mosaic with interleaved sub-pixels of different colors', } | |
@property | |
def flat_panel_sub_pixel_layout(self): | |
return self._flat_panel_sub_pixel_layouts.get( | |
self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Flat Panel Sub-Pixel Layout'])[0]) | |
_display_controller_type_lsb = { | |
0x01: 'Conexant', | |
0x02: 'Genesis Microchip', | |
0x03: 'Macronix', | |
0x04: 'MRT (Media Reality Technologies)', | |
0x05: 'Mstar Semiconductor', | |
0x06: 'Myson', | |
0x07: 'Philips', | |
0x08: 'PixelWorks', | |
0x09: 'RealTek Semiconductor', | |
0x0A: 'Sage', | |
0x0B: 'Silicon Image', | |
0x0C: 'SmartASIC', | |
0x0D: 'STMicroelectronics', | |
0x0E: 'Topro', | |
0x0F: 'Trumpion', | |
0x10: 'Welltrend', | |
0x11: 'Samsung', | |
0x12: 'Novatek Microelectronics', | |
0x13: 'STK', | |
0xFF: 'Manufacturer designed controller', } | |
@property | |
def display_controller_type(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Display Controller Type'])[0] | |
return self._display_controller_type_lsb.get(value & 0xff) | |
@property | |
def display_firmware_level(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Display Firmware Level'])[0] | |
return '{}.{}'.format(value >> 8, value & 0x1f) | |
@property | |
def application_enable_key(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Application Enable Key'])[0] | |
@property | |
def vcp_version(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['VCP Version'])[0] | |
return '{}.{}'.format(value >> 8, value & 0xff) | |
@property | |
def display_usage_time(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Display Usage Time'])[0] | |
@property | |
def vertical_frequency(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Vertical Frequency'])[0] / 100 | |
@property | |
def horizontal_frequency(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Horizontal Frequency'])[0] | |
if value < 50000: # MSB is missing, should be 0 or 1 | |
value |= 0x10000 | |
return value / 1000 | |
@property | |
def brightness(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Luminance'])[0] | |
@brightness.setter | |
def brightness(self, value): | |
current, max = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Luminance']) | |
if value == current: | |
return | |
if value > max: | |
value = max | |
elif value < 0: | |
value = 0 | |
self._set_vcp_feature(vcp_codes['Luminance'], value) | |
@property | |
def max_brightness(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Luminance'])[1] | |
@property | |
def contrast(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Contrast'])[0] | |
@contrast.setter | |
def contrast(self, value): | |
current, max = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Contrast']) | |
if value == current: | |
return | |
if value > max: | |
value = max | |
elif value < 0: | |
value = 0 | |
self._set_vcp_feature(vcp_codes['Contrast'], value) | |
@property | |
def max_contrast(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Contrast'])[1] | |
@property | |
def color_temperature(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Color Temperature Request'])[0] | |
increment = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Color Temperature Increment'])[0] | |
return 3000 + increment * value | |
@color_temperature.setter | |
def color_temperature(self, value): | |
current, max = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Color Temperature Request']) | |
increment = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Color Temperature Increment'])[0] | |
value = (value - 3000) // increment | |
if value == current: | |
return | |
if value > max: | |
value = max | |
elif value < 0: | |
value = 0 | |
self._set_vcp_feature(vcp_codes['Color Temperature Request'], value) | |
@property | |
def max_color_temperature(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Color Temperature Request'])[1] | |
increment = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Color Temperature Increment'])[0] | |
return 3000 + increment * value | |
_color_presets = {0x05: '6500K', 0x06: '7500K', | |
0x08: '9300K', 0x0B: 'user'} | |
_color_presets_i = {'6500K': 0x05, '7500K': 0x06, | |
'9300K': 0x08, 'user': 0x0B} | |
@property | |
def color_preset(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Select Color Preset'])[0] | |
return self._color_presets[value] | |
@color_preset.setter | |
def color_preset(self, value): | |
value = self._color_presets_i[value] | |
if value == self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Select Color Preset'])[0]: | |
return | |
self._set_vcp_feature(vcp_codes['Select Color Preset'], value) | |
@property | |
def rgb(self): | |
return [self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Video Gain (Drive): {}'.format(color)])[0] | |
for color in ['Red', 'Green', 'Blue']] | |
@rgb.setter | |
def rgb(self, values): | |
color = ['Red', 'Green', 'Blue'] | |
template = 'Video Gain (Drive): {}' | |
for i, value in enumerate(values): | |
vcp_name = template.format(color[i]) | |
current, max = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes[vcp_name]) | |
if value in (-1, current): | |
continue | |
if value > max: | |
value = max | |
elif value < 0: | |
value = 0 | |
self._set_vcp_feature(vcp_codes[vcp_name], value) | |
@property | |
def max_rgb(self): | |
return [self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Video Gain (Drive): {}'.format(color)])[1] | |
for color in ['Red', 'Green', 'Blue']] | |
_input_sources = {0x01: 'd-sub', 0x03: 'dvi', 0x11: 'hdmi'} | |
_input_sources_i = {'d-sub': 0x01, 'dvi': 0x03, 'hdmi': 0x11} | |
@property | |
def input_source(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Input Source'])[0] | |
return self._input_sources[value] | |
@input_source.setter | |
def input_source(self, value): | |
value = self._input_sources_i[value] | |
if value == self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Input Source'])[0]: | |
return | |
self._set_vcp_feature(vcp_codes['Input Source'], value) | |
_audio_mute = {0x01: 'on', 0x02: 'off'} | |
_audio_mute_i = {'on': 0x01, 'off': 0x02} | |
@property | |
def audio_mute(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Audio Mute'])[0] | |
return self._audio_mute[value] | |
@audio_mute.setter | |
def audio_mute(self, value): | |
value = self._audio_mute_i[value] | |
if value == self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Audio Mute'])[0]: | |
return | |
self._set_vcp_feature(vcp_codes['Audio Mute'], value) | |
@property | |
def audio_speaker_volume(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Audio: Speaker Volume'])[0] | |
@audio_speaker_volume.setter | |
def audio_speaker_volume(self, value): | |
current, max = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Audio: Speaker Volume']) | |
if value == current: | |
return | |
if value > max: | |
value = max | |
elif value < 0: | |
value = 0 | |
self._set_vcp_feature(vcp_codes['Audio: Speaker Volume'], value) | |
@property | |
def audio_speaker_max_volume(self): | |
return self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Audio: Speaker Volume'])[1] | |
_power_mode = {0x01: 'on', 0x05: 'off'} | |
_power_mode_i = {'on': 0x01, 'off': 0x05} | |
@property | |
def power_mode(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Power Mode'])[0] | |
return self._power_mode[value] | |
@power_mode.setter | |
def power_mode(self, value): | |
value = self._power_mode_i[value] | |
if value == self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['Power Mode'])[0]: | |
return | |
self._set_vcp_feature(vcp_codes['Power Mode'], value) | |
_osd_language = {0x02: 'english', 0x03: 'french', 0x04: 'german', | |
0x05: 'italian', 0x06: 'japanese', 0x09: 'russian', | |
0x0a: 'spanish', 0x0d: 'chinese', 0x12: 'czech', | |
0x14: 'dutch', 0x1e: 'polish'} | |
_osd_language_i = {'english': 0x02, 'french': 0x03, 'german': 0x04, | |
'italian': 0x05, 'japanese': 0x06, 'russian': 0x09, | |
'spanish': 0x0a, 'chinese': 0x0d, 'czech': 0x12, | |
'dutch': 0x14, 'polish': 0x1e} | |
@property | |
def osd_language(self): | |
value = self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['OSD Language'])[0] | |
return self._osd_language[value] | |
@osd_language.setter | |
def osd_language(self, value): | |
value = self._osd_language_i[value] | |
if value == self._get_vcf_feature_and_vcf_feature_reply( | |
vcp_codes['OSD Language'])[0]: | |
return | |
self._set_vcp_feature(vcp_codes['OSD Language'], value) | |
_restore_options = { | |
'all': 'Restore Factory Defaults', | |
'luminance': 'Restore Factory Luminance/ Contrast Defaults', | |
'color': 'Restore Factory Color Defaults'} | |
def restore(self, option): | |
"""Restore factory defaults ('all' / 'luminance' / 'colors')""" | |
self._set_vcp_feature(vcp_codes[self._restore_options[option]], 1) | |
def close(self): | |
if not ctypes.windll.dxva2.DestroyPhysicalMonitor(self._handle): | |
raise ctypes.WinError() | |
def __del__(self): | |
self.close() | |
if __name__ == '__main__': | |
import sys | |
import os | |
import argparse | |
import textwrap | |
# Custom Action for argparse | |
# The accepted number of arguments can be specified with '_nargs' (list) | |
# 'nargs' must be '*' | |
# Added also 'default_str' and 'const_str', formatted with 'default' and | |
# 'const' respectively, that can be used on the 'help' string | |
class VariableNArgsAction(argparse.Action): | |
def __init__(self, option_strings, dest, **kwargs): | |
self._nargs = kwargs.pop('_nargs', None) | |
self.const_str = kwargs.pop('const_str', None) | |
self.default_str = kwargs.pop('default_str', None) | |
argparse.Action.__init__(self, option_strings, dest, **kwargs) | |
if self.const_str is not None: | |
self.const_str = self.const_str.format(*self.const) | |
if self.default_str is not None: | |
self.default_str = self.default_str.format(*self.default) | |
def __call__(self, parser, namespace, values, option_string=None): | |
if len(values) not in self._nargs: | |
parser.error('argument {}: invalid number of arguments' | |
.format('/'.join(self.option_strings))) | |
if not values and self.const is not None: | |
values = self.const | |
setattr(namespace, self.dest, values) | |
# For VariableNArgsAction, use 'metavar' as the displayed args string | |
# XXX: not part of the public API | |
class RawDescriptionHelpFormatter2(argparse.RawDescriptionHelpFormatter): | |
def _format_args(self, action, *args, **kwargs): | |
if action.metavar is not None and isinstance(action, | |
VariableNArgsAction): | |
return action.metavar | |
return argparse.RawDescriptionHelpFormatter._format_args( | |
self, action, *args, **kwargs) | |
# Parse command line | |
name = os.path.basename(__file__) | |
description, license1, license2 = __doc__.rpartition('\nCopyright') | |
license = license1 + license2 | |
parser = argparse.ArgumentParser(prog=name, description=description, | |
epilog=license, formatter_class=RawDescriptionHelpFormatter2, | |
add_help=False) | |
parser_o = parser.add_argument_group('optional arguments', | |
'if not argument is passed, all available display info is printed') | |
parser_o.add_argument('-h', '--help', action='help', | |
help='show this help message and exit') | |
parser_o.add_argument('-V', '--version', action='version', | |
version='{} v{}\n{}'.format(name, version, license)) | |
parser_o.add_argument('-b', '--brightness', nargs='?', type=int, | |
const=-1, metavar='NEW_BRIGHTNESS', | |
help='retrieve or set brightness (0-100)') | |
parser_o.add_argument('-c', '--contrast', type=int, nargs='?', | |
const=-1, metavar='NEW_CONTRAST', | |
help='retrieve or set contrast (0-100)') | |
parser_o.add_argument('-cp', '--color_preset', type = str.lower, nargs='?', | |
choices=sorted(Display._color_presets_i.keys()), | |
const=-1, help='retrieve or set the color preset') | |
parser_o.add_argument('-u', '--rgb', nargs='*', action=VariableNArgsAction, | |
_nargs=(0, 3), type=int, const=-1, | |
metavar='[NEW_RED NEW_GREEN NEW_BLUE]', | |
help='retrieve or set red, green and blue gains ' | |
'(0-100)') | |
parser_o.add_argument('-i', '--input_source', type = str.lower, nargs='?', | |
choices=sorted(Display._input_sources_i.keys()), | |
const=-1, help='retrieve or set the input source') | |
parser_o.add_argument('-m', '--audio_mute', type = str.lower, nargs='?', | |
choices=sorted(Display._audio_mute_i.keys()), const=-1, | |
help='retrieve or set the audio mute state') | |
parser_o.add_argument('-v', '--audio_speaker_volume', | |
nargs='?', type=int, const=-1, metavar='NEW_VOLUME', | |
help='retrieve or set the audio speaker volume (0-100)') | |
parser_o.add_argument('-p', '--power_mode', type = str.lower, nargs='?', | |
choices=sorted(Display._power_mode_i.keys()), | |
const=-1, help='retrieve or set the power mode') | |
parser_o.add_argument('-l', '--osd_language', type = str.lower, nargs='?', | |
choices=sorted(Display._osd_language_i.keys()), | |
const=-1, help='retrieve or set the OSD language') | |
parser_o.add_argument('-r', '--restore', type = str.lower, | |
choices=sorted(Display._restore_options.keys()), | |
help='restore factory defaults') | |
settings = vars(parser.parse_args()) | |
# Set / show display parameters | |
for display in get_displays(): | |
model = display.capabilities['model'] | |
if model in ('W2413', 'W2713'): | |
if len(sys.argv) == 1: | |
print(textwrap.dedent('''\ | |
model: {model} | |
display technology type: {display.display_technology_type} | |
flat panel sub-pixel layout: {display.flat_panel_sub_pixel_layout} | |
display controller type: {display.display_controller_type} | |
display firmware level: v{display.display_firmware_level} | |
application enable key: {display.application_enable_key} | |
vcp version: v{display.vcp_version} | |
display usage time: {display.display_usage_time} hours | |
vertical frequency: {display.vertical_frequency} Hz | |
horizontal frequency: {display.horizontal_frequency} kHz | |
brightness: {display.brightness} | |
contrast {display.contrast} | |
color preset: {display.color_preset} | |
rgb gains: {display.rgb} | |
input source: {display.input_source} | |
audio mute: {display.audio_mute} | |
audio speaker volume: {display.audio_speaker_volume} | |
power mode: {display.power_mode} | |
osd language: {display.osd_language} | |
'''.format(model=model, display=display))) | |
sys.exit() | |
restore = settings.pop('restore') | |
if restore: | |
display.restore(restore) | |
for key, value in settings.items(): | |
if value is not None: | |
if value == -1: | |
print(getattr(display, key)) | |
else: | |
setattr(display, key, value) | |
else: | |
print('model not supported:', model, file=sys.stderr) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment