screen_brightness_control

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

Returns the current display brightness

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

Sets the screen brightness

Arguments:
  • 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]]:
137def fade_brightness(
138    finish: Union[int, str],
139    start: Optional[Union[int, str]] = None,
140    interval: float = 0.01,
141    increment: int = 1,
142    blocking: bool = True,
143    force: bool = False,
144    logarithmic: bool = True,
145    **kwargs
146) -> Union[List[threading.Thread], List[int]]:
147    '''
148    Gradually change the brightness of one or more displays
149
150    Args:
151        finish (int or str): the brightness level to end up on
152        start (int or str): where the brightness should fade from.
153            If not specified the function starts from the current brightness
154        interval (float or int): the time delay between each step in brightness
155        increment (int): the amount to change the brightness by per step
156        blocking (bool): whether this should occur in the main thread (`True`) or a new daemonic thread (`False`)
157        force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
158            This is because on most displays a brightness of 0 will turn off the backlight.
159            If True, this check is bypassed
160        logarithmic (bool): follow a logarithmic brightness curve when adjusting the brightness
161        kwargs (dict): passed directly to `set_brightness`.
162            Any compatible kwargs are passed to `filter_monitors` as well. (eg: display, method...)
163
164    Returns:
165        list: list of `threading.Thread` objects if `blocking == False`,
166            otherwise it returns the result of `get_brightness()`
167
168    Example:
169        ```python
170        import screen_brightness_control as sbc
171
172        # fade brightness from the current brightness to 50%
173        sbc.fade_brightness(50)
174
175        # fade the brightness from 25% to 75%
176        sbc.fade_brightness(75, start=25)
177
178        # fade the brightness from the current value to 100% in steps of 10%
179        sbc.fade_brightness(100, increment=10)
180
181        # fade the brightness from 100% to 90% with time intervals of 0.1 seconds
182        sbc.fade_brightness(90, start=100, interval=0.1)
183
184        # fade the brightness to 100% in a new thread
185        sbc.fade_brightness(100, blocking=False)
186        ```
187    '''
188    def fade(start, finish, increment, monitor):
189        range_func = logarithmic_range if logarithmic else range
190
191        increment = abs(increment)
192        if start > finish:
193            increment = -increment
194
195        logger.debug(
196            f'fade display {monitor.index} of {monitor.method}'
197            f' {start}->{finish}:{increment}:logarithmic={logarithmic}'
198        )
199        for value in range_func(start, finish, increment):
200            monitor.set_brightness(value, no_return=True)
201            time.sleep(interval)
202
203        if monitor.get_brightness() != finish:
204            monitor.set_brightness(finish, no_return=True)
205
206    # make sure only compatible kwargs are passed to filter_monitors
207    available_monitors = filter_monitors(
208        **{k: v for k, v in kwargs.items() if k in (
209            'display', 'haystack', 'method', 'include'
210        )}
211    )
212
213    # minimum brightness value
214    if platform.system() == 'Linux' and not force:
215        lower_bound = 1
216    else:
217        lower_bound = 0
218
219    threads = []
220    for i in available_monitors:
221        try:
222            monitor = Monitor(i)
223
224            # same effect as monitor.is_active()
225            current = monitor.get_brightness()
226        except Exception as e:
227            logger.error(f'exception when preparing to fade monitor {i} - {format_exc(e)}')
228            continue
229
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
248    if not blocking:
249        return threads
250
251    for t in threads:
252        t.join()
253    return get_brightness(**kwargs)

Gradually change the brightness of one or more displays

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

List detailed information about all displays that are controllable by this library

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

List the names of all detected displays

Arguments:
  • method (str): the method to use to list the available displays. See get_methods for more info on available methods
Returns:
  • list: list of strings
