screen_brightness_control.windows

  1import ctypes
  2import platform
  3import threading
  4import time
  5from ctypes import POINTER, WINFUNCTYPE, Structure, WinError, byref, windll
  6from ctypes.wintypes import (BOOL, BYTE, DWORD, HANDLE, HDC, HMONITOR, LPARAM,
  7                             RECT, WCHAR)
  8from typing import List, Optional, Union
  9
 10import pythoncom
 11import win32api
 12import win32con
 13import wmi
 14
 15from . import filter_monitors, get_methods
 16from ._debug import log
 17from .helpers import EDID, __cache__, _monitor_brand_lookup
 18
 19# a bunch of typing classes were deprecated in Python 3.9
 20# in favour of collections.abc (https://www.python.org/dev/peps/pep-0585/)
 21if int(platform.python_version_tuple()[1]) < 9:
 22    from typing import Generator
 23else:
 24    from collections.abc import Generator
 25
 26
 27def _wmi_init():
 28    '''internal function to create and return a wmi instance'''
 29    # WMI calls don't work in new threads so we have to run this check
 30    if threading.current_thread() != threading.main_thread():
 31        pythoncom.CoInitialize()
 32    return wmi.WMI(namespace='wmi')
 33
 34
 35def enum_display_devices() -> Generator[win32api.PyDISPLAY_DEVICEType, None, None]:
 36    '''
 37    Yields all display devices connected to the computer
 38
 39    Yields:
 40        win32api.PyDISPLAY_DEVICEType
 41    '''
 42    for monitor_enum in win32api.EnumDisplayMonitors():
 43        pyhandle = monitor_enum[0]
 44        monitor_info = win32api.GetMonitorInfo(pyhandle)
 45        for adaptor_index in range(5):
 46            try:
 47                device = win32api.EnumDisplayDevices(monitor_info['Device'], adaptor_index, 1)
 48            except Exception:
 49                log.debug(f'failed to get display device {monitor_info["Device"]} on adaptor index {adaptor_index}')
 50                pass
 51            else:
 52                yield device
 53                break
 54
 55
 56def get_display_info() -> List[dict]:
 57    '''
 58    Gets information about all connected displays using WMI and win32api
 59
 60    Returns:
 61        list: list of dictionaries
 62
 63    Example:
 64        ```python
 65        import screen_brightness_control as s
 66
 67        info = s.windows.get_display_info()
 68        for display in info:
 69            print(display['name'])
 70        ```
 71    '''
 72    info = __cache__.get('windows_monitors_info_raw')
 73    if info is None:
 74        info = []
 75        try:
 76            # collect all monitor UIDs (derived from DeviceID)
 77            monitor_uids = {}
 78            for device in enum_display_devices():
 79                monitor_uids[device.DeviceID.split('#')[2]] = device
 80
 81            # gather list of laptop displays to check against later
 82            wmi = _wmi_init()
 83            try:
 84                laptop_displays = [
 85                    i.InstanceName
 86                    for i in wmi.WmiMonitorBrightness()
 87                ]
 88            except Exception as e:
 89                log.debug(f'get_display_info: failed to gather list of laptop displays - {e}')
 90                laptop_displays = []
 91
 92            extras, desktop, laptop = [], 0, 0
 93            uid_keys = list(monitor_uids.keys())
 94            for monitor in wmi.WmiMonitorDescriptorMethods():
 95                try:
 96                    model, serial, manufacturer, man_id, edid = None, None, None, None, None
 97                    instance_name = monitor.InstanceName.replace('_0', '', 1).split('\\')[2]
 98                    pydevice = monitor_uids[instance_name]
 99
