screen_brightness_control

  1import platform
  2import threading
  3import time
  4import traceback
  5from typing import Any, Dict, List, Optional, Tuple, Union
  6
  7from ._debug import info as debug_info  # noqa: F401
  8from ._debug import log
  9from ._version import __author__, __version__  # noqa: F401
 10from .helpers import MONITOR_MANUFACTURER_CODES  # noqa: F401
 11from .helpers import ScreenBrightnessError, logarithmic_range
 12from .helpers import flatten_list  # noqa: F401  - deprecated.
 13
 14
 15def get_brightness(
 16    display: Optional[Union[int, str]] = None,
 17    method: Optional[str] = None,
 18    verbose_error: bool = False
 19) -> List[int]:
 20    '''
 21    Returns the current display brightness
 22
 23    Args:
 24        display (str or int): the specific display to query
 25        method (str): the method to use to get the brightness. See `get_methods` for
 26            more info on available methods
 27        verbose_error (bool): controls the level of detail in the error messages
 28
 29    Returns:
 30        list: a list of integers (from 0 to 100), each integer being the
 31            percentage brightness of a display (invalid displays may return None)
 32
 33    Example:
 34        ```python
 35        import screen_brightness_control as sbc
 36
 37        # get the current screen brightness (for all detected displays)
 38        current_brightness = sbc.get_brightness()
 39
 40        # get the brightness of the primary display
 41        primary_brightness = sbc.get_brightness(display=0)
 42
 43        # get the brightness of the secondary display (if connected)
 44        secondary_brightness = sbc.get_brightness(display=1)
 45        ```
 46    '''
 47    return __brightness(display=display, method=method, meta_method='get', verbose_error=verbose_error)
 48
 49
 50def set_brightness(
 51    value: Union[int, float, str],
 52    display: Optional[Union[str, int]] = None,
 53    method: Optional[str] = None,
 54    force: bool = False,
 55    verbose_error: bool = False,
 56    no_return: bool = True
 57) -> Union[None, List[int]]:
 58    '''
 59    Sets the screen brightness
 60
 61    Args:
 62        value (int or float or str): a value 0 to 100. This is a percentage or a string as '+5' or '-5'
 63        display (int or str): the specific display to adjust
 64        method (str): the method to use to set the brightness. See `get_methods` for
 65            more info on available methods
 66        force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
 67            This is because on most displays a brightness of 0 will turn off the backlight.
 68            If True, this check is bypassed
 69        verbose_error (bool): boolean value controls the amount of detail error messages will contain
 70        no_return (bool): if False, this function returns new brightness (by calling `get_brightness`).
 71            If True, this function returns None (default behaviour).
 72
 73    Returns:
 74        None: if the `no_return` kwarg is `True`
 75        list: a list of integers (from 0 to 100), each integer being the
 76            percentage brightness of a display (invalid displays may return None)
 77
 78    Example:
 79        ```python
 80        import screen_brightness_control as sbc
 81
 82        # set brightness to 50%
 83        sbc.set_brightness(50)
 84
 85        # set brightness to 0%
 86        sbc.set_brightness(0, force=True)
 87
 88        # increase brightness by 25%
 89        sbc.set_brightness('+25')
 90
 91        # decrease brightness by 30%
 92        sbc.set_brightness('-30')
 93
 94        # set the brightness of display 0 to 50%
 95        sbc.set_brightness(50, display=0)
 96        ```
 97    '''
 98    if isinstance(value, str) and ('+' in value or '-' in value):
 99        value = int(float(value))
