diff --git a/.dprint.jsonc b/.dprint.jsonc new file mode 100644 index 00000000..ecd1b1c4 --- /dev/null +++ b/.dprint.jsonc @@ -0,0 +1,4 @@ +{ + // https://dprint.dev/config/#extending-a-different-configuration-file + "extends": "https://raw.githubusercontent.com/BesLogic/shared-configs/main/.dprint.jsonc", +} diff --git a/.github/workflows/autofix.yml b/.github/workflows/autofix.yml new file mode 100644 index 00000000..0f518b3e --- /dev/null +++ b/.github/workflows/autofix.yml @@ -0,0 +1,28 @@ +# https://autofix.ci/setup#getting-started +name: autofix.ci # needed to securely identify the workflow +on: + pull_request: + branches: + - main +permissions: + contents: read + +jobs: + autofix: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: astral-sh/ruff-action@v3 + with: + version-file: "pyproject.toml" + args: "check --fix" + - run: ruff format + # Format even if the the previous step failed + if: ${{ !cancelled() }} + + - run: npx dprint fmt + # Format even if the the previous step failed + if: ${{ !cancelled() }} + + - uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef diff --git a/.github/workflows/lint-and-build.yml b/.github/workflows/lint-and-build.yml index e0ded7c7..e4bee84f 100644 --- a/.github/workflows/lint-and-build.yml +++ b/.github/workflows/lint-and-build.yml @@ -36,17 +36,6 @@ concurrency: cancel-in-progress: true jobs: - Ruff: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - uses: astral-sh/ruff-action@v3 - with: - version-file: "pyproject.toml" - - run: ruff format --check --diff - # Show formatting issues even if the "check" step failed - if: ${{ !cancelled() }} - Pyright: runs-on: ${{ matrix.os }} strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index ca8d1079..00000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,30 +0,0 @@ -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 - hooks: - - id: pretty-format-json - exclude: ".vscode/.*" # Exclude jsonc - args: [--autofix, --no-sort-keys] - - id: trailing-whitespace - args: [--markdown-linebreak-ext=md] - - id: end-of-file-fixer - - id: mixed-line-ending - args: [--fix=lf] - - id: check-case-conflict - - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.14.0 - hooks: - - id: pretty-format-yaml - args: [--autofix, --indent, "2", --offset, "2", --preserve-quotes, --line-width, "100"] - - id: pretty-format-ini - args: [--autofix] - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 # Must match requirements-dev.txt - hooks: - - id: ruff - args: [--fix] - - id: ruff-format - -ci: - autoupdate_branch: main - autoupdate_schedule: quarterly diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 967aacdb..8ca6b03b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -10,6 +10,7 @@ // Only keep those used by your specific project "recommendations": [ // General + "dprint.dprint", "eamodio.gitlens", "editorconfig.editorconfig", "pkief.material-icon-theme", diff --git a/.vscode/launch.json b/.vscode/launch.json index 270f80e1..42409a40 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,7 +11,7 @@ "preLaunchTask": "Compile resources", "program": "src/AutoSplit.py", "console": "integratedTerminal", - "justMyCode": false + "justMyCode": false, }, { "name": "Python: AutoSplit", @@ -20,7 +20,7 @@ "preLaunchTask": "Compile resources", "program": "src/AutoSplit.py", "console": "integratedTerminal", - "justMyCode": true + "justMyCode": true, }, { "name": "Python: AutoSplit --auto-controlled", @@ -29,10 +29,10 @@ "preLaunchTask": "Compile resources", "program": "src/AutoSplit.py", "args": [ - "--auto-controlled" + "--auto-controlled", ], "console": "integratedTerminal", - "justMyCode": true - } - ] + "justMyCode": true, + }, + ], } diff --git a/.vscode/settings.json b/.vscode/settings.json index bb224cdf..2afe9adc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,17 +5,13 @@ // https://github.com/BesLogic/shared-configs/blob/main/.vscode/settings.json // Modifications to this file that are not project-specific should also be done upstream. { - "json.schemas": [ - { - // Not all .jsonc files have a pre-associated schema - "fileMatch": [ - "*.jsonc", - ], - "schema": { - "allowTrailingCommas": true, - }, + "json.schemas": [{ + // Not all .jsonc files have a pre-associated schema + "fileMatch": ["*.jsonc"], + "schema": { + "allowTrailingCommas": true, }, - ], + }], "files.associations": { // Qt Creator "*.qrc": "xml", @@ -64,24 +60,28 @@ // IDEM, but also it's annoying to remove imports because of commented code while testing "source.removeUnusedImports": "never", }, + // NOTE: due to a bug in VSCode, we have to specify editor.defaultFormatter individually to ensure // it overrides user settings. Please upvote: https://github.com/microsoft/vscode/issues/168411 + /* * Markdown */ "[markdown]": { "files.trimTrailingWhitespace": false, - "editor.defaultFormatter": "vscode.markdown-language-features", + "editor.defaultFormatter": "dprint.dprint", }, + /* * JSON */ "[json]": { - "editor.defaultFormatter": "vscode.json-language-features", + "editor.defaultFormatter": "dprint.dprint", }, "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features", + "editor.defaultFormatter": "dprint.dprint", }, + /* * Python */ @@ -111,6 +111,7 @@ "ruff.organizeImports": true, // Use the Ruff extension instead "isort.check": false, + /* * TOML */ @@ -134,15 +135,17 @@ "evenBetterToml.formatter.trailingNewline": true, // We like keeping TOML keys in a certain non-alphabetical order that feels more natural "evenBetterToml.formatter.reorderKeys": false, + /* * YAML */ "[yaml]": { - "editor.defaultFormatter": "redhat.vscode-yaml", + "editor.defaultFormatter": "dprint.dprint", }, "yaml.schemas": { "https://json.schemastore.org/github-issue-config.json": ".github/ISSUE_TEMPLATE/config.yml", }, + /* * XML */ @@ -167,6 +170,7 @@ // Custom "string", ], + /* * Powershell */ diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2ac053ad..4b0763af 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -8,10 +8,10 @@ "-NoProfile", "-ExecutionPolicy", "Bypass", - "-Command" - ] - } - } + "-Command", + ], + }, + }, }, "linux": { "options": { @@ -19,10 +19,10 @@ "executable": "pwsh", "args": [ "-NoProfile", - "-Command" - ] - } - } + "-Command", + ], + }, + }, }, "osx": { "options": { @@ -30,16 +30,16 @@ "executable": "pwsh", "args": [ "-NoProfile", - "-Command" - ] - } - } + "-Command", + ], + }, + }, }, "tasks": [ { "label": "Compile resources", "type": "shell", - "command": "scripts/compile_resources.ps1" + "command": "scripts/compile_resources.ps1", }, { "label": "Build AutoSplit", @@ -47,8 +47,8 @@ "command": "scripts/build.ps1", "group": { "kind": "build", - "isDefault": true - } - } - ] + "isDefault": true, + }, + }, + ], } diff --git a/README.md b/README.md index 2ba03880..5cac6719 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ To understand how to use AutoSplit and how it works in-depth, please read the [t ## Download and open - Download the [latest version](/../../releases/latest) -- You can also check out the [latest dev builds](/../../actions/workflows/lint-and-build.yml?query=event%3Apush+is%3Asuccess) (requires a GitHub account) +- You can also check out the [latest dev builds](/../../actions/workflows/lint-and-build.yml?query=event%3Apush+is%3Asuccess) (requires a GitHub account)\ (If you don't have a GitHub account, you can try [nightly.link](https://nightly.link/Toufool/AutoSplit/workflows/lint-and-build/main)) - Linux users must ensure they are in the `tty` and `input` groups and have write access to `/dev/uinput`. You can run the following commands to do so: @@ -77,17 +77,19 @@ See the [installation instructions](https://github.com/Toufool/LiveSplit.AutoSpl ## Resources Still need help? + + - [Check if your issue already exists](../../issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc) - If it does, upvote it 👍 - If it doesn't, create a new one -- Join the [AutoSplit Discord -![AutoSplit Discord](https://badgen.net/discord/members/Qcbxv9y)](https://discord.gg/Qcbxv9y) +- Join the [AutoSplit Discord\ + ![AutoSplit Discord](https://badgen.net/discord/members/Qcbxv9y)](https://discord.gg/Qcbxv9y) ## Contributing -See [CONTRIBUTING.md](/docs/CONTRIBUTING.md) for our contributing standards. -Refer to the [build instructions](/docs/build%20instructions.md) if you're interested in building the application yourself or running it in Python. +See [CONTRIBUTING.md](/docs/CONTRIBUTING.md) for our contributing standards.\ +Refer to the [build instructions](/docs/build%20instructions.md) if you're interested in building the application yourself or running it in Python. Not a developer? You can still help through the following methods: diff --git a/SECURITY.md b/SECURITY.md index 39c1afda..9eb8c06b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Supported Versions -| Version | Supported | -| ------- | :-------: | -| main | :white_check_mark: | +| Version | Supported | +| --------------- | :-------------------------------------------: | +| main | :white_check_mark: | | everything else | WIP branches, security support may be lacking | ## Reporting a Vulnerability diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index a7d6cc83..9b6c6892 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -4,16 +4,14 @@ ## Python Setup and Building -Refer to the [build instructions](/docs/build%20instructions.md) if you're interested in building the application yourself or running it in Python. +Refer to the [build instructions](/docs/build%20instructions.md) if you're interested in building the application yourself or running it in Python. ## Linting and formatting -The project is setup to automatically configure VSCode with the proper extensions and settings. Fixers and formatters will be run on save. +The project is setup to automatically configure VSCode with the proper extensions and settings. Fixers and formatters will be run on save.\ If you use a different IDE or for some reason cannot / don't want to use the recommended extensions, you can run `scripts/lint.ps1`. Project configurations for other IDEs are welcome. -If you like to use pre-commit hooks, `.pre-commit-config.yaml` is setup for such uses. - The CI will automatically fix and commit any autofixable issue your changes may have. ## Visual Designer @@ -60,4 +58,4 @@ Whenever a split image overrides a default global setting, we add a getter that ## Testing -None 😦 Please help us create test suites, we lack the time, but we really want (*need!*) them. +None 😦 Please help us create test suites, we lack the time, but we really want (_need!_) them. diff --git a/docs/D3DDD-Note-Laptops.md b/docs/D3DDD-Note-Laptops.md index bbf456f3..b9b73dee 100644 --- a/docs/D3DDD-Note-Laptops.md +++ b/docs/D3DDD-Note-Laptops.md @@ -19,16 +19,16 @@ Therefore, to be able to use _D3D Desktop Duplication_ on hybrid GPU laptops, we _You must be running Windows 10 1809 or later for this to work._ 1. Press the Windows Key, type `Graphics settings` and press enter -2. You should see the following window: -![image](https://user-images.githubusercontent.com/35039/84433008-a3b65d00-abfb-11ea-8343-81b8f265afc4.png) +2. You should see the following window:\ + ![image](https://user-images.githubusercontent.com/35039/84433008-a3b65d00-abfb-11ea-8343-81b8f265afc4.png) 3. Make sure the dropdown is set to `Desktop App` and click `Browse` -4. Find the `python.exe` used by your _D3D Desktop Duplication_ project. Example: -![image](https://user-images.githubusercontent.com/35039/84433419-3d7e0a00-abfc-11ea-99a4-b5176535b0e5.png) +4. Find the `python.exe` used by your _D3D Desktop Duplication_ project. Example:\ + ![image](https://user-images.githubusercontent.com/35039/84433419-3d7e0a00-abfc-11ea-99a4-b5176535b0e5.png) 5. Click on `Options` -6. Select `Power saving` and click `Save` -![image](https://user-images.githubusercontent.com/35039/84433562-7918d400-abfc-11ea-807a-e3c0b15d9fb2.png) -7. If you did everything right it should look like this: -![image](https://user-images.githubusercontent.com/35039/84433706-bda46f80-abfc-11ea-9c64-a702b96095b8.png) +6. Select `Power saving` and click `Save`\ + ![image](https://user-images.githubusercontent.com/35039/84433562-7918d400-abfc-11ea-807a-e3c0b15d9fb2.png) +7. If you did everything right it should look like this:\ + ![image](https://user-images.githubusercontent.com/35039/84433706-bda46f80-abfc-11ea-9c64-a702b96095b8.png) 8. Repeat the process for other potentially relevant executables for your project: `ipython.exe`, `jupyter-kernel.exe` etc. ## Approach 2: Nvidia Control Panel @@ -44,4 +44,5 @@ Need help to fill in this section. See issue [SerpentAI/D3DShot#28](https://gith Preliminary answer: No. This is telling Windows how to _render_ Python processes with the Desktop Window Manager. Most Python applications are console applications that don't have a window. Even if you have a GUI application with one or more windows, this should only affect the rendering aspect (i.e. your windows won't be rendered through the dedicated GPU) and shouldn't limit hardware access in any way. --- + (copied and adapted from ) diff --git a/docs/build instructions.md b/docs/build instructions.md index 85a348ca..7c41d119 100644 --- a/docs/build instructions.md +++ b/docs/build instructions.md @@ -4,12 +4,12 @@ ### Windows -- Microsoft Visual C++ 14.0 or greater may be required to build the executable. Get it with [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/). +- Microsoft Visual C++ 14.0 or greater may be required to build the executable. Get it with [Microsoft C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/). ### Linux -- You need to be part of the `input` and `tty` groups, as well as have permissions on a few files and folders. - If you are missing from either groups, the install script will take care of it on its first run, but you'll need to restart your session. +- You need to be part of the `input` and `tty` groups, as well as have permissions on a few files and folders.\ + If you are missing from either groups, the install script will take care of it on its first run, but you'll need to restart your session. ### WSL diff --git a/docs/tutorial.md b/docs/tutorial.md index ba14333c..7ed9bd55 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -40,47 +40,49 @@ - Perceptual Hash: An explanation on pHash comparison can be found [here](http://www.hackerfactor.com/blog/index.php?/archives/432-Looks-Like-It.html). It is highly recommended to NOT use pHash if you use masked images, or it'll be very inaccurate. #### Capture Method + ##### Windows -- **Windows Graphics Capture** (fast, most compatible, capped at 60fps) - Only available in Windows 10.0.17134 and up. - Allows recording UWP apps, Hardware Accelerated and Exclusive Fullscreen windows. - Adds a yellow border on Windows 10 (not on Windows 11). - Caps at around 60 FPS. -- **BitBlt** (fastest, least compatible) - The best option when compatible. But it cannot properly record OpenGL, Hardware Accelerated or Exclusive Fullscreen windows. - The smaller the selected region, the more efficient it is. -- **Direct3D Desktop Duplication** (slower, bound to display) - Duplicates the desktop using Direct3D. - It can record OpenGL and Hardware Accelerated windows. - Up to 15x slower than BitBlt for tiny regions. Not affected by window size. - Limited by the target window and monitor's refresh rate. - Overlapping windows will show up and can't record across displays. - This option may not be available for hybrid GPU laptops, see [D3DDD-Note-Laptops.md](/docs/D3DDD-Note-Laptops.md) for a solution. -- **Force Full Content Rendering** (very slow, can affect rendering) - Uses BitBlt behind the scene, but passes a special flag to PrintWindow to force rendering the entire desktop. - About 10-15x slower than BitBlt based on original window size and can mess up some applications' rendering pipelines. +- **Windows Graphics Capture** (fast, most compatible, capped at 60fps)\ + Only available in Windows 10.0.17134 and up.\ + Allows recording UWP apps, Hardware Accelerated and Exclusive Fullscreen windows.\ + Adds a yellow border on Windows 10 (not on Windows 11). + Caps at around 60 FPS. +- **BitBlt** (fastest, least compatible)\ + The best option when compatible. But it cannot properly record OpenGL, Hardware Accelerated or Exclusive Fullscreen windows.\ + The smaller the selected region, the more efficient it is. +- **Direct3D Desktop Duplication** (slower, bound to display)\ + Duplicates the desktop using Direct3D.\ + It can record OpenGL and Hardware Accelerated windows.\ + Up to 15x slower than BitBlt for tiny regions. Not affected by window size. + Limited by the target window and monitor's refresh rate. + Overlapping windows will show up and can't record across displays.\ + This option may not be available for hybrid GPU laptops, see [D3DDD-Note-Laptops.md](/docs/D3DDD-Note-Laptops.md) for a solution. +- **Force Full Content Rendering** (very slow, can affect rendering)\ + Uses BitBlt behind the scene, but passes a special flag to PrintWindow to force rendering the entire desktop.\ + About 10-15x slower than BitBlt based on original window size and can mess up some applications' rendering pipelines. ##### Linux -- **X11 XCB** (fast, requires XCB) - Uses the XCB library to take screenshots of the X11 server. -- **Scrot** (very slow, may leave files) - Uses Scrot (SCReenshOT) to take screenshots. - Leaves behind a screenshot file if interrupted. - - "scrot" must be installed: `sudo apt-get install scrot` +- **X11 XCB** (fast, requires XCB)\ + Uses the XCB library to take screenshots of the X11 server. +- **Scrot** (very slow, may leave files)\ + Uses Scrot (SCReenshOT) to take screenshots.\ + Leaves behind a screenshot file if interrupted. + + "scrot" must be installed: `sudo apt-get install scrot` ##### All platforms -- **Video Capture Device** - Uses a Video Capture Device, like a webcam, virtual cam, or capture card. +- **Video Capture Device**\ + Uses a Video Capture Device, like a webcam, virtual cam, or capture card. #### Capture Device Select the Video Capture Device that you wanna use if selecting the `Video Capture Device` Capture Method. + #### Show Live Similarity @@ -244,12 +246,13 @@ methods = [1, 0] The methods are then checked in the order you defined and the best match upon them wins. -Note: This method can cause high CPU usage at the standard comparison FPS. You should therefor limit the comparison FPS when you use this method to 1 or 2 FPS using the `fps_limit` option. +Note: This method can cause high CPU usage at the standard comparison FPS. You should therefor limit the comparison FPS when you use this method to 1 or 2 FPS using the `fps_limit` option.\ The size of the selected rectangle can also impact the CPU load (bigger = more CPU load). ### Profiles + - Profiles use the extension `.toml`. Profiles can be saved and loaded by using `File -> Save Profile As...` and `File -> Load Profile`. - The profile contains all of your settings, including information about the capture region. - You can save multiple profiles, which is useful if you speedrun multiple games. diff --git a/pyproject.toml b/pyproject.toml index 46f9cb43..a089e580 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dependencies = [ # # Linux-only dependencies "PyScreeze >=1.0.0; sys_platform == 'linux'", + "dprint-py>=0.50.0.0", "pillow >=11.0; sys_platform == 'linux'", # Python 3.13 support # Necessary for PyScreeze/ImageGrab. "python-xlib >=0.33; sys_platform == 'linux'", ] @@ -51,7 +52,7 @@ dev = [ # Linters & Formatters "mypy[faster-cache] >=1.16", "pyright[nodejs] >=1.1.400", # reportPrivateImportUsage behaviour change - "ruff >=0.11.13", # Must match .pre-commit-config.yaml + "ruff >=0.11.13", # # Types "scipy-stubs >=1.14.1.1", diff --git a/ruff.toml b/ruff.toml index bb8ebdb2..ca902063 100644 --- a/ruff.toml +++ b/ruff.toml @@ -115,7 +115,7 @@ allow-multiline = false [lint.isort] combine-as-imports = true split-on-trailing-comma = false -# When ran through pre-commit, the src-based layout detection differs +# The src-based layout detection can differ on CI known-third-party = ["gen"] # https://docs.astral.sh/ruff/settings/#mccabe diff --git a/scripts/lint.ps1 b/scripts/lint.ps1 index 747fa82d..b4306ba7 100644 --- a/scripts/lint.ps1 +++ b/scripts/lint.ps1 @@ -4,6 +4,9 @@ $originalDirectory = $pwd Set-Location "$PSScriptRoot/.." $exitCodes = 0 +Write-Host "`nRunning dprint fmt ..." +uv run --active dprint fmt + Write-Host "`nRunning Ruff check ..." uv run --active ruff check --fix $exitCodes += $LastExitCode diff --git a/uv.lock b/uv.lock index 9289c32d..3c26b346 100644 --- a/uv.lock +++ b/uv.lock @@ -33,6 +33,7 @@ name = "autosplit" version = "0" source = { virtual = "." } dependencies = [ + { name = "dprint-py", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "keyboard", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "levenshtein", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, { name = "numpy", marker = "sys_platform == 'linux' or sys_platform == 'win32'" }, @@ -77,6 +78,7 @@ dev = [ [package.metadata] requires-dist = [ + { name = "dprint-py", specifier = ">=0.50.0.0" }, { name = "keyboard", git = "https://github.com/boppreh/keyboard.git" }, { name = "levenshtein", specifier = ">=0.25" }, { name = "numpy", specifier = ">=2.1" }, @@ -128,6 +130,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4c/44/72009bb0a0d8286f6408c9cb70552350e21e9c280bfa1ef30784b30dfc0f/comtypes-1.4.10-py3-none-any.whl", hash = "sha256:e078555721ee7ab40648a3363697d420b845b323e5944b55846e96aff97d2534", size = 241481, upload-time = "2025-02-09T23:50:52.125Z" }, ] +[[package]] +name = "dprint-py" +version = "0.50.0.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/be/d4aef796fad5e4a34a621905c678d7fe0124795c1c8a65c96e71137c81b5/dprint_py-0.50.0.0-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:1dfe5b4d73aeadc10116f37341c4eac4651c85254a68e8693eb83e6928ae80de", size = 23866263, upload-time = "2025-05-19T01:26:50.035Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2e/a82a2de42d057a0e9713a497fdefe9729ce5d79d523a826c4a150e68ecc4/dprint_py-0.50.0.0-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:19cd159462841442f6d7431e36d29911f6fb63702163ad5fb5bb438ad04f6863", size = 24582934, upload-time = "2025-05-19T01:26:54.347Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3c/57941c239eef3fa74ff44fb1452e56b04cb08ae3af39669b8b7178ff80ad/dprint_py-0.50.0.0-py3-none-win_amd64.whl", hash = "sha256:90de7885d3c9d6b13949ed609f2f2256b4ea74bf1355adcdfda0c30f7e3f9d47", size = 20785813, upload-time = "2025-05-19T01:26:57.162Z" }, +] + [[package]] name = "ewmhlib" version = "0.2"