100                    # get the EDID
101                    try:
102                        edid = ''.join(f'{char:02x}' for char in monitor.WmiGetMonitorRawEEdidV1Block(0)[0])
103                    except Exception as e:
104                        log.debug(f'failed to get EDID string for {monitor.InstanceName} - {type(e).__name__}: {e}')
105                        edid = None
106
107                    # get serial, model, manufacturer and manufacturer ID
108                    try:
109                        if edid is None:
110                            # we already log the EDID exception earlier so don't log it again here
111                            raise Exception('invalid EDID. Cannot parse')
112                        # we do the EDID parsing ourselves because calling wmi.WmiMonitorID
113                        # takes too long
114                        parsed = EDID.parse(edid)
115                        man_id, manufacturer, model, name, serial = parsed
116                        if name is None:
117                            raise Exception('parsed EDID returned invalid monitor name')
118                    except Exception as e:
119                        log.debug(f'exception parsing edid str for {monitor.InstanceName} - {type(e).__name__}: {e}')
120                        devid = pydevice.DeviceID.split('#')
121                        serial = devid[2]
122                        man_id = devid[1][:3]
123                        model = devid[3]
124                        del devid
125                        try:
126                            man_id, manufacturer = _monitor_brand_lookup(man_id)
127                        except TypeError:
128                            manufacturer = None
129
130                    if (serial, model) != (None, None):
131                        data = {
132                            'name': f'{manufacturer} {model}',
133                            'model': model,
134                            'serial': serial,
135                            'manufacturer': manufacturer,
136                            'manufacturer_id': man_id,
137                            'edid': edid
138                        }
139                        if monitor.InstanceName in laptop_displays:
140                            data['index'] = laptop
141                            data['method'] = WMI
142                            laptop += 1
143                        else:
144                            data['method'] = VCP
145                            desktop += 1
146
147                        if instance_name in uid_keys:
148                            # insert the data into the uid_keys list because
149                            # uid_keys has the monitors sorted correctly. This
150                            # means we don't have to re-sort the list later
151                            uid_keys[uid_keys.index(instance_name)] = data
152                        else:
153                            extras.append(data)
154                except Exception as e:
155                    log.debug(f'exception getting device info for {monitor.InstanceName} - {type(e).__name__}: {e}')
156                    pass
157
158            info = uid_keys + extras
159            if desktop:
160                # now make sure desktop monitors have the correct index
161                count = 0
162                for item in info:
163                    if item['method'] == VCP:
164                        item['index'] = count
165                        count += 1
166        except Exception as e:
167            log.debug(f'error gathering display information - {type(e).__name__}: {e}', exc_info=True)
168            pass
169
170        # return info only which has correct data
171        info = [i for i in info if isinstance(i, dict)]
172
173        __cache__.store('windows_monitors_info_raw', info)
174
175    return info
176
177
178class WMI:
179    '''
180    A collection of screen brightness related methods using the WMI API.
181    This class primarily works with laptop displays.
182    '''
183    @classmethod
184    def get_display_info(cls, display: Optional[Union[int, str]] = None) -> List[dict]:
185        '''
186        Returns a list of dictionaries of info about all detected monitors
187
188        Args:
189            display (str or int): [*Optional*] the monitor to return info about.
190                Pass in the serial number, name, model, edid or index
191
192        Returns:
193            list: list of dicts
194
195        Example:
196            ```python
197            import screen_brightness_control as sbc
198
199            info = sbc.windows.WMI.get_display_info()
200            for i in info:
201                print('================')
202                for key, value in i.items():
203                    print(key, ':', value)
204
205            # get information about the first WMI addressable monitor
206            primary_info = sbc.windows.WMI.get_display_info(0)
207
208            # get information about a monitor with a specific name
209            benq_info = sbc.windows.WMI.get_display_info('BenQ GL2450H')
210            ```
211        '''
212        info = [i for i in get_display_info() if i['method'] == cls]
213        if display is not None:
214            info = filter_monitors(display=display, haystack=info)
215        return info
216
217    @classmethod
218    def set_brightness(cls, value: int, display: Optional[int] = None):
219        '''
220        Sets the display brightness for Windows using WMI
221
222        Args:
223            value (int): The percentage to set the brightness to
224            display (int): The specific display you wish to query.
225
226        Raises:
227            LookupError: if the given display cannot be found
228
229        Example:
230            ```python
231            import screen_brightness_control as sbc
232
233            # set brightness of WMI addressable monitors to 50%
234            sbc.windows.WMI.set_brightness(50)
235
236            # set the primary display brightness to 75%
237            sbc.windows.WMI.set_brightness(75, display = 0)
238
239            # set the brightness of the secondary display to 25%
240            sbc.windows.WMI.set_brightness(25, display = 1)
241            ```
242        '''
243        brightness_method = _wmi_init().WmiMonitorBrightnessMethods()
244        if display is not None:
245            brightness_method = [brightness_method[display]]
246
247        for method in brightness_method:
248            method.WmiSetBrightness(value, 0)
249
250    @classmethod
251    def get_brightness(cls, display: Optional[int] = None) -> List[int]:
252        '''
253        Returns the current display brightness using WMI
254
255        Args:
256            display (int): The specific display you wish to query.
257
258        Returns:
259            list: list of integers (0 to 100)
260
261        Raises:
262            LookupError: if the given display cannot be found
263
264        Example:
265            ```python
266            import screen_brightness_control as sbc
267
268            # get brightness of all WMI addressable monitors
269            current_brightness = sbc.windows.WMI.get_brightness()
270            if type(current_brightness) is int:
271                print('There is only one detected display')
272            else:
273                print('There are', len(current_brightness), 'detected displays')
274
275            # get the primary display brightness
276            primary_brightness = sbc.windows.WMI.get_brightness(display = 0)
277
278            # get the brightness of the secondary monitor
279            benq_brightness = sbc.windows.WMI.get_brightness(display = 1)
280            ```
281        '''
282        brightness_method = _wmi_init().WmiMonitorBrightness()
283        if display is not None:
284            brightness_method = [brightness_method[display]]
285
286        values = [i.CurrentBrightness for i in brightness_method]
287        return values
288
289
290class VCP:
291    '''Collection of screen brightness related methods using the DDC/CI commands'''
292    _MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM)
293
294    class _PHYSICAL_MONITOR(Structure):
295        '''internal class, do not call'''
296        _fields_ = [('handle', HANDLE),
297                    ('description', WCHAR * 128)]
298
299    @classmethod
300    def iter_physical_monitors(cls, start: int = 0) -> Generator[ctypes.wintypes.HANDLE, None, None]:
301        '''
302        A generator to iterate through all physical monitors
303        and then close them again afterwards, yielding their handles.
304        It is not recommended to use this function unless you are familiar with `ctypes` and `windll`
305
306        Args:
307            start (int): skip the first X handles
308
309        Yields:
310            ctypes.wintypes.HANDLE
311
312        Raises:
313            ctypes.WinError: upon failure to enumerate through the monitors
314        '''
315        def callback(hmonitor, *_):
316            monitors.append(HMONITOR(hmonitor))
317            return True
318
319        monitors = []
320        if not windll.user32.EnumDisplayMonitors(None, None, cls._MONITORENUMPROC(callback), None):
321            raise WinError('EnumDisplayMonitors failed')
322
323        # user index keeps track of valid monitors
324        user_index = 0
325        # monitor index keeps track of valid and pseudo monitors
326        monitor_index = 0
327        display_devices = list(enum_display_devices())
328
329        wmi = _wmi_init()
330        try:
331            laptop_displays = [
332                i.InstanceName.replace('_0', '').split('\\')[2]
333                for i in wmi.WmiMonitorBrightness()
334            ]
335        except Exception as e:
336            log.debug(f'iter_physical_monitors: failed to gather list of laptop displays - {e}')
337            laptop_displays = []
338
339        for monitor in monitors:
340            # Get physical monitor count
341            count = DWORD()
342            if not windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count)):
343                raise WinError('call to GetNumberOfPhysicalMonitorsFromHMONITOR returned invalid result')
344            if count.value > 0:
345                # Get physical monitor handles
346                physical_array = (cls._PHYSICAL_MONITOR * count.value)()
347                if not windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array):
348                    raise WinError('call to GetPhysicalMonitorsFromHMONITOR returned invalid result')
349                for item in physical_array:
350                    # check that the monitor is not a pseudo monitor by
351                    # checking it's StateFlags for the
352                    # win32con DISPLAY_DEVICE_ATTACHED_TO_DESKTOP flag
353                    if display_devices[monitor_index].StateFlags & win32con.DISPLAY_DEVICE_ATTACHED_TO_DESKTOP:
354                        # check if monitor is actually a laptop display
355                        if display_devices[monitor_index].DeviceID.split('#')[2] not in laptop_displays:
356                            if start is None or user_index >= start:
357                                yield item.handle
358                            # increment user index as a valid monitor was found
359                            user_index += 1
360                    # increment monitor index
361                    monitor_index += 1
362                    windll.dxva2.DestroyPhysicalMonitor(item.handle)
363
364    @classmethod
365    def get_display_info(cls, display: Optional[Union[int, str]] = None) -> List[dict]:
366        '''
367        Returns a dictionary of info about all detected monitors or a selection of monitors
368
369        Args:
370            display (int or str): [*Optional*] the monitor to return info about.
371                Pass in the serial number, name, model, edid or index
372
373        Returns:
374            list: list of dicts
375
376        Example:
377            ```python
378            import screen_brightness_control as sbc
379
380            # get the information about all monitors
381            vcp_info = sbc.windows.VCP.get_display_info()
382            print(vcp_info)
383            # EG output: [{'name': 'BenQ GL2450H', ... }, {'name': 'Dell U2211H', ... }]
384
385            # get information about a monitor with this specific model
386            bnq_info = sbc.windows.VCP.get_display_info('GL2450H')
387            # EG output: {'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }
388            ```
389        '''
390        info = [i for i in get_display_info() if i['method'] == cls]
391        if display is not None:
392            info = filter_monitors(display=display, haystack=info)
393        return info
394
395    @classmethod
396    def get_brightness(cls, display: Optional[int] = None, max_tries: int = 50) -> List[int]:
397        '''
398        Retrieve the brightness of all connected displays using the `ctypes.windll` API
399
400        Args:
401            display (int): The specific display you wish to query.
402            max_tries (int): the maximum allowed number of attempts to
403                read the VCP output from the monitor
404
405        Returns:
406            list: list of ints (0 to 100)
407
408        Examples:
409            ```python
410            import screen_brightness_control as sbc
411
412            # Get the brightness for all detected displays
413            current_brightness = sbc.windows.VCP.get_brightness()
414            print('There are', len(current_brightness), 'detected displays')
415
416            # Get the brightness for the primary display
417            primary_brightness = sbc.windows.VCP.get_brightness(display = 0)[0]
418
419            # Get the brightness for a secondary display
420            secondary_brightness = sbc.windows.VCP.get_brightness(display = 1)[0]
421            ```
422        '''
423        code = BYTE(0x10)
424        values = []
425        start = display if display is not None else 0
426        for index, monitor in enumerate(cls.iter_physical_monitors(start=start), start=start):
427            current = __cache__.get(f'vcp_brightness_{index}')
428            if current is None:
429                cur_out = DWORD()
430                handle = HANDLE(monitor)
431                for attempt in range(max_tries):
432                    if windll.dxva2.GetVCPFeatureAndVCPFeatureReply(handle, code, None, byref(cur_out), None):
433                        current = cur_out.value
434                        break
435                    current = None
436                    time.sleep(0.02 if attempt < 20 else 0.1)
437
438            if current is not None:
439                __cache__.store(f'vcp_brightness_{index}', current, expires=0.1)
440                values.append(current)
441
442            if display == index:
443                # if we have just got the display we wanted then exit here
444                # no point iterating through all the other ones
445                break
446
447        return values
448
449    @classmethod
450    def set_brightness(cls, value: int, display: Optional[int] = None, max_tries: int = 50):
451        '''
452        Sets the brightness for all connected displays using the `ctypes.windll` API
453
454        Args:
455            display (int): The specific display you wish to query.
456            max_tries (int): the maximum allowed number of attempts to
457                send the VCP input to the monitor
458
459        Examples:
460            ```python
461            import screen_brightness_control as sbc
462
463            # Set the brightness for all detected displays to 50%
464            sbc.windows.VCP.set_brightness(50)
465
466            # Set the brightness for the primary display to 75%
467            sbc.windows.VCP.set_brightness(75, display = 0)
468
469            # Set the brightness for a secondary display to 25%
470            sbc.windows.VCP.set_brightness(25, display = 1)
471            ```
472        '''
473        __cache__.expire(startswith='vcp_brightness_')
474        code = BYTE(0x10)
475        value = DWORD(value)
476        start = display if display is not None else 0
477        for index, monitor in enumerate(cls.iter_physical_monitors(start=start), start=start):
478            if display is None or display == index:
479                handle = HANDLE(monitor)
480                for attempt in range(max_tries):
481                    if windll.dxva2.SetVCPFeature(handle, code, value):
482                        break
483                    time.sleep(0.02 if attempt < 20 else 0.1)
484
485
486def list_monitors_info(method: Optional[str] = None, allow_duplicates: bool = False) -> List[dict]:
487    '''
488    Lists detailed information about all detected monitors
489
490    Args:
491        method (str): the method the monitor can be addressed by. See `screen_brightness_control.get_methods`
492            for more info on available methods
493        allow_duplicates (bool): whether to filter out duplicate displays (displays with the same EDID) or not
494
495    Returns:
496        list: list of dicts upon success, empty list upon failure
497
498    Raises:
499        ValueError: if the method kwarg is invalid
500
501    Example:
502        ```python
503        import screen_brightness_control as sbc
504
505        monitors = sbc.windows.list_monitors_info()
506        for info in monitors:
507            print('=======================')
508            # the manufacturer name plus the model
509            print('Name:', info['name'])
510            # the general model of the display
511            print('Model:', info['model'])
512            # a unique string assigned by Windows to this display
513            print('Serial:', info['serial'])
514            # the name of the brand of the monitor
515            print('Manufacturer:', info['manufacturer'])
516            # the 3 letter code corresponding to the brand name, EG: BNQ -> BenQ
517            print('Manufacturer ID:', info['manufacturer_id'])
518            # the index of that display FOR THE SPECIFIC METHOD THE DISPLAY USES
519            print('Index:', info['index'])
520            # the method this monitor can be addressed by
521            print('Method:', info['method'])
522            # the EDID string of the monitor
523            print('EDID:', info['edid'])
524        ```
525    '''
526    # no caching here because get_display_info caches its results
527    info = get_display_info()
528
529    all_methods = get_methods()
530
531    if method is not None:
532        method = method.lower()
533        if method not in all_methods:
534            log.debug(f'requested method {repr(method)} invalid')
535            raise ValueError(f'method must be one of: {list(all_methods)}')
536        info = [i for i in info if i['method'].__name__.lower() == method]
537
538    if allow_duplicates:
539        return info
540
541    try:
542        # use filter_monitors to remove duplicates
543        return filter_monitors(haystack=info)
544    except LookupError:
545        return []

