# 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)
Cross-Platform module which provides a set of features to get info on and control monitors.
Additional tools/extensions/APIs used:
- Linux:
- Xlib's randr extension
- xrandr command-line tool
- xset command-line tool
- Windows:
- VCP MCCS API interface
- macOS:
- pmset command-line tool
## General Features
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 |
## Monitor Class
Class to access all methods and functions to get info and control a given monitor plugged to the system.
This class is not meant to be directly instantiated. Instead, use convenience functions like `getAllMonitors()`,
`getPrimary()` or `findMonitorsAtPoint(x, y)`. Use [PyWinCtl](https://github.com/Kalmat/PyWinCtl) module in case you need to
find the monitor a given window is in, by using `getDisplay()` method which returns the name of the monitor that
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 |
| setBrightness | X (2) | X | X |
| 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 |
(1) Working only in versions older than Catalina (thanks to University of Utah - Marriott Library - Apple Infrastructure)
(2) If monitor has no VCP MCCS support, these methods won't likely work.
(3) It doesn't exactly return / change contrast, but gamma values.
(4) Different behaviour according to OS:
- Windows: Working with VCP MCCS support only.
- Linux: It will suspend ALL monitors. To address just one monitor, try using turnOff() / turnOn() / detach() / attach() methods.
- 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
### Important OS-dependent behaviors and limitations:
- Windows:
- Primary monitor is mandatory, and it is always placed at (0, 0) coordinates.
- 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 RIGHT_TOP.
- If you attach / detach / plug / unplug a monitor, all IDs may change. The module will try to refresh the IDs for all Monitor class instances, but take into account it may fail!
- 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.
- xrandr won't accept negative values, so the whole setup will be referenced to (0, 0) coordinates.
- xrandr will sort primary monitors first. Because of this and for homegeneity, when positioning a monitor as primary (only with setPosition() method), it will be placed at (0 ,0) and all the rest to RIGHT_TOP.
- macOS:
- Primary monitor is mandatory, and it is always placed at (0, 0) coordinates.
- Monitors can overlap, so take this into account when setting a new monitor position.
- To set a monitor as Primary, it is necessary to reposition primary monitor first, so the rest of monitors will sequentially be repositioned to RIGHT_TOP.
- setScale() method uses a workaround by applying the nearest monitor mode to magnify text to given value
It is highly recommended to use `arrangeMonitors()` function for complex setups or just in case there are two or more monitors.
## Keep track of Monitor(s) changes
You can activate a watchdog, running in a separate Thread, which will allow you to keep monitors
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 |
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.
You can check if the watchdog is working (`isWatchdogEnabled()`) and also change its update interval
(`updateWatchdogInterval()`) in case you need a custom period (default is 0.5 seconds). Adjust this value to your needs,
but take into account that higher values will take longer to detect and notify changes; whilst lower values will
consume more CPU and may produce additional notifications for intermediate (non-final) status.
### Keep Monitors info updated
| Info update methods: |
|:--------------------------:|
| enableUpdateInfo |
| disableUpdateInfo |
| 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
at a given moment, but not continuously updated, just invoke `getAllMonitors()` at your convenience.
If enabled, it will activate a separate thread which will periodically update the list of monitors and
their properties (see `getAllMonitors()` and `getAllMonitorsDict()` function).
### Get notified on Monitors changes
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 |
The information passed to the listeners is as follows:
- Names of the monitors which have changed (as a list of strings)
- All monitors info, as returned by `getAllMonitorsDict()`. To access monitors properties, use monitor name/s as dictionary key
Example:
import pymonctl as pmc
import time
def countChanged(names, screensInfo):
print("MONITOR PLUGGED/UNPLUGGED:", names)
for name in names:
print("MONITORS INFO:", screensInfo[name])
def propsChanged(names, screensInfo):
print("MONITOR CHANGED:", names)
for name in names:
print("MONITORS INFO:", screensInfo[name])
pmc.plugListenerRegister(countChanged)
pmc.changeListenerRegister(propsChanged)
print("Plug/Unplug monitors, or change monitor properties while running")
print("Press Ctl-C to Quit")
while True:
try:
time.sleep(1)
except KeyboardInterrupt:
break
pmc.plugListenerUnregister(countChanged)
pmc.changeListenerUnregister(propsChanged)
## INSTALL <a name="install"></a>
To install this module on your system, you can use pip:
pip install pymonctl
or
python3 -m pip install pymonctl
Alternatively, you can download the wheel file (.whl) available in the [Download page](https://pypi.org/project/PyMonCtl/#files) and the [dist folder](https://github.com/Kalmat/PyMonCtl/tree/master/dist), and run this (don't forget to replace 'x.x.xx' with proper version number):
pip install PyMonCtl-x.x.xx-py3-none-any.whl
You may want to add `--force-reinstall` option to be sure you are installing the right dependencies version.
Then, you can use it on your own projects just importing it:
import pymonctl
## SUPPORT <a name="support"></a>
In case you have a problem, comments or suggestions, do not hesitate to [open issues](https://github.com/Kalmat/PyMonCtl/issues) on the [project homepage](https://github.com/Kalmat/PyMonCtl)
## USING THIS CODE <a name="using"></a>
If you want to use this code or contribute, you can either:
* Create a fork of the [repository](https://github.com/Kalmat/PyMonCtl), or
* [Download the repository](https://github.com/Kalmat/PyMonCtl/archive/refs/heads/master.zip), uncompress, and open it on your IDE of choice (e.g. PyCharm)
Be sure you install all dependencies described on `requirements.txt` by using pip
python3 -m pip install -r requirements.txt
## TEST <a name="test"></a>
To test this module on your own system, cd to `tests` folder and run:
python3 test_pymonctl.py
Raw data
{
"_id": null,
"home_page": "https://github.com/Kalmat/PyMonCtl",
"name": "PyMonCtl",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "screen display monitor control geometry size position frequency scale orientation screen-size mouse-position",
"author": "Kalmat",
"author_email": "palookjones@gmail.com",
"download_url": null,
"platform": null,
"description": "# PyMonCtl\r\n[![Type Checking](https://github.com/Kalmat/PyMonCtl/actions/workflows/type-checking.yml/badge.svg)](https://github.com/Kalmat/PyMonCtl/actions/workflows/type-checking.yml)\r\n[![PyPI version](https://badge.fury.io/py/PyMonCtl.svg)](https://badge.fury.io/py/PyMonCtl)\r\n\r\nCross-Platform module which provides a set of features to get info on and control monitors.\r\n\r\nAdditional tools/extensions/APIs used:\r\n- Linux:\r\n - Xlib's randr extension\r\n - xrandr command-line tool\r\n - xset command-line tool\r\n- Windows:\r\n - VCP MCCS API interface\r\n- macOS:\r\n - pmset command-line tool\r\n \r\n\r\n## General Features\r\n\r\nFunctions to get monitor instances, get info and arrange monitors plugged to the system.\r\n\r\n| General functions: |\r\n|:-----------------------:|\r\n| getAllMonitors |\r\n| getAllMonitorsDict |\r\n| getMonitorsCount |\r\n| getPrimary |\r\n| findMonitorsAtPoint |\r\n| findMonitorsAtPointInfo |\r\n| findMonitorWithName |\r\n| findMonitorWithNameInfo |\r\n| saveSetup |\r\n| restoreSetup |\r\n| arrangeMonitors |\r\n| getMousePos |\r\n\r\n\r\n## Monitor Class\r\n\r\nClass to access all methods and functions to get info and control a given monitor plugged to the system.\r\n\r\nThis class is not meant to be directly instantiated. Instead, use convenience functions like `getAllMonitors()`,\r\n`getPrimary()` or `findMonitorsAtPoint(x, y)`. Use [PyWinCtl](https://github.com/Kalmat/PyWinCtl) module in case you need to \r\nfind the monitor a given window is in, by using `getDisplay()` method which returns the name of the monitor that\r\ncan directly be used to invoke `findMonitorWithName(name)` function.\r\n\r\nTo instantiate it, you need to pass the monitor handle (OS-dependent). It can raise ValueError exception in case \r\nthe provided handle is not valid.\r\n\r\n| Methods | Windows | Linux | macOS |\r\n|:--------------:|:-------:|:-----:|:-----:|\r\n| size | X | X | X |\r\n| workarea | X | X | X |\r\n| position | X | X | X |\r\n| setPosition | X | X | X |\r\n| box | X | X | X |\r\n| rect | X | X | X |\r\n| frequency | X | X | X |\r\n| colordepth | X | X | X |\r\n| dpi | X | X | X |\r\n| scale | X | X | X |\r\n| setScale | X | X | X |\r\n| orientation | X | X | X |\r\n| setOrientation | X | X | X (1) |\r\n| brightness | X (2) | X | X |\r\n| setBrightness | X (2) | X | X |\r\n| contrast | X (2) | X (3) | X (3) |\r\n| setContrast | X (2) | X (3) | X (3) |\r\n| mode | X | X | X |\r\n| setMode | X | X | X |\r\n| defaultMode | X | X | X |\r\n| setDefaultMode | X | X | X |\r\n| allModes | X | X | X |\r\n| setPrimary | X | X | X |\r\n| isPrimary | X | X | X |\r\n| turnOn | X (4) | X | X (4) |\r\n| turnOff | X (4) | X | X (4) |\r\n| isOn | X (2) | X | X |\r\n| suspend | X (4) | X (4) | X (4) |\r\n| isSuspended | X (2) | X | X |\r\n| attach | X | X | |\r\n| detach | X | X | |\r\n| isAttached | X | X | X |\r\n\r\n\r\n(1) Working only in versions older than Catalina (thanks to University of Utah - Marriott Library - Apple Infrastructure)\r\n\r\n(2) If monitor has no VCP MCCS support, these methods won't likely work.\r\n\r\n(3) It doesn't exactly return / change contrast, but gamma values.\r\n\r\n(4) Different behaviour according to OS:\r\n- Windows: Working with VCP MCCS support only.\r\n- Linux: It will suspend ALL monitors. To address just one monitor, try using turnOff() / turnOn() / detach() / attach() methods.\r\n- macOS: It will suspend ALL monitors. Use turnOn() to wake them up again\r\n\r\n\r\n#### WARNING: Most of these properties may return ''None'' in case the value can not be obtained\r\n\r\n### Important OS-dependent behaviors and limitations:\r\n\r\n - Windows:\r\n - Primary monitor is mandatory, and it is always placed at (0, 0) coordinates. \r\n - Monitors can not overlap.\r\n - To set a monitor as Primary, it is necessary to reposition primary monitor first, so the rest of monitors will sequentially be repositioned to RIGHT_TOP.\r\n - If you attach / detach / plug / unplug a monitor, all IDs may change. The module will try to refresh the IDs for all Monitor class instances, but take into account it may fail!\r\n - Linux:\r\n - Primary monitor can be anywhere, and even there can be no primary monitor. \r\n - Monitors can overlap, so take this into account when setting a new monitor position. \r\n - xrandr won't accept negative values, so the whole setup will be referenced to (0, 0) coordinates.\r\n - xrandr will sort primary monitors first. Because of this and for homegeneity, when positioning a monitor as primary (only with setPosition() method), it will be placed at (0 ,0) and all the rest to RIGHT_TOP.\r\n - macOS:\r\n - Primary monitor is mandatory, and it is always placed at (0, 0) coordinates. \r\n - Monitors can overlap, so take this into account when setting a new monitor position. \r\n - To set a monitor as Primary, it is necessary to reposition primary monitor first, so the rest of monitors will sequentially be repositioned to RIGHT_TOP.\r\n - setScale() method uses a workaround by applying the nearest monitor mode to magnify text to given value\r\n\r\nIt is highly recommended to use `arrangeMonitors()` function for complex setups or just in case there are two or more monitors. \r\n\r\n## Keep track of Monitor(s) changes\r\n\r\nYou can activate a watchdog, running in a separate Thread, which will allow you to keep monitors \r\ninformation updated, without negatively impacting your main process, and define hooks and its callbacks to be \r\nnotified when monitors are plugged / unplugged or their properties change.\r\n\r\n| Watchdog methods: |\r\n|:----------------------:|\r\n| isWatchdogEnabled |\r\n| updateWatchdogInterval |\r\n\r\nThe watchdog will automatically start while the update information is enabled and / or there are any listeners \r\nregistered, and will automatically stop otherwise or if the script finishes.\r\n\r\nYou can check if the watchdog is working (`isWatchdogEnabled()`) and also change its update interval \r\n(`updateWatchdogInterval()`) in case you need a custom period (default is 0.5 seconds). Adjust this value to your needs, \r\nbut take into account that higher values will take longer to detect and notify changes; whilst lower values will \r\nconsume more CPU and may produce additional notifications for intermediate (non-final) status.\r\n\r\n### Keep Monitors info updated\r\n\r\n| Info update methods: |\r\n|:--------------------------:|\r\n| enableUpdateInfo |\r\n| disableUpdateInfo |\r\n| isUpdateInfoEnabled |\r\n\r\nEnable this only if you need to keep track of monitor-related events like changing its resolution, position, scale,\r\nor if monitors can be dynamically plugged or unplugged in a multi-monitor setup. If you need monitors info updated \r\nat a given moment, but not continuously updated, just invoke `getAllMonitors()` at your convenience.\r\n\r\nIf enabled, it will activate a separate thread which will periodically update the list of monitors and\r\ntheir properties (see `getAllMonitors()` and `getAllMonitorsDict()` function).\r\n\r\n### Get notified on Monitors changes\r\n\r\nIt is possible to register listeners to be invoked in case the number of connected monitors or their \r\nproperties change.\r\n\r\n| Listeners methods: |\r\n|:--------------------------:|\r\n| plugListenerRegister |\r\n| changeListenerRegister |\r\n| plugListenerUnregister |\r\n| changeListenerUnregister |\r\n| isPlugListenerRegistered |\r\n| isChangeListenerRegistered |\r\n\r\nThe information passed to the listeners is as follows:\r\n\r\n - Names of the monitors which have changed (as a list of strings)\r\n - All monitors info, as returned by `getAllMonitorsDict()`. To access monitors properties, use monitor name/s as dictionary key\r\n\r\nExample:\r\n\r\n import pymonctl as pmc\r\n import time\r\n\r\n def countChanged(names, screensInfo):\r\n print(\"MONITOR PLUGGED/UNPLUGGED:\", names)\r\n for name in names:\r\n print(\"MONITORS INFO:\", screensInfo[name])\r\n\r\n def propsChanged(names, screensInfo):\r\n print(\"MONITOR CHANGED:\", names)\r\n for name in names:\r\n print(\"MONITORS INFO:\", screensInfo[name])\r\n\r\n pmc.plugListenerRegister(countChanged)\r\n pmc.changeListenerRegister(propsChanged)\r\n\r\n print(\"Plug/Unplug monitors, or change monitor properties while running\")\r\n print(\"Press Ctl-C to Quit\")\r\n while True:\r\n try:\r\n time.sleep(1)\r\n except KeyboardInterrupt:\r\n break\r\n\r\n pmc.plugListenerUnregister(countChanged)\r\n pmc.changeListenerUnregister(propsChanged)\r\n\r\n\r\n## INSTALL <a name=\"install\"></a>\r\n\r\nTo install this module on your system, you can use pip: \r\n\r\n pip install pymonctl\r\n\r\nor\r\n\r\n python3 -m pip install pymonctl\r\n\r\nAlternatively, you can download the wheel file (.whl) available in the [Download page](https://pypi.org/project/PyMonCtl/#files) and the [dist folder](https://github.com/Kalmat/PyMonCtl/tree/master/dist), and run this (don't forget to replace 'x.x.xx' with proper version number):\r\n\r\n pip install PyMonCtl-x.x.xx-py3-none-any.whl\r\n\r\nYou may want to add `--force-reinstall` option to be sure you are installing the right dependencies version.\r\n\r\nThen, you can use it on your own projects just importing it:\r\n\r\n import pymonctl\r\n\r\n## SUPPORT <a name=\"support\"></a>\r\n\r\nIn case you have a problem, comments or suggestions, do not hesitate to [open issues](https://github.com/Kalmat/PyMonCtl/issues) on the [project homepage](https://github.com/Kalmat/PyMonCtl)\r\n\r\n## USING THIS CODE <a name=\"using\"></a>\r\n\r\nIf you want to use this code or contribute, you can either:\r\n\r\n* Create a fork of the [repository](https://github.com/Kalmat/PyMonCtl), or \r\n* [Download the repository](https://github.com/Kalmat/PyMonCtl/archive/refs/heads/master.zip), uncompress, and open it on your IDE of choice (e.g. PyCharm)\r\n\r\nBe sure you install all dependencies described on `requirements.txt` by using pip\r\n \r\n python3 -m pip install -r requirements.txt\r\n\r\n## TEST <a name=\"test\"></a>\r\n\r\nTo test this module on your own system, cd to `tests` folder and run:\r\n\r\n python3 test_pymonctl.py\r\n",
"bugtrack_url": null,
"license": "BSD 3",
"summary": "Cross-Platform toolkit to get info on and control monitors connected",
"version": "0.91",
"project_urls": {
"Homepage": "https://github.com/Kalmat/PyMonCtl"
},
"split_keywords": [
"screen",
"display",
"monitor",
"control",
"geometry",
"size",
"position",
"frequency",
"scale",
"orientation",
"screen-size",
"mouse-position"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "538ebf1d577345cdf40fc7ff50318e50f3fbd6cb4db31e908b700de9ff59b7e4",
"md5": "86058569f21366c35e25fb20e59be273",
"sha256": "48b40fb543a05f457e5d2625bf18b2a9878fff3e267d19876c23d983a7eb82ed"
},
"downloads": -1,
"filename": "PyMonCtl-0.91-py3-none-any.whl",
"has_sig": false,
"md5_digest": "86058569f21366c35e25fb20e59be273",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": null,
"size": 45551,
"upload_time": "2024-04-18T19:52:02",
"upload_time_iso_8601": "2024-04-18T19:52:02.604106Z",
"url": "https://files.pythonhosted.org/packages/53/8e/bf1d577345cdf40fc7ff50318e50f3fbd6cb4db31e908b700de9ff59b7e4/PyMonCtl-0.91-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-04-18 19:52:02",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Kalmat",
"github_project": "PyMonCtl",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [],
"lcname": "pymonctl"
}