centre 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- centre-0.1.0/LICENSE +21 -0
- centre-0.1.0/PKG-INFO +199 -0
- centre-0.1.0/README.md +178 -0
- centre-0.1.0/pyproject.toml +38 -0
- centre-0.1.0/setup.cfg +4 -0
- centre-0.1.0/src/centre/__init__.py +4 -0
- centre-0.1.0/src/centre/cli.py +31 -0
- centre-0.1.0/src/centre/core.py +109 -0
- centre-0.1.0/src/centre/utilities.py +71 -0
- centre-0.1.0/src/centre.egg-info/PKG-INFO +199 -0
- centre-0.1.0/src/centre.egg-info/SOURCES.txt +13 -0
- centre-0.1.0/src/centre.egg-info/dependency_links.txt +1 -0
- centre-0.1.0/src/centre.egg-info/entry_points.txt +2 -0
- centre-0.1.0/src/centre.egg-info/requires.txt +3 -0
- centre-0.1.0/src/centre.egg-info/top_level.txt +1 -0
centre-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jack Scott
|
|
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.
|
centre-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: centre
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Your Window Position Manager
|
|
5
|
+
Author-email: Jack Scott <cloner.bl12@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JackScott7/centre
|
|
8
|
+
Project-URL: Repository, https://github.com/JackScott7/centre
|
|
9
|
+
Project-URL: Issues, https://github.com/JackScott7/centre/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: keyboard>=0.13.5
|
|
18
|
+
Requires-Dist: pyautogui>=0.9.54
|
|
19
|
+
Requires-Dist: pygetwindow>=0.0.9
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Centre
|
|
23
|
+
|
|
24
|
+
[](https://opensource.org/licenses/MIT)
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Centre is your window position manager.
|
|
28
|
+
|
|
29
|
+
Take control of your windows by choosing where they appear, how they’re arranged, and what size they should be.
|
|
30
|
+
|
|
31
|
+
Consistency is `centre`'s goal.
|
|
32
|
+
|
|
33
|
+
## What Centre Tries to Achieve
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
Centre was built for users who want consistent window placement across desktop sessions.
|
|
37
|
+
|
|
38
|
+
# Install
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
```PowerShell
|
|
43
|
+
pip install centre
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Start Centre Automatically
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
To do that, create a Windows Task Scheduler task.
|
|
50
|
+
|
|
51
|
+
Run the following command as Administrator in PowerShell/cmd to create a Task in Windows Task Scheduler.
|
|
52
|
+
```PowerShell
|
|
53
|
+
schtasks /Create /TN "centre" /SC ONLOGON /TR "centre -s" /RL LIMITED /F
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
# Usage
|
|
57
|
+
|
|
58
|
+
After your window configuration is ready, start the listener:
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
$ centre -s
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
# The CLI
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
You can use `centre` CLI to find out window titles, sizes and screen positions.
|
|
69
|
+
The CLI will help you to accurately find your window title to set the exact position you want on the screen with your
|
|
70
|
+
desired size.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
|
|
74
|
+
```PowerShell
|
|
75
|
+
# List all active window titles
|
|
76
|
+
centre -l
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Refresh your config without restarting
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
You can use the predefined shortcut `ctrl+alt+r` to reload/refresh your config without restarting the background
|
|
83
|
+
process.
|
|
84
|
+
This is useful when trying to edit your config.
|
|
85
|
+
|
|
86
|
+
# Window Configuration (config.json)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
Your config will be created at the first startup in:
|
|
90
|
+
|
|
91
|
+
CMD
|
|
92
|
+
|
|
93
|
+
```cmd
|
|
94
|
+
%USERPROFILE%\.centre\config.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
PowerShell
|
|
98
|
+
|
|
99
|
+
```PowerShell
|
|
100
|
+
$env:USERPROFILE\.centre\config.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The default config includes these values:
|
|
104
|
+
|
|
105
|
+
- The resolution key is based on your display resolution.
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"presets": {
|
|
110
|
+
"1920x1080": {}
|
|
111
|
+
},
|
|
112
|
+
"predefined_keybindings": {
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"bindings": {
|
|
115
|
+
"refresh": "ctrl+alt+r",
|
|
116
|
+
"center": "ctrl+alt+d",
|
|
117
|
+
"minimize": "ctrl+alt+m"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"logging": false
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**_You can change any predefined keyboard shortcut, for example setting refresh to ctrl+shift+f10._**
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
Window presets should be placed inside the "presets" object in `config.json`.
|
|
128
|
+
|
|
129
|
+
A window preset should look like this:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"PS7": {
|
|
134
|
+
"LEFT": 224,
|
|
135
|
+
"TOP": 168,
|
|
136
|
+
"SIZE_X": 1473,
|
|
137
|
+
"SIZE_Y": 697
|
|
138
|
+
},
|
|
139
|
+
"Default_Position": {
|
|
140
|
+
"LEFT": 25,
|
|
141
|
+
"TOP": 34,
|
|
142
|
+
"SIZE_X": 1860,
|
|
143
|
+
"SIZE_Y": 980
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- Be sure to add `Default_Position` in your presets under the generated default resolution.
|
|
150
|
+
When Centre does not find the active window in your presets, it uses `Default_Position` as the fallback size and position.
|
|
151
|
+
|
|
152
|
+
- `Default_Position` is useful when you have a list of apps that you have set a custom position for,
|
|
153
|
+
but intend to keep all other apps in one specific location.
|
|
154
|
+
|
|
155
|
+
Your final config should look something like this:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"presets": {
|
|
160
|
+
"1920x1080": {
|
|
161
|
+
"PS7": {
|
|
162
|
+
"LEFT": 224,
|
|
163
|
+
"TOP": 168,
|
|
164
|
+
"SIZE_X": 1473,
|
|
165
|
+
"SIZE_Y": 697
|
|
166
|
+
},
|
|
167
|
+
"Default_Position": {
|
|
168
|
+
"LEFT": 25,
|
|
169
|
+
"TOP": 34,
|
|
170
|
+
"SIZE_X": 1860,
|
|
171
|
+
"SIZE_Y": 980
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"predefined_keybindings": {
|
|
176
|
+
"enabled": true,
|
|
177
|
+
"bindings": {
|
|
178
|
+
"refresh": "ctrl+alt+r",
|
|
179
|
+
"center": "ctrl+alt+d",
|
|
180
|
+
"minimize": "ctrl+alt+m"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"logging": false
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
# Caveats
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
Some windows may overlap even when they use the same configured position and size.
|
|
192
|
+
|
|
193
|
+
This is due to some apps having a bigger actual window than the rendered UI.
|
|
194
|
+
|
|
195
|
+
# License
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
centre-0.1.0/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Centre
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Centre is your window position manager.
|
|
7
|
+
|
|
8
|
+
Take control of your windows by choosing where they appear, how they’re arranged, and what size they should be.
|
|
9
|
+
|
|
10
|
+
Consistency is `centre`'s goal.
|
|
11
|
+
|
|
12
|
+
## What Centre Tries to Achieve
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
Centre was built for users who want consistent window placement across desktop sessions.
|
|
16
|
+
|
|
17
|
+
# Install
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
```PowerShell
|
|
22
|
+
pip install centre
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Start Centre Automatically
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
To do that, create a Windows Task Scheduler task.
|
|
29
|
+
|
|
30
|
+
Run the following command as Administrator in PowerShell/cmd to create a Task in Windows Task Scheduler.
|
|
31
|
+
```PowerShell
|
|
32
|
+
schtasks /Create /TN "centre" /SC ONLOGON /TR "centre -s" /RL LIMITED /F
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
# Usage
|
|
36
|
+
|
|
37
|
+
After your window configuration is ready, start the listener:
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
$ centre -s
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
# The CLI
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
You can use `centre` CLI to find out window titles, sizes and screen positions.
|
|
48
|
+
The CLI will help you to accurately find your window title to set the exact position you want on the screen with your
|
|
49
|
+
desired size.
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
|
|
53
|
+
```PowerShell
|
|
54
|
+
# List all active window titles
|
|
55
|
+
centre -l
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Refresh your config without restarting
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
You can use the predefined shortcut `ctrl+alt+r` to reload/refresh your config without restarting the background
|
|
62
|
+
process.
|
|
63
|
+
This is useful when trying to edit your config.
|
|
64
|
+
|
|
65
|
+
# Window Configuration (config.json)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
Your config will be created at the first startup in:
|
|
69
|
+
|
|
70
|
+
CMD
|
|
71
|
+
|
|
72
|
+
```cmd
|
|
73
|
+
%USERPROFILE%\.centre\config.json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
PowerShell
|
|
77
|
+
|
|
78
|
+
```PowerShell
|
|
79
|
+
$env:USERPROFILE\.centre\config.json
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The default config includes these values:
|
|
83
|
+
|
|
84
|
+
- The resolution key is based on your display resolution.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{
|
|
88
|
+
"presets": {
|
|
89
|
+
"1920x1080": {}
|
|
90
|
+
},
|
|
91
|
+
"predefined_keybindings": {
|
|
92
|
+
"enabled": true,
|
|
93
|
+
"bindings": {
|
|
94
|
+
"refresh": "ctrl+alt+r",
|
|
95
|
+
"center": "ctrl+alt+d",
|
|
96
|
+
"minimize": "ctrl+alt+m"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"logging": false
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**_You can change any predefined keyboard shortcut, for example setting refresh to ctrl+shift+f10._**
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
Window presets should be placed inside the "presets" object in `config.json`.
|
|
107
|
+
|
|
108
|
+
A window preset should look like this:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"PS7": {
|
|
113
|
+
"LEFT": 224,
|
|
114
|
+
"TOP": 168,
|
|
115
|
+
"SIZE_X": 1473,
|
|
116
|
+
"SIZE_Y": 697
|
|
117
|
+
},
|
|
118
|
+
"Default_Position": {
|
|
119
|
+
"LEFT": 25,
|
|
120
|
+
"TOP": 34,
|
|
121
|
+
"SIZE_X": 1860,
|
|
122
|
+
"SIZE_Y": 980
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- Be sure to add `Default_Position` in your presets under the generated default resolution.
|
|
129
|
+
When Centre does not find the active window in your presets, it uses `Default_Position` as the fallback size and position.
|
|
130
|
+
|
|
131
|
+
- `Default_Position` is useful when you have a list of apps that you have set a custom position for,
|
|
132
|
+
but intend to keep all other apps in one specific location.
|
|
133
|
+
|
|
134
|
+
Your final config should look something like this:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"presets": {
|
|
139
|
+
"1920x1080": {
|
|
140
|
+
"PS7": {
|
|
141
|
+
"LEFT": 224,
|
|
142
|
+
"TOP": 168,
|
|
143
|
+
"SIZE_X": 1473,
|
|
144
|
+
"SIZE_Y": 697
|
|
145
|
+
},
|
|
146
|
+
"Default_Position": {
|
|
147
|
+
"LEFT": 25,
|
|
148
|
+
"TOP": 34,
|
|
149
|
+
"SIZE_X": 1860,
|
|
150
|
+
"SIZE_Y": 980
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"predefined_keybindings": {
|
|
155
|
+
"enabled": true,
|
|
156
|
+
"bindings": {
|
|
157
|
+
"refresh": "ctrl+alt+r",
|
|
158
|
+
"center": "ctrl+alt+d",
|
|
159
|
+
"minimize": "ctrl+alt+m"
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"logging": false
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
# Caveats
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
Some windows may overlap even when they use the same configured position and size.
|
|
171
|
+
|
|
172
|
+
This is due to some apps having a bigger actual window than the rendered UI.
|
|
173
|
+
|
|
174
|
+
# License
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "centre"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Your Window Position Manager"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Jack Scott", email = "cloner.bl12@gmail.com" }
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICENSE"]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"keyboard>=0.13.5",
|
|
18
|
+
"pyautogui>=0.9.54",
|
|
19
|
+
"pygetwindow>=0.0.9",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Operating System :: Microsoft :: Windows",
|
|
26
|
+
"Environment :: Win32 (MS Windows)",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/JackScott7/centre"
|
|
31
|
+
Repository = "https://github.com/JackScott7/centre"
|
|
32
|
+
Issues = "https://github.com/JackScott7/centre/issues"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
centre = "centre.cli:main"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
centre-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from .core import Centre
|
|
3
|
+
from .utilities import Utilities
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main() -> None:
|
|
7
|
+
parser = argparse.ArgumentParser(prog="centre", description='Centre is A lightweight Windows tool'
|
|
8
|
+
' for consistent app placement, sizing, and desktop layout control.')
|
|
9
|
+
|
|
10
|
+
group = parser.add_mutually_exclusive_group()
|
|
11
|
+
group.add_argument('-l', '--list', action='store_true', help='List Window Title, Size and Position')
|
|
12
|
+
group.add_argument('-s', '--start', action='store_true', help='Start Centre as background process')
|
|
13
|
+
group.add_argument('-c', '--read-config', action='store_true', help='Read config file and print out')
|
|
14
|
+
|
|
15
|
+
args = parser.parse_args()
|
|
16
|
+
|
|
17
|
+
ctr = Centre()
|
|
18
|
+
if args.list:
|
|
19
|
+
titles = Utilities.list_window_titles()
|
|
20
|
+
print(titles)
|
|
21
|
+
elif args.read_config:
|
|
22
|
+
print(ctr.get_config)
|
|
23
|
+
elif args.start:
|
|
24
|
+
ctr.listen()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
main()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
__all__ = ["Centre"]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import keyboard
|
|
5
|
+
from .utilities import Utilities
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Centre:
|
|
9
|
+
"""
|
|
10
|
+
Base class for Centre
|
|
11
|
+
|
|
12
|
+
This is where config and log handling are initialized.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self):
|
|
15
|
+
config_dir = os.path.join(os.path.expanduser("~"), ".centre")
|
|
16
|
+
if not os.path.isdir(config_dir):
|
|
17
|
+
os.mkdir(config_dir)
|
|
18
|
+
|
|
19
|
+
self.__config_file_path = os.path.join(config_dir, "config.json")
|
|
20
|
+
|
|
21
|
+
if not os.path.isfile(self.__config_file_path):
|
|
22
|
+
with open(self.__config_file_path, 'w') as f:
|
|
23
|
+
self.__config = {
|
|
24
|
+
"presets": {
|
|
25
|
+
f"{Utilities.get_display_resolution()}": {}
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
"predefined_keybindings": {
|
|
29
|
+
"enabled": True,
|
|
30
|
+
"bindings": {
|
|
31
|
+
"refresh": "ctrl+alt+r",
|
|
32
|
+
"center": "ctrl+alt+d",
|
|
33
|
+
"minimize": "ctrl+alt+m",
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"logging": False
|
|
37
|
+
}
|
|
38
|
+
json.dump(self.__config, f, indent=4)
|
|
39
|
+
return
|
|
40
|
+
self.load_config()
|
|
41
|
+
|
|
42
|
+
self.__logging = self.__config.get("logging", False)
|
|
43
|
+
if self.__logging:
|
|
44
|
+
logging.basicConfig(level=logging.INFO, filename=Utilities.get_log_file_path())
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def get_config(self) -> str:
|
|
48
|
+
"""
|
|
49
|
+
Get the loaded config upon initialization
|
|
50
|
+
|
|
51
|
+
:return: loaded config
|
|
52
|
+
"""
|
|
53
|
+
return json.dumps(self.__config, indent=4)
|
|
54
|
+
|
|
55
|
+
def load_config(self) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Load the `config.json`
|
|
58
|
+
|
|
59
|
+
This method is used to refresh/reload the config without restarting the background process.
|
|
60
|
+
|
|
61
|
+
Config will be accessible through Centre.get_config
|
|
62
|
+
|
|
63
|
+
:return: None
|
|
64
|
+
"""
|
|
65
|
+
try:
|
|
66
|
+
with open(self.__config_file_path, "r") as f:
|
|
67
|
+
self.__config = json.load(f)
|
|
68
|
+
except FileNotFoundError as e:
|
|
69
|
+
raise FileNotFoundError(f"Config file not found at '{self.__config_file_path}'\nPlease run centre again.")
|
|
70
|
+
except json.decoder.JSONDecodeError as parse_error:
|
|
71
|
+
print(f"Syntax error in {self.__config_file_path}\nError: {parse_error.msg} on line {parse_error.lineno}")
|
|
72
|
+
exit(1)
|
|
73
|
+
|
|
74
|
+
def __get_keybindings(self) -> dict:
|
|
75
|
+
bindings = self.__config.get("predefined_keybindings").get("bindings", {})
|
|
76
|
+
return bindings
|
|
77
|
+
|
|
78
|
+
def __assign_keyboard_bindings(self, bindings: dict) -> None:
|
|
79
|
+
if not bindings:
|
|
80
|
+
raise ValueError("Your config seems to have an issue that's causing centre to exit.")
|
|
81
|
+
|
|
82
|
+
if not self.__config.get("predefined_keybindings").get("enabled", False):
|
|
83
|
+
raise ValueError("Please enable the predefined keybindings in config so centre can assign keybindings.")
|
|
84
|
+
|
|
85
|
+
for k,v in bindings.items():
|
|
86
|
+
if k == "refresh":
|
|
87
|
+
keyboard.add_hotkey(v, Utilities.refresh_hotkey, (self,))
|
|
88
|
+
elif k == "center":
|
|
89
|
+
args = (self.__config.get("presets"), Utilities.get_display_resolution())
|
|
90
|
+
keyboard.add_hotkey(v, Utilities.center_hotkey, args)
|
|
91
|
+
elif k == "minimize":
|
|
92
|
+
keyboard.add_hotkey(v, Utilities.minimize_window_hotkey)
|
|
93
|
+
else:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
def listen(self) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Main entry point for centre, will run in the background when called.
|
|
99
|
+
|
|
100
|
+
Keyboard shortcuts in config will be processed.
|
|
101
|
+
:return: None
|
|
102
|
+
"""
|
|
103
|
+
bindings = self.__get_keybindings()
|
|
104
|
+
self.__assign_keyboard_bindings(bindings)
|
|
105
|
+
try:
|
|
106
|
+
print("[+] Centre running in background")
|
|
107
|
+
keyboard.wait()
|
|
108
|
+
except KeyboardInterrupt or Exception:
|
|
109
|
+
exit(0)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import pygetwindow as gw
|
|
4
|
+
from pyautogui import size as resolution_size
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Utilities:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def entry_path(file: str) -> str:
|
|
10
|
+
return os.path.dirname(os.path.abspath(file))
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_display_resolution() -> str:
|
|
14
|
+
width, height = resolution_size()
|
|
15
|
+
return f"{width}x{height}"
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def get_log_file_path() -> str:
|
|
19
|
+
config_dir = os.path.join(os.path.expanduser("~"), '.centre')
|
|
20
|
+
return os.path.join(config_dir, 'centre.log')
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def minimize_window_hotkey() -> None:
|
|
24
|
+
window = gw.getActiveWindow()
|
|
25
|
+
if window:
|
|
26
|
+
window.minimize()
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def center_hotkey(presets, current_preset) -> None:
|
|
30
|
+
window = gw.getActiveWindow()
|
|
31
|
+
if not window:
|
|
32
|
+
return
|
|
33
|
+
app_poses = presets[current_preset]
|
|
34
|
+
|
|
35
|
+
if not app_poses.get('Default_Position', None):
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
for key in app_poses.keys():
|
|
39
|
+
if key in window.title:
|
|
40
|
+
window.resizeTo(app_poses[key]['SIZE_X'], app_poses[key]['SIZE_Y'])
|
|
41
|
+
window.moveTo(app_poses[key]['LEFT'], app_poses[key]['TOP'])
|
|
42
|
+
return
|
|
43
|
+
else:
|
|
44
|
+
if window:
|
|
45
|
+
window.resizeTo(app_poses['Default_Position']['SIZE_X'], app_poses['Default_Position']['SIZE_Y'])
|
|
46
|
+
window.moveTo(app_poses['Default_Position']['LEFT'], app_poses['Default_Position']['TOP'])
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def refresh_hotkey(centre) -> None:
|
|
50
|
+
centre.load_config()
|
|
51
|
+
|
|
52
|
+
@staticmethod
|
|
53
|
+
def list_window_titles() -> str:
|
|
54
|
+
"""
|
|
55
|
+
Gets a list of all window titles.
|
|
56
|
+
|
|
57
|
+
:return: list of window titles
|
|
58
|
+
"""
|
|
59
|
+
return json.dumps([
|
|
60
|
+
{
|
|
61
|
+
"title": x.title,
|
|
62
|
+
"size": {
|
|
63
|
+
"width": x.size.width,
|
|
64
|
+
"height": x.size.height
|
|
65
|
+
},
|
|
66
|
+
"top": x.top,
|
|
67
|
+
"left": x.left,
|
|
68
|
+
"bottom": x.bottom,
|
|
69
|
+
"right": x.right,
|
|
70
|
+
} for x in gw.getAllWindows() if x.title
|
|
71
|
+
], indent=4)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: centre
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Your Window Position Manager
|
|
5
|
+
Author-email: Jack Scott <cloner.bl12@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/JackScott7/centre
|
|
8
|
+
Project-URL: Repository, https://github.com/JackScott7/centre
|
|
9
|
+
Project-URL: Issues, https://github.com/JackScott7/centre/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: keyboard>=0.13.5
|
|
18
|
+
Requires-Dist: pyautogui>=0.9.54
|
|
19
|
+
Requires-Dist: pygetwindow>=0.0.9
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Centre
|
|
23
|
+
|
|
24
|
+
[](https://opensource.org/licenses/MIT)
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Centre is your window position manager.
|
|
28
|
+
|
|
29
|
+
Take control of your windows by choosing where they appear, how they’re arranged, and what size they should be.
|
|
30
|
+
|
|
31
|
+
Consistency is `centre`'s goal.
|
|
32
|
+
|
|
33
|
+
## What Centre Tries to Achieve
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
Centre was built for users who want consistent window placement across desktop sessions.
|
|
37
|
+
|
|
38
|
+
# Install
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
```PowerShell
|
|
43
|
+
pip install centre
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Start Centre Automatically
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
To do that, create a Windows Task Scheduler task.
|
|
50
|
+
|
|
51
|
+
Run the following command as Administrator in PowerShell/cmd to create a Task in Windows Task Scheduler.
|
|
52
|
+
```PowerShell
|
|
53
|
+
schtasks /Create /TN "centre" /SC ONLOGON /TR "centre -s" /RL LIMITED /F
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
# Usage
|
|
57
|
+
|
|
58
|
+
After your window configuration is ready, start the listener:
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
$ centre -s
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
# The CLI
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
You can use `centre` CLI to find out window titles, sizes and screen positions.
|
|
69
|
+
The CLI will help you to accurately find your window title to set the exact position you want on the screen with your
|
|
70
|
+
desired size.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
|
|
74
|
+
```PowerShell
|
|
75
|
+
# List all active window titles
|
|
76
|
+
centre -l
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Refresh your config without restarting
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
You can use the predefined shortcut `ctrl+alt+r` to reload/refresh your config without restarting the background
|
|
83
|
+
process.
|
|
84
|
+
This is useful when trying to edit your config.
|
|
85
|
+
|
|
86
|
+
# Window Configuration (config.json)
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
Your config will be created at the first startup in:
|
|
90
|
+
|
|
91
|
+
CMD
|
|
92
|
+
|
|
93
|
+
```cmd
|
|
94
|
+
%USERPROFILE%\.centre\config.json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
PowerShell
|
|
98
|
+
|
|
99
|
+
```PowerShell
|
|
100
|
+
$env:USERPROFILE\.centre\config.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The default config includes these values:
|
|
104
|
+
|
|
105
|
+
- The resolution key is based on your display resolution.
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"presets": {
|
|
110
|
+
"1920x1080": {}
|
|
111
|
+
},
|
|
112
|
+
"predefined_keybindings": {
|
|
113
|
+
"enabled": true,
|
|
114
|
+
"bindings": {
|
|
115
|
+
"refresh": "ctrl+alt+r",
|
|
116
|
+
"center": "ctrl+alt+d",
|
|
117
|
+
"minimize": "ctrl+alt+m"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"logging": false
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**_You can change any predefined keyboard shortcut, for example setting refresh to ctrl+shift+f10._**
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
Window presets should be placed inside the "presets" object in `config.json`.
|
|
128
|
+
|
|
129
|
+
A window preset should look like this:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"PS7": {
|
|
134
|
+
"LEFT": 224,
|
|
135
|
+
"TOP": 168,
|
|
136
|
+
"SIZE_X": 1473,
|
|
137
|
+
"SIZE_Y": 697
|
|
138
|
+
},
|
|
139
|
+
"Default_Position": {
|
|
140
|
+
"LEFT": 25,
|
|
141
|
+
"TOP": 34,
|
|
142
|
+
"SIZE_X": 1860,
|
|
143
|
+
"SIZE_Y": 980
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- Be sure to add `Default_Position` in your presets under the generated default resolution.
|
|
150
|
+
When Centre does not find the active window in your presets, it uses `Default_Position` as the fallback size and position.
|
|
151
|
+
|
|
152
|
+
- `Default_Position` is useful when you have a list of apps that you have set a custom position for,
|
|
153
|
+
but intend to keep all other apps in one specific location.
|
|
154
|
+
|
|
155
|
+
Your final config should look something like this:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"presets": {
|
|
160
|
+
"1920x1080": {
|
|
161
|
+
"PS7": {
|
|
162
|
+
"LEFT": 224,
|
|
163
|
+
"TOP": 168,
|
|
164
|
+
"SIZE_X": 1473,
|
|
165
|
+
"SIZE_Y": 697
|
|
166
|
+
},
|
|
167
|
+
"Default_Position": {
|
|
168
|
+
"LEFT": 25,
|
|
169
|
+
"TOP": 34,
|
|
170
|
+
"SIZE_X": 1860,
|
|
171
|
+
"SIZE_Y": 980
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
"predefined_keybindings": {
|
|
176
|
+
"enabled": true,
|
|
177
|
+
"bindings": {
|
|
178
|
+
"refresh": "ctrl+alt+r",
|
|
179
|
+
"center": "ctrl+alt+d",
|
|
180
|
+
"minimize": "ctrl+alt+m"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
"logging": false
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
# Caveats
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
Some windows may overlap even when they use the same configured position and size.
|
|
192
|
+
|
|
193
|
+
This is due to some apps having a bigger actual window than the rendered UI.
|
|
194
|
+
|
|
195
|
+
# License
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
This project is licensed under the MIT License. See [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/centre/__init__.py
|
|
5
|
+
src/centre/cli.py
|
|
6
|
+
src/centre/core.py
|
|
7
|
+
src/centre/utilities.py
|
|
8
|
+
src/centre.egg-info/PKG-INFO
|
|
9
|
+
src/centre.egg-info/SOURCES.txt
|
|
10
|
+
src/centre.egg-info/dependency_links.txt
|
|
11
|
+
src/centre.egg-info/entry_points.txt
|
|
12
|
+
src/centre.egg-info/requires.txt
|
|
13
|
+
src/centre.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
centre
|