def enum_display_devices() -> collections.abc.Generator[win32api.PyDISPLAY_DEVICEType, None, None]:
36def enum_display_devices() -> Generator[win32api.PyDISPLAY_DEVICEType, None, None]:
37    '''
38    Yields all display devices connected to the computer
39
40    Yields:
41        win32api.PyDISPLAY_DEVICEType
42    '''
43    for monitor_enum in win32api.EnumDisplayMonitors():
44        pyhandle = monitor_enum[0]
45        monitor_info = win32api.GetMonitorInfo(pyhandle)
46        for adaptor_index in range(5):
47            try:
48                device = win32api.EnumDisplayDevices(monitor_info['Device'], adaptor_index, 1)
49            except Exception:
50                log.debug(f'failed to get display device {monitor_info["Device"]} on adaptor index {adaptor_index}')
51                pass
52            else:
53                yield device
54                break

Yields all display devices connected to the computer

Yields
  • win32api.PyDISPLAY_DEVICEType
def get_display_info() -> List[dict]:
 57def get_display_info() -> List[dict]:
 58    '''
 59    Gets information about all connected displays using WMI and win32api
 60
 61    Returns:
 62        list: list of dictionaries
 63
 64    Example:
 65        ```python
 66        import screen_brightness_control as s
 67
 68        info = s.windows.get_display_info()
 69        for display in info:
 70            print(display['name'])
 71        ```
 72    '''
 73    info = __cache__.get('windows_monitors_info_raw')
 74    if info is None:
 75        info = []
 76        try:
 77            # collect all monitor UIDs (derived from DeviceID)
 78            monitor_uids = {}
 79            for device in enum_display_devices():
 80                monitor_uids[device.DeviceID.split('#')[2]] = device
 81
 82            # gather list of laptop displays to check against later
 83            wmi = _wmi_init()
 84            try:
 85                laptop_displays = [
 86                    i.InstanceName
 87                    for i in wmi.WmiMonitorBrightness()
 88                ]
 89            except Exception as e:
 90                log.debug(f'get_display_info: failed to gather list of laptop displays - {e}')
 91                laptop_displays = []
 92
 93            extras, desktop, laptop = [], 0, 0
 94            uid_keys = list(monitor_uids.keys())
 95            for monitor in wmi.WmiMonitorDescriptorMethods():
 96                try:
 97                    model, serial, manufacturer, man_id, edid = None, None, None, None, None
 98                    instance_name = monitor.InstanceName.replace('_0', '', 1).split('\\')[2]
 99                    pydevice = monitor_uids[instance_name]
