├── .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 | 
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 | |

|

|
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
343 |
344 | ## Enable/Disable Minimize Button
345 |
346 | ```python
347 | minimize_button.disable(window) # hides minimize button
348 | # MinimizeButton.enable(window)
349 | ```
350 |
351 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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` |

|
437 | | `square` |

|
438 | | `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 | 
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 | |  |  |
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 | |  |  |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
770 | 
771 | 
772 | 
773 | 
774 | 
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 |
--------------------------------------------------------------------------------