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"
]
},