100
101                    # get the EDID
102                    try:
103                        edid = ''.join(f'{char:02x}' for char in monitor.WmiGetMonitorRawEEdidV1Block(0)[0])
104                    except Exception as e:
105                        log.debug(f'failed to get EDID string for {monitor.InstanceName} - {type(e).__name__}: {e}')
106                        edid = None
107
108                    # get serial, model, manufacturer and manufacturer ID
109                    try:
110                        if edid is None:
111                            # we already log the EDID exception earlier so don't log it again here
112                            raise Exception('invalid EDID. Cannot parse')
113                        # we do the EDID parsing ourselves because calling wmi.WmiMonitorID
114                        # takes too long
115                        parsed = EDID.parse(edid)
116                        man_id, manufacturer, model, name, serial = parsed
117                        if name is None:
118                            raise Exception('parsed EDID returned invalid monitor name')
119                    except Exception as e:
120                        log.debug(f'exception parsing edid str for {monitor.InstanceName} - {type(e).__name__}: {e}')
121                        devid = pydevice.DeviceID.split('#')
122                        serial = devid[2]
123                        man_id = devid[1][:3]
124                        model = devid[3]
125                        del devid
126                        try:
127                            man_id, manufacturer = _monitor_brand_lookup(man_id)
128                        except TypeError:
129                            manufacturer = None
130
131                    if (serial, model) != (None, None):
132                        data = {
133                            'name': f'{manufacturer} {model}',
134                            'model': model,
135                            'serial': serial,
136                            'manufacturer': manufacturer,
137                            'manufacturer_id': man_id,
138                            'edid': edid
139                        }
140                        if monitor.InstanceName in laptop_displays:
141                            data['index'] = laptop
142                            data['method'] = WMI
143                            laptop += 1
144                        else:
145                            data['method'] = VCP
146                            desktop += 1
147
148                        if instance_name in uid_keys:
149                            # insert the data into the uid_keys list because
150                            # uid_keys has the monitors sorted correctly. This
151                            # means we don't have to re-sort the list later
152                            uid_keys[uid_keys.index(instance_name)] = data
153                        else:
154                            extras.append(data)
155                except Exception as e:
156                    log.debug(f'exception getting device info for {monitor.InstanceName} - {type(e).__name__}: {e}')
157                    pass
158
159            info = uid_keys + extras
160            if desktop:
161                # now make sure desktop monitors have the correct index
162                count = 0
163                for item in info:
164                    if item['method'] == VCP:
165                        item['index'] = count
166                        count += 1
167        except Exception as e:
168            log.debug(f'error gathering display information - {type(e).__name__}: {e}', exc_info=True)
169            pass
170
171        # return info only which has correct data
172        info = [i for i in info if isinstance(i, dict)]
173
174        __cache__.store('windows_monitors_info_raw', info)
175
176    return info

Gets information about all connected displays using WMI and win32api

Returns
  • list: list of dictionaries
Example
import screen_brightness_control as s

info = s.windows.get_display_info()
for display in info:
    print(display['name'])
class WMI:
179class WMI:
180    '''
181    A collection of screen brightness related methods using the WMI API.
182    This class primarily works with laptop displays.
183    '''
184    @classmethod
185    def get_display_info(cls, display: Optional[Union[int, str]] = None) -> List[dict]:
186        '''
187        Returns a list of dictionaries of info about all detected monitors
188
189        Args:
190            display (str or int): [*Optional*] the monitor to return info about.
191                Pass in the serial number, name, model, edid or index
192
193        Returns:
194            list: list of dicts
195
196        Example:
197            ```python
198            import screen_brightness_control as sbc
199
200            info = sbc.windows.WMI.get_display_info()
201            for i in info:
202                print('================')
203                for key, value in i.items():
204                    print(key, ':', value)
205
206            # get information about the first WMI addressable monitor
207            primary_info = sbc.windows.WMI.get_display_info(0)
208
209            # get information about a monitor with a specific name
210            benq_info = sbc.windows.WMI.get_display_info('BenQ GL2450H')
211            ```
212        '''
213        info = [i for i in get_display_info() if i['method'] == cls]
214        if display is not None:
215            info = filter_monitors(display=display, haystack=info)
216        return info
217
218    @classmethod
219    def set_brightness(cls, value: int, display: Optional[int] = None):
220        '''
221        Sets the display brightness for Windows using WMI
222
223        Args:
224            value (int): The percentage to set the brightness to
225            display (int): The specific display you wish to query.
226
227        Raises:
228            LookupError: if the given display cannot be found
229
230        Example:
231            ```python
232            import screen_brightness_control as sbc
233
234            # set brightness of WMI addressable monitors to 50%
235            sbc.windows.WMI.set_brightness(50)
236
237            # set the primary display brightness to 75%
238            sbc.windows.WMI.set_brightness(75, display = 0)
239
240            # set the brightness of the secondary display to 25%
241            sbc.windows.WMI.set_brightness(25, display = 1)
242            ```
243        '''
244        brightness_method = _wmi_init().WmiMonitorBrightnessMethods()
245        if display is not None:
246            brightness_method = [brightness_method[display]]
247
248        for method in brightness_method:
249            method.WmiSetBrightness(value, 0)
250
251    @classmethod
252    def get_brightness(cls, display: Optional[int] = None) -> List[int]:
253        '''
254        Returns the current display brightness using WMI
255
256        Args:
257            display (int): The specific display you wish to query.
258
259        Returns:
260            list: list of integers (0 to 100)
261
262        Raises:
263            LookupError: if the given display cannot be found
264
265        Example:
266            ```python
267            import screen_brightness_control as sbc
268
269            # get brightness of all WMI addressable monitors
270            current_brightness = sbc.windows.WMI.get_brightness()
271            if type(current_brightness) is int:
272                print('There is only one detected display')
273            else:
274                print('There are', len(current_brightness), 'detected displays')
275
276            # get the primary display brightness
277            primary_brightness = sbc.windows.WMI.get_brightness(display = 0)
278
279            # get the brightness of the secondary monitor
280            benq_brightness = sbc.windows.WMI.get_brightness(display = 1)
281            ```
282        '''
283        brightness_method = _wmi_init().WmiMonitorBrightness()
284        if display is not None:
285            brightness_method = [brightness_method[display]]
286
287        values = [i.CurrentBrightness for i in brightness_method]
288        return values