100        monitors = filter_monitors(display=display, method=method)
101        if len(monitors) > 1:
102            output = []
103            for monitor in monitors:
104                identifier = Monitor.get_identifier(monitor)[1]
105                tmp = set_brightness(
106                    get_brightness(display=identifier)[0] + value,
107                    display=identifier,
108                    force=force,
109                    verbose_error=verbose_error,
110                    no_return=no_return
111                )
112                if isinstance(tmp, list):
113                    output += tmp
114                else:  # otherwise the output should be None
115                    output.append(tmp)
116            return output
117
118        value += get_brightness(display=Monitor.get_identifier(monitors[0])[1])[0]
119    else:
120        value = int(float(str(value)))
121
122    # make sure value is within bounds
123    value = max(min(100, value), 0)
124
125    if platform.system() == 'Linux' and not force:
126        value = max(1, value)
127
128    return __brightness(
129        value, display=display, method=method,
130        meta_method='set', no_return=no_return,
131        verbose_error=verbose_error
132    )
133
134
135def fade_brightness(
136    finish: Union[int, str],
137    start: Optional[Union[int, str]] = None,
138    interval: float = 0.01,
139    increment: int = 1,
140    blocking: bool = True,
141    force: bool = False,
142    logarithmic: bool = True,
143    **kwargs
144) -> Union[List[threading.Thread], List[int]]:
145    '''
146    A function to somewhat gently fade the screen brightness from `start` to `finish`
147
148    Args:
149        finish (int or str): the brightness level to end up on
150        start (int or str): where the brightness should fade from.
151            If not specified the function starts from the current screen brightness
152        interval (float or int): the time delay between each step in brightness
153        increment (int): the amount to change the brightness by per step
154        blocking (bool): whether this should occur in the main thread (`True`) or a new daemonic thread (`False`)
155        force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
156            This is because on most displays a brightness of 0 will turn off the backlight.
157            If True, this check is bypassed
158        logarithmic (bool): follow a logarithmic brightness curve when adjusting the brightness
159        kwargs (dict): passed directly to `set_brightness`.
160            Any compatible kwargs are passed to `filter_monitors` as well. (eg: display, method...)
161
162    Returns:
163        list: list of `threading.Thread` objects if `blocking == False`,
164            otherwise it returns the result of `get_brightness()`
165
166    Example:
167        ```python
168        import screen_brightness_control as sbc
169
170        # fade brightness from the current brightness to 50%
171        sbc.fade_brightness(50)
172
173        # fade the brightness from 25% to 75%
174        sbc.fade_brightness(75, start=25)
175
176        # fade the brightness from the current value to 100% in steps of 10%
177        sbc.fade_brightness(100, increment=10)
178
179        # fade the brightness from 100% to 90% with time intervals of 0.1 seconds
180        sbc.fade_brightness(90, start=100, interval=0.1)
181
182        # fade the brightness to 100% in a new thread
183        sbc.fade_brightness(100, blocking=False)
184        ```
185    '''
186    def fade(start, finish, increment, monitor):
187        range_func = logarithmic_range if logarithmic else range
188
189        increment = abs(increment)
190        if start > finish:
191            increment = -increment
192
193        log.debug(
194            f'fade display {monitor.index} of {monitor.method}'
195            f' {start}->{finish}:{increment}:logarithmic={logarithmic}'
196        )
197        for value in range_func(start, finish, increment):
198            monitor.set_brightness(value, no_return=True)
199            time.sleep(interval)
200
201        if monitor.get_brightness() != finish:
202            monitor.set_brightness(finish, no_return=True)
203
204    def err_msg(e):
205        return f'\n\tfilter_monitors -> {type(e).__name__}: {e}'
206
207    try:
208        # make sure only compatible kwargs are passed to filter_monitors
209        available_monitors = filter_monitors(
210            **{k: v for k, v in kwargs.items() if k in (
211                'display', 'haystack', 'method', 'include'
212            )}
213        )
214    except Exception as e:
215        raise ScreenBrightnessError(err_msg(e)) from e
216
217    # minimum brightness value
218    if platform.system() == 'Linux' and not force:
219        lower_bound = 1
220    else:
221        lower_bound = 0
222
223    threads = []
224    for i in available_monitors:
225        try:
226            monitor = Monitor(i)
227
228            # same effect as monitor.is_active()
229            current = monitor.get_brightness()
230            st, fi = start, finish
231            # convert strings like '+5' to an actual brightness value
232            if isinstance(fi, str):
233                if "+" in fi or "-" in fi:
234                    fi = current + int(float(fi))
235            if isinstance(st, str):
236                if "+" in st or "-" in st:
237                    st = current + int(float(st))
238
239            st = current if st is None else st
240            # make sure both values are within the correct range
241            fi = min(max(int(float(fi)), lower_bound), 100)
242            st = min(max(int(float(st)), lower_bound), 100)
243
244            t1 = threading.Thread(target=fade, args=(st, fi, increment, monitor))
245            t1.start()
246            threads.append(t1)
247        except Exception as e:
248            log.debug(f'exception when preparing to fade monitor {i} - {type(e).__name__}: {e}')
249            pass
250
251    if not blocking:
252        return threads
253
254    for t in threads:
255        t.join()
256    return get_brightness(**kwargs)
257
258
259def list_monitors_info(method: Optional[str] = None, allow_duplicates: bool = False) -> List[dict]:
260    '''
261    list detailed information about all monitors that are controllable by this library
262
263    Args:
264        method (str): the method to use to list the available monitors. See `get_methods` for
265            more info on available methods
266        allow_duplicates (bool): whether to filter out duplicate displays or not
267
268    Returns:
269        list: list of dictionaries
270
271    Example:
272        ```python
273        import screen_brightness_control as sbc
274        monitors = sbc.list_monitors_info()
275        for monitor in monitors:
276            print('=======================')
277            # the manufacturer name plus the model
278            print('Name:', monitor['name'])
279            # the general model of the display
280            print('Model:', monitor['model'])
281            # the serial of the display
282            print('Serial:', monitor['serial'])
283            # the name of the brand of the monitor
284            print('Manufacturer:', monitor['manufacturer'])
285            # the 3 letter code corresponding to the brand name, EG: BNQ -> BenQ
286            print('Manufacturer ID:', monitor['manufacturer_id'])
287            # the index of that display FOR THE SPECIFIC METHOD THE DISPLAY USES
288            print('Index:', monitor['index'])
289            # the method this monitor can be addressed by
290            print('Method:', monitor['method'])
291            # the EDID string associated with that monitor
292            print('EDID:', monitor['edid'])
293        ```
294    '''
295    return _OS_MODULE.list_monitors_info(method=method, allow_duplicates=allow_duplicates)
296
297
298def list_monitors(method: Optional[str] = None) -> List[str]:
299    '''
300    List the names of all detected monitors
301
302    Args:
303        method (str): the method to use to list the available monitors. See `get_methods` for
304            more info on available methods
305
306    Returns:
307        list: list of strings
308
309    Example:
310        ```python
311        import screen_brightness_control as sbc
312        monitor_names = sbc.list_monitors()
313        # eg: ['BenQ GL2450H', 'Dell U2211H']
314        ```
315    '''
316    return [i['name'] for i in list_monitors_info(method=method)]
317
318
319def get_methods() -> Dict[str, object]:
320    '''
321    Returns all available brightness method names and their associated classes.
322
323    Returns:
324        dict: keys are the method names. This is what you would use
325            if a function has a `method` kwarg.
326            Values are the classes themselves
327
328    Example:
329        ```python
330        import screen_brightness_control as sbc
331
332        all_methods = sbc.get_methods()
333
334        for method_name, method_class in all_methods.items():
335            print('Method:', method_name)
336            print('Class:', method_class)
337            print('Associated monitors:', sbc.list_monitors(method=method_name))
338        ```
339    '''
340    if platform.system() == 'Windows':
341        methods = (_OS_MODULE.WMI, _OS_MODULE.VCP)
342    else:  # linux
343        methods = (
344            _OS_MODULE.SysFiles, _OS_MODULE.I2C,
345            _OS_MODULE.XRandr, _OS_MODULE.DDCUtil,
346            _OS_MODULE.Light
347        )
348
349    return {i.__name__.lower(): i for i in methods}
350
351
352class Monitor():
353    '''A class to manage a single monitor and its relevant information'''
354    def __init__(self, display: Union[int, str, dict]):
355        '''
356        Args:
357            display (int or str or dict): the index/name/model name/serial/edid
358                of the display you wish to control. Is passed to `filter_monitors`
359                to decide which display to use.
360
361        Raises:
362            LookupError: if a matching display could not be found
363            TypeError: if the given display type is not int or str
364
365        Example:
366            ```python
367            import screen_brightness_control as sbc
368
369            # create a class for the primary monitor and then a specifically named monitor
370            primary = sbc.Monitor(0)
371            benq_monitor = sbc.Monitor('BenQ GL2450H')
372
373            # check if the benq monitor is the primary one
374            if primary.serial == benq_monitor.serial:
375                print('BenQ GL2450H is the primary display')
376            else:
377                print('The primary display is', primary.name)
378
379            # this class can also be accessed like a dictionary
380            print(primary['name'])
381            print(benq_monitor['name'])
382            ```
383        '''
384        monitors_info = list_monitors_info(allow_duplicates=True)
385        if isinstance(display, dict):
386            if display in monitors_info:
387                info = display
388            else:
389                info = filter_monitors(
390                    display=self.get_identifier(display),
391                    haystack=monitors_info
392                )[0]
393        else:
394            info = filter_monitors(display=display, haystack=monitors_info)[0]
395
396        # make a copy so that we don't alter the dict in-place
397        info = info.copy()
398
399        self.serial: str = info.pop('serial')
400        '''the serial number of the display or (if serial is not available) an ID assigned by the OS'''
401        self.name: str = info.pop('name')
402        '''the monitors manufacturer name plus its model'''
403        self.method = info.pop('method')
404        '''the method by which this monitor can be addressed.
405        This will be a class from either the windows or linux sub-module'''
406        self.manufacturer: str = info.pop('manufacturer')
407        '''the name of the brand of the monitor'''
408        self.manufacturer_id: str = info.pop('manufacturer_id')
409        '''the 3 letter manufacturing code corresponding to the manufacturer name'''
410        self.model: str = info.pop('model')
411        '''the general model of the display'''
412        self.index: int = info.pop('index')
413        '''the index of the monitor FOR THE SPECIFIC METHOD THIS MONITOR USES.'''
414        self.edid: str = info.pop('edid')
415        '''a unique string returned by the monitor that contains its DDC capabilities, serial and name'''
416
417        # this assigns any extra info that is returned to this class
418        # eg: the 'interface' key in XRandr monitors on Linux
419        for key, value in info.items():
420            if value is not None:
421                setattr(self, key, value)
422
423    def __getitem__(self, item: Any) -> Any:
424        return getattr(self, item)
425
426    def get_identifier(self, monitor: dict = None) -> Tuple[str, Union[int, str]]:
427        '''
428        Returns the piece of information used to identify this monitor.
429        Will iterate through the EDID, serial, name and index and return the first
430        value that is not equal to None
431
432        Args:
433            monitor (dict): extract an identifier from this dict instead of the monitor class
434
435        Returns:
436            tuple: the name of the property returned and the value of said property.
437                EG: `('serial', '123abc...')` or `('name', 'BenQ GL2450H')`
438
439        Example:
440            ```python
441            import screen_brightness_control as sbc
442            primary = sbc.Monitor(0)
443            print(primary.get_identifier())  # eg: ('serial', '123abc...')
444
445            secondary = sbc.list_monitors_info()[1]
446            print(primary.get_identifier(monitor=secondary))  # eg: ('serial', '456def...')
447
448            # you can also use the class uninitialized
449            print(sbc.Monitor.get_identifier(secondary))  # eg: ('serial', '456def...')
450            ```
451        '''
452        if monitor is None:
453            monitor = self
454
455        for key in ('edid', 'serial', 'name', 'index'):
456            value = monitor[key]
457            if value is not None:
458                return key, value
459
460    def set_brightness(self, value: int, no_return: bool = True, force: bool = False) -> Union[None, int]:
461        '''
462        Sets the brightness for this display. See `set_brightness` for the full docs
463
464        Args:
465            value (int): the brightness value to set the display to (from 0 to 100)
466            no_return (bool): if true, this function returns `None`
467                Otherwise it returns the result of `Monitor.get_brightness`
468            force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
469                This is because on most displays a brightness of 0 will turn off the backlight.
470                If True, this check is bypassed
471
472        Returns:
473            None: if `no_return==True`
474            int: from 0 to 100
475
476        Example:
477            ```python
478            import screen_brightness_control as sbc
479
480            # set the brightness of the primary monitor to 50%
481            primary = sbc.Monitor(0)
482            primary.set_brightness(50)
483            ```
484        '''
485        # refresh display info, in case another display has been unplugged or something
486        # which would change the index of this display
487        self.get_info()
488        # min brightness value
489        if platform.system() == 'Linux' and not force:
490            lower_bound = 1
491        else:
492            lower_bound = 0
493        value = max(lower_bound, min(value, 100))
494        self.method.set_brightness(value, display=self.index)
495        if no_return:
496            return None
497        return self.get_brightness()
498
499    def get_brightness(self) -> int:
500        '''
501        Returns the brightness of this display.
502
503        Returns:
504            int: from 0 to 100
505
506        Example:
507            ```python
508            import screen_brightness_control as sbc
509
510            # get the brightness of the primary monitor
511            primary = sbc.Monitor(0)
512            primary_brightness = primary.get_brightness()
513            ```
514        '''
515        # refresh display info, in case another display has been unplugged or something
516        # which would change the index of this display
517        self.get_info()
518        return self.method.get_brightness(display=self.index)[0]
519
520    def fade_brightness(self, *args, **kwargs) -> Union[threading.Thread, int]:
521        '''
522        Fades the brightness for this display. See `fade_brightness` for the full docs
523
524        Args:
525            args (tuple): passed directly to `fade_brightness`
526            kwargs (dict): passed directly to `fade_brightness`.
527                The `display` and `method` kwargs are always
528                overwritten.
529
530        Returns:
531            threading.Thread: if the the blocking kwarg is False
532            int: if the blocking kwarg is True
533
534        Example:
535            ```python
536            import screen_brightness_control as sbc
537
538            # fade the brightness of the primary monitor to 50%
539            primary = sbc.Monitor(0)
540            primary.fade_brightness(50)
541            ```
542        '''
543        # refresh display info, in case another display has been unplugged or something
544        # which would change the index of this display
545        self.get_info(refresh=False)
546        kwargs['display'] = self.index
547        # the reason we override the method kwarg here is that
548        # the 'index' is method specific and `fade_brightness`
549        # is a top-level function. `self.set_brightness` and `self.get_brightness`
550        # call directly to the method so they don't need this step
551        kwargs['method'] = self.method.__name__.lower()
552
553        brightness = fade_brightness(*args, **kwargs)
554        # fade_brightness will call the top-level get_brightness
555        # function, which will return list OR int
556        if isinstance(brightness, list):
557            return brightness[0]
558        return brightness
559
560    def get_info(self, refresh: bool = True) -> dict:
561        '''
562        Returns all known information about this monitor instance
563
564        Args:
565            refresh (bool): whether to refresh the information
566                or to return the cached version
567
568        Returns:
569            dict
570
571        Example:
572            ```python
573            import screen_brightness_control as sbc
574
575            # initialize class for primary monitor
576            primary = sbc.Monitor(0)
577            # get the info
578            info = primary.get_info()
579            ```
580        '''
581        if not refresh:
582            return vars(self)
583
584        identifier = self.get_identifier()
585
586        if identifier is not None:
587            # refresh the info we have on this monitor
588            info = filter_monitors(display=identifier[1], method=self.method.__name__)[0]
589            for key, value in info.items():
590                if value is not None:
591                    setattr(self, key, value)
592
593        return vars(self)
594
595    def is_active(self) -> bool:
596        '''
597        Attempts to retrieve the brightness for this display. If it works the display is deemed active
598
599        Returns:
600            bool: True means active, False means inactive
601
602        Example:
603            ```python
604            import screen_brightness_control as sbc
605
606            primary = sbc.Monitor(0)
607            if primary.is_active():
608                primary.set_brightness(50)
609            ```
610        '''
611        try:
612            self.get_brightness()
613            return True
614        except Exception as e:
615            log.debug(
616                f'Monitor.is_active: {self.get_identifier()} failed get_brightness call'
617                f' - {type(e).__name__}: {e}'
618            )
619            return False
620
621
622def filter_monitors(
623    display: Optional[Union[int, str]] = None,
624    haystack: Optional[List[dict]] = None,
625    method: Optional[str] = None,
626    include: List[str] = []
627) -> List[dict]:
628    '''
629    Searches through the information for all detected displays
630    and attempts to return the info matching the value given.
631    Will attempt to match against index, name, model, edid, method and serial
632
633    Args:
634        display (str or int): the display you are searching for.
635            Can be serial, name, model number, edid string or index of the display
636        haystack (list): the information to filter from.
637            If this isn't set it defaults to the return of `list_monitors_info`
638        method (str): the method the monitors use. See `get_methods` for
639            more info on available methods
640        include (list): extra fields of information to sort by
641
642    Raises:
643        TypeError: if the display kwarg is not an int, str or None
644        LookupError: if the display, does not have a match
645
646    Returns:
647        list: list of dicts
648
649    Example:
650        ```python
651        import screen_brightness_control as sbc
652
653        search = 'GL2450H'
654        match = sbc.filter_displays(search)
655        print(match)
656        # EG output: [{'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }]
657        ```
658    '''
659    if display is not None and type(display) not in (str, int):
660        raise TypeError(f'display kwarg must be int or str, not "{type(display).__name__}"')
661
662    def get_monitor_list():
663        # if we have been provided with a list of monitors to sift through then use that
664        # otherwise, get the info ourselves
665        if haystack:
666            monitors_with_duplicates = haystack
667            if method is not None:
668                monitors_with_duplicates = [i for i in haystack if method.lower() == i['method'].__name__.lower()]
669        else:
670            monitors_with_duplicates = list_monitors_info(method=method, allow_duplicates=True)
671
672        return monitors_with_duplicates
673
674    def filter_monitor_list():
675        # This loop does two things: 1. Filters out duplicate monitors and 2. Matches the display kwarg (if applicable)
676        unique_identifiers = []
677        monitors = []
678        for monitor in monitors_with_duplicates:
679            # find a valid identifier for a monitor, excluding any which are equal to None
680            added = False
681            for identifier in ['edid', 'serial', 'name', 'model'] + include:
682                if monitor.get(identifier, None) is not None:
683                    # check we haven't already added the monitor
684                    if monitor[identifier] not in unique_identifiers:
685                        # check if the display kwarg (if str) matches this monitor
686                        if monitor[identifier] == display or not isinstance(display, str):
687                            # if valid and monitor[identifier] not in unique_identifiers:
688                            if not added:
689                                monitors.append(monitor)
690                                unique_identifiers.append(monitor[identifier])
691                                added = True
692
693                            # if the display kwarg is an integer and we are currently at that index
694                            if isinstance(display, int) and len(monitors) - 1 == display:
695                                return [monitor]
696                            if added:
697                                break
698                    else:
699                        # if we have already added a monitor with the same identifier
700                        # then any info matching this monitor will match the other one
701                        # so exit the checking now
702                        break
703        return monitors
704
705    monitors_with_duplicates = get_monitor_list()
706    monitors = filter_monitor_list()
707    for _ in range(3):
708        if monitors == [] and not haystack:
709            # try again
710            time.sleep(0.4)
711            monitors_with_duplicates = get_monitor_list()
712            monitors = filter_monitor_list()
713        else:
714            break
715
716    if not monitors:
717        # if no monitors matched the query
718        msg = 'no monitors found'
719        if display is not None:
720            msg += f' with name/serial/model/edid/index of {repr(display)}'
721        if method is not None:
722            msg += f' with method of "{method}"'
723        raise LookupError(msg)
724
725    return monitors
726
727
728def __brightness(
729    *args, display=None, method=None, meta_method='get', no_return=False,
730    verbose_error=False, **kwargs
731):
732    '''Internal function used to get/set brightness'''
733
734    log.debug(f"brightness {meta_method} request display {display} with method {method}")
735
736    def format_exc(name, e):
737        errors.append((
738            name, e.__class__.__name__,
739            traceback.format_exc() if verbose_error else e
740        ))
741
742    output = []
743    errors = []
744    method = method.lower() if isinstance(method, str) else method
745
746    try:
747        monitors = filter_monitors(display=display, method=method)
748    except Exception as e:
749        format_exc('filter_monitors', e)
750    else:
751        for monitor in monitors:
752            try:
753                if meta_method == 'set':
754                    monitor['method'].set_brightness(*args, display=monitor['index'], **kwargs)
755                    if no_return:
756                        output.append(None)
757                        continue
758
759                output += monitor['method'].get_brightness(display=monitor['index'], **kwargs)
760            except Exception as e:
761                output.append(None)
762                format_exc(monitor, e)
763
764    if output:
765        output_is_none = set(output) == {None}
766        if (
767            # can't have None output if we are trying to get the brightness
768            (meta_method == 'get' and not output_is_none)
769            or (
770                # if we are setting the brightness then we CAN have a None output
771                # but only if no_return is True.
772                meta_method == 'set'
773                and ((no_return and output_is_none) or not output_is_none)
774            )
775        ):
776            return None if no_return else output
777
778    # if the function hasn't returned then it has failed
779    msg = '\n'
780    if errors:
781        for monitor, exc_name, exc in errors:
782            if isinstance(monitor, str):
783                msg += f'\t{monitor}'
784            else:
785                msg += f'\t{monitor["name"]} ({monitor["serial"]})'
786            msg += f' -> {exc_name}: '
787            msg += str(exc).replace('\n', '\n\t\t') + '\n'
788    else:
789        msg += '\tno valid output was received from brightness methods'
790
791    raise ScreenBrightnessError(msg)
792
793
794if platform.system() == 'Windows':
795    from . import windows
796    _OS_MODULE = windows
797elif platform.system() == 'Linux':
798    from . import linux
799    _OS_MODULE = linux
800else:
801    log.warning(f'package imported on unsupported platform ({platform.system()})')

