Skip to content

Commit 1c3d3a6

Browse files
committed
Deprecate Monitor class and move Display class into root package.
`Monitor` is deprecated in favour of the new `Display` class, which is leaner meaner and cleaner. Moved `Display` into root level namespace to reflect it's new importance
1 parent 1c23ef5 commit 1c3d3a6

File tree

5 files changed

+258
-244
lines changed

5 files changed

+258
-244
lines changed

screen_brightness_control/__init__.py

Lines changed: 188 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44
import time
55
import traceback
66
import warnings
7-
from dataclasses import fields
7+
from dataclasses import dataclass, fields, field
88
from typing import Any, Dict, List, Optional, Tuple, Union
99

1010
from ._debug import info as debug_info # noqa: F401
1111
from ._version import __author__, __version__ # noqa: F401
1212
from .exceptions import NoValidDisplayError, format_exc # noqa: F401
1313
from .helpers import (MONITOR_MANUFACTURER_CODES, # noqa: F401
14-
BrightnessMethod, Display, ScreenBrightnessError,
14+
BrightnessMethod, ScreenBrightnessError,
1515
logarithmic_range, percentage)
1616
from .types import DisplayIdentifier, IntPercentage, Percentage
1717

@@ -315,8 +315,184 @@ def get_methods(name: str = None) -> Dict[str, BrightnessMethod]:
315315
f'invalid method {name!r}, must be one of: {list(methods)}')
316316

317317