Example:
import screen_brightness_control as sbc
display_names = sbc.list_monitors()
# eg: ['BenQ GL2450H', 'Dell U2211H']
def get_methods( name: str = None) -> Dict[str, screen_brightness_control.helpers.BrightnessMethod]:
321def get_methods(name: str = None) -> Dict[str, BrightnessMethod]:
322    '''
323    Returns all available brightness method names and their associated classes.
324
325    Args:
326        name (str): if specified, return the method corresponding to this name
327
328    Returns:
329        dict: keys are the method names. This is what you would use
330            if a function has a `method` kwarg.
331            Values are the classes themselves
332
333    Raises:
334        ValueError: if the given name is incorrect
335
336    Example:
337        ```python
338        import screen_brightness_control as sbc
339
340        all_methods = sbc.get_methods()
341
342        for method_name, method_class in all_methods.items():
343            print('Method:', method_name)
344            print('Class:', method_class)
345            print('Associated monitors:', sbc.list_monitors(method=method_name))
346        ```
347    '''
348    methods = {i.__name__.lower(): i for i in _OS_METHODS}
349
350    if name is None:
351        return methods
352
353    if not isinstance(name, str):
354        raise TypeError(f'name must be of type str, not {type(name)!r}')
355
356    name = name.lower()
357    if name in methods:
358        return {name: methods[name]}
359
360    logger.debug(f'requested method {name!r} invalid')
361    raise ValueError(f'invalid method {name!r}, must be one of: {list(methods)}')

Returns all available brightness method names and their associated classes.

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

A class to manage a single monitor and its relevant information

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

# create a class for the primary display 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]]:
437    def get_identifier(self, monitor: dict = None) -> Tuple[str, Union[int, str]]:
438        '''
439        Returns the piece of information used to identify this display.
440        Will iterate through the EDID, serial, name and index and return the first
441        value that is not equal to None
442
443        Args:
444            monitor (dict): extract an identifier from this dict instead of the monitor class
445
446        Returns:
447            tuple: the name of the property returned and the value of said property.
448                EG: `('serial', '123abc...')` or `('name', 'BenQ GL2450H')`
449
450        Example:
451            ```python
452            import screen_brightness_control as sbc
453            primary = sbc.Monitor(0)
454            print(primary.get_identifier())  # eg: ('serial', '123abc...')
455
456            secondary = sbc.list_monitors_info()[1]
457            print(primary.get_identifier(monitor=secondary))  # eg: ('serial', '456def...')
458
459            # you can also use the class uninitialized
460            print(sbc.Monitor.get_identifier(secondary))  # eg: ('serial', '456def...')
461            ```
462        '''
463        if monitor is None:
464            monitor = self
465
466        for key in ('edid', 'serial', 'name', 'index'):
467            value = monitor[key]
468            if value is not None:
469                return key, value

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

Arguments:
  • 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: Union[int, str], no_return: bool = True, force: bool = False) -> Optional[int]:
471    def set_brightness(self, value: Union[int, str], no_return: bool = True, force: bool = False) -> Union[None, int]:
472        '''
473        Sets the brightness for this display. See `set_brightness` for the full docs
474
475        Args:
476            value (int or str): the brightness value to set the display to (from 0 to 100) \
477                or in increment as string '+5' or '-5'
478            no_return (bool): if true, this function returns `None`
479                Otherwise it returns the result of `Monitor.get_brightness`
480            force (bool): [*Linux Only*] if False the brightness will never be set lower than 1.
481                This is because on most displays a brightness of 0 will turn off the backlight.
482                If True, this check is bypassed
483
484        Returns:
485            None: if `no_return==True`
486            int: from 0 to 100
487
488        Example:
489            ```python
490            import screen_brightness_control as sbc
491
492            # set the brightness of the primary monitor to 50%
493            primary = sbc.Monitor(0)
494            primary.set_brightness(50)
495            ```
496        '''
497        # refresh display info, in case another display has been unplugged or something
498        # which would change the index of this display
499        self.get_info()
500
501        # convert brightness value to percentage
502        if platform.system() == 'Linux' and not force:
503            lower_bound = 1
504        else:
505            lower_bound = 0
506
507        value = percentage(
508            value,
509            current=lambda: self.method.get_brightness(display=self.index)[0],
510            lower_bound=lower_bound
511        )
512        self.method.set_brightness(value, display=self.index)
513        if no_return:
514            return None
515        return self.get_brightness()

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

