fastled 1.3.38__py3-none-any.whl → 1.4.0__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 +0 -1
- fastled/__version__.py +1 -1
- fastled/client_server.py +10 -0
- fastled/open_browser.py +40 -8
- fastled/parse_args.py +3 -3
- fastled/playwright_browser.py +495 -0
- {fastled-1.3.38.dist-info → fastled-1.4.0.dist-info}/METADATA +42 -1
- {fastled-1.3.38.dist-info → fastled-1.4.0.dist-info}/RECORD +12 -11
- {fastled-1.3.38.dist-info → fastled-1.4.0.dist-info}/WHEEL +0 -0
- {fastled-1.3.38.dist-info → fastled-1.4.0.dist-info}/entry_points.txt +0 -0
- {fastled-1.3.38.dist-info → fastled-1.4.0.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.3.38.dist-info → fastled-1.4.0.dist-info}/top_level.txt +0 -0
fastled/__init__.py
CHANGED
fastled/__version__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# IMPORTANT! There's a bug in github which will REJECT any version update
|
2
2
|
# that has any other change in the repo. Please bump the version as the
|
3
3
|
# ONLY change in a commit, or else the pypi update and the release will fail.
|
4
|
-
__version__ = "1.
|
4
|
+
__version__ = "1.4.0"
|
5
5
|
|
6
6
|
__version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
|
fastled/client_server.py
CHANGED
@@ -25,6 +25,16 @@ from fastled.web_compile import (
|
|
25
25
|
)
|
26
26
|
|
27
27
|
|
28
|
+
def _always_false() -> bool:
|
29
|
+
return False
|
30
|
+
|
31
|
+
|
32
|
+
try:
|
33
|
+
from fastled.playwright_browser import is_playwright_available
|
34
|
+
except ImportError:
|
35
|
+
is_playwright_available = _always_false
|
36
|
+
|
37
|
+
|
28
38
|
def _create_error_html(error_message: str) -> str:
|
29
39
|
return f"""<!DOCTYPE html>
|
30
40
|
<html>
|
fastled/open_browser.py
CHANGED
@@ -8,6 +8,29 @@ from pathlib import Path
|
|
8
8
|
|
9
9
|
from fastled.server_flask import run_flask_in_thread
|
10
10
|
|
11
|
+
try:
|
12
|
+
from fastled.playwright_browser import is_playwright_available, open_with_playwright
|
13
|
+
|
14
|
+
PLAYWRIGHT_AVAILABLE = is_playwright_available()
|
15
|
+
except ImportError:
|
16
|
+
PLAYWRIGHT_AVAILABLE = False
|
17
|
+
open_with_playwright = None
|
18
|
+
|
19
|
+
# Global reference to keep Playwright browser alive
|
20
|
+
_playwright_browser_proxy = None
|
21
|
+
|
22
|
+
|
23
|
+
def cleanup_playwright_browser() -> None:
|
24
|
+
"""Clean up the Playwright browser on exit."""
|
25
|
+
global _playwright_browser_proxy
|
26
|
+
if _playwright_browser_proxy:
|
27
|
+
_playwright_browser_proxy.close()
|
28
|
+
_playwright_browser_proxy = None
|
29
|
+
|
30
|
+
|
31
|
+
# Register cleanup function
|
32
|
+
atexit.register(cleanup_playwright_browser)
|
33
|
+
|
11
34
|
DEFAULT_PORT = 8089 # different than live version.
|
12
35
|
PYTHON_EXE = sys.executable
|
13
36
|
|
@@ -105,14 +128,23 @@ def spawn_http_server(
|
|
105
128
|
|
106
129
|
wait_for_server(port)
|
107
130
|
if open_browser:
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
131
|
+
url = f"http://localhost:{port}"
|
132
|
+
if PLAYWRIGHT_AVAILABLE and open_with_playwright is not None:
|
133
|
+
print(f"Opening FastLED sketch in Playwright browser: {url}")
|
134
|
+
print(
|
135
|
+
"Auto-resize enabled: Browser window will automatically adjust to content size"
|
136
|
+
)
|
137
|
+
global _playwright_browser_proxy
|
138
|
+
_playwright_browser_proxy = open_with_playwright(url)
|
139
|
+
else:
|
140
|
+
print(f"Opening browser to {url}")
|
141
|
+
import webbrowser
|
142
|
+
|
143
|
+
webbrowser.open(
|
144
|
+
url=url,
|
145
|
+
new=1,
|
146
|
+
autoraise=True,
|
147
|
+
)
|
116
148
|
return proc
|
117
149
|
|
118
150
|
|
fastled/parse_args.py
CHANGED
@@ -82,8 +82,8 @@ def parse_args() -> Args:
|
|
82
82
|
parser.add_argument(
|
83
83
|
"--ram-disk-size",
|
84
84
|
type=str,
|
85
|
-
default="
|
86
|
-
help="
|
85
|
+
default="1gb",
|
86
|
+
help="Size of the RAM disk for compilation (e.g., '1gb', '512mb')",
|
87
87
|
)
|
88
88
|
parser.add_argument(
|
89
89
|
"--web",
|
@@ -113,7 +113,7 @@ def parse_args() -> Args:
|
|
113
113
|
parser.add_argument(
|
114
114
|
"--no-auto-updates",
|
115
115
|
action="store_true",
|
116
|
-
help="Disable automatic updates of the wasm compiler image when using docker.",
|
116
|
+
help="Disable automatic updates of the wasm compiler image when using docker. (Default: False)",
|
117
117
|
)
|
118
118
|
parser.add_argument(
|
119
119
|
"--no-platformio",
|
@@ -0,0 +1,495 @@
|
|
1
|
+
"""
|
2
|
+
Playwright browser integration for FastLED WASM compiler.
|
3
|
+
|
4
|
+
This module provides functionality to open the compiled FastLED sketch
|
5
|
+
in a Playwright browser instead of the default system browser when
|
6
|
+
the 'full' optional dependency is installed.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import asyncio
|
10
|
+
import sys
|
11
|
+
import warnings
|
12
|
+
from typing import TYPE_CHECKING, Any
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from playwright.async_api import Browser, Page
|
16
|
+
|
17
|
+
try:
|
18
|
+
from playwright.async_api import Browser, Page, async_playwright
|
19
|
+
|
20
|
+
PLAYWRIGHT_AVAILABLE = True
|
21
|
+
except ImportError:
|
22
|
+
PLAYWRIGHT_AVAILABLE = False
|
23
|
+
async_playwright = None
|
24
|
+
Browser = Any # type: ignore
|
25
|
+
Page = Any # type: ignore
|
26
|
+
|
27
|
+
|
28
|
+
def is_playwright_available() -> bool:
|
29
|
+
"""Check if Playwright is available."""
|
30
|
+
return PLAYWRIGHT_AVAILABLE
|
31
|
+
|
32
|
+
|
33
|
+
class PlaywrightBrowser:
|
34
|
+
"""Playwright browser manager for FastLED sketches."""
|
35
|
+
|
36
|
+
def __init__(self, headless: bool = False):
|
37
|
+
"""Initialize the Playwright browser manager.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
headless: Whether to run the browser in headless mode
|
41
|
+
"""
|
42
|
+
if not PLAYWRIGHT_AVAILABLE:
|
43
|
+
raise ImportError(
|
44
|
+
"Playwright is not installed. Install with: pip install fastled[full]"
|
45
|
+
)
|
46
|
+
|
47
|
+
self.headless = headless
|
48
|
+
self.auto_resize = True # Always enable auto-resize
|
49
|
+
self.browser: Any = None
|
50
|
+
self.page: Any = None
|
51
|
+
self.playwright: Any = None
|
52
|
+
self._should_exit = asyncio.Event()
|
53
|
+
|
54
|
+
async def start(self) -> None:
|
55
|
+
"""Start the Playwright browser."""
|
56
|
+
if self.playwright is None and async_playwright is not None:
|
57
|
+
self.playwright = await async_playwright().start()
|
58
|
+
|
59
|
+
if self.browser is None and self.playwright is not None:
|
60
|
+
# Try Chrome first, then Firefox, then WebKit
|
61
|
+
try:
|
62
|
+
self.browser = await self.playwright.chromium.launch(
|
63
|
+
headless=self.headless,
|
64
|
+
args=["--disable-web-security", "--allow-running-insecure-content"],
|
65
|
+
)
|
66
|
+
except Exception:
|
67
|
+
try:
|
68
|
+
self.browser = await self.playwright.firefox.launch(
|
69
|
+
headless=self.headless
|
70
|
+
)
|
71
|
+
except Exception:
|
72
|
+
self.browser = await self.playwright.webkit.launch(
|
73
|
+
headless=self.headless
|
74
|
+
)
|
75
|
+
|
76
|
+
if self.page is None and self.browser is not None:
|
77
|
+
# Create a new browser context and page
|
78
|
+
context = await self.browser.new_context()
|
79
|
+
self.page = await context.new_page()
|
80
|
+
|
81
|
+
async def open_url(self, url: str) -> None:
|
82
|
+
"""Open a URL in the Playwright browser.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
url: The URL to open
|
86
|
+
"""
|
87
|
+
if self.page is None:
|
88
|
+
await self.start()
|
89
|
+
|
90
|
+
print(f"Opening FastLED sketch in Playwright browser: {url}")
|
91
|
+
if self.page is not None:
|
92
|
+
await self.page.goto(url)
|
93
|
+
|
94
|
+
# Wait for the page to load
|
95
|
+
await self.page.wait_for_load_state("networkidle")
|
96
|
+
|
97
|
+
# Set up auto-resizing functionality if enabled
|
98
|
+
if self.auto_resize:
|
99
|
+
await self._setup_auto_resize()
|
100
|
+
|
101
|
+
async def _setup_auto_resize(self) -> None:
|
102
|
+
"""Set up automatic window resizing based on content size."""
|
103
|
+
if self.page is None:
|
104
|
+
print("[PYTHON] Cannot setup auto-resize: page is None")
|
105
|
+
return
|
106
|
+
|
107
|
+
print(
|
108
|
+
"[PYTHON] Setting up browser window tracking with viewport-only adjustment"
|
109
|
+
)
|
110
|
+
|
111
|
+
# Start polling loop that tracks browser window changes and adjusts viewport only
|
112
|
+
asyncio.create_task(self._track_browser_adjust_viewport())
|
113
|
+
|
114
|
+
async def _get_window_info(self) -> dict[str, int] | None:
|
115
|
+
"""Get browser window dimensions information.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
Dictionary containing window dimensions or None if unable to retrieve
|
119
|
+
"""
|
120
|
+
if self.page is None:
|
121
|
+
return None
|
122
|
+
|
123
|
+
try:
|
124
|
+
return await self.page.evaluate(
|
125
|
+
"""
|
126
|
+
() => {
|
127
|
+
return {
|
128
|
+
outerWidth: window.outerWidth,
|
129
|
+
outerHeight: window.outerHeight,
|
130
|
+
innerWidth: window.innerWidth,
|
131
|
+
innerHeight: window.innerHeight,
|
132
|
+
contentWidth: document.documentElement.clientWidth,
|
133
|
+
contentHeight: document.documentElement.clientHeight
|
134
|
+
};
|
135
|
+
}
|
136
|
+
"""
|
137
|
+
)
|
138
|
+
except Exception:
|
139
|
+
return None
|
140
|
+
|
141
|
+
async def _track_browser_adjust_viewport(self) -> None:
|
142
|
+
"""Track browser window outer size changes and adjust viewport accordingly."""
|
143
|
+
if self.page is None:
|
144
|
+
return
|
145
|
+
|
146
|
+
print(
|
147
|
+
"[PYTHON] Starting browser window tracking (outer size → viewport adjustment)"
|
148
|
+
)
|
149
|
+
last_outer_size = None
|
150
|
+
|
151
|
+
while True:
|
152
|
+
try:
|
153
|
+
# Wait 1 second between polls
|
154
|
+
await asyncio.sleep(1)
|
155
|
+
|
156
|
+
# Check if page is still alive
|
157
|
+
if self.page is None or self.page.is_closed():
|
158
|
+
print("[PYTHON] Page closed, signaling exit")
|
159
|
+
self._should_exit.set()
|
160
|
+
return
|
161
|
+
|
162
|
+
# Get browser window dimensions
|
163
|
+
window_info = await self._get_window_info()
|
164
|
+
|
165
|
+
if window_info:
|
166
|
+
current_outer = (
|
167
|
+
window_info["outerWidth"],
|
168
|
+
window_info["outerHeight"],
|
169
|
+
)
|
170
|
+
|
171
|
+
# Print current state occasionally
|
172
|
+
if last_outer_size is None or current_outer != last_outer_size:
|
173
|
+
print(
|
174
|
+
f"[PYTHON] Browser: outer={window_info['outerWidth']}x{window_info['outerHeight']}, content={window_info['contentWidth']}x{window_info['contentHeight']}"
|
175
|
+
)
|
176
|
+
|
177
|
+
# Track changes in OUTER window size (user resizes browser)
|
178
|
+
if last_outer_size is None or current_outer != last_outer_size:
|
179
|
+
|
180
|
+
if last_outer_size is not None:
|
181
|
+
print("[PYTHON] *** BROWSER WINDOW RESIZED ***")
|
182
|
+
print(
|
183
|
+
f"[PYTHON] Outer window changed from {last_outer_size[0]}x{last_outer_size[1]} to {current_outer[0]}x{current_outer[1]}"
|
184
|
+
)
|
185
|
+
|
186
|
+
last_outer_size = current_outer
|
187
|
+
|
188
|
+
# Set viewport to match the outer window size
|
189
|
+
if not self.headless:
|
190
|
+
try:
|
191
|
+
outer_width = int(window_info["outerWidth"])
|
192
|
+
outer_height = int(window_info["outerHeight"])
|
193
|
+
|
194
|
+
print(
|
195
|
+
f"[PYTHON] Setting viewport to match outer window size: {outer_width}x{outer_height}"
|
196
|
+
)
|
197
|
+
|
198
|
+
await self.page.set_viewport_size(
|
199
|
+
{"width": outer_width, "height": outer_height}
|
200
|
+
)
|
201
|
+
print("[PYTHON] Viewport set successfully")
|
202
|
+
|
203
|
+
# Wait briefly for browser to settle after viewport change
|
204
|
+
# await asyncio.sleep(0.5)
|
205
|
+
|
206
|
+
# Query the actual window dimensions after the viewport change
|
207
|
+
updated_window_info = await self._get_window_info()
|
208
|
+
|
209
|
+
if updated_window_info:
|
210
|
+
|
211
|
+
# Update our tracking with the actual final outer size
|
212
|
+
last_outer_size = (
|
213
|
+
updated_window_info["outerWidth"],
|
214
|
+
updated_window_info["outerHeight"],
|
215
|
+
)
|
216
|
+
print(
|
217
|
+
f"[PYTHON] Updated last_outer_size to actual final size: {last_outer_size}"
|
218
|
+
)
|
219
|
+
else:
|
220
|
+
print("[PYTHON] Could not get updated window info")
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
print(f"[PYTHON] Failed to set viewport: {e}")
|
224
|
+
|
225
|
+
else:
|
226
|
+
print("[PYTHON] Could not get browser window info")
|
227
|
+
|
228
|
+
except Exception as e:
|
229
|
+
print(f"[PYTHON] Error in browser tracking: {e}")
|
230
|
+
continue
|
231
|
+
|
232
|
+
async def wait_for_close(self) -> None:
|
233
|
+
"""Wait for the browser to be closed."""
|
234
|
+
if self.browser is None:
|
235
|
+
return
|
236
|
+
|
237
|
+
try:
|
238
|
+
# Wait for the browser to be closed
|
239
|
+
while not self.browser.is_closed():
|
240
|
+
await asyncio.sleep(1)
|
241
|
+
except Exception:
|
242
|
+
pass
|
243
|
+
|
244
|
+
async def close(self) -> None:
|
245
|
+
"""Close the Playwright browser."""
|
246
|
+
if self.page:
|
247
|
+
await self.page.close()
|
248
|
+
self.page = None
|
249
|
+
|
250
|
+
if self.browser:
|
251
|
+
await self.browser.close()
|
252
|
+
self.browser = None
|
253
|
+
|
254
|
+
if self.playwright:
|
255
|
+
await self.playwright.stop()
|
256
|
+
self.playwright = None
|
257
|
+
|
258
|
+
|
259
|
+
def run_playwright_browser(url: str, headless: bool = False) -> None:
|
260
|
+
"""Run Playwright browser in a separate process.
|
261
|
+
|
262
|
+
Args:
|
263
|
+
url: The URL to open
|
264
|
+
headless: Whether to run in headless mode
|
265
|
+
"""
|
266
|
+
if not PLAYWRIGHT_AVAILABLE:
|
267
|
+
warnings.warn(
|
268
|
+
"Playwright is not installed. Install with: pip install fastled[full]. "
|
269
|
+
"Falling back to default browser."
|
270
|
+
)
|
271
|
+
return
|
272
|
+
|
273
|
+
async def main():
|
274
|
+
browser = PlaywrightBrowser(headless=headless)
|
275
|
+
try:
|
276
|
+
await browser.start()
|
277
|
+
await browser.open_url(url)
|
278
|
+
|
279
|
+
if not headless:
|
280
|
+
print("Playwright browser opened. Press Ctrl+C to close.")
|
281
|
+
await browser.wait_for_close()
|
282
|
+
else:
|
283
|
+
# In headless mode, just wait a bit for the page to load
|
284
|
+
await asyncio.sleep(2)
|
285
|
+
|
286
|
+
except KeyboardInterrupt:
|
287
|
+
print("\nClosing Playwright browser...")
|
288
|
+
finally:
|
289
|
+
await browser.close()
|
290
|
+
|
291
|
+
try:
|
292
|
+
asyncio.run(main())
|
293
|
+
except KeyboardInterrupt:
|
294
|
+
print("\nPlaywright browser closed.")
|
295
|
+
except Exception as e:
|
296
|
+
warnings.warn(
|
297
|
+
f"Playwright browser failed: {e}. Falling back to default browser."
|
298
|
+
)
|
299
|
+
|
300
|
+
|
301
|
+
class PlaywrightBrowserProxy:
|
302
|
+
"""Proxy object to manage Playwright browser lifecycle."""
|
303
|
+
|
304
|
+
def __init__(self):
|
305
|
+
self.process = None
|
306
|
+
self.browser_manager = None
|
307
|
+
self.monitor_thread = None
|
308
|
+
self._closing_intentionally = False
|
309
|
+
|
310
|
+
def open(self, url: str, headless: bool = False) -> None:
|
311
|
+
"""Open URL with Playwright browser and keep it alive.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
url: The URL to open
|
315
|
+
headless: Whether to run in headless mode
|
316
|
+
"""
|
317
|
+
if not PLAYWRIGHT_AVAILABLE:
|
318
|
+
warnings.warn(
|
319
|
+
"Playwright is not installed. Install with: pip install fastled[full]. "
|
320
|
+
"Falling back to default browser."
|
321
|
+
)
|
322
|
+
# Fall back to default browser
|
323
|
+
import webbrowser
|
324
|
+
|
325
|
+
webbrowser.open(url)
|
326
|
+
return
|
327
|
+
|
328
|
+
try:
|
329
|
+
# Run Playwright in a separate process to avoid blocking
|
330
|
+
import multiprocessing
|
331
|
+
|
332
|
+
self.process = multiprocessing.Process(
|
333
|
+
target=run_playwright_browser_persistent,
|
334
|
+
args=(url, headless),
|
335
|
+
)
|
336
|
+
self.process.start()
|
337
|
+
|
338
|
+
# Start monitoring thread to exit main process when browser subprocess exits
|
339
|
+
self._start_monitor_thread()
|
340
|
+
|
341
|
+
# Register cleanup
|
342
|
+
import atexit
|
343
|
+
|
344
|
+
atexit.register(self.close)
|
345
|
+
|
346
|
+
except Exception as e:
|
347
|
+
warnings.warn(
|
348
|
+
f"Failed to start Playwright browser: {e}. Falling back to default browser."
|
349
|
+
)
|
350
|
+
import webbrowser
|
351
|
+
|
352
|
+
webbrowser.open(url)
|
353
|
+
|
354
|
+
def _start_monitor_thread(self) -> None:
|
355
|
+
"""Start a thread to monitor the browser process and exit main process when it terminates."""
|
356
|
+
if self.monitor_thread is not None:
|
357
|
+
return
|
358
|
+
|
359
|
+
import os
|
360
|
+
import threading
|
361
|
+
|
362
|
+
def monitor_process():
|
363
|
+
"""Monitor the browser process and exit when it terminates."""
|
364
|
+
if self.process is None:
|
365
|
+
return
|
366
|
+
|
367
|
+
try:
|
368
|
+
# Wait for the process to terminate
|
369
|
+
self.process.join()
|
370
|
+
|
371
|
+
# Check if the process terminated (and we didn't kill it ourselves)
|
372
|
+
if (
|
373
|
+
self.process.exitcode is not None
|
374
|
+
and not self._closing_intentionally
|
375
|
+
):
|
376
|
+
print("[MAIN] Browser closed, exiting main program")
|
377
|
+
# Force exit the entire program
|
378
|
+
os._exit(0)
|
379
|
+
|
380
|
+
except Exception as e:
|
381
|
+
print(f"[MAIN] Error monitoring browser process: {e}")
|
382
|
+
|
383
|
+
self.monitor_thread = threading.Thread(target=monitor_process, daemon=True)
|
384
|
+
self.monitor_thread.start()
|
385
|
+
|
386
|
+
def close(self) -> None:
|
387
|
+
"""Close the Playwright browser."""
|
388
|
+
if self.process and self.process.is_alive():
|
389
|
+
print("Closing Playwright browser...")
|
390
|
+
# Mark that we're intentionally closing to prevent monitor from triggering exit
|
391
|
+
self._closing_intentionally = True
|
392
|
+
self.process.terminate()
|
393
|
+
self.process.join(timeout=5)
|
394
|
+
if self.process.is_alive():
|
395
|
+
self.process.kill()
|
396
|
+
self.process = None
|
397
|
+
|
398
|
+
|
399
|
+
def run_playwright_browser_persistent(url: str, headless: bool = False) -> None:
|
400
|
+
"""Run Playwright browser in a persistent mode that stays alive until terminated.
|
401
|
+
|
402
|
+
Args:
|
403
|
+
url: The URL to open
|
404
|
+
headless: Whether to run in headless mode
|
405
|
+
"""
|
406
|
+
if not PLAYWRIGHT_AVAILABLE:
|
407
|
+
return
|
408
|
+
|
409
|
+
async def main():
|
410
|
+
browser = PlaywrightBrowser(headless=headless)
|
411
|
+
try:
|
412
|
+
await browser.start()
|
413
|
+
await browser.open_url(url)
|
414
|
+
|
415
|
+
print(
|
416
|
+
"Playwright browser opened. Browser will remain open until the FastLED process exits."
|
417
|
+
)
|
418
|
+
|
419
|
+
# Keep the browser alive until exit is signaled
|
420
|
+
while not browser._should_exit.is_set():
|
421
|
+
await asyncio.sleep(0.1)
|
422
|
+
|
423
|
+
except KeyboardInterrupt:
|
424
|
+
print("\nClosing Playwright browser...")
|
425
|
+
except Exception as e:
|
426
|
+
print(f"Playwright browser error: {e}")
|
427
|
+
finally:
|
428
|
+
await browser.close()
|
429
|
+
|
430
|
+
try:
|
431
|
+
asyncio.run(main())
|
432
|
+
except KeyboardInterrupt:
|
433
|
+
print("\nPlaywright browser closed.")
|
434
|
+
except Exception as e:
|
435
|
+
print(f"Playwright browser failed: {e}")
|
436
|
+
|
437
|
+
|
438
|
+
def open_with_playwright(url: str, headless: bool = False) -> PlaywrightBrowserProxy:
|
439
|
+
"""Open URL with Playwright browser and return a proxy object for lifecycle management.
|
440
|
+
|
441
|
+
This function can be used as a drop-in replacement for webbrowser.open().
|
442
|
+
|
443
|
+
Args:
|
444
|
+
url: The URL to open
|
445
|
+
headless: Whether to run in headless mode
|
446
|
+
|
447
|
+
Returns:
|
448
|
+
PlaywrightBrowserProxy object for managing the browser lifecycle
|
449
|
+
"""
|
450
|
+
proxy = PlaywrightBrowserProxy()
|
451
|
+
proxy.open(url, headless)
|
452
|
+
return proxy
|
453
|
+
|
454
|
+
|
455
|
+
def install_playwright_browsers() -> bool:
|
456
|
+
"""Install Playwright browsers if not already installed.
|
457
|
+
|
458
|
+
Returns:
|
459
|
+
True if installation was successful or browsers were already installed
|
460
|
+
"""
|
461
|
+
if not PLAYWRIGHT_AVAILABLE:
|
462
|
+
return False
|
463
|
+
|
464
|
+
try:
|
465
|
+
from playwright.sync_api import sync_playwright
|
466
|
+
|
467
|
+
with sync_playwright() as p:
|
468
|
+
# Try to launch a browser to see if it's installed
|
469
|
+
try:
|
470
|
+
browser = p.chromium.launch(headless=True)
|
471
|
+
browser.close()
|
472
|
+
return True
|
473
|
+
except Exception:
|
474
|
+
pass
|
475
|
+
|
476
|
+
# If we get here, browsers need to be installed
|
477
|
+
print("Installing Playwright browsers...")
|
478
|
+
import subprocess
|
479
|
+
|
480
|
+
result = subprocess.run(
|
481
|
+
[sys.executable, "-m", "playwright", "install", "chromium"],
|
482
|
+
capture_output=True,
|
483
|
+
text=True,
|
484
|
+
)
|
485
|
+
|
486
|
+
if result.returncode == 0:
|
487
|
+
print("Playwright browsers installed successfully.")
|
488
|
+
return True
|
489
|
+
else:
|
490
|
+
print(f"Failed to install Playwright browsers: {result.stderr}")
|
491
|
+
return False
|
492
|
+
|
493
|
+
except Exception as e:
|
494
|
+
print(f"Error installing Playwright browsers: {e}")
|
495
|
+
return False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fastled
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: FastLED Wasm Compiler
|
5
5
|
Home-page: https://github.com/zackees/fastled-wasm
|
6
6
|
Maintainer: Zachary Vorhies
|
@@ -22,6 +22,8 @@ Requires-Dist: Flask>=3.0.0
|
|
22
22
|
Requires-Dist: flask-cors>=4.0.0
|
23
23
|
Requires-Dist: livereload
|
24
24
|
Requires-Dist: disklru>=2.0.4
|
25
|
+
Provides-Extra: full
|
26
|
+
Requires-Dist: playwright>=1.40.0; extra == "full"
|
25
27
|
Dynamic: home-page
|
26
28
|
Dynamic: license-file
|
27
29
|
Dynamic: maintainer
|
@@ -71,7 +73,34 @@ $ cd mysketchdirectory
|
|
71
73
|
$ fastled
|
72
74
|
```
|
73
75
|
|
76
|
+
## Playwright Browser Support
|
74
77
|
|
78
|
+
For enhanced browser control and automation capabilities, you can install the full version with Playwright support:
|
79
|
+
|
80
|
+
```bash
|
81
|
+
$ pip install fastled[full]
|
82
|
+
```
|
83
|
+
|
84
|
+
When installed, FastLED will automatically use Playwright to open your compiled sketch in a controlled browser environment instead of your system's default browser:
|
85
|
+
|
86
|
+
```bash
|
87
|
+
$ fastled my_sketch
|
88
|
+
```
|
89
|
+
|
90
|
+
The Playwright browser provides better automation capabilities and is especially useful for:
|
91
|
+
- Automated testing of your LED sketches
|
92
|
+
- Consistent cross-platform browser behavior
|
93
|
+
- Advanced debugging and development workflows
|
94
|
+
- Persistent browser sessions that stay open until the FastLED process exits
|
95
|
+
|
96
|
+
**Key Benefits:**
|
97
|
+
- Automatically enabled when `fastled[full]` is installed - no additional flags needed
|
98
|
+
- The Playwright browser remains open throughout your development session
|
99
|
+
- Automatic cleanup when the FastLED process exits
|
100
|
+
- Better control over browser behavior and automation capabilities
|
101
|
+
- Consistent behavior across different platforms
|
102
|
+
|
103
|
+
If Playwright is not installed, the system will gracefully fall back to your default browser.
|
75
104
|
|
76
105
|
# Install
|
77
106
|
|
@@ -81,14 +110,26 @@ This is a python app, so any python package manager will work. We also provide p
|
|
81
110
|
|
82
111
|
`pip install fastled`
|
83
112
|
|
113
|
+
### Pip with Playwright Support
|
114
|
+
|
115
|
+
`pip install fastled[full]`
|
116
|
+
|
84
117
|
### UV
|
85
118
|
|
86
119
|
`uv pip install fastled --system`
|
87
120
|
|
121
|
+
### UV with Playwright Support
|
122
|
+
|
123
|
+
`uv pip install "fastled[full]" --system`
|
124
|
+
|
88
125
|
### Pipx
|
89
126
|
|
90
127
|
`pipx install fastled`
|
91
128
|
|
129
|
+
### Pipx with Playwright Support
|
130
|
+
|
131
|
+
`pipx install "fastled[full]"`
|
132
|
+
|
92
133
|
### Executables
|
93
134
|
|
94
135
|
* Windows: https://github.com/zackees/fastled-wasm/releases/latest/download/fastled-windows-x64.zip
|
@@ -1,12 +1,12 @@
|
|
1
|
-
fastled/__init__.py,sha256=
|
1
|
+
fastled/__init__.py,sha256=ha3O0CDjzbyeMMHkptpxLFt7jWlupMuPav2N3v46JJw,7144
|
2
2
|
fastled/__main__.py,sha256=OcKv2ER1_iQAsZzLIUb3C8hRC9L2clNOhCrjpshrlf4,336
|
3
|
-
fastled/__version__.py,sha256=
|
3
|
+
fastled/__version__.py,sha256=8-8HxUEfNauwJ4tp9B9B_cOmZd65cNGlcqReTxEQzUo,372
|
4
4
|
fastled/app.py,sha256=TFVn4qIRdt7dYbpDWudEHrhvD9pwyj9sIGXs4F26nhk,5880
|
5
5
|
fastled/args.py,sha256=kucRGYpff_YKfmMpwWsJh6WIrvW_UPcNlZNFdw15z-Y,3475
|
6
6
|
fastled/cli.py,sha256=drgR2AOxVrj3QEz58iiKscYAumbbin2vIV-k91VCOAA,561
|
7
7
|
fastled/cli_test.py,sha256=W-1nODZrip_JU6BEbYhxOa4ckxduOsiX8zIoRkTyxv4,550
|
8
8
|
fastled/cli_test_interactive.py,sha256=BjNhveZOk5aCffHbcrxPQQjWmAuj4ClVKKcKX5eY6yM,542
|
9
|
-
fastled/client_server.py,sha256=
|
9
|
+
fastled/client_server.py,sha256=Gvrc_dvQmEPFOmQvPi7gIj3PLj4yVUVJRO1Z9VqKj9I,20579
|
10
10
|
fastled/compile_server.py,sha256=yQtwLOSKINO1CKD0NWxf-7YQKSatf9sF9RuqaWGOkCs,3038
|
11
11
|
fastled/compile_server_impl.py,sha256=9vTGaDQ0W_g9Xsfy0gC3nJEc2g_pnXcF4VO2U3GLOVg,11982
|
12
12
|
fastled/docker_manager.py,sha256=rkq39ZKrU6NHIyDa3mzs0Unb6o9oMeAwxhqiuHJU_RY,40291
|
@@ -14,9 +14,10 @@ fastled/filewatcher.py,sha256=gEcJJHTDJ1X3gKJzltmEBhixWGbZj2eJD7a4vwSvITQ,10036
|
|
14
14
|
fastled/keyboard.py,sha256=UTAsqCn1UMYnB8YDzENiLTj4GeL45tYfEcO7_5fLFEg,3556
|
15
15
|
fastled/keyz.py,sha256=LO-8m_7CpNDiZLM-FXhQ30f9gN1bUYz5lOsUPTIbI-c,4020
|
16
16
|
fastled/live_client.py,sha256=yp_ujG92EHYpSedGOUteuG2nQvMKbp1GbUpgQ6nU4Dc,3083
|
17
|
-
fastled/open_browser.py,sha256=
|
18
|
-
fastled/parse_args.py,sha256=
|
17
|
+
fastled/open_browser.py,sha256=xYENAjcGqJzDlqga0y4unOSx3GWONX8QV-HoP0StGvo,4778
|
18
|
+
fastled/parse_args.py,sha256=MAkoaa0zIQ-LfqAqu_uDBJnKe8HJbvuznnL_ONBX64c,11349
|
19
19
|
fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
|
20
|
+
fastled/playwright_browser.py,sha256=SoUZUVfpcGRt6dXoQUASHNgMXIQzFYqN6bBvqsVZDsU,17033
|
20
21
|
fastled/print_filter.py,sha256=nc_rqYYdCUPinFycaK7fiQF5PG1up51pmJptR__QyAs,1499
|
21
22
|
fastled/project_init.py,sha256=bBt4DwmW5hZkm9ICt9Qk-0Nr_0JQM7icCgH5Iv-bCQs,3984
|
22
23
|
fastled/select_sketch_directory.py,sha256=-eudwCns3AKj4HuHtSkZAFwbnf005SNL07pOzs9VxnE,1383
|
@@ -37,9 +38,9 @@ fastled/site/build.py,sha256=2YKU_UWKlJdGnjdbAbaL0co6kceFMSTVYwH1KCmgPZA,13987
|
|
37
38
|
fastled/site/examples.py,sha256=s6vj2zJc6BfKlnbwXr1QWY1mzuDBMt6j5MEBOWjO_U8,155
|
38
39
|
fastled/test/can_run_local_docker_tests.py,sha256=LEuUbHctRhNNFWcvnz2kEGmjDJeXO4c3kNpizm3yVJs,400
|
39
40
|
fastled/test/examples.py,sha256=GfaHeY1E8izBl6ZqDVjz--RHLyVR4NRnQ5pBesCFJFY,1673
|
40
|
-
fastled-1.
|
41
|
-
fastled-1.
|
42
|
-
fastled-1.
|
43
|
-
fastled-1.
|
44
|
-
fastled-1.
|
45
|
-
fastled-1.
|
41
|
+
fastled-1.4.0.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
42
|
+
fastled-1.4.0.dist-info/METADATA,sha256=ZZz8meXb5dwXovyqYmW5YgL_-2ltHRvkgyfTbxiC5uY,32253
|
43
|
+
fastled-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
44
|
+
fastled-1.4.0.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
|
45
|
+
fastled-1.4.0.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
|
46
|
+
fastled-1.4.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|