def get_brightness( display: Union[str, int, NoneType] = None, method: Optional[str] = None, verbose_error: bool = False) -> List[int]:
16def get_brightness(
17    display: Optional[Union[int, str]] = None,
18    method: Optional[str] = None,
19    verbose_error: bool = False
20) -> List[int]:
21    '''
22    Returns the current display brightness
23
24    Args:
25        display (str or int): the specific display to query
26        method (str): the method to use to get the brightness. See `get_methods` for
27            more info on available methods
28        verbose_error (bool): controls the level of detail in the error messages
29
30    Returns:
31        list: a list of integers (from 0 to 100), each integer being the
32            percentage brightness of a display (invalid displays may return None)
33
34    Example:
35        ```python
36        import screen_brightness_control as sbc
37
38        # get the current screen brightness (for all detected displays)
39        current_brightness = sbc.get_brightness()
40
41        # get the brightness of the primary display
42        primary_brightness = sbc.get_brightness(display=0)
43
44        # get the brightness of the secondary display (if connected)
45        secondary_brightness = sbc.get_brightness(display=1)
46        ```
47    '''
48    return __brightness(display=display, method=method, meta_method='get', verbose_error=verbose_error)

Returns the current display brightness

Args
  • display (str or int): the specific display to query
  • method (str): the method to use to get the brightness. See get_methods for more info on available methods
  • verbose_error (bool): controls the level of detail in the error messages
