diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 188af8b..84c2c21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,10 +24,17 @@ concurrency: cancel-in-progress: true jobs: + ruff: + runs-on: ubuntu-latest + timeout-minutes: &timeout-minutes 5 + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/ruff-action@v4.0.0 + mypy: # Arm runners are faster (as long as the same wheels are available) runs-on: ubuntu-24.04-arm - timeout-minutes: &timeout-minutes 5 + timeout-minutes: *timeout-minutes strategy: # mypy is os and python-version sensitive. Test on all supported combinations matrix: @@ -41,3 +48,44 @@ jobs: activate-environment: true - run: uv sync --locked - run: mypy . --python-version=${{ matrix.python-version }} + + tests: + # Arm runners are faster (as long as the same wheels are available) + # This project doesn't have any code that should act differently per architecture + runs-on: ubuntu-24.04-arm + timeout-minutes: *timeout-minutes + strategy: + # Test on all supported runtime combinations + matrix: + # TODO: Run tests in parallel on free-threaded python to catch free-threading issues + # See: https://py-free-threading.github.io/testing/ + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + fail-fast: false + steps: + - uses: actions/checkout@v6 + - name: Install Linux Packages + run: | + sudo apt update + sudo apt install xvfb openbox + - uses: astral-sh/setup-uv@v8.2.0 + with: + python-version: ${{ matrix.python-version }} + activate-environment: true + - run: uv sync --locked + - name: Run tests + working-directory: tests + run: xvfb-run --server-args="-screen 0 1280x1024x24" bash -c "openbox & sleep 1 && python test_ewmhlib.py" + + sphinx: + runs-on: ubuntu-24.04-arm # Keep in sync with build.os in .readthedocs.yaml + timeout-minutes: *timeout-minutes + steps: + - uses: actions/checkout@v6 + - uses: astral-sh/setup-uv@v8.2.0 + with: + python-version: "3.14" # Keep in sync with build.tools.python in .readthedocs.yaml + activate-environment: true + - run: uv sync --locked --no-default-groups --group=docs + - name: Build docs + # TODO: Add --fail-on-warning, but still too many warnings right now + run: sphinx-build --keep-going --builder html docs/source docs/_build/html diff --git a/docstrings.md b/docstrings.md index 79b5380..390af99 100644 --- a/docstrings.md +++ b/docstrings.md @@ -714,8 +714,8 @@ def setClosed(winId: int, userAction: bool = True) Close target window -The Window Manager MUST then attempt to close the window specified. See the section called “Source indication -in requests” for details on the source indication. +The Window Manager MUST then attempt to close the window specified. See the section called “Source indication +in requests” for details on the source indication. Rationale: A Window Manager might be more clever than the usual method (send WM_DELETE message if the protocol is selected, XKillClient otherwise). It might introduce a timeout, for example. Instead of duplicating the @@ -750,8 +750,8 @@ A gravity of 0 indicates that the Window Manager should use the gravity specifie The bits 8 to 11 indicate the presence of x, y, width and height. -The bits 12 to 15 indicate the source (see the section called “Source indication -in requests”), so 0001 indicates the application and 0010 indicates a Pager or a Taskbar. +The bits 12 to 15 indicate the source (see the section called “Source indication +in requests”), so 0001 indicates the application and 0010 indicates a Pager or a Taskbar. The remaining bits should be set to zero. @@ -791,8 +791,8 @@ def setWmMoveResize(winId: int, This message allows Clients to initiate window movement or resizing. They can define their own move and size "grips", whilst letting the Window Manager control the actual operation. This means that all moves/resizes -can happen in a consistent manner as defined by the Window Manager. See the section called “Source indication -in requests” for details on the source indication. +can happen in a consistent manner as defined by the Window Manager. See the section called “Source indication +in requests” for details on the source indication. When sending this message in response to a button press event, button SHOULD indicate the button which was pressed, x_root and y_root MUST indicate the position of the button press with respect to the root window @@ -830,7 +830,7 @@ def setWmStacking(winId: int, This request is similar to ConfigureRequest with CWSibling and CWStackMode flags. It should be used only by pagers, applications can use normal ConfigureRequests. The source indication field should be therefore -set to 2, see the section called “Source indication in requests” for details. +set to 2, see the section called “Source indication in requests” for details. To obtain good interoperability between different Desktop Environments, the following layered stacking order is recommended, from the bottom: @@ -1394,14 +1394,14 @@ decorations. Additionally, the Window Manager is responsible for restoring the o a switch from fullscreen back to normal window. For example, a presentation program would use this hint. _NET_WM_STATE_ABOVE indicates that the window should be on top of most windows (see the section called -“Stacking order” for details). +“Stacking order” for details). _NET_WM_STATE_BELOW indicates that the window should be below most windows (see the section called -“Stacking order” for details). +“Stacking order” for details). _NET_WM_STATE_ABOVE and _NET_WM_STATE_BELOW are mainly meant for user preferences and should not be used by applications e.g. for drawing attention to their dialogs (the Urgency hint should be used in -that case, see the section called “Urgency”).' +that case, see the section called “Urgency”).' _NET_WM_STATE_DEMANDS_ATTENTION indicates that some action in or with the window happened. For example, it may be set by the Window Manager if the window requested activation but the Window Manager refused it, @@ -1434,7 +1434,7 @@ See setWmState() documentation for more information on Window States. This message allows two properties to be changed simultaneously, specifically to allow both horizontal and vertical maximization to be altered together. l[2] MUST be set to zero if only one property is to -be changed. See the section called “Source indication in requests” for details on the source indication. +be changed. See the section called “Source indication in requests” for details on the source indication. l[0], the action, MUST be one of: _NET_WM_STATE_REMOVE 0 # remove/unset property @@ -1656,8 +1656,8 @@ If set, this property MUST contain the process ID of the client owning this wind the Window Manager to kill windows which do not respond to the _NET_WM_PING protocol. If _NET_WM_PID is set, the ICCCM-specified property WM_CLIENT_MACHINE MUST also be set. While the ICCCM -only requests that WM_CLIENT_MACHINE is set “ to a string that forms the name of the machine running the -client as seen from the machine running the server” conformance to this specification requires that +only requests that WM_CLIENT_MACHINE is set “ to a string that forms the name of the machine running the +client as seen from the machine running the server” conformance to this specification requires that WM_CLIENT_MACHINE be set to the fully-qualified domain name of the client's host. **Returns**: @@ -1789,8 +1789,8 @@ def setActive(userAction: bool = True) Set current window as active (focused). Source indication should be 1 when the request comes from an application, and 2 when it comes from a pager. -Clients using older version of this spec use 0 as source indication, see the section called “Source indication -in requests” for details. The timestamp is Client's last user activity timestamp (see _NET_WM_USER_TIME) at +Clients using older version of this spec use 0 as source indication, see the section called “Source indication +in requests” for details. The timestamp is Client's last user activity timestamp (see _NET_WM_USER_TIME) at the time of the request, and the currently active window is the Client's active toplevel window, if any (the Window Manager may be e.g. more likely to obey the request if it will mean transferring focus from one active window to another). @@ -1812,8 +1812,8 @@ def setClosed(userAction: bool = True) Request to close current window. -The Window Manager MUST then attempt to close the window specified. See the section called “Source -indication in requests” for details on the source indication. +The Window Manager MUST then attempt to close the window specified. See the section called “Source +indication in requests” for details on the source indication. Rationale: A Window Manager might be more clever than the usual method (send WM_DELETE message if the protocol is selected, XKillClient otherwise). It might introduce a timeout, for example. Instead of @@ -1896,8 +1896,8 @@ A gravity of 0 indicates that the Window Manager should use the gravity specifie The bits 8 to 11 indicate the presence of x, y, width and height. -The bits 12 to 15 indicate the source (see the section called “Source indication -in requests”), so 0001 indicates the application and 0010 indicates a Pager or a Taskbar. +The bits 12 to 15 indicate the source (see the section called “Source indication +in requests”), so 0001 indicates the application and 0010 indicates a Pager or a Taskbar. The remaining bits should be set to zero. @@ -1935,8 +1935,8 @@ def setWmMoveResize(x_root: int, This message allows Clients to initiate window movement or resizing. They can define their own move and size "grips", whilst letting the Window Manager control the actual operation. This means that all moves/resizes -can happen in a consistent manner as defined by the Window Manager. See the section called “Source indication -in requests” for details on the source indication. +can happen in a consistent manner as defined by the Window Manager. See the section called “Source indication +in requests” for details on the source indication. When sending this message in response to a button press event, button SHOULD indicate the button which was pressed, x_root and y_root MUST indicate the position of the button press with respect to the root window @@ -1970,7 +1970,7 @@ def setWmStacking(siblingId: int, detail: int, userAction: bool = True) This request is similar to ConfigureRequest with CWSibling and CWStackMode flags. It should be used only by pagers, applications can use normal ConfigureRequests. The source indication field should be therefore -set to 2, see the section called “Source indication in requests” for details. +set to 2, see the section called “Source indication in requests” for details. Rationale: A Window Manager may put restrictions on configure requests from applications, for example it may under some conditions refuse to raise a window. This request makes it clear it comes from a pager or similar diff --git a/pyproject.toml b/pyproject.toml index de23653..a5232df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ name = "EWMHlib" version = "0.2" description = "Extended Window Manager Hints implementation in Python 3" authors = [ - { name = "Kalmat", email = "palookjones@gmail.com" } + { name = "Kalmat", email = "palookjones@gmail.com" }, ] readme = "README.md" license = "BSD-3-Clause" @@ -54,6 +54,7 @@ docs = [ ] dev = [ { include-group = "docs" }, + "ruff>=0.15.16", "mypy>=0.990,<2", "types-python-xlib>=0.32", ] diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..1b79097 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,91 @@ +[lint] +future-annotations = true +# https://docs.astral.sh/ruff/rules/ +extend-select = [ + "ANN2", # flake8-annotations: missing-return-type + "C4", # flake8-comprehensions + "F401", # unused-import + "F404", # late-future-import + "FA", # flake8-future-annotations + "FLY", # Flynt + "ICN", # flake8-import-conventions + "PERF", # Perflint + "PGH", # pygrep-hooks (blanket-* rules) + "PYI", # flake8-pyi + "RUF", # Ruff-specific rules + "SIM9", # flake8-simplify: split-static-string + zip-dict-keys-and-values + "SLOT", # flake8-slots + "TC", # flake8-type-checking + "TID", # flake8-tidy-imports + "UP", # pyupgrade + "YTT", # flake8-2020 +] +ignore = [ + # Only enforce return types on public functions. Where otherwise mypy infers as Any + # Still worth running `ruff check --fix --select=202` once in a while for autofixes + "ANN202", # missing-return-type-private-function + # Explicit is preferred + "UP015", # redundant-open-modes, + # Autofixes print-f style formatting to f-strings, + # which is sometimes simpler, but looses template code reading semantics + "UP032", # f-string + # TC helps prevent circular imports, reduce runtime cost of typing symbols, + # and prevent leaking implementations details into modules + # However stdlib is not at risk of circular import, is clearly not public API, + # and assume it's gonna be included in the import chain at some point anyway + "TC003", # typing-only-standard-library-import + # Typeshed doesn't want complex or non-literal defaults for maintenance and testing reasons. + # This doesn't affect us, let's have more complete stubs. + "PYI011", # typed-argument-default-in-stub + "PYI014", # argument-default-in-stub + "PYI053", # string-or-bytes-too-long + # Good to watch for, but often unfixable + # Anyway Python 3.11 introduced "zero cost" exception handling + "PERF203", # try-except-in-loop, + + # TODO: Add and configure isort (I) first + "RUF022", + + # TODO: Consider later + "UP031", # printf-string-formatting + "RUF059", # unused-unpacked-variable + + # TODO: Not autofixable, address in a separate PR ! + "E722", + "E402", + "F401", + "ANN201", +] +# F401 would remove imports not marked as explicit re-exports, which may break API boundaries +extend-unsafe-fixes = ["F401"] + +[lint.per-file-ignores] +"**/typings/**/*.pyi" = [ + "E402", # https://github.com/astral-sh/ruff/issues/26160 + "F811", # Re-exports false positives + # The following can't be controlled for external libraries: + "A", # Shadowing builtin names + "E741", # ambiguous variable name + "F403", # `from . import *` used; unable to detect undefined names + "FBT", # flake8-boolean-trap + "ICN001", # unconventional-import-alias + "N8", # Naming conventions + "PLC2701", # Private name import + "PLE0302", # The special method expects a given signature + "PLR0904", # Too many public methods + "PLR0913", # Argument count + "PLR0917", # Too many positional arguments + "PLW3201", # misspelled dunder method name + "SLOT", # flake8-slots + # Stubs can sometimes re-export entire modules. + # Issues with using a star-imported name will be caught by type-checkers. + "F405", # may be undefined, or defined from star imports + # It's normal to be missing annotations for local stubs. + # If they were complete, we'd upload them to typeshed! + "ANN0", + "ANN2", +] + +# https://docs.astral.sh/ruff/settings/#lintflake8-type-checking +[lint.flake8-type-checking] +quote-annotations = true diff --git a/src/ewmhlib/Props/__init__.py b/src/ewmhlib/Props/__init__.py index c38be67..e05ec79 100644 --- a/src/ewmhlib/Props/__init__.py +++ b/src/ewmhlib/Props/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- from ._props import (Root, DesktopLayout, Window, WindowType, State, StateAction, MoveResize, DataFormat, Mode, StackMode, HintAction) diff --git a/src/ewmhlib/Props/_props.py b/src/ewmhlib/Props/_props.py index 9ef146f..61bb65d 100644 --- a/src/ewmhlib/Props/_props.py +++ b/src/ewmhlib/Props/_props.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- from enum import IntEnum import Xlib.X diff --git a/src/ewmhlib/Structs/__init__.py b/src/ewmhlib/Structs/__init__.py index 8391b84..aec0fd5 100644 --- a/src/ewmhlib/Structs/__init__.py +++ b/src/ewmhlib/Structs/__init__.py @@ -1,4 +1,3 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- from ._structs import (ScreensInfo, DisplaysInfo, WmHints, Aspect, WmNormalHints) diff --git a/src/ewmhlib/Structs/_structs.py b/src/ewmhlib/Structs/_structs.py index 7ce4feb..3f5c309 100644 --- a/src/ewmhlib/Structs/_structs.py +++ b/src/ewmhlib/Structs/_structs.py @@ -1,13 +1,15 @@ #!/usr/bin/python -# -*- coding: utf-8 -*-#from typing import List +from __future__ import annotations from typing_extensions import TypedDict -from typing import List +from typing import TYPE_CHECKING from ctypes import Structure, c_int32, c_ulong, c_uint32 -import Xlib.xobject -from Xlib.protocol.rq import Struct -from Xlib.xobject.drawable import Window as XWindow + +if TYPE_CHECKING: + from Xlib.xobject.drawable import Window as XWindow + import Xlib.xobject + from Xlib.protocol.rq import Struct class ScreensInfo(TypedDict): @@ -36,7 +38,7 @@ class DisplaysInfo(TypedDict): display: Xlib.display.Display name: str is_default: bool - screens: List[ScreensInfo] + screens: list[ScreensInfo] """ diff --git a/src/ewmhlib/__init__.py b/src/ewmhlib/__init__.py index dcea897..389454e 100644 --- a/src/ewmhlib/__init__.py +++ b/src/ewmhlib/__init__.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- from importlib.metadata import version as _importlib_version __all__ = [ diff --git a/src/ewmhlib/_ewmhlib.py b/src/ewmhlib/_ewmhlib.py index 6d3da51..8ffea58 100644 --- a/src/ewmhlib/_ewmhlib.py +++ b/src/ewmhlib/_ewmhlib.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- from __future__ import annotations import sys @@ -9,11 +8,10 @@ import os import threading import time -from typing import Optional, cast, Callable, Union, List, Tuple +from typing import cast, Callable, TYPE_CHECKING import Xlib.display import Xlib.protocol -from Xlib.protocol.rq import Struct import Xlib.X import Xlib.Xatom import Xlib.Xutil @@ -25,23 +23,26 @@ MoveResize, DataFormat, Mode, HintAction) from .Structs import DisplaysInfo, ScreensInfo, WmHints, Aspect, WmNormalHints +if TYPE_CHECKING: + from Xlib.protocol.rq import Struct + defaultDisplay: Xlib.display.Display = Xlib.display.Display() defaultScreen: Struct = defaultDisplay.screen() defaultRoot: XWindow = defaultScreen.root -def _getDisplays() -> List[Xlib.display.Display]: +def _getDisplays() -> list[Xlib.display.Display]: """ Get a list of connections to all existing Displays. :return: list of display connections """ - displays: List[Xlib.display.Display] = [] + displays: list[Xlib.display.Display] = [] if os.environ.get('XDG_SESSION_TYPE', "").lower() != "wayland": # Wayland adds a "fake" display (typically ":1") that freezes when trying to get a connection. Using default # Thanks to SamuMazzi - https://github.com/SamuMazzi for pointing out this issue - files: List[str] = os.listdir("/tmp/.X11-unix") + files: list[str] = os.listdir("/tmp/.X11-unix") for d in files: if d.startswith("X"): name: str = d.replace("X", ":", 1) @@ -52,11 +53,11 @@ def _getDisplays() -> List[Xlib.display.Display]: if not displays: displays = [defaultDisplay] return displays -_displays: List[Xlib.display.Display] = _getDisplays() +_displays: list[Xlib.display.Display] = _getDisplays() displaysCount: int = len(_displays) -def getDisplays() -> List[Xlib.display.Display]: +def getDisplays() -> list[Xlib.display.Display]: global _displays return _displays @@ -81,7 +82,7 @@ def getDisplaysInfo() -> dict[str, DisplaysInfo]: dspInfo: dict[str, DisplaysInfo] = {} for display in getDisplays(): name = display.get_display_name() - screens: List[ScreensInfo] = [] + screens: list[ScreensInfo] = [] for s in range(display.screen_count()): try: screen: Struct = display.screen(s) @@ -105,7 +106,7 @@ def getDisplaysInfo() -> dict[str, DisplaysInfo]: return dspInfo -def getDisplayFromWindow(winId: int) -> Tuple[Xlib.display.Display, Struct, XWindow]: +def getDisplayFromWindow(winId: int) -> tuple[Xlib.display.Display, Struct, XWindow]: """ Get display connection, screen and root window from a given window id to which it belongs @@ -117,7 +118,7 @@ def getDisplayFromWindow(winId: int) -> Tuple[Xlib.display.Display, Struct, XWin for rootData in rootsInfo: display, screen, root = rootData atom: int = display.get_atom(Root.CLIENT_LIST) - ret: Optional[Xlib.protocol.request.GetProperty] = root.get_full_property(atom, Xlib.X.AnyPropertyType) + ret: Xlib.protocol.request.GetProperty | None = root.get_full_property(atom, Xlib.X.AnyPropertyType) if ret and hasattr(ret, "value") and winId in ret.value: return display, screen, root return defaultDisplay, defaultScreen, defaultRoot @@ -125,7 +126,7 @@ def getDisplayFromWindow(winId: int) -> Tuple[Xlib.display.Display, Struct, XWin getRootFromWindow = getDisplayFromWindow -def getDisplayFromRoot(rootId: Optional[int]) -> Tuple[Xlib.display.Display, Struct, XWindow]: +def getDisplayFromRoot(rootId: int | None) -> tuple[Xlib.display.Display, Struct, XWindow]: """ Get display connection, screen and root window from a given root id to which it belongs. For default root, this is not needed. Use defaultDisplay, defaultScreen and defaultRoot instead. @@ -143,15 +144,15 @@ def getDisplayFromRoot(rootId: Optional[int]) -> Tuple[Xlib.display.Display, Str getScreenFromRoot = getDisplayFromRoot -def _getRoots(updateDisplays: bool = False) -> List[Tuple[Xlib.display.Display, Struct, XWindow]]: - rootsInfo: List[Tuple[Xlib.display.Display, Struct, XWindow]] = [(defaultDisplay, defaultScreen, defaultRoot)] +def _getRoots(updateDisplays: bool = False) -> list[tuple[Xlib.display.Display, Struct, XWindow]]: + rootsInfo: list[tuple[Xlib.display.Display, Struct, XWindow]] = [(defaultDisplay, defaultScreen, defaultRoot)] global displaysCount if updateDisplays or displaysCount > 1 or defaultDisplay.screen_count() > 1: if updateDisplays: display = Xlib.display.Display() screen: Struct = display.screen() rootsInfo = [(display, screen, screen.root)] - displays: List[Xlib.display.Display] = _getDisplays() + displays: list[Xlib.display.Display] = _getDisplays() else: displays = getDisplays() for display in displays: @@ -164,10 +165,10 @@ def _getRoots(updateDisplays: bool = False) -> List[Tuple[Xlib.display.Display, except Exception: pass return rootsInfo -_roots: List[Tuple[Xlib.display.Display, Struct, XWindow]] = _getRoots() +_roots: list[tuple[Xlib.display.Display, Struct, XWindow]] = _getRoots() -def getRoots() -> List[Tuple[Xlib.display.Display, Struct, XWindow]]: +def getRoots() -> list[tuple[Xlib.display.Display, Struct, XWindow]]: """ Get root windows objects. @@ -177,9 +178,9 @@ def getRoots() -> List[Tuple[Xlib.display.Display, Struct, XWindow]]: return _roots -def getProperty(window: XWindow, prop: Union[str, int], +def getProperty(window: XWindow, prop: str | int, prop_type: int = Xlib.X.AnyPropertyType, - display: Xlib.display.Display = defaultDisplay) -> Optional[Xlib.protocol.request.GetProperty]: + display: Xlib.display.Display = defaultDisplay) -> Xlib.protocol.request.GetProperty | None: """ Get given window/root property @@ -196,7 +197,7 @@ def getProperty(window: XWindow, prop: Union[str, int], return None -def changeProperty(window: XWindow, prop: Union[str, int], data: Union[List[int], str], +def changeProperty(window: XWindow, prop: str | int, data: list[int] | str, prop_type: int = Xlib.Xatom.ATOM, propMode: int = Xlib.X.PropModeReplace, display: Xlib.display.Display = defaultDisplay): """ @@ -224,7 +225,7 @@ def changeProperty(window: XWindow, prop: Union[str, int], data: Union[List[int] display.flush() -def sendMessage(winId: int, prop: Union[str, int], data: Union[List[int], str], +def sendMessage(winId: int, prop: str | int, data: list[int] | str, display: Xlib.display.Display = defaultDisplay, root: XWindow = defaultRoot): """ Send Client Message to given window/root @@ -252,8 +253,8 @@ def sendMessage(winId: int, prop: Union[str, int], data: Union[List[int], str], display.flush() -def getPropertyValue(prop: Optional[Xlib.protocol.request.GetProperty], text: bool = False, - display: Xlib.display.Display = defaultDisplay) -> Optional[Union[List[int], List[str]]]: +def getPropertyValue(prop: Xlib.protocol.request.GetProperty | None, text: bool = False, + display: Xlib.display.Display = defaultDisplay) -> list[int] | list[str] | None: """ Extract data from retrieved window/root property @@ -265,7 +266,7 @@ def getPropertyValue(prop: Optional[Xlib.protocol.request.GetProperty], text: bo if prop and hasattr(prop, "value"): # Value is either str, bytes (separated by '\x00' when multiple values) or array.array of integers. # The type of array values is stored in array.typecode ('I' in this case). - valueData: Union[str, int, bytes, array.array[int], List[str], List[int]] = prop.value + valueData: str | int | bytes | array.array[int] | list[str] | list[int] = prop.value if isinstance(valueData, str): return [valueData] elif isinstance(valueData, int): @@ -277,7 +278,7 @@ def getPropertyValue(prop: Optional[Xlib.protocol.request.GetProperty], text: bo pass return [result] elif isinstance(valueData, bytes): - resultStr: List[str] = [a for a in valueData.decode().split("\x00") if a] + resultStr: list[str] = [a for a in valueData.decode().split("\x00") if a] return resultStr elif isinstance(valueData, array.array) or isinstance(valueData, list): if text: @@ -287,7 +288,7 @@ def getPropertyValue(prop: Optional[Xlib.protocol.request.GetProperty], text: bo except Exception: text = False if not text: - resultInt: List[int] = [a for a in valueData if isinstance(a, int)] + resultInt: list[int] = [a for a in valueData if isinstance(a, int)] return resultInt return None @@ -321,15 +322,15 @@ class EwmhRoot: WM_PROTOCOLS messages (PING/SYNC) are accessible using wmProtocols subclass (EwmhRoot.wmProtocols.Ping/Sync) """ - def __init__(self, root: Optional[Union[XWindow, int]] = None): + def __init__(self, root: XWindow | int | None = None) -> None: if root and isinstance(root, XWindow): root = root.id - self.display, self.screen, self.root = getDisplayFromRoot(cast(Optional[int], root)) + self.display, self.screen, self.root = getDisplayFromRoot(cast("int | None", root)) self.id = self.root.id self.wmProtocols = self._WmProtocols(self.display, self.root) - def getProperty(self, prop: Union[str, int], prop_type: int = Xlib.X.AnyPropertyType) -> Optional[Xlib.protocol.request.GetProperty]: + def getProperty(self, prop: str | int, prop_type: int = Xlib.X.AnyPropertyType) -> Xlib.protocol.request.GetProperty | None: """ Retrieves given property from root @@ -341,7 +342,7 @@ def getProperty(self, prop: Union[str, int], prop_type: int = Xlib.X.AnyProperty prop = self.display.get_atom(prop) return getProperty(self.root, prop, prop_type, self.display) - def setProperty(self, prop: Union[str, int], data: Union[List[int], str]): + def setProperty(self, prop: str | int, data: list[int] | str): """ Sets the given property for root @@ -352,7 +353,7 @@ def setProperty(self, prop: Union[str, int], data: Union[List[int], str]): """ sendMessage(self.root.id, prop, data, self.display, self.root) - def sendMessage(self, winId: int, prop: Union[str, int], data: Union[List[int], str]): + def sendMessage(self, winId: int, prop: str | int, data: list[int] | str): """ Sends a ClientMessage event to given window @@ -362,7 +363,7 @@ def sendMessage(self, winId: int, prop: Union[str, int], data: Union[List[int], """ sendMessage(winId, prop, data, self.display, self.root) - def getSupportedHints(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: + def getSupportedHints(self, text: bool = False) -> list[int] | list[str] | None: """ Returns the list of supported hints by the Window Manager. @@ -376,7 +377,7 @@ def getSupportedHints(self, text: bool = False) -> Optional[Union[List[int], Lis """ return getPropertyValue(self.getProperty(Root.SUPPORTED), text, self.display) - def getClientList(self) -> Optional[List[int]]: + def getClientList(self) -> list[int] | None: """ Returns the list of XWindows currently opened and managed by the Window Manager, ordered older-to-newer. @@ -385,13 +386,13 @@ def getClientList(self) -> Optional[List[int]]: :return: list of integers (XWindows id's) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.CLIENT_LIST) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.CLIENT_LIST) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getClientListStacking(self) -> Optional[List[int]]: + def getClientListStacking(self) -> list[int] | None: """ Returns the list of XWindows currently opened and managed by the Window Manager, ordered in bottom-to-top. @@ -400,20 +401,20 @@ def getClientListStacking(self) -> Optional[List[int]]: :return: list of integers (XWindows id's) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.CLIENT_LIST_STACKING) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.CLIENT_LIST_STACKING) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getNumberOfDesktops(self) -> Optional[int]: + def getNumberOfDesktops(self) -> int | None: """ This property SHOULD be set and updated by the Window Manager to indicate the number of virtual desktops. :return: number of desktops in int format or None """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.NUMBER_OF_DESKTOPS) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.NUMBER_OF_DESKTOPS) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res and isinstance(res[0], int): return res[0] return None @@ -439,7 +440,7 @@ def setNumberOfDesktops(self, number: int): """ self.setProperty(Root.NUMBER_OF_DESKTOPS, [number]) - def getDesktopGeometry(self) -> Optional[List[int]]: + def getDesktopGeometry(self) -> list[int] | None: """ Array of two cardinals that defines the common size of all desktops (this is equal to the screen size if the Window Manager doesn't support large desktops, otherwise it's equal to the virtual size of the desktop). @@ -447,10 +448,10 @@ def getDesktopGeometry(self) -> Optional[List[int]]: :return: tuple of integers (width, height) or None if it couldn't be retrieved """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_GEOMETRY) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.DESKTOP_GEOMETRY) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res def setDesktopGeometry(self, newWidth: int, newHeight: int): @@ -470,20 +471,20 @@ def setDesktopGeometry(self, newWidth: int, newHeight: int): """ self.setProperty(Root.DESKTOP_GEOMETRY, [newWidth, newHeight]) - def getDesktopViewport(self) -> Optional[List[Tuple[int, int]]]: + def getDesktopViewport(self) -> list[tuple[int, int]] | None: """ Array of pairs of cardinals that define the top left corner of each desktop's viewport. For Window Managers that don't support large desktops, this MUST always be set to (0,0). :return: list of int tuples or None (if the value couldn't be retrieved) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_VIEWPORT) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.DESKTOP_VIEWPORT) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - result: List[Tuple[int, int]] = [] + result: list[tuple[int, int]] = [] for i in range(len(res)): if i % 2 != 0: - item: Tuple[int, int] = (res[i-1], res[i]) + item: tuple[int, int] = (res[i-1], res[i]) result.append(item) else: result = [] @@ -501,7 +502,7 @@ def setDesktopViewport(self, newWidth: int, newHeight: int): """ self.setProperty(Root.DESKTOP_VIEWPORT, [newWidth, newHeight]) - def getCurrentDesktop(self) -> Optional[int]: + def getCurrentDesktop(self) -> int | None: """ The index of the current desktop. This is always an integer between 0 and _NET_NUMBER_OF_DESKTOPS - 1. This MUST be set and updated by the Window Manager. If a Pager wants to switch to another virtual desktop, @@ -509,8 +510,8 @@ def getCurrentDesktop(self) -> Optional[int]: :return: index of current desktop in int format or None if it couldn't be retrieved """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.CURRENT_DESKTOP) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.CURRENT_DESKTOP) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res and isinstance(res[0], int): return res[0] return None @@ -527,7 +528,7 @@ def setCurrentDesktop(self, newDesktop: int): if desks and newDesktop < desks and newDesktop != self.getCurrentDesktop(): self.setProperty(Root.CURRENT_DESKTOP, [newDesktop, Xlib.X.CurrentTime]) - def getDesktopNames(self) -> Optional[List[str]]: + def getDesktopNames(self) -> list[str] | None: """ The names of all virtual desktops. This is a list of NULL-terminated strings in UTF-8 encoding [UTF8]. This property MAY be changed by a Pager or the Window Manager at any time. @@ -544,13 +545,13 @@ def getDesktopNames(self) -> Optional[List[str]]: :return: list of desktop names in str format """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_NAMES) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.DESKTOP_NAMES) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[str], res) + res = cast("list[str]", res) return res - def getActiveWindow(self) -> Optional[int]: + def getActiveWindow(self) -> int | None: """ The window ID of the currently active window or None if no window has the focus. This is a read-only property set by the Window Manager. If a Client wants to activate another window, it MUST send a @@ -558,13 +559,13 @@ def getActiveWindow(self) -> Optional[int]: :return: window id or None """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.ACTIVE) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.ACTIVE) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res and isinstance(res[0], int): return res[0] return None - def getWorkArea(self) -> Optional[List[int]]: + def getWorkArea(self) -> list[int] | None: """ This property MUST be set by the Window Manager upon calculating the work area for each desktop. Contains a geometry for each desktop. These geometries are specified relative to the viewport on each @@ -576,13 +577,13 @@ def getWorkArea(self) -> Optional[List[int]]: :return: tuple containing workarea coordinates (x, y, width, height) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.WORKAREA) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.WORKAREA) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getSupportingWMCheck(self) -> Optional[List[int]]: + def getSupportingWMCheck(self) -> list[int] | None: """ The Window Manager MUST set this property on the root window to be the ID of a child window created by himself, to indicate that a compliant window manager is active. The child window MUST also have the @@ -597,13 +598,13 @@ def getSupportingWMCheck(self) -> Optional[List[int]]: :return: ''True'' if compliant Window Manager is active or None if it couldn't be retrieved """ # Not sure what this property is intended to return. In my system it returns None! - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.SUPPORTING_WM_CHECK) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.SUPPORTING_WM_CHECK) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getVirtualRoots(self) -> Optional[List[int]]: + def getVirtualRoots(self) -> list[int] | None: """ To implement virtual desktops, some Window Managers reparent client windows to a child of the root window. Window Managers using this technique MUST set this property to a list of IDs for windows that are acting @@ -612,20 +613,20 @@ def getVirtualRoots(self) -> Optional[List[int]]: :return: List of virtual roots id's as integers """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.VIRTUAL_ROOTS) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.VIRTUAL_ROOTS) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getDesktopLayout(self) -> Optional[List[int]]: - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.DESKTOP_LAYOUT) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + def getDesktopLayout(self) -> list[int] | None: + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.DESKTOP_LAYOUT) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def setDesktopLayout(self, orientation: Union[int, DesktopLayout], columns: int, rows: int, starting_corner: Union[int, DesktopLayout]): + def setDesktopLayout(self, orientation: int | DesktopLayout, columns: int, rows: int, starting_corner: int | DesktopLayout): """ Values (as per EwmhRoot.DesktopLayout): _NET_WM_ORIENTATION_HORZ 0 @@ -699,7 +700,7 @@ def setDesktopLayout(self, orientation: Union[int, DesktopLayout], columns: int, """ self.setProperty(Root.DESKTOP_LAYOUT, [orientation, columns, rows, starting_corner]) - def getShowingDesktop(self) -> Optional[bool]: + def getShowingDesktop(self) -> bool | None: """ Some Window Managers have a "showing the desktop" mode in which windows are hidden, and the desktop background is displayed and focused. If a Window Manager supports the _NET_SHOWING_DESKTOP hint, it @@ -708,8 +709,8 @@ def getShowingDesktop(self) -> Optional[bool]: :return: ''True'' if showing desktop """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Root.SHOWING_DESKTOP) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Root.SHOWING_DESKTOP) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res: return res[0] != 0 return None @@ -748,7 +749,7 @@ def setClosed(self, winId: int, userAction: bool = True): """ self.sendMessage(winId, Root.CLOSE, [Xlib.X.CurrentTime, 2 if userAction else 1]) - def setMoveResize(self, winId: int, gravity: int = 0, x: Optional[int] = None, y: Optional[int] = None, width: Optional[int] = None, height: Optional[int] = None, userAction: bool = True): + def setMoveResize(self, winId: int, gravity: int = 0, x: int | None = None, y: int | None = None, width: int | None = None, height: int | None = None, userAction: bool = True): """ Moves and/or resize given window @@ -915,7 +916,7 @@ class _WmProtocols: # Is all this necessary/interesting? # Besides, should they be included within EwmhRoot class or outside? - def __init__(self, display: Xlib.display.Display, root: XWindow): + def __init__(self, display: Xlib.display.Display, root: XWindow) -> None: self._display = display self._root = root @@ -1027,7 +1028,7 @@ class EwmhWindow: available using extensions subclass (EwmhWindow.extensions.*) """ - def __init__(self, window: Union[int, XWindow]): + def __init__(self, window: int | XWindow) -> None: if isinstance(window, XWindow): self.id: int = window.id @@ -1044,8 +1045,8 @@ def __init__(self, window: Union[int, XWindow]): self._currDesktop = os.environ.get('XDG_CURRENT_DESKTOP', '').lower() - def getProperty(self, prop: Union[str, int], prop_type: int = Xlib.X.AnyPropertyType) \ - -> Optional[Xlib.protocol.request.GetProperty]: + def getProperty(self, prop: str | int, prop_type: int = Xlib.X.AnyPropertyType) \ + -> Xlib.protocol.request.GetProperty | None: """ Retrieves given property data from given window @@ -1057,7 +1058,7 @@ def getProperty(self, prop: Union[str, int], prop_type: int = Xlib.X.AnyProperty prop = self.display.get_atom(prop) return getProperty(self.xWindow, prop, prop_type, self.display) - def sendMessage(self, prop: Union[str, int], data: Union[List[int], str]): + def sendMessage(self, prop: str | int, data: list[int] | str): """ Sends a ClientMessage event to current window @@ -1066,7 +1067,7 @@ def sendMessage(self, prop: Union[str, int], data: Union[List[int], str]): """ return sendMessage(self.id, prop, data) - def changeProperty(self, prop: Union[str, int], data: Union[List[int], str], + def changeProperty(self, prop: str | int, data: list[int] | str, prop_type: int = Xlib.Xatom.ATOM, propMode: Mode = Mode.REPLACE): """ Sets given property for the current window. The property might be ignored by the Window Manager, but returned @@ -1080,7 +1081,7 @@ def changeProperty(self, prop: Union[str, int], data: Union[List[int], str], """ changeProperty(self.xWindow, prop, data, prop_type, propMode, self.display) - def getName(self) -> Optional[str]: + def getName(self) -> str | None: """ Get the name of the current window. Some windows may not have a title, the title may change or even this query may fail (e.g. for root windows) @@ -1092,8 +1093,8 @@ def getName(self) -> Optional[str]: """ # Xlib's get_wm_name() returns empty strings in some cases # Thanks to cedricscheepers - https://github.com/cedricscheepers for pointing out this issue - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.NAME) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) # Some (older) apps do not set _NET_WM_NAME property, but WM_NAME. Using it as fallback # Thans to ReaperMantis - https://github.com/ReaperMantis for finding a solution! if res: @@ -1112,7 +1113,7 @@ def setName(self, name: str): """ self.changeProperty(Window.NAME, name) - def getVisibleName(self) -> Optional[str]: + def getVisibleName(self) -> str | None: """ Get the visible name of the current window. Some windows may not have a title, the title may change or even this query may fail (e.g. for root windows) @@ -1125,8 +1126,8 @@ def getVisibleName(self) -> Optional[str]: :return: visible name of the window as str or None (nothing obtained) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.VISIBLE_NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.VISIBLE_NAME) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res: return str(res[0]) return None @@ -1139,7 +1140,7 @@ def setVisibleName(self, name: str): """ self.changeProperty(Window.VISIBLE_NAME, name) - def getIconName(self) -> Optional[str]: + def getIconName(self) -> str | None: """ Get the name of the window icon @@ -1148,8 +1149,8 @@ def getIconName(self) -> Optional[str]: :return: icon name as string """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Xlib.Xatom.WM_ICON_NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Xlib.Xatom.WM_ICON_NAME) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res: return str(res[0]) return None @@ -1162,7 +1163,7 @@ def setIconName(self, name: str): """ self.changeProperty(Xlib.Xatom.WM_ICON_NAME, name) - def getVisibleIconName(self) -> Optional[str]: + def getVisibleIconName(self) -> str | None: """ Get the visible name of the window icon. @@ -1171,8 +1172,8 @@ def getVisibleIconName(self) -> Optional[str]: :return: visible icon name as string """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.VISIBLE_ICON_NAME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.VISIBLE_ICON_NAME) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res: return str(res[0]) return None @@ -1185,7 +1186,7 @@ def setVisibleIconName(self, name: str): """ self.changeProperty(Window.VISIBLE_ICON_NAME, name) - def getDesktop(self) -> Optional[int]: + def getDesktop(self) -> int | None: """ Cardinal to determine the desktop the window is in (or wants to be) starting with 0 for the first desktop. A Client MAY choose not to set this property, in which case the Window Manager SHOULD place it as it wishes. @@ -1203,8 +1204,8 @@ def getDesktop(self) -> Optional[int]: :return: desktop index on which current window is showing """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.DESKTOP) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.DESKTOP) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res and isinstance(res[0], int): return int(res[0]) return None @@ -1216,12 +1217,12 @@ def setDesktop(self, newDesktop: int, userAction: bool = True): :param newDesktop: target desktop index (as per getNumberOfDesktops()) :param userAction: source indication (user or pager/manager action). Defaults to True """ - numDesktops: Optional[int] = self.ewmhRoot.getNumberOfDesktops() - currDesktop: Optional[int] = self.ewmhRoot.getCurrentDesktop() + numDesktops: int | None = self.ewmhRoot.getNumberOfDesktops() + currDesktop: int | None = self.ewmhRoot.getCurrentDesktop() if numDesktops is not None and 0 <= newDesktop <= numDesktops and currDesktop is not None and newDesktop != currDesktop: self.sendMessage(Window.DESKTOP, [newDesktop, Xlib.X.CurrentTime, 2 if userAction else 1]) - def getWmWindowType(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: + def getWmWindowType(self, text: bool = False) -> list[int] | list[str] | None: """ Gets the window type of current window @@ -1269,7 +1270,7 @@ def getWmWindowType(self, text: bool = False) -> Optional[Union[List[int], List[ """ return getPropertyValue(self.getProperty(Window.WM_WINDOW_TYPE), text, self.display) - def setWmWindowType(self, winType: Union[str, WindowType]): + def setWmWindowType(self, winType: str | WindowType): """ Changes the type of current window. @@ -1285,7 +1286,7 @@ def setWmWindowType(self, winType: Union[str, WindowType]): self.display.flush() self.setMoveResize(x=x, y=y, width=w, height=h) - def getWmState(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: + def getWmState(self, text: bool = False) -> list[int] | list[str] | None: """ Get the window states values of current window. @@ -1361,7 +1362,7 @@ def getWmState(self, text: bool = False) -> Optional[Union[List[int], List[str]] """ return getPropertyValue(self.getProperty(Window.WM_STATE), text, self.display) - def changeWmState(self, action: StateAction, state: Union[str, State], state2: Union[str, State] = State.NULL, userAction: bool = True): + def changeWmState(self, action: StateAction, state: str | State, state2: str | State = State.NULL, userAction: bool = True): """ Sets the window states values of current window. @@ -1395,38 +1396,38 @@ def setMaximized(self, maxHorz: bool, maxVert: bool): NULL = State.NULL state1 = NULL state2 = NULL - ret: Optional[Union[List[int], List[str]]] = self.getWmState(True) - states: List[str] = [] if ret is None else [a for a in ret if a and isinstance(a, str)] + ret: list[int] | list[str] | None = self.getWmState(True) + states: list[str] = [] if ret is None else [a for a in ret if a and isinstance(a, str)] if maxHorz and maxVert: if State.MAXIMIZED_HORZ not in states: state1 = State.MAXIMIZED_HORZ if State.MAXIMIZED_VERT not in states: state2 = State.MAXIMIZED_VERT if state1 or state2: - self.changeWmState(StateAction.ADD, cast(State, state1) if state1 != NULL else cast(State, state2), - cast(State, state2) if state1 != NULL else cast(State, NULL)) + self.changeWmState(StateAction.ADD, cast("State", state1) if state1 != NULL else cast("State", state2), + cast("State", state2) if state1 != NULL else cast("State", NULL)) elif maxHorz: if State.MAXIMIZED_HORZ not in states: state = State.MAXIMIZED_HORZ - self.changeWmState(StateAction.ADD, cast(State, state), cast(State, NULL)) + self.changeWmState(StateAction.ADD, cast("State", state), cast("State", NULL)) if State.MAXIMIZED_VERT in states: state = State.MAXIMIZED_VERT - self.changeWmState(StateAction.REMOVE, cast(State, state), cast(State, NULL)) + self.changeWmState(StateAction.REMOVE, cast("State", state), cast("State", NULL)) elif maxVert: if State.MAXIMIZED_HORZ in states: state = State.MAXIMIZED_HORZ - self.changeWmState(StateAction.REMOVE, cast(State, state), cast(State, NULL)) + self.changeWmState(StateAction.REMOVE, cast("State", state), cast("State", NULL)) if State.MAXIMIZED_VERT not in states: state = State.MAXIMIZED_VERT - self.changeWmState(StateAction.ADD, cast(State, state), cast(State, NULL)) + self.changeWmState(StateAction.ADD, cast("State", state), cast("State", NULL)) else: if State.MAXIMIZED_HORZ in states: state1 = State.MAXIMIZED_HORZ if State.MAXIMIZED_VERT in states: state2 = State.MAXIMIZED_VERT if state1 or state2: - self.changeWmState(StateAction.REMOVE, cast(State, state1) if state1 != NULL else cast(State, state2), - cast(State, state2) if state1 != NULL else cast(State, NULL)) + self.changeWmState(StateAction.REMOVE, cast("State", state1) if state1 != NULL else cast("State", state2), + cast("State", state2) if state1 != NULL else cast("State", NULL)) def setMinimized(self): """ @@ -1439,7 +1440,7 @@ def setMinimized(self): atom = self.display.get_atom(Window.CHANGE_STATE, True) self.sendMessage(atom, [Xlib.Xutil.IconicState]) - def getAllowedActions(self, text: bool = False) -> Optional[Union[List[int], List[str]]]: + def getAllowedActions(self, text: bool = False) -> list[int] | list[str] | None: """ Gets the list of allowed actions for current window. @@ -1497,7 +1498,7 @@ def getAllowedActions(self, text: bool = False) -> Optional[Union[List[int], Lis # """ # pass - def getStrut(self) -> Optional[List[int]]: + def getStrut(self) -> list[int] | None: """ This property is equivalent to a _NET_WM_STRUT_PARTIAL property where all start values are 0 and all end values are the height or width of the logical screen. _NET_WM_STRUT_PARTIAL was introduced later @@ -1507,10 +1508,10 @@ def getStrut(self) -> Optional[List[int]]: :return: List of integers (left, right, top, bottom) defining the width of the reserved area at each border of the screen """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.STRUT) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.STRUT) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res def setStrut(self, left: int, right: int, top: int, bottom: int): @@ -1527,7 +1528,7 @@ def setStrut(self, left: int, right: int, top: int, bottom: int): # Need to understand this self.sendMessage(Window.STRUT, [left, right, top, bottom]) - def getStrutPartial(self) -> Optional[List[int]]: + def getStrutPartial(self) -> list[int] | None: """ This property MUST be set by the Client if the window is to reserve space at the edge of the screen. The property contains 4 cardinals specifying the width of the reserved area at each border of the screen, @@ -1568,10 +1569,10 @@ def getStrutPartial(self) -> Optional[List[int]]: four struts. The order of the values is left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.STRUT_PARTIAL) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.STRUT_PARTIAL) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res # Need to understand this / Can it be set and, if so, how to pass all those values??? @@ -1585,7 +1586,7 @@ def getStrutPartial(self) -> Optional[List[int]]: # """ # pass - def getIconGeometry(self) -> Optional[List[int]]: + def getIconGeometry(self) -> list[int] | None: """ Get the geometry of current window icon. @@ -1597,13 +1598,13 @@ def getIconGeometry(self) -> Optional[List[int]]: :return: List of integers containing the icon geometry or None (no obtained) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.ICON_GEOMETRY) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.ICON_GEOMETRY) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getPid(self) -> Optional[int]: + def getPid(self) -> int | None: """ Get the Process ID (pid) of the process to which current window belongs to. @@ -1617,13 +1618,13 @@ def getPid(self) -> Optional[int]: :return: pid of current process as integer """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.PID) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.PID) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res: return int(res[0]) return None - def getHandledIcons(self) -> Optional[List[int]]: + def getHandledIcons(self) -> list[int] | None: """ Get the id of icons handled by the window. @@ -1633,13 +1634,13 @@ def getHandledIcons(self) -> Optional[List[int]]: :return: List of integers or None (not obtained) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.HANDLED_ICONS) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.HANDLED_ICONS) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getUserTime(self) -> Optional[List[int]]: + def getUserTime(self) -> list[int] | None: """ Get time since last user activity on current window. @@ -1667,20 +1668,20 @@ def getUserTime(self) -> Optional[List[int]]: :return: timestamp in integer format or None (not obtained) """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.USER_TIME) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.USER_TIME) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def _getNetFrameExtents(self) -> Optional[Union[List[int], List[str]]]: + def _getNetFrameExtents(self) -> list[int] | list[str] | None: return getPropertyValue(self.getProperty("_NET_FRAME_EXTENTS")) - def _getGtkFrameExtents(self) -> Optional[Union[List[int], List[str]]]: + def _getGtkFrameExtents(self) -> list[int] | list[str] | None: return getPropertyValue(self.getProperty("_GTK_FRAME_EXTENTS")) - def getFrameExtents(self) -> Optional[List[int]]: + def getFrameExtents(self) -> list[int] | None: """ Get the current window frame extents (space reserved by the window manager around window) @@ -1692,12 +1693,12 @@ def getFrameExtents(self) -> Optional[List[int]]: :return: left, right, top, bottom """ - res: Optional[Union[List[int], List[str]]] = self._getNetFrameExtents() or self._getGtkFrameExtents() + res: list[int] | list[str] | None = self._getNetFrameExtents() or self._getGtkFrameExtents() if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getOpaqueRegion(self) -> Optional[List[int]]: + def getOpaqueRegion(self) -> list[int] | None: """ The Client MAY set this property to a list of 4-tuples [x, y, width, height], each representing a rectangle in window coordinates where the pixels of the window's contents have a fully opaque alpha value. If the @@ -1711,13 +1712,13 @@ def getOpaqueRegion(self) -> Optional[List[int]]: Rationale: This gives the compositing manager more room for optimizations. For example, it can avoid drawing occluded portions behind the window. """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.OPAQUE_REGION) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.OPAQUE_REGION) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res is not None: - res = cast(List[int], res) + res = cast("list[int]", res) return res - def getBypassCompositor(self) -> Optional[int]: + def getBypassCompositor(self) -> int | None: """ The Client MAY set this property to a list of 4-tuples [x, y, width, height], each representing a rectangle in window coordinates where the pixels of the window's contents have a fully opaque alpha value. If the window @@ -1731,8 +1732,8 @@ def getBypassCompositor(self) -> Optional[int]: Rationale: This gives the compositing manager more room for optimizations. For example, it can avoid drawing occluded portions behind the window. """ - ret: Optional[Xlib.protocol.request.GetProperty] = self.getProperty(Window.BYPASS_COMPOSITOR) - res: Optional[Union[List[int], List[str]]] = getPropertyValue(ret, display=self.display) + ret: Xlib.protocol.request.GetProperty | None = self.getProperty(Window.BYPASS_COMPOSITOR) + res: list[int] | list[str] | None = getPropertyValue(ret, display=self.display) if res: return int(res[0]) return None @@ -1772,7 +1773,7 @@ def setClosed(self, userAction: bool = True): atom = self.display.get_atom(Window.CLOSE, True) self.sendMessage(atom, [Xlib.X.CurrentTime, 2 if userAction else 1]) - def changeStacking(self, mode: int, sibling: Optional[XWindow] = None): + def changeStacking(self, mode: int, sibling: XWindow | None = None): """ Changes current window position (stacking) in relation to its siblings. @@ -1820,7 +1821,7 @@ def changeStacking(self, mode: int, sibling: Optional[XWindow] = None): self.xWindow.configure(stack_mode=mode) self.display.flush() - def setMoveResize(self, gravity: int = 0, x: Optional[int] = None, y: Optional[int] = None, width: Optional[int] = None, height: Optional[int] = None, userAction: bool = True): + def setMoveResize(self, gravity: int = 0, x: int | None = None, y: int | None = None, width: int | None = None, height: int | None = None, userAction: bool = True): """ Move and/or resize current window @@ -1879,7 +1880,7 @@ def setMoveResize(self, gravity: int = 0, x: Optional[int] = None, y: Optional[i gravity_flags = gravity_flags | (1 << 13) self.sendMessage(Window.MOVERESIZE, [gravity_flags, x, y, width, height]) - def setWmMoveResize(self, x_root: int, y_root: int, orientation: Union[int, MoveResize], button: int, userAction: bool = True): + def setWmMoveResize(self, x_root: int, y_root: int, orientation: int | MoveResize, button: int, userAction: bool = True): """ This message allows Clients to initiate window movement or resizing. They can define their own move and size "grips", whilst letting the Window Manager control the actual operation. This means that all moves/resizes @@ -1951,14 +1952,14 @@ class _Extensions: Additional, non-EWMH features, related to low-level window properties like hints, protocols and events """ - def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow): + def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow) -> None: self.winId = winId self.display = display self.root = root self.xWindow = self.display.create_resource_object('window', winId) self.checkEvents = self._CheckEvents(winId, display, root) - def getWmHints(self) -> Optional[WmHints]: + def getWmHints(self) -> WmHints | None: """ Get window hints. @@ -2027,7 +2028,7 @@ def getWmHints(self) -> Optional[WmHints]: :return: Hints struct """ - hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_hints() + hints: Xlib.protocol.rq.DictWrapper | None = self.xWindow.get_wm_hints() if hints is not None: ret: WmHints = WmHints( flags=hints.flags, @@ -2044,12 +2045,12 @@ def getWmHints(self) -> Optional[WmHints]: return None def setWmHints(self, input_mode: int = HintAction.KEEP, initial_state: int = HintAction.KEEP, - icon_pixmap: Union[Xlib.xobject.drawable.Pixmap, int] = HintAction.KEEP, - icon_window: Union[XWindow, int] = HintAction.KEEP, + icon_pixmap: Xlib.xobject.drawable.Pixmap | int = HintAction.KEEP, + icon_window: XWindow | int = HintAction.KEEP, icon_x: int = HintAction.KEEP, icon_y: int = HintAction.KEEP, - icon_mask: Union[Xlib.xobject.drawable.Pixmap, int] = HintAction.KEEP, - window_group: Union[XWindow, int] = HintAction.KEEP, - urgency: Union[bool, int] = HintAction.KEEP): + icon_mask: Xlib.xobject.drawable.Pixmap | int = HintAction.KEEP, + window_group: XWindow | int = HintAction.KEEP, + urgency: bool | int = HintAction.KEEP): """ Set new hints for current window. @@ -2087,7 +2088,7 @@ def setWmHints(self, input_mode: int = HintAction.KEEP, initial_state: int = Hin :param window_group: group of windows the current window belongs to :param urgency: True/False to activate/deactivate urgency falg """ - hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_hints() + hints: Xlib.protocol.rq.DictWrapper | None = self.xWindow.get_wm_hints() if hints is not None: # If None: WM doesn't use them or should we initialize them (and how)? if input_mode != HintAction.KEEP: @@ -2154,7 +2155,7 @@ def setWmHints(self, input_mode: int = HintAction.KEEP, initial_state: int = Hin self.xWindow.set_wm_hints(hints) self.display.flush() - def getWmNormalHints(self) -> Optional[WmNormalHints]: + def getWmNormalHints(self) -> WmNormalHints | None: """ Xlib provides functions that you can use to set or read the WM_NORMAL_HINTS property for a given window. The functions use the flags and the XSizeHints structure, as defined in the X11/Xutil.h header file. @@ -2215,7 +2216,7 @@ def getWmNormalHints(self) -> Optional[WmNormalHints]: client asked, the position on the parent window's border named by the win_gravity will be placed where the client window would have been placed in the absence of a window manager. """ - normal_hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_normal_hints() + normal_hints: Xlib.protocol.rq.DictWrapper | None = self.xWindow.get_wm_normal_hints() if normal_hints is not None: min_aspect: Aspect = Aspect( num=normal_hints.min_aspect.num, @@ -2245,8 +2246,8 @@ def getWmNormalHints(self) -> Optional[WmNormalHints]: def setWmNormalHints(self, min_width: int = HintAction.KEEP, min_height: int = HintAction.KEEP, max_width: int = HintAction.KEEP, max_height: int = HintAction.KEEP, width_inc: int = HintAction.KEEP, height_inc: int = HintAction.KEEP, - min_aspect: Union[Aspect, int] = HintAction.KEEP, - max_aspect: Union[Aspect, int] = HintAction.KEEP, + min_aspect: Aspect | int = HintAction.KEEP, + max_aspect: Aspect | int = HintAction.KEEP, base_width: int = HintAction.KEEP, base_height: int = HintAction.KEEP, win_gravity: int = HintAction.KEEP): """ @@ -2289,7 +2290,7 @@ def setWmNormalHints(self, min_width: int = HintAction.KEEP, min_height: int = H :param base_height: Preferred height of window :param int win_gravity: window gravity for placing an re-stacking """ - normal_hints: Optional[Xlib.protocol.rq.DictWrapper] = self.xWindow.get_wm_normal_hints() + normal_hints: Xlib.protocol.rq.DictWrapper | None = self.xWindow.get_wm_normal_hints() if normal_hints is not None: # If None: WM doesn't use them or should we initialize them (and how)? if min_width != HintAction.KEEP: @@ -2365,7 +2366,7 @@ def setWmNormalHints(self, min_width: int = HintAction.KEEP, min_height: int = H self.xWindow.set_wm_normal_hints(normal_hints) self.display.flush() - def getWmProtocols(self, text: bool = False) -> Union[List[int], List[str]]: + def getWmProtocols(self, text: bool = False) -> list[int] | list[str]: """ Get the protocols supported by current window. @@ -2391,13 +2392,13 @@ def getWmProtocols(self, text: bool = False) -> Union[List[int], List[str]]: :param text: select whether the protocols will be returned as integers or strings :return: List of protocols in integer or string format """ - prots: List[int] = self.xWindow.get_wm_protocols() + prots: list[int] = self.xWindow.get_wm_protocols() if text: return [self.display.get_atom_name(p) for p in prots if isinstance(p, int) and p != 0] else: - return [p for p in prots] + return list(prots) - def addWmProtocols(self, atoms: Union[List[str], List[int]]): + def addWmProtocols(self, atoms: list[str] | list[int]): """ Adds new protocols atoms for current window. @@ -2413,8 +2414,8 @@ def addWmProtocols(self, atoms: Union[List[str], List[int]]): :param atoms: List of protocols to be added """ - prots: List[int] = self.xWindow.get_wm_protocols() - newAtoms: List[int] = [] + prots: list[int] = self.xWindow.get_wm_protocols() + newAtoms: list[int] = [] for atom in atoms: if isinstance(atom, str): a: int = self.display.get_atom(atom) @@ -2425,7 +2426,7 @@ def addWmProtocols(self, atoms: Union[List[str], List[int]]): self.xWindow.set_wm_protocols(newAtoms) self.display.flush() - def delWmProtocols(self, atoms: Union[List[str], List[int]]): + def delWmProtocols(self, atoms: list[str] | list[int]): """ Deletes existing protocols atoms for current window. @@ -2441,14 +2442,14 @@ def delWmProtocols(self, atoms: Union[List[str], List[int]]): :param atoms: List of protocols to be deleted """ - atomsList: List[int] = [] + atomsList: list[int] = [] for atom in atoms: if isinstance(atom, str): atomsList.append(self.display.get_atom(atom)) else: atomsList.append(int(atom)) - prots: List[int] = self.xWindow.get_wm_protocols() - newAtoms: List[int] = [a for a in prots if a not in atomsList] + prots: list[int] = self.xWindow.get_wm_protocols() + newAtoms: list[int] = [a for a in prots if a not in atomsList] self.xWindow.set_wm_protocols(newAtoms) self.display.flush() @@ -2459,7 +2460,7 @@ class _CheckEvents: It's important to define proper mask and event list accordingly. See checkEvents() documentation. """ - def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow): + def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow) -> None: self._winId: int = winId self._display: Xlib.display.Display = display @@ -2467,7 +2468,7 @@ def __init__(self, winId: int, display: Xlib.display.Display, root: XWindow): self._keep: threading.Event = threading.Event() self._stopRequested: bool = False - self._checkThread: Optional[threading.Thread] = None + self._checkThread: threading.Thread | None = None self._threadStarted: bool = False self._interval = 0.1 @@ -2496,7 +2497,7 @@ def _checkDisplayEvents(self): self._root.change_attributes(event_mask=Xlib.X.NoEventMask) self._display.flush() - def start(self, events: List[int], mask: int, callback: Callable[[Xlib.protocol.rq.Event], None]): + def start(self, events: list[int], mask: int, callback: Callable[[Xlib.protocol.rq.Event], None]): """ Activate a watchdog to be notified on given events (to provided callback function). @@ -2564,7 +2565,7 @@ def start(self, events: List[int], mask: int, callback: Callable[[Xlib.protocol. :param mask: Events mask according to selected events as integer: Xlib.X.mask1 | Xlib.mask2 | ... :param callback: Function to be invoked when a selected event is received, passing received event to it """ - self._events: List[int] = events + self._events: list[int] = events self._mask: int = mask self._callback: Callable[[Xlib.protocol.rq.Event], None] = callback @@ -2610,7 +2611,7 @@ def _getWindowParent(win: XWindow, rootId: int) -> int: return win.id -def _getWindowGeom(win: XWindow, rootId: int = defaultRoot.id) -> Tuple[int, int, int, int]: +def _getWindowGeom(win: XWindow, rootId: int = defaultRoot.id) -> tuple[int, int, int, int]: # https://stackoverflow.com/questions/12775136/get-window-position-and-size-in-python-with-xlib - mgalgs geom = win.get_geometry() x = geom.x @@ -2630,7 +2631,7 @@ def _getWindowGeom(win: XWindow, rootId: int = defaultRoot.id) -> Tuple[int, int return x, y, w, h -def _xlibGetAllWindows(parent: Optional[XWindow] = None, title: str = "", klass: Optional[Tuple[str, str]] = None) -> List[XWindow]: +def _xlibGetAllWindows(parent: XWindow | None = None, title: str = "", klass: tuple[str, str] | None = None) -> list[XWindow]: """ Retrieves all open windows, including "system", non-user windows (unlike getClientList() or getClientListStacking()). @@ -2641,25 +2642,25 @@ def _xlibGetAllWindows(parent: Optional[XWindow] = None, title: str = "", klass: """ parentWin: XWindow = parent or defaultRoot - allWindows: List[XWindow] = [] + allWindows: list[XWindow] = [] def findit(hwnd: XWindow) -> None: try: query = hwnd.query_tree() - children: List[XWindow] = query.children + children: list[XWindow] = query.children except Exception: children = [] for child in children: try: # This returns empty name in some cases... must use getProperty!!!! - ret: Optional[str] = child.get_wm_name() + ret: str | None = child.get_wm_name() except Exception: ret = None if ret is not None: winTitle: str = ret else: winTitle = "" - winClass: Optional[Tuple[str, str]] = None + winClass: tuple[str, str] | None = None try: winClass = child.get_wm_class() except Exception: diff --git a/src/ewmhlib/_main.py b/src/ewmhlib/_main.py index a529339..fb787d3 100644 --- a/src/ewmhlib/_main.py +++ b/src/ewmhlib/_main.py @@ -1,5 +1,4 @@ #!/usr/bin/python -# -*- coding: utf-8 -*- from ._ewmhlib import (displaysCount, getDisplays, getDisplaysInfo, getRoots, defaultDisplay, defaultScreen, defaultRoot, defaultEwmhRoot, diff --git a/tests/test_ewmhlib.py b/tests/test_ewmhlib.py index ad87414..97b0284 100644 --- a/tests/test_ewmhlib.py +++ b/tests/test_ewmhlib.py @@ -8,7 +8,7 @@ from ewmhlib import Props, getDisplaysInfo, EwmhRoot, EwmhWindow -def main(): +def main() -> None: print("ALL DISPLAYS") print(getDisplaysInfo()) diff --git a/uv.lock b/uv.lock index 7b2fac0..88684a7 100644 --- a/uv.lock +++ b/uv.lock @@ -206,6 +206,7 @@ dev = [ { name = "myst-parser", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "myst-parser", version = "4.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "myst-parser", version = "5.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ruff" }, { name = "types-python-xlib", version = "0.33.0.20250809", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "types-python-xlib", version = "0.33.0.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, ] @@ -225,6 +226,7 @@ requires-dist = [ dev = [ { name = "mypy", specifier = ">=0.990,<2" }, { name = "myst-parser" }, + { name = "ruff", specifier = ">=0.15.16" }, { name = "types-python-xlib", specifier = ">=0.32" }, ] docs = [{ name = "myst-parser" }] @@ -764,6 +766,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, ] +[[package]] +name = "ruff" +version = "0.15.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" }, + { url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" }, + { url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" }, + { url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" }, + { url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" }, + { url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" }, + { url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" }, +] + [[package]] name = "six" version = "1.17.0"