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