A collection of screen brightness related methods using the WMI API. This class primarily works with laptop displays.

WMI()
@classmethod
def get_display_info(cls, display: Union[str, int, NoneType] = None) -> List[dict]:
184    @classmethod
185    def get_display_info(cls, display: Optional[Union[int, str]] = None) -> List[dict]:
186        '''
187        Returns a list of dictionaries of info about all detected monitors
188
189        Args:
190            display (str or int): [*Optional*] the monitor to return info about.
191                Pass in the serial number, name, model, edid or index
192
193        Returns:
194            list: list of dicts
195
196        Example:
197            ```python
198            import screen_brightness_control as sbc
199
200            info = sbc.windows.WMI.get_display_info()
201            for i in info:
202                print('================')
203                for key, value in i.items():
204                    print(key, ':', value)
205
206            # get information about the first WMI addressable monitor
207            primary_info = sbc.windows.WMI.get_display_info(0)
208
209            # get information about a monitor with a specific name
210            benq_info = sbc.windows.WMI.get_display_info('BenQ GL2450H')
211            ```
212        '''
213        info = [i for i in get_display_info() if i['method'] == cls]
214        if display is not None:
215            info = filter_monitors(display=display, haystack=info)
216        return info

Returns a list of dictionaries of info about all detected monitors

Args
  • display (str or int): [Optional] the monitor to return info about. Pass in the serial number, name, model, edid or index
Returns
  • list: list of dicts
Example
import screen_brightness_control as sbc

info = sbc.windows.WMI.get_display_info()
for i in info:
    print('================')
    for key, value in i.items():
        print(key, ':', value)

# get information about the first WMI addressable monitor
primary_info = sbc.windows.WMI.get_display_info(0)

# get information about a monitor with a specific name
benq_info = sbc.windows.WMI.get_display_info('BenQ GL2450H')
@classmethod
def set_brightness(cls, value: int, display: Optional[int] = None):
218    @classmethod
219    def set_brightness(cls, value: int, display: Optional[int] = None):
220        '''
221        Sets the display brightness for Windows using WMI
222
223        Args:
224            value (int): The percentage to set the brightness to
225            display (int): The specific display you wish to query.
226
227        Raises:
228            LookupError: if the given display cannot be found
229
230        Example:
231            ```python
232            import screen_brightness_control as sbc
233
234            # set brightness of WMI addressable monitors to 50%
235            sbc.windows.WMI.set_brightness(50)
236
237            # set the primary display brightness to 75%
238            sbc.windows.WMI.set_brightness(75, display = 0)
239
240            # set the brightness of the secondary display to 25%
241            sbc.windows.WMI.set_brightness(25, display = 1)
242            ```
243        '''
244        brightness_method = _wmi_init().WmiMonitorBrightnessMethods()
245        if display is not None:
246            brightness_method = [brightness_method[display]]
247
248        for method in brightness_method:
249            method.WmiSetBrightness(value, 0)

Sets the display brightness for Windows using WMI

Args
  • value (int): The percentage to set the brightness to
  • display (int): The specific display you wish to query.
Raises
  • LookupError: if the given display cannot be found
Example
import screen_brightness_control as sbc

# set brightness of WMI addressable monitors to 50%
sbc.windows.WMI.set_brightness(50)

# set the primary display brightness to 75%
sbc.windows.WMI.set_brightness(75, display = 0)

# set the brightness of the secondary display to 25%
sbc.windows.WMI.set_brightness(25, display = 1)
@classmethod
def get_brightness(cls, display: Optional[int] = None) -> List[int]:
251    @classmethod
252    def get_brightness(cls, display: Optional[int] = None) -> List[int]:
253        '''
254        Returns the current display brightness using WMI
255
256        Args:
257            display (int): The specific display you wish to query.
258
259        Returns:
260            list: list of integers (0 to 100)
261
262        Raises:
263            LookupError: if the given display cannot be found
264
265        Example:
266            ```python
267            import screen_brightness_control as sbc
268
269            # get brightness of all WMI addressable monitors
270            current_brightness = sbc.windows.WMI.get_brightness()
271            if type(current_brightness) is int:
272                print('There is only one detected display')
273            else:
274                print('There are', len(current_brightness), 'detected displays')
275
276            # get the primary display brightness
277            primary_brightness = sbc.windows.WMI.get_brightness(display = 0)
278
279            # get the brightness of the secondary monitor
280            benq_brightness = sbc.windows.WMI.get_brightness(display = 1)
281            ```
282        '''
283        brightness_method = _wmi_init().WmiMonitorBrightness()
284        if display is not None:
285            brightness_method = [brightness_method[display]]
286
287        values = [i.CurrentBrightness for i in brightness_method]
288        return values

Returns the current display brightness using WMI

Args
  • display (int): The specific display you wish to query.
Returns
  • list: list of integers (0 to 100)
Raises
  • LookupError: if the given display cannot be found
Example
import screen_brightness_control as sbc

# get brightness of all WMI addressable monitors
current_brightness = sbc.windows.WMI.get_brightness()
if type(current_brightness) is int:
    print('There is only one detected display')
else:
    print('There are', len(current_brightness), 'detected displays')

# get the primary display brightness
primary_brightness = sbc.windows.WMI.get_brightness(display = 0)

