├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── SECURITY.md ├── assets │ ├── accent_border.png │ ├── accent_titlebar.png │ ├── all_stuffs.png │ ├── border_color.png │ ├── center_relative.png │ ├── center_relative_example.gif │ ├── circle_motion.gif │ ├── corner_round.png │ ├── corner_small_round.png │ ├── corner_square.png │ ├── dwm_cloak_example.gif │ ├── dwm_no_cloak_example.gif │ ├── dwm_rtl.png │ ├── dwm_transitions_disabled.gif │ ├── dwm_transitions_enabled.gif │ ├── flashing.gif │ ├── hPyT-preview-app.mp4 │ ├── maximize.png │ ├── maximize_minimize.png │ ├── minimize.png │ ├── no_span.gif │ ├── opacity.png │ ├── rainbow_border.gif │ ├── rainbow_titlebar.gif │ ├── span.gif │ ├── stylize_text.gif │ ├── synchronization-example.gif │ ├── title_text_color.png │ ├── titlebar.png │ └── titlebar_color.png ├── dependabot.yml └── workflows │ ├── github-ci.yaml │ └── python-publish.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── examples ├── kivy-example.py ├── pyQt-example.py ├── pyside-example.py ├── rainbow-synchronization-example.py ├── tkinter-example.py └── wx-example.py ├── hPyT ├── __init__.py └── hPyT.py └── setup.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | ## Environment 14 | 15 | Which environment were you using when you encountered the problem? 16 | 17 | ```bash 18 | $ python -m platform 19 | # TODO: Your output goes here 20 | 21 | $ python --version 22 | # TODO: Your output goes here 23 | 24 | $ pip show hPyT 25 | # TODO: Your output goes here 26 | ``` 27 | 28 | ## Code 29 | This is a minimal, complete example that shows the issue: 30 | 31 | ```python 32 | # TODO: Your code goes here 33 | ``` 34 | 35 | ## Traceback 36 | 37 | This is the complete traceback I see: 38 | 39 | ``` 40 | # TODO: Your traceback goes here (if applicable) 41 | ``` 42 | 43 | ## Expected behavior 44 | A clear and concise description of what you expected to happen. 45 | 46 | ## Screenshots 47 | If applicable, add screenshots to help explain your problem. 48 | 49 | ## Additional context 50 | Add any other context about the problem here. 51 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEAT]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Explanation 11 | 12 | Explain briefly what you want to achieve. 13 | 14 | ## Is your feature request related to a problem? Please describe. 15 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 16 | 17 | ## Describe the solution you'd like 18 | A clear and concise description of what you want to happen. 19 | 20 | ## Code Example 21 | 22 | How would your feature be used? (Remove this if it is not applicable.) 23 | 24 | ```python 25 | from hPyT import * 26 | 27 | ... # your new feature in action! 28 | ``` 29 | 30 | **Additional context** 31 | Add any other context or screenshots about the feature request here. 32 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security fixes are applied to the latest version. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you find a potential security issue, please report it to admin@spoo.me (the current maintainer). 10 | 11 | We will try to find a fix in a timely manner and will then issue a security advisory together with the update via GitHub. 12 | 13 | If you don't get a reaction within 30 days, please open a public issue on GitHub. 14 | -------------------------------------------------------------------------------- /.github/assets/accent_border.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/accent_border.png -------------------------------------------------------------------------------- /.github/assets/accent_titlebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/accent_titlebar.png -------------------------------------------------------------------------------- /.github/assets/all_stuffs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/all_stuffs.png -------------------------------------------------------------------------------- /.github/assets/border_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/border_color.png -------------------------------------------------------------------------------- /.github/assets/center_relative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/center_relative.png -------------------------------------------------------------------------------- /.github/assets/center_relative_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/center_relative_example.gif -------------------------------------------------------------------------------- /.github/assets/circle_motion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/circle_motion.gif -------------------------------------------------------------------------------- /.github/assets/corner_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/corner_round.png -------------------------------------------------------------------------------- /.github/assets/corner_small_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/corner_small_round.png -------------------------------------------------------------------------------- /.github/assets/corner_square.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/corner_square.png -------------------------------------------------------------------------------- /.github/assets/dwm_cloak_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/dwm_cloak_example.gif -------------------------------------------------------------------------------- /.github/assets/dwm_no_cloak_example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/dwm_no_cloak_example.gif -------------------------------------------------------------------------------- /.github/assets/dwm_rtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/dwm_rtl.png -------------------------------------------------------------------------------- /.github/assets/dwm_transitions_disabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/dwm_transitions_disabled.gif -------------------------------------------------------------------------------- /.github/assets/dwm_transitions_enabled.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/dwm_transitions_enabled.gif -------------------------------------------------------------------------------- /.github/assets/flashing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/flashing.gif -------------------------------------------------------------------------------- /.github/assets/hPyT-preview-app.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/hPyT-preview-app.mp4 -------------------------------------------------------------------------------- /.github/assets/maximize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/maximize.png -------------------------------------------------------------------------------- /.github/assets/maximize_minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/maximize_minimize.png -------------------------------------------------------------------------------- /.github/assets/minimize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/minimize.png -------------------------------------------------------------------------------- /.github/assets/no_span.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/no_span.gif -------------------------------------------------------------------------------- /.github/assets/opacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/opacity.png -------------------------------------------------------------------------------- /.github/assets/rainbow_border.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/rainbow_border.gif -------------------------------------------------------------------------------- /.github/assets/rainbow_titlebar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/rainbow_titlebar.gif -------------------------------------------------------------------------------- /.github/assets/span.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/span.gif -------------------------------------------------------------------------------- /.github/assets/stylize_text.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/stylize_text.gif -------------------------------------------------------------------------------- /.github/assets/synchronization-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/synchronization-example.gif -------------------------------------------------------------------------------- /.github/assets/title_text_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/title_text_color.png -------------------------------------------------------------------------------- /.github/assets/titlebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/titlebar.png -------------------------------------------------------------------------------- /.github/assets/titlebar_color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zingzy/hPyT/fb88c455ae10185f82b9382010008cc35113137f/.github/assets/titlebar_color.png -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/github-ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - '**/*.md' 8 | - '**/*.rst' 9 | pull_request: 10 | branches: [ main ] 11 | paths-ignore: 12 | - '**/*.md' 13 | - '**/*.rst' 14 | workflow_dispatch: 15 | 16 | jobs: 17 | codestyle: 18 | name: Check code style issues 19 | runs-on: windows-latest 20 | 21 | steps: 22 | - name: Checkout Code 23 | uses: actions/checkout@v4 24 | with: 25 | submodules: 'recursive' 26 | 27 | - name: Setup Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: "3.12" 31 | 32 | - name: Upgrade pip 33 | run: python -m pip install --upgrade pip 34 | 35 | - name: Install hPyT 36 | run: | 37 | python -m pip install . 38 | 39 | - name: Install Dependencies 40 | run: | 41 | python -m pip install ruff 42 | python -m pip install mypy 43 | 44 | - name: Test and Lint with Ruff 45 | run: | 46 | ruff check . 47 | ruff format --check . 48 | 49 | - name: Test with Mypy 50 | run: | 51 | mypy hPyT -------------------------------------------------------------------------------- /.github/workflows/python-publish.yaml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.9' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish package 28 | uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc 29 | with: 30 | user: __token__ 31 | password: ${{ secrets.PYPI_API_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # UV 98 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | uv.lock 102 | 103 | # poetry 104 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 105 | # This is especially recommended for binary packages to ensure reproducibility, and is more 106 | # commonly ignored for libraries. 107 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 108 | poetry.lock 109 | 110 | # pdm 111 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 112 | #pdm.lock 113 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 114 | # in version control. 115 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 116 | .pdm.toml 117 | .pdm-python 118 | .pdm-build/ 119 | 120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 121 | __pypackages__/ 122 | 123 | # Celery stuff 124 | celerybeat-schedule 125 | celerybeat.pid 126 | 127 | # SageMath parsed files 128 | *.sage.py 129 | 130 | # Environments 131 | .env 132 | .venv 133 | env/ 134 | venv/ 135 | ENV/ 136 | env.bak/ 137 | venv.bak/ 138 | 139 | # Spyder project settings 140 | .spyderproject 141 | .spyproject 142 | 143 | # Rope project settings 144 | .ropeproject 145 | 146 | # mkdocs documentation 147 | /site 148 | 149 | # mypy 150 | .mypy_cache/ 151 | .dmypy.json 152 | dmypy.json 153 | 154 | # Pyre type checker 155 | .pyre/ 156 | 157 | # pytype static type analyzer 158 | .pytype/ 159 | 160 | # Cython debug symbols 161 | cython_debug/ 162 | 163 | # PyCharm 164 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 165 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 166 | # and can be added to the global gitignore or merged into this file. For a more nuclear 167 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 168 | #.idea/ 169 | 170 | # PyPI configuration file 171 | .pypirc 172 | 173 | # ruff 174 | .ruff_cache/ 175 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | support@spoo.me. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zingzy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hPyT - Hack Python Titlebar 2 | 3 | A package to manipulate windows and titlebar of GUI applications made using python 4 | **Supports Windows 7-11** 5 | 6 | https://github.com/Zingzy/hPyT/assets/90309290/f86df1c7-b75b-4477-974a-eb34cc117df3 7 | 8 | **You can download the above hPyT-preview-app from [github releases](https://github.com/Zingzy/hPyT-preview-app/releases/latest/) to test out the package before using it in your projects** 9 | 10 |
11 | 12 |
13 | 14 | 📖 Table of Contents 15 | 16 | - [hPyT - Hack Python Titlebar](#hpyt---hack-python-titlebar) 17 | - [📚 Supported Libraries](#-supported-libraries) 18 | - [📦 Installing](#-installing) 19 | - [📥 Importing](#-importing) 20 | - [NEW Features in `v1.4.0` 🎉](#new-features-in-v140-) 21 | - [Hide/Unhide TitleBar](#hideunhide-titlebar) 22 | - [**Understanding Window Geometry**](#understanding-window-geometry) 23 | - [**Impact of Hiding the Title Bar**](#impact-of-hiding-the-title-bar) 24 | - [**Potential Issues**](#potential-issues) 25 | - [**Solution**](#solution) 26 | - [Example Usage](#example-usage) 27 | - [Comparision of the dimensions with and without `no_span=True`:](#comparision-of-the-dimensions-with-and-without-no_spantrue) 28 | - [Visual Example:](#visual-example) 29 | - [🌈 Rainbow TitleBar](#-rainbow-titlebar) 30 | - [🌈 Rainbow Border](#-rainbow-border) 31 | - [🔄 Synchronizing the Rainbow Effect with other elements](#-synchronizing-the-rainbow-effect-with-other-elements) 32 | - [Hide/Unhide both Maximize and Minimize Buttons (Completely Hides both buttons)](#hideunhide-both-maximize-and-minimize-buttons-completely-hides-both-buttons) 33 | - [Hide/Unhide All Buttons or Stuffs](#hideunhide-all-buttons-or-stuffs) 34 | - [Enable/Disable Maximize Button](#enabledisable-maximize-button) 35 | - [Enable/Disable Minimize Button](#enabledisable-minimize-button) 36 | - [🎨 Custom TitleBar Color](#-custom-titlebar-color) 37 | - [Set TitleBar Color to windows Accent Color](#set-titlebar-color-to-windows-accent-color) 38 | - [🖌️ Custom TitleBar Text Color](#️-custom-titlebar-text-color) 39 | - [🖌️ Custom Border Color](#️-custom-border-color) 40 | - [Set Border Color to windows Accent Color](#set-border-color-to-windows-accent-color) 41 | - [Window Corner Radius](#window-corner-radius) 42 | - [Window DWM Manipulation](#window-dwm-manipulation) 43 | - [Enable RTL Layout for the DWM Window](#enable-rtl-layout-for-the-dwm-window) 44 | - [Disable DWM Transitions for the DWM Window](#disable-dwm-transitions-for-the-dwm-window) 45 | - [Window Cloaking](#window-cloaking) 46 | - [Example of window rendering with and without cloacking](#example-of-window-rendering-with-and-without-cloacking) 47 | - [Opacity](#opacity) 48 | - [⚡ Flashing Window](#-flashing-window) 49 | - [💻 Window Management](#-window-management) 50 | - [Center a window on the screen](#center-a-window-on-the-screen) 51 | - [Center a secondary window relative to the primary window](#center-a-secondary-window-relative-to-the-primary-window) 52 | - [Other basic window management functions](#other-basic-window-management-functions) 53 | - [✨ Window Animations](#-window-animations) 54 | - [Circle Motion](#circle-motion) 55 | - [Verical Shake](#verical-shake) 56 | - [Horizontal Shake](#horizontal-shake) 57 | - [✏️ Stylize text](#️-stylize-text) 58 | - [Workaround for other libraries](#workaround-for-other-libraries) 59 | - [Miscellaneous](#miscellaneous) 60 | - [Get Windows Accent Color](#get-windows-accent-color) 61 | - [Stylize text](#stylize-text) 62 | - [📜 hPyT Changelog](#-hpyt-changelog) 63 | - [v1.4.0](#v140) 64 | - [v1.3.7](#v137) 65 | - [v1.3.6](#v136) 66 | - [v1.3.5](#v135) 67 | - [v1.3.4](#v134) 68 | - [v1.3.3](#v133) 69 | - [v1.3.2](#v132) 70 | - [v1.3.1](#v131) 71 | - [v1.3.0](#v130) 72 | - [v1.2.1](#v121) 73 | - [v1.2.0](#v120) 74 | - [v1.1.3](#v113) 75 | - [v1.1.2](#v112) 76 | - [v1.1.1](#v111) 77 | - [v1.1.0](#v110) 78 |
79 | 80 | --- 81 | 82 | ## 📚 Supported Libraries 83 | 84 | - Tkinter & CustomTkinter 85 | - PyQt 86 | - PySide 87 | - WxPython 88 | - Kivy 89 | - Almost all other UI libraries 90 | 91 | > [!IMPORTANT] 92 | > follow this [section](#workaround-for-other-libraries) to see how to use hPyT with other libraries 93 | 94 | ## 📦 Installing 95 | 96 | ```powershell 97 | pip install hPyT==1.4.0 98 | ``` 99 | 100 | ## 📥 Importing 101 | 102 | ```python 103 | from hPyT import * 104 | from customtkinter import * # you can use any other library from the above mentioned list 105 | 106 | window = CTk() # creating a window using CustomTkinter 107 | ``` 108 | 109 | ## NEW Features in `v1.4.0` 🎉 110 | 111 | - Function to change `corner radius` of the window with the [`hPyT.corner_radius`](#window-corner-radius) module 112 | - Functions to manipulate `DWM` window attributes with the [`hPyT.window_dwm`](#window-dwm-manipulation) module 113 | - Enable **RTL layout** 114 | - Disable **DWM transitions** 115 | - Cloak the window 116 | 117 | ## Hide/Unhide TitleBar 118 | 119 | ```python 120 | title_bar.hide(window, no_span = False) # hides full titlebar 121 | # optional parameter : no_span, more details in the note below 122 | # title_bar.unhide(window) 123 | ``` 124 | 125 | | Parameter | Type | Default | Description | 126 | | :-------: | :--: | :-----: | :----------: | 127 | | `no_span` | `bool` | `False` | If `True`, the content area height will not be adjusted to accommodate the title bar. | 128 | 129 | ![Hide Titlebar preview](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/titlebar.png) 130 | 131 |
132 |

❗Important Note when hiding the titlebar and using no_span parameter

133 | 134 | When hiding a title bar, the application window's total geometry and its content area geometry behave differently, which may introduce ambiguities. Here's a detailed explanation of the issue: 135 | 136 | #### **Understanding Window Geometry** 137 | 138 | 1. **Full Window Dimensions**: 139 | - Includes the content area, title bar, and borders. 140 | - When the user specifies dimensions (e.g., `400x400`), it usually represents the **content area dimensions**. The total window height becomes `content height + title bar height + border width`. 141 | - The color of the `top border` and `title bar` is usually the same in **windows 11 & 10**, making it appear as a single entity. So when hiding the title bar, we also need to hide the top border. 142 | - However, in **windows 7 & 8**, the top border is a different color from the title bar, so we don't need to hide the top border when hiding the title bar. Moreover removing the top border will make the window behave abnormally in these versions. 143 | 144 | 2. **Content Area Dimensions**: 145 | - Represents only the usable area inside the window, excluding the title bar and borders. 146 | 147 | ### **Impact of Hiding the Title Bar** 148 | 149 | When the title bar is hidden: 150 | - The **content area height** expands to occupy the height previously used by the title bar. For example, a `400x400` content area might expand to `400x438` (assuming the visual title bar height is 38px). 151 | 152 | Better illustrated in the following example: 153 | 154 | ```python 155 | ... 156 | 157 | def show_window_dimensions(): 158 | hwnd: int = ctypes.windll.user32.GetForegroundWindow() 159 | 160 | x_with_decorations: int = root.winfo_rootx() # X position of the full window 161 | y_with_decorations: int = root.winfo_rooty() # Y position of the full window 162 | 163 | x_without_decorations: int = root.winfo_x() # X position of the content area 164 | y_without_decorations: int = root.winfo_y() # Y position of the content area 165 | 166 | titlebar_height: int = y_with_decorations - y_without_decorations 167 | border_width: int = x_with_decorations - x_without_decorations 168 | 169 | window_rect: RECT = get_window_rect(hwnd) 170 | 171 | width: int = window_rect.right - window_rect.left 172 | height: int = window_rect.bottom - window_rect.top 173 | 174 | print(f"Title bar height: {titlebar_height}") 175 | print(f"Border width: {border_width}") 176 | print(f"Main window dimensions: {width}x{height}") 177 | print( 178 | f"Content window dimensions: {root.winfo_geometry()}" 179 | ) # This will return the dimensions of the content area only 180 | 181 | ... 182 | 183 | def click(e=None): 184 | root.update_idletasks() 185 | 186 | print("------ Before hiding title bar ------") 187 | show_window_dimensions() 188 | 189 | title_bar.hide(root) 190 | is_hidden = True 191 | 192 | print("------ After hiding title bar ------") 193 | show_window_dimensions() 194 | 195 | 196 | button = CTkButton(root, text="Click Me", command=click) 197 | button.place(relx=0.5, rely=0.5, anchor="center") 198 | 199 | root.mainloop() 200 | ``` 201 | 202 | Output: 203 | 204 | ```cmd 205 | ------ Before hiding title bar ------ 206 | Title bar height: 38 207 | Border width: 9 208 | Main window dimensions: 468x497 209 | Content window dimensions: 450x450 210 | ------ After hiding title bar ------ 211 | Title bar height: 0 212 | Border width: 9 213 | Main window dimensions: 468x497 214 | Content window dimensions: 450x488 215 | ``` 216 | 217 | By the above example, you can see that the content area height has increased from `450px` to `488px` after hiding the title bar. 218 | 219 | ### **Potential Issues** 220 | This automatic resizing may cause layout problems or unintended behavior in some applications. For instance: 221 | - UI elements might **overlap** or **stretch**. 222 | - Custom layouts may require recalibration. 223 | 224 | ### **Solution** 225 | To address this, a `no_span` parameter is introduced in the `hide` method. This parameter allows users to control whether the content area height should be adjusted dynamically to maintain its original size. 226 | 227 | - **Default Behavior (`no_span=False`)**: 228 | The content area height will expand to occupy the title bar's space. 229 | - **With `no_span=True`**: 230 | The content area will be resized dynamically to maintain its original dimensions. 231 | 232 | ### Example Usage 233 | 234 | ```python 235 | title_bar.hide(root, no_span=True) 236 | ``` 237 | 238 | ### Comparision of the dimensions with and without `no_span=True`: 239 | 240 | ```diff 241 | - Content window dimensions: 450x488 242 | + Content window dimensions: 450x450 243 | 244 | - Main window dimensions: 468x497 245 | + Main window dimensions: 468x459 246 | ``` 247 | 248 | ### Visual Example: 249 | 250 |
251 | 252 | | `no_span = False` | `no_span = True` | 253 | | :---------------: | :--------------: | 254 | | Height of the Content area changes when the no_span paramer is set to False by default | Height of the Content area does not change when the no_span paramer is set to False by default | 255 | 256 |
257 | 258 | --- 259 | 260 |
261 | 262 | ## 🌈 Rainbow TitleBar 263 | 264 | This feature is only supported on Windows 11. 265 | 266 | ```python 267 | rainbow_title_bar.start(window, interval=5) # starts the rainbow titlebar 268 | # rainbow_title_bar.stop(window) # stops the rainbow titlebar 269 | ``` 270 | 271 | | Parameter | Type | Default | Description | 272 | | :-------: | :--: | :-----: | :----------: | 273 | | `interval` | `int` | `5` | The time in milliseconds in which the color would change. | 274 | | `color_stops` | `int` | `5` | The number of color stops between each RGB value. | 275 | 276 | ![Rainbow TitleBar](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/rainbow_titlebar.gif) 277 | 278 | ## 🌈 Rainbow Border 279 | 280 | This feature is only supported on Windows 11. 281 | 282 | ```python 283 | rainbow_border.start(window, interval=4) # starts the rainbow border 284 | # rainbow_border.stop(window) # stops the rainbow border 285 | ``` 286 | 287 | | Parameter | Type | Default | Description | 288 | | :-------: | :--: | :-----: | :----------: | 289 | | `interval` | `int` | `5` | The time in milliseconds in which the color would change. | 290 | | `color_stops` | `int` | `5` | The number of color stops between each RGB value. | 291 | 292 | ![Rainbow Border](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/rainbow_border.gif) 293 | 294 | ### 🔄 Synchronizing the Rainbow Effect with other elements 295 | 296 | ```python 297 | from hPyT import * 298 | 299 | ... 300 | 301 | rainbow_title_bar.start(window, interval=30) # starts the rainbow titlebar 302 | # rainbow_border.start(window, interval=30) # also works with rainbow border 303 | 304 | current_color = rainbow_title_bar.get_current_color() # or rainbow_border.get_current_color() 305 | # you can use this color to synchronize the color of other elements with the titlebar 306 | ``` 307 | 308 | ![synchronization example](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/synchronization-example.gif) 309 | 310 | 311 | **Code for the above illustration available in [`/examples/rainbow-synchronization-example.py`](https://github.com/Zingzy/hPyT/blob/main/examples/rainbow-synchronization-example.py)** 312 | 313 | 314 | ## Hide/Unhide both Maximize and Minimize Buttons (Completely Hides both buttons) 315 | 316 | ```python 317 | maximize_minimize_button.hide(window) # hides both maximize and minimize button 318 | # maximize_minimize_button.unhide(window) 319 | ``` 320 | 321 | ![Hidden Maximize and Minimize Buttons](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/maximize_minimize.png) 322 | 323 | ## Hide/Unhide All Buttons or Stuffs 324 | 325 | ```python 326 | all_stuffs.hide(window) # hides close button 327 | # all_stuffs.unhide(window) 328 | ``` 329 | 330 | ![Hide Everything](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/all_stuffs.png) 331 | 332 | > [!TIP] 333 | > **To hide the text set the window title to `''`** 334 | 335 | ## Enable/Disable Maximize Button 336 | 337 | ```python 338 | maximize_button.disable(window) # hides maximize button 339 | # MaximizeButton.enable(window) 340 | ``` 341 | 342 | ![Disabled Maximize Button](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/maximize.png) 343 | 344 | ## Enable/Disable Minimize Button 345 | 346 | ```python 347 | minimize_button.disable(window) # hides minimize button 348 | # MinimizeButton.enable(window) 349 | ``` 350 | 351 | ![Disabled Minimize Button](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/minimize.png) 352 | 353 | ## 🎨 Custom TitleBar Color 354 | 355 | This feature is only supported on Windows 11. 356 | 357 | ```python 358 | title_bar_color.set(window, color='#ff00ff') # sets the titlebar color to magenta 359 | # title_bar_color.reset(window) # resets the titlebar color to default 360 | ``` 361 | 362 | | Parameter | Type | Default | Description | 363 | | :-------: | :--: | :-----: | :----------: | 364 | | `color` | `Union[str, Tuple[int]]` | ❌ | The color to set the titlebar to in either Hex (string) or RGB (tuple of integers) format | 365 | 366 | 367 | ![Custom TitleBar Color](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/titlebar_color.png) 368 | 369 | ### Set TitleBar Color to windows Accent Color 370 | 371 | ```python 372 | title_bar_color.set_accent(window) # sets the titlebar color to the current windows accent color 373 | ``` 374 | 375 | ![Accent TitleBar](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/accent_titlebar.png) 376 | 377 | > [!NOTE] 378 | > *The titlebar color will automatically change when the windows accent color changes* 379 | 380 | ## 🖌️ Custom TitleBar Text Color 381 | 382 | This feature is only supported on Windows 11. 383 | 384 | ```python 385 | title_bar_text_color.set(window, color='#ff00ff') # sets the titlebar text color to magenta 386 | # title_bar_text_color.reset(window) # resets the titlebar text color to default 387 | ``` 388 | 389 | | Parameter | Type | Default | Description | 390 | | :-------: | :--: | :-----: | :----------: | 391 | | `color` | `Union[str, Tuple[int]]` | ❌ | The color to set the titlebar text to in either Hex (string) or RGB (tuple of integers) format | 392 | 393 | ![Custom TitleBar Text Color](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/title_text_color.png) 394 | 395 | ## 🖌️ Custom Border Color 396 | 397 | This feature is only supported on Windows 11. 398 | 399 | ```python 400 | border_color.set(window, color='#ff00ff') # sets the border color to magenta 401 | # border_color.reset(window) # resets the border color to default 402 | ``` 403 | 404 | | Parameter | Type | Default | Description | 405 | | :-------: | :--: | :-----: | :----------: | 406 | | `color` | `Union[str, Tuple[int]]` | ❌ | The color to set the border to in either Hex (string) or RGB (tuple of integers) format | 407 | 408 | ![Custom Border Color](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/border_color.png) 409 | 410 | ### Set Border Color to windows Accent Color 411 | 412 | ```python 413 | border_color.set_accent(window) # sets the border color to the current windows accent color 414 | ``` 415 | 416 | ![Accent Border](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/accent_border.png) 417 | 418 | > [!NOTE] 419 | > *The border color will automatically change when the windows accent color changes* 420 | 421 | ## Window Corner Radius 422 | 423 | This feature is only supported on Windows 11. 424 | 425 | ```python 426 | corner_radius.set(window, style="round-small") # sets the window border radius to round-small 427 | # corner_radius.reset(window) # resets the window border radius to default 428 | ``` 429 | 430 | **List of available styles**: 431 | 432 |
433 | 434 | | Style | Preview | 435 | | :---: | :---: | 436 | | `round-small` | Round-small | 437 | | `square` | Square | 438 | | `round` | Round | 439 | 440 |
441 | 442 | ## Window DWM Manipulation 443 | 444 | `DWM` - **Desktop Window Manager** is the component of a window which controls `non-client area of the window`. The features below are generally used to improve the **accessibility** of a window. 445 | 446 | ### Enable RTL Layout for the DWM Window 447 | 448 | ```python 449 | window_dwm.toggle_rtl_layout(window, enabled=True) # enables RTL layout for the window 450 | # window_dwm.toggle_rtl_layout(window, enabled=False) # disables RTL layout for the window 451 | ``` 452 | 453 | ![RTL Layout](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/dwm_rtl.png) 454 | 455 | > [!NOTE] 456 | > *This feature enables the **RTL layout** for the **non-client area** of the window which includes the **titlebar**, **border**, etc.* 457 | 458 | ### Disable DWM Transitions for the DWM Window 459 | 460 | Disabling DWM Transitions will make the animations for minimize, maximize, restore, etc. more snappy and faster. 461 | 462 | ```python 463 | window_dwm.toggle_dwm_transitions(window, enabled=False) # disables DWM transitions for the window 464 | # window_dwm.toggle_dwm_transitions(window, enabled=True) # enables DWM transitions for the window 465 | ``` 466 | 467 |
468 | 469 | | DWM Transitions `Disabled` | DWM Transitions `Enabled` | 470 | | :----------------------: | :---------------------: | 471 | | ![DWM Transitions Disabled](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/dwm_transitions_disabled.gif) | ![DWM Transitions Enabled](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/dwm_transitions_enabled.gif) | 472 | 473 |
474 | 475 | > [!NOTE] 476 | > *This will only affect the minimize, maximize, restore, etc. animations. It will not affect custom animations.* 477 | 478 | > [!IMPORTANT] 479 | > *This feature won't work if the global animations are disabled by the user in the windows settings.* 480 | 481 | ### Window Cloaking 482 | 483 | If window cloaking is enabled, the window will be hidden while still being composed by DWM i.e the window will be rendered but it will not be visible to the user. 484 | 485 | ```python 486 | window_dwm.toggle_cloak(window, enabled=True) # hides the window 487 | 488 | # Do complex taks like rendering window widgets 489 | 490 | window_dwm.toggle_cloak(window, enabled=False) # shows the window 491 | ``` 492 | 493 | > [!TIP] 494 | > *By cloaking a window, `DWM` can optimize the **rendering process** since it doesn't have to display the window's content on the screen. This can help **improve the performance** of applications, especially those with complex UI elements or animations.* 495 | 496 | #### Example of window rendering with and without cloacking 497 | 498 |
499 | 500 | | Cloacking Used | Cloacking Not Used | 501 | | :------------: | :---------------: | 502 | | ![Cloacking Used](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/dwm_cloak_example.gif) | ![Cloacking Not Used](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/dwm_no_cloak_example.gif) | 503 | 504 |
505 | 506 | ## Opacity 507 | 508 | ```python 509 | opacity.set(window, opacity=0.5) # sets the window opacity to 50% 510 | # opacity.set(window, opacity=1.0) # resets the window opacity to 100% 511 | ``` 512 | 513 | | Parameter | Type | Default | Description | 514 | | :-------: | :--: | :-----: | :----------: | 515 | | `opacity` | `float` | ❌ | The opacity to set the window to. It should be a value between 0 (**transparent**) and 1.0 (**opaque**). | 516 | 517 | ![Opacity 0.5 preview](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/opacity.png) 518 | 519 | ## ⚡ Flashing Window 520 | 521 | ```python 522 | window_flash.flash(window, count=10, interval=100) # flashes the window 10 times with 100ms interval 523 | # window_flash.stop(window) # stops the flashing immediately 524 | ``` 525 | 526 | | Parameter | Type | Default | Description | 527 | | :-------: | :--: | :-----: | :----------: | 528 | | `count` | `int` | `5` | The number of times the window will flash. | 529 | | `interval` | `int` | `1000` | The time in milliseconds in which the window will flash. | 530 | 531 | ![Flashing Window](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/flashing.gif) 532 | 533 | ## 💻 Window Management 534 | 535 | ### Center a window on the screen 536 | 537 | ```python 538 | window_frame.center(window) 539 | ``` 540 | 541 | ### Center a secondary window relative to the primary window 542 | 543 | ```python 544 | window_frame.center_relative(window, child_window) 545 | ``` 546 | 547 | ![Center Relative](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/center_relative.png) 548 | 549 | **Example Usecase** 550 | 551 | ```python 552 | window = CTk() 553 | window.title("Primary Window") 554 | window.geometry('600x300') 555 | window_frame.center(window) 556 | 557 | child_window = CTkToplevel() 558 | 559 | window_frame.center_relative(window, child_window) 560 | 561 | window.bind("", lambda event: window_frame.center_relative(window, child_window)) 562 | 563 | def on_close(): # this is required to stop the window from centering the child window after the parent window is closed 564 | window.unbind("") 565 | child_window.destroy() 566 | 567 | child_window.protocol("WM_DELETE_WINDOW", on_close) 568 | 569 | window.mainloop() 570 | ``` 571 | 572 | ![Center Relative Example](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/center_relative_example.gif) 573 | 574 | ### Other basic window management functions 575 | 576 | ```python 577 | window_frame.move(window, 100, 100) # moves the window to (100, 100) 578 | window_frame.resize(window, 500, 500) # resizes the window to 500x500 579 | window_frame.maximize(window) # maximizes the window 580 | window_frame.minimize(window) # minimizes the window 581 | window_frame.restore(window) # restores the window 582 | ``` 583 | 584 | ## ✨ Window Animations 585 | 586 | ### Circle Motion 587 | 588 | ```python 589 | window_animation.circle_motion(window, count=5, interval=5, radius=30) 590 | # moves the window in a circular motion 5 times with 5ms interval and 30px radius 591 | ``` 592 | 593 | | Parameter | Type | Default | Description | 594 | | :-------: | :--: | :-----: | :----------: | 595 | | `count` | `int` | `5` | The number of times the window will move in a circular motion. | 596 | | `interval` | `int` | `5` | The time in milliseconds in which the window will move in a circular motion. | 597 | | `radius` | `int` | `20` | The radius of the circular motion. | 598 | 599 | ![Circle Motion](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/circle_motion.gif) 600 | 601 | *The animation might appear a bit fasterer and rougher in the above preview gif than it is* 602 | 603 | ### Verical Shake 604 | 605 | ```python 606 | window_animation.vertical_shake(window, count=5, interval=5, amplitude=20) 607 | # shakes the window vertically 5 times with 5ms interval and 10px distance 608 | ``` 609 | 610 | | Parameter | Type | Default | Description | 611 | | :-------: | :--: | :-----: | :----------: | 612 | | `count` | `int` | `5` | The number of times the window will shake vertically. | 613 | | `interval` | `int` | `5` | The time in milliseconds in which the window will shake vertically. | 614 | | `amplitude` | `int` | `20` | The distance the window will shake vertically. | 615 | 616 | ### Horizontal Shake 617 | 618 | ```python 619 | window_animation.horizontal_shake(window, count=5, interval=5, amplitude=20) 620 | # shakes the window horizontally 5 times with 5ms interval and 10px distance 621 | ``` 622 | 623 | | Parameter | Type | Default | Description | 624 | | :-------: | :--: | :-----: | :----------: | 625 | | `count` | `int` | `5` | The number of times the window will shake horizontally. | 626 | | `interval` | `int` | `5` | The time in milliseconds in which the window will shake horizontally. | 627 | | `amplitude` | `int` | `20` | The distance the window will shake horizontally. | 628 | 629 | ## ✏️ Stylize text 630 | 631 | ```python 632 | title_text.stylize(window, style=1) 633 | ``` 634 | 635 | **Choose any style from 1 to 10.** 636 | 637 | *Below is a gif demonstrating all of the available styles* 638 | 639 | ![Stylize Text](https://raw.githubusercontent.com/zingzy/hPyT/main/.github/assets/stylize_text.gif) 640 | 641 | > [!NOTE] 642 | > *It is recommended to use the `title_text.set` function to change the title of the window instead of directly setting the title of the window.* 643 | > 644 | > *This is because the `title_text.set` function also caches the original title and the styled title so that it can be used to verify the consistency of the title before applying the style.* 645 | 646 | ## Workaround for other libraries 647 | 648 | This menthod is applicable for any other UI library like pygame pySimpleGUI, etc. which are not mentioned in the [supported libraries list](#-supported-libraries). You just need to pass the `hwnd` of the window instead of the `window object` to the functions as demonstrated below. 649 | 650 | ```python 651 | import tkinter as tk 652 | from hPyT import * 653 | import ctypes 654 | 655 | # make sure that the window has been created and is active 656 | 657 | hwnd = ctypes.windll.user32.GetActiveWindow() 658 | rainbow_border.start(hwnd, interval=4) # use the hwnd of the window instead of the window object 659 | ``` 660 | 661 | [Back to top](#-installing) 662 | 663 | ## Miscellaneous 664 | 665 | ### Get Windows Accent Color 666 | 667 | ```python 668 | print(get_accent_color()) # prints the current windows accent color 669 | >>> '#1b595a' 670 | ``` 671 | 672 | ### Stylize text 673 | 674 | ```python 675 | print(stylize_text("Your Custom Text", style=1)) # stylizes your text 676 | # all of the styles shown on the above gif are available 677 | >>> "𝔜𝔬𝔲𝔯 ℭ𝔲𝔰𝔱𝔬𝔪 𝔗𝔢𝔵𝔱" 678 | ``` 679 | 680 |
681 | 682 | ## 📜 hPyT Changelog 683 | 684 | ### v1.4.0 685 | 686 | - Add new feature for customizing the corner radius of the window 687 | - Add new feature for manipulating the non-client area of the window 688 | - Fix the issue with stylize text not looking for changes made by the user 689 | - Fix the issue with title text not being consistent on older versions of windows 690 | - Add support for x86/x32 pythonarchitecture 691 | 692 | ### v1.3.7 693 | 694 | - Fix color conversion issue which returned the wrong color when the windows accent color was set to a custom color 695 | - Add handling for WM_NCACTIVATE and WM_NCPAINT messages to improve title bar rendering 696 | - Add dynamic height adjustment to hide_titlebar method using the no_span parameter 697 | 698 | ### v1.3.6 699 | 700 | - Minor Bug Fixes 701 | 702 | ### v1.3.5 703 | 704 | - Add feature for automatically changing the accent color of the titlebar and border 705 | - Fix an issue which caused ImportError when used with a python version less than 3.9 706 | 707 | ### v1.3.4 708 | 709 | - Add method for applying the current windows accent color to the titlebar and border color 710 | - Add method for getting the current windows accent color 711 | - Add type annotations and docstrings to functions for better clarity and autocompletion 712 | 713 | ### v1.3.3 714 | 715 | - Fixed taskbar unhide/hide bug 716 | 717 | ### v1.3.2 718 | 719 | - Add support for synchronizing the rainbow effect with other ui elements. 720 | 721 | ### v1.3.1 722 | 723 | - Add support for UI libraries like Kivy, PySimpleGUI, PyGame, etc. 724 | - Improve the rainbow titlebar & border effects. 725 | - Improve the center_relative function & examples. 726 | 727 | ### v1.3.0 728 | 729 | - Add support for setting custom border color 730 | - Add support for rainbow border color effect 731 | - Add support for resetting the titleBar color and titleText color 732 | - Fix an issue which caused the titleBar to appear black after the rainbow titleBar effect was stopped 733 | 734 | ### v1.2.1 735 | 736 | - Minor Bug Fixes 737 | 738 | ### v1.2.0 739 | 740 | - Add support for rainbow titlebar 741 | - Add support for styling title text 742 | - Add support for vertical, horizontal shake and circle motion window animations 743 | - Add support for centering a window on the screen 744 | - Add support for centering a window relative to another window 745 | - Add support for moving/resizing/maximizing/minimizing/restoring a window 746 | - Add support for setting custom titlebar color 747 | - Add support for setting custom titlebar text color 748 | 749 | ### v1.1.3 750 | 751 | - Add flashing inverval support 752 | 753 | ### v1.1.2 754 | 755 | - Add window flashing support 756 | - Add window opacity support 757 | - Add support for PyGTK 758 | 759 | ### v1.1.1 760 | 761 | - Add support for WxPython, PyQt and PySide 762 | 763 | ### v1.1.0 764 | 765 | - Initial Release 766 | 767 |
768 | 769 | ![PyPI](https://img.shields.io/pypi/v/hPyT?style=flat-square) 770 | ![Python](https://img.shields.io/badge/python-3.6+-blue) 771 | ![Downloads](https://img.shields.io/pypi/dm/hPyT?style=flat-square) 772 | ![Stars](https://img.shields.io/github/stars/zingzy/hPyT?style=flat-square) 773 | ![Contributors](https://img.shields.io/github/contributors/zingzy/hPyT?style=flat-square) 774 | ![Last Commit](https://img.shields.io/github/last-commit/zingzy/hPyT?style=flat-square) 775 | 776 | --- 777 | 778 |
779 | 780 |
781 | © zingzy . 2025 782 | 783 | All Rights Reserved
784 | 785 |

786 | 787 |

788 | -------------------------------------------------------------------------------- /examples/kivy-example.py: -------------------------------------------------------------------------------- 1 | from kivy.app import App 2 | from kivy.lang import Builder 3 | from kivy.core.window import Window 4 | from hPyT import title_bar_color 5 | 6 | 7 | Window.clearcolor = (1, 0.5, 0, 0.0) 8 | 9 | 10 | root = Builder.load_string(""" 11 | Label: 12 | markup: True 13 | text: 14 | ('[b]Hello[/b] [color=94D000]World[/color]\\n' 15 | '[color=94D000]Hello[/color] [b]World[/b]') 16 | font_size: '64pt' 17 | 18 | """) 19 | 20 | 21 | class TestApp(App): 22 | def build(self): 23 | return root 24 | 25 | def on_start(self): 26 | # window parameter ---> self 27 | title_bar_color.set(self, "#ff8000") 28 | 29 | return super().on_start() 30 | 31 | 32 | if __name__ == "__main__": 33 | TestApp().run() 34 | -------------------------------------------------------------------------------- /examples/pyQt-example.py: -------------------------------------------------------------------------------- 1 | from hPyT import ( 2 | title_bar, 3 | maximize_button, 4 | minimize_button, 5 | window_flash, 6 | opacity, 7 | rainbow_title_bar, 8 | rainbow_border, 9 | ) 10 | 11 | # ---------- PyQT5 ---------- 12 | 13 | import sys 14 | from PyQt5 import QtWidgets 15 | 16 | app = QtWidgets.QApplication(sys.argv) 17 | window = QtWidgets.QWidget() 18 | 19 | title_bar.hide(window) # hides title bar 20 | # title_bar.unhide(window) 21 | 22 | maximize_button.disable(window) # disables maximize button 23 | # maximize_button.enable(window) 24 | 25 | minimize_button.disable(window) # disables minimize button 26 | # minimize_button.enable(window) 27 | 28 | window_flash.flash(window, 10) # flashes the window 10 times 29 | # window_flash.stop(window) # stops flashing immediately 30 | 31 | opacity.set(window, 0.5) # sets the opacity of the window to 50% 32 | 33 | rainbow_title_bar.start(window) # starts the rainbow effect on taskbar 34 | # rainbow_title_bar.stop(window) # stops the rainbow effect on taskbar 35 | 36 | rainbow_border.start(window) # starts the rainbow effect on border 37 | # rainbow_border.stop(window) # stops the rainbow effect on border 38 | 39 | # check out the readme.md file for other functions 40 | 41 | window.show() 42 | sys.exit(app.exec_()) 43 | -------------------------------------------------------------------------------- /examples/pyside-example.py: -------------------------------------------------------------------------------- 1 | from hPyT import ( 2 | title_bar, 3 | maximize_button, 4 | minimize_button, 5 | window_flash, 6 | opacity, 7 | rainbow_title_bar, 8 | rainbow_border, 9 | ) 10 | 11 | # ---------- PySide2 ---------- 12 | 13 | from PySide2.QtWidgets import QApplication, QWidget 14 | import sys 15 | 16 | app = QApplication(sys.argv) 17 | window = QWidget() 18 | 19 | title_bar.hide(window) # hides title bar 20 | # title_bar.unhide(window) 21 | 22 | maximize_button.disable(window) # disables maximize button 23 | # maximize_button.enable(window) 24 | 25 | minimize_button.disable(window) # disables minimize button 26 | # minimize_button.enable(window) 27 | 28 | window_flash.flash(window, 10) # flashes the window 10 times 29 | # window_flash.stop(window) # stops flashing immediately 30 | 31 | opacity.set(window, 0.5) # sets the opacity of the window to 50% 32 | 33 | rainbow_title_bar.start(window) # starts the rainbow effect on taskbar 34 | # rainbow_title_bar.stop(window) # stops the rainbow effect on taskbar 35 | 36 | rainbow_border.start(window) # starts the rainbow effect on border 37 | # rainbow_border.stop(window) # stops the rainbow effect on border 38 | 39 | # check out the readme.md file for other functions 40 | 41 | window.show() 42 | app.exec_() 43 | -------------------------------------------------------------------------------- /examples/rainbow-synchronization-example.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | 4 | try: 5 | from PyQt6.QtWidgets import ( 6 | QApplication, 7 | QProgressBar, 8 | QVBoxLayout, 9 | QWidget, 10 | QPushButton, 11 | ) 12 | from PyQt6.QtCore import Qt, QTimer 13 | except ImportError: 14 | os.system("pip install PyQt6") 15 | from PyQt6.QtWidgets import ( 16 | QApplication, 17 | QProgressBar, 18 | QVBoxLayout, 19 | QWidget, 20 | QPushButton, 21 | ) 22 | from PyQt6.QtCore import Qt, QTimer 23 | try: 24 | import qdarktheme 25 | except ImportError: 26 | os.system("pip install pyqtdarktheme==2.1.0 --ignore-requires-python") 27 | import qdarktheme 28 | from hPyT import rainbow_title_bar 29 | 30 | 31 | class RainbowProgressBar(QProgressBar): 32 | def __init__(self, *args, interval=int, **kwargs): 33 | super().__init__(*args, **kwargs) 34 | self.timer = QTimer() 35 | self.timer.timeout.connect(self.update_color) 36 | self.timer.start(interval) 37 | 38 | def update_color(self): 39 | # get current title bar color from the rainbow_title_bar 40 | r, g, b = rainbow_title_bar.get_current_color() 41 | 42 | # set the current title bar color as the current progressbar color 43 | self.setStyleSheet( 44 | f"QProgressBar::chunk {{ background-color: rgb({r}, {g}, {b}); }}" 45 | ) 46 | 47 | 48 | class MainWindow(QWidget): 49 | def __init__(self): 50 | super().__init__() 51 | # initialize the window 52 | self.setWindowTitle('"Rainbow ProgressBar" hPyT Sync Example') 53 | self.setGeometry(100, 100, 600, 400) 54 | qdarktheme.setup_theme( 55 | theme="dark", custom_colors={"[dark]": {"primary": "#FFFFFF"}} 56 | ) 57 | 58 | self.interval = 30 # define the interval for color updates 59 | window = self.window() # define the window for the rainbow title bar 60 | 61 | # Start the rainbow title bar effect for the winow 62 | rainbow_title_bar.start(window, interval=self.interval) 63 | 64 | # initialize the layout 65 | layout = QVBoxLayout() 66 | self.setLayout(layout) 67 | 68 | # initialize the custom color changing progress bar class 69 | self.progress_bar = RainbowProgressBar( 70 | self, interval=self.interval 71 | ) # in order to synchronize the colors make sure the rainbow title bar and your custom color changing widget (here: a progressbar) use the same interval for color updates 72 | self.progress_bar.setGeometry(50, 50, 500, 50) 73 | self.progress_bar.setRange(0, 100) 74 | 75 | # create a button which starts a progress simulation for the progressbar 76 | self.start_button = QPushButton("start") 77 | self.start_button.clicked.connect(self.start_progress_simulation) 78 | 79 | # define the layout 80 | layout.addStretch() 81 | layout.addWidget(self.progress_bar, alignment=Qt.AlignmentFlag.AlignVCenter) 82 | layout.addWidget(self.start_button, alignment=Qt.AlignmentFlag.AlignVCenter) 83 | layout.addStretch() 84 | 85 | # initialize a timer for the progressbar-progress simulation 86 | self.progress_timer = QTimer() 87 | self.progress_timer.timeout.connect(self.progress_simulation) 88 | 89 | def start_progress_simulation(self): 90 | self.pb_value = 0 91 | progress_update_interval = 500 92 | 93 | self.progress_timer.start(progress_update_interval) 94 | 95 | def progress_simulation(self): 96 | self.pb_value += 5 97 | self.progress_bar.setValue(self.pb_value) 98 | 99 | 100 | if __name__ == "__main__": 101 | app = QApplication(sys.argv) 102 | window = MainWindow() 103 | window.show() 104 | sys.exit(app.exec()) 105 | -------------------------------------------------------------------------------- /examples/tkinter-example.py: -------------------------------------------------------------------------------- 1 | from hPyT import ( 2 | title_bar, 3 | maximize_button, 4 | minimize_button, 5 | window_flash, 6 | opacity, 7 | rainbow_title_bar, 8 | rainbow_border, 9 | maximize_minimize_button, 10 | all_stuffs, 11 | ) 12 | 13 | # ---------- Tkinter ---------- 14 | 15 | from customtkinter import CTk 16 | 17 | window = CTk() 18 | window.title("hPyT") 19 | window.geometry("400x100") 20 | 21 | maximize_minimize_button.hide(window) # hides both maximize and minimize button 22 | # maximize_minimize_button.unhide(window) 23 | 24 | all_stuffs.hide(window) # hides close button 25 | # all_stuffs.unhide(window) 26 | 27 | title_bar.hide(window) # hides title bar 28 | # title_bar.unhide(window) 29 | 30 | maximize_button.disable(window) # disables maximize button 31 | # maximize_button.enable(window) 32 | 33 | minimize_button.disable(window) # disables minimize button 34 | # minimize_button.enable(window) 35 | 36 | window_flash.flash(window, 10) # flashes the window 10 times 37 | # window_flash.stop(window) # stops flashing immediately 38 | 39 | opacity.set(window, 0.5) # sets the opacity of the window to 50% 40 | 41 | rainbow_title_bar.start(window) # starts the rainbow effect on taskbar 42 | # rainbow_title_bar.stop(window) # stops the rainbow effect on taskbar 43 | 44 | rainbow_border.start(window) # starts the rainbow effect on border 45 | # rainbow_border.stop(window) # stops the rainbow effect on border 46 | 47 | # check out the readme.md file for other functions 48 | 49 | window.mainloop() 50 | -------------------------------------------------------------------------------- /examples/wx-example.py: -------------------------------------------------------------------------------- 1 | from hPyT import ( 2 | title_bar, 3 | maximize_button, 4 | minimize_button, 5 | window_flash, 6 | opacity, 7 | rainbow_title_bar, 8 | rainbow_border, 9 | ) 10 | 11 | # ---------- wxPython ---------- 12 | 13 | import wx 14 | 15 | app = wx.App() 16 | 17 | window = wx.Frame(parent=None, title="hPyT") 18 | 19 | title_bar.hide(window) 20 | # title_bar.unhide(window) 21 | 22 | maximize_button.disable(window) # disables maximize button 23 | # maximize_button.enable(window) 24 | 25 | minimize_button.disable(window) # disables minimize button 26 | # minimize_button.enable(window) 27 | 28 | window_flash.flash(window, 10) # flashes the window 10 times 29 | # window_flash.stop(window) # stops flashing immediately 30 | 31 | opacity.set(window, 0.5) # sets the opacity of the window to 50% 32 | 33 | rainbow_title_bar.start(window) # starts the rainbow effect on taskbar 34 | # rainbow_title_bar.stop(window) # stops the rainbow effect on taskbar 35 | 36 | rainbow_border.start(window) # starts the rainbow effect on border 37 | # rainbow_border.stop(window) # stops the rainbow effect on border 38 | 39 | # check out the readme.md file for other functions 40 | 41 | window.Show() 42 | app.MainLoop() 43 | -------------------------------------------------------------------------------- /hPyT/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | hPyT - Hack Python Titlebar 3 | Author - zingzy 4 | version - 1.4.0 5 | License - MIT 6 | Homepage - https://github.com/zingzy/hPyT 7 | """ 8 | 9 | from .hPyT import ( 10 | all_stuffs, 11 | maximize_button, 12 | maximize_minimize_button, 13 | minimize_button, 14 | opacity, 15 | rainbow_title_bar, 16 | rainbow_border, 17 | title_bar, 18 | title_bar_color, 19 | title_bar_text_color, 20 | border_color, 21 | title_text, 22 | window_animation, 23 | window_dwm, 24 | window_flash, 25 | window_frame, 26 | corner_radius, 27 | get_accent_color, 28 | ) 29 | 30 | __all__ = [ 31 | "title_bar", 32 | "maximize_minimize_button", 33 | "all_stuffs", 34 | "maximize_button", 35 | "minimize_button", 36 | "opacity", 37 | "window_flash", 38 | "title_bar_color", 39 | "title_bar_text_color", 40 | "rainbow_title_bar", 41 | "window_frame", 42 | "corner_radius", 43 | "window_animation", 44 | "window_dwm", 45 | "title_text", 46 | "border_color", 47 | "rainbow_border", 48 | "get_accent_color", 49 | ] 50 | 51 | __version__ = "1.4.0" 52 | -------------------------------------------------------------------------------- /hPyT/hPyT.py: -------------------------------------------------------------------------------- 1 | import ctypes.wintypes 2 | import math 3 | import threading 4 | import time 5 | from typing import Any, Tuple, Union, Optional, List, Dict 6 | import platform 7 | 8 | try: 9 | import ctypes 10 | from ctypes.wintypes import HWND, RECT, UINT 11 | import winreg 12 | except ImportError: 13 | raise ImportError("hPyT import Error : No Windows Enviorment Found") 14 | 15 | set_window_pos = ctypes.windll.user32.SetWindowPos 16 | 17 | # Check if the system is 32-bit or 64-bit 18 | # GetWindowLongPtrA and SetWindowLongPtrA are not supported in 32-bit systems 19 | if platform.architecture()[0] == "64bit": 20 | set_window_long = ctypes.windll.user32.SetWindowLongPtrW 21 | get_window_long = ctypes.windll.user32.GetWindowLongPtrA 22 | else: 23 | set_window_long = ctypes.windll.user32.SetWindowLongW 24 | get_window_long = ctypes.windll.user32.GetWindowLongA 25 | 26 | def_window_proc = ctypes.windll.user32.DefWindowProcW 27 | call_window_proc = ctypes.windll.user32.CallWindowProcW 28 | flash_window_ex = ctypes.windll.user32.FlashWindowEx 29 | 30 | 31 | GWL_STYLE = -16 32 | GWL_EXSTYLE = -20 33 | GWL_WNDPROC = -4 34 | 35 | WS_MINIMIZEBOX = 0x00020000 36 | WS_MAXIMIZEBOX = 0x00010000 37 | WS_CAPTION = 0x00C00000 38 | WS_SYSMENU = 0x00080000 39 | WS_BORDER = 0x00800000 40 | 41 | WS_EX_LAYERED = 524288 42 | 43 | WM_NCCALCSIZE = 0x0083 44 | WM_NCHITTEST = 0x0084 45 | 46 | WM_NCACTIVATE = 0x0086 47 | WM_NCPAINT = 0x0085 48 | 49 | SWP_NOZORDER = 4 50 | SWP_NOMOVE = 2 51 | SWP_NOSIZE = 1 52 | SWP_FRAMECHANGED = 32 53 | 54 | LWA_ALPHA = 2 55 | 56 | FLASHW_STOP = 0 57 | FLASHW_CAPTION = 1 58 | FLASHW_TRAY = 2 59 | FLASHW_ALL = 3 60 | FLASHW_TIMER = 4 61 | FLASHW_TIMERNOFG = 12 62 | 63 | WS_CHILD = 0x40000000 64 | WS_POPUP = 0x80000000 65 | WS_VISIBLE = 0x10000000 66 | 67 | 68 | class FLASHWINFO(ctypes.Structure): 69 | _fields_ = [ 70 | ("cbSize", ctypes.c_uint), 71 | ("hwnd", ctypes.c_void_p), 72 | ("dwFlags", ctypes.c_uint), 73 | ("uCount", ctypes.c_uint), 74 | ("dwTimeout", ctypes.c_uint), 75 | ] 76 | 77 | 78 | class PWINDOWPOS(ctypes.Structure): 79 | _fields_ = [ 80 | ("hWnd", HWND), 81 | ("hwndInsertAfter", HWND), 82 | ("x", ctypes.c_int), 83 | ("y", ctypes.c_int), 84 | ("cx", ctypes.c_int), 85 | ("cy", ctypes.c_int), 86 | ("flags", UINT), 87 | ] 88 | 89 | 90 | class NCCALCSIZE_PARAMS(ctypes.Structure): 91 | _fields_ = [("rgrc", RECT * 3), ("lppos", ctypes.POINTER(PWINDOWPOS))] 92 | 93 | 94 | rnbtbs: List[int] = [] 95 | rnbbcs: List[int] = [] 96 | accent_color_titlebars: List[int] = [] 97 | accent_color_borders: List[int] = [] 98 | 99 | WINDOWS_VERSION = float(platform.version().split(".")[0]) 100 | 101 | 102 | class title_bar: 103 | """Hide or unhide the title bar of a window.""" 104 | 105 | _height_reduction: Dict[ 106 | int, int 107 | ] = {} # To track the height reduction applied for each window 108 | 109 | @classmethod 110 | def hide(cls, window: Any, no_span: bool = False) -> None: 111 | """ 112 | Hide the title bar of the specified window. 113 | 114 | Args: 115 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 116 | no_span (bool): Whether to resize the window to fit the content area only. Default is False. 117 | """ 118 | 119 | def handle(hwnd: int, msg: int, wp: int, lp: int) -> int: 120 | if msg == WM_NCCALCSIZE and wp: 121 | # Adjust the non-client area (title bar) size 122 | # Here we are basically removing the top border (because the title bar in windows is made of 2 components: the actual titlebar and the border, both having same color) 123 | lpncsp = NCCALCSIZE_PARAMS.from_address(lp) 124 | lpncsp.rgrc[0].top -= border_width # Reduce the height of the title bar 125 | 126 | elif msg in [WM_NCACTIVATE, WM_NCPAINT]: 127 | # Prevent Windows from drawing the title bar when the window is activated or painted 128 | # Here we are telling windows not to draw border when the window is switched 129 | return 1 # Tell Windows not to process further 130 | 131 | # Default processing for other messages 132 | return call_window_proc( 133 | *map(ctypes.c_uint64, (globals()[old], hwnd, msg, wp, lp)) 134 | ) 135 | 136 | old, new = "old_wndproc", "new_wndproc" 137 | prototype = ctypes.WINFUNCTYPE( 138 | ctypes.c_uint64, 139 | ctypes.c_uint64, 140 | ctypes.c_uint64, 141 | ctypes.c_uint64, 142 | ctypes.c_uint64, 143 | ) 144 | 145 | hwnd: int = module_find(window) 146 | 147 | # Get the window and client dimensions 148 | rect = RECT() 149 | client_rect = RECT() 150 | ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)) 151 | ctypes.windll.user32.GetClientRect(hwnd, ctypes.byref(client_rect)) 152 | 153 | full_width: int = rect.right - rect.left 154 | full_height: int = rect.bottom - rect.top 155 | client_width: int = client_rect.right 156 | client_height: int = client_rect.bottom 157 | 158 | # Calculate the border width and title bar height 159 | border_width: int = (full_width - client_width) // 2 160 | title_bar_height: int = full_height - client_height - border_width 161 | 162 | # Override the window procedure if not already done 163 | if globals().get(old) is None: 164 | globals()[old] = get_window_long(hwnd, GWL_WNDPROC) 165 | 166 | # Do not remove the top border when on windows 7 or lower 167 | if WINDOWS_VERSION >= 10.0: # Windows 10 is version 10.0 168 | globals()[new] = prototype(handle) 169 | set_window_long(hwnd, GWL_WNDPROC, globals()[new]) 170 | 171 | old_style = get_window_long(hwnd, GWL_STYLE) 172 | new_style = (old_style & ~WS_CAPTION) | WS_BORDER 173 | set_window_long(hwnd, GWL_STYLE, new_style) 174 | 175 | if no_span: 176 | cls._height_reduction[hwnd] = title_bar_height 177 | set_window_pos( 178 | hwnd, 179 | 0, 180 | 0, 181 | 0, 182 | full_width, 183 | full_height - title_bar_height, 184 | SWP_NOZORDER | SWP_NOMOVE, 185 | ) 186 | return 187 | 188 | set_window_pos( 189 | hwnd, 190 | 0, 191 | 0, 192 | 0, 193 | 0, 194 | 0, 195 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 196 | ) 197 | 198 | @classmethod 199 | def unhide(cls, window: Any) -> None: 200 | """ 201 | Unhide the title bar of the specified window. 202 | 203 | Args: 204 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 205 | """ 206 | 207 | hwnd: int = module_find(window) 208 | 209 | if globals().get("old_wndproc") is not None: 210 | set_window_long(hwnd, GWL_WNDPROC, globals()["old_wndproc"]) 211 | globals()["old_wndproc"] = None 212 | 213 | # Restore the original height if no_span was used 214 | height_reduction: int = cls._height_reduction.pop(hwnd, 0) 215 | 216 | old_style = get_window_long(hwnd, GWL_STYLE) 217 | new_style = old_style | WS_CAPTION 218 | set_window_long(hwnd, GWL_STYLE, new_style) 219 | 220 | if height_reduction: 221 | rect = RECT() 222 | ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)) 223 | set_window_pos( 224 | hwnd, 225 | 0, 226 | 0, 227 | 0, 228 | rect.right - rect.left, 229 | rect.bottom - rect.top + height_reduction, 230 | SWP_NOZORDER | SWP_NOMOVE, 231 | ) 232 | return 233 | 234 | set_window_pos( 235 | hwnd, 236 | 0, 237 | 0, 238 | 0, 239 | 0, 240 | 0, 241 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 242 | ) 243 | 244 | 245 | class maximize_minimize_button: 246 | """Hide or unhide both the maximize and minimize buttons of a window.""" 247 | 248 | @classmethod 249 | def hide(cls, window: Any) -> None: 250 | """ 251 | Hide both the maximize and minimize buttons of the specified window. 252 | 253 | Args: 254 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 255 | """ 256 | 257 | hwnd: int = module_find(window) 258 | old_style = get_window_long(hwnd, GWL_STYLE) 259 | new_style = old_style & ~WS_MAXIMIZEBOX & ~WS_MINIMIZEBOX 260 | set_window_long(hwnd, GWL_STYLE, new_style) 261 | set_window_pos( 262 | hwnd, 263 | 0, 264 | 0, 265 | 0, 266 | 0, 267 | 0, 268 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 269 | ) 270 | 271 | @classmethod 272 | def unhide(cls, window: Any) -> None: 273 | """ 274 | Unhide both the maximize and minimize buttons of the specified window. 275 | 276 | Args: 277 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 278 | """ 279 | 280 | hwnd: int = module_find(window) 281 | old_style = get_window_long(hwnd, GWL_STYLE) 282 | new_style = old_style | WS_MAXIMIZEBOX | WS_MINIMIZEBOX 283 | set_window_long(hwnd, GWL_STYLE, new_style) 284 | set_window_pos( 285 | hwnd, 286 | 0, 287 | 0, 288 | 0, 289 | 0, 290 | 0, 291 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 292 | ) 293 | 294 | 295 | class maximize_button: 296 | """Enable or Disable only the maximize button of a window.""" 297 | 298 | @classmethod 299 | def disable(cls, window: Any) -> None: 300 | """ 301 | Disable the maximize button of the specified window. 302 | 303 | Args: 304 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 305 | """ 306 | 307 | hwnd: int = module_find(window) 308 | old_style = get_window_long(hwnd, GWL_STYLE) 309 | new_style = old_style & ~WS_MAXIMIZEBOX 310 | set_window_long(hwnd, GWL_STYLE, new_style) 311 | set_window_pos( 312 | hwnd, 313 | 0, 314 | 0, 315 | 0, 316 | 0, 317 | 0, 318 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 319 | ) 320 | 321 | @classmethod 322 | def enable(cls, window: Any) -> None: 323 | """ 324 | Enable the maximize button of the specified window. 325 | 326 | Args: 327 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 328 | """ 329 | 330 | hwnd: int = module_find(window) 331 | old_style = get_window_long(hwnd, GWL_STYLE) 332 | new_style = old_style | WS_MAXIMIZEBOX 333 | set_window_long(hwnd, GWL_STYLE, new_style) 334 | set_window_pos( 335 | hwnd, 336 | 0, 337 | 0, 338 | 0, 339 | 0, 340 | 0, 341 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 342 | ) 343 | 344 | 345 | class minimize_button: 346 | """Enable or Disable only the minimize button of a window.""" 347 | 348 | @classmethod 349 | def disable(cls, window: Any) -> None: 350 | """ 351 | Disable the minimize button of the specified window. 352 | 353 | Args: 354 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 355 | """ 356 | 357 | hwnd: int = module_find(window) 358 | old_style = get_window_long(hwnd, GWL_STYLE) 359 | new_style = old_style & ~WS_MINIMIZEBOX 360 | set_window_long(hwnd, GWL_STYLE, new_style) 361 | set_window_pos( 362 | hwnd, 363 | 0, 364 | 0, 365 | 0, 366 | 0, 367 | 0, 368 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 369 | ) 370 | 371 | @classmethod 372 | def enable(cls, window: Any) -> None: 373 | """ 374 | Enable the minimize button of the specified window. 375 | 376 | Args: 377 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 378 | """ 379 | 380 | hwnd: int = module_find(window) 381 | old_style = get_window_long(hwnd, GWL_STYLE) 382 | new_style = old_style | WS_MINIMIZEBOX 383 | set_window_long(hwnd, GWL_STYLE, new_style) 384 | set_window_pos( 385 | hwnd, 386 | 0, 387 | 0, 388 | 0, 389 | 0, 390 | 0, 391 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 392 | ) 393 | 394 | 395 | class all_stuffs: 396 | """Hide or unhide all the buttons, icon and title of a window.""" 397 | 398 | @classmethod 399 | def hide(cls, window: Any) -> None: 400 | """ 401 | Hide all the buttons, icon and title of the specified window. 402 | 403 | Args: 404 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 405 | """ 406 | 407 | hwnd: int = module_find(window) 408 | old_style = get_window_long(hwnd, GWL_STYLE) 409 | new_style = old_style & ~WS_SYSMENU 410 | set_window_long(hwnd, GWL_STYLE, new_style) 411 | set_window_pos( 412 | hwnd, 413 | 0, 414 | 0, 415 | 0, 416 | 0, 417 | 0, 418 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 419 | ) 420 | 421 | @classmethod 422 | def unhide(cls, window: Any) -> None: 423 | """ 424 | Unhide all the buttons, icon and title of the specified window. 425 | 426 | Args: 427 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 428 | """ 429 | 430 | hwnd: int = module_find(window) 431 | old_style = get_window_long(hwnd, GWL_STYLE) 432 | new_style = old_style | WS_SYSMENU 433 | set_window_long(hwnd, GWL_STYLE, new_style) 434 | set_window_pos( 435 | hwnd, 436 | 0, 437 | 0, 438 | 0, 439 | 0, 440 | 0, 441 | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED, 442 | ) 443 | 444 | 445 | class window_flash: 446 | """Adds a flashing simulation to a window, which is useful for alerting the user or grabbing their attention.""" 447 | 448 | @classmethod 449 | def flash(cls, window: Any, count: int = 5, interval: int = 1000) -> None: 450 | """ 451 | Flash the specified window. 452 | 453 | Args: 454 | window (object): The window object to flash (e.g., a Tk instance in Tkinter). 455 | count (int): The number of times to flash the window. Default is 5. 456 | interval (int): The interval between each flash in milliseconds. Default is 1000. 457 | 458 | Example: 459 | >>> window_flash.flash(window, count=10, interval=500) 460 | """ 461 | 462 | hwnd: int = module_find(window) 463 | info = FLASHWINFO( 464 | cbSize=ctypes.sizeof(FLASHWINFO), 465 | hwnd=hwnd, 466 | dwFlags=FLASHW_ALL | FLASHW_TIMER, 467 | uCount=count, 468 | dwTimeout=interval, 469 | ) 470 | flash_window_ex(ctypes.pointer(info)) 471 | 472 | @classmethod 473 | def stop(cls, window: Any) -> None: 474 | """ 475 | Stop the flashing simulation of the specified window immediately. 476 | 477 | Args: 478 | window (object): The window object to stop flashing (e.g., a Tk instance in Tkinter). 479 | 480 | Example: 481 | >>> window_flash.flash(window, count=20, interval=1000) 482 | >>> time.sleep(5) 483 | >>> window_flash.stop(window) 484 | """ 485 | 486 | hwnd: int = module_find(window) 487 | info = FLASHWINFO( 488 | cbSize=ctypes.sizeof(FLASHWINFO), 489 | hwnd=hwnd, 490 | dwFlags=FLASHW_STOP, 491 | uCount=0, 492 | dwTimeout=0, 493 | ) 494 | flash_window_ex(ctypes.pointer(info)) 495 | 496 | 497 | class opacity: 498 | """Change the opacity of the specified window.""" 499 | 500 | @classmethod 501 | def set(cls, window: Any, opacity: float) -> None: 502 | """ 503 | Set the opacity of the specified window. 504 | 505 | Args: 506 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 507 | opacity (int/float): The opacity value to set. It should be a value between 0 (transparent) and 1 (opaque). 508 | 509 | Example: 510 | >>> opacity.set(window, 0.5) # sets the window opacity to 50% 511 | >>> opacity.set(window, 1) # sets the window opacity to 100% (oppaque) 512 | """ 513 | 514 | hwnd: int = module_find(window) 515 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 516 | new_ex_style = old_ex_style | WS_EX_LAYERED 517 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 518 | 519 | # Opacity is a value between 0 (transparent) and 255 (opaque) 520 | # If the input is a float between 0.0 and 1.0, convert it to an integer between 0 and 255 521 | if isinstance(opacity, float) and 0.0 <= opacity <= 1.0: 522 | opacity = int(opacity * 255) 523 | 524 | ctypes.windll.user32.SetLayeredWindowAttributes(hwnd, 0, opacity, LWA_ALPHA) 525 | 526 | 527 | class title_bar_color: 528 | """Change the color of the title bar of a window.""" 529 | 530 | @classmethod 531 | def set(cls, window: Any, color: Union[Tuple[int, int, int], str]) -> None: 532 | """ 533 | Set the color of the title bar of the specified window. 534 | 535 | Args: 536 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 537 | color (tuple/string): The RGB or HEX color value to set. It can be a tuple of integers (e.g., (255, 0, 0)) or a HEX string (e.g., "#FF0000"). 538 | 539 | Example: 540 | >>> title_bar_color.set(window, (255, 0, 0)) 541 | """ 542 | 543 | converted_color: int = convert_color(color) 544 | hwnd: int = module_find(window) 545 | if hwnd in accent_color_titlebars: 546 | accent_color_titlebars.remove(hwnd) 547 | if hwnd in rnbtbs: 548 | raise RuntimeError( 549 | "Failed to change the title bar color. Please stop the rainbow titlebar effect using the `stop()` function." 550 | ) 551 | 552 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 553 | new_ex_style = old_ex_style | WS_EX_LAYERED 554 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 555 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 556 | hwnd, 35, ctypes.byref(ctypes.c_int(converted_color)), 4 557 | ) 558 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) # Reset the window style 559 | 560 | @classmethod 561 | def set_accent(cls, window: Any) -> None: 562 | """ 563 | Set the color of the title bar of the specified window to the system accent color. 564 | 565 | Args: 566 | window (object): The window objec t to modify (e.g., a Tk instance in Tkinter). 567 | """ 568 | 569 | def set_titlebar_color_accent(hwnd) -> None: 570 | old_accent: str = "#000000" 571 | 572 | while hwnd in accent_color_titlebars: 573 | if old_accent != get_accent_color(): 574 | color: int = convert_color(get_accent_color()) 575 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 576 | new_ex_style = old_ex_style | WS_EX_LAYERED 577 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 578 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 579 | hwnd, 35, ctypes.byref(ctypes.c_int(color)), 4 580 | ) 581 | set_window_long( 582 | hwnd, GWL_EXSTYLE, old_ex_style 583 | ) # Reset the window style 584 | 585 | old_accent = get_accent_color() 586 | 587 | time.sleep(1) 588 | 589 | hwnd: int = module_find(window) 590 | if hwnd in rnbtbs: 591 | raise RuntimeError( 592 | "Failed to change the title bar color. Please stop the rainbow titlebar effect using the `stop()` function." 593 | ) 594 | 595 | if hwnd not in accent_color_titlebars: 596 | accent_color_titlebars.append(hwnd) 597 | thread = threading.Thread( 598 | target=set_titlebar_color_accent, args=(hwnd,), daemon=True 599 | ) 600 | thread.start() 601 | else: 602 | raise RuntimeError( 603 | "The titlebar's color of the specified window is already set to the accent color." 604 | ) 605 | 606 | @classmethod 607 | def reset(cls, window: Any) -> None: 608 | """ 609 | Reset the color of the title bar of the specified window to the default theme. 610 | 611 | Args: 612 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 613 | """ 614 | 615 | hwnd: int = module_find(window) 616 | if hwnd in accent_color_titlebars: 617 | accent_color_titlebars.remove(hwnd) 618 | if hwnd in rnbtbs: 619 | raise RuntimeError( 620 | "Failed to reset the title bar color. Please stop the rainbow titlebar effect using the `stop()` function." 621 | ) 622 | 623 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 624 | new_ex_style = old_ex_style | WS_EX_LAYERED 625 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 626 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 627 | hwnd, 35, ctypes.byref(ctypes.c_int(-1)), 4 628 | ) 629 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) 630 | 631 | 632 | class title_bar_text_color: 633 | """Change the color of the title bar text of a window.""" 634 | 635 | @classmethod 636 | def set(cls, window, color: Union[Tuple[int, int, int], str]) -> None: 637 | """ 638 | Set the color of the title bar text of the specified window. 639 | 640 | Args: 641 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 642 | color (tuple/string): The RGB or HEX color value to set. It can be a tuple of integers (e.g., (255, 0, 0)) or a HEX string (e.g., "#FF0000"). 643 | 644 | Example: 645 | >>> title_bar_text_color.set(window, (255, 0, 0)) 646 | """ 647 | 648 | converted_color: int = convert_color(color) 649 | hwnd: int = module_find(window) 650 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 651 | new_ex_style = old_ex_style | WS_EX_LAYERED 652 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 653 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 654 | hwnd, 36, ctypes.byref(ctypes.c_int(converted_color)), 4 655 | ) 656 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) # Reset the window style 657 | 658 | @classmethod 659 | def reset(cls, window: Any) -> None: 660 | """ 661 | Reset the color of the title bar text of the specified window to the default color. 662 | 663 | Args: 664 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 665 | """ 666 | 667 | hwnd: int = module_find(window) 668 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 669 | new_ex_style = old_ex_style | WS_EX_LAYERED 670 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 671 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 672 | hwnd, 36, ctypes.byref(ctypes.c_int(-1)), 4 673 | ) 674 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) 675 | 676 | 677 | class border_color: 678 | """Change the color of the border of a window.""" 679 | 680 | @classmethod 681 | def set(cls, window: Any, color: Union[Tuple[int, int, int], str]) -> None: 682 | """ 683 | Set the color of the border of the specified window. 684 | 685 | Args: 686 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 687 | color (tuple/string): The RGB or HEX color value to set. It can be a tuple of integers (e.g., (255, 0, 0)) or a HEX string (e.g., "#FF0000"). 688 | 689 | Example: 690 | >>> border_color.set(window, (255, 0, 0)) 691 | """ 692 | 693 | converted_color: int = convert_color(color) 694 | hwnd: int = module_find(window) 695 | if hwnd in accent_color_borders: 696 | accent_color_borders.remove(hwnd) 697 | if hwnd in rnbbcs: 698 | raise RuntimeError( 699 | "Failed to change the border color. Please stop the rainbow border effect using the `stop()` function." 700 | ) 701 | 702 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 703 | new_ex_style = old_ex_style | WS_EX_LAYERED 704 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 705 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 706 | hwnd, 34, ctypes.byref(ctypes.c_int(converted_color)), 4 707 | ) 708 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) # Reset the window style 709 | 710 | @classmethod 711 | def set_accent(cls, window: Any) -> None: 712 | """ 713 | Set the color of the border of the specified window to the system accent color. 714 | 715 | Args: 716 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 717 | """ 718 | 719 | def set_border_color_accent(hwnd) -> None: 720 | old_accent: str = "#000000" 721 | 722 | while hwnd in accent_color_borders: 723 | if not old_accent == get_accent_color(): 724 | color = convert_color(get_accent_color()) 725 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 726 | new_ex_style = old_ex_style | WS_EX_LAYERED 727 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 728 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 729 | hwnd, 34, ctypes.byref(ctypes.c_int(color)), 4 730 | ) 731 | set_window_long( 732 | hwnd, GWL_EXSTYLE, old_ex_style 733 | ) # Reset the window style 734 | 735 | old_accent = get_accent_color() 736 | 737 | time.sleep(1) 738 | 739 | hwnd: int = module_find(window) 740 | if hwnd in rnbbcs: 741 | raise RuntimeError( 742 | "Failed to change the border color. Please stop the rainbow border effect using the `stop()` function." 743 | ) 744 | 745 | if hwnd not in accent_color_borders: 746 | accent_color_borders.append(hwnd) 747 | thread = threading.Thread( 748 | target=set_border_color_accent, args=(hwnd,), daemon=True 749 | ) 750 | thread.start() 751 | else: 752 | raise RuntimeError( 753 | "The border's color of the specified window is already set to the accent color." 754 | ) 755 | 756 | @classmethod 757 | def reset(cls, window: Any) -> None: 758 | """ 759 | Reset the color of the border of the specified window to the default color. 760 | 761 | Args: 762 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 763 | """ 764 | 765 | hwnd: int = module_find(window) 766 | if hwnd in accent_color_borders: 767 | accent_color_borders.remove(hwnd) 768 | if hwnd in rnbbcs: 769 | raise RuntimeError( 770 | "Failed to reset the border color. Please stop the rainbow border effect using the `stop()` function." 771 | ) 772 | 773 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 774 | new_ex_style = old_ex_style | WS_EX_LAYERED 775 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 776 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 777 | hwnd, 34, ctypes.byref(ctypes.c_int(-1)), 4 778 | ) 779 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) 780 | 781 | 782 | class rainbow_title_bar: 783 | """Add a rainbow effect to the title bar of a window.""" 784 | 785 | current_color = None 786 | 787 | @classmethod 788 | def start(cls, window: Any, interval: int = 5, color_stops: int = 5) -> None: 789 | """ 790 | Start the rainbow effect on the title bar of the specified window. 791 | 792 | Args: 793 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 794 | interval (int): The interval between each color change in milliseconds. Default is 5. 795 | color_stops (int): The number of color stops between each RGB value. Default is 5. 796 | 797 | Example: 798 | >>> rainbow_title_bar.start(window, interval=10, color_stops=10) 799 | 800 | Notes: 801 | The `interval` parameter controls the speed of the rainbow effect, and the `color_stops` parameter controls the number of color stops between each RGB value. 802 | Higher values for `interval` will result in a slower rainbow effect. 803 | Higher values for `color_stops` might skip most of the colors defying the purpose of the rainbow effect. 804 | """ 805 | 806 | def color_changer(hwnd: int, interval: int) -> None: 807 | r, g, b = 200, 0, 0 808 | while hwnd in rnbtbs: 809 | cls.current_color = (r << 16) | (g << 8) | b 810 | 811 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 812 | hwnd, 35, ctypes.byref(ctypes.c_int(cls.current_color)), 4 813 | ) 814 | if r < 255 and g == 0 and b == 0: 815 | r = min(255, r + color_stops) 816 | elif r == 255 and g < 255 and b == 0: 817 | g = min(255, g + color_stops) 818 | elif r > 0 and g == 255 and b == 0: 819 | r = max(0, r - color_stops) 820 | elif g == 255 and b < 255 and r == 0: 821 | b = min(255, b + color_stops) 822 | elif g > 0 and b == 255 and r == 0: 823 | g = max(0, g - color_stops) 824 | elif b == 255 and r < 255 and g == 0: 825 | r = min(255, r + color_stops) 826 | elif b > 0 and r == 255 and g == 0: 827 | b = max(0, b - color_stops) 828 | ctypes.windll.kernel32.Sleep(interval) 829 | else: 830 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 831 | hwnd, 35, ctypes.byref(ctypes.c_int(-1)), 4 832 | ) 833 | 834 | hwnd: int = module_find(window) 835 | if hwnd in accent_color_titlebars: 836 | accent_color_titlebars.remove(hwnd) 837 | 838 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 839 | new_ex_style = old_ex_style | WS_EX_LAYERED 840 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 841 | 842 | if hwnd not in rnbtbs: 843 | rnbtbs.append(hwnd) 844 | thread = threading.Thread(target=color_changer, args=(hwnd, interval)) 845 | thread.daemon = True 846 | thread.start() 847 | else: 848 | raise RuntimeError( 849 | "The rainbow title bar effect is already applied to this window." 850 | ) 851 | 852 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) # Reset the window style 853 | 854 | @classmethod 855 | def get_current_color(cls) -> Tuple[int, int, int]: 856 | """ 857 | Get the current RGB color value of the title bar, which is being displayed by the rainbow effect. 858 | Can be useful if you want to synchronize the title bar's rainbow effect with other elements of the window. 859 | 860 | Returns: 861 | tuple: A tuple containing the RGB color values of the title bar. 862 | 863 | Notes: 864 | Example implementation of this feature available at [examples/rainbow-synchronization-example.py](https://github.com/Zingzy/hPyT/blob/main/examples/rainbow-synchronization-example.py). 865 | """ 866 | 867 | color = cls.current_color 868 | if color is None: 869 | return (0, 0, 0) # Default color if current_color is None 870 | b = (color >> 16) & 0xFF 871 | g = (color >> 8) & 0xFF 872 | r = color & 0xFF 873 | return (r, g, b) 874 | 875 | @classmethod 876 | def stop(cls, window: Any) -> None: 877 | """ 878 | Stop the rainbow effect on the title bar of the specified window. 879 | 880 | Args: 881 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 882 | """ 883 | 884 | hwnd: int = module_find(window) 885 | if hwnd in rnbtbs: 886 | rnbtbs.remove(hwnd) 887 | else: 888 | raise ValueError("Rainbow title bar is not running on this window.") 889 | 890 | 891 | class rainbow_border: 892 | """Add a rainbow effect to the border of a window.""" 893 | 894 | current_color = None 895 | 896 | @classmethod 897 | def start(cls, window: Any, interval: int = 5, color_stops: int = 5) -> None: 898 | """ 899 | Start the rainbow effect on the border of the specified window. 900 | 901 | Args: 902 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 903 | interval (int): The interval between each color change in milliseconds. Default is 5. 904 | color_stops (int): The number of color stops between each RGB value. Default is 5. 905 | 906 | Example: 907 | >>> rainbow_border.start(window, interval=10, color_stops=10) 908 | 909 | Notes: 910 | The `interval` parameter controls the speed of the rainbow effect, and the `color_stops` parameter controls the number of color stops between each RGB value. 911 | Higher values for `interval` will result in a slower rainbow effect. 912 | Higher values for `color_stops` might skip most of the colors defying the purpose of the rainbow effect. 913 | """ 914 | 915 | def color_changer(hwnd, interval: int): 916 | r, g, b = 200, 0, 0 917 | while hwnd in rnbbcs: 918 | cls.current_color = (r << 16) | (g << 8) | b 919 | 920 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 921 | hwnd, 34, ctypes.byref(ctypes.c_int(cls.current_color)), 4 922 | ) 923 | if r < 255 and g == 0 and b == 0: 924 | r = min(255, r + color_stops) 925 | elif r == 255 and g < 255 and b == 0: 926 | g = min(255, g + color_stops) 927 | elif r > 0 and g == 255 and b == 0: 928 | r = max(0, r - color_stops) 929 | elif g == 255 and b < 255 and r == 0: 930 | b = min(255, b + color_stops) 931 | elif g > 0 and b == 255 and r == 0: 932 | g = max(0, g - color_stops) 933 | elif b == 255 and r < 255 and g == 0: 934 | r = min(255, r + color_stops) 935 | elif b > 0 and r == 255 and g == 0: 936 | b = max(0, b - color_stops) 937 | ctypes.windll.kernel32.Sleep(interval) 938 | else: 939 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 940 | hwnd, 34, ctypes.byref(ctypes.c_int(-1)), 4 941 | ) 942 | 943 | hwnd: int = module_find(window) 944 | if hwnd in accent_color_borders: 945 | accent_color_borders.remove(hwnd) 946 | 947 | old_ex_style = get_window_long(hwnd, GWL_EXSTYLE) 948 | new_ex_style = old_ex_style | WS_EX_LAYERED 949 | set_window_long(hwnd, GWL_EXSTYLE, new_ex_style) 950 | 951 | if hwnd not in rnbbcs: 952 | rnbbcs.append(hwnd) 953 | thread = threading.Thread(target=color_changer, args=(hwnd, interval)) 954 | thread.daemon = True 955 | thread.start() 956 | else: 957 | raise RuntimeError( 958 | "The rainbow border effect is already applied to this window." 959 | ) 960 | 961 | set_window_long(hwnd, GWL_EXSTYLE, old_ex_style) # Reset the window style 962 | 963 | @classmethod 964 | def get_current_color(cls) -> Tuple[int, int, int]: 965 | """ 966 | Get the current RGB color value of the border, which is being displayed by the rainbow effect. 967 | Can be useful if you want to synchronize the border's rainbow effect with other elements of the window. 968 | 969 | Returns: 970 | tuple: A tuple containing the RGB color values of the border. 971 | 972 | Notes: 973 | Example implementation of this feature available at [examples/rainbow-synchronization-example.py](https://github.com/Zingzy/hPyT/blob/main/examples/rainbow-synchronization-example.py 974 | """ 975 | 976 | color = cls.current_color 977 | if color is None: 978 | return (0, 0, 0) # Default color if current_color is None 979 | b = (color >> 16) & 0xFF 980 | g = (color >> 8) & 0xFF 981 | r = color & 0xFF 982 | return (r, g, b) 983 | 984 | @classmethod 985 | def stop(cls, window: Any) -> None: 986 | """ 987 | Stop the rainbow effect on the border of the specified window. 988 | 989 | Args: 990 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 991 | """ 992 | 993 | hwnd: int = module_find(window) 994 | if hwnd in rnbbcs: 995 | rnbbcs.remove(hwnd) 996 | else: 997 | raise ValueError("Rainbow border is not running on this window.") 998 | 999 | 1000 | class window_frame: 1001 | """Change the position, size, and state of a window.""" 1002 | 1003 | @classmethod 1004 | def center(cls, window: Any) -> None: 1005 | """ 1006 | Center the specified window on the screen. 1007 | 1008 | Args: 1009 | window (object): The window object to center (e.g., a Tk instance in Tkinter). 1010 | """ 1011 | 1012 | hwnd: int = module_find(window) 1013 | 1014 | # Get the window's current position and size 1015 | rect = ctypes.wintypes.RECT() 1016 | ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)) 1017 | window_width = rect.right - rect.left 1018 | window_height = rect.bottom - rect.top 1019 | 1020 | # Get the screen's width and height 1021 | screen_width = ctypes.windll.user32.GetSystemMetrics(0) 1022 | screen_height = ctypes.windll.user32.GetSystemMetrics(1) 1023 | 1024 | # Calculate the new position 1025 | new_x = (screen_width - window_width) // 2 1026 | new_y = (screen_height - window_height) // 2 1027 | 1028 | # Set the window's new position 1029 | ctypes.windll.user32.SetWindowPos(hwnd, 0, new_x, new_y, 0, 0, 0x0001) 1030 | 1031 | @classmethod 1032 | def center_relative(cls, window_parent: Any, window_child: Any) -> None: 1033 | """ 1034 | Center the specified child window relative to the parent window. 1035 | 1036 | Args: 1037 | window_parent (object): The parent window object (e.g., a Tk instance in Tkinter). 1038 | window_child (object): The child window object to center with respect to the `window_parent` (e.g., a Tk instance in Tkinter). 1039 | 1040 | Example: 1041 | >>> window_frame.center_relative(parent_window, child_window) 1042 | """ 1043 | 1044 | hwnd_parent: int = module_find(window_parent) 1045 | hwnd_child: int = module_find(window_child) 1046 | 1047 | # Get the parent window's current position and size 1048 | rect_parent = ctypes.wintypes.RECT() 1049 | ctypes.windll.user32.GetWindowRect(hwnd_parent, ctypes.byref(rect_parent)) 1050 | parent_width = rect_parent.right - rect_parent.left 1051 | parent_height = rect_parent.bottom - rect_parent.top 1052 | 1053 | # Get the child window's current position and size 1054 | rect_child = ctypes.wintypes.RECT() 1055 | ctypes.windll.user32.GetWindowRect(hwnd_child, ctypes.byref(rect_child)) 1056 | child_width = rect_child.right - rect_child.left 1057 | child_height = rect_child.bottom - rect_child.top 1058 | 1059 | # Calculate the new position 1060 | new_x = rect_parent.left + (parent_width - child_width) // 2 1061 | new_y = rect_parent.top + (parent_height - child_height) // 2 1062 | 1063 | # Set the child window's new position 1064 | ctypes.windll.user32.SetWindowPos(hwnd_child, 0, new_x, new_y, 0, 0, 0x0001) 1065 | 1066 | @classmethod 1067 | def move(cls, window: Any, x: int, y: int) -> None: 1068 | """ 1069 | Move the specified window to the specified position. 1070 | 1071 | Args: 1072 | window (object): The window object to move (e.g., a Tk instance in Tkinter). 1073 | x (int): The new X-coordinate of the window. 1074 | y (int): The new Y-coordinate of the window. 1075 | 1076 | Example: 1077 | >>> window_frame.move(window, 100, 100) # moves the window to (100, 100) 1078 | """ 1079 | 1080 | hwnd: int = module_find(window) 1081 | set_window_pos(hwnd, 0, x, y, 0, 0, 0x0001) 1082 | 1083 | @classmethod 1084 | def resize(cls, window: Any, width: int, height: int) -> None: 1085 | """ 1086 | Resize the specified window to the specified width and height. 1087 | 1088 | Args: 1089 | window (object): The window object to resize (e.g., a Tk instance in Tkinter). 1090 | width (int): The new width of the window. 1091 | height (int): The new height of the window. 1092 | 1093 | Example: 1094 | >>> window_frame.resize(window, 800, 600) # resizes the window to 800x600 1095 | """ 1096 | 1097 | hwnd: int = module_find(window) 1098 | set_window_pos(hwnd, 0, 0, 0, width, height, 0x0001) 1099 | 1100 | @classmethod 1101 | def minimize(cls, window: Any) -> None: 1102 | """ 1103 | Minimize the specified window. 1104 | 1105 | Args: 1106 | window (object): The window object to minimize (e.g., a Tk instance in Tkinter). 1107 | """ 1108 | 1109 | hwnd = module_find(window) 1110 | ctypes.windll.user32.ShowWindow(hwnd, 6) 1111 | 1112 | @classmethod 1113 | def maximize(cls, window: Any) -> None: 1114 | """ 1115 | Maximize the specified window. 1116 | 1117 | Args: 1118 | window (object): The window object to maximize (e.g., a Tk instance in Tkinter). 1119 | """ 1120 | 1121 | hwnd: int = module_find(window) 1122 | ctypes.windll.user32.ShowWindow(hwnd, 3) 1123 | 1124 | @classmethod 1125 | def restore(cls, window: Any) -> None: 1126 | """ 1127 | Restore a minimized or maximized window to its original size and position. 1128 | """ 1129 | 1130 | hwnd: int = module_find(window) 1131 | ctypes.windll.user32.ShowWindow(hwnd, 9) 1132 | 1133 | 1134 | class corner_radius: 1135 | """Control the corner radius of a window. This feature is only supported on Windows 11 and later versions.""" 1136 | 1137 | @classmethod 1138 | def set(cls, window: Any, style: str = "round") -> None: 1139 | """ 1140 | Set the corner radius style of the specified window. 1141 | 1142 | Args: 1143 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 1144 | style (str): The corner style to apply. Can be "square", "round-small", or "round". 1145 | For compatibility, "roundsmall", "small-round", and "round small" are also accepted. 1146 | Default is "round". 1147 | 1148 | Example: 1149 | >>> corner_radius.set(window, "round-small") 1150 | >>> corner_radius.set(window, "round") 1151 | >>> corner_radius.set(window, "square") 1152 | 1153 | Raises: 1154 | ValueError: If the specified style is not one of "square", "round-small", or "round". 1155 | """ 1156 | 1157 | style = style.lower() 1158 | # Map alternative style names for compatibility 1159 | if style in ["roundsmall", "small-round", "round small"]: 1160 | style = "round-small" 1161 | 1162 | if style not in ["square", "round-small", "round"]: 1163 | raise ValueError('Style must be one of "square", "round-small", or "round"') 1164 | 1165 | hwnd: int = module_find(window) 1166 | 1167 | # DWM_WINDOW_CORNER_PREFERENCE values: 1168 | # DWMWCP_DEFAULT = 0 (Let Windows decide) 1169 | # DWMWCP_DONOTROUND = 1 (Square) 1170 | # DWMWCP_ROUND = 2 (Round) 1171 | # DWMWCP_ROUNDSMALL = 3 (Round Small) 1172 | 1173 | value = {"square": 1, "round": 2, "round-small": 3}[style] 1174 | 1175 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 1176 | hwnd, 1177 | 33, # DWMWA_WINDOW_CORNER_PREFERENCE 1178 | ctypes.byref(ctypes.c_int(value)), 1179 | 4, 1180 | ) 1181 | 1182 | @classmethod 1183 | def reset(cls, window: Any) -> None: 1184 | """ 1185 | Reset the corner radius of the specified window to the system default. 1186 | 1187 | Args: 1188 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 1189 | """ 1190 | 1191 | hwnd: int = module_find(window) 1192 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 1193 | hwnd, 1194 | 33, # DWMWA_WINDOW_CORNER_PREFERENCE 1195 | ctypes.byref(ctypes.c_int(0)), # Default (Let Windows decide) 1196 | 4, 1197 | ) 1198 | 1199 | 1200 | class window_animation: 1201 | """Add linear animations to a window.""" 1202 | 1203 | @classmethod 1204 | def circle_motion( 1205 | cls, window: Any, count: int = 5, interval: int = 5, radius: int = 20 1206 | ) -> None: 1207 | """ 1208 | Move the specified window in a circular motion. 1209 | 1210 | Args: 1211 | window (object): The window object to move (e.g., a Tk instance in Tkinter). 1212 | count (int): The number of times to move the window in a circle. Default is 5. 1213 | interval (int): The interval between each movement in milliseconds. Default is 5. 1214 | radius (int): The radius of the circular motion. Default is 20. 1215 | 1216 | Example: 1217 | >>> window_animation.circle_motion(window, count=10, interval=10, radius=30) 1218 | 1219 | Notes: 1220 | The `interval` parameter controls the speed of the circular motion. Lower values will result in a faster circular motion. 1221 | """ 1222 | 1223 | def motion() -> None: 1224 | hwnd: int = module_find(window) 1225 | rect = ctypes.wintypes.RECT() 1226 | ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)) 1227 | original_x = rect.left 1228 | original_y = rect.top 1229 | for angle in range(0, 360 * count, 5): # move in a circle count times 1230 | rad: float = math.radians(angle) 1231 | x = original_x + int(radius * math.cos(rad)) 1232 | y = original_y + int(radius * math.sin(rad)) 1233 | ctypes.windll.user32.SetWindowPos(hwnd, 0, x, y, 0, 0, 0x0001) 1234 | ctypes.windll.kernel32.Sleep(interval) 1235 | 1236 | thread = threading.Thread(target=motion) 1237 | thread.daemon = True 1238 | thread.start() 1239 | 1240 | @classmethod 1241 | def vertical_shake( 1242 | cls, window: Any, count: int = 5, interval: int = 3, amplitude: int = 20 1243 | ) -> None: 1244 | """ 1245 | Shake the specified window vertically. 1246 | 1247 | Args: 1248 | window (object): The window object to shake (e.g., a Tk instance in Tkinter). 1249 | count (int): The number of times to shake the window. Default is 5. 1250 | interval (int): The interval between each shake in milliseconds. Default is 3. 1251 | amplitude (int): The amplitude or upward/downward distance of the shake. Default is 20. 1252 | 1253 | Example: 1254 | >>> window_animation.vertical_shake(window, count=10, interval=5, amplitude=30) 1255 | 1256 | Notes: 1257 | The `interval` parameter controls the speed of the shake. Lower values will result in a faster 1258 | """ 1259 | 1260 | def motion() -> None: 1261 | hwnd: int = module_find(window) 1262 | rect = ctypes.wintypes.RECT() 1263 | ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)) 1264 | original_y = rect.top 1265 | for offset in range(0, 360 * count, count): # move up and down 5 times 1266 | rad: float = math.radians(offset) 1267 | y = original_y + int(amplitude * math.sin(rad)) 1268 | ctypes.windll.user32.SetWindowPos(hwnd, 0, rect.left, y, 0, 0, 0x0001) 1269 | ctypes.windll.kernel32.Sleep(interval) 1270 | 1271 | thread = threading.Thread(target=motion) 1272 | thread.daemon = True 1273 | thread.start() 1274 | 1275 | @classmethod 1276 | def horizontal_shake( 1277 | cls, window: Any, count: int = 5, interval: int = 3, amplitude: int = 20 1278 | ) -> None: 1279 | """ 1280 | Shake the specified window horizontally. 1281 | 1282 | Args: 1283 | window (object): The window object to shake (e.g., a Tk instance in Tkinter). 1284 | count (int): The number of times to shake the window. Default is 5. 1285 | interval (int): The interval between each shake in milliseconds. Default is 3. 1286 | amplitude (int): The amplitude or left/right distance of the shake. Default is 20. 1287 | 1288 | Example: 1289 | >>> window_animation.horizontal_shake(window, count=10, interval=5, amplitude=30) 1290 | 1291 | Notes: 1292 | The `interval` parameter controls the speed of the shake. Lower values will result in a faster shake. 1293 | """ 1294 | 1295 | def motion() -> None: 1296 | hwnd: int = module_find(window) 1297 | rect = ctypes.wintypes.RECT() 1298 | ctypes.windll.user32.GetWindowRect(hwnd, ctypes.byref(rect)) 1299 | original_x = rect.left 1300 | for offset in range(0, 360 * count, count): # move left and right 5 times 1301 | rad: float = math.radians(offset) 1302 | x = original_x + int(amplitude * math.sin(rad)) 1303 | ctypes.windll.user32.SetWindowPos(hwnd, 0, x, rect.top, 0, 0, 0x0001) 1304 | ctypes.windll.kernel32.Sleep(interval) 1305 | 1306 | thread = threading.Thread(target=motion) 1307 | thread.daemon = True 1308 | thread.start() 1309 | 1310 | 1311 | class window_dwm: 1312 | """Control DWM (Desktop Window Manager) specific features.""" 1313 | 1314 | @classmethod 1315 | def toggle_dwm_transitions(cls, window: Any, enabled: bool = True) -> None: 1316 | """ 1317 | Toggle DWM transitions/animations for a window. 1318 | This affects built-in Windows animations like minimize/maximize, 1319 | but does not affect custom animations. 1320 | 1321 | Args: 1322 | window (object): The window object to modify. 1323 | enabled (bool): True to enable animations, False to disable. 1324 | 1325 | Example: 1326 | # Disable DWM animations 1327 | >>> window_dwm.toggle_dwm_transitions(window, False) 1328 | 1329 | # Re-enable DWM animations 1330 | >>> window_dwm.toggle_dwm_transitions(window, True) 1331 | """ 1332 | hwnd: int = module_find(window) 1333 | 1334 | # DWMWA_TRANSITIONS_FORCEDISABLED = 3 1335 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 1336 | hwnd, 1337 | 3, # DWMWA_TRANSITIONS_FORCEDISABLED 1338 | ctypes.byref( 1339 | ctypes.c_int(0 if enabled else 1) 1340 | ), # 0 to enable, 1 to disable 1341 | 4, 1342 | ) 1343 | 1344 | @classmethod 1345 | def toggle_rtl_layout(cls, window: Any, enabled: bool = True) -> None: 1346 | """ 1347 | Set Right-to-Left layout for window's non-client area. 1348 | 1349 | Args: 1350 | window (object): The window object to modify. 1351 | enabled (bool): True for RTL layout, False for default LTR layout. 1352 | 1353 | Example: 1354 | # Enable RTL layout (for Arabic, Hebrew, etc.) 1355 | >>> window_dwm.set_rtl_layout(window, True) 1356 | 1357 | # Reset to default Left-to-Right layout 1358 | >>> window_dwm.set_rtl_layout(window, False) 1359 | """ 1360 | hwnd: int = module_find(window) 1361 | 1362 | # DWMWA_NONCLIENT_RTL_LAYOUT = 6 1363 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 1364 | hwnd, 1365 | 6, # DWMWA_NONCLIENT_RTL_LAYOUT 1366 | ctypes.byref(ctypes.c_int(1 if enabled else 0)), 1367 | 4, 1368 | ) 1369 | 1370 | @classmethod 1371 | def toggle_cloak(cls, window: Any, enabled: bool = True) -> None: 1372 | """ 1373 | Toggle window cloaking (invisibility while still being composed by DWM). 1374 | Useful for DirectComposition animations and special effects. 1375 | Note: Only supported on Windows 8 and later. 1376 | 1377 | Args: 1378 | window (object): The window object to modify. 1379 | enabled (bool): True to cloak (hide) the window, False to uncloak (show). 1380 | 1381 | Example: 1382 | # Cloak (hide) the window 1383 | >>> window_dwm.toggle_cloak(window, True) 1384 | 1385 | # Uncloak (show) the window 1386 | >>> window_dwm.toggle_cloak(window, False) 1387 | 1388 | Notes: 1389 | - Window remains composed by DWM even when cloaked 1390 | - Particularly useful with DirectComposition for layered child window animations 1391 | - Does not affect window functionality, only visibility 1392 | """ 1393 | hwnd: int = module_find(window) 1394 | 1395 | # DWMWA_CLOAK = 13 1396 | ctypes.windll.dwmapi.DwmSetWindowAttribute( 1397 | hwnd, 1398 | 13, # DWMWA_CLOAK 1399 | ctypes.byref(ctypes.wintypes.BOOL(enabled)), 1400 | ctypes.sizeof(ctypes.wintypes.BOOL(enabled)), 1401 | ) 1402 | 1403 | 1404 | class title_text: 1405 | """Play with the title of a window.""" 1406 | 1407 | # Track both original and styled titles 1408 | _title_cache: Dict[ 1409 | int, Tuple[str, str, Optional[int]] 1410 | ] = {} # {hwnd: (original_title, styled_title, current_style)} 1411 | 1412 | @classmethod 1413 | def set(cls, window: Any, title: str) -> None: 1414 | """ 1415 | Changes the title of the specified window. 1416 | 1417 | Args: 1418 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 1419 | title (str): The new title to set. 1420 | """ 1421 | 1422 | hwnd: int = module_find(window) 1423 | # If there's a cached style, verify current title and apply style to the new title 1424 | if hwnd in cls._title_cache: 1425 | cached_original, cached_styled, current_style = cls._title_cache[hwnd] 1426 | 1427 | # Get current window title to verify cache consistency 1428 | buffer_size = 1024 1429 | buffer = ctypes.create_unicode_buffer(buffer_size) 1430 | ctypes.windll.user32.GetWindowTextW(hwnd, buffer, buffer_size) 1431 | current_title = buffer.value 1432 | 1433 | # Only apply cached style if current title matches our cache 1434 | if current_style is not None and current_title == cached_styled: 1435 | styled_title = stylize_text(title, current_style) 1436 | cls._title_cache[hwnd] = (title, styled_title, current_style) 1437 | ctypes.windll.user32.SetWindowTextW(hwnd, styled_title) 1438 | return 1439 | 1440 | # If cache is invalid, clear it 1441 | del cls._title_cache[hwnd] 1442 | 1443 | # Otherwise just set the new title 1444 | ctypes.windll.user32.SetWindowTextW(hwnd, title) 1445 | 1446 | @classmethod 1447 | def stylize(cls, window: Any, style: int = 1) -> None: 1448 | """ 1449 | Stylize the title of the specified window. 1450 | 1451 | Args: 1452 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 1453 | style (int): The style to apply to the title. There are 10 styles available (1-10). Default is 1. 1454 | """ 1455 | 1456 | hwnd: int = module_find(window) 1457 | 1458 | # Get current window title 1459 | current_title_buffer = ctypes.create_unicode_buffer(1024) 1460 | ctypes.windll.user32.GetWindowTextW(hwnd, current_title_buffer, 1024) 1461 | current_title = current_title_buffer.value 1462 | 1463 | # If no style applied yet or title has changed, update cache 1464 | if hwnd not in cls._title_cache or cls._title_cache[hwnd][1] != current_title: 1465 | cls._title_cache[hwnd] = (current_title, current_title, None) 1466 | 1467 | # Only restyle if style has changed 1468 | if cls._title_cache[hwnd][2] != style: 1469 | stylized_title = stylize_text(cls._title_cache[hwnd][0], style) 1470 | stylized_title_buffer = ctypes.create_unicode_buffer( 1471 | stylized_title, size=len(stylized_title.encode("unicode_escape")) 1472 | ) 1473 | ctypes.windll.user32.SetWindowTextW(hwnd, stylized_title_buffer) 1474 | # Update cache with new styled title and style 1475 | cls._title_cache[hwnd] = (cls._title_cache[hwnd][0], stylized_title, style) 1476 | 1477 | @classmethod 1478 | def reset(cls, window: Any) -> None: 1479 | """ 1480 | Remove the stylized title of the specified window and reset it to the original title. 1481 | 1482 | Args: 1483 | window (object): The window object to modify (e.g., a Tk instance in Tkinter). 1484 | """ 1485 | 1486 | hwnd: int = module_find(window) 1487 | if hwnd in cls._title_cache: 1488 | # Restore original title 1489 | ctypes.windll.user32.SetWindowTextW(hwnd, cls._title_cache[hwnd][0]) 1490 | del cls._title_cache[hwnd] 1491 | 1492 | 1493 | def stylize_text(text: str, style: int) -> str: 1494 | """ 1495 | Helper function to stylize the text based on the specified style. 1496 | 1497 | Args: 1498 | text (str): The text to stylize. 1499 | style (int): The style to apply to the text. There are 10 styles available (1-10). 1500 | 1501 | Returns: 1502 | str: The stylized text based on the specified style. 1503 | 1504 | Example: 1505 | stylize_text("Hello, World!", 3) 1506 | 1507 | Raises: 1508 | ValueError: If the specified style is not between 1 and 10. 1509 | """ 1510 | 1511 | normal = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" 1512 | styles = [ 1513 | "𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ1234567890", 1514 | "𝖆𝖇𝖈𝖉𝖊𝖋𝖌𝖍𝖎𝖏𝖐𝖑𝖒𝖓𝖔𝖕𝖖𝖗𝖘𝖙𝖚𝖛𝖜𝖝𝖞𝖟𝕬𝕭𝕮𝕯𝕰𝕱𝕲𝕳𝕴𝕵𝕶𝕷𝕸𝕹𝕺𝕻𝕼𝕽𝕾𝕿𝖀𝖁𝖂𝖃𝖄𝖅1234567890", 1515 | "𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩1234567890", 1516 | "𝒶𝒷𝒸𝒹𝑒𝒻𝑔𝒽𝒾𝒿𝓀𝓁𝓂𝓃𝑜𝓅𝓆𝓇𝓈𝓉𝓊𝓋𝓌𝓍𝓎𝓏𝒜𝐵𝒞𝒟𝐸𝐹𝒢𝐻𝐼𝒥𝒦𝐿𝑀𝒩𝒪𝒫𝒬𝑅𝒮𝒯𝒰𝒱𝒲𝒳𝒴𝒵𝟣𝟤𝟥𝟦𝟧𝟨𝟩𝟪𝟫𝟢", 1517 | "🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉1234567890", 1518 | "ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏ①②③④⑤⑥⑦⑧⑨⓪", 1519 | "ᗩᗷᑕᗪEᖴGᕼIᒍKᒪᗰᑎOᑭᑫᖇᔕTᑌᐯᗯ᙭YᘔᗩᗷᑕᗪEᖴGᕼIᒍKᒪᗰᑎOᑭᑫᖇᔕTᑌᐯᗯ᙭Yᘔ1234567890", 1520 | "𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡𝟘", 1521 | "₳฿₵ĐɆ₣₲ⱧłJ₭Ⱡ₥₦Ø₱QⱤ₴₮ɄV₩ӾɎⱫ₳฿₵ĐɆ₣₲ⱧłJ₭Ⱡ₥₦Ø₱QⱤ₴₮ɄV₩ӾɎⱫ1234567890", 1522 | "卂乃匚ᗪ乇千Ꮆ卄丨フҜㄥ爪几ㄖ卩Ɋ尺丂ㄒㄩᐯ山乂ㄚ乙卂乃匚ᗪ乇千Ꮆ卄丨フҜㄥ爪几ㄖ卩Ɋ尺丂ㄒㄩᐯ山乂ㄚ乙1234567890", 1523 | ] 1524 | 1525 | if style < 1 or style > len(styles): 1526 | raise ValueError("Invalid style number") 1527 | 1528 | translation_table: Dict[int, int] = str.maketrans(normal, styles[style - 1]) 1529 | return text.translate(translation_table) 1530 | 1531 | 1532 | def convert_color(color: Union[Tuple[int, int, int], str]) -> int: 1533 | """ 1534 | Helper function to convert the color value to an integer. 1535 | 1536 | Args: 1537 | color (tuple/string): The RGB or HEX color value to convert. 1538 | 1539 | Returns: 1540 | int: The converted color value as an integer. 1541 | """ 1542 | 1543 | if isinstance(color, tuple) and len(color) == 3: # RGB format 1544 | r, g, b = color 1545 | return (b << 16) | (g << 8) | r 1546 | elif isinstance(color, str) and color.startswith("#"): # HEX format 1547 | r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16) 1548 | return (b << 16) | (g << 8) | r 1549 | else: 1550 | raise ValueError("Invalid color format. Expected RGB tuple or HEX string.") 1551 | 1552 | 1553 | def get_accent_color() -> str: 1554 | """ 1555 | Helper function to get the system accent color. 1556 | 1557 | Returns: 1558 | tuple: A tuple containing the RGB color values of the system accent color. 1559 | 1560 | Example: 1561 | >>> get_accent_color() 1562 | """ 1563 | 1564 | key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\DWM") 1565 | value, type = winreg.QueryValueEx(key, "ColorizationAfterglow") 1566 | winreg.CloseKey(key) 1567 | 1568 | if len(hex(value)[4:]) == 6: 1569 | return "#" + hex(value)[4:] 1570 | else: 1571 | return "#" + hex(value)[2:] 1572 | 1573 | 1574 | def module_find(window: Any) -> int: 1575 | """ 1576 | Helper function to find the window handle based on the module used. 1577 | 1578 | Args: 1579 | window (object): The window object to find. 1580 | 1581 | Returns: 1582 | int: The window handle. 1583 | """ 1584 | 1585 | try: 1586 | window.update() # for tk 1587 | return ctypes.windll.user32.GetParent(window.winfo_id()) 1588 | except Exception: 1589 | pass 1590 | try: 1591 | return window.winId().__int__() # for pyQt and PySide 1592 | except Exception: 1593 | pass 1594 | try: 1595 | return window.GetHandle() # for wx 1596 | except Exception: 1597 | pass 1598 | try: 1599 | gdk_window = window.get_window() # for PyGTK 1600 | return gdk_window.get_xid() 1601 | except Exception: 1602 | pass 1603 | try: 1604 | return window.root_window.get_window_info().window # for kivy 1605 | except Exception: 1606 | pass 1607 | 1608 | return window # others / notfound 1609 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | with open("README.md", "r", encoding="utf-8") as f: 4 | long_description = f.read() 5 | 6 | setup( 7 | name="hPyT", 8 | version="1.4.0", 9 | description="Hack Python Titlebar - A package to manipulate windows and titlebar of GUI applications made using python.", 10 | long_description=long_description, 11 | long_description_content_type="text/markdown", 12 | author="zingzy", 13 | url="https://github.com/zingzy/hPyT", 14 | license="MIT", 15 | packages=find_packages(), 16 | classifiers=[ 17 | "Intended Audience :: Developers", 18 | "Development Status :: 5 - Production/Stable", 19 | "Intended Audience :: Developers", 20 | "License :: OSI Approved :: MIT License", 21 | "Programming Language :: Python :: 3", 22 | "Programming Language :: Python :: 3.6", 23 | "Programming Language :: Python :: 3.7", 24 | "Programming Language :: Python :: 3.8", 25 | "Programming Language :: Python :: 3.9", 26 | "Programming Language :: Python :: 3.10", 27 | "Programming Language :: Python :: 3.11", 28 | "Programming Language :: Python :: 3.12", 29 | "Operating System :: Microsoft :: Windows", 30 | ], 31 | keywords="Tkinter wxpython pyqt pyside GUI window controls decorations hide show titlebar border color animation", 32 | python_requires=">=3.6", 33 | project_urls={ 34 | "Documentation": "https://github.com/zingzy/hPyT#readme", 35 | "Source": "https://github.com/zingzy/hPyT", 36 | "Tracker": "https://github.com/zingzy/hPyT/issues", 37 | }, 38 | ) 39 | --------------------------------------------------------------------------------