diff --git a/docs/source/conf.py b/docs/source/conf.py index 3f6ca99..df82bd1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -8,6 +8,7 @@ import re import time +from urllib.request import urlretrieve project = 'PyMonCtl' year = time.strftime("%Y") @@ -42,8 +43,6 @@ # -- 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" diff --git a/ruff.toml b/ruff.toml index 6cbc1bb..3dc79f7 100644 --- a/ruff.toml +++ b/ruff.toml @@ -49,16 +49,6 @@ ignore = [ # TODO: Consider later "UP031", # printf-string-formatting "RUF059", # unused-unpacked-variable - - # TODO: Not autofixable, address in a separate PR ! - "E402", - "E722", - "F401", - "PERF401", - "PYI063", - "RUF003", - "RUF012", - "RUF022", ] # F401 would remove imports not marked as explicit re-exports, which may break API boundaries extend-unsafe-fixes = ["F401"] diff --git a/src/pymonctl/__init__.py b/src/pymonctl/__init__.py index c8592b9..a017d27 100644 --- a/src/pymonctl/__init__.py +++ b/src/pymonctl/__init__.py @@ -1,6 +1,15 @@ #!/usr/bin/python from importlib.metadata import version as _importlib_version +from ._main import (getAllMonitors, getAllMonitorsDict, getMonitorsCount, getPrimary, + findMonitorsAtPoint, findMonitorsAtPointInfo, findMonitorWithName, findMonitorWithNameInfo, + saveSetup, restoreSetup, arrangeMonitors, getMousePos, Monitor, + enableUpdateInfo, disableUpdateInfo, isUpdateInfoEnabled, isWatchdogEnabled, updateWatchdogInterval, + plugListenerRegister, plugListenerUnregister, isPlugListenerRegistered, + changeListenerRegister, changeListenerUnregister, isChangeListenerRegistered, + DisplayMode, ScreenValue, Size, Point, Box, Rect, Position, Orientation + ) + __all__ = [ "getAllMonitors", "getAllMonitorsDict", "getMonitorsCount", "getPrimary", "findMonitorsAtPoint", "findMonitorsAtPointInfo", "findMonitorWithName", "findMonitorWithNameInfo", @@ -19,11 +28,3 @@ def version(numberOnly: bool = True) -> str: return ("" if numberOnly else "PyMonCtl-")+__version__ -from ._main import (getAllMonitors, getAllMonitorsDict, getMonitorsCount, getPrimary, - findMonitorsAtPoint, findMonitorsAtPointInfo, findMonitorWithName, findMonitorWithNameInfo, - saveSetup, restoreSetup, arrangeMonitors, getMousePos, Monitor, - enableUpdateInfo, disableUpdateInfo, isUpdateInfoEnabled, isWatchdogEnabled, updateWatchdogInterval, - plugListenerRegister, plugListenerUnregister, isPlugListenerRegistered, - changeListenerRegister, changeListenerUnregister, isChangeListenerRegistered, - DisplayMode, ScreenValue, Size, Point, Box, Rect, Position, Orientation - ) diff --git a/src/pymonctl/_main.py b/src/pymonctl/_main.py index a4fdaa4..32f7844 100644 --- a/src/pymonctl/_main.py +++ b/src/pymonctl/_main.py @@ -212,11 +212,7 @@ def saveSetup() -> list[tuple[Monitor, ScreenValue]]: :return: list of tuples containing all necessary info to restore saved setup as required by restoreSetup() """ - result: list[tuple[Monitor, ScreenValue]] = [] - monDict: dict[str, ScreenValue] = getAllMonitorsDict() - for monName in monDict.keys(): - result.append((Monitor(monDict[monName]["id"]), monDict[monName])) - return result + return [(Monitor(screenValue["id"]), screenValue) for screenValue in getAllMonitorsDict().values()] def restoreSetup(setup: list[tuple[Monitor, ScreenValue]]): @@ -1002,18 +998,21 @@ def _getRelativePosition(monitor, relativeTo) -> tuple[int, int]: return x, y + if sys.platform == "darwin": from ._pymonctl_macos import (_getAllMonitors, _getAllMonitorsDict, _getMonitorsCount, _getPrimary, - _findMonitor, _arrangeMonitors, _getMousePos as getMousePos, MacOSMonitor as Monitor + _findMonitor, _arrangeMonitors, _getMousePos, MacOSMonitor as Monitor ) elif sys.platform == "win32": from ._pymonctl_win import (_getAllMonitors, _getAllMonitorsDict, _getMonitorsCount, _getPrimary, - _findMonitor, _arrangeMonitors, _getMousePos as getMousePos, Win32Monitor as Monitor + _findMonitor, _arrangeMonitors, _getMousePos, Win32Monitor as Monitor ) elif sys.platform == "linux": from ._pymonctl_linux import (_getAllMonitors, _getAllMonitorsDict, _getAllMonitorsDictThread, _getMonitorsData, - _getMonitorsCount, _getPrimary, _findMonitor, _arrangeMonitors, _getMousePos as getMousePos, + _getMonitorsCount, _getPrimary, _findMonitor, _arrangeMonitors, _getMousePos, LinuxMonitor as Monitor ) else: raise NotImplementedError('PyMonCtl currently does not support this platform. If you think you can help, please contribute! https://github.com/Kalmat/PyMonCtl') + +getMousePos = _getMousePos diff --git a/src/pymonctl/_pymonctl_linux.py b/src/pymonctl/_pymonctl_linux.py index 129ce2a..077222b 100644 --- a/src/pymonctl/_pymonctl_linux.py +++ b/src/pymonctl/_pymonctl_linux.py @@ -3,7 +3,8 @@ import sys -assert sys.platform == "linux" +if sys.platform != "linux": + raise OSError(f"Cannot import {__name__} on {sys.platform}") import math import os @@ -107,11 +108,11 @@ def _getMonitorsCount() -> int: def _findMonitor(x: int, y: int) -> list[LinuxMonitor]: - monitors = [] - for monitor in _XgetMonitors(): - if _pointInBox(x, y, monitor.x, monitor.y, monitor.width_in_pixels, monitor.height_in_pixels): - monitors.append(LinuxMonitor(monitor.crtcs[0])) - return monitors + return [ + LinuxMonitor(monitor.crtcs[0]) + for monitor in _XgetMonitors() + if _pointInBox(x, y, monitor.x, monitor.y, monitor.width_in_pixels, monitor.height_in_pixels) + ] def _getPrimary() -> LinuxMonitor: @@ -377,9 +378,9 @@ def dpi(self) -> tuple[float, float] | None: monitors = _XgetMonitors(self.name) if monitors: monitor = monitors[0] - _x, _y, w, h = monitor.x, monitor.y, monitor.width_in_pixels, monitor.height_in_pixels if monitor.width_in_millimeters != 0 and monitor.height_in_millimeters != 0: - dpiX, dpiY = round((w * 25.4) / monitor.width_in_millimeters), round((h * 25.4) / monitor.height_in_millimeters) + dpiX = round((monitor.width_in_pixels * 25.4) / monitor.width_in_millimeters), + dpiY = round((monitor.height_in_pixels * 25.4) / monitor.height_in_millimeters) else: dpiX, dpiY = 0.0, 0.0 return dpiX, dpiY @@ -1001,7 +1002,7 @@ def _XgetMonitorData(handle: int | None = None) -> tuple[Xlib.display.Display, S for monitorData in _XgetAllMonitors(): display, screen, root, monitor, monName = monitorData output = monitor.crtcs[0] - if (handle and handle == output) or (not handle and monitor.primary == 1): + if (handle == output if handle else monitor.primary == 1): return display, screen, root, monitor, output, monName return None diff --git a/src/pymonctl/_pymonctl_macos.py b/src/pymonctl/_pymonctl_macos.py index d0c1ef8..2d75144 100644 --- a/src/pymonctl/_pymonctl_macos.py +++ b/src/pymonctl/_pymonctl_macos.py @@ -6,7 +6,9 @@ import ctypes import sys -assert sys.platform == "darwin" + +if sys.platform != "darwin": + raise OSError(f"Cannot import {__name__} on {sys.platform}") import subprocess import threading @@ -22,11 +24,8 @@ def _getAllMonitors() -> list[MacOSMonitor]: - monitors = [] v, ids, cnt = CG.CGGetOnlineDisplayList(10, None, None) # --> How to get display name from this? - for displayId in ids: - monitors.append(MacOSMonitor(displayId)) - return monitors + return [MacOSMonitor(displayId) for displayId in ids] def _getAllMonitorsDict() -> dict[str, ScreenValue]: @@ -617,7 +616,7 @@ def isOn(self) -> bool | None: return bool(CG.CGDisplayIsActive(self.handle) == 1) def suspend(self): - # Also injecting: Control–Shift–Media_Eject + # Also injecting: Control-Shift-Media_Eject cmd = "pmset displaysleepnow" try: _ = subprocess.run(cmd, text=True, shell=True, capture_output=True, timeout=1) diff --git a/src/pymonctl/_pymonctl_win.py b/src/pymonctl/_pymonctl_win.py index 4f1a873..58900a0 100644 --- a/src/pymonctl/_pymonctl_win.py +++ b/src/pymonctl/_pymonctl_win.py @@ -2,7 +2,9 @@ from __future__ import annotations import sys -assert sys.platform == "win32" + +if sys.platform != "win32": + raise OSError(f"Cannot import {__name__} on {sys.platform}") import gc import platform @@ -849,8 +851,7 @@ class _PHYSICAL_MONITOR(ctypes.Structure): physical_array = (_PHYSICAL_MONITOR * count.value)() ret = ctypes.windll.dxva2.GetPhysicalMonitorsFromHMONITOR(hMon, count.value, physical_array) if ret: - for physical in physical_array: - handles.append(physical.handle) + handles.extend([physical.handle for physical in physical_array]) return handles diff --git a/src/pymonctl/_structs.py b/src/pymonctl/_structs.py index 945d598..95be811 100644 --- a/src/pymonctl/_structs.py +++ b/src/pymonctl/_structs.py @@ -3,7 +3,7 @@ import sys from enum import IntEnum -from typing import NamedTuple, TypedDict +from typing import ClassVar, NamedTuple, TypedDict class Box(NamedTuple): @@ -111,14 +111,14 @@ class Orientation(IntEnum): # ==================================== PathsInfo class _DUMMYSTRUCTNAME(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('cloneGroupId', ctypes.c_uint32), ('sourceModeInfoIdx', ctypes.c_uint32) ] class _DUMMYUNIONNAME(ctypes.Union): - _fields_ = [ + _fields_: ClassVar = [ ('modeInfoIdx', ctypes.c_uint32), ('dummyStructName', _DUMMYSTRUCTNAME) ] @@ -126,14 +126,14 @@ class _DUMMYUNIONNAME(ctypes.Union): class _LUID(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('lowPart', ctypes.wintypes.DWORD), ('highPart', ctypes.wintypes.LONG) ] class _DISPLAYCONFIG_PATH_SOURCE_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('adapterId', _LUID), ('id', ctypes.c_uint32), ('dummyUnionName', _DUMMYUNIONNAME), @@ -142,14 +142,14 @@ class _DISPLAYCONFIG_PATH_SOURCE_INFO(ctypes.Structure): class _DISPLAYCONFIG_RATIONAL(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('numerator', ctypes.c_uint32), ('denominator', ctypes.c_uint32) ] class _DISPLAYCONFIG_PATH_TARGET_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('adapterId', _LUID), ('id', ctypes.c_uint32), ('dummyUnionName', _DUMMYUNIONNAME), @@ -164,7 +164,7 @@ class _DISPLAYCONFIG_PATH_TARGET_INFO(ctypes.Structure): class _DISPLAYCONFIG_PATH_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('sourceInfo', _DISPLAYCONFIG_PATH_SOURCE_INFO), ('targetInfo', _DISPLAYCONFIG_PATH_TARGET_INFO), ('flags', ctypes.c_uint32) @@ -174,14 +174,14 @@ class _DISPLAYCONFIG_PATH_INFO(ctypes.Structure): # ==================================== ModesInfo class _DISPLAYCONFIG_2DREGION(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('cx', ctypes.c_uint32), ('cy', ctypes.c_uint32) ] class _ADDITIONAL_SIGNAL_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('videoStandard', ctypes.c_uint32), ('vSyncFreqDivider', ctypes.c_uint32), ('reserved', ctypes.c_uint32) @@ -189,14 +189,14 @@ class _ADDITIONAL_SIGNAL_INFO(ctypes.Structure): class _DUMMYUNIONNAME_MODE_SIGNAL(ctypes.Union): - _fields_ = [ + _fields_: ClassVar = [ ('additionalSignalInfo', _ADDITIONAL_SIGNAL_INFO), ('videoStandard', ctypes.c_uint32) ] class _DISPLAYCONFIG_VIDEO_SIGNAL_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('pixelRate', ctypes.c_uint64), ('hSyncFreq', _DISPLAYCONFIG_RATIONAL), ('vSyncFreq', _DISPLAYCONFIG_RATIONAL), @@ -208,20 +208,20 @@ class _DISPLAYCONFIG_VIDEO_SIGNAL_INFO(ctypes.Structure): class _DISPLAYCONFIG_TARGET_MODE(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('targetVideoSignalInfo', _DISPLAYCONFIG_VIDEO_SIGNAL_INFO) ] class _POINTL(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('x', ctypes.wintypes.LONG), ('y', ctypes.wintypes.LONG) ] class _DISPLAYCONFIG_SOURCE_MODE(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('width', ctypes.c_uint32), ('height', ctypes.c_uint32), ('pixelFormat', ctypes.c_uint32), @@ -230,7 +230,7 @@ class _DISPLAYCONFIG_SOURCE_MODE(ctypes.Structure): class _RECTL(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('left', ctypes.c_uint32), ('top', ctypes.c_uint32), ('right', ctypes.c_uint32), @@ -239,7 +239,7 @@ class _RECTL(ctypes.Structure): class _DISPLAYCONFIG_DESKTOP_IMAGE_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('pathSourceSize', _POINTL), ('desktopImageRegion', _RECTL), ('desktopImageClip', _RECTL) @@ -247,7 +247,7 @@ class _DISPLAYCONFIG_DESKTOP_IMAGE_INFO(ctypes.Structure): class _DUMMYUNIONNAME_MODE(ctypes.Union): - _fields_ = [ + _fields_: ClassVar = [ ('targetMode', _DISPLAYCONFIG_TARGET_MODE), ('sourceMode', _DISPLAYCONFIG_SOURCE_MODE), ('desktopImageInfo', _DISPLAYCONFIG_DESKTOP_IMAGE_INFO) @@ -255,7 +255,7 @@ class _DUMMYUNIONNAME_MODE(ctypes.Union): class _DISPLAYCONFIG_MODE_INFO(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('infoType', ctypes.c_uint32), ('id', ctypes.c_uint32), ('adapterId', _LUID), @@ -274,7 +274,7 @@ class _DISPLAYCONFIG_MODE_INFO(ctypes.Structure): class _DISPLAYCONFIG_DEVICE_INFO_HEADER(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('type', ctypes.c_uint32), ('size', ctypes.c_uint32), ('adapterId', _LUID), @@ -286,7 +286,7 @@ class _DISPLAYCONFIG_DEVICE_INFO_HEADER(ctypes.Structure): class _DISPLAYCONFIG_SOURCE_DPI_SCALE_GET(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('header', _DISPLAYCONFIG_DEVICE_INFO_HEADER), ('minScaleRel', ctypes.c_uint32), ('curScaleRel', ctypes.c_uint32), @@ -298,7 +298,7 @@ class _DISPLAYCONFIG_SOURCE_DPI_SCALE_GET(ctypes.Structure): class _DISPLAYCONFIG_SOURCE_DPI_SCALE_SET(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('header', _DISPLAYCONFIG_DEVICE_INFO_HEADER), ('scaleRel', ctypes.c_uint32) ] @@ -308,7 +308,7 @@ class _DISPLAYCONFIG_SOURCE_DPI_SCALE_SET(ctypes.Structure): class _DISPLAY_CONFIG_TARGET_DEVICE_NAME(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('header', _DISPLAYCONFIG_DEVICE_INFO_HEADER), ('flags', ctypes.c_uint32), ('outputTechnology', ctypes.c_uint32), @@ -324,7 +324,7 @@ class _DISPLAY_CONFIG_TARGET_DEVICE_NAME(ctypes.Structure): class _DISPLAYCONFIG_SOURCE_DEVICE_NAME(ctypes.Structure): - _fields_ = [ + _fields_: ClassVar = [ ('header', _DISPLAYCONFIG_DEVICE_INFO_HEADER), ('viewGdiDeviceName', ctypes.wintypes.WCHAR * 32) ] diff --git a/typings/Quartz/__init__.pyi b/typings/Quartz/__init__.pyi index 862d4c2..0473929 100644 --- a/typings/Quartz/__init__.pyi +++ b/typings/Quartz/__init__.pyi @@ -19,4 +19,4 @@ from Quartz.QuartzCore import * from Quartz.QuartzFilters import * from Quartz.QuickLookUI import * -def __getattr__(__name: str) -> Any: ... +def __getattr__(name: str, /) -> Any: ...