diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..9a3b2a3 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,35 @@ +# Read the Docs configuration file for Sphinx projects +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Set the OS, Python version and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.11" + # You can also specify other tool versions: + # nodejs: "20" + # rust: "1.70" + # golang: "1.20" + +# Build documentation in the "docs/" directory with Sphinx +sphinx: + configuration: docs/source/conf.py + # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs + # builder: "dirhtml" + # Fail on all warnings to avoid broken references + # fail_on_warning: true + +# Optionally build your docs in additional formats such as PDF and ePub +# formats: +# - pdf +# - epub + +# Optional but recommended, declare the Python requirements required +# to build your documentation +# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html +python: + install: + - requirements: requirements.txt diff --git a/DISPLAY_MANAGER_LICENSE.txt b/DISPLAY_MANAGER_LICENSE.txt deleted file mode 100644 index 25dade2..0000000 --- a/DISPLAY_MANAGER_LICENSE.txt +++ /dev/null @@ -1,14 +0,0 @@ -######################################################################## -# Copyright (c) 2018 University of Utah Student Computing Labs. # -# All Rights Reserved. # -# # -# Permission to use, copy, modify, and distribute this software and # -# its documentation for any purpose and without fee is hereby granted, # -# provided that the above copyright notice appears in all copies and # -# that both that copyright notice and this permission notice appear # -# in supporting documentation, and that the name of The University # -# of Utah not be used in advertising or publicity pertaining to # -# distribution of the software without specific, written prior # -# permission. This software is supplied as is without expressed or # -# implied warranties of any kind. # -######################################################################## diff --git a/README.md b/README.md index d956a1b..fb13f02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # PyMonCtl [![Type Checking](https://github.com/Kalmat/PyMonCtl/actions/workflows/type-checking.yml/badge.svg)](https://github.com/Kalmat/PyMonCtl/actions/workflows/type-checking.yml) [![PyPI version](https://badge.fury.io/py/PyMonCtl.svg)](https://badge.fury.io/py/PyMonCtl) +[![Documentation Status](https://readthedocs.org/projects/pymonctl/badge/?version=latest)](https://pymonctl.readthedocs.io/en/latest/?badge=latest) + Cross-Platform module which provides a set of features to get info on and control monitors. @@ -19,20 +21,20 @@ Additional tools/extensions/APIs used: Functions to get monitor instances, get info and arrange monitors plugged to the system. -| General functions: | -|:-----------------------:| -| getAllMonitors | -| getAllMonitorsDict | -| getMonitorsCount | -| getPrimary | -| findMonitorsAtPoint | -| findMonitorsAtPointInfo | -| findMonitorWithName | -| findMonitorWithNameInfo | -| saveSetup | -| restoreSetup | -| arrangeMonitors | -| getMousePos | +| General functions: | +|:----------------------------------------------------------------:| +| [getAllMonitors](docstrings.md#getallmonitors) | +| [getAllMonitorsDict](docstrings.md#getallmonitorsdict) | +| [getMonitorsCount](docstrings.md#getmonitorscount) | +| [getPrimary](docstrings.md#getprimary) | +| [findMonitorsAtPoint](docstrings.md#findmonitorsatpoint) | +| [findMonitorsAtPointInfo](docstrings.md#findmonitorsatpointinfo) | +| [findMonitorWithName](docstrings.md#findmonitorwithname) | +| [findMonitorWithNameInfo](docstrings.md#findmonitorwithnameinfo) | +| [saveSetup](docstrings.md#savesetup) | +| [restoreSetup](docstrings.md#restoresetup) | +| [arrangeMonitors](docstrings.md#arrangemonitors) | +| [getMousePos](docstrings.md#getmousepos) | ## Monitor Class @@ -47,40 +49,40 @@ can directly be used to invoke `findMonitorWithName(name)` function. To instantiate it, you need to pass the monitor handle (OS-dependent). It can raise ValueError exception in case the provided handle is not valid. -| Methods | Windows | Linux | macOS | -|:--------------:|:-------:|:-----:|:-----:| -| size | X | X | X | -| workarea | X | X | X | -| position | X | X | X | -| setPosition | X | X | X | -| box | X | X | X | -| rect | X | X | X | -| frequency | X | X | X | -| colordepth | X | X | X | -| dpi | X | X | X | -| scale | X | X | X | -| setScale | X | X | X | -| orientation | X | X | X | -| setOrientation | X | X | X (1) | -| brightness | X (2) | X | X (1) | -| setBrightness | X (2) | X | X (1) | -| contrast | X (2) | X (3) | X (3) | -| setContrast | X (2) | X (3) | X (3) | -| mode | X | X | X | -| setMode | X | X | X | -| defaultMode | X | X | X | -| setDefaultMode | X | X | X | -| allModes | X | X | X | -| setPrimary | X | X | X | -| isPrimary | X | X | X | -| turnOn | X (4) | X | X (4) | -| turnOff | X (4) | X | X (4) | -| isOn | X (2) | X | X | -| suspend | X (4) | X (4) | X (4) | -| isSuspended | X (2) | X | X | -| attach | X | X | | -| detach | X | X | | -| isAttached | X | X | X | +| Methods | Windows | Linux | macOS | +|:----------------------------------------------:|:-------:|:-----:|:-----:| +| [size](docstrings.md#size) | X | X | X | +| [workarea](docstrings.md#workarea) | X | X | X | +| [position](docstrings.md#position) | X | X | X | +| [setPosition](docstrings.md#setposition) | X | X | X | +| [box](docstrings.md#box) | X | X | X | +| [rect](docstrings.md#rect) | X | X | X | +| [frequency](docstrings.md#frequency) | X | X | X | +| [colordepth](docstrings.md#colordepth) | X | X | X | +| [dpi](docstrings.md#dpi) | X | X | X | +| [scale](docstrings.md#scale) | X | X | X | +| [setScale](docstrings.md#setscale) | X | X | X | +| [orientation](docstrings.md#orientation) | X | X | X | +| [setOrientation](docstrings.md#setorientation) | X | X | X (1) | +| [brightness](docstrings.md#brightness) | X (2) | X | X | +| [setBrightness](docstrings.md#setbrightness) | X (2) | X | X | +| [contrast](docstrings.md#contrast) | X (2) | X (3) | X (3) | +| [setContrast](docstrings.md#setcontrast) | X (2) | X (3) | X (3) | +| [mode](docstrings.md#mode) | X | X | X | +| [setMode](docstrings.md#setmode) | X | X | X | +| [defaultMode](docstrings.md#defaultmode) | X | X | X | +| [setDefaultMode](docstrings.md#setdefaultmode) | X | X | X | +| [allModes](docstrings.md#allmodes) | X | X | X | +| [setPrimary](docstrings.md#setprimary) | X | X | X | +| [isPrimary](docstrings.md#isprimary) | X | X | X | +| [turnOn](docstrings.md#turnon) | X (4) | X | X (4) | +| [turnOff](docstrings.md#turnoff) | X (4) | X | X (4) | +| [isOn](docstrings.md#ison) | X (2) | X | X | +| [suspend](docstrings.md#suspend) | X (4) | X (4) | X (4) | +| [isSuspended](docstrings.md#issuspended) | X (2) | X | X | +| [attach](docstrings.md#attach) | X | X | | +| [detach](docstrings.md#detach) | X | X | | +| [isAttached](docstrings.md#isattached) | X | X | X | (1) Maybe not working in all macOS versions and/or architectures (thanks to University of [Utah - Marriott Library - Apple Infrastructure](https://github.com/univ-of-utah-marriott-library-apple/privacy_services_manager), [eryksun](https://stackoverflow.com/questions/22841741/calling-functions-with-arguments-from-corefoundation-using-ctypes) and [nriley](https://github.com/nriley/brightness/blob/master/brightness.c) for pointing me to the solution) @@ -95,7 +97,7 @@ the provided handle is not valid. - macOS: It will suspend ALL monitors. Use turnOn() to wake them up again -#### WARNING: Most of these properties may return ''None'' in case the value can not be obtained +***WARNING: Most of these properties may return ''None'' in case the value can not be obtained*** ### Important OS-dependent behaviors and limitations: @@ -123,10 +125,10 @@ You can activate a watchdog, running in a separate Thread, which will allow you information updated, without negatively impacting your main process, and define hooks and its callbacks to be notified when monitors are plugged / unplugged or their properties change. -| Watchdog methods: | -|:----------------------:| -| isWatchdogEnabled | -| updateWatchdogInterval | +| Watchdog methods: | +|:--------------------------------------------------------------:| +| [isWatchdogEnabled](docstrings.md#iswatchdogenabled) | +| [updateWatchdogInterval](docstrings.md#updatewatchdoginterval) | The watchdog will automatically start while the update information is enabled and / or there are any listeners registered, and will automatically stop otherwise or if the script finishes. @@ -138,11 +140,11 @@ consume more CPU and may produce additional notifications for intermediate (non- ### Keep Monitors info updated -| Info update methods: | -|:--------------------------:| -| enableUpdateInfo | -| disableUpdateInfo | -| isUpdateInfoEnabled | +| Info update methods: | +|:---------------------------------------------------------:| +| [enableUpdateInfo](docstrings.md#enableupdateinfo) | +| [disableUpdateInfo](docstrings.md#disableupdateinfo) | +| [isUpdateInfoEnabled](docstrings.md#isupdateinfoenabled) | Enable this only if you need to keep track of monitor-related events like changing its resolution, position, scale, or if monitors can be dynamically plugged or unplugged in a multi-monitor setup. If you need monitors info updated @@ -156,14 +158,14 @@ their properties (see `getAllMonitors()` and `getAllMonitorsDict()` function). It is possible to register listeners to be invoked in case the number of connected monitors or their properties change. -| Listeners methods: | -|:--------------------------:| -| plugListenerRegister | -| changeListenerRegister | -| plugListenerUnregister | -| changeListenerUnregister | -| isPlugListenerRegistered | -| isChangeListenerRegistered | +| Listeners methods: | +|:----------------------------------------------------------------------:| +| [plugListenerRegister](docstrings.md#pluglistenerregister) | +| [changeListenerRegister](docstrings.md#changelistenerregister) | +| [plugListenerUnregister](docstrings.md#pluglistenerunregister) | +| [changeListenerUnregister](docstrings.md#changelistenerunregister) | +| [isPlugListenerRegistered](docstrings.md#ispluglistenerregistered) | +| [isChangeListenerRegistered](docstrings.md#ischangelistenerregistered) | The information passed to the listeners is as follows: diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..d5a655d --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,54 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import re +import time + +project = 'PyMonCtl' +year = time.strftime("%Y") +author = 'Kalmat' +copyright = year + ", " + author +release = "latest" +with open("../../src/pymonctl/__init__.py", "r") as fileObj: + match = re.search( + r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fileObj.read(), re.MULTILINE + ) + if match: + release = match.group(1) + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +extensions = ["myst_parser"] +myst_enable_extensions = ["colon_fence"] +source_suffix = { + '.rst': 'restructuredtext', + '.txt': 'markdown', + '.md': 'markdown', +} +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output +# https://www.sphinx-doc.org/en/master/usage/theming.html +html_theme = 'bizstyle' +html_static_path = ['_static'] +myst_heading_anchors = 7 + +# -- Copy the modules documentation ------------------------------------------ +# https://stackoverflow.com/questions/66495200/is-it-possible-to-include-external-rst-files-in-my-documentation +from urllib.request import urlretrieve + +# urlretrieve( +# "https://raw.githubusercontent.com/kalmat/pymonctl/master/README.md", +# "index.md" +# ) +# urlretrieve( +# "https://raw.githubusercontent.com/kalmat/pymonctl/master/docstrings.md", +# "docstrings.md" +# ) diff --git a/docstrings.md b/docstrings.md new file mode 100644 index 0000000..9906090 --- /dev/null +++ b/docstrings.md @@ -0,0 +1,1138 @@ +## General Functions + + + +#### getAllMonitors + +```python +def getAllMonitors() -> list[Monitor] +``` + +Get the list with all Monitor instances from plugged monitors. + +In case you plan to use this function in a scenario in which it could be invoked quickly and repeatedly, +it's highly recommended to enable update watchdog (see enableUpdate() function). + +**Returns**: + +list of Monitor instances + + + +#### getAllMonitorsDict + +```python +def getAllMonitorsDict() -> dict[str, ScreenValue] +``` + +Get all monitors info plugged to the system, as a dict. + +In case you plan to use this function in a scenario in which it could be invoked quickly and repeatedly, +it's highly recommended to enable update watchdog (see enableUpdate() function). + +**Returns**: + +Monitors info as python dictionary +Output Format: + Key: + Display name (in macOS it is necessary to add handle to avoid duplicates) + + Values: + "system_name": + display name as returned by the system (in macOS, the name can be duplicated!) + "id": + display handle according to each platform/OS + "is_primary": + ''True'' if monitor is primary (shows clock and notification area, sign in, lock, CTRL+ALT+DELETE screens...) + "position": + Point(x, y) struct containing the display position ((0, 0) for the primary screen) + "size": + Size(width, height) struct containing the display size, in pixels + "workarea": + Rect(left, top, right, bottom) struct with the screen workarea, in pixels + "scale": + Scale ratio, as a tuple of (x, y) scale percentage + "dpi": + Dots per inch, as a tuple of (x, y) dpi values + "orientation": + Display orientation: 0 - Landscape / 1 - Portrait / 2 - Landscape (reversed) / 3 - Portrait (reversed) + "frequency": + Refresh rate of the display, in Hz + "colordepth": + Bits per pixel referred to the display color depth + + + +#### getMonitorsCount + +```python +def getMonitorsCount() -> int +``` + +Get the number of monitors currently connected to the system. + +**Returns**: + +number of monitors as integer + + + +#### getPrimary + +```python +def getPrimary() -> Monitor +``` + +Get primary monitor instance. This is equivalent to invoking ''Monitor()'', with empty input params. + +**Returns**: + +Monitor instance or None + + + +#### findMonitorsAtPoint + +```python +def findMonitorsAtPoint(x: int, y: int) -> List[Monitor] +``` + +Get all Monitor class instances in which given coordinates (x, y) are found. + +**Arguments**: + +- `x`: target X coordinate +- `y`: target Y coordinate + +**Returns**: + +List of Monitor instances or empty + + + +#### findMonitorsAtPointInfo + +```python +def findMonitorsAtPointInfo(x: int, y: int) -> List[dict[str, ScreenValue]] +``` + +Get all monitors info in which given coordinates (x, y) are found. + +**Arguments**: + +- `x`: target X coordinate +- `y`: target Y coordinate + +**Returns**: + +list of monitor info (see getAllMonitorsDict() doc) as a list of dicts, or empty + + + +#### findMonitorWithName + +```python +def findMonitorWithName(name: str) -> Optional[Monitor] +``` + +Get the Monitor class instance which name matches given name. + +**Arguments**: + +- `name`: target monitor name + +**Returns**: + +Monitor or None if not found + + + +#### findMonitorWithNameInfo + +```python +def findMonitorWithNameInfo(name: str) -> dict[str, ScreenValue] +``` + +Get monitor instance which name matches given name. + +**Arguments**: + +- `name`: target monitor name + +**Returns**: + +monitor info (see getAllMonitorsDict() doc) as dict, or empty + + + +#### arrangeMonitors + +```python +def arrangeMonitors(arrangement: dict[str, + dict[str, + Optional[Union[str, int, Position, + Point, Size]]]]) +``` + +Arrange all monitors in a given shape. + +For that, you must pass a dict with the following structure: + "Monitor name": + monitor name as keys() returned by getAllMonitorsDict() (don't use "system_name" value for this) + "relativePos": + position of this monitor in relation to the monitor provided in ''relativeTo'' + "relativeTo": + monitor name to which ''relativePos'' is referred to (or None if PRIMARY) + + +You MUST pass the position of ALL monitors, and SET ONE of them as PRIMARY. + +HIGHLY RECOMMENDED: When building complex arrangements, start by the primary monitor and then build the rest +taking previous ones as references. + +EXAMPLE for a 3-Monitors setup in which second is at the left and third is on top of primary monitor: + + { + "Display_1": {"relativePos": Position.PRIMARY, "relativeTo": None}, + + "Display_2": {"relativePos": Position.LEFT_TOP, "relativeTo": "Display_1"}, + + "Display_3": {"relativePos": Position.ABOVE_LEFT, "relativeTo": "Display_1"} + } + +**Arguments**: + +- `arrangement`: arrangement structure as dict + + + +#### getMousePos + +```python +def getMousePos() -> Point +``` + +Get the current (x, y) coordinates of the mouse pointer on screen, in pixels + +**Returns**: + +Point struct + + + +#### saveSetup + +```python +def saveSetup() -> List[Tuple[Monitor, ScreenValue]] +``` + +Save current monitors setup information to be restored afterward. + +If you just need monitors information in dictionary format, use getAllMonitorsDict() instead. + +If you need all monitors instances to access all methods, use getAllMonitors() instead. + +**Returns**: + +list of tuples containing all necessary info to restore saved setup as required by restoreSetup() + + + +#### restoreSetup + +```python +def restoreSetup(setup: List[Tuple[Monitor, ScreenValue]]) +``` + +Restore given monitors setup (position, mode, orientation, scale, etc.). The function will also + +try to re-attach / turn on / wake monitors if needed. + +In case you want to just reposition monitors without changing all other settings, use arrangeMonitors() instead. + +**Arguments**: + +- `setup`: monitors info dictionary as returned by saveSetup() + + + + + +#### version + +```python +def version(numberOnly: bool = True) -> str +``` + +Returns the current version of PyMonCtl module, in the form ''x.x.xx'' as string + +## Monitor Class Methods + +```python +class Monitor() +``` + + + +#### size + +```python +@property +@abstractmethod +def size() -> Optional[Size] +``` + +Get the dimensions of the monitor as a size struct (width, height) + +This property can not be set independently. To do so, choose an allowed mode (from monitor.allModes) +and set the monitor mode property (monitor.mode = selectedMode) + +**Returns**: + +Size + + + +#### workarea + +```python +@property +@abstractmethod +def workarea() -> Optional[Rect] +``` + +Get dimensions of the "usable by applications" area (screen size minus docks, taskbars and so on), as + +a rect struct (x, y, right, bottom) + +This property can not be set. + +**Returns**: + +Rect + + + +#### position + +```python +@property +@abstractmethod +def position() -> Optional[Point] +``` + +Get monitor position coordinates as a point struct (x, y) + +This property can not be set. Use setPosition() method instead. + +**Returns**: + +Point + + + +#### setPosition + +```python +@abstractmethod +def setPosition(relativePos: Union[int, Position, Point, Tuple[int, int]], + relativeTo: Optional[str]) +``` + +Change relative position of the current the monitor in relation to another existing monitor (e.g. primary monitor). + +On Windows and macOS, setting position to (0, 0) will also mean setting the monitor as primary, and viceversa; +but not on Linux (on Linux, combine setPrimary() and setPosition() to get desired config). + +In general, it is HIGHLY recommendable to use arrangeMonitors() method instead of setPosition(), and most +especially in complex arrangements or setups with more than 2 monitors. + +Important OS-dependent behaviors and limitations: + +- On Windows, primary monitor is mandatory, and it is always placed at (0, 0) coordinates. Besides, the monitors can not overlap. To set a monitor as Primary, it is necessary to reposition primary monitor first, so the rest of monitors will sequentially be repositioned to LEFT. + +- On Linux, primary monitor can be anywhere, and even there can be no primary monitor. Monitors can overlap, so take this into account when setting a new monitor position. Also bear in mind that xranr won't accept negative values, so the whole config will be referenced to (0, 0) coordinates. + +- On macOS, primary monitor is mandatory, and it is always placed at (0, 0) coordinates. The monitors can overlap. To set a monitor as Primary, it is necessary to reposition primary monitor first, so the rest of monitors will sequentially be repositioned to LEFT. + +**Arguments**: + +- `relativePos`: position in relation to another existing monitor (e.g. primary) as per Positions.* +- `relativeTo`: monitor in relation to which this monitor must be placed + + + +#### box + +```python +@property +@abstractmethod +def box() -> Optional[Box] +``` + +Get monitor dimensions as a box struct (x, y, width, height) + +This property can not be set. + +**Returns**: + +Box + + + +#### rect + +```python +@property +@abstractmethod +def rect() -> Optional[Rect] +``` + +Get monitor dimensions as a rect struct (x, y, right, bottom) + +This property can not be set. + +**Returns**: + +Rect + + + +#### scale + +```python +@property +@abstractmethod +def scale() -> Optional[Tuple[float, float]] +``` + +Get scale for the monitor + +Note not all scales will be allowed for all monitors and/or modes + +**Returns**: + +tuple of float scale value in X, Y coordinates + + + +#### setScale + +```python +@abstractmethod +def setScale(scale: Tuple[float, float], applyGlobally: bool = True) +``` + +Change scale for the monitor + +Note not all scales will be allowed for all monitors and/or modes + +**Arguments**: + +- `scale`: target percentage as tuple of float value +- `applyGlobally`: (GNOME/X11 ONLY) Will affect all monitors (''True'', default) or selected one only (''False'') + + + +#### dpi + +```python +@property +@abstractmethod +def dpi() -> Optional[Tuple[float, float]] +``` + +Get the dpi (dots/pixels per inch) value for the monitor + +This property can not be set + +**Returns**: + +tuple of dpi float value in X, Y coordinates + + + +#### orientation + +```python +@property +@abstractmethod +def orientation() -> Optional[Union[int, Orientation]] +``` + +Get current orientation for the monitor identified by name (or primary if empty) + +The available orientations are: + 0 - 0 degrees (normal) + 1 - 90 degrees (right) + 2 - 180 degrees (inverted) + 3 - 270 degrees (left) + +**Returns**: + +current orientation value as int (or Orientation value) + + + +#### setOrientation + +```python +@abstractmethod +def setOrientation(orientation: Optional[Union[int, Orientation]]) +``` + +Change orientation for the monitor identified by name (or primary if empty) + +The available orientations are: + 0 - 0 degrees (normal) + 1 - 90 degrees (right) + 2 - 180 degrees (inverted) + 3 - 270 degrees (left) + +**Arguments**: + +- `orientation`: orientation as per Orientations.* + + + +#### frequency + +```python +@property +@abstractmethod +def frequency() -> Optional[float] +``` + +Get current refresh rate of monitor. + +This property can not be set independently. To do so, choose an allowed mode (from monitor.allModes) +and set the monitor mode property (monitor.mode = selectedMode) + +**Returns**: + +float + + + +#### colordepth + +```python +@property +@abstractmethod +def colordepth() -> Optional[int] +``` + +Get the colordepth (bits per pixel to describe color) value for the monitor + +This property can not be set + +**Returns**: + +int + + + +#### brightness + +```python +@property +@abstractmethod +def brightness() -> Optional[int] +``` + +Get the brightness of monitor. The return value is normalized to 0-100 (as a percentage) + +**Returns**: + +brightness as int (1-100) + + + +#### setBrightness + +```python +@abstractmethod +def setBrightness(brightness: Optional[int]) +``` + +Change the brightness of monitor. The input parameter must be defined as a percentage (0-100) + +**Arguments**: + +- `brightness`: brightness value to be set (0-100) + + + +#### contrast + +```python +@property +@abstractmethod +def contrast() -> Optional[int] +``` + +Get the contrast of monitor. The return value is normalized to 0-100 (as a percentage) + +WARNING: In Linux and macOS contrast is calculated from Gamma RGB values. + +**Returns**: + +contrast as int (1-100) + + + +#### setContrast + +```python +@abstractmethod +def setContrast(contrast: Optional[int]) +``` + +Change the contrast of monitor. The input parameter must be defined as a percentage (0-100) + +WARNING: In Linux and macOS the change will apply to Gamma homogeneously for all color components (R, G, B). + +Example for Linux: A value of 50.0 (50%), will result in a Gamma of ''0.5:0.5:0.5'' + +**Arguments**: + +- `contrast`: contrast value to be set (0-100) + + + +#### mode + +```python +@property +@abstractmethod +def mode() -> Optional[DisplayMode] +``` + +Get the current monitor mode (width, height, refresh-rate) for the monitor + +**Returns**: + +current mode as DisplayMode struct + + + +#### setMode + +```python +@abstractmethod +def setMode(mode: Optional[DisplayMode]) +``` + +Change current monitor mode (resolution and/or refresh-rate) for the monitor + +The mode must be one of the allowed modes by the monitor (see allModes property). + +**Arguments**: + +- `mode`: target mode as DisplayMode (width, height and frequency) + + + +#### defaultMode + +```python +@property +@abstractmethod +def defaultMode() -> Optional[DisplayMode] +``` + +Get the preferred mode for the monitor + +**Returns**: + +DisplayMode struct (width, height, frequency) + + + +#### setDefaultMode + +```python +@abstractmethod +def setDefaultMode() +``` + +Change current mode to default / preferred mode + + + +#### allModes + +```python +@property +@abstractmethod +def allModes() -> list[DisplayMode] +``` + +Get all allowed modes for the monitor + +**Returns**: + +list of DisplayMode (width, height, frequency) + + + +#### isPrimary + +```python +@property +@abstractmethod +def isPrimary() -> bool +``` + +Check if given monitor is primary. + +**Returns**: + +''True'' if given monitor is primary, ''False'' otherwise + + + +#### setPrimary + +```python +@abstractmethod +def setPrimary() +``` + +Set monitor as the primary one. + +WARNING: Notice this can also change the monitor position, altering the whole monitors setup. +To properly handle this, use arrangeMonitors() instead. + + + +#### turnOn + +```python +@abstractmethod +def turnOn() +``` + +Turn on or wakeup monitor if it was off or suspended (but not if it is detached). + + + +#### turnOff + +```python +@abstractmethod +def turnOff() +``` + +Turn off monitor + +WARNING: + + Windows: + If monitor has no VCP MCCS support, it can not be addressed separately, so ALL monitors will be turned off. + To address a specific monitor, try using detach() method + + macOS: + Didn't find a way to programmatically turn off a given monitor. Use suspend instead. + + + +#### isOn + +```python +@property +@abstractmethod +def isOn() -> Optional[bool] +``` + +Check if monitor is on + +WARNING: not working in macOS (... yet?) + +**Returns**: + +''True'' if monitor is on + + + +#### suspend + +```python +@abstractmethod +def suspend() +``` + +Suspend (standby) monitor + +WARNING: + + Windows: + If monitor has no VCP MCCS support, it can not be addressed separately, so ALL monitors will be suspended. + To address a specific monitor, try using detach() method + + Linux: + This method will suspend ALL monitors. + + macOS: + This method will suspend ALL monitors. + + + +#### isSuspended + +```python +@property +@abstractmethod +def isSuspended() -> Optional[bool] +``` + +Check if monitor is in standby mode + +**Returns**: + +''True'' if monitor is in standby mode + + + +#### attach + +```python +@abstractmethod +def attach() +``` + +Attach a previously detached monitor to system + +WARNING: In Windows, all IDs may change when attach / detach / plug / unplug a monitor. The module + will try to refresh all IDs from all Monitor class instances... but take into account it may fail! + +WARNING: not working in macOS (... yet?) + + + +#### detach + +```python +@abstractmethod +def detach(permanent: bool = False) +``` + +Detach monitor from system. + +Be aware that if you detach a monitor and the script ends, you will have to physically re-attach the monitor. + +It will not likely work if system has just one monitor plugged. + +WARNING: In Windows, all IDs may change when attach / detach / plug / unplug a monitor. The module + will try to refresh all IDs from all Monitor class instances... but take into account it may fail! + +WARNING: not working in macOS (... yet?) + +**Arguments**: + +- `permanent`: set to ''True'' to permanently detach the monitor from system + + + +#### isAttached + +```python +@property +@abstractmethod +def isAttached() -> Optional[bool] +``` + +Check if monitor is attached (not necessarily ON) to system + +**Returns**: + +''True'' if monitor is attached + + + +## Watchdog Methods + +```python +class _UpdateScreens(threading.Thread) +``` + + + +#### enableUpdateInfo + +```python +def enableUpdateInfo() +``` + +Enable this only if you need to keep track of monitor-related events like changing its resolution, position, +or if monitors can be dynamically plugged or unplugged in a multi-monitor setup. This function can also be +useful in scenarios in which monitors list or properties need to be queried quickly and repeatedly, thus keeping +this information updated without impacting main process. + +If enabled, it will activate a separate thread which will periodically update the list of monitors and +their properties (see getAllMonitors() and getAllMonitorsDict() functions). + +If disabled, the information on the monitors connected to the system will be updated right at the moment, +but this might be slow and CPU-consuming, especially if quickly and repeatedly invoked. + + + +#### disableUpdateInfo + +```python +def disableUpdateInfo() +``` + +The monitors information will be immediately queried after disabling this feature, not taking advantage of +keeping information updated on a separate thread. + +Enable this process again, or invoke getMonitors() function if you need updated info. + + + +#### plugListenerRegister + +```python +def plugListenerRegister( + monitorCountChanged: Callable[[List[str], dict[str, ScreenValue]], + None]) +``` + +Use this only if you need to keep track of monitor that can be dynamically plugged or unplugged in a + +multi-monitor setup. + +The registered callbacks will be invoked in case the number of connected monitors change. +The information passed to the callbacks is: + + - Names of the screens which have changed (as a list of strings). + - All screens info, as returned by getAllMonitorsDict() function. + +It is possible to access all monitors information by using screen name as dictionary key + +**Arguments**: + +- `monitorCountChanged`: callback to be invoked in case the number of monitor connected changes + + + +#### plugListenerUnregister + +```python +def plugListenerUnregister( + monitorCountChanged: Callable[[List[str], dict[str, ScreenValue]], + None]) +``` + +Use this function to un-register your custom callback. The callback will not be invoked anymore in case + +the number of monitor changes. + +**Arguments**: + +- `monitorCountChanged`: callback previously registered + + + +#### changeListenerRegister + +```python +def changeListenerRegister( + monitorPropsChanged: Callable[[List[str], dict[str, ScreenValue]], + None]) +``` + +Use this only if you need to keep track of monitor properties changes (position, size, refresh-rate, etc.) in a + +multi-monitor setup. + +The registered callbacks will be invoked in case these properties change. +The information passed to the callbacks is: + + - Names of the screens which have changed (as a list of strings). + - All screens info, as returned by getAllMonitorsDict() function. + +It is possible to access all monitor information by using screen name as dictionary key + +**Arguments**: + +- `monitorPropsChanged`: callback to be invoked in case the number of monitor properties change + + + +#### changeListenerUnregister + +```python +def changeListenerUnregister( + monitorPropsChanged: Callable[[List[str], dict[str, ScreenValue]], + None]) +``` + +Use this function to un-register your custom callback. The callback will not be invoked anymore in case + +the monitor properties change. + +**Arguments**: + +- `monitorPropsChanged`: callback previously registered + + + +#### isWatchdogEnabled + +```python +def isWatchdogEnabled() -> bool +``` + +Check if the daemon updating screens information and (if applies) invoking callbacks when needed is alive. + +If it is not, just enable update process, or register the callbacks you need. It will be automatically started. + +**Returns**: + +Return ''True'' is process (thread) is alive + + + +#### isUpdateInfoEnabled + +```python +def isUpdateInfoEnabled() -> bool +``` + +Get monitors watch process status (enabled / disabled). + +**Returns**: + +Returns ''True'' if enabled. + + + +#### isPlugListenerRegistered + +```python +def isPlugListenerRegistered( + monitorCountChanged: Callable[[List[str], dict[str, ScreenValue]], + None]) +``` + +Check if callback is already registered to be invoked when monitor plugged count change + +**Returns**: + +Returns ''True'' if registered + + + +#### isChangeListenerRegistered + +```python +def isChangeListenerRegistered( + monitorPropsChanged: Callable[[List[str], dict[str, ScreenValue]], + None]) +``` + +Check if callback is already registered to be invoked when monitor properties change + +**Returns**: + +Returns ''True'' if registered + + + +#### updateWatchdogInterval + +```python +def updateWatchdogInterval(interval: float) +``` + +Change the wait interval for the thread loop in seconds (or fractions), Default is 0.50 seconds. + +Higher values will take longer to detect and notify changes. + +Lower values will make it faster, but will consume more CPU. + +Also bear in mind that the OS will take some time to refresh changes, so lowering the update interval +may not necessarily produce better (faster) results. + +**Arguments**: + +- `interval`: new interval value in seconds (or fractions), as float. + +## Structs + + + +#### Box + +```python +class Box(NamedTuple) +``` + +Container class to handle Box struct (left, top, width, height) + + + +#### Rect + +```python +class Rect(NamedTuple) +``` + +Container class to handle Rect struct (left, top, right, bottom) + + + +#### Point + +```python +class Point(NamedTuple) +``` + +Container class to handle Point struct (x, y) + + + +#### Size + +```python +class Size(NamedTuple) +``` + +Container class to handle Size struct (right, bottom) + + + +#### ScreenValue + +```python +class ScreenValue(TypedDict) +``` + +Container class to handle ScreenValue struct: + + - system_name (str): name of the monitor as known by the system + - id (int): handle/identifier of the monitor + - is_primary (bool): ''True'' if it is the primary monitor + - position (Point): position of the monitor + - size (Size): size of the monitor, in pixels + - workarea (Rect): coordinates of the usable area of the monitor usable by apps/windows (no docks, taskbars, ...) + - scale (Tuple[int, int]): text scale currently applied to monitor + - dpi (Tuple[int, int]): dpi values of current resolution + - orientation (int): rotation value of the monitor as per Orientation values (NORMAL = 0, RIGHT = 1, INVERTED = 2, LEFT = 3) + - frequency (float): refresh rate of the monitor + - colordepth (int): color depth of the monitor + + + +#### DisplayMode + +```python +class DisplayMode(NamedTuple) +``` + +Container class to handle DisplayMode struct: + + - width (int): width, in pixels + - height (int): height, in pixels + - frequency (float): refresh rate diff --git a/setup.cfg b/setup.cfg index 8bb2787..bd959a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ description_file = README.md [mypy] -python_version = 3.7 +python_version = 3.8 mypy_path = src/, typings/ strict = True diff --git a/setup.py b/setup.py index 455e286..5e69cb3 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ "mypy>=0.990", "types-pywin32>=305.0.0.3", "types-python-xlib>=0.32", + "myst-parser", "pywinctl>=0.3" ] },