# get the brightness of the secondary monitor
benq_brightness = sbc.windows.WMI.get_brightness(display = 1)
class VCP:
291class VCP:
292    '''Collection of screen brightness related methods using the DDC/CI commands'''
293    _MONITORENUMPROC = WINFUNCTYPE(BOOL, HMONITOR, HDC, POINTER(RECT), LPARAM)
294
295    class _PHYSICAL_MONITOR(Structure):
296        '''internal class, do not call'''
297        _fields_ = [('handle', HANDLE),
298                    ('description', WCHAR * 128)]
299
300    @classmethod
301    def iter_physical_monitors(cls, start: int = 0) -> Generator[ctypes.wintypes.HANDLE, None, None]:
302        '''
303        A generator to iterate through all physical monitors
304        and then close them again afterwards, yielding their handles.
305        It is not recommended to use this function unless you are familiar with `ctypes` and `windll`
306
307        Args:
308            start (int): skip the first X handles
309
310        Yields:
311            ctypes.wintypes.HANDLE
312
313        Raises:
314            ctypes.WinError: upon failure to enumerate through the monitors
315        '''
316        def callback(hmonitor, *_):
317            monitors.append(HMONITOR(hmonitor))
318            return True
319
320        monitors = []
321        if not windll.user32.EnumDisplayMonitors(None, None, cls._MONITORENUMPROC(callback), None):
322            raise WinError('EnumDisplayMonitors failed')
323
324        # user index keeps track of valid monitors
325        user_index = 0
326        # monitor index keeps track of valid and pseudo monitors
327        monitor_index = 0
328        display_devices = list(enum_display_devices())
329
330        wmi = _wmi_init()
331        try:
332            laptop_displays = [
333                i.InstanceName.replace('_0', '').split('\\')[2]
334                for i in wmi.WmiMonitorBrightness()
335            ]
336        except Exception as e:
337            log.debug(f'iter_physical_monitors: failed to gather list of laptop displays - {e}')
338            laptop_displays = []
339
340        for monitor in monitors:
341            # Get physical monitor count
342            count = DWORD()
343            if not windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count)):
344                raise WinError('call to GetNumberOfPhysicalMonitorsFromHMONITOR returned invalid result')
345            if count.value > 0:
346                # Get physical monitor handles
347                physical_array = (cls._PHYSICAL_MONITOR * count.value)()
348                if not windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array):
349                    raise WinError('call to GetPhysicalMonitorsFromHMONITOR returned invalid result')
350                for item in physical_array:
351                    # check that the monitor is not a pseudo monitor by
352                    # checking it's StateFlags for the
353                    # win32con DISPLAY_DEVICE_ATTACHED_TO_DESKTOP flag
354                    if display_devices[monitor_index].StateFlags & win32con.DISPLAY_DEVICE_ATTACHED_TO_DESKTOP:
355                        # check if monitor is actually a laptop display
356                        if display_devices[monitor_index].DeviceID.split('#')[2] not in laptop_displays:
357                            if start is None or user_index >= start:
358                                yield item.handle
359                            # increment user index as a valid monitor was found
360                            user_index += 1
361                    # increment monitor index
362                    monitor_index += 1
363                    windll.dxva2.DestroyPhysicalMonitor(item.handle)
364
365    @classmethod
366    def get_display_info(cls, display: Optional[Union[int, str]] = None) -> List[dict]:
367        '''
368        Returns a dictionary of info about all detected monitors or a selection of monitors
369
370        Args:
371            display (int or str): [*Optional*] the monitor to return info about.
372                Pass in the serial number, name, model, edid or index
373
374        Returns:
375            list: list of dicts
376
377        Example:
378            ```python
379            import screen_brightness_control as sbc
380
381            # get the information about all monitors
382            vcp_info = sbc.windows.VCP.get_display_info()
383            print(vcp_info)
384            # EG output: [{'name': 'BenQ GL2450H', ... }, {'name': 'Dell U2211H', ... }]
385
386            # get information about a monitor with this specific model
387            bnq_info = sbc.windows.VCP.get_display_info('GL2450H')
388            # EG output: {'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }
389            ```
390        '''
391        info = [i for i in get_display_info() if i['method'] == cls]
392        if display is not None:
393            info = filter_monitors(display=display, haystack=info)
394        return info
395
396    @classmethod
397    def get_brightness(cls, display: Optional[int] = None, max_tries: int = 50) -> List[int]:
398        '''
399        Retrieve the brightness of all connected displays using the `ctypes.windll` API
400
401        Args:
402            display (int): The specific display you wish to query.
403            max_tries (int): the maximum allowed number of attempts to
404                read the VCP output from the monitor
405
406        Returns:
407            list: list of ints (0 to 100)
408
409        Examples:
410            ```python
411            import screen_brightness_control as sbc
412
413            # Get the brightness for all detected displays
414            current_brightness = sbc.windows.VCP.get_brightness()
415            print('There are', len(current_brightness), 'detected displays')
416
417            # Get the brightness for the primary display
418            primary_brightness = sbc.windows.VCP.get_brightness(display = 0)[0]
419
420            # Get the brightness for a secondary display
421            secondary_brightness = sbc.windows.VCP.get_brightness(display = 1)[0]
422            ```
423        '''
424        code = BYTE(0x10)
425        values = []
426        start = display if display is not None else 0
427        for index, monitor in enumerate(cls.iter_physical_monitors(start=start), start=start):
428            current = __cache__.get(f'vcp_brightness_{index}')
429            if current is None:
430                cur_out = DWORD()
431                handle = HANDLE(monitor)
432                for attempt in range(max_tries):
433                    if windll.dxva2.GetVCPFeatureAndVCPFeatureReply(handle, code, None, byref(cur_out), None):
434                        current = cur_out.value
435                        break
436                    current = None
437                    time.sleep(0.02 if attempt < 20 else 0.1)
438
439            if current is not None:
440                __cache__.store(f'vcp_brightness_{index}', current, expires=0.1)
441                values.append(current)
442
443            if display == index:
444                # if we have just got the display we wanted then exit here
445                # no point iterating through all the other ones
446                break
447
448        return values
449
450    @classmethod
451    def set_brightness(cls, value: int, display: Optional[int] = None, max_tries: int = 50):
452        '''
453        Sets the brightness for all connected displays using the `ctypes.windll` API
454
455        Args:
456            display (int): The specific display you wish to query.
457            max_tries (int): the maximum allowed number of attempts to
458                send the VCP input to the monitor
459
460        Examples:
461            ```python
462            import screen_brightness_control as sbc
463
464            # Set the brightness for all detected displays to 50%
465            sbc.windows.VCP.set_brightness(50)
466
467            # Set the brightness for the primary display to 75%
468            sbc.windows.VCP.set_brightness(75, display = 0)
469
470            # Set the brightness for a secondary display to 25%
471            sbc.windows.VCP.set_brightness(25, display = 1)
472            ```
473        '''
474        __cache__.expire(startswith='vcp_brightness_')
475        code = BYTE(0x10)
476        value = DWORD(value)
477        start = display if display is not None else 0
478        for index, monitor in enumerate(cls.iter_physical_monitors(start=start), start=start):
479            if display is None or display == index:
480                handle = HANDLE(monitor)
481                for attempt in range(max_tries):
482                    if windll.dxva2.SetVCPFeature(handle, code, value):
483                        break
484                    time.sleep(0.02 if attempt < 20 else 0.1)

