fastled 1.0.15__py2.py3-none-any.whl → 1.0.17__py2.py3-none-any.whl
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.
- fastled-1.0.17.dist-info/METADATA +112 -0
- fastled-1.0.17.dist-info/RECORD +18 -0
- fastled-1.0.17.dist-info/entry_points.txt +3 -0
- fastled-1.0.17.dist-info/top_level.txt +2 -0
- fled/__init__.py +0 -0
- {fastled → fled}/app.py +86 -164
- fled/check_cpp_syntax.py +34 -0
- {fastled → fled}/cli.py +1 -1
- {fastled → fled}/compile_server.py +207 -221
- {fastled → fled}/docker_manager.py +7 -11
- {fastled → fled}/filewatcher.py +146 -196
- {fastled → fled}/open_browser.py +1 -5
- fled/web_compile.py +173 -0
- fastled/__init__.py +0 -3
- fastled/keyboard.py +0 -91
- fastled/sketch.py +0 -55
- fastled/util.py +0 -10
- fastled/web_compile.py +0 -291
- fastled-1.0.15.dist-info/METADATA +0 -196
- fastled-1.0.15.dist-info/RECORD +0 -20
- fastled-1.0.15.dist-info/entry_points.txt +0 -4
- fastled-1.0.15.dist-info/top_level.txt +0 -2
- {fastled-1.0.15.dist-info → fastled-1.0.17.dist-info}/LICENSE +0 -0
- {fastled-1.0.15.dist-info → fastled-1.0.17.dist-info}/WHEEL +0 -0
- {fastled → fled}/assets/example.txt +0 -0
- {fastled → fled}/build_mode.py +0 -0
- {fastled → fled}/paths.py +0 -0
{fastled → fled}/filewatcher.py
RENAMED
@@ -1,196 +1,146 @@
|
|
1
|
-
"""File system watcher implementation using watchdog
|
2
|
-
"""
|
3
|
-
|
4
|
-
import hashlib
|
5
|
-
import
|
6
|
-
import
|
7
|
-
import
|
8
|
-
import
|
9
|
-
from
|
10
|
-
|
11
|
-
from
|
12
|
-
from
|
13
|
-
from
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
path
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
self.
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
self.
|
73
|
-
|
74
|
-
|
75
|
-
self.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
self.event_handler =
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
current_time
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
return
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
changed_files.append(changed_file)
|
148
|
-
# clear all the changes from the queue
|
149
|
-
self.change_queue.queue.clear()
|
150
|
-
return changed_files
|
151
|
-
|
152
|
-
|
153
|
-
def _process_wrapper(root: Path, excluded_patterns: list[str], queue: Queue):
|
154
|
-
with open(os.devnull, "w") as fnull: # Redirect to /dev/null
|
155
|
-
with redirect_stdout(fnull):
|
156
|
-
watcher = FileChangedNotifier(
|
157
|
-
str(root), excluded_patterns=excluded_patterns
|
158
|
-
)
|
159
|
-
watcher.start()
|
160
|
-
while True:
|
161
|
-
try:
|
162
|
-
changed_files = watcher.get_all_changes()
|
163
|
-
for file in changed_files:
|
164
|
-
queue.put(file)
|
165
|
-
except KeyboardInterrupt:
|
166
|
-
break
|
167
|
-
watcher.stop()
|
168
|
-
|
169
|
-
|
170
|
-
class FileWatcherProcess:
|
171
|
-
def __init__(self, root: Path, excluded_patterns: list[str]) -> None:
|
172
|
-
self.queue: Queue = Queue()
|
173
|
-
self.process = Process(
|
174
|
-
target=_process_wrapper,
|
175
|
-
args=(root, excluded_patterns, self.queue),
|
176
|
-
daemon=True,
|
177
|
-
)
|
178
|
-
self.process.start()
|
179
|
-
|
180
|
-
def stop(self):
|
181
|
-
self.process.terminate()
|
182
|
-
self.process.join()
|
183
|
-
self.queue.close()
|
184
|
-
self.queue.join_thread()
|
185
|
-
|
186
|
-
def get_all_changes(self, timeout: float | None = None) -> list[str]:
|
187
|
-
changed_files = []
|
188
|
-
block = timeout is not None
|
189
|
-
|
190
|
-
while True:
|
191
|
-
try:
|
192
|
-
changed_file = self.queue.get(block=block, timeout=timeout)
|
193
|
-
changed_files.append(changed_file)
|
194
|
-
except Empty:
|
195
|
-
break
|
196
|
-
return changed_files
|
1
|
+
"""File system watcher implementation using watchdog
|
2
|
+
"""
|
3
|
+
|
4
|
+
import hashlib
|
5
|
+
import queue
|
6
|
+
import threading
|
7
|
+
import time
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Dict, Set
|
10
|
+
|
11
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
12
|
+
from watchdog.observers import Observer
|
13
|
+
from watchdog.observers.api import BaseObserver
|
14
|
+
|
15
|
+
|
16
|
+
class MyEventHandler(FileSystemEventHandler):
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
change_queue: queue.Queue,
|
20
|
+
excluded_patterns: Set[str],
|
21
|
+
file_hashes: Dict[str, str],
|
22
|
+
) -> None:
|
23
|
+
super().__init__()
|
24
|
+
self.change_queue = change_queue
|
25
|
+
self.excluded_patterns = excluded_patterns
|
26
|
+
self.file_hashes = file_hashes
|
27
|
+
|
28
|
+
def _get_file_hash(self, filepath: str) -> str:
|
29
|
+
try:
|
30
|
+
with open(filepath, "rb") as f:
|
31
|
+
return hashlib.md5(f.read()).hexdigest()
|
32
|
+
except Exception: # pylint: disable=broad-except
|
33
|
+
return ""
|
34
|
+
|
35
|
+
def on_modified(self, event: FileSystemEvent) -> None:
|
36
|
+
if not event.is_directory:
|
37
|
+
path = Path(event.src_path)
|
38
|
+
# Check if any part of the path matches excluded patterns
|
39
|
+
if not any(part in self.excluded_patterns for part in path.parts):
|
40
|
+
new_hash = self._get_file_hash(event.src_path)
|
41
|
+
if new_hash and new_hash != self.file_hashes.get(event.src_path):
|
42
|
+
self.file_hashes[event.src_path] = new_hash
|
43
|
+
self.change_queue.put(event.src_path)
|
44
|
+
|
45
|
+
|
46
|
+
class FileChangedNotifier(threading.Thread):
|
47
|
+
"""Watches a directory for file changes and queues notifications"""
|
48
|
+
|
49
|
+
def __init__(
|
50
|
+
self,
|
51
|
+
path: str,
|
52
|
+
debounce_seconds: float = 1.0,
|
53
|
+
excluded_patterns: list[str] | None = None,
|
54
|
+
) -> None:
|
55
|
+
"""Initialize the notifier with a path to watch
|
56
|
+
|
57
|
+
Args:
|
58
|
+
path: Directory path to watch for changes
|
59
|
+
debounce_seconds: Minimum time between notifications for the same file
|
60
|
+
excluded_patterns: List of directory/file patterns to exclude from watching
|
61
|
+
"""
|
62
|
+
super().__init__(daemon=True)
|
63
|
+
self.path = path
|
64
|
+
self.observer: BaseObserver | None = None
|
65
|
+
self.event_handler: MyEventHandler | None = None
|
66
|
+
|
67
|
+
# Combine default and user-provided patterns
|
68
|
+
self.excluded_patterns = (
|
69
|
+
set(excluded_patterns) if excluded_patterns is not None else set()
|
70
|
+
)
|
71
|
+
self.stopped = False
|
72
|
+
self.change_queue: queue.Queue = queue.Queue()
|
73
|
+
self.last_notification: Dict[str, float] = {}
|
74
|
+
self.file_hashes: Dict[str, str] = {}
|
75
|
+
self.debounce_seconds = debounce_seconds
|
76
|
+
|
77
|
+
def stop(self) -> None:
|
78
|
+
"""Stop watching for changes"""
|
79
|
+
print("watcher stop")
|
80
|
+
self.stopped = True
|
81
|
+
if self.observer:
|
82
|
+
self.observer.stop()
|
83
|
+
self.observer.join()
|
84
|
+
self.observer = None
|
85
|
+
self.event_handler = None
|
86
|
+
|
87
|
+
def run(self) -> None:
|
88
|
+
"""Thread main loop - starts watching for changes"""
|
89
|
+
self.event_handler = MyEventHandler(
|
90
|
+
self.change_queue, self.excluded_patterns, self.file_hashes
|
91
|
+
)
|
92
|
+
self.observer = Observer()
|
93
|
+
self.observer.schedule(self.event_handler, self.path, recursive=True)
|
94
|
+
self.observer.start()
|
95
|
+
|
96
|
+
try:
|
97
|
+
while not self.stopped:
|
98
|
+
time.sleep(0.1)
|
99
|
+
except KeyboardInterrupt:
|
100
|
+
print("File watcher stopped by user.")
|
101
|
+
finally:
|
102
|
+
self.stop()
|
103
|
+
|
104
|
+
def get_next_change(self, timeout: float = 0.001) -> str | None:
|
105
|
+
"""Get the next file change event from the queue
|
106
|
+
|
107
|
+
Args:
|
108
|
+
timeout: How long to wait for next change in seconds
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
Changed filepath or None if no change within timeout
|
112
|
+
"""
|
113
|
+
try:
|
114
|
+
filepath = self.change_queue.get(timeout=timeout)
|
115
|
+
current_time = time.time()
|
116
|
+
|
117
|
+
# Check if we've seen this file recently
|
118
|
+
last_time = self.last_notification.get(filepath, 0)
|
119
|
+
if current_time - last_time < self.debounce_seconds:
|
120
|
+
return None
|
121
|
+
|
122
|
+
self.last_notification[filepath] = current_time
|
123
|
+
return filepath
|
124
|
+
except KeyboardInterrupt:
|
125
|
+
raise
|
126
|
+
except queue.Empty:
|
127
|
+
return None
|
128
|
+
|
129
|
+
def get_all_changes(self, timeout: float = 0.001) -> list[str]:
|
130
|
+
"""Get all file change events from the queue
|
131
|
+
|
132
|
+
Args:
|
133
|
+
timeout: How long to wait for next change in seconds
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
List of changed filepaths
|
137
|
+
"""
|
138
|
+
changed_files = []
|
139
|
+
while True:
|
140
|
+
changed_file = self.get_next_change(timeout=timeout)
|
141
|
+
if changed_file is None:
|
142
|
+
break
|
143
|
+
changed_files.append(changed_file)
|
144
|
+
# clear all the changes from the queue
|
145
|
+
self.change_queue.queue.clear()
|
146
|
+
return changed_files
|
{fastled → fled}/open_browser.py
RENAMED
@@ -24,11 +24,7 @@ def _open_browser_python(fastled_js: Path, port: int) -> subprocess.Popen:
|
|
24
24
|
]
|
25
25
|
|
26
26
|
# Start the process
|
27
|
-
process = subprocess.Popen(
|
28
|
-
cmd,
|
29
|
-
# stdout=subprocess.DEVNULL,
|
30
|
-
stderr=subprocess.DEVNULL,
|
31
|
-
)
|
27
|
+
process = subprocess.Popen(cmd)
|
32
28
|
return process
|
33
29
|
|
34
30
|
|
fled/web_compile.py
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
import shutil
|
2
|
+
import tempfile
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
import httpx
|
7
|
+
|
8
|
+
from fled.build_mode import BuildMode
|
9
|
+
|
10
|
+
DEFAULT_HOST = "https://fastled.onrender.com"
|
11
|
+
ENDPOINT_COMPILED_WASM = "compile/wasm"
|
12
|
+
_TIMEOUT = 60 * 4 # 2 mins timeout
|
13
|
+
_AUTH_TOKEN = "oBOT5jbsO4ztgrpNsQwlmFLIKB"
|
14
|
+
|
15
|
+
|
16
|
+
@dataclass
|
17
|
+
class WebCompileResult:
|
18
|
+
success: bool
|
19
|
+
stdout: str
|
20
|
+
hash_value: str | None
|
21
|
+
zip_bytes: bytes
|
22
|
+
|
23
|
+
def __bool__(self) -> bool:
|
24
|
+
return self.success
|
25
|
+
|
26
|
+
|
27
|
+
def _sanitize_host(host: str) -> str:
|
28
|
+
if host.startswith("http"):
|
29
|
+
return host
|
30
|
+
is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
|
31
|
+
use_https = not is_local_host
|
32
|
+
if use_https:
|
33
|
+
return host if host.startswith("https://") else f"https://{host}"
|
34
|
+
return host if host.startswith("http://") else f"http://{host}"
|
35
|
+
|
36
|
+
|
37
|
+
_CONNECTION_ERROR_MAP: dict[str, bool] = {}
|
38
|
+
|
39
|
+
|
40
|
+
def web_compile(
|
41
|
+
directory: Path,
|
42
|
+
host: str | None = None,
|
43
|
+
auth_token: str | None = None,
|
44
|
+
build_mode: BuildMode | None = None,
|
45
|
+
profile: bool = False,
|
46
|
+
) -> WebCompileResult:
|
47
|
+
host = _sanitize_host(host or DEFAULT_HOST)
|
48
|
+
auth_token = auth_token or _AUTH_TOKEN
|
49
|
+
# zip up the files
|
50
|
+
print("Zipping files...")
|
51
|
+
|
52
|
+
# Create a temporary zip file
|
53
|
+
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_zip:
|
54
|
+
# Create temporary directory for organizing files
|
55
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
56
|
+
# Create wasm subdirectory
|
57
|
+
wasm_dir = Path(temp_dir) / "wasm"
|
58
|
+
|
59
|
+
# Copy all files from source to wasm subdirectory, excluding fastled_js
|
60
|
+
def ignore_fastled_js(dir: str, files: list[str]) -> list[str]:
|
61
|
+
if "fastled_js" in dir:
|
62
|
+
return files
|
63
|
+
if dir.startswith("."):
|
64
|
+
return files
|
65
|
+
return []
|
66
|
+
|
67
|
+
shutil.copytree(directory, wasm_dir, ignore=ignore_fastled_js)
|
68
|
+
# Create zip archive from the temp directory
|
69
|
+
shutil.make_archive(tmp_zip.name[:-4], "zip", temp_dir)
|
70
|
+
|
71
|
+
print(f"Web compiling on {host}...")
|
72
|
+
|
73
|
+
try:
|
74
|
+
with open(tmp_zip.name, "rb") as zip_file:
|
75
|
+
files = {"file": ("wasm.zip", zip_file, "application/x-zip-compressed")}
|
76
|
+
|
77
|
+
tested = host in _CONNECTION_ERROR_MAP
|
78
|
+
if not tested:
|
79
|
+
test_url = f"{host}/healthz"
|
80
|
+
print(f"Testing connection to {test_url}")
|
81
|
+
timeout = 10
|
82
|
+
with httpx.Client(
|
83
|
+
transport=httpx.HTTPTransport(local_address="0.0.0.0"),
|
84
|
+
timeout=timeout,
|
85
|
+
) as test_client:
|
86
|
+
test_response = test_client.get(test_url, timeout=timeout)
|
87
|
+
if test_response.status_code != 200:
|
88
|
+
print(f"Connection to {test_url} failed")
|
89
|
+
_CONNECTION_ERROR_MAP[host] = True
|
90
|
+
return WebCompileResult(
|
91
|
+
success=False,
|
92
|
+
stdout="Connection failed",
|
93
|
+
hash_value=None,
|
94
|
+
zip_bytes=b"",
|
95
|
+
)
|
96
|
+
_CONNECTION_ERROR_MAP[host] = False
|
97
|
+
|
98
|
+
ok = not _CONNECTION_ERROR_MAP[host]
|
99
|
+
if not ok:
|
100
|
+
return WebCompileResult(
|
101
|
+
success=False,
|
102
|
+
stdout="Connection failed",
|
103
|
+
hash_value=None,
|
104
|
+
zip_bytes=b"",
|
105
|
+
)
|
106
|
+
print(f"Connection to {host} successful")
|
107
|
+
with httpx.Client(
|
108
|
+
transport=httpx.HTTPTransport(local_address="0.0.0.0"), # forces IPv4
|
109
|
+
timeout=_TIMEOUT,
|
110
|
+
) as client:
|
111
|
+
url = f"{host}/{ENDPOINT_COMPILED_WASM}"
|
112
|
+
headers = {
|
113
|
+
"accept": "application/json",
|
114
|
+
"authorization": auth_token,
|
115
|
+
"build": (
|
116
|
+
build_mode.value.lower()
|
117
|
+
if build_mode
|
118
|
+
else BuildMode.QUICK.value.lower()
|
119
|
+
),
|
120
|
+
"profile": "true" if profile else "false",
|
121
|
+
}
|
122
|
+
print(f"Compiling on {url}")
|
123
|
+
response = client.post(
|
124
|
+
url,
|
125
|
+
files=files,
|
126
|
+
headers=headers,
|
127
|
+
timeout=_TIMEOUT,
|
128
|
+
)
|
129
|
+
|
130
|
+
if response.status_code != 200:
|
131
|
+
json_response = response.json()
|
132
|
+
detail = json_response.get("detail", "Could not compile")
|
133
|
+
return WebCompileResult(
|
134
|
+
success=False, stdout=detail, hash_value=None, zip_bytes=b""
|
135
|
+
)
|
136
|
+
|
137
|
+
print(f"Response status code: {response}")
|
138
|
+
# Create a temporary directory to extract the zip
|
139
|
+
with tempfile.TemporaryDirectory() as extract_dir:
|
140
|
+
extract_path = Path(extract_dir)
|
141
|
+
|
142
|
+
# Write the response content to a temporary zip file
|
143
|
+
temp_zip = extract_path / "response.zip"
|
144
|
+
temp_zip.write_bytes(response.content)
|
145
|
+
|
146
|
+
# Extract the zip
|
147
|
+
shutil.unpack_archive(temp_zip, extract_path, "zip")
|
148
|
+
|
149
|
+
# Read stdout from out.txt if it exists
|
150
|
+
stdout_file = extract_path / "out.txt"
|
151
|
+
hash_file = extract_path / "hash.txt"
|
152
|
+
stdout = stdout_file.read_text() if stdout_file.exists() else ""
|
153
|
+
hash_value = hash_file.read_text() if hash_file.exists() else None
|
154
|
+
|
155
|
+
return WebCompileResult(
|
156
|
+
success=True,
|
157
|
+
stdout=stdout,
|
158
|
+
hash_value=hash_value,
|
159
|
+
zip_bytes=response.content,
|
160
|
+
)
|
161
|
+
except KeyboardInterrupt:
|
162
|
+
print("Keyboard interrupt")
|
163
|
+
raise
|
164
|
+
except httpx.HTTPError as e:
|
165
|
+
print(f"Error: {e}")
|
166
|
+
return WebCompileResult(
|
167
|
+
success=False, stdout=str(e), hash_value=None, zip_bytes=b""
|
168
|
+
)
|
169
|
+
finally:
|
170
|
+
try:
|
171
|
+
Path(tmp_zip.name).unlink()
|
172
|
+
except PermissionError:
|
173
|
+
print("Warning: Could not delete temporary zip file")
|
fastled/__init__.py
DELETED
fastled/keyboard.py
DELETED
@@ -1,91 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import select
|
3
|
-
import sys
|
4
|
-
import time
|
5
|
-
from multiprocessing import Process, Queue
|
6
|
-
from queue import Empty
|
7
|
-
|
8
|
-
|
9
|
-
class SpaceBarWatcher:
|
10
|
-
def __init__(self) -> None:
|
11
|
-
self.queue: Queue = Queue()
|
12
|
-
self.queue_cancel: Queue = Queue()
|
13
|
-
self.process = Process(target=self._watch_for_space)
|
14
|
-
self.process.start()
|
15
|
-
|
16
|
-
def _watch_for_space(self) -> None:
|
17
|
-
# Set stdin to non-blocking mode
|
18
|
-
fd = sys.stdin.fileno()
|
19
|
-
|
20
|
-
if os.name == "nt": # Windows
|
21
|
-
import msvcrt
|
22
|
-
|
23
|
-
while True:
|
24
|
-
# Check for cancel signal
|
25
|
-
try:
|
26
|
-
self.queue_cancel.get(timeout=0.1)
|
27
|
-
break
|
28
|
-
except Empty:
|
29
|
-
pass
|
30
|
-
|
31
|
-
# Check if there's input ready
|
32
|
-
if msvcrt.kbhit(): # type: ignore
|
33
|
-
char = msvcrt.getch().decode() # type: ignore
|
34
|
-
if char == " ":
|
35
|
-
self.queue.put(ord(" "))
|
36
|
-
|
37
|
-
else: # Unix-like systems
|
38
|
-
import termios
|
39
|
-
import tty
|
40
|
-
|
41
|
-
old_settings = termios.tcgetattr(fd) # type: ignore
|
42
|
-
try:
|
43
|
-
tty.setraw(fd) # type: ignore
|
44
|
-
while True:
|
45
|
-
# Check for cancel signal
|
46
|
-
try:
|
47
|
-
self.queue_cancel.get(timeout=0.1)
|
48
|
-
break
|
49
|
-
except Empty:
|
50
|
-
pass
|
51
|
-
|
52
|
-
# Check if there's input ready
|
53
|
-
if select.select([sys.stdin], [], [], 0.1)[0]:
|
54
|
-
char = sys.stdin.read(1)
|
55
|
-
if char == " ":
|
56
|
-
self.queue.put(ord(" "))
|
57
|
-
finally:
|
58
|
-
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # type: ignore
|
59
|
-
|
60
|
-
def space_bar_pressed(self) -> bool:
|
61
|
-
found = False
|
62
|
-
while not self.queue.empty():
|
63
|
-
key = self.queue.get()
|
64
|
-
if key == ord(" "):
|
65
|
-
found = True
|
66
|
-
return found
|
67
|
-
|
68
|
-
def stop(self) -> None:
|
69
|
-
self.queue_cancel.put(True)
|
70
|
-
self.process.terminate()
|
71
|
-
self.process.join()
|
72
|
-
self.queue.close()
|
73
|
-
self.queue.join_thread()
|
74
|
-
|
75
|
-
|
76
|
-
def main() -> None:
|
77
|
-
watcher = SpaceBarWatcher()
|
78
|
-
try:
|
79
|
-
while True:
|
80
|
-
if watcher.space_bar_pressed():
|
81
|
-
break
|
82
|
-
time.sleep(1)
|
83
|
-
finally:
|
84
|
-
watcher.stop()
|
85
|
-
|
86
|
-
|
87
|
-
if __name__ == "__main__":
|
88
|
-
try:
|
89
|
-
main()
|
90
|
-
except KeyboardInterrupt:
|
91
|
-
print("Keyboard interrupt detected.")
|