Returns
  • list: a list of integers (from 0 to 100), each integer being the percentage brightness of a display (invalid displays may return None)
Example
import screen_brightness_control as sbc

# get the current screen brightness (for all detected displays)
current_brightness = sbc.get_brightness()

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

# get the brightness of the secondary display (if connected)
secondary_brightness = sbc.get_brightness(display=1)
def set_brightness( value: Union[int, float, str], display: Union[str, int, NoneType] = None, method: Optional[str] = None, force: bool = False, verbose_error: bool = False, no_return: bool = True) -> Optional[List[int]]:
 51def set_brightness(
 52    value: Union[int, float, str],
 53    display: Optional[Union[str, int]] = None,
 54    method: Optional[str] = None,
 55    force: bool = False,
 56    verbose_error: bool = False,
 57    no_return: bool = True
 58) -> Union[None, List[int]]:
 59    '''
 60    Sets the screen brightness
 61
 62    Args:
 63        value (int or float or str): a value 0 to 100. This is a percentage or a string as '+5' or '-5'
 64        display (int or str): the specific display to adjust
 65        method (str): the method to use to set the brightness. See `get_methods` for
 66            more info on available methods
 67        force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
 68            This is because on most displays a brightness of 0 will turn off the backlight.
 69            If True, this check is bypassed
 70        verbose_error (bool): boolean value controls the amount of detail error messages will contain
 71        no_return (bool): if False, this function returns new brightness (by calling `get_brightness`).
 72            If True, this function returns None (default behaviour).
 73
 74    Returns:
 75        None: if the `no_return` kwarg is `True`
 76        list: a list of integers (from 0 to 100), each integer being the
 77            percentage brightness of a display (invalid displays may return None)
 78
 79    Example:
 80        ```python
 81        import screen_brightness_control as sbc
 82
 83        # set brightness to 50%
 84        sbc.set_brightness(50)
 85
 86        # set brightness to 0%
 87        sbc.set_brightness(0, force=True)
 88
 89        # increase brightness by 25%
 90        sbc.set_brightness('+25')
 91
 92        # decrease brightness by 30%
 93        sbc.set_brightness('-30')
 94
 95        # set the brightness of display 0 to 50%
 96        sbc.set_brightness(50, display=0)
 97        ```
 98    '''
 99    if isinstance(value, str) and ('+' in value or '-' in value):
100        value = int(float(value))
101        monitors = filter_monitors(display=display, method=method)
102        if len(monitors) > 1:
103            output = []
104            for monitor in monitors:
105                identifier = Monitor.get_identifier(monitor)[1]
106                tmp = set_brightness(
107                    get_brightness(display=identifier)[0] + value,
108                    display=identifier,
109                    force=force,
110                    verbose_error=verbose_error,
111                    no_return=no_return
112                )
113                if isinstance(tmp, list):
114                    output += tmp
115                else:  # otherwise the output should be None
116                    output.append(tmp)
117            return output
118
119        value += get_brightness(display=Monitor.get_identifier(monitors[0])[1])[0]
120    else:
121        value = int(float(str(value)))
122
123    # make sure value is within bounds
124    value = max(min(100, value), 0)
125
126    if platform.system() == 'Linux' and not force:
127        value = max(1, value)
128
129    return __brightness(
130        value, display=display, method=method,
131        meta_method='set', no_return=no_return,
132        verbose_error=verbose_error
133    )

Sets the screen brightness

Args
  • value (int or float or str): a value 0 to 100. This is a percentage or a string as '+5' or '-5'
  • display (int or str): the specific display to adjust
  • method (str): the method to use to set the brightness. See get_methods for more info on available methods
  • force (bool): [Linux Only] if False the brightness will never be set lower than 1. This is because on most displays a brightness of 0 will turn off the backlight. If True, this check is bypassed
  • verbose_error (bool): boolean value controls the amount of detail error messages will contain
  • no_return (bool): if False, this function returns new brightness (by calling get_brightness). If True, this function returns None (default behaviour).
Returns
  • None: if the no_return kwarg is True
  • list: a list of integers (from 0 to 100), each integer being the percentage brightness of a display (invalid displays may return None)
Example
import screen_brightness_control as sbc

# set brightness to 50%
sbc.set_brightness(50)

# set brightness to 0%
sbc.set_brightness(0, force=True)

# increase brightness by 25%
sbc.set_brightness('+25')

# decrease brightness by 30%
sbc.set_brightness('-30')