Arguments:
  • value (int or str): the brightness value to set the display to (from 0 to 100) or in increment as string '+5' or '-5'
  • 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:
517    def get_brightness(self) -> int:
518        '''
519        Returns the brightness of this display.
520
521        Returns:
522            int: from 0 to 100
523
524        Example:
525            ```python
526            import screen_brightness_control as sbc
527
528            # get the brightness of the primary display
529            primary = sbc.Monitor(0)
530            primary_brightness = primary.get_brightness()
531            ```
532        '''
533        # refresh display info, in case another display has been unplugged or something
534        # which would change the index of this display
535        self.get_info()
536        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 display
primary = sbc.Monitor(0)
primary_brightness = primary.get_brightness()
def fade_brightness(self, *args, **kwargs) -> Union[threading.Thread, int]:
538    def fade_brightness(self, *args, **kwargs) -> Union[threading.Thread, int]:
539        '''
540        Fades the brightness for this display. See `fade_brightness` for the full docs
541
542        Args:
543            args (tuple): passed directly to `fade_brightness`
544            kwargs (dict): passed directly to `fade_brightness`.
545                The `display` and `method` kwargs are always
546                overwritten.
547
548        Returns:
549            threading.Thread: if the the blocking kwarg is False
550            int: if the blocking kwarg is True
551
552        Example:
553            ```python
554            import screen_brightness_control as sbc
555
556            # fade the brightness of the primary display to 50%
557            primary = sbc.Monitor(0)
558            primary.fade_brightness(50)
559            ```
560        '''
561        # refresh display info, in case another display has been unplugged or something
562        # which would change the index of this display
563        self.get_info(refresh=False)
564        kwargs['display'] = self.index
565        # the reason we override the method kwarg here is that
566        # the 'index' is method specific and `fade_brightness`
567        # is a top-level function. `self.set_brightness` and `self.get_brightness`
568        # call directly to the method so they don't need this step
569        kwargs['method'] = self.method.__name__.lower()
570
571        brightness = fade_brightness(*args, **kwargs)
572        # fade_brightness will call the top-level get_brightness
573        # function, which will return list OR int
574        if isinstance(brightness, list):
575            return brightness[0]
576        return brightness

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

Arguments:
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 display to 50%
primary = sbc.Monitor(0)
primary.fade_brightness(50)
def get_info(self, refresh: bool = True) -> dict:
578    def get_info(self, refresh: bool = True) -> dict:
579        '''
580        Returns all known information about this monitor instance
581
582        Args:
583            refresh (bool): whether to refresh the information
584                or to return the cached version
585
586        Returns:
587            dict
588
589        Example:
590            ```python
591            import screen_brightness_control as sbc
592
593            # initialize class for primary display
594            primary = sbc.Monitor(0)
595            # get the info
596            info = primary.get_info()
597            ```
598        '''
599        def vars_self():
600            return {k: v for k, v in vars(self).items() if not k.startswith('_')}
601
602        if not refresh:
603            return vars_self()
604
605        identifier = self.get_identifier()
606
607        if identifier is not None:
608            # refresh the info we have on this monitor
609            info = filter_monitors(display=identifier[1], method=self.method.__name__)[0]
610            for key, value in info.items():
611                if value is not None:
612                    setattr(self, key, value)
613
614        return vars_self()

Returns all known information about this monitor instance

Arguments:
  • 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 display
