fastled 1.3.38__py3-none-any.whl → 1.3.39__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/__version__.py +1 -1
- fastled/args.py +10 -0
- fastled/client_server.py +6 -0
- fastled/open_browser.py +57 -8
- fastled/parse_args.py +15 -0
- fastled/playwright_browser.py +468 -0
- {fastled-1.3.38.dist-info → fastled-1.3.39.dist-info}/METADATA +41 -1
- {fastled-1.3.38.dist-info → fastled-1.3.39.dist-info}/RECORD +12 -11
- {fastled-1.3.38.dist-info → fastled-1.3.39.dist-info}/WHEEL +0 -0
- {fastled-1.3.38.dist-info → fastled-1.3.39.dist-info}/entry_points.txt +0 -0
- {fastled-1.3.38.dist-info → fastled-1.3.39.dist-info}/licenses/LICENSE +0 -0
- {fastled-1.3.38.dist-info → fastled-1.3.39.dist-info}/top_level.txt +0 -0
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.3.
|
4
|
+
__version__ = "1.3.39"
|
5
5
|
|
6
6
|
__version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
|
fastled/args.py
CHANGED
@@ -23,6 +23,8 @@ class Args:
|
|
23
23
|
quick: bool
|
24
24
|
release: bool
|
25
25
|
ram_disk_size: str # suffixed liked "25mb" or "1gb"
|
26
|
+
playwright: bool = False # Use Playwright browser instead of system default
|
27
|
+
no_playwright_auto_resize: bool = False # Disable Playwright auto-resize
|
26
28
|
clear = False # Force the last running container to be removed. Useful for benchmarking.
|
27
29
|
|
28
30
|
@staticmethod
|
@@ -66,6 +68,12 @@ class Args:
|
|
66
68
|
assert isinstance(
|
67
69
|
args.release, bool
|
68
70
|
), f"expected bool, got {type(args.release)}"
|
71
|
+
assert isinstance(
|
72
|
+
args.playwright, bool
|
73
|
+
), f"expected bool, got {type(args.playwright)}"
|
74
|
+
assert isinstance(
|
75
|
+
args.no_playwright_auto_resize, bool
|
76
|
+
), f"expected bool, got {type(args.no_playwright_auto_resize)}"
|
69
77
|
|
70
78
|
init: bool | str = False
|
71
79
|
if args.init is None:
|
@@ -93,4 +101,6 @@ class Args:
|
|
93
101
|
quick=args.quick,
|
94
102
|
release=args.release,
|
95
103
|
ram_disk_size=args.ram_disk_size,
|
104
|
+
playwright=args.playwright,
|
105
|
+
no_playwright_auto_resize=args.no_playwright_auto_resize,
|
96
106
|
)
|
fastled/client_server.py
CHANGED
@@ -258,6 +258,8 @@ def run_client(
|
|
258
258
|
) = None, # None means auto select a free port, http_port < 0 means no server.
|
259
259
|
clear: bool = False,
|
260
260
|
no_platformio: bool = False,
|
261
|
+
use_playwright: bool = False,
|
262
|
+
playwright_auto_resize: bool = True,
|
261
263
|
) -> int:
|
262
264
|
has_checked_newer_version_yet = False
|
263
265
|
compile_server: CompileServer | None = None
|
@@ -338,6 +340,8 @@ def run_client(
|
|
338
340
|
port=http_port,
|
339
341
|
compile_server_port=port,
|
340
342
|
open_browser=open_web_browser,
|
343
|
+
use_playwright=use_playwright,
|
344
|
+
playwright_auto_resize=playwright_auto_resize,
|
341
345
|
)
|
342
346
|
else:
|
343
347
|
print("\nCompilation successful.")
|
@@ -560,6 +564,8 @@ def run_client_server(args: Args) -> int:
|
|
560
564
|
profile=profile,
|
561
565
|
clear=args.clear,
|
562
566
|
no_platformio=no_platformio,
|
567
|
+
use_playwright=args.playwright,
|
568
|
+
playwright_auto_resize=not args.no_playwright_auto_resize,
|
563
569
|
)
|
564
570
|
except KeyboardInterrupt:
|
565
571
|
return 1
|
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
|
|
@@ -78,6 +101,8 @@ def spawn_http_server(
|
|
78
101
|
compile_server_port: int,
|
79
102
|
port: int | None = None,
|
80
103
|
open_browser: bool = True,
|
104
|
+
use_playwright: bool = False,
|
105
|
+
playwright_auto_resize: bool = True,
|
81
106
|
) -> Process:
|
82
107
|
|
83
108
|
if port is not None and not is_port_free(port):
|
@@ -105,14 +130,38 @@ def spawn_http_server(
|
|
105
130
|
|
106
131
|
wait_for_server(port)
|
107
132
|
if open_browser:
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
133
|
+
url = f"http://localhost:{port}"
|
134
|
+
if use_playwright and PLAYWRIGHT_AVAILABLE and open_with_playwright is not None:
|
135
|
+
print(f"Opening FastLED sketch in Playwright browser: {url}")
|
136
|
+
if playwright_auto_resize:
|
137
|
+
print(
|
138
|
+
"Auto-resize enabled: Browser window will automatically adjust to content size"
|
139
|
+
)
|
140
|
+
global _playwright_browser_proxy
|
141
|
+
_playwright_browser_proxy = open_with_playwright(
|
142
|
+
url, auto_resize=playwright_auto_resize
|
143
|
+
)
|
144
|
+
elif use_playwright and not PLAYWRIGHT_AVAILABLE:
|
145
|
+
print(
|
146
|
+
"Playwright requested but not available. Install with: pip install fastled[full]"
|
147
|
+
)
|
148
|
+
print(f"Falling back to system browser: {url}")
|
149
|
+
import webbrowser
|
150
|
+
|
151
|
+
webbrowser.open(
|
152
|
+
url=url,
|
153
|
+
new=1,
|
154
|
+
autoraise=True,
|
155
|
+
)
|
156
|
+
else:
|
157
|
+
print(f"Opening browser to {url}")
|
158
|
+
import webbrowser
|
159
|
+
|
160
|
+
webbrowser.open(
|
161
|
+
url=url,
|
162
|
+
new=1,
|
163
|
+
autoraise=True,
|
164
|
+
)
|
116
165
|
return proc
|
117
166
|
|
118
167
|
|
fastled/parse_args.py
CHANGED
@@ -35,12 +35,15 @@ FastLED WASM Compiler - Useful options:
|
|
35
35
|
--profile Enable profiling the C++ build system
|
36
36
|
--update Update the docker image for the wasm compiler
|
37
37
|
--purge Remove all FastLED containers and images
|
38
|
+
--playwright Use Playwright browser (requires 'pip install fastled[full]')
|
39
|
+
--no-playwright-auto-resize Disable automatic window resizing in Playwright
|
38
40
|
--version Show version information
|
39
41
|
--help Show detailed help
|
40
42
|
Examples:
|
41
43
|
fastled (will auto detect the sketch directory and prompt you)
|
42
44
|
fastled my_sketch
|
43
45
|
fastled my_sketch --web (compiles using the web compiler only)
|
46
|
+
fastled my_sketch --playwright (opens in Playwright browser)
|
44
47
|
fastled --init Blink (initializes a new sketch directory with the Blink example)
|
45
48
|
fastled --server (runs the compiler server in the current directory)
|
46
49
|
|
@@ -158,6 +161,18 @@ def parse_args() -> Args:
|
|
158
161
|
help="Remove all FastLED containers and images",
|
159
162
|
)
|
160
163
|
|
164
|
+
parser.add_argument(
|
165
|
+
"--playwright",
|
166
|
+
action="store_true",
|
167
|
+
help="Use Playwright browser instead of system default (requires 'pip install fastled[full]')",
|
168
|
+
)
|
169
|
+
|
170
|
+
parser.add_argument(
|
171
|
+
"--no-playwright-auto-resize",
|
172
|
+
action="store_true",
|
173
|
+
help="Disable automatic window resizing in Playwright browser",
|
174
|
+
)
|
175
|
+
|
161
176
|
build_mode = parser.add_mutually_exclusive_group()
|
162
177
|
build_mode.add_argument("--debug", action="store_true", help="Build in debug mode")
|
163
178
|
build_mode.add_argument(
|
@@ -0,0 +1,468 @@
|
|
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, auto_resize: bool = True):
|
37
|
+
"""Initialize the Playwright browser manager.
|
38
|
+
|
39
|
+
Args:
|
40
|
+
headless: Whether to run the browser in headless mode
|
41
|
+
auto_resize: Whether to automatically resize the browser window to fit content
|
42
|
+
"""
|
43
|
+
if not PLAYWRIGHT_AVAILABLE:
|
44
|
+
raise ImportError(
|
45
|
+
"Playwright is not installed. Install with: pip install fastled[full]"
|
46
|
+
)
|
47
|
+
|
48
|
+
# debug
|
49
|
+
auto_resize = True
|
50
|
+
|
51
|
+
self.headless = headless
|
52
|
+
self.auto_resize = auto_resize
|
53
|
+
self.browser: Any = None
|
54
|
+
self.page: Any = None
|
55
|
+
self.playwright: Any = None
|
56
|
+
|
57
|
+
async def start(self) -> None:
|
58
|
+
"""Start the Playwright browser."""
|
59
|
+
if self.playwright is None and async_playwright is not None:
|
60
|
+
self.playwright = await async_playwright().start()
|
61
|
+
|
62
|
+
if self.browser is None and self.playwright is not None:
|
63
|
+
# Try Chrome first, then Firefox, then WebKit
|
64
|
+
try:
|
65
|
+
self.browser = await self.playwright.chromium.launch(
|
66
|
+
headless=self.headless,
|
67
|
+
args=["--disable-web-security", "--allow-running-insecure-content"],
|
68
|
+
)
|
69
|
+
except Exception:
|
70
|
+
try:
|
71
|
+
self.browser = await self.playwright.firefox.launch(
|
72
|
+
headless=self.headless
|
73
|
+
)
|
74
|
+
except Exception:
|
75
|
+
self.browser = await self.playwright.webkit.launch(
|
76
|
+
headless=self.headless
|
77
|
+
)
|
78
|
+
|
79
|
+
if self.page is None and self.browser is not None:
|
80
|
+
# Create a new browser context and page
|
81
|
+
context = await self.browser.new_context()
|
82
|
+
self.page = await context.new_page()
|
83
|
+
|
84
|
+
async def open_url(self, url: str) -> None:
|
85
|
+
"""Open a URL in the Playwright browser.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
url: The URL to open
|
89
|
+
"""
|
90
|
+
if self.page is None:
|
91
|
+
await self.start()
|
92
|
+
|
93
|
+
print(f"Opening FastLED sketch in Playwright browser: {url}")
|
94
|
+
if self.page is not None:
|
95
|
+
await self.page.goto(url)
|
96
|
+
|
97
|
+
# Wait for the page to load
|
98
|
+
await self.page.wait_for_load_state("networkidle")
|
99
|
+
|
100
|
+
# Set up auto-resizing functionality if enabled
|
101
|
+
if self.auto_resize:
|
102
|
+
await self._setup_auto_resize()
|
103
|
+
|
104
|
+
async def _setup_auto_resize(self) -> None:
|
105
|
+
"""Set up automatic window resizing based on content size."""
|
106
|
+
if self.page is None:
|
107
|
+
print("[PYTHON] Cannot setup auto-resize: page is None")
|
108
|
+
return
|
109
|
+
|
110
|
+
print(
|
111
|
+
"[PYTHON] Setting up browser window tracking with viewport-only adjustment"
|
112
|
+
)
|
113
|
+
|
114
|
+
# Start polling loop that tracks browser window changes and adjusts viewport only
|
115
|
+
asyncio.create_task(self._track_browser_adjust_viewport())
|
116
|
+
|
117
|
+
async def _get_window_info(self) -> dict[str, int] | None:
|
118
|
+
"""Get browser window dimensions information.
|
119
|
+
|
120
|
+
Returns:
|
121
|
+
Dictionary containing window dimensions or None if unable to retrieve
|
122
|
+
"""
|
123
|
+
if self.page is None:
|
124
|
+
return None
|
125
|
+
|
126
|
+
try:
|
127
|
+
return await self.page.evaluate(
|
128
|
+
"""
|
129
|
+
() => {
|
130
|
+
return {
|
131
|
+
outerWidth: window.outerWidth,
|
132
|
+
outerHeight: window.outerHeight,
|
133
|
+
innerWidth: window.innerWidth,
|
134
|
+
innerHeight: window.innerHeight,
|
135
|
+
contentWidth: document.documentElement.clientWidth,
|
136
|
+
contentHeight: document.documentElement.clientHeight
|
137
|
+
};
|
138
|
+
}
|
139
|
+
"""
|
140
|
+
)
|
141
|
+
except Exception:
|
142
|
+
return None
|
143
|
+
|
144
|
+
async def _track_browser_adjust_viewport(self) -> None:
|
145
|
+
"""Track browser window outer size changes and adjust viewport accordingly."""
|
146
|
+
if self.page is None:
|
147
|
+
return
|
148
|
+
|
149
|
+
print(
|
150
|
+
"[PYTHON] Starting browser window tracking (outer size → viewport adjustment)"
|
151
|
+
)
|
152
|
+
last_outer_size = None
|
153
|
+
|
154
|
+
while True:
|
155
|
+
try:
|
156
|
+
# Wait 1 second between polls
|
157
|
+
await asyncio.sleep(1)
|
158
|
+
|
159
|
+
# Check if page is still alive
|
160
|
+
if self.page is None or self.page.is_closed():
|
161
|
+
print("[PYTHON] Page closed, stopping browser tracking")
|
162
|
+
break
|
163
|
+
|
164
|
+
# Get browser window dimensions
|
165
|
+
window_info = await self._get_window_info()
|
166
|
+
|
167
|
+
if window_info:
|
168
|
+
current_outer = (
|
169
|
+
window_info["outerWidth"],
|
170
|
+
window_info["outerHeight"],
|
171
|
+
)
|
172
|
+
|
173
|
+
# Print current state occasionally
|
174
|
+
if last_outer_size is None or current_outer != last_outer_size:
|
175
|
+
print(
|
176
|
+
f"[PYTHON] Browser: outer={window_info['outerWidth']}x{window_info['outerHeight']}, content={window_info['contentWidth']}x{window_info['contentHeight']}"
|
177
|
+
)
|
178
|
+
|
179
|
+
# Track changes in OUTER window size (user resizes browser)
|
180
|
+
if last_outer_size is None or current_outer != last_outer_size:
|
181
|
+
|
182
|
+
if last_outer_size is not None:
|
183
|
+
print("[PYTHON] *** BROWSER WINDOW RESIZED ***")
|
184
|
+
print(
|
185
|
+
f"[PYTHON] Outer window changed from {last_outer_size[0]}x{last_outer_size[1]} to {current_outer[0]}x{current_outer[1]}"
|
186
|
+
)
|
187
|
+
|
188
|
+
last_outer_size = current_outer
|
189
|
+
|
190
|
+
# Set viewport to match the outer window size
|
191
|
+
if not self.headless:
|
192
|
+
try:
|
193
|
+
outer_width = int(window_info["outerWidth"])
|
194
|
+
outer_height = int(window_info["outerHeight"])
|
195
|
+
|
196
|
+
print(
|
197
|
+
f"[PYTHON] Setting viewport to match outer window size: {outer_width}x{outer_height}"
|
198
|
+
)
|
199
|
+
|
200
|
+
await self.page.set_viewport_size(
|
201
|
+
{"width": outer_width, "height": outer_height}
|
202
|
+
)
|
203
|
+
print("[PYTHON] Viewport set successfully")
|
204
|
+
|
205
|
+
# Wait briefly for browser to settle after viewport change
|
206
|
+
# await asyncio.sleep(0.5)
|
207
|
+
|
208
|
+
# Query the actual window dimensions after the viewport change
|
209
|
+
updated_window_info = await self._get_window_info()
|
210
|
+
|
211
|
+
if updated_window_info:
|
212
|
+
|
213
|
+
# Update our tracking with the actual final outer size
|
214
|
+
last_outer_size = (
|
215
|
+
updated_window_info["outerWidth"],
|
216
|
+
updated_window_info["outerHeight"],
|
217
|
+
)
|
218
|
+
print(
|
219
|
+
f"[PYTHON] Updated last_outer_size to actual final size: {last_outer_size}"
|
220
|
+
)
|
221
|
+
else:
|
222
|
+
print("[PYTHON] Could not get updated window info")
|
223
|
+
|
224
|
+
except Exception as e:
|
225
|
+
print(f"[PYTHON] Failed to set viewport: {e}")
|
226
|
+
|
227
|
+
else:
|
228
|
+
print("[PYTHON] Could not get browser window info")
|
229
|
+
|
230
|
+
except Exception as e:
|
231
|
+
print(f"[PYTHON] Error in browser tracking: {e}")
|
232
|
+
continue
|
233
|
+
|
234
|
+
async def wait_for_close(self) -> None:
|
235
|
+
"""Wait for the browser to be closed."""
|
236
|
+
if self.browser is None:
|
237
|
+
return
|
238
|
+
|
239
|
+
try:
|
240
|
+
# Wait for the browser to be closed
|
241
|
+
while not self.browser.is_closed():
|
242
|
+
await asyncio.sleep(1)
|
243
|
+
except Exception:
|
244
|
+
pass
|
245
|
+
|
246
|
+
async def close(self) -> None:
|
247
|
+
"""Close the Playwright browser."""
|
248
|
+
if self.page:
|
249
|
+
await self.page.close()
|
250
|
+
self.page = None
|
251
|
+
|
252
|
+
if self.browser:
|
253
|
+
await self.browser.close()
|
254
|
+
self.browser = None
|
255
|
+
|
256
|
+
if self.playwright:
|
257
|
+
await self.playwright.stop()
|
258
|
+
self.playwright = None
|
259
|
+
|
260
|
+
|
261
|
+
def run_playwright_browser(
|
262
|
+
url: str, headless: bool = False, auto_resize: bool = True
|
263
|
+
) -> None:
|
264
|
+
"""Run Playwright browser in a separate process.
|
265
|
+
|
266
|
+
Args:
|
267
|
+
url: The URL to open
|
268
|
+
headless: Whether to run in headless mode
|
269
|
+
auto_resize: Whether to automatically resize the browser window to fit content
|
270
|
+
"""
|
271
|
+
if not PLAYWRIGHT_AVAILABLE:
|
272
|
+
warnings.warn(
|
273
|
+
"Playwright is not installed. Install with: pip install fastled[full]. "
|
274
|
+
"Falling back to default browser."
|
275
|
+
)
|
276
|
+
return
|
277
|
+
|
278
|
+
async def main():
|
279
|
+
browser = PlaywrightBrowser(headless=headless, auto_resize=auto_resize)
|
280
|
+
try:
|
281
|
+
await browser.start()
|
282
|
+
await browser.open_url(url)
|
283
|
+
|
284
|
+
if not headless:
|
285
|
+
print("Playwright browser opened. Press Ctrl+C to close.")
|
286
|
+
await browser.wait_for_close()
|
287
|
+
else:
|
288
|
+
# In headless mode, just wait a bit for the page to load
|
289
|
+
await asyncio.sleep(2)
|
290
|
+
|
291
|
+
except KeyboardInterrupt:
|
292
|
+
print("\nClosing Playwright browser...")
|
293
|
+
finally:
|
294
|
+
await browser.close()
|
295
|
+
|
296
|
+
try:
|
297
|
+
asyncio.run(main())
|
298
|
+
except KeyboardInterrupt:
|
299
|
+
print("\nPlaywright browser closed.")
|
300
|
+
except Exception as e:
|
301
|
+
warnings.warn(
|
302
|
+
f"Playwright browser failed: {e}. Falling back to default browser."
|
303
|
+
)
|
304
|
+
|
305
|
+
|
306
|
+
class PlaywrightBrowserProxy:
|
307
|
+
"""Proxy object to manage Playwright browser lifecycle."""
|
308
|
+
|
309
|
+
def __init__(self):
|
310
|
+
self.process = None
|
311
|
+
self.browser_manager = None
|
312
|
+
|
313
|
+
def open(self, url: str, headless: bool = False, auto_resize: bool = True) -> None:
|
314
|
+
"""Open URL with Playwright browser and keep it alive.
|
315
|
+
|
316
|
+
Args:
|
317
|
+
url: The URL to open
|
318
|
+
headless: Whether to run in headless mode
|
319
|
+
auto_resize: Whether to automatically resize the browser window to fit content
|
320
|
+
"""
|
321
|
+
if not PLAYWRIGHT_AVAILABLE:
|
322
|
+
warnings.warn(
|
323
|
+
"Playwright is not installed. Install with: pip install fastled[full]. "
|
324
|
+
"Falling back to default browser."
|
325
|
+
)
|
326
|
+
# Fall back to default browser
|
327
|
+
import webbrowser
|
328
|
+
|
329
|
+
webbrowser.open(url)
|
330
|
+
return
|
331
|
+
|
332
|
+
try:
|
333
|
+
# Run Playwright in a separate process to avoid blocking
|
334
|
+
import multiprocessing
|
335
|
+
|
336
|
+
self.process = multiprocessing.Process(
|
337
|
+
target=run_playwright_browser_persistent,
|
338
|
+
args=(url, headless, auto_resize),
|
339
|
+
)
|
340
|
+
self.process.start()
|
341
|
+
|
342
|
+
# Register cleanup
|
343
|
+
import atexit
|
344
|
+
|
345
|
+
atexit.register(self.close)
|
346
|
+
|
347
|
+
except Exception as e:
|
348
|
+
warnings.warn(
|
349
|
+
f"Failed to start Playwright browser: {e}. Falling back to default browser."
|
350
|
+
)
|
351
|
+
import webbrowser
|
352
|
+
|
353
|
+
webbrowser.open(url)
|
354
|
+
|
355
|
+
def close(self) -> None:
|
356
|
+
"""Close the Playwright browser."""
|
357
|
+
if self.process and self.process.is_alive():
|
358
|
+
print("Closing Playwright browser...")
|
359
|
+
self.process.terminate()
|
360
|
+
self.process.join(timeout=5)
|
361
|
+
if self.process.is_alive():
|
362
|
+
self.process.kill()
|
363
|
+
self.process = None
|
364
|
+
|
365
|
+
|
366
|
+
def run_playwright_browser_persistent(
|
367
|
+
url: str, headless: bool = False, auto_resize: bool = True
|
368
|
+
) -> None:
|
369
|
+
"""Run Playwright browser in a persistent mode that stays alive until terminated.
|
370
|
+
|
371
|
+
Args:
|
372
|
+
url: The URL to open
|
373
|
+
headless: Whether to run in headless mode
|
374
|
+
auto_resize: Whether to automatically resize the browser window to fit content
|
375
|
+
"""
|
376
|
+
if not PLAYWRIGHT_AVAILABLE:
|
377
|
+
return
|
378
|
+
|
379
|
+
async def main():
|
380
|
+
browser = PlaywrightBrowser(headless=headless, auto_resize=auto_resize)
|
381
|
+
try:
|
382
|
+
await browser.start()
|
383
|
+
await browser.open_url(url)
|
384
|
+
|
385
|
+
print(
|
386
|
+
"Playwright browser opened. Browser will remain open until the FastLED process exits."
|
387
|
+
)
|
388
|
+
|
389
|
+
# Keep the browser alive indefinitely
|
390
|
+
while True:
|
391
|
+
await asyncio.sleep(1)
|
392
|
+
|
393
|
+
except KeyboardInterrupt:
|
394
|
+
print("\nClosing Playwright browser...")
|
395
|
+
except Exception as e:
|
396
|
+
print(f"Playwright browser error: {e}")
|
397
|
+
finally:
|
398
|
+
await browser.close()
|
399
|
+
|
400
|
+
try:
|
401
|
+
asyncio.run(main())
|
402
|
+
except KeyboardInterrupt:
|
403
|
+
print("\nPlaywright browser closed.")
|
404
|
+
except Exception as e:
|
405
|
+
print(f"Playwright browser failed: {e}")
|
406
|
+
|
407
|
+
|
408
|
+
def open_with_playwright(
|
409
|
+
url: str, headless: bool = False, auto_resize: bool = True
|
410
|
+
) -> PlaywrightBrowserProxy:
|
411
|
+
"""Open URL with Playwright browser and return a proxy object for lifecycle management.
|
412
|
+
|
413
|
+
This function can be used as a drop-in replacement for webbrowser.open().
|
414
|
+
|
415
|
+
Args:
|
416
|
+
url: The URL to open
|
417
|
+
headless: Whether to run in headless mode
|
418
|
+
auto_resize: Whether to automatically resize the browser window to fit content
|
419
|
+
|
420
|
+
Returns:
|
421
|
+
PlaywrightBrowserProxy object for managing the browser lifecycle
|
422
|
+
"""
|
423
|
+
proxy = PlaywrightBrowserProxy()
|
424
|
+
proxy.open(url, headless, auto_resize)
|
425
|
+
return proxy
|
426
|
+
|
427
|
+
|
428
|
+
def install_playwright_browsers() -> bool:
|
429
|
+
"""Install Playwright browsers if not already installed.
|
430
|
+
|
431
|
+
Returns:
|
432
|
+
True if installation was successful or browsers were already installed
|
433
|
+
"""
|
434
|
+
if not PLAYWRIGHT_AVAILABLE:
|
435
|
+
return False
|
436
|
+
|
437
|
+
try:
|
438
|
+
from playwright.sync_api import sync_playwright
|
439
|
+
|
440
|
+
with sync_playwright() as p:
|
441
|
+
# Try to launch a browser to see if it's installed
|
442
|
+
try:
|
443
|
+
browser = p.chromium.launch(headless=True)
|
444
|
+
browser.close()
|
445
|
+
return True
|
446
|
+
except Exception:
|
447
|
+
pass
|
448
|
+
|
449
|
+
# If we get here, browsers need to be installed
|
450
|
+
print("Installing Playwright browsers...")
|
451
|
+
import subprocess
|
452
|
+
|
453
|
+
result = subprocess.run(
|
454
|
+
[sys.executable, "-m", "playwright", "install", "chromium"],
|
455
|
+
capture_output=True,
|
456
|
+
text=True,
|
457
|
+
)
|
458
|
+
|
459
|
+
if result.returncode == 0:
|
460
|
+
print("Playwright browsers installed successfully.")
|
461
|
+
return True
|
462
|
+
else:
|
463
|
+
print(f"Failed to install Playwright browsers: {result.stderr}")
|
464
|
+
return False
|
465
|
+
|
466
|
+
except Exception as e:
|
467
|
+
print(f"Error installing Playwright browsers: {e}")
|
468
|
+
return False
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: fastled
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.39
|
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,33 @@ $ 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
|
+
This enables the `--playwright` flag which opens your compiled sketch in a Playwright-controlled browser instead of your system's default browser:
|
85
|
+
|
86
|
+
```bash
|
87
|
+
$ fastled my_sketch --playwright
|
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
|
+
- The Playwright browser remains open throughout your development session
|
98
|
+
- Automatic cleanup when the FastLED process exits
|
99
|
+
- Better control over browser behavior and automation capabilities
|
100
|
+
- Consistent behavior across different platforms
|
101
|
+
|
102
|
+
If Playwright is not installed and you use the `--playwright` flag, the system will gracefully fall back to your default browser.
|
75
103
|
|
76
104
|
# Install
|
77
105
|
|
@@ -81,14 +109,26 @@ This is a python app, so any python package manager will work. We also provide p
|
|
81
109
|
|
82
110
|
`pip install fastled`
|
83
111
|
|
112
|
+
### Pip with Playwright Support
|
113
|
+
|
114
|
+
`pip install fastled[full]`
|
115
|
+
|
84
116
|
### UV
|
85
117
|
|
86
118
|
`uv pip install fastled --system`
|
87
119
|
|
120
|
+
### UV with Playwright Support
|
121
|
+
|
122
|
+
`uv pip install "fastled[full]" --system`
|
123
|
+
|
88
124
|
### Pipx
|
89
125
|
|
90
126
|
`pipx install fastled`
|
91
127
|
|
128
|
+
### Pipx with Playwright Support
|
129
|
+
|
130
|
+
`pipx install "fastled[full]"`
|
131
|
+
|
92
132
|
### Executables
|
93
133
|
|
94
134
|
* Windows: https://github.com/zackees/fastled-wasm/releases/latest/download/fastled-windows-x64.zip
|
@@ -1,12 +1,12 @@
|
|
1
1
|
fastled/__init__.py,sha256=NBk5Ef65nIe0F_rvBrSGeoyouFOKFeQXqHrTCJQheeI,7201
|
2
2
|
fastled/__main__.py,sha256=OcKv2ER1_iQAsZzLIUb3C8hRC9L2clNOhCrjpshrlf4,336
|
3
|
-
fastled/__version__.py,sha256=
|
3
|
+
fastled/__version__.py,sha256=P02pxWlKUAylIYVEU3tL1Bf3RXb-VwgZjzO4VqZoHmc,373
|
4
4
|
fastled/app.py,sha256=TFVn4qIRdt7dYbpDWudEHrhvD9pwyj9sIGXs4F26nhk,5880
|
5
|
-
fastled/args.py,sha256=
|
5
|
+
fastled/args.py,sha256=sugtV0liHa8EnsTeXMMyeH0fO5DgeIzCbDcM5Pbr4k0,4010
|
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=XH38jdEkkfizMrHYMDis5XEQqlG1XS7Bd-Yg1tQ1Zuk,20693
|
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=So4znH-jjc-dheyRVGXrKcLj2vKgfGxCl_ECEfILY9g,5392
|
18
|
+
fastled/parse_args.py,sha256=hNyMeVlN2TX3MbhUkIAdVIeByHLoGFxuajXYmwK_7Fg,11948
|
19
19
|
fastled/paths.py,sha256=VsPmgu0lNSCFOoEC0BsTYzDygXqy15AHUfN-tTuzDZA,99
|
20
|
+
fastled/playwright_browser.py,sha256=4Awad4ZIxWW-n9DMYwSGJBEzd6_7PA0ThQNms1kDxh4,16106
|
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.3.
|
41
|
-
fastled-1.3.
|
42
|
-
fastled-1.3.
|
43
|
-
fastled-1.3.
|
44
|
-
fastled-1.3.
|
45
|
-
fastled-1.3.
|
41
|
+
fastled-1.3.39.dist-info/licenses/LICENSE,sha256=b6pOoifSXiUaz_lDS84vWlG3fr4yUKwB8fzkrH9R8bQ,1064
|
42
|
+
fastled-1.3.39.dist-info/METADATA,sha256=p2oZYp8GXF-76q_IB9VE41428EnWMBjhXracpjGkdzg,32198
|
43
|
+
fastled-1.3.39.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
44
|
+
fastled-1.3.39.dist-info/entry_points.txt,sha256=RCwmzCSOS4-C2i9EziANq7Z2Zb4KFnEMR1FQC0bBwAw,101
|
45
|
+
fastled-1.3.39.dist-info/top_level.txt,sha256=Bbv5kpJpZhWNCvDF4K0VcvtBSDMa8B7PTOrZa9CezHY,8
|
46
|
+
fastled-1.3.39.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|