# set the brightness of display 0 to 50%
sbc.set_brightness(50, display=0)
def fade_brightness( finish: Union[int, str], start: Union[str, int, NoneType] = None, interval: float = 0.01, increment: int = 1, blocking: bool = True, force: bool = False, logarithmic: bool = True, **kwargs) -> Union[List[threading.Thread], List[int]]:
136def fade_brightness(
137    finish: Union[int, str],
138    start: Optional[Union[int, str]] = None,
139    interval: float = 0.01,
140    increment: int = 1,
141    blocking: bool = True,
142    force: bool = False,
143    logarithmic: bool = True,
144    **kwargs
145) -> Union[List[threading.Thread], List[int]]:
146    '''
147    A function to somewhat gently fade the screen brightness from `start` to `finish`
148
149    Args:
150        finish (int or str): the brightness level to end up on
151        start (int or str): where the brightness should fade from.
152            If not specified the function starts from the current screen brightness
153        interval (float or int): the time delay between each step in brightness
154        increment (int): the amount to change the brightness by per step
155        blocking (bool): whether this should occur in the main thread (`True`) or a new daemonic thread (`False`)
156        force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
157            This is because on most displays a brightness of 0 will turn off the backlight.
158            If True, this check is bypassed
159        logarithmic (bool): follow a logarithmic brightness curve when adjusting the brightness
160        kwargs (dict): passed directly to `set_brightness`.
161            Any compatible kwargs are passed to `filter_monitors` as well. (eg: display, method...)
162
163    Returns:
164        list: list of `threading.Thread` objects if `blocking == False`,
165            otherwise it returns the result of `get_brightness()`
166
167    Example:
168        ```python
169        import screen_brightness_control as sbc
170
171        # fade brightness from the current brightness to 50%
172        sbc.fade_brightness(50)
173
174        # fade the brightness from 25% to 75%
175        sbc.fade_brightness(75, start=25)
176
177        # fade the brightness from the current value to 100% in steps of 10%
178        sbc.fade_brightness(100, increment=10)
179
180        # fade the brightness from 100% to 90% with time intervals of 0.1 seconds
181        sbc.fade_brightness(90, start=100, interval=0.1)
182
183        # fade the brightness to 100% in a new thread
184        sbc.fade_brightness(100, blocking=False)
185        ```
186    '''
187    def fade(start, finish, increment, monitor):
188        range_func = logarithmic_range if logarithmic else range
189
190        increment = abs(increment)
191        if start > finish:
192            increment = -increment
193
194        log.debug(
195            f'fade display {monitor.index} of {monitor.method}'
196            f' {start}->{finish}:{increment}:logarithmic={logarithmic}'
197        )
198        for value in range_func(start, finish, increment):
199            monitor.set_brightness(value, no_return=True)
200            time.sleep(interval)
201
202        if monitor.get_brightness() != finish:
203            monitor.set_brightness(finish, no_return=True)
204
205    def err_msg(e):
206        return f'\n\tfilter_monitors -> {type(e).__name__}: {e}'
207
208    try:
209        # make sure only compatible kwargs are passed to filter_monitors
210        available_monitors = filter_monitors(
211            **{k: v for k, v in kwargs.items() if k in (
212                'display', 'haystack', 'method', 'include'
213            )}
214        )
215    except Exception as e:
216        raise ScreenBrightnessError(err_msg(e)) from e
217
218    # minimum brightness value
219    if platform.system() == 'Linux' and not force:
220        lower_bound = 1
221    else:
222        lower_bound = 0
223
224    threads = []
225    for i in available_monitors:
226        try:
227            monitor = Monitor(i)
228
229            # same effect as monitor.is_active()
230            current = monitor.get_brightness()
231            st, fi = start, finish
232            # convert strings like '+5' to an actual brightness value
233            if isinstance(fi, str):
234                if "+" in fi or "-" in fi:
235                    fi = current + int(float(fi))
236            if isinstance(st, str):
237                if "+" in st or "-" in st:
238                    st = current + int(float(st))
239
240            st = current if st is None else st
241            # make sure both values are within the correct range
242            fi = min(max(int(float(fi)), lower_bound), 100)
243            st = min(max(int(float(st)), lower_bound), 100)
244
245            t1 = threading.Thread(target=fade, args=(st, fi, increment, monitor))
246            t1.start()
247            threads.append(t1)
248        except Exception as e:
249            log.debug(f'exception when preparing to fade monitor {i} - {type(e).__name__}: {e}')
250            pass
251
252    if not blocking:
253        return threads
254
255    for t in threads:
256        t.join()
257    return get_brightness(**kwargs)

A function to somewhat gently fade the screen brightness from start to finish

Args
  • finish (int or str): the brightness level to end up on
  • start (int or str): where the brightness should fade from. If not specified the function starts from the current screen brightness
  • interval (float or int): the time delay between each step in brightness
  • increment (int): the amount to change the brightness by per step
  • blocking (bool): whether this should occur in the main thread (True) or a new daemonic thread (False)
  • force (bool): [Linux Only] if False the brightness will never be set lower than 1. This is because on most displays a brightness of 0 will turn off the backlight. If True, this check is bypassed
  • logarithmic (bool): follow a logarithmic brightness curve when adjusting the brightness
  • kwargs (dict): passed directly to set_brightness. Any compatible kwargs are passed to filter_monitors as well. (eg: display, method...)
Returns
  • list: list of threading.Thread objects if blocking == False, otherwise it returns the result of get_brightness()
Example
import screen_brightness_control as sbc

# fade brightness from the current brightness to 50%
sbc.fade_brightness(50)

# fade the brightness from 25% to 75%
sbc.fade_brightness(75, start=25)

# fade the brightness from the current value to 100% in steps of 10%
sbc.fade_brightness(100, increment=10)

# fade the brightness from 100% to 90% with time intervals of 0.1 seconds
sbc.fade_brightness(90, start=100, interval=0.1)

# fade the brightness to 100% in a new thread
sbc.fade_brightness(100, blocking=False)
def list_monitors_info( method: Optional[str] = None, allow_duplicates: bool = False) -> List[dict]:
260def list_monitors_info(method: Optional[str] = None, allow_duplicates: bool = False) -> List[dict]:
261    '''
262    list detailed information about all monitors that are controllable by this library
263
264    Args:
265        method (str): the method to use to list the available monitors. See `get_methods` for
266            more info on available methods
267        allow_duplicates (bool): whether to filter out duplicate displays or not
268
269    Returns:
270        list: list of dictionaries
271
272    Example:
273        ```python
274        import screen_brightness_control as sbc
275        monitors = sbc.list_monitors_info()
276        for monitor in monitors:
277            print('=======================')
278            # the manufacturer name plus the model
279            print('Name:', monitor['name'])
280            # the general model of the display
281            print('Model:', monitor['model'])
282            # the serial of the display
283            print('Serial:', monitor['serial'])
284            # the name of the brand of the monitor
285            print('Manufacturer:', monitor['manufacturer'])
286            # the 3 letter code corresponding to the brand name, EG: BNQ -> BenQ
287            print('Manufacturer ID:', monitor['manufacturer_id'])
288            # the index of that display FOR THE SPECIFIC METHOD THE DISPLAY USES
289            print('Index:', monitor['index'])
290            # the method this monitor can be addressed by
291            print('Method:', monitor['method'])
292            # the EDID string associated with that monitor
293            print('EDID:', monitor['edid'])
294        ```
295    '''
296    return _OS_MODULE.list_monitors_info(method=method, allow_duplicates=allow_duplicates)

list detailed information about all monitors that are controllable by this library

Args
  • method (str): the method to use to list the available monitors. See get_methods for more info on available methods
  • allow_duplicates (bool): whether to filter out duplicate displays or not
Returns
  • list: list of dictionaries
Example
import screen_brightness_control as sbc
monitors = sbc.list_monitors_info()
for monitor in monitors:
    print('=======================')
    # the manufacturer name plus the model
    print('Name:', monitor['name'])
    # the general model of the display
    print('Model:', monitor['model'])
    # the serial of the display
    print('Serial:', monitor['serial'])
    # the name of the brand of the monitor
    print('Manufacturer:', monitor['manufacturer'])
    # the 3 letter code corresponding to the brand name, EG: BNQ -> BenQ
    print('Manufacturer ID:', monitor['manufacturer_id'])
    # the index of that display FOR THE SPECIFIC METHOD THE DISPLAY USES
    print('Index:', monitor['index'])
    # the method this monitor can be addressed by
    print('Method:', monitor['method'])
    # the EDID string associated with that monitor
    print('EDID:', monitor['edid'])
def list_monitors(method: Optional[str] = None) -> List[str]:
299def list_monitors(method: Optional[str] = None) -> List[str]:
300    '''
301    List the names of all detected monitors
302
303    Args:
304        method (str): the method to use to list the available monitors. See `get_methods` for
305            more info on available methods
306
307    Returns:
308        list: list of strings
309
310    Example:
311        ```python
312        import screen_brightness_control as sbc
313        monitor_names = sbc.list_monitors()
314        # eg: ['BenQ GL2450H', 'Dell U2211H']
315        ```
316    '''
317    return [i['name'] for i in list_monitors_info(method=method)]

List the names of all detected monitors

Args
  • method (str): the method to use to list the available monitors. See get_methods for more info on available methods
Returns
  • list: list of strings
Example
import screen_brightness_control as sbc
monitor_names = sbc.list_monitors()
# eg: ['BenQ GL2450H', 'Dell U2211H']
def get_methods() -> Dict[str, object]:
320def get_methods() -> Dict[str, object]:
321    '''
322    Returns all available brightness method names and their associated classes.
323
324    Returns:
325        dict: keys are the method names. This is what you would use
326            if a function has a `method` kwarg.
327            Values are the classes themselves
328
329    Example:
330        ```python
331        import screen_brightness_control as sbc
332
333        all_methods = sbc.get_methods()
334
335        for method_name, method_class in all_methods.items():
336            print('Method:', method_name)
337            print('Class:', method_class)
338            print('Associated monitors:', sbc.list_monitors(method=method_name))
339        ```
340    '''
341    if platform.system() == 'Windows':
342        methods = (_OS_MODULE.WMI, _OS_MODULE.VCP)
343    else:  # linux
344        methods = (
345            _OS_MODULE.SysFiles, _OS_MODULE.I2C,
346            _OS_MODULE.XRandr, _OS_MODULE.DDCUtil,
347            _OS_MODULE.Light
348        )
349
350    return {i.__name__.lower(): i for i in methods}

Returns all available brightness method names and their associated classes.

Returns
  • dict: keys are the method names. This is what you would use if a function has a method kwarg. Values are the classes themselves
Example
import screen_brightness_control as sbc

all_methods = sbc.get_methods()

