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()})')
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)
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 isTrue
- 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)
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 tofilter_monitors
as well. (eg: display, method...)
Returns:
- list: list of
threading.Thread
objects ifblocking == False
, otherwise it returns the result ofget_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)
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'])
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']
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))
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
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'])
the serial number of the display or (if serial is not available) an ID assigned by the OS
the method by which this monitor can be addressed. This will be a class from either the windows or linux sub-module
a unique string returned by the monitor that contains its DDC capabilities, serial and name
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...')
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 ofMonitor.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)
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()
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:
- args (tuple): passed directly to
fade_brightness
- kwargs (dict): passed directly to
fade_brightness
. Thedisplay
andmethod
kwargs are always overwritten.
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)
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()
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)
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', ... }]