Collection of screen brightness related methods using the DDC/CI commands

VCP()
@classmethod
def iter_physical_monitors( cls, start: int = 0) -> collections.abc.Generator[wintypes.HANDLE, None, None]:
300    @classmethod
301    def iter_physical_monitors(cls, start: int = 0) -> Generator[ctypes.wintypes.HANDLE, None, None]:
302        '''
303        A generator to iterate through all physical monitors
304        and then close them again afterwards, yielding their handles.
305        It is not recommended to use this function unless you are familiar with `ctypes` and `windll`
306
307        Args:
308            start (int): skip the first X handles
309
310        Yields:
311            ctypes.wintypes.HANDLE
312
313        Raises:
314            ctypes.WinError: upon failure to enumerate through the monitors
315        '''
316        def callback(hmonitor, *_):
317            monitors.append(HMONITOR(hmonitor))
318            return True
319
320        monitors = []
321        if not windll.user32.EnumDisplayMonitors(None, None, cls._MONITORENUMPROC(callback), None):
322            raise WinError('EnumDisplayMonitors failed')
323
324        # user index keeps track of valid monitors
325        user_index = 0
326        # monitor index keeps track of valid and pseudo monitors
327        monitor_index = 0
328        display_devices = list(enum_display_devices())
329
330        wmi = _wmi_init()
331        try:
332            laptop_displays = [
333                i.InstanceName.replace('_0', '').split('\\')[2]
334                for i in wmi.WmiMonitorBrightness()
335            ]
336        except Exception as e:
337            log.debug(f'iter_physical_monitors: failed to gather list of laptop displays - {e}')
338            laptop_displays = []
339
340        for monitor in monitors:
341            # Get physical monitor count
342            count = DWORD()
343            if not windll.dxva2.GetNumberOfPhysicalMonitorsFromHMONITOR(monitor, byref(count)):
344                raise WinError('call to GetNumberOfPhysicalMonitorsFromHMONITOR returned invalid result')
345            if count.value > 0:
346                # Get physical monitor handles
347                physical_array = (cls._PHYSICAL_MONITOR * count.value)()
348                if not windll.dxva2.GetPhysicalMonitorsFromHMONITOR(monitor, count.value, physical_array):
349                    raise WinError('call to GetPhysicalMonitorsFromHMONITOR returned invalid result')
350                for item in physical_array:
351                    # check that the monitor is not a pseudo monitor by
352                    # checking it's StateFlags for the
353                    # win32con DISPLAY_DEVICE_ATTACHED_TO_DESKTOP flag
354                    if display_devices[monitor_index].StateFlags & win32con.DISPLAY_DEVICE_ATTACHED_TO_DESKTOP:
355                        # check if monitor is actually a laptop display
356                        if display_devices[monitor_index].DeviceID.split('#')[2] not in laptop_displays:
357                            if start is None or user_index >= start:
358                                yield item.handle
359                            # increment user index as a valid monitor was found
360                            user_index += 1
361                    # increment monitor index
362                    monitor_index += 1
363                    windll.dxva2.DestroyPhysicalMonitor(item.handle)

A generator to iterate through all physical monitors and then close them again afterwards, yielding their handles. It is not recommended to use this function unless you are familiar with ctypes and windll

Args
  • start (int): skip the first X handles
Yields
  • ctypes.wintypes.HANDLE
Raises
  • ctypes.WinError: upon failure to enumerate through the monitors
@classmethod
def get_display_info(cls, display: Union[str, int, NoneType] = None) -> List[dict]:
365    @classmethod
366    def get_display_info(cls, display: Optional[Union[int, str]] = None) -> List[dict]:
367        '''
368        Returns a dictionary of info about all detected monitors or a selection of monitors
369
370        Args:
371            display (int or str): [*Optional*] the monitor to return info about.
372                Pass in the serial number, name, model, edid or index
373
374        Returns:
375            list: list of dicts
376
377        Example:
378            ```python
379            import screen_brightness_control as sbc
380
381            # get the information about all monitors
382            vcp_info = sbc.windows.VCP.get_display_info()
383            print(vcp_info)
384            # EG output: [{'name': 'BenQ GL2450H', ... }, {'name': 'Dell U2211H', ... }]
385
386            # get information about a monitor with this specific model
387            bnq_info = sbc.windows.VCP.get_display_info('GL2450H')
388            # EG output: {'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }
389            ```
390        '''
391        info = [i for i in get_display_info() if i['method'] == cls]
392        if display is not None:
393            info = filter_monitors(display=display, haystack=info)
394        return info

Returns a dictionary of info about all detected monitors or a selection of monitors

Args
  • display (int or str): [Optional] the monitor to return info about. Pass in the serial number, name, model, edid or index
Returns
  • list: list of dicts
Example
import screen_brightness_control as sbc

# get the information about all monitors
vcp_info = sbc.windows.VCP.get_display_info()
print(vcp_info)
# EG output: [{'name': 'BenQ GL2450H', ... }, {'name': 'Dell U2211H', ... }]

# get information about a monitor with this specific model
bnq_info = sbc.windows.VCP.get_display_info('GL2450H')
# EG output: {'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }
@classmethod
def get_brightness(cls, display: Optional[int] = None, max_tries: int = 50) -> List[int]:
396    @classmethod
397    def get_brightness(cls, display: Optional[int] = None, max_tries: int = 50) -> List[int]:
398        '''
399        Retrieve the brightness of all connected displays using the `ctypes.windll` API
400
401        Args:
402            display (int): The specific display you wish to query.
403            max_tries (int): the maximum allowed number of attempts to
404                read the VCP output from the monitor
405
406        Returns:
407            list: list of ints (0 to 100)
408
409        Examples:
410            ```python
411            import screen_brightness_control as sbc
412
413            # Get the brightness for all detected displays
414            current_brightness = sbc.windows.VCP.get_brightness()
415            print('There are', len(current_brightness), 'detected displays')
416
417            # Get the brightness for the primary display
418            primary_brightness = sbc.windows.VCP.get_brightness(display = 0)[0]
419
420            # Get the brightness for a secondary display
421            secondary_brightness = sbc.windows.VCP.get_brightness(display = 1)[0]
422            ```
423        '''
424        code = BYTE(0x10)
425        values = []
426        start = display if display is not None else 0
427        for index, monitor in enumerate(cls.iter_physical_monitors(start=start), start=start):
428            current = __cache__.get(f'vcp_brightness_{index}')
429            if current is None:
430                cur_out = DWORD()
431                handle = HANDLE(monitor)
432                for attempt in range(max_tries):
433                    if windll.dxva2.GetVCPFeatureAndVCPFeatureReply(handle, code, None, byref(cur_out), None):
434                        current = cur_out.value
435                        break
436                    current = None
437                    time.sleep(0.02 if attempt < 20 else 0.1)
438
439            if current is not None:
440                __cache__.store(f'vcp_brightness_{index}', current, expires=0.1)
441                values.append(current)
442
443            if display == index:
444                # if we have just got the display we wanted then exit here
445                # no point iterating through all the other ones
446                break
447
448        return values