for method_name, method_class in all_methods.items():
    print('Method:', method_name)
    print('Class:', method_class)
    print('Associated monitors:', sbc.list_monitors(method=method_name))
class Monitor:
353class Monitor():
354    '''A class to manage a single monitor and its relevant information'''
355    def __init__(self, display: Union[int, str, dict]):
356        '''
357        Args:
358            display (int or str or dict): the index/name/model name/serial/edid
359                of the display you wish to control. Is passed to `filter_monitors`
360                to decide which display to use.
361
362        Raises:
363            LookupError: if a matching display could not be found
364            TypeError: if the given display type is not int or str
365
366        Example:
367            ```python
368            import screen_brightness_control as sbc
369
370            # create a class for the primary monitor and then a specifically named monitor
371            primary = sbc.Monitor(0)
372            benq_monitor = sbc.Monitor('BenQ GL2450H')
373
374            # check if the benq monitor is the primary one
375            if primary.serial == benq_monitor.serial:
376                print('BenQ GL2450H is the primary display')
377            else:
378                print('The primary display is', primary.name)
379
380            # this class can also be accessed like a dictionary
381            print(primary['name'])
382            print(benq_monitor['name'])
383            ```
384        '''
385        monitors_info = list_monitors_info(allow_duplicates=True)
386        if isinstance(display, dict):
387            if display in monitors_info:
388                info = display
389            else:
390                info = filter_monitors(
391                    display=self.get_identifier(display),
392                    haystack=monitors_info
393                )[0]
394        else:
395            info = filter_monitors(display=display, haystack=monitors_info)[0]
396
397        # make a copy so that we don't alter the dict in-place
398        info = info.copy()
399
400        self.serial: str = info.pop('serial')
401        '''the serial number of the display or (if serial is not available) an ID assigned by the OS'''
402        self.name: str = info.pop('name')
403        '''the monitors manufacturer name plus its model'''
404        self.method = info.pop('method')
405        '''the method by which this monitor can be addressed.
406        This will be a class from either the windows or linux sub-module'''
407        self.manufacturer: str = info.pop('manufacturer')
408        '''the name of the brand of the monitor'''
409        self.manufacturer_id: str = info.pop('manufacturer_id')
410        '''the 3 letter manufacturing code corresponding to the manufacturer name'''
411        self.model: str = info.pop('model')
412        '''the general model of the display'''
413        self.index: int = info.pop('index')
414        '''the index of the monitor FOR THE SPECIFIC METHOD THIS MONITOR USES.'''
415        self.edid: str = info.pop('edid')
416        '''a unique string returned by the monitor that contains its DDC capabilities, serial and name'''
417
418        # this assigns any extra info that is returned to this class
419        # eg: the 'interface' key in XRandr monitors on Linux
420        for key, value in info.items():
421            if value is not None:
422                setattr(self, key, value)
423
424    def __getitem__(self, item: Any) -> Any:
425        return getattr(self, item)
426
427    def get_identifier(self, monitor: dict = None) -> Tuple[str, Union[int, str]]:
428        '''
429        Returns the piece of information used to identify this monitor.
430        Will iterate through the EDID, serial, name and index and return the first
431        value that is not equal to None
432
433        Args:
434            monitor (dict): extract an identifier from this dict instead of the monitor class
435
436        Returns:
437            tuple: the name of the property returned and the value of said property.
438                EG: `('serial', '123abc...')` or `('name', 'BenQ GL2450H')`
439
440        Example:
441            ```python
442            import screen_brightness_control as sbc
443            primary = sbc.Monitor(0)
444            print(primary.get_identifier())  # eg: ('serial', '123abc...')
445
446            secondary = sbc.list_monitors_info()[1]
447            print(primary.get_identifier(monitor=secondary))  # eg: ('serial', '456def...')
448
449            # you can also use the class uninitialized
450            print(sbc.Monitor.get_identifier(secondary))  # eg: ('serial', '456def...')
451            ```
452        '''
453        if monitor is None:
454            monitor = self
455
456        for key in ('edid', 'serial', 'name', 'index'):
457            value = monitor[key]
458            if value is not None:
459                return key, value
460
461    def set_brightness(self, value: int, no_return: bool = True, force: bool = False) -> Union[None, int]:
462        '''
463        Sets the brightness for this display. See `set_brightness` for the full docs
464
465        Args:
466            value (int): the brightness value to set the display to (from 0 to 100)
467            no_return (bool): if true, this function returns `None`
468                Otherwise it returns the result of `Monitor.get_brightness`
469            force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
470                This is because on most displays a brightness of 0 will turn off the backlight.
471                If True, this check is bypassed
472
473        Returns:
474            None: if `no_return==True`
475            int: from 0 to 100
476
477        Example:
478            ```python
479            import screen_brightness_control as sbc
480
481            # set the brightness of the primary monitor to 50%
482            primary = sbc.Monitor(0)
483            primary.set_brightness(50)
484            ```
485        '''
486        # refresh display info, in case another display has been unplugged or something
487        # which would change the index of this display
488        self.get_info()
489        # min brightness value
490        if platform.system() == 'Linux' and not force:
491            lower_bound = 1
492        else:
493            lower_bound = 0
494        value = max(lower_bound, min(value, 100))
495        self.method.set_brightness(value, display=self.index)
496        if no_return:
497            return None
498        return self.get_brightness()
499
500    def get_brightness(self) -> int:
501        '''
502        Returns the brightness of this display.
503
504        Returns:
505            int: from 0 to 100
506
507        Example:
508            ```python
509            import screen_brightness_control as sbc
510
511            # get the brightness of the primary monitor
512            primary = sbc.Monitor(0)
513            primary_brightness = primary.get_brightness()
514            ```
515        '''
516        # refresh display info, in case another display has been unplugged or something
517        # which would change the index of this display
518        self.get_info()
519        return self.method.get_brightness(display=self.index)[0]
520
521    def fade_brightness(self, *args, **kwargs) -> Union[threading.Thread, int]:
522        '''
523        Fades the brightness for this display. See `fade_brightness` for the full docs
524
525        Args:
526            args (tuple): passed directly to `fade_brightness`
527            kwargs (dict): passed directly to `fade_brightness`.
528                The `display` and `method` kwargs are always
529                overwritten.
530
531        Returns:
532            threading.Thread: if the the blocking kwarg is False
533            int: if the blocking kwarg is True
534
535        Example:
536            ```python
537            import screen_brightness_control as sbc
538
539            # fade the brightness of the primary monitor to 50%
540            primary = sbc.Monitor(0)
541            primary.fade_brightness(50)
542            ```
543        '''
544        # refresh display info, in case another display has been unplugged or something
545        # which would change the index of this display
546        self.get_info(refresh=False)
547        kwargs['display'] = self.index
548        # the reason we override the method kwarg here is that
549        # the 'index' is method specific and `fade_brightness`
550        # is a top-level function. `self.set_brightness` and `self.get_brightness`
551        # call directly to the method so they don't need this step
552        kwargs['method'] = self.method.__name__.lower()
553
554        brightness = fade_brightness(*args, **kwargs)
555        # fade_brightness will call the top-level get_brightness
556        # function, which will return list OR int
557        if isinstance(brightness, list):
558            return brightness[0]
559        return brightness
560
561    def get_info(self, refresh: bool = True) -> dict:
562        '''
563        Returns all known information about this monitor instance
564
565        Args:
566            refresh (bool): whether to refresh the information
567                or to return the cached version
568
569        Returns:
570            dict
571
572        Example:
573            ```python
574            import screen_brightness_control as sbc
575
576            # initialize class for primary monitor
577            primary = sbc.Monitor(0)
578            # get the info
579            info = primary.get_info()
580            ```
581        '''
582        if not refresh:
583            return vars(self)
584
585        identifier = self.get_identifier()
586
587        if identifier is not None:
588            # refresh the info we have on this monitor
589            info = filter_monitors(display=identifier[1], method=self.method.__name__)[0]
590            for key, value in info.items():
591                if value is not None:
592                    setattr(self, key, value)
593
594        return vars(self)
595
596    def is_active(self) -> bool:
597        '''
598        Attempts to retrieve the brightness for this display. If it works the display is deemed active
599
600        Returns:
601            bool: True means active, False means inactive
602
603        Example:
604            ```python
605            import screen_brightness_control as sbc
606
607            primary = sbc.Monitor(0)
608            if primary.is_active():
609                primary.set_brightness(50)
610            ```
611        '''
612        try:
613            self.get_brightness()
614            return True
615        except Exception as e:
616            log.debug(
617                f'Monitor.is_active: {self.get_identifier()} failed get_brightness call'
618                f' - {type(e).__name__}: {e}'
619            )
620            return False

