fastled 1.4.31__py3-none-any.whl → 1.4.33__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.
@@ -14,6 +14,8 @@ import warnings
14
14
  from pathlib import Path
15
15
  from typing import Any
16
16
 
17
+ from fastled.playwright.resize_tracking import ResizeTracker
18
+
17
19
  # Set custom Playwright browser installation path
18
20
  PLAYWRIGHT_DIR = Path.home() / ".fastled" / "playwright"
19
21
  PLAYWRIGHT_DIR.mkdir(parents=True, exist_ok=True)
@@ -69,6 +71,7 @@ class PlaywrightBrowser:
69
71
  self.playwright: Any = None
70
72
  self._should_exit = asyncio.Event()
71
73
  self._extensions_dir: Path | None = None
74
+ self.resize_tracker: ResizeTracker | None = None
72
75
 
73
76
  # Initialize extensions if enabled
74
77
  if self.enable_extensions:
@@ -278,50 +281,26 @@ class PlaywrightBrowser:
278
281
  "[PYTHON] Setting up browser window tracking with viewport-only adjustment"
279
282
  )
280
283
 
284
+ # Create resize tracker instance
285
+ self.resize_tracker = ResizeTracker(self.page)
286
+
281
287
  # Start polling loop that tracks browser window changes and adjusts viewport only
282
288
  asyncio.create_task(self._track_browser_adjust_viewport())
283
289
 
284
- async def _get_window_info(self) -> dict[str, int] | None:
285
- """Get browser window dimensions information.
286
-
287
- Returns:
288
- Dictionary containing window dimensions or None if unable to retrieve
289
- """
290
- if self.page is None:
291
- return None
292
-
293
- try:
294
- return await self.page.evaluate(
295
- """
296
- () => {
297
- return {
298
- outerWidth: window.outerWidth,
299
- outerHeight: window.outerHeight,
300
- innerWidth: window.innerWidth,
301
- innerHeight: window.innerHeight,
302
- contentWidth: document.documentElement.clientWidth,
303
- contentHeight: document.documentElement.clientHeight
304
- };
305
- }
306
- """
307
- )
308
- except Exception:
309
- return None
310
-
311
290
  async def _track_browser_adjust_viewport(self) -> None:
312
291
  """Track browser window changes and adjust viewport accordingly.
313
292
 
314
- This method polls for changes in the browser window size and adjusts
315
- the viewport size to match, maintaining the browser window dimensions
316
- while ensuring the content area matches the sketch requirements.
293
+ This method polls for changes in the browser window size using the
294
+ ResizeTracker and handles any errors that occur.
317
295
  """
318
-
319
- last_outer_size = None
296
+ if self.resize_tracker is None:
297
+ print("[PYTHON] Cannot start tracking: resize_tracker is None")
298
+ return
320
299
 
321
300
  while not self._should_exit.is_set():
322
301
  try:
323
- # Wait 1 second between polls
324
- await asyncio.sleep(0.25) # Poll every 500ms
302
+ # Wait between polls
303
+ await asyncio.sleep(0.25) # Poll every 250ms
325
304
 
326
305
  # Check if page is still alive
327
306
  if self.page is None or self.page.is_closed():
@@ -329,144 +308,70 @@ class PlaywrightBrowser:
329
308
  self._should_exit.set()
330
309
  return
331
310
 
332
- # Try to get window outer dimensions for context
333
- try:
334
- window_info = await self._get_window_info()
335
-
336
- if window_info:
311
+ # Update resize tracking
312
+ result = await self.resize_tracker.update()
313
+
314
+ if result is not None:
315
+ assert isinstance(result, Exception)
316
+ # An exception occurred in resize tracking
317
+ error_message = str(result)
318
+ warnings.warn(f"[PYTHON] Error in resize tracking: {error_message}")
319
+
320
+ # Be EXTREMELY conservative about browser close detection
321
+ # Only trigger shutdown on very specific errors that definitively indicate browser closure
322
+ browser_definitely_closed = any(
323
+ phrase in error_message.lower()
324
+ for phrase in [
325
+ "browser has been closed",
326
+ "target closed",
327
+ "connection closed",
328
+ "target page, probably because the page has been closed",
329
+ "page has been closed",
330
+ "browser context has been closed",
331
+ ]
332
+ )
337
333
 
