fastled 1.2.33__py3-none-any.whl → 1.4.50__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/__init__.py +51 -192
- fastled/__main__.py +14 -0
- fastled/__version__.py +6 -0
- fastled/app.py +124 -27
- fastled/args.py +124 -0
- fastled/assets/localhost-key.pem +28 -0
- fastled/assets/localhost.pem +27 -0
- fastled/cli.py +10 -2
- fastled/cli_test.py +21 -0
- fastled/cli_test_interactive.py +21 -0
- fastled/client_server.py +334 -55
- fastled/compile_server.py +12 -1
- fastled/compile_server_impl.py +115 -42
- fastled/docker_manager.py +392 -69
- fastled/emoji_util.py +27 -0
- fastled/filewatcher.py +100 -8
- fastled/find_good_connection.py +105 -0
- fastled/header_dump.py +63 -0
- fastled/install/__init__.py +1 -0
- fastled/install/examples_manager.py +62 -0
- fastled/install/extension_manager.py +113 -0
- fastled/install/main.py +156 -0
- fastled/install/project_detection.py +167 -0
- fastled/install/test_install.py +373 -0
- fastled/install/vscode_config.py +344 -0
- fastled/interruptible_http.py +148 -0
- fastled/keyboard.py +1 -0
- fastled/keyz.py +84 -0
- fastled/live_client.py +26 -1
- fastled/open_browser.py +133 -89
- fastled/parse_args.py +219 -15
- fastled/playwright/chrome_extension_downloader.py +207 -0
- fastled/playwright/playwright_browser.py +773 -0
- fastled/playwright/resize_tracking.py +127 -0
- fastled/print_filter.py +52 -0
- fastled/project_init.py +20 -13
- fastled/select_sketch_directory.py +142 -17
- fastled/server_flask.py +487 -0
- fastled/server_start.py +21 -0
- fastled/settings.py +53 -4
- fastled/site/build.py +2 -10
- fastled/site/examples.py +10 -0
- fastled/sketch.py +129 -7
- fastled/string_diff.py +218 -9
- fastled/test/examples.py +7 -5
- fastled/types.py +22 -2
- fastled/util.py +78 -0
- fastled/version.py +41 -0
- fastled/web_compile.py +401 -218
- fastled/zip_files.py +76 -0
- {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/METADATA +533 -382
- fastled-1.4.50.dist-info/RECORD +60 -0
- {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/WHEEL +1 -1
- fastled/open_browser2.py +0 -111
- fastled-1.2.33.dist-info/RECORD +0 -33
- {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/entry_points.txt +0 -0
- {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info/licenses}/LICENSE +0 -0
- {fastled-1.2.33.dist-info → fastled-1.4.50.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""VSCode configuration generation for FastLED projects."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def update_launch_json_for_arduino() -> None:
|
|
8
|
+
"""Update launch.json with Arduino debugging configuration."""
|
|
9
|
+
launch_json_path = Path.cwd() / ".vscode" / "launch.json"
|
|
10
|
+
|
|
11
|
+
# Default launch configuration
|
|
12
|
+
arduino_config = {
|
|
13
|
+
"name": "🎯 Auto Debug (Smart File Detection)",
|
|
14
|
+
"type": "auto-debug",
|
|
15
|
+
"request": "launch",
|
|
16
|
+
"map": {
|
|
17
|
+
"*.ino": "Arduino: Run .ino with FastLED",
|
|
18
|
+
"*.py": "Python: Current File (UV)",
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if launch_json_path.exists():
|
|
23
|
+
# Merge with existing
|
|
24
|
+
try:
|
|
25
|
+
with open(launch_json_path, "r") as f:
|
|
26
|
+
data = json.load(f)
|
|
27
|
+
except json.JSONDecodeError:
|
|
28
|
+
data = {"version": "0.2.0", "configurations": []}
|
|
29
|
+
else:
|
|
30
|
+
data = {"version": "0.2.0", "configurations": []}
|
|
31
|
+
|
|
32
|
+
# Check if configuration already exists
|
|
33
|
+
configs = data.get("configurations", [])
|
|
34
|
+
exists = any(cfg.get("name") == arduino_config["name"] for cfg in configs)
|
|
35
|
+
|
|
36
|
+
if not exists:
|
|
37
|
+
configs.insert(0, arduino_config) # Add at the beginning
|
|
38
|
+
data["configurations"] = configs
|
|
39
|
+
|
|
40
|
+
# Write back
|
|
41
|
+
launch_json_path.parent.mkdir(exist_ok=True)
|
|
42
|
+
with open(launch_json_path, "w") as f:
|
|
43
|
+
json.dump(data, f, indent=4)
|
|
44
|
+
|
|
45
|
+
print(f"✅ Updated {launch_json_path}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def generate_fastled_tasks() -> None:
|
|
49
|
+
"""Generate/update tasks.json with FastLED build tasks."""
|
|
50
|
+
tasks_json_path = Path.cwd() / ".vscode" / "tasks.json"
|
|
51
|
+
|
|
52
|
+
# FastLED tasks
|
|
53
|
+
fastled_tasks = [
|
|
54
|
+
{
|
|
55
|
+
"type": "shell",
|
|
56
|
+
"label": "Run FastLED (Debug)",
|
|
57
|
+
"command": "fastled",
|
|
58
|
+
"args": ["${file}", "--debug", "--app"],
|
|
59
|
+
"options": {"cwd": "${workspaceFolder}"},
|
|
60
|
+
"group": {"kind": "build", "isDefault": True},
|
|
61
|
+
"presentation": {
|
|
62
|
+
"echo": True,
|
|
63
|
+
"reveal": "always",
|
|
64
|
+
"focus": True,
|
|
65
|
+
"panel": "new",
|
|
66
|
+
"showReuseMessage": False,
|
|
67
|
+
"clear": True,
|
|
68
|
+
},
|
|
69
|
+
"detail": "Run FastLED with debug mode and app visualization",
|
|
70
|
+
"problemMatcher": [],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"type": "shell",
|
|
74
|
+
"label": "Run FastLED (Quick)",
|
|
75
|
+
"command": "fastled",
|
|
76
|
+
"args": ["${file}", "--background-update"],
|
|
77
|
+
"options": {"cwd": "${workspaceFolder}"},
|
|
78
|
+
"group": "build",
|
|
79
|
+
"presentation": {
|
|
80
|
+
"echo": True,
|
|
81
|
+
"reveal": "always",
|
|
82
|
+
"focus": True,
|
|
83
|
+
"panel": "new",
|
|
84
|
+
"showReuseMessage": False,
|
|
85
|
+
"clear": True,
|
|
86
|
+
},
|
|
87
|
+
"detail": "Run FastLED with quick background update mode",
|
|
88
|
+
"problemMatcher": [],
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
if tasks_json_path.exists():
|
|
93
|
+
# Merge with existing
|
|
94
|
+
try:
|
|
95
|
+
with open(tasks_json_path, "r") as f:
|
|
96
|
+
data = json.load(f)
|
|
97
|
+
except json.JSONDecodeError:
|
|
98
|
+
data = {"version": "2.0.0", "tasks": []}
|
|
99
|
+
else:
|
|
100
|
+
data = {"version": "2.0.0", "tasks": []}
|
|
101
|
+
|
|
102
|
+
# Get existing tasks
|
|
103
|
+
existing_tasks = data.get("tasks", [])
|
|
104
|
+
existing_labels = {task.get("label") for task in existing_tasks}
|
|
105
|
+
|
|
106
|
+
# Add new tasks if they don't exist
|
|
107
|
+
for task in fastled_tasks:
|
|
108
|
+
if task["label"] not in existing_labels:
|
|
109
|
+
existing_tasks.append(task)
|
|
110
|
+
|
|
111
|
+
data["tasks"] = existing_tasks
|
|
112
|
+
|
|
113
|
+
# Write back
|
|
114
|
+
tasks_json_path.parent.mkdir(exist_ok=True)
|
|
115
|
+
with open(tasks_json_path, "w") as f:
|
|
116
|
+
json.dump(data, f, indent=4)
|
|
117
|
+
|
|
118
|
+
print(f"✅ Updated {tasks_json_path}")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def update_vscode_settings_for_fastled() -> None:
|
|
122
|
+
"""
|
|
123
|
+
🚨 Repository-only: Apply clangd settings and IntelliSense overrides.
|
|
124
|
+
This should ONLY be called for the actual FastLED repository.
|
|
125
|
+
"""
|
|
126
|
+
from .project_detection import is_fastled_repository
|
|
127
|
+
|
|
128
|
+
# Safety check - only apply in actual repository
|
|
129
|
+
if not is_fastled_repository():
|
|
130
|
+
return
|
|
131
|
+
settings_json_path = Path.cwd() / ".vscode" / "settings.json"
|
|
132
|
+
|
|
133
|
+
# FastLED repository-specific settings - updated to match the official FastLED repo
|
|
134
|
+
fastled_settings = {
|
|
135
|
+
# Terminal configuration
|
|
136
|
+
"terminal.integrated.defaultProfile.windows": "Git Bash",
|
|
137
|
+
"terminal.integrated.shellIntegration.enabled": False,
|
|
138
|
+
"terminal.integrated.profiles.windows": {
|
|
139
|
+
"Command Prompt": {"path": "C:\\Windows\\System32\\cmd.exe"},
|
|
140
|
+
"Git Bash": {
|
|
141
|
+
"path": "C:\\Program Files\\Git\\bin\\bash.exe",
|
|
142
|
+
"args": ["--cd=."],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
# File settings
|
|
146
|
+
"files.eol": "\n", # Unix line endings
|
|
147
|
+
"files.autoDetectEol": False, # Prevent VS Code from auto-detecting and changing EOL
|
|
148
|
+
"files.insertFinalNewline": True, # Ensure files end with a newline
|
|
149
|
+
"files.trimFinalNewlines": True, # Remove extra newlines at the end
|
|
150
|
+
"editor.tabSize": 4,
|
|
151
|
+
"editor.insertSpaces": True,
|
|
152
|
+
"editor.detectIndentation": True,
|
|
153
|
+
"editor.formatOnSave": False, # Disabled to prevent conflicts
|
|
154
|
+
# Debugger defaults - ensure C++ debugger is used for C++ files
|
|
155
|
+
"debug.defaultDebuggerType": "cppdbg",
|
|
156
|
+
"debug.toolBarLocation": "docked",
|
|
157
|
+
"debug.console.fontSize": 14,
|
|
158
|
+
"debug.console.lineHeight": 19,
|
|
159
|
+
# Python configuration (using uv as per project rules)
|
|
160
|
+
"python.defaultInterpreterPath": "uv",
|
|
161
|
+
"python.debugger": "debugpy",
|
|
162
|
+
# File associations for debugger
|
|
163
|
+
"[cpp]": {
|
|
164
|
+
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd",
|
|
165
|
+
"debug.defaultDebuggerType": "cppdbg",
|
|
166
|
+
},
|
|
167
|
+
"[c]": {
|
|
168
|
+
"editor.defaultFormatter": "ms-vscode.cpptools",
|
|
169
|
+
"debug.defaultDebuggerType": "cppdbg",
|
|
170
|
+
},
|
|
171
|
+
"[ino]": {
|
|
172
|
+
"editor.defaultFormatter": "ms-vscode.cpptools",
|
|
173
|
+
"debug.defaultDebuggerType": "cppdbg",
|
|
174
|
+
},
|
|
175
|
+
# clangd configuration - enhanced
|
|
176
|
+
"clangd.arguments": [
|
|
177
|
+
"--compile-commands-dir=${workspaceFolder}",
|
|
178
|
+
"--clang-tidy",
|
|
179
|
+
"--header-insertion=never",
|
|
180
|
+
"--completion-style=detailed",
|
|
181
|
+
"--function-arg-placeholders=false",
|
|
182
|
+
"--background-index",
|
|
183
|
+
"--pch-storage=memory",
|
|
184
|
+
],
|
|
185
|
+
"clangd.fallbackFlags": [
|
|
186
|
+
"-std=c++17",
|
|
187
|
+
"-I${workspaceFolder}/src",
|
|
188
|
+
"-I${workspaceFolder}/tests",
|
|
189
|
+
"-Wno-global-constructors",
|
|
190
|
+
],
|
|
191
|
+
# Disable conflicting IntelliSense to let clangd handle C++ analysis
|
|
192
|
+
"C_Cpp.intelliSenseEngine": "disabled",
|
|
193
|
+
"C_Cpp.autocomplete": "disabled",
|
|
194
|
+
"C_Cpp.errorSquiggles": "disabled",
|
|
195
|
+
"C_Cpp.suggestSnippets": False,
|
|
196
|
+
"C_Cpp.intelliSenseEngineFallback": "disabled",
|
|
197
|
+
"C_Cpp.autocompleteAddParentheses": False,
|
|
198
|
+
"C_Cpp.formatting": "disabled",
|
|
199
|
+
"C_Cpp.vcpkg.enabled": False,
|
|
200
|
+
"C_Cpp.configurationWarnings": "disabled",
|
|
201
|
+
"C_Cpp.intelliSenseCachePath": "",
|
|
202
|
+
"C_Cpp.intelliSenseCacheSize": 0,
|
|
203
|
+
"C_Cpp.intelliSenseUpdateDelay": 0,
|
|
204
|
+
"C_Cpp.workspaceParsingPriority": "lowest",
|
|
205
|
+
"C_Cpp.disabled": True,
|
|
206
|
+
# File associations - comprehensive
|
|
207
|
+
"files.associations": {
|
|
208
|
+
"*.ino": "cpp",
|
|
209
|
+
"*.h": "cpp",
|
|
210
|
+
"*.hpp": "cpp",
|
|
211
|
+
"*.cpp": "cpp",
|
|
212
|
+
"*.c": "c",
|
|
213
|
+
"*.inc": "cpp",
|
|
214
|
+
"*.tcc": "cpp",
|
|
215
|
+
"*.embeddedhtml": "html",
|
|
216
|
+
# Core C++ standard library files
|
|
217
|
+
"compare": "cpp",
|
|
218
|
+
"type_traits": "cpp",
|
|
219
|
+
"cmath": "cpp",
|
|
220
|
+
"limits": "cpp",
|
|
221
|
+
"iostream": "cpp",
|
|
222
|
+
"random": "cpp",
|
|
223
|
+
"functional": "cpp",
|
|
224
|
+
"bit": "cpp",
|
|
225
|
+
"vector": "cpp",
|
|
226
|
+
"array": "cpp",
|
|
227
|
+
"string": "cpp",
|
|
228
|
+
"memory": "cpp",
|
|
229
|
+
"algorithm": "cpp",
|
|
230
|
+
"iterator": "cpp",
|
|
231
|
+
"utility": "cpp",
|
|
232
|
+
"optional": "cpp",
|
|
233
|
+
"variant": "cpp",
|
|
234
|
+
"numeric": "cpp",
|
|
235
|
+
"chrono": "cpp",
|
|
236
|
+
"thread": "cpp",
|
|
237
|
+
"mutex": "cpp",
|
|
238
|
+
"atomic": "cpp",
|
|
239
|
+
"future": "cpp",
|
|
240
|
+
"condition_variable": "cpp",
|
|
241
|
+
},
|
|
242
|
+
# Disable Java language support and popups
|
|
243
|
+
"java.enabled": False,
|
|
244
|
+
"java.jdt.ls.enabled": False,
|
|
245
|
+
"java.compile.nullAnalysis.mode": "disabled",
|
|
246
|
+
"java.configuration.checkProjectSettingsExclusions": False,
|
|
247
|
+
"java.import.gradle.enabled": False,
|
|
248
|
+
"java.import.maven.enabled": False,
|
|
249
|
+
"java.autobuild.enabled": False,
|
|
250
|
+
"java.maxConcurrentBuilds": 0,
|
|
251
|
+
"java.recommendations.enabled": False,
|
|
252
|
+
"java.help.showReleaseNotes": False,
|
|
253
|
+
"redhat.telemetry.enabled": False,
|
|
254
|
+
# Java exclusions
|
|
255
|
+
"java.project.sourcePaths": [],
|
|
256
|
+
"java.project.referencedLibraries": [],
|
|
257
|
+
"files.exclude": {
|
|
258
|
+
"**/.classpath": True,
|
|
259
|
+
"**/.project": True,
|
|
260
|
+
"**/.factorypath": True,
|
|
261
|
+
},
|
|
262
|
+
# Disable PlatformIO auto-detection for .ino files in FastLED project
|
|
263
|
+
"platformio.disableToolchainAutoInstaller": True,
|
|
264
|
+
"platformio-ide.autoRebuildAutocompleteIndex": False,
|
|
265
|
+
"platformio-ide.activateProjectOnTextEditorChange": False,
|
|
266
|
+
"platformio-ide.autoOpenPlatformIOIniFile": False,
|
|
267
|
+
"platformio-ide.autoPreloadEnvTasks": False,
|
|
268
|
+
"platformio-ide.autoCloseSerialMonitor": False,
|
|
269
|
+
"platformio-ide.disablePIOHomeStartup": True,
|
|
270
|
+
# Disable conflicting extensions
|
|
271
|
+
"extensions.ignoreRecommendations": True,
|
|
272
|
+
# Semantic token color customizations for better C++ development
|
|
273
|
+
"editor.semanticTokenColorCustomizations": {
|
|
274
|
+
"rules": {
|
|
275
|
+
# Types (classes, structs, enums) - Teal/Cyan
|
|
276
|
+
"class": "#4EC9B0",
|
|
277
|
+
"struct": "#4EC9B0",
|
|
278
|
+
"type": "#4EC9B0",
|
|
279
|
+
"enum": "#4EC9B0",
|
|
280
|
+
"enumMember": "#B5CEA8",
|
|
281
|
+
"typedef": "#4EC9B0",
|
|
282
|
+
# Variables - Almost pure white for maximum readability
|
|
283
|
+
"variable": "#FAFAFA",
|
|
284
|
+
"variable.local": "#FAFAFA",
|
|
285
|
+
# Parameters - Orange for clear distinction
|
|
286
|
+
"parameter": "#FF8C42",
|
|
287
|
+
"variable.parameter": "#FF8C42",
|
|
288
|
+
# Properties - Light purple/pink
|
|
289
|
+
"property": "#D197D9",
|
|
290
|
+
# Functions and methods - Yellow
|
|
291
|
+
"function": "#DCDCAA",
|
|
292
|
+
"method": "#DCDCAA",
|
|
293
|
+
"function.declaration": "#DCDCAA",
|
|
294
|
+
"method.declaration": "#DCDCAA",
|
|
295
|
+
# Namespaces - Soft blue
|
|
296
|
+
"namespace": "#86C5F7",
|
|
297
|
+
# Constants and readonly - Light green with italic
|
|
298
|
+
"variable.readonly": {"foreground": "#B5CEA8", "fontStyle": "italic"},
|
|
299
|
+
"variable.defaultLibrary": "#B5CEA8",
|
|
300
|
+
# Macros and defines - Muted red/salmon
|
|
301
|
+
"macro": "#E06C75",
|
|
302
|
+
# String literals - Peach/salmon
|
|
303
|
+
"string": "#CE9178",
|
|
304
|
+
# Numbers - Light green
|
|
305
|
+
"number": "#B5CEA8",
|
|
306
|
+
# Keywords - Pink/magenta
|
|
307
|
+
"keyword": "#C586C0",
|
|
308
|
+
# Storage specifiers - Bright magenta/pink
|
|
309
|
+
"keyword.storage": "#FF79C6",
|
|
310
|
+
"storageClass": "#FF79C6",
|
|
311
|
+
# Built-in types - Different from user-defined types
|
|
312
|
+
"type.builtin": "#569CD6",
|
|
313
|
+
"keyword.type": "#569CD6",
|
|
314
|
+
# Comments - Green
|
|
315
|
+
"comment": "#6A9955",
|
|
316
|
+
"comment.documentation": "#6A9955",
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
# Inlay hints - Brighter gray for better visibility
|
|
320
|
+
"editor.inlayHints.fontColor": "#808080",
|
|
321
|
+
"editor.inlayHints.background": "#3C3C3C20",
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if settings_json_path.exists():
|
|
325
|
+
# Merge with existing
|
|
326
|
+
try:
|
|
327
|
+
with open(settings_json_path, "r") as f:
|
|
328
|
+
data = json.load(f)
|
|
329
|
+
except json.JSONDecodeError:
|
|
330
|
+
data = {}
|
|
331
|
+
else:
|
|
332
|
+
data = {}
|
|
333
|
+
|
|
334
|
+
# Update settings
|
|
335
|
+
data.update(fastled_settings)
|
|
336
|
+
|
|
337
|
+
# Write back
|
|
338
|
+
settings_json_path.parent.mkdir(exist_ok=True)
|
|
339
|
+
with open(settings_json_path, "w") as f:
|
|
340
|
+
json.dump(data, f, indent=4)
|
|
341
|
+
|
|
342
|
+
print(
|
|
343
|
+
f"✅ Updated {settings_json_path} with comprehensive FastLED development settings"
|
|
344
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interruptible HTTP requests that can be cancelled with Ctrl+C.
|
|
3
|
+
|
|
4
|
+
This module provides cross-platform HTTP request functionality that can be
|
|
5
|
+
interrupted with Ctrl+C by using asyncio cancellation and periodic checks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InterruptibleHTTPRequest:
|
|
14
|
+
"""A wrapper for making HTTP requests that can be interrupted by Ctrl+C."""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.cancelled = False
|
|
18
|
+
|
|
19
|
+
async def _make_request_async(
|
|
20
|
+
self,
|
|
21
|
+
url: str,
|
|
22
|
+
files: dict,
|
|
23
|
+
headers: dict,
|
|
24
|
+
transport: httpx.HTTPTransport | None = None,
|
|
25
|
+
timeout: float = 240,
|
|
26
|
+
follow_redirects: bool = True,
|
|
27
|
+
) -> httpx.Response:
|
|
28
|
+
"""Make an async HTTP request."""
|
|
29
|
+
# Convert sync transport to async transport if provided
|
|
30
|
+
async_transport = None
|
|
31
|
+
if transport is not None:
|
|
32
|
+
# For IPv4 connections, create async transport with local address
|
|
33
|
+
async_transport = httpx.AsyncHTTPTransport(local_address="0.0.0.0")
|
|
34
|
+
|
|
35
|
+
async with httpx.AsyncClient(
|
|
36
|
+
transport=async_transport,
|
|
37
|
+
timeout=timeout,
|
|
38
|
+
) as client:
|
|
39
|
+
response = await client.post(
|
|
40
|
+
url,
|
|
41
|
+
follow_redirects=follow_redirects,
|
|
42
|
+
files=files,
|
|
43
|
+
headers=headers,
|
|
44
|
+
)
|
|
45
|
+
return response
|
|
46
|
+
|
|
47
|
+
def make_request_interruptible(
|
|
48
|
+
self,
|
|
49
|
+
url: str,
|
|
50
|
+
files: dict,
|
|
51
|
+
headers: dict,
|
|
52
|
+
transport: httpx.HTTPTransport | None = None,
|
|
53
|
+
timeout: float = 240,
|
|
54
|
+
follow_redirects: bool = True,
|
|
55
|
+
) -> httpx.Response:
|
|
56
|
+
"""Make an HTTP request that can be interrupted by Ctrl+C."""
|
|
57
|
+
try:
|
|
58
|
+
# Create a new event loop if we're not in one
|
|
59
|
+
try:
|
|
60
|
+
loop = asyncio.get_running_loop()
|
|
61
|
+
# We're already in an event loop, use run_in_executor
|
|
62
|
+
return asyncio.run_coroutine_threadsafe(
|
|
63
|
+
self._run_with_keyboard_check(
|
|
64
|
+
url, files, headers, transport, timeout, follow_redirects
|
|
65
|
+
),
|
|
66
|
+
loop,
|
|
67
|
+
).result()
|
|
68
|
+
except RuntimeError:
|
|
69
|
+
# No running loop, create one
|
|
70
|
+
return asyncio.run(
|
|
71
|
+
self._run_with_keyboard_check(
|
|
72
|
+
url, files, headers, transport, timeout, follow_redirects
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
except KeyboardInterrupt:
|
|
76
|
+
print("\nHTTP request cancelled by user")
|
|
77
|
+
raise
|
|
78
|
+
|
|
79
|
+
async def _run_with_keyboard_check(
|
|
80
|
+
self,
|
|
81
|
+
url: str,
|
|
82
|
+
files: dict,
|
|
83
|
+
headers: dict,
|
|
84
|
+
transport: httpx.HTTPTransport | None = None,
|
|
85
|
+
timeout: float = 240,
|
|
86
|
+
follow_redirects: bool = True,
|
|
87
|
+
) -> httpx.Response:
|
|
88
|
+
"""Run the request with periodic keyboard interrupt checks."""
|
|
89
|
+
task = asyncio.create_task(
|
|
90
|
+
self._make_request_async(
|
|
91
|
+
url, files, headers, transport, timeout, follow_redirects
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
# Poll for keyboard interrupt while waiting for the request
|
|
96
|
+
# This approach allows the task to be cancelled when KeyboardInterrupt
|
|
97
|
+
# is raised in the calling thread
|
|
98
|
+
while not task.done():
|
|
99
|
+
try:
|
|
100
|
+
# Wait for either completion or a short timeout
|
|
101
|
+
response = await asyncio.wait_for(asyncio.shield(task), timeout=0.1)
|
|
102
|
+
return response
|
|
103
|
+
except asyncio.TimeoutError:
|
|
104
|
+
# Continue waiting - the short timeout allows for more responsive
|
|
105
|
+
# cancellation when KeyboardInterrupt is raised
|
|
106
|
+
continue
|
|
107
|
+
except KeyboardInterrupt:
|
|
108
|
+
task.cancel()
|
|
109
|
+
print("\nHTTP request cancelled by user")
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
return await task
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def make_interruptible_post_request(
|
|
116
|
+
url: str,
|
|
117
|
+
files: dict | None = None,
|
|
118
|
+
headers: dict | None = None,
|
|
119
|
+
transport: httpx.HTTPTransport | None = None,
|
|
120
|
+
timeout: float = 240,
|
|
121
|
+
follow_redirects: bool = True,
|
|
122
|
+
) -> httpx.Response:
|
|
123
|
+
"""
|
|
124
|
+
Convenience function to make an interruptible POST request.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
url: The URL to make the request to
|
|
128
|
+
files: Files to upload (optional)
|
|
129
|
+
headers: HTTP headers (optional)
|
|
130
|
+
transport: HTTP transport to use (optional)
|
|
131
|
+
timeout: Request timeout in seconds
|
|
132
|
+
follow_redirects: Whether to follow redirects
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The HTTP response
|
|
136
|
+
|
|
137
|
+
Raises:
|
|
138
|
+
KeyboardInterrupt: If the request was cancelled by Ctrl+C
|
|
139
|
+
"""
|
|
140
|
+
request_handler = InterruptibleHTTPRequest()
|
|
141
|
+
return request_handler.make_request_interruptible(
|
|
142
|
+
url=url,
|
|
143
|
+
files=files or {},
|
|
144
|
+
headers=headers or {},
|
|
145
|
+
transport=transport,
|
|
146
|
+
timeout=timeout,
|
|
147
|
+
follow_redirects=follow_redirects,
|
|
148
|
+
)
|
fastled/keyboard.py
CHANGED
fastled/keyz.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# This was an experiment for https://localhost
|
|
2
|
+
# Important for audio sampling from sites like Youtube or an app like Audacity.
|
|
3
|
+
# However, pkg_resources does not work in the exe version of this app.
|
|
4
|
+
# For this to work pyinstaller needs special handling (probably a spec-file)
|
|
5
|
+
# in order to properly package up external resources.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
_ENABLE_SSL_CONFIG = False
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class SslConfig:
|
|
15
|
+
cert: str
|
|
16
|
+
key: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_ssl_config() -> SslConfig | None:
|
|
20
|
+
"""Get the keys for the server"""
|
|
21
|
+
if not _ENABLE_SSL_CONFIG:
|
|
22
|
+
return None
|
|
23
|
+
return SslConfig(
|
|
24
|
+
cert=_CERT,
|
|
25
|
+
key=_PRIVATE_KEY,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_PRIVATE_KEY = """-----BEGIN PRIVATE KEY-----
|
|
30
|
+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDlxbWcpUXPpjqs
|
|
31
|
+
DJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcHS+pkgXnG46g6bUcL/AK5Ba08
|
|
32
|
+
vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh2jSCeOTagNoaHLYEugARkkEu
|
|
33
|
+
0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU7K+BLWsCiZ308NMpzHF5APZ6
|
|
34
|
+
YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXWyyPG0jwPQyjBot/cNtt8GrsN
|
|
35
|
+
gcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jprHEsw+/UhnG6Qhksm8C/DN9kP
|
|
36
|
+
hselewffAgMBAAECggEARjQ6YTo+Mkvf8WGGbRjLxteJRiBX7lKOD+V7aY2ce06P
|
|
37
|
+
21LREbbTCm+vljXZN2OnqvJomsjNLCsH21+jaTOIZg5x79LyDn2Au7N8CWdELwVT
|
|
38
|
+
mTbBO2Ql63P4R0UY54onGYNcOeV6z+OX9u7a8L/qYHCxFdHalBZpsfj0gjaQeStJ
|
|
39
|
+
JSnvGjo6tKkwC/nUmX01qEVQgUO1+39WYqCaIWjijZNXt6XiKclEuu1AkL0u6Mpt
|
|
40
|
+
CzvzEDrEA66D0Lvl3Tek9B4O16Oie5anNnNMHigwU9yVc6dI8vDCRSEiz7laPTFK
|
|
41
|
+
xzOCQmqPGClKXkX3U+OvZp/Ss9U26Wpu0AbRKTvzAQKBgQDsMR9NjMpOmUaWkAwl
|
|
42
|
+
1wlUxsZ9YkRuTy7R3RfIdYWj6Lcoc4/iN0qILFM7xidkHhYTFqnsnP1SQkV6lEHV
|
|
43
|
+
OalYxZu9F2l1rHPc8G5YWh/KOg1rAEI47MVT4iwhA2fw6JLti/rm25AeSTMjSTqu
|
|
44
|
+
ht3146036opcIF3v86oGUrSXDwKBgQD5CsNcwLeUDGXozjq62T8/mTYwd2Tw3aiY
|
|
45
|
+
KaGp+exAW321vYm5SKsMitBMGU2tGFlv5eptSI48h7SCpgnszaexw7yj30KuvqjG
|
|
46
|
+
bBqq/MsKuXHyn2sG0A7MJ6zfk+4l46B45blDJZ+x7xL0dyS4UCU3zUeesgSGo4zK
|
|
47
|
+
ZOspPIQCMQKBgQCk35VuWP1P6IbxyxPvxi/pUeh01gfWyMdyD9fuQrtLM8PHJQQn
|
|
48
|
+
cVlBvU9MxoHwzV+za3qqhNwAc+p0KtHZuiqQoUCZuqIPVpZ6gAtG+YJ/dA6xxrhz
|
|
49
|
+
bDRC3frYALyp2m/WCoTWaiYsPgTIePHRqqt+XbQo+DwlGyL3wSvKxijx2QKBgCb0
|
|
50
|
+
OwioEE70/X/DukX9szn0chh0pHJUiYl7gZD/yadraCdkRUWZC0BD+j7c+lxn4Z1y
|
|
51
|
+
HhAH+E+Zfm+tHwJOTLuufTQ4uMpygh2/TRCPyAaeaSdlLi17n8TpM84o6mg8yZ3/
|
|
52
|
+
eNH68Za4aYOZm0HFL30h++DjwXd534zM6keh8pgRAoGBAKUrsjDGjuSo8l1fi4Cq
|
|
53
|
+
INu/rQop2h/db02zyJP5q7NKhE1nqogeLwwn+2M/LtHQ1nIzZR+rvrLNgt6oWY31
|
|
54
|
+
sPsv8JUfVT8GmdhU9KKmizK6eUu3rWdj2+rJARmuEaPmHcD5O6oJaGU0qadqQP34
|
|
55
|
+
H+enwWmpyZXAIbEu/q63DFhV
|
|
56
|
+
-----END PRIVATE KEY-----"""
|
|
57
|
+
|
|
58
|
+
_CERT = """-----BEGIN _CERTIFICATE-----
|
|
59
|
+
MIIEfTCCAuWgAwIBAgIRAPb7jkLrCuqToG+s3AQYeuUwDQYJKoZIhvcNAQELBQAw
|
|
60
|
+
gakxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE/MD0GA1UECww2REVT
|
|
61
|
+
S1RPUC1JMzcxOERPXFphY2ggVm9yaGllc0BERVNLVE9QLUkzNzE4RE8gKG5pdGVy
|
|
62
|
+
aXMpMUYwRAYDVQQDDD1ta2NlcnQgREVTS1RPUC1JMzcxOERPXFphY2ggVm9yaGll
|
|
63
|
+
c0BERVNLVE9QLUkzNzE4RE8gKG5pdGVyaXMpMB4XDTI1MDQyODAwMzk1MFoXDTI3
|
|
64
|
+
MDcyODAwMzk1MFowajEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRp
|
|
65
|
+
ZmljYXRlMT8wPQYDVQQLDDZERVNLVE9QLUkzNzE4RE9cWmFjaCBWb3JoaWVzQERF
|
|
66
|
+
U0tUT1AtSTM3MThETyAobml0ZXJpcykwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
|
67
|
+
ggEKAoIBAQDlxbWcpUXPpjqsDJPgFF1FsXPZqq0JPJqssHh4ZLfN0h4yJmj+kRcH
|
|
68
|
+
S+pkgXnG46g6bUcL/AK5Ba08vwnUUGkPH0v4ShKiAGYwvOcbWaqTmvvJuIoaDBXh
|
|
69
|
+
2jSCeOTagNoaHLYEugARkkEu0/FEW5P/79wU5vJ5G+SyZ8rBCVdxlU57pL1hKWBU
|
|
70
|
+
7K+BLWsCiZ308NMpzHF5APZ6YxVjhFosJPr4TjN6yXr+whrsAjSTHamD5690MbXW
|
|
71
|
+
yyPG0jwPQyjBot/cNtt8GrsNgcjA1E+8VKFvxO8RvZanMZLb0CGEpt7u3oaJ/jpr
|
|
72
|
+
HEsw+/UhnG6Qhksm8C/DN9kPhselewffAgMBAAGjXjBcMA4GA1UdDwEB/wQEAwIF
|
|
73
|
+
oDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAWgBSPBydvhr9wI+FsoW/H
|
|
74
|
+
WK3DbS8IUDAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggGB
|
|
75
|
+
AJVrF1yczZaxt+A2AhdeFbJQUR6NzGBTc20YeWF1YzLV5sV3QVumwZLP2M9ggRgd
|
|
76
|
+
xWV0xfwUHobFQk6RIPTADcLKctiurql0cgF4DPnpWVvto9RM00U3AkQcMj3xtKBV
|
|
77
|
+
wUqo83TcbqgL+euudFZ09gGTs9u9AENaZPcMh+rW8DDO92t+EwMI/IfopxVOJGUB
|
|
78
|
+
RSM3yTwV93BMYBuddt8mclzLzPK/1WONfsHU2xEascaHR1tYMOmJN9Vq4o0fzWxo
|
|
79
|
+
a2vI6K0aJqZV/ztdXq3akwLc6/e9hyptHWa0i/022xVCyNWIlnuEhT7ENMPxh6rX
|
|
80
|
+
ZCQCZVnhcSWAyFjggLJql3aSID5fPF8rmN7wWsB/I5pl9qwMR1/THMPrm5aWn1Xj
|
|
81
|
+
xW6PxkSGm73kd57DH7tqm5HTd8eYCbnsFofI9rC7xI6HCfwchKp+YHvIEu/LJ56E
|
|
82
|
+
FLnCZW/orYkHCzWntzxv1bddrw1BwaNR8Q+mu3imRP8fuyXb2UkFkINVVyOOWHuW
|
|
83
|
+
Kw==
|
|
84
|
+
-----END _CERTIFICATE-----"""
|
fastled/live_client.py
CHANGED
|
@@ -16,13 +16,19 @@ class LiveClient:
|
|
|
16
16
|
auto_start: bool = True,
|
|
17
17
|
auto_updates: bool = True,
|
|
18
18
|
open_web_browser: bool = True,
|
|
19
|
+
http_port: (
|
|
20
|
+
int | None
|
|
21
|
+
) = None, # None means auto select a free port. -1 means no server.
|
|
19
22
|
keep_running: bool = True,
|
|
20
23
|
build_mode: BuildMode = BuildMode.QUICK,
|
|
21
24
|
profile: bool = False,
|
|
25
|
+
no_platformio: bool = False,
|
|
26
|
+
enable_https: bool = True, # Enable HTTPS for the local server
|
|
22
27
|
) -> None:
|
|
23
28
|
self.sketch_directory = sketch_directory
|
|
24
29
|
self.host = host
|
|
25
30
|
self.open_web_browser = open_web_browser
|
|
31
|
+
self.http_port = http_port
|
|
26
32
|
self.keep_running = keep_running
|
|
27
33
|
self.build_mode = build_mode
|
|
28
34
|
self.profile = profile
|
|
@@ -30,6 +36,8 @@ class LiveClient:
|
|
|
30
36
|
self.shutdown = threading.Event()
|
|
31
37
|
self.thread: threading.Thread | None = None
|
|
32
38
|
self.auto_updates = auto_updates
|
|
39
|
+
self.no_platformio = no_platformio
|
|
40
|
+
self.enable_https = enable_https
|
|
33
41
|
if auto_start:
|
|
34
42
|
self.start()
|
|
35
43
|
if self.auto_updates is False:
|
|
@@ -47,18 +55,23 @@ class LiveClient:
|
|
|
47
55
|
build_mode=self.build_mode,
|
|
48
56
|
profile=self.profile,
|
|
49
57
|
shutdown=self.shutdown,
|
|
58
|
+
http_port=self.http_port,
|
|
59
|
+
no_platformio=self.no_platformio,
|
|
60
|
+
enable_https=self.enable_https,
|
|
50
61
|
)
|
|
51
62
|
return rtn
|
|
52
63
|
|
|
53
64
|
def url(self) -> str:
|
|
54
65
|
"""Get the URL of the server."""
|
|
66
|
+
from fastled.settings import SERVER_PORT
|
|
67
|
+
|
|
55
68
|
if isinstance(self.host, CompileServer):
|
|
56
69
|
return self.host.url()
|
|
57
70
|
if self.host is None:
|
|
58
71
|
import warnings
|
|
59
72
|
|
|
60
73
|
warnings.warn("TODO: use the actual host.")
|
|
61
|
-
return "http://localhost:
|
|
74
|
+
return f"http://localhost:{SERVER_PORT}"
|
|
62
75
|
return self.host
|
|
63
76
|
|
|
64
77
|
@property
|
|
@@ -89,3 +102,15 @@ class LiveClient:
|
|
|
89
102
|
|
|
90
103
|
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
|
91
104
|
self.finalize()
|
|
105
|
+
|
|
106
|
+
def get_emsdk_headers(self, filepath: Path) -> None:
|
|
107
|
+
"""Get EMSDK headers ZIP data from the server and save to filepath."""
|
|
108
|
+
if isinstance(self.host, CompileServer):
|
|
109
|
+
self.host.get_emsdk_headers(filepath)
|
|
110
|
+
else:
|
|
111
|
+
# Handle string host or None case by using web_compile approach
|
|
112
|
+
from fastled.settings import DEFAULT_URL
|
|
113
|
+
from fastled.util import download_emsdk_headers
|
|
114
|
+
|
|
115
|
+
base_url = self.host if isinstance(self.host, str) else DEFAULT_URL
|
|
116
|
+
download_emsdk_headers(base_url, filepath)
|