Retrieve the brightness of all connected displays using the ctypes.windll API

Args
  • display (int): The specific display you wish to query.
  • max_tries (int): the maximum allowed number of attempts to read the VCP output from the monitor
Returns
  • list: list of ints (0 to 100)
Examples
import screen_brightness_control as sbc

# Get the brightness for all detected displays
current_brightness = sbc.windows.VCP.get_brightness()
print('There are', len(current_brightness), 'detected displays')

# Get the brightness for the primary display
primary_brightness = sbc.windows.VCP.get_brightness(display = 0)[0]

# Get the brightness for a secondary display
secondary_brightness = sbc.windows.VCP.get_brightness(display = 1)[0]
@classmethod
def set_brightness(cls, value: int, display: Optional[int] = None, max_tries: int = 50):
450    @classmethod
451    def set_brightness(cls, value: int, display: Optional[int] = None, max_tries: int = 50):
452        '''
453        Sets the brightness for all connected displays using the `ctypes.windll` API
454
455        Args:
456            display (int): The specific display you wish to query.
457            max_tries (int): the maximum allowed number of attempts to
458                send the VCP input to the monitor
459
460        Examples:
461            ```python
462            import screen_brightness_control as sbc
463
464            # Set the brightness for all detected displays to 50%
465            sbc.windows.VCP.set_brightness(50)
466
467            # Set the brightness for the primary display to 75%
468            sbc.windows.VCP.set_brightness(75, display = 0)
469
470            # Set the brightness for a secondary display to 25%
471            sbc.windows.VCP.set_brightness(25, display = 1)
472            ```
473        '''
474        __cache__.expire(startswith='vcp_brightness_')
475        code = BYTE(0x10)
476        value = DWORD(value)
477        start = display if display is not None else 0
478        for index, monitor in enumerate(cls.iter_physical_monitors(start=start), start=start):
479            if display is None or display == index:
480                handle = HANDLE(monitor)
481                for attempt in range(max_tries):
482                    if windll.dxva2.SetVCPFeature(handle, code, value):
483                        break
484                    time.sleep(0.02 if attempt < 20 else 0.1)

Sets the brightness for all connected displays using the ctypes.windll API

Args
  • display (int): The specific display you wish to query.
  • max_tries (int): the maximum allowed number of attempts to send the VCP input to the monitor
Examples
import screen_brightness_control as sbc

# Set the brightness for all detected displays to 50%
sbc.windows.VCP.set_brightness(50)

# Set the brightness for the primary display to 75%
sbc.windows.VCP.set_brightness(75, display = 0)

# Set the brightness for a secondary display to 25%
sbc.windows.VCP.set_brightness(25, display = 1)
def list_monitors_info( method: Optional[str] = None, allow_duplicates: bool = False) -> List[dict]:
487def list_monitors_info(method: Optional[str] = None, allow_duplicates: bool = False) -> List[dict]:
488    '''
489    Lists detailed information about all detected monitors
490
491    Args:
492        method (str): the method the monitor can be addressed by. See `screen_brightness_control.get_methods`
493            for more info on available methods
494        allow_duplicates (bool): whether to filter out duplicate displays (displays with the same EDID) or not
495
496    Returns:
497        list: list of dicts upon success, empty list upon failure
498
499    Raises:
500        ValueError: if the method kwarg is invalid
501
502    Example:
503        ```python
504        import screen_brightness_control as sbc
505
506        monitors = sbc.windows.list_monitors_info()
507        for info in monitors:
508            print('=======================')
509            # the manufacturer name plus the model
510            print('Name:', info['name'])
511            # the general model of the display
512            print('Model:', info['model'])
513            # a unique string assigned by Windows to this display
514            print('Serial:', info['serial'])
515            # the name of the brand of the monitor
516            print('Manufacturer:', info['manufacturer'])
517            # the 3 letter code corresponding to the brand name, EG: BNQ -> BenQ
518            print('Manufacturer ID:', info['manufacturer_id'])
519            # the index of that display FOR THE SPECIFIC METHOD THE DISPLAY USES
520            print('Index:', info['index'])
521            # the method this monitor can be addressed by
522            print('Method:', info['method'])
523            # the EDID string of the monitor
524            print('EDID:', info['edid'])
525        ```
526    '''
527    # no caching here because get_display_info caches its results
528    info = get_display_info()
529
530    all_methods = get_methods()
531
532    if method is not None:
533        method = method.lower()
534        if method not in all_methods:
535            log.debug(f'requested method {repr(method)} invalid')
536            raise ValueError(f'method must be one of: {list(all_methods)}')
537        info = [i for i in info if i['method'].__name__.lower() == method]
538
539    if allow_duplicates:
540        return info
541
542    try:
543        # use filter_monitors to remove duplicates
544        return filter_monitors(haystack=info)
545    except LookupError:
546        return []

Lists detailed information about all detected monitors

Args
  • method (str): the method the monitor can be addressed by. See screen_brightness_control.get_methods for more info on available methods
  • allow_duplicates (bool): whether to filter out duplicate displays (displays with the same EDID) or not
Returns
  • list: list of dicts upon success, empty list upon failure
Raises
  • ValueError: if the method kwarg is invalid
Example
import screen_brightness_control as sbc

monitors = sbc.windows.list_monitors_info()
for info in monitors:
    print('=======================')
    # the manufacturer name plus the model
    print('Name:', info['name'])
    # the general model of the display
    print('Model:', info['model'])
    # a unique string assigned by Windows to this display
    print('Serial:', info['serial'])
    # the name of the brand of the monitor
    print('Manufacturer:', info['manufacturer'])
    # the 3 letter code corresponding to the brand name, EG: BNQ -> BenQ
    print('Manufacturer ID:', info['manufacturer_id'])
    # the index of that display FOR THE SPECIFIC METHOD THE DISPLAY USES
    print('Index:', info['index'])
    # the method this monitor can be addressed by
    print('Method:', info['method'])
    # the EDID string of the monitor
    print('EDID:', info['edid'])