338
- current_outer = (
339
- window_info["outerWidth"],
340
- window_info["outerHeight"],
334
+ # Also check actual browser state before deciding to shut down
335
+ browser_state_indicates_closed = False
336
+ try:
337
+ if self.browser and hasattr(self.browser, "is_closed"):
338
+ browser_state_indicates_closed = self.browser.is_closed()
339
+ elif self.context and hasattr(self.context, "closed"):
340
+ browser_state_indicates_closed = self.context.closed
341
+ except Exception:
342
+ # If we can't check the state, don't assume it's closed
343
+ warnings.warn(
344
+ f"[PYTHON] Could not check browser state: {result}. Assuming browser is not closed."
341
345
  )
346
+ browser_state_indicates_closed = False
342
347
 
343
- # Print current state occasionally
344
- if last_outer_size is None or current_outer != last_outer_size:
345
-
346
- if last_outer_size is not None:
347
- print("[PYTHON] *** BROWSER WINDOW RESIZED ***")
348
- print(
349
- f"[PYTHON] Outer window changed from {last_outer_size[0]}x{last_outer_size[1]} to {current_outer[0]}x{current_outer[1]}"
350
- )
351
-
352
- last_outer_size = current_outer
353
-
354
- # Set viewport to match the outer window size
355
- outer_width = int(window_info["outerWidth"])
356
- outer_height = int(window_info["outerHeight"])
357
-
348
+ if browser_definitely_closed or browser_state_indicates_closed:
349
+ if browser_definitely_closed:
358
350
  print(
359
- f"[PYTHON] Setting viewport to match outer window size: {outer_width}x{outer_height}"
351
+ f'[PYTHON] Browser has been closed because "{error_message}" matched one of the error phrases or browser state indicates closed, shutting down gracefully...'
360
352
  )
361
-
362
- await self.page.set_viewport_size(
363
- {"width": outer_width, "height": outer_height}
353
+ elif browser_state_indicates_closed:
354
+ print(
355
+ "[PYTHON] Browser state indicates closed, shutting down gracefully..."
364
356
  )
365
- print("[PYTHON] Viewport set successfully")
366
-
367
- # Wait briefly for browser to settle after viewport change
368
- # await asyncio.sleep(2)
369
-
370
- # Query the actual window dimensions after the viewport change
371
- updated_window_info = await self._get_window_info()
372
-
373
- if updated_window_info:
374
- print(
375
- f"[PYTHON] Updated window info: {updated_window_info}"
376
- )
377
-
378
- # Update our tracking with the actual final outer size
379
- last_outer_size = (
380
- updated_window_info["outerWidth"],
381
- updated_window_info["outerHeight"],
382
- )
383
- print(
384
- f"[PYTHON] Updated last_outer_size to actual final size: {last_outer_size}"
385
- )
386
- else:
387
- print("[PYTHON] Could not get updated window info")
388
-
389
- except Exception as e:
390
- warnings.warn(
391
- f"[PYTHON] Could not get browser window info: {e}. Assuming browser is not closed."
392
- )
393
- import traceback
394
-
395
- traceback.print_exc()
396
- pass
397
-
398
- # Get the browser window information periodically
399
- try:
400
- browser_info = await self.page.evaluate(
401
- """
402
- () => {
403
- return {
404
- userAgent: navigator.userAgent,
405
- platform: navigator.platform,
406
- cookieEnabled: navigator.cookieEnabled,
407
- language: navigator.language
408
- };
409
- }
410
- """
411
- )
412
-
413
- if browser_info:
414
- pass # We have browser info, but don't need to print it constantly
357
+ self._should_exit.set()
358
+ return
415
359
  else:
416
- print("[PYTHON] Could not get browser window info")
417
-
418
- except Exception as e:
419
- print(f"[PYTHON] Could not get browser info: {e}")
360
+ # For other errors, just log and continue - don't shut down
361
+ print(
362
+ f"[PYTHON] Recoverable error in resize tracking: {result}"
363
+ )
364
+ # Add a small delay to prevent tight error loops
365
+ await asyncio.sleep(1.0)
420
366
 
421
367
  except Exception as e:
422
368
  error_message = str(e)
423
- warnings.warn(f"[PYTHON] Error in browser tracking: {error_message}")
424
- # Be EXTREMELY conservative about browser close detection
425
- # Only trigger shutdown on very specific errors that definitively indicate browser closure
426
- browser_definitely_closed = any(
427
- phrase in error_message.lower()
428
- for phrase in [
429
- "browser has been closed",
430
- "target closed",
431
- "connection closed",
432
- "target page, probably because the page has been closed",
433
- # "execution context was destroyed",
434
- "page has been closed",
435
- "browser context has been closed",
436
- ]
369
+ warnings.warn(
370
+ f"[PYTHON] Unexpected error in browser tracking loop: {error_message}"
437
371
  )
372
+ # Add a small delay to prevent tight error loops
373
+ await asyncio.sleep(1.0)
438
374
 
439
- # Also check actual browser state before deciding to shut down
440
- browser_state_indicates_closed = False
441
- try:
442
- if self.browser and hasattr(self.browser, "is_closed"):
443
- browser_state_indicates_closed = self.browser.is_closed()
444
- elif self.context and hasattr(self.context, "closed"):
445
- browser_state_indicates_closed = self.context.closed
446
- except Exception:
447
- # If we can't check the state, don't assume it's closed
448
- warnings.warn(
449
- f"[PYTHON] Could not check browser state: {e}. Assuming browser is not closed."
450
- )
451
- browser_state_indicates_closed = False
452
-
453
- if browser_definitely_closed or browser_state_indicates_closed:
454
- if browser_definitely_closed:
455
- print(
456
- f'[PYTHON] Browser has been closed because "{error_message}" matched one of the error phrases or browser state indicates closed, shutting down gracefully...'
457
- )
458
- elif browser_state_indicates_closed:
459
- print(
460
- "[PYTHON] Browser state indicates closed, shutting down gracefully..."
461
- )
462
- self._should_exit.set()
463
- break
464
- else:
465
- # For other errors, just log and continue - don't shut down
466
- print(f"[PYTHON] Recoverable error in browser tracking: {e}")
467
- # Add a small delay to prevent tight error loops
468
- await asyncio.sleep(1.0)
469
- continue
470
375
  warnings.warn("[PYTHON] Browser tracking loop exited.")
471
376
 
472
377
  async def wait_for_close(self) -> None:
@@ -492,6 +397,8 @@ class PlaywrightBrowser:
492
397
  self._should_exit.set()
493
398
 
494
399
  try:
400
+ # Clean up resize tracker
401
+ self.resize_tracker = None
495
402
 
496
403
  if self.page:
497
404
  await self.page.close()
@@ -0,0 +1,127 @@
1
+ """
2
+ Resize tracking for Playwright browser integration.
3
+
4
+ This module provides a class to track browser window resize events and adjust
5
+ the viewport accordingly without using internal polling loops.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+
11
+ class ResizeTracker:
12
+ """Tracks browser window resize events and adjusts viewport accordingly."""
13
+
14
+ def __init__(self, page: Any):
15
+ """Initialize the resize tracker.
16
+
17
+ Args:
18
+ page: The Playwright page object to track
19
+ """
20
+ self.page = page
21
+ self.last_outer_size: tuple[int, int] | None = None
22
+
23
+ async def _get_window_info(self) -> dict[str, int] | None:
24
+ """Get browser window dimensions information.
25
+
26
+ Returns:
27
+ Dictionary containing window dimensions or None if unable to retrieve
28
+ """
29
+ if self.page is None:
30
+ return None
31
+
32
+ try:
33
+ return await self.page.evaluate(
34
+ """
35
+ () => {
36
+ return {
37
+ outerWidth: window.outerWidth,
38
+ outerHeight: window.outerHeight,
39
+ innerWidth: window.innerWidth,
40
+ innerHeight: window.innerHeight,
41
+ contentWidth: document.documentElement.clientWidth,
42
+ contentHeight: document.documentElement.clientHeight
43
+ };
44
+ }
45
+ """
46
+ )
47
+ except Exception:
48
+ return None
49
+
50
+ async def set_viewport_size(self, width: int, height: int) -> None:
51
+ """Set the viewport size.
52
+
53
+ Args:
54
+ width: The viewport width
55
+ height: The viewport height
56
+ """
57
+ if self.page is None:
58
+ raise Exception("Page is None")
59
+
60
+ await self.page.set_viewport_size({"width": width, "height": height})
61
+
62
+ async def update(self) -> Exception | None:
63
+ """Update the resize tracking and adjust viewport if needed.
64
+
65
+ Returns:
66
+ None if successful, Exception if an error occurred
67
+ """
68
+ try:
69
+ # Check if page is still alive
70
+ if self.page is None or self.page.is_closed():
71
+ return Exception("Page closed")
72
+
73
+ window_info = await self._get_window_info()
74
+
75
+ if window_info:
76
+ current_outer = (
77
+ window_info["outerWidth"],
78
+ window_info["outerHeight"],
79
+ )
80
+
81
+ # Check if window size changed
82
+ if (
83
+ self.last_outer_size is None
84
+ or current_outer != self.last_outer_size
85
+ ):
86
+
87
+ if self.last_outer_size is not None:
88
+ print("[PYTHON] *** BROWSER WINDOW RESIZED ***")
89
+ print(
90
+ f"[PYTHON] Outer window changed from {self.last_outer_size[0]}x{self.last_outer_size[1]} to {current_outer[0]}x{current_outer[1]}"
91
+ )
92
+
93
+ self.last_outer_size = current_outer
94
+
95
+ # Set viewport to match the outer window size
96
+ outer_width = int(window_info["outerWidth"])
97
+ outer_height = int(window_info["outerHeight"])
98
+
99
+ print(
100
+ f"[PYTHON] Setting viewport to match outer window size: {outer_width}x{outer_height}"
101
+ )
102
+
103
+ await self.set_viewport_size(outer_width, outer_height)
104
+ print("[PYTHON] Viewport set successfully")
105
+
106
+ # Query the actual window dimensions after the viewport change
107
+ updated_window_info = await self._get_window_info()
108
+
109
+ if updated_window_info:
110
+ print(f"[PYTHON] Updated window info: {updated_window_info}")
111
+
112
+ # Update our tracking with the actual final outer size
113
+ self.last_outer_size = (
114
+ updated_window_info["outerWidth"],
115
+ updated_window_info["outerHeight"],
116
+ )
117
+ print(
118
+ f"[PYTHON] Updated last_outer_size to actual final size: {self.last_outer_size}"
119
+ )
120
+ else:
121
+ print("[PYTHON] Could not get updated window info")
122
+
123
+ except Exception as e:
124
+ return e
125
+
126
+ # Success case
127
+ return None
fastled/print_filter.py CHANGED
@@ -1,52 +1,52 @@
1
- import re
2
- from abc import ABC, abstractmethod
3
- from enum import Enum
4
-
5
-
6
- class PrintFilter(ABC):
7
- """Abstract base class for filtering text output."""
8
-
9
- def __init__(self, echo: bool = True) -> None:
10
- self.echo = echo
11
-
12
- @abstractmethod
13
- def filter(self, text: str) -> str:
14
- """Filter the text according to implementation-specific rules."""
15
- pass
16
-
17
- def print(self, text: str | bytes) -> str:
18
- """Prints the text to the console after filtering."""
19
- if isinstance(text, bytes):
20
- text = text.decode("utf-8")
21
- text = self.filter(text)
22
- if self.echo:
23
- print(text, end="")
24
- return text
25
-
26
-
27
- def _handle_ino_cpp(line: str) -> str:
28
- if ".ino.cpp" in line[0:30]:
29
- # Extract the filename without path and extension
30
- match = re.search(r"src/([^/]+)\.ino\.cpp", line)
31
- if match:
32
- filename = match.group(1)
33
- # Replace with examples/Filename/Filename.ino format
34
- line = line.replace(
35
- f"src/{filename}.ino.cpp", f"examples/{filename}/{filename}.ino"
36
- )
37
- else:
38
- # Fall back to simple extension replacement if regex doesn't match
39
- line = line.replace(".ino.cpp", ".ino")
40
- return line
41
-
42
-
43
- class PrintFilterDefault(PrintFilter):
44
- """Provides default filtering for FastLED output."""
45
-
46
- def filter(self, text: str) -> str:
47
- return text
48
-
49
-
50
- class CompileOrLink(Enum):
51
- COMPILE = "compile"
52
- LINK = "link"
1
+ import re
2
+ from abc import ABC, abstractmethod
3
+ from enum import Enum
4
+
5
+
6
+ class PrintFilter(ABC):
7
+ """Abstract base class for filtering text output."""
8
+
9
+ def __init__(self, echo: bool = True) -> None:
10
+ self.echo = echo
11
+
12
+ @abstractmethod
13
+ def filter(self, text: str) -> str:
14
+ """Filter the text according to implementation-specific rules."""
15
+ pass
16
+
17
+ def print(self, text: str | bytes) -> str:
18
+ """Prints the text to the console after filtering."""
19
+ if isinstance(text, bytes):
20
+ text = text.decode("utf-8")
21
+ text = self.filter(text)
22
+ if self.echo:
23
+ print(text, end="")
24
+ return text
25
+
26
+
27
+ def _handle_ino_cpp(line: str) -> str:
28
+ if ".ino.cpp" in line[0:30]:
29
+ # Extract the filename without path and extension
30
+ match = re.search(r"src/([^/]+)\.ino\.cpp", line)
31
+ if match:
32
+ filename = match.group(1)
33
+ # Replace with examples/Filename/Filename.ino format
34
+ line = line.replace(
35
+ f"src/{filename}.ino.cpp", f"examples/{filename}/{filename}.ino"
36
+ )
37
+ else:
38
+ # Fall back to simple extension replacement if regex doesn't match
39
+ line = line.replace(".ino.cpp", ".ino")
40
+ return line
41
+
42
+
43
+ class PrintFilterDefault(PrintFilter):
44
+ """Provides default filtering for FastLED output."""
45
+
46
+ def filter(self, text: str) -> str:
47
+ return text
48
+
49
+
50
+ class CompileOrLink(Enum):
51
+ COMPILE = "compile"
52
+ LINK = "link"