From a4e55fae7aea3744c9aaaa0ef58e1413334b5e1a Mon Sep 17 00:00:00 2001 From: Avasam Date: Wed, 24 Jun 2026 19:28:55 -0400 Subject: [PATCH] Allow getPrimary to return None --- CHANGES.txt | 1 + src/pymonctl/_main.py | 35 +++++++++++++++++++++++---------- src/pymonctl/_pymonctl_linux.py | 5 +---- src/pymonctl/_pymonctl_macos.py | 4 ---- src/pymonctl/_pymonctl_win.py | 4 ---- tests/test_pymonctl.py | 4 ++-- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ab9fe4a..2ca2ba0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ 0.93, 2026/XX/XX -- ALL: Replaced bare `except:` clauses with `except Exception:`, preventing accidental suppression of system-exit signals (KeyboardInterrupt, SystemExit) during error handling. ALL: Removed dependency on `typing_extensions` + ALL: Allow `getPrimary` to return `None` if the underlying `Monitor()` call throws a `ValueError` (no primary monitor) 0.8, 2023/10/11 -- ALL: Added saveSetup() and restoreSetup(). Fixed / Improved watchdog (especially in Linux). Fixed / improved setPosition() method LINUX: Added ewmhlib as separate module. Fixed watchdog (freezing randomly invoking screen_resources and get_output_info), fixed workarea crash (some apps/environments do not set it), improved to work almost fine in Manjaro/KDE, avoid crashing in Wayland for "fake" :1 display (though module won't likely work) WIN32: Fixed dev.StateFlags returning weird values for multi-monitor. Fixed GetAwarenessFromDpiAwarenessContext not supported on Windows Server diff --git a/src/pymonctl/_main.py b/src/pymonctl/_main.py index 1cd6dcd..227705a 100644 --- a/src/pymonctl/_main.py +++ b/src/pymonctl/_main.py @@ -6,7 +6,7 @@ import threading from abc import abstractmethod, ABC from collections.abc import Callable -from typing import List, Optional, Union, Tuple, cast +from typing import List, Optional, Union, Tuple from ._structs import DisplayMode, ScreenValue, Size, Point, Box, Rect, Position, Orientation @@ -95,14 +95,20 @@ def getMonitorsCount() -> int: """ return _getMonitorsCount() - -def getPrimary() -> Monitor: +def getPrimary() -> Monitor | None: """ - Get primary monitor instance. This is equivalent to invoking ''Monitor()'', with empty input params. + Get primary monitor instance. + + This is equivalent to invoking `Monitor()`, with empty input params. + With a fallback to None in case of `ValueError`. + Which usually means lack of primary (or any) monitor. :return: Monitor instance or None """ - return _getPrimary() + try: + return Monitor() + except ValueError: + return None def findMonitorsAtPoint(x: int, y: int) -> List[Monitor]: @@ -197,6 +203,15 @@ def arrangeMonitors(arrangement: dict[str, dict[str, Optional[Union[str, int, Po _arrangeMonitors(arrangement) +def getMousePos() -> Point: + """ + Get the current (x, y) coordinates of the mouse pointer on screen, in pixels + + :return: Point struct + """ + return _getMousePos() + + def saveSetup() -> List[Tuple[Monitor, ScreenValue]]: """ Save current monitors setup information to be restored afterward. @@ -1002,16 +1017,16 @@ def _getRelativePosition(monitor, relativeTo) -> Tuple[int, int]: if sys.platform == "darwin": - from ._pymonctl_macos import (_getAllMonitors, _getAllMonitorsDict, _getMonitorsCount, _getPrimary, - _findMonitor, _arrangeMonitors, _getMousePos as getMousePos, MacOSMonitor as Monitor + from ._pymonctl_macos import (_getAllMonitors, _getAllMonitorsDict, _getMonitorsCount, + _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 + from ._pymonctl_win import (_getAllMonitors, _getAllMonitorsDict, _getMonitorsCount, + _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, _findMonitor, _arrangeMonitors, _getMousePos, LinuxMonitor as Monitor ) else: diff --git a/src/pymonctl/_pymonctl_linux.py b/src/pymonctl/_pymonctl_linux.py index afc00fd..afbaecb 100644 --- a/src/pymonctl/_pymonctl_linux.py +++ b/src/pymonctl/_pymonctl_linux.py @@ -113,10 +113,6 @@ def _findMonitor(x: int, y: int) -> List[LinuxMonitor]: return monitors -def _getPrimary() -> LinuxMonitor: - return LinuxMonitor() - - def _arrangeMonitors(arrangement: dict[str, dict[str, Optional[Union[str, int, Position, Point, Size]]]]): monitors = _XgetMonitorsDict() @@ -223,6 +219,7 @@ def __init__(self, handle: Optional[int] = None): getPrimary() or findMonitor(x, y). It can raise ValueError exception in case provided handle is not valid + or there's no primary monitor with no provided handle. """ monitorData = _XgetMonitorData(handle) if monitorData: diff --git a/src/pymonctl/_pymonctl_macos.py b/src/pymonctl/_pymonctl_macos.py index 4b90719..8bb1e83 100644 --- a/src/pymonctl/_pymonctl_macos.py +++ b/src/pymonctl/_pymonctl_macos.py @@ -78,10 +78,6 @@ def _findMonitor(x: int, y: int) -> List[MacOSMonitor]: return [MacOSMonitor(displayId) for displayId in ids] -def _getPrimary() -> MacOSMonitor: - return MacOSMonitor() - - def _arrangeMonitors(arrangement: dict[str, dict[str, Optional[Union[str, int, Position, Point, Size]]]]): monitors = _NSgetAllMonitorsDict() diff --git a/src/pymonctl/_pymonctl_win.py b/src/pymonctl/_pymonctl_win.py index 88f0713..a3aa5e3 100644 --- a/src/pymonctl/_pymonctl_win.py +++ b/src/pymonctl/_pymonctl_win.py @@ -111,10 +111,6 @@ def _findMonitor(x: int, y: int) -> List[Win32Monitor]: return [] -def _getPrimary() -> Win32Monitor: - return Win32Monitor() - - def _arrangeMonitors(arrangement: dict[str, dict[str, Optional[Union[str, int, Position, Point, Size]]]]): # https://stackoverflow.com/questions/35814309/winapi-changedisplaysettingsex-does-not-work # https://stackoverflow.com/questions/195267/use-windows-api-from-c-sharp-to-set-primary-monitor diff --git a/tests/test_pymonctl.py b/tests/test_pymonctl.py index 43987fd..0c43789 100644 --- a/tests/test_pymonctl.py +++ b/tests/test_pymonctl.py @@ -7,7 +7,6 @@ import pymonctl as pmc - _TIMELAP = 5 @@ -26,7 +25,8 @@ def changedCB(names: List[str], info: dict[str, pmc.ScreenValue]): print("MONITORS COUNT:", pmc.getMonitorsCount()) -print("PRIMARY MONITOR:", pmc.getPrimary().name) +primary = pmc.getPrimary() +print("PRIMARY MONITOR:", primary and primary.name) print() monDict = pmc.getAllMonitorsDict() for mon in monDict: