QTS3native 0.1.1__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.
- qts3native-0.1.1/LICENCE +21 -0
- qts3native-0.1.1/PKG-INFO +90 -0
- qts3native-0.1.1/README.md +67 -0
- qts3native-0.1.1/pyproject.toml +39 -0
- qts3native-0.1.1/setup.cfg +4 -0
- qts3native-0.1.1/src/QTS3native/__init__.py +5 -0
- qts3native-0.1.1/src/QTS3native/keylistener.py +236 -0
- qts3native-0.1.1/src/QTS3native/qtElements.py +691 -0
- qts3native-0.1.1/src/QTS3native/rpcs3Hooks.py +330 -0
- qts3native-0.1.1/src/QTS3native.egg-info/PKG-INFO +90 -0
- qts3native-0.1.1/src/QTS3native.egg-info/SOURCES.txt +12 -0
- qts3native-0.1.1/src/QTS3native.egg-info/dependency_links.txt +1 -0
- qts3native-0.1.1/src/QTS3native.egg-info/requires.txt +4 -0
- qts3native-0.1.1/src/QTS3native.egg-info/top_level.txt +1 -0
qts3native-0.1.1/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 l-S4M-l
|
|
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.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: QTS3native
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: PyQt menu, key listener, and RPCS3 hook helpers for Skate 3 native tooling
|
|
5
|
+
Author: S4M
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/l-S4M-l/QTS3native
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/l-S4M-l/QTS3native/issues
|
|
9
|
+
Project-URL: Ko-fi, https://ko-fi.com/chillsam2
|
|
10
|
+
Keywords: rpcs3,skate3,pyqt,modding,pymem
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENCE
|
|
18
|
+
Requires-Dist: PyQt5
|
|
19
|
+
Requires-Dist: keyboard
|
|
20
|
+
Requires-Dist: pymem
|
|
21
|
+
Requires-Dist: clipboard
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# QTS3native
|
|
25
|
+
|
|
26
|
+
QTS3native is a Python library for building Skate 3 native-style menus and input-driven tooling on top of RPCS3.
|
|
27
|
+
|
|
28
|
+
It bundles three main parts:
|
|
29
|
+
|
|
30
|
+
- a PyQt-based menu and UI system
|
|
31
|
+
- an RPCS3 controller and memory hook layer
|
|
32
|
+
- a global keyboard listener for menu input and text entry
|
|
33
|
+
|
|
34
|
+
The library is aimed at Windows-based Skate 3 tooling where you want a native-feeling in-game style menu driven by controller input, keyboard input, or both.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- PyQt menu item system with buttons, toggles, sliders, option items, labels, and typing items
|
|
39
|
+
- controller polling thread for reading Skate 3 / RPCS3 input values
|
|
40
|
+
- bind handler for opening or triggering menu actions from controller input
|
|
41
|
+
- global keyboard listener for typing and keyboard-driven interactions
|
|
42
|
+
- internal Skate 3 menu text writer for rendering menu text into RPCS3 memory
|
|
43
|
+
- AOB scan helper for searching memory regions
|
|
44
|
+
|
|
45
|
+
## Modules
|
|
46
|
+
|
|
47
|
+
### `qtElements.py`
|
|
48
|
+
|
|
49
|
+
Contains the main menu and UI logic, including:
|
|
50
|
+
|
|
51
|
+
- `menu_controller`
|
|
52
|
+
- `sub_menu`
|
|
53
|
+
- menu item classes
|
|
54
|
+
- `bind_handler`
|
|
55
|
+
- controller-to-menu input routing
|
|
56
|
+
|
|
57
|
+
### `rpcs3Hooks.py`
|
|
58
|
+
|
|
59
|
+
Contains the RPCS3-side helpers, including:
|
|
60
|
+
|
|
61
|
+
- `Controller`
|
|
62
|
+
- `ControllerThread`
|
|
63
|
+
- `skate_3_internal_menu_controller`
|
|
64
|
+
|
|
65
|
+
This module reads controller state from RPCS3 memory, applies deadzones, and exposes the values to the menu system. It also writes menu text into the Skate 3 internal menu text area and includes an AOB scanning helper.
|
|
66
|
+
|
|
67
|
+
### `keylistener.py`
|
|
68
|
+
|
|
69
|
+
Contains the global keyboard listener used for keyboard input, text entry, and menu interaction.
|
|
70
|
+
|
|
71
|
+
## Requirements
|
|
72
|
+
|
|
73
|
+
- Python 3.10+
|
|
74
|
+
- Windows
|
|
75
|
+
- RPCS3 running
|
|
76
|
+
- Skate 3 running in RPCS3
|
|
77
|
+
- permission to read and write process memory
|
|
78
|
+
|
|
79
|
+
## Dependencies
|
|
80
|
+
|
|
81
|
+
- `PyQt5`
|
|
82
|
+
- `pymem`
|
|
83
|
+
- `clipboard`
|
|
84
|
+
- `keyboard`
|
|
85
|
+
|
|
86
|
+
## Install
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
pip install QTS3native
|
|
90
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# QTS3native
|
|
2
|
+
|
|
3
|
+
QTS3native is a Python library for building Skate 3 native-style menus and input-driven tooling on top of RPCS3.
|
|
4
|
+
|
|
5
|
+
It bundles three main parts:
|
|
6
|
+
|
|
7
|
+
- a PyQt-based menu and UI system
|
|
8
|
+
- an RPCS3 controller and memory hook layer
|
|
9
|
+
- a global keyboard listener for menu input and text entry
|
|
10
|
+
|
|
11
|
+
The library is aimed at Windows-based Skate 3 tooling where you want a native-feeling in-game style menu driven by controller input, keyboard input, or both.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- PyQt menu item system with buttons, toggles, sliders, option items, labels, and typing items
|
|
16
|
+
- controller polling thread for reading Skate 3 / RPCS3 input values
|
|
17
|
+
- bind handler for opening or triggering menu actions from controller input
|
|
18
|
+
- global keyboard listener for typing and keyboard-driven interactions
|
|
19
|
+
- internal Skate 3 menu text writer for rendering menu text into RPCS3 memory
|
|
20
|
+
- AOB scan helper for searching memory regions
|
|
21
|
+
|
|
22
|
+
## Modules
|
|
23
|
+
|
|
24
|
+
### `qtElements.py`
|
|
25
|
+
|
|
26
|
+
Contains the main menu and UI logic, including:
|
|
27
|
+
|
|
28
|
+
- `menu_controller`
|
|
29
|
+
- `sub_menu`
|
|
30
|
+
- menu item classes
|
|
31
|
+
- `bind_handler`
|
|
32
|
+
- controller-to-menu input routing
|
|
33
|
+
|
|
34
|
+
### `rpcs3Hooks.py`
|
|
35
|
+
|
|
36
|
+
Contains the RPCS3-side helpers, including:
|
|
37
|
+
|
|
38
|
+
- `Controller`
|
|
39
|
+
- `ControllerThread`
|
|
40
|
+
- `skate_3_internal_menu_controller`
|
|
41
|
+
|
|
42
|
+
This module reads controller state from RPCS3 memory, applies deadzones, and exposes the values to the menu system. It also writes menu text into the Skate 3 internal menu text area and includes an AOB scanning helper.
|
|
43
|
+
|
|
44
|
+
### `keylistener.py`
|
|
45
|
+
|
|
46
|
+
Contains the global keyboard listener used for keyboard input, text entry, and menu interaction.
|
|
47
|
+
|
|
48
|
+
## Requirements
|
|
49
|
+
|
|
50
|
+
- Python 3.10+
|
|
51
|
+
- Windows
|
|
52
|
+
- RPCS3 running
|
|
53
|
+
- Skate 3 running in RPCS3
|
|
54
|
+
- permission to read and write process memory
|
|
55
|
+
|
|
56
|
+
## Dependencies
|
|
57
|
+
|
|
58
|
+
- `PyQt5`
|
|
59
|
+
- `pymem`
|
|
60
|
+
- `clipboard`
|
|
61
|
+
- `keyboard`
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install QTS3native
|
|
67
|
+
```
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "QTS3native"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "PyQt menu, key listener, and RPCS3 hook helpers for Skate 3 native tooling"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "S4M" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["rpcs3", "skate3", "pyqt", "modding", "pymem"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: Microsoft :: Windows",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
dependencies = [
|
|
24
|
+
"PyQt5",
|
|
25
|
+
"keyboard",
|
|
26
|
+
"pymem",
|
|
27
|
+
"clipboard"
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools]
|
|
31
|
+
package-dir = {"" = "src"}
|
|
32
|
+
|
|
33
|
+
[tool.setuptools.packages.find]
|
|
34
|
+
where = ["src"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/l-S4M-l/QTS3native"
|
|
38
|
+
"Bug Tracker" = "https://github.com/l-S4M-l/QTS3native/issues"
|
|
39
|
+
Ko-fi = "https://ko-fi.com/chillsam2"
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import keyboard
|
|
2
|
+
from PyQt5 import QtCore
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GlobalKeyboardListener(QtCore.QThread):
|
|
6
|
+
keys_pressed = QtCore.pyqtSignal(tuple)
|
|
7
|
+
|
|
8
|
+
NON_SYMBOL_KEYS = [
|
|
9
|
+
"BACKSPACE",
|
|
10
|
+
"TAB",
|
|
11
|
+
"ENTER",
|
|
12
|
+
"SHIFT",
|
|
13
|
+
"CTRL",
|
|
14
|
+
"ALT",
|
|
15
|
+
"ALT_GR",
|
|
16
|
+
"PAUSE",
|
|
17
|
+
"CAPSLOCK",
|
|
18
|
+
"ESCAPE",
|
|
19
|
+
"SPACE",
|
|
20
|
+
"PAGE_UP",
|
|
21
|
+
"PAGE_DOWN",
|
|
22
|
+
"END",
|
|
23
|
+
"HOME",
|
|
24
|
+
"LEFT",
|
|
25
|
+
"UP",
|
|
26
|
+
"RIGHT",
|
|
27
|
+
"DOWN",
|
|
28
|
+
"INSERT",
|
|
29
|
+
"DELETE",
|
|
30
|
+
"PRINT_SCREEN",
|
|
31
|
+
"SCROLLLOCK",
|
|
32
|
+
"NUMLOCK",
|
|
33
|
+
"LWIN",
|
|
34
|
+
"RWIN",
|
|
35
|
+
"MENU",
|
|
36
|
+
"APPS",
|
|
37
|
+
"CLEAR",
|
|
38
|
+
"HELP",
|
|
39
|
+
"SELECT",
|
|
40
|
+
"EXECUTE",
|
|
41
|
+
"SLEEP",
|
|
42
|
+
"DECIMAL",
|
|
43
|
+
"SEPARATOR",
|
|
44
|
+
"SUBTRACT",
|
|
45
|
+
"ADD",
|
|
46
|
+
"MULTIPLY",
|
|
47
|
+
"DIVIDE",
|
|
48
|
+
"NUMPAD_0",
|
|
49
|
+
"NUMPAD_1",
|
|
50
|
+
"NUMPAD_2",
|
|
51
|
+
"NUMPAD_3",
|
|
52
|
+
"NUMPAD_4",
|
|
53
|
+
"NUMPAD_5",
|
|
54
|
+
"NUMPAD_6",
|
|
55
|
+
"NUMPAD_7",
|
|
56
|
+
"NUMPAD_8",
|
|
57
|
+
"NUMPAD_9",
|
|
58
|
+
"F1",
|
|
59
|
+
"F2",
|
|
60
|
+
"F3",
|
|
61
|
+
"F4",
|
|
62
|
+
"F5",
|
|
63
|
+
"F6",
|
|
64
|
+
"F7",
|
|
65
|
+
"F8",
|
|
66
|
+
"F9",
|
|
67
|
+
"F10",
|
|
68
|
+
"F11",
|
|
69
|
+
"F12",
|
|
70
|
+
"F13",
|
|
71
|
+
"F14",
|
|
72
|
+
"F15",
|
|
73
|
+
"F16",
|
|
74
|
+
"F17",
|
|
75
|
+
"F18",
|
|
76
|
+
"F19",
|
|
77
|
+
"F20",
|
|
78
|
+
"F21",
|
|
79
|
+
"F22",
|
|
80
|
+
"F23",
|
|
81
|
+
"F24",
|
|
82
|
+
"MEDIA_PLAY_PAUSE",
|
|
83
|
+
"MEDIA_STOP",
|
|
84
|
+
"MEDIA_NEXT_TRACK",
|
|
85
|
+
"MEDIA_PREV_TRACK",
|
|
86
|
+
"VOLUME_UP",
|
|
87
|
+
"VOLUME_DOWN",
|
|
88
|
+
"VOLUME_MUTE",
|
|
89
|
+
"BROWSER_BACK",
|
|
90
|
+
"BROWSER_FORWARD",
|
|
91
|
+
"BROWSER_REFRESH",
|
|
92
|
+
"BROWSER_STOP",
|
|
93
|
+
"BROWSER_SEARCH",
|
|
94
|
+
"BROWSER_FAVORITES",
|
|
95
|
+
"BROWSER_HOME",
|
|
96
|
+
"LAUNCH_MAIL",
|
|
97
|
+
"LAUNCH_MEDIA_SELECT",
|
|
98
|
+
"LAUNCH_APP1",
|
|
99
|
+
"LAUNCH_APP2",
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
MODIFIER_KEYS = {"CTRL", "ALT", "SHIFT", "ALT_GR"}
|
|
103
|
+
MODIFIER_ORDER = {
|
|
104
|
+
"CTRL": 0,
|
|
105
|
+
"ALT": 1,
|
|
106
|
+
"ALT_GR": 2,
|
|
107
|
+
"SHIFT": 3,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
KEY_NAME_MAP = {
|
|
111
|
+
"backspace": "BACKSPACE",
|
|
112
|
+
"tab": "TAB",
|
|
113
|
+
"enter": "ENTER",
|
|
114
|
+
"return": "ENTER",
|
|
115
|
+
"shift": "SHIFT",
|
|
116
|
+
"left shift": "SHIFT",
|
|
117
|
+
"right shift": "SHIFT",
|
|
118
|
+
"ctrl": "CTRL",
|
|
119
|
+
"left ctrl": "CTRL",
|
|
120
|
+
"right ctrl": "CTRL",
|
|
121
|
+
"alt": "ALT",
|
|
122
|
+
"left alt": "ALT",
|
|
123
|
+
"right alt": "ALT",
|
|
124
|
+
"alt gr": "ALT_GR",
|
|
125
|
+
"pause": "PAUSE",
|
|
126
|
+
"caps lock": "CAPSLOCK",
|
|
127
|
+
"esc": "ESCAPE",
|
|
128
|
+
"escape": "ESCAPE",
|
|
129
|
+
"space": "SPACE",
|
|
130
|
+
"page up": "PAGE_UP",
|
|
131
|
+
"page down": "PAGE_DOWN",
|
|
132
|
+
"end": "END",
|
|
133
|
+
"home": "HOME",
|
|
134
|
+
"left": "LEFT",
|
|
135
|
+
"up": "UP",
|
|
136
|
+
"right": "RIGHT",
|
|
137
|
+
"down": "DOWN",
|
|
138
|
+
"insert": "INSERT",
|
|
139
|
+
"delete": "DELETE",
|
|
140
|
+
"print screen": "PRINT_SCREEN",
|
|
141
|
+
"scroll lock": "SCROLLLOCK",
|
|
142
|
+
"num lock": "NUMLOCK",
|
|
143
|
+
"windows": "LWIN",
|
|
144
|
+
"left windows": "LWIN",
|
|
145
|
+
"right windows": "RWIN",
|
|
146
|
+
"menu": "MENU",
|
|
147
|
+
"apps": "APPS",
|
|
148
|
+
"decimal": "DECIMAL",
|
|
149
|
+
"separator": "SEPARATOR",
|
|
150
|
+
"subtract": "SUBTRACT",
|
|
151
|
+
"add": "ADD",
|
|
152
|
+
"multiply": "MULTIPLY",
|
|
153
|
+
"divide": "DIVIDE",
|
|
154
|
+
"play/pause media": "MEDIA_PLAY_PAUSE",
|
|
155
|
+
"stop media": "MEDIA_STOP",
|
|
156
|
+
"next track": "MEDIA_NEXT_TRACK",
|
|
157
|
+
"previous track": "MEDIA_PREV_TRACK",
|
|
158
|
+
"volume up": "VOLUME_UP",
|
|
159
|
+
"volume down": "VOLUME_DOWN",
|
|
160
|
+
"volume mute": "VOLUME_MUTE",
|
|
161
|
+
"browser back": "BROWSER_BACK",
|
|
162
|
+
"browser forward": "BROWSER_FORWARD",
|
|
163
|
+
"browser refresh": "BROWSER_REFRESH",
|
|
164
|
+
"browser stop": "BROWSER_STOP",
|
|
165
|
+
"browser search": "BROWSER_SEARCH",
|
|
166
|
+
"browser favorites": "BROWSER_FAVORITES",
|
|
167
|
+
"browser home": "BROWSER_HOME",
|
|
168
|
+
"launch mail": "LAUNCH_MAIL",
|
|
169
|
+
"launch media select": "LAUNCH_MEDIA_SELECT",
|
|
170
|
+
"launch app 1": "LAUNCH_APP1",
|
|
171
|
+
"launch app 2": "LAUNCH_APP2",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def __init__(self, parent=None):
|
|
175
|
+
super().__init__(parent)
|
|
176
|
+
self._pressed_keys = set()
|
|
177
|
+
self._hook = None
|
|
178
|
+
self._running = False
|
|
179
|
+
|
|
180
|
+
def normalise_key_name(self, key_name):
|
|
181
|
+
if key_name is None:
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
key_name = str(key_name).strip().lower()
|
|
185
|
+
|
|
186
|
+
if key_name in self.KEY_NAME_MAP:
|
|
187
|
+
return self.KEY_NAME_MAP[key_name]
|
|
188
|
+
|
|
189
|
+
if key_name.startswith("numpad "):
|
|
190
|
+
suffix = key_name.replace("numpad ", "")
|
|
191
|
+
if suffix.isdigit():
|
|
192
|
+
return f"NUMPAD_{suffix}"
|
|
193
|
+
|
|
194
|
+
if key_name.startswith("f") and key_name[1:].isdigit():
|
|
195
|
+
return key_name.upper()
|
|
196
|
+
|
|
197
|
+
if len(key_name) == 1:
|
|
198
|
+
return key_name
|
|
199
|
+
|
|
200
|
+
return key_name.upper().replace(" ", "_")
|
|
201
|
+
|
|
202
|
+
def build_key_tuple(self, current_key):
|
|
203
|
+
held_modifiers = [key for key in self._pressed_keys if key in self.MODIFIER_KEYS]
|
|
204
|
+
held_modifiers.sort(key=lambda key: self.MODIFIER_ORDER.get(key, 999))
|
|
205
|
+
|
|
206
|
+
if current_key in self.MODIFIER_KEYS:
|
|
207
|
+
return tuple(held_modifiers)
|
|
208
|
+
|
|
209
|
+
return tuple(held_modifiers + [current_key])
|
|
210
|
+
|
|
211
|
+
def _keyboard_callback(self, event):
|
|
212
|
+
key_name = self.normalise_key_name(event.name)
|
|
213
|
+
if key_name is None:
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
if event.event_type == "down":
|
|
217
|
+
self._pressed_keys.add(key_name)
|
|
218
|
+
self.keys_pressed.emit(self.build_key_tuple(key_name))
|
|
219
|
+
|
|
220
|
+
elif event.event_type == "up":
|
|
221
|
+
self._pressed_keys.discard(key_name)
|
|
222
|
+
|
|
223
|
+
def run(self):
|
|
224
|
+
self._running = True
|
|
225
|
+
self._hook = keyboard.hook(self._keyboard_callback)
|
|
226
|
+
|
|
227
|
+
while self._running:
|
|
228
|
+
self.msleep(10)
|
|
229
|
+
|
|
230
|
+
if self._hook is not None:
|
|
231
|
+
keyboard.unhook(self._hook)
|
|
232
|
+
self._hook = None
|
|
233
|
+
|
|
234
|
+
def stop(self):
|
|
235
|
+
self._running = False
|
|
236
|
+
self.wait()
|