318+
@dataclass
319+
class Display():
320+
'''
321+
Represents a single connected display.
322+
'''
323+
index: int
324+
'''The index of the display relative to the method it uses.
325+
So if the index is 0 and the method is `windows.VCP`, then this is the 1st
326+
display reported by `windows.VCP`, not the first display overall.'''
327+
method: BrightnessMethod
328+
'''The method by which this monitor can be addressed.
329+
This will be a class from either the windows or linux sub-module'''
330+
331+
edid: str = None
332+
'''A 256 character hex string containing information about a display and its capabilities'''
333+
manufacturer: str = None
334+
'''Name of the display's manufacturer'''
335+
manufacturer_id: str = None
336+
'''3 letter code corresponding to the manufacturer name'''
337+
model: str = None
338+
'''Model name of the display'''
339+
name: str = None
340+
'''The name of the display, often the manufacturer name plus the model name'''
341+
serial: str = None
342+
'''The serial number of the display or (if serial is not available) an ID assigned by the OS'''
343+
344+
_logger: logging.Logger = field(init=False)
345+
346+
def __post_init__(self):
347+
self._logger = logger.getChild(self.__class__.__name__).getChild(
348+
str(self.get_identifier()[1])[:20])
349+
350+
def fade_brightness(
351+
self,
352+
finish: Percentage,
353+
start: Optional[Percentage] = None,
354+
interval: float = 0.01,
355+
increment: int = 1,
356+
force: bool = False,
357+
logarithmic: bool = True
358+
) -> IntPercentage:
359+
'''
360+
Gradually change the brightness of this display to a set value.
361+
This works by incrementally changing the brightness until the desired
362+
value is reached.
363+
364+
Args:
365+
finish (types.Percentage): the brightness level to end up on
366+
start (types.Percentage): where the fade should start from. Defaults
367+
to whatever the current brightness level for the display is
368+
interval: time delay between each change in brightness
369+
increment: amount to change the brightness by each time (as a percentage)
370+
force: [*Linux only*] allow the brightness to be set to 0. By default,
371+
brightness values will never be set lower than 1, since setting them to 0
372+
often turns off the backlight
373+
logarithmic: follow a logarithmic curve when setting brightness values.
374+
See `logarithmic_range` for rationale
375+
376+
Returns:
377+
The brightness of the display after the fade is complete.
378+
See `types.IntPercentage`
379+
'''
380+
# minimum brightness value
381+
if platform.system() == 'Linux' and not force:
382+
lower_bound = 1
383+
else:
384+
lower_bound = 0
385+
386+
current = self.get_brightness()
387+
388+
finish = percentage(finish, current, lower_bound)
389+
start = percentage(
390+
current if start is None else start, current, lower_bound)
391+
392+
range_func = logarithmic_range if logarithmic else range
393+
increment = abs(increment)
394+
if start > finish:
395+
increment = -increment
396+
397+
self._logger.debug(
398+
f'fade {start}->{finish}:{increment}:logarithmic={logarithmic}')
399+
400+
for value in range_func(start, finish, increment):
401+
self.set_brightness(value)
402+
time.sleep(interval)
403+
404+
if self.get_brightness() != finish:
405+
self.set_brightness(finish, no_return=True)
406+
407+
return self.get_brightness()
408+
409+
@classmethod
410+
def from_dict(cls, display: dict) -> 'Display':
411+
'''
412+
Initialise an instance of the class from a dictionary, ignoring
413+
any unwanted keys
414+
'''
415+
return cls(
416+
index=display['index'],
417+
method=display['method'],
418+
edid=display['edid'],
419+
manufacturer=display['manufacturer'],
420+
manufacturer_id=display['manufacturer_id'],
421+
model=display['model'],
422+
name=display['name'],
423+
serial=display['serial']
424+
)
425+
426+
def get_brightness(self) -> IntPercentage:
427+
'''
428+
Returns the brightness of this display.
429+
430+
Returns:
431+
The brightness value of the display, as a percentage.
432+
See `types.IntPercentage`
433+
'''
434+
return self.method.get_brightness(display=self.index)[0]
435+
436+
def get_identifier(self) -> Tuple[str, DisplayIdentifier]:
437+
'''
438+
Returns the `types.DisplayIdentifier` for 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+
Returns:
443+
The name of the property returned and the value of said property.
444+
EG: `('serial', '123abc...')` or `('name', 'BenQ GL2450H')`
445+
'''
446+
for key in ('edid', 'serial', 'name', 'index'):
447+
value = getattr(self, key, None)
448+
if value is not None:
449+
return key, value
450+
451+
def is_active(self) -> bool:
452+
'''
453+
Attempts to retrieve the brightness for this display. If it works the display is deemed active
454+
'''
455+
try:
456+
self.get_brightness()
457+
return True
458+
except Exception as e:
459+
self._logger.error(
460+
f'Monitor.is_active: {self.get_identifier()} failed get_brightness call'
461+
f' - {format_exc(e)}'
462+
)
463+
return False
464+
465+
def set_brightness(self, value: Percentage, force: bool = False):
466+
'''
467+
Sets the brightness for this display. See `set_brightness` for the full docs
468+
469+
Args:
470+
value (types.Percentage): the brightness percentage to set the display to
471+
force: allow the brightness to be set to 0 on Linux. This is disabled by default
472+
because setting the brightness of 0 will often turn off the backlight
473+
'''
474+
# convert brightness value to percentage
475+
if platform.system() == 'Linux' and not force:
476+
lower_bound = 1
477+
else:
478+
lower_bound = 0
479+
480+
value = percentage(
481+
value,
482+
current=lambda: self.method.get_brightness(display=self.index)[0],
483+
lower_bound=lower_bound
484+
)
485+
486+
self.method.set_brightness(value, display=self.index)
487+
488+
318489
class Monitor(Display):
319-
'''A class to manage a single monitor and its relevant information'''
490+
'''
491+
Legacy class for managing displays.
492+
493+
.. warning:: Deprecated
494+
Deprecated for removal in v0.23.0. Please use the new `Display` class instead
495+
'''
320496

321497
def __init__(self, display: Union[int, str, dict]):
322498
'''
@@ -345,6 +521,14 @@ def __init__(self, display: Union[int, str, dict]):
345521
print(benq_monitor['name'])
346522
```
347523
'''
524+
warnings.warn(
525+
(
526+
'`Monitor` is deprecated for removal in v0.23.0.'
527+
' Please use the new `Display` class instead'
528+
),
529+
DeprecationWarning
530+
)
531+
348532
monitors_info = list_monitors_info(allow_duplicates=True)
349533
if isinstance(display, dict):
350534
if display in monitors_info:
@@ -375,7 +559,7 @@ def __getitem__(self, item: Any) -> Any:
375559
This behaviour is deprecated and will be removed in v0.22.0
376560
'''
377561
warnings.warn(
378-
'Monitor.__getitem__ is deprecated for removal in v0.22.0',
562+
'`Monitor.__getitem__` is deprecated for removal in v0.22.0',
379563
DeprecationWarning
380564
)
381565
return getattr(self, item)

0 commit comments

Comments
 (0)