primary = sbc.Monitor(0)
# get the info
info = primary.get_info()
def is_active(self) -> bool:
616    def is_active(self) -> bool:
617        '''
618        Attempts to retrieve the brightness for this display. If it works the display is deemed active
619
620        Returns:
621            bool: True means active, False means inactive
622
623        Example:
624            ```python
625            import screen_brightness_control as sbc
626
627            primary = sbc.Monitor(0)
628            if primary.is_active():
629                primary.set_brightness(50)
630            ```
631        '''
632        try:
633            self.get_brightness()
634            return True
635        except Exception as e:
636            self._logger.error(
637                f'Monitor.is_active: {self.get_identifier()} failed get_brightness call'
638                f' - {format_exc(e)}'
639            )
640            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]:
643def filter_monitors(
644    display: Optional[Union[int, str]] = None,
645    haystack: Optional[List[dict]] = None,
646    method: Optional[str] = None,
647    include: List[str] = []
648) -> List[dict]:
649    '''
650    Searches through the information for all detected displays
651    and attempts to return the info matching the value given.
652    Will attempt to match against index, name, model, edid, method and serial
653
654    Args:
655        display (str or int): the display you are searching for.
656            Can be serial, name, model number, edid string or index of the display
657        haystack (list): the information to filter from.
658            If this isn't set it defaults to the return of `list_monitors_info`
659        method (str): the method the monitors use. See `get_methods` for
660            more info on available methods
661        include (list): extra fields of information to sort by
662
663    Raises:
664        NoValidDisplayError: if the display does not have a match
665
666    Returns:
667        list: list of dicts
668
669    Example:
670        ```python
671        import screen_brightness_control as sbc
672
673        search = 'GL2450H'
674        match = sbc.filter_displays(search)
675        print(match)
676        # EG output: [{'name': 'BenQ GL2450H', 'model': 'GL2450H', ... }]
677        ```
678    '''
679    if display is not None and type(display) not in (str, int):
680        raise TypeError(f'display kwarg must be int or str, not "{type(display).__name__}"')
681
682    def get_monitor_list():
683        # if we have been provided with a list of monitors to sift through then use that
684        # otherwise, get the info ourselves
685        if haystack is not None:
686            monitors_with_duplicates = haystack
687            if method is not None:
688                method_class = next(get_methods(method).values())
689                monitors_with_duplicates = [
690                    i for i in haystack if i['method'] == method_class]
691        else:
692            monitors_with_duplicates = list_monitors_info(method=method, allow_duplicates=True)
693
694        return monitors_with_duplicates
695
696    def filter_monitor_list(to_filter):
697        # This loop does two things:
698        # 1. Filters out duplicate monitors
699        # 2. Matches the display kwarg (if applicable)
700        filtered_displays = {}
701        for monitor in to_filter:
702            # find a valid identifier for a monitor, excluding any which are equal to None
703            added = False
704            for identifier in ['edid', 'serial', 'name', 'model'] + include:
705                # check we haven't already added the monitor
706                if monitor.get(identifier, None) is None:
707                    continue
708
709                m_id = monitor[identifier]
710                if m_id in filtered_displays:
711                    break
712
713                if isinstance(display, str) and m_id != display:
714                    continue
715
716                if not added:
717                    filtered_displays[m_id] = monitor
718                    added = True
719
720                # if the display kwarg is an integer and we are currently at that index
721                if isinstance(display, int) and len(filtered_displays) - 1 == display:
722                    return [monitor]
723
724                if added:
725                    break
726        return list(filtered_displays.values())
727
728    duplicates = []
729    for _ in range(3):
730        duplicates = get_monitor_list()
731        if duplicates:
732            break
733        time.sleep(0.4)
734    else:
735        msg = 'no displays detected'
736        if method is not None:
737            msg += f' with method: {method!r}'
738        raise NoValidDisplayError(msg)
739
740    monitors = filter_monitor_list(duplicates)
741    if not monitors:
742        # if no displays matched the query
743        msg = 'no displays found'
744        if display is not None:
745            msg += f' with name/serial/model/edid/index of {display!r}'
746        if method is not None:
747            msg += f' with method of {method!r}'
748        raise NoValidDisplayError(msg)
749
750    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

Arguments:
  • 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:
  • NoValidDisplayError: 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', ... }]