A class to manage a single monitor and its relevant information

Monitor(display: Union[int, str, dict])
355    def __init__(self, display: Union[int, str, dict]):
356        '''
357        Args:
358            display (int or str or dict): the index/name/model name/serial/edid
359                of the display you wish to control. Is passed to `filter_monitors`
360                to decide which display to use.
361
362        Raises:
363            LookupError: if a matching display could not be found
364            TypeError: if the given display type is not int or str
365
366        Example:
367            ```python
368            import screen_brightness_control as sbc
369
370            # create a class for the primary monitor and then a specifically named monitor
371            primary = sbc.Monitor(0)
372            benq_monitor = sbc.Monitor('BenQ GL2450H')
373
374            # check if the benq monitor is the primary one
375            if primary.serial == benq_monitor.serial:
376                print('BenQ GL2450H is the primary display')
377            else:
378                print('The primary display is', primary.name)
379
380            # this class can also be accessed like a dictionary
381            print(primary['name'])
382            print(benq_monitor['name'])
383            ```
384        '''
385        monitors_info = list_monitors_info(allow_duplicates=True)
386        if isinstance(display, dict):
387            if display in monitors_info:
388                info = display
389            else:
390                info = filter_monitors(
391                    display=self.get_identifier(display),
392                    haystack=monitors_info
393                )[0]
394        else:
395            info = filter_monitors(display=display, haystack=monitors_info)[0]
396
397        # make a copy so that we don't alter the dict in-place
398        info = info.copy()
399
400        self.serial: str = info.pop('serial')
401        '''the serial number of the display or (if serial is not available) an ID assigned by the OS'''
402        self.name: str = info.pop('name')
403        '''the monitors manufacturer name plus its model'''
404        self.method = info.pop('method')
405        '''the method by which this monitor can be addressed.
406        This will be a class from either the windows or linux sub-module'''
407        self.manufacturer: str = info.pop('manufacturer')
408        '''the name of the brand of the monitor'''
409        self.manufacturer_id: str = info.pop('manufacturer_id')
410        '''the 3 letter manufacturing code corresponding to the manufacturer name'''
411        self.model: str = info.pop('model')
412        '''the general model of the display'''
413        self.index: int = info.pop('index')
414        '''the index of the monitor FOR THE SPECIFIC METHOD THIS MONITOR USES.'''
415        self.edid: str = info.pop('edid')
416        '''a unique string returned by the monitor that contains its DDC capabilities, serial and name'''
417
418        # this assigns any extra info that is returned to this class
419        # eg: the 'interface' key in XRandr monitors on Linux
420        for key, value in info.items():
421            if value is not None:
422                setattr(self, key, value)
Args
  • display (int or str or dict): the index/name/model name/serial/edid of the display you wish to control. Is passed to filter_monitors to decide which display to use.
Raises
  • LookupError: if a matching display could not be found
  • TypeError: if the given display type is not int or str
Example
import screen_brightness_control as sbc

# create a class for the primary monitor and then a specifically named monitor
primary = sbc.Monitor(0)
benq_monitor = sbc.Monitor('BenQ GL2450H')

# check if the benq monitor is the primary one
if primary.serial == benq_monitor.serial:
    print('BenQ GL2450H is the primary display')
else:
    print('The primary display is', primary.name)

# this class can also be accessed like a dictionary
print(primary['name'])
print(benq_monitor['name'])
serial: str

the serial number of the display or (if serial is not available) an ID assigned by the OS

name: str

the monitors manufacturer name plus its model

method

the method by which this monitor can be addressed. This will be a class from either the windows or linux sub-module

manufacturer: str

the name of the brand of the monitor

manufacturer_id: str

the 3 letter manufacturing code corresponding to the manufacturer name

model: str

the general model of the display

index: int

the index of the monitor FOR THE SPECIFIC METHOD THIS MONITOR USES.

edid: str

a unique string returned by the monitor that contains its DDC capabilities, serial and name

def get_identifier(self, monitor: dict = None) -> Tuple[str, Union[int, str]]:
427    def get_identifier(self, monitor: dict = None) -> Tuple[str, Union[int, str]]:
428        '''
429        Returns the piece of information used to identify this monitor.
430        Will iterate through the EDID, serial, name and index and return the first
431        value that is not equal to None
432
433        Args:
434            monitor (dict): extract an identifier from this dict instead of the monitor class
435
436        Returns:
437            tuple: the name of the property returned and the value of said property.
438                EG: `('serial', '123abc...')` or `('name', 'BenQ GL2450H')`
439
440        Example:
441            ```python
442            import screen_brightness_control as sbc
443            primary = sbc.Monitor(0)
444            print(primary.get_identifier())  # eg: ('serial', '123abc...')
445
446            secondary = sbc.list_monitors_info()[1]
447            print(primary.get_identifier(monitor=secondary))  # eg: ('serial', '456def...')
448
449            # you can also use the class uninitialized
450            print(sbc.Monitor.get_identifier(secondary))  # eg: ('serial', '456def...')
451            ```
452        '''
453        if monitor is None:
454            monitor = self
455
456        for key in ('edid', 'serial', 'name', 'index'):
457            value = monitor[key]
458            if value is not None:
459                return key, value

Returns the piece of information used to identify this monitor. Will iterate through the EDID, serial, name and index and return the first value that is not equal to None

Args
  • monitor (dict): extract an identifier from this dict instead of the monitor class
Returns
  • tuple: the name of the property returned and the value of said property. EG: ('serial', '123abc...') or ('name', 'BenQ GL2450H')
Example
import screen_brightness_control as sbc
primary = sbc.Monitor(0)
print(primary.get_identifier())  # eg: ('serial', '123abc...')

secondary = sbc.list_monitors_info()[1]
print(primary.get_identifier(monitor=secondary))  # eg: ('serial', '456def...')

# you can also use the class uninitialized
print(sbc.Monitor.get_identifier(secondary))  # eg: ('serial', '456def...')
def set_brightness( self, value: int, no_return: bool = True, force: bool = False) -> Optional[int]:
461    def set_brightness(self, value: int, no_return: bool = True, force: bool = False) -> Union[None, int]:
462        '''
463        Sets the brightness for this display. See `set_brightness` for the full docs
464
465        Args:
466            value (int): the brightness value to set the display to (from 0 to 100)
467            no_return (bool): if true, this function returns `None`
468                Otherwise it returns the result of `Monitor.get_brightness`
469            force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
470                This is because on most displays a brightness of 0 will turn off the backlight.
471                If True, this check is bypassed
472
473        Returns:
474            None: if `no_return==True`
475            int: from 0 to 100
476
477        Example:
478            ```python
479            import screen_brightness_control as sbc
480
481            # set the brightness of the primary monitor to 50%
482            primary = sbc.Monitor(0)
483            primary.set_brightness(50)
484            ```
485        '''
486        # refresh display info, in case another display has been unplugged or something
487        # which would change the index of this display
488        self.get_info()
489        # min brightness value
490        if platform.system() == 'Linux' and not force:
491            lower_bound = 1
492        else:
493            lower_bound = 0
494        value = max(lower_bound, min(value, 100))
495        self.method.set_brightness(value, display=self.index)
496        if no_return:
497            return None
498        return self.get_brightness()

Sets the brightness for this display. See set_brightness for the full docs

Args
  • value (int): the brightness value to set the display to (from 0 to 100)
  • no_return (bool): if true, this function returns None Otherwise it returns the result of Monitor.get_brightness
  • force (bool): [Linux Only] if False the brightness will never be set lower than 1. This is because on most displays a brightness of 0 will turn off the backlight. If True, this check is bypassed
Returns
  • None: if no_return==True
  • int: from 0 to 100
Example
import screen_brightness_control as sbc

# set the brightness of the primary monitor to 50%
primary = sbc.Monitor(0)
primary.set_brightness(50)
def get_brightness(self) -> int:
500    def get_brightness(self) -> int:
501        '''
502        Returns the brightness of this display.
503
504        Returns:
505            int: from 0 to 100
506
507        Example:
508            ```python
509            import screen_brightness_control as sbc
510
511            # get the brightness of the primary monitor
512            primary = sbc.Monitor(0)
513            primary_brightness = primary.get_brightness()
514            ```
515        '''
516        # refresh display info, in case another display has been unplugged or something
517        # which would change the index of this display
518        self.get_info()
519        return self.method.get_brightness(display=self.index)[0]

Returns the brightness of this display.

Returns
  • int: from 0 to 100
Example
import screen_brightness_control as sbc

# get the brightness of the primary monitor
primary = sbc.Monitor(0)
primary_brightness = primary.get_brightness()
def fade_brightness(self, *args, **kwargs) -> Union[threading.Thread, int]:
521    def fade_brightness(self, *args, **kwargs) -> Union[threading.Thread, int]:
522        '''
523        Fades the brightness for this display. See `fade_brightness` for the full docs
524
525        Args:
526            args (tuple): passed directly to `fade_brightness`
527            kwargs (dict): passed directly to `fade_brightness`.
528                The `display` and `method` kwargs are always
529                overwritten.
530
531        Returns:
532            threading.Thread: if the the blocking kwarg is False
533            int: if the blocking kwarg is True
534
535        Example:
536            ```python
537            import screen_brightness_control as sbc
538
539            # fade the brightness of the primary monitor to 50%
540            primary = sbc.Monitor(0)
541            primary.fade_brightness(50)
542            ```
543        '''
544        # refresh display info, in case another display has been unplugged or something
545        # which would change the index of this display
546        self.get_info(refresh=False)
547        kwargs['display'] = self.index
548        # the reason we override the method kwarg here is that
549        # the 'index' is method specific and `fade_brightness`
550        # is a top-level function. `self.set_brightness` and `self.get_brightness`
551        # call directly to the method so they don't need this step
552        kwargs['method'] = self.method.__name__.lower()
553
554        brightness = fade_brightness(*args, **kwargs)
555        # fade_brightness will call the top-level get_brightness
556        # function, which will return list OR int
557        if isinstance(brightness, list):
558            return brightness[0]
559        return brightness

Fades the brightness for this display. See fade_brightness for the full docs

Args
Returns
  • threading.Thread: if the the blocking kwarg is False
  • int: if the blocking kwarg is True
Example
import screen_brightness_control as sbc

# fade the brightness of the primary monitor to 50%
primary = sbc.Monitor(0)
primary.fade_brightness(50)
def get_info(self, refresh: bool = True) -> dict:
561    def get_info(self, refresh: bool = True) -> dict:
562        '''
563        Returns all known information about this monitor instance
564
565        Args:
566            refresh (bool): whether to refresh the information
567                or to return the cached version
568
569        Returns:
570            dict
571
572        Example:
573            ```python
574            import screen_brightness_control as sbc
575
576            # initialize class for primary monitor
577            primary = sbc.Monitor(0)
578            # get the info
579            info = primary.get_info()
580            ```
581        '''
582        if not refresh:
583            return vars(self)
584
585        identifier = self.get_identifier()
586
587        if identifier is not None:
588            # refresh the info we have on this monitor
589            info = filter_monitors(display=identifier[1], method=self.method.__name__)[0]
590            for key, value in info.items():
591                if value is not None:
592                    setattr(self, key, value)
593
594        return vars(self)

Returns all known information about this monitor instance

Args
  • refresh (bool): whether to refresh the information or to return the cached version
Returns
  • dict
Example
import screen_brightness_control as sbc

# initialize class for primary monitor
primary = sbc.Monitor(0)
# get the info
info = primary.get_info()
def is_active(self) -> bool:
596    def is_active(self) -> bool:
597        '''
598        Attempts to retrieve the brightness for this display. If it works the display is deemed active
599
600        Returns:
601            bool: True means active, False means inactive
602
603        Example:
604            ```python
605            import screen_brightness_control as sbc
606
607            primary = sbc.Monitor(0)
608            if primary.is_active():
609                primary.set_brightness(50)
610            ```
611        '''
612        try:
613            self.get_brightness()
614            return True
615        except Exception as e:
616            log.debug(
617                f'Monitor.is_active: {self.get_identifier()} failed get_brightness call'
618                f' - {type(e).__name__}: {e}'
619            )
620            return False

Attempts to retrieve the brightness for this display. If it works the display is deemed active

Returns
  • bool: True means active, False means inactive
Example
import screen_brightness_control as sbc

primary = sbc.Monitor(0)
if primary.is_active():
    primary.set_brightness(50)
def filter_monitors( display: Union[str, int, NoneType] = None, haystack: Optional[List[dict]] = None, method: Optional[str] = None, include: List[str] = []) -> List[dict]:
623def filter_monitors(
624    display: Optional[Union[int, str]] = None,
625    haystack: Optional[List[dict]] = None,
626    method: Optional[str] = None,
627    include: List[str] = []
628) -> List[dict]:
629    '''
630    Searches through the information for all detected displays
631    and attempts to return the info matching the value given.
632    Will attempt to match against index, name, model, edid, method and serial
633
634    Args:
635        display (str or int): the display you are searching for.
636            Can be serial, name, model number, edid string or index of the display
637        haystack (list): the information to filter from.
638            If this isn't set it defaults to the return of `list_monitors_info`
639        method (str): the method the monitors use. See `get_methods` for
640            more info on available methods
641        include (list): extra fields of information to sort by
642
643    Raises:
644        TypeError: if the display kwarg is not an int, str or None
645        LookupError: if the display, does not have a match
646
647    Returns:
648        list: list of dicts
649
650    Example:
651        ```python
652        import screen_brightness_control as sbc
653
654        search = 'GL2450H'
655        match = sbc.filter_displays(search)
656        print(match)
657        # EG output: [{'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }]
658        ```
659    '''
660    if display is not None and type(display) not in (str, int):
661        raise TypeError(f'display kwarg must be int or str, not "{type(display).__name__}"')
662
663    def get_monitor_list():
664        # if we have been provided with a list of monitors to sift through then use that
665        # otherwise, get the info ourselves
666        if haystack:
667            monitors_with_duplicates = haystack
668            if method is not None:
669                monitors_with_duplicates = [i for i in haystack if method.lower() == i['method'].__name__.lower()]
670        else:
671            monitors_with_duplicates = list_monitors_info(method=method, allow_duplicates=True)
672
673        return monitors_with_duplicates
674
675    def filter_monitor_list():
676        # This loop does two things: 1. Filters out duplicate monitors and 2. Matches the display kwarg (if applicable)
677        unique_identifiers = []
678        monitors = []
679        for monitor in monitors_with_duplicates:
680            # find a valid identifier for a monitor, excluding any which are equal to None
681            added = False
682            for identifier in ['edid', 'serial', 'name', 'model'] + include:
683                if monitor.get(identifier, None) is not None:
684                    # check we haven't already added the monitor
685                    if monitor[identifier] not in unique_identifiers:
686                        # check if the display kwarg (if str) matches this monitor
687                        if monitor[identifier] == display or not isinstance(display, str):
688                            # if valid and monitor[identifier] not in unique_identifiers:
689                            if not added:
690                                monitors.append(monitor)
691                                unique_identifiers.append(monitor[identifier])
692                                added = True
693
694                            # if the display kwarg is an integer and we are currently at that index
695                            if isinstance(display, int) and len(monitors) - 1 == display:
696                                return [monitor]
697                            if added:
698                                break
699                    else:
700                        # if we have already added a monitor with the same identifier
701                        # then any info matching this monitor will match the other one
702                        # so exit the checking now
703                        break
704        return monitors
705
706    monitors_with_duplicates = get_monitor_list()
707    monitors = filter_monitor_list()
708    for _ in range(3):
709        if monitors == [] and not haystack:
710            # try again
711            time.sleep(0.4)
712            monitors_with_duplicates = get_monitor_list()
713            monitors = filter_monitor_list()
714        else:
715            break
716
717    if not monitors:
718        # if no monitors matched the query
719        msg = 'no monitors found'
720        if display is not None:
721            msg += f' with name/serial/model/edid/index of {repr(display)}'
722        if method is not None:
723            msg += f' with method of "{method}"'
724        raise LookupError(msg)
725
726    return monitors

Searches through the information for all detected displays and attempts to return the info matching the value given. Will attempt to match against index, name, model, edid, method and serial

Args
  • display (str or int): the display you are searching for. Can be serial, name, model number, edid string or index of the display
  • haystack (list): the information to filter from. If this isn't set it defaults to the return of list_monitors_info
  • method (str): the method the monitors use. See get_methods for more info on available methods
  • include (list): extra fields of information to sort by
Raises
  • TypeError: if the display kwarg is not an int, str or None
  • LookupError: if the display, does not have a match
Returns
  • list: list of dicts
Example
import screen_brightness_control as sbc

search = 'GL2450H'
match = sbc.filter_displays(search)
print(match)
# EG output: [{'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }]