fastled 1.4.3__tar.gz → 1.4.4__tar.gz

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.
Files changed (132) hide show
  1. {fastled-1.4.3 → fastled-1.4.4}/PKG-INFO +2 -2
  2. {fastled-1.4.3 → fastled-1.4.4}/compiler/run.py +2 -1
  3. {fastled-1.4.3 → fastled-1.4.4}/install +1 -1
  4. {fastled-1.4.3 → fastled-1.4.4}/pyproject.toml +1 -8
  5. fastled-1.4.4/requirements.docker.txt +1 -0
  6. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/__init__.py +12 -1
  7. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/__version__.py +1 -1
  8. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/client_server.py +23 -2
  9. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/compile_server.py +2 -0
  10. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/compile_server_impl.py +13 -0
  11. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/filewatcher.py +5 -4
  12. fastled-1.4.4/src/fastled/find_good_connection.py +105 -0
  13. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/playwright_browser.py +77 -2
  14. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/site/build.py +5 -2
  15. fastled-1.4.4/src/fastled/web_compile.py +309 -0
  16. fastled-1.4.4/src/fastled/zip_files.py +76 -0
  17. {fastled-1.4.3 → fastled-1.4.4}/src/fastled.egg-info/PKG-INFO +2 -2
  18. {fastled-1.4.3 → fastled-1.4.4}/src/fastled.egg-info/SOURCES.txt +3 -0
  19. {fastled-1.4.3 → fastled-1.4.4}/src/fastled.egg-info/requires.txt +1 -1
  20. fastled-1.4.4/tests/integration/test_libcompile.py +294 -0
  21. fastled-1.4.3/requirements.docker.txt +0 -1
  22. fastled-1.4.3/src/fastled/web_compile.py +0 -335
  23. {fastled-1.4.3 → fastled-1.4.4}/.aiderignore +0 -0
  24. {fastled-1.4.3 → fastled-1.4.4}/.cursorrules +0 -0
  25. {fastled-1.4.3 → fastled-1.4.4}/.dockerignore +0 -0
  26. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/build_multi_docker_image.yml +0 -0
  27. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/build_webpage.yml +0 -0
  28. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/lint.yml +0 -0
  29. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/publish_release.yml +0 -0
  30. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/template_build_docker_image.yml +0 -0
  31. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/test_build_exe.yml +0 -0
  32. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/test_macos.yml +0 -0
  33. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/test_ubuntu.yml +0 -0
  34. {fastled-1.4.3 → fastled-1.4.4}/.github/workflows/test_win.yml +0 -0
  35. {fastled-1.4.3 → fastled-1.4.4}/.gitignore +0 -0
  36. {fastled-1.4.3 → fastled-1.4.4}/.pylintrc +0 -0
  37. {fastled-1.4.3 → fastled-1.4.4}/.vscode/launch.json +0 -0
  38. {fastled-1.4.3 → fastled-1.4.4}/.vscode/settings.json +0 -0
  39. {fastled-1.4.3 → fastled-1.4.4}/.vscode/tasks.json +0 -0
  40. {fastled-1.4.3 → fastled-1.4.4}/DEBUGGER.md +0 -0
  41. {fastled-1.4.3 → fastled-1.4.4}/Dockerfile +0 -0
  42. {fastled-1.4.3 → fastled-1.4.4}/FAQ.md +0 -0
  43. {fastled-1.4.3 → fastled-1.4.4}/LICENSE +0 -0
  44. {fastled-1.4.3 → fastled-1.4.4}/MANIFEST.in +0 -0
  45. {fastled-1.4.3 → fastled-1.4.4}/README.md +0 -0
  46. {fastled-1.4.3 → fastled-1.4.4}/RELEASE.md +0 -0
  47. {fastled-1.4.3 → fastled-1.4.4}/TODO.md +0 -0
  48. {fastled-1.4.3 → fastled-1.4.4}/build_exe.py +0 -0
  49. {fastled-1.4.3 → fastled-1.4.4}/build_local_docker.py +0 -0
  50. {fastled-1.4.3 → fastled-1.4.4}/build_site.py +0 -0
  51. {fastled-1.4.3 → fastled-1.4.4}/clean +0 -0
  52. {fastled-1.4.3 → fastled-1.4.4}/compiler/debug.sh +0 -0
  53. {fastled-1.4.3 → fastled-1.4.4}/demo/100dots.html +0 -0
  54. {fastled-1.4.3 → fastled-1.4.4}/demo/demo_threejs.html +0 -0
  55. {fastled-1.4.3 → fastled-1.4.4}/demo/micdemo.html +0 -0
  56. {fastled-1.4.3 → fastled-1.4.4}/demo/mp3upload.html +0 -0
  57. {fastled-1.4.3 → fastled-1.4.4}/demo/webgl_postprocessing_unreal_bloom.html +0 -0
  58. {fastled-1.4.3 → fastled-1.4.4}/docker-compose.yml +0 -0
  59. {fastled-1.4.3 → fastled-1.4.4}/entrypoint.sh +0 -0
  60. {fastled-1.4.3 → fastled-1.4.4}/install_linux.sh +0 -0
  61. {fastled-1.4.3 → fastled-1.4.4}/lint +0 -0
  62. {fastled-1.4.3 → fastled-1.4.4}/requirements.testing.txt +0 -0
  63. {fastled-1.4.3 → fastled-1.4.4}/setup.cfg +0 -0
  64. {fastled-1.4.3 → fastled-1.4.4}/setup.py +0 -0
  65. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/__main__.py +0 -0
  66. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/app.py +0 -0
  67. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/args.py +0 -0
  68. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/assets/example.txt +0 -0
  69. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/assets/localhost-key.pem +0 -0
  70. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/assets/localhost.pem +0 -0
  71. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/cli.py +0 -0
  72. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/cli_test.py +0 -0
  73. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/cli_test_interactive.py +0 -0
  74. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/docker_manager.py +0 -0
  75. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/keyboard.py +0 -0
  76. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/keyz.py +0 -0
  77. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/live_client.py +0 -0
  78. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/open_browser.py +0 -0
  79. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/parse_args.py +0 -0
  80. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/paths.py +0 -0
  81. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/print_filter.py +0 -0
  82. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/project_init.py +0 -0
  83. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/select_sketch_directory.py +0 -0
  84. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/server_flask.py +0 -0
  85. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/server_start.py +0 -0
  86. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/settings.py +0 -0
  87. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/site/examples.py +0 -0
  88. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/sketch.py +0 -0
  89. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/spinner.py +0 -0
  90. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/string_diff.py +0 -0
  91. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/test/can_run_local_docker_tests.py +0 -0
  92. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/test/examples.py +0 -0
  93. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/types.py +0 -0
  94. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/util.py +0 -0
  95. {fastled-1.4.3 → fastled-1.4.4}/src/fastled/version.py +0 -0
  96. {fastled-1.4.3 → fastled-1.4.4}/src/fastled.egg-info/dependency_links.txt +0 -0
  97. {fastled-1.4.3 → fastled-1.4.4}/src/fastled.egg-info/entry_points.txt +0 -0
  98. {fastled-1.4.3 → fastled-1.4.4}/src/fastled.egg-info/top_level.txt +0 -0
  99. {fastled-1.4.3 → fastled-1.4.4}/test +0 -0
  100. {fastled-1.4.3 → fastled-1.4.4}/tests/integration/test_build_examples.py +0 -0
  101. {fastled-1.4.3 → fastled-1.4.4}/tests/integration/test_examples.py +0 -0
  102. {fastled-1.4.3 → fastled-1.4.4}/tests/integration/test_playwright_integration.py +0 -0
  103. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/html/index.html +0 -0
  104. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_api.py +0 -0
  105. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_bad_ino.py +0 -0
  106. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_banner_string.py +0 -0
  107. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_cli.py +0 -0
  108. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_cli_no_platformio.py +0 -0
  109. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_compile_server.py +0 -0
  110. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_debug_fetch_source_files.py +0 -0
  111. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_docker_linux_on_windows.py +0 -0
  112. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_embedded_data.py +0 -0
  113. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_filechanger.py +0 -0
  114. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_flask_headers.py +0 -0
  115. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_http_server.py +0 -0
  116. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_ino/bad/bad.ino +0 -0
  117. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_ino/bad_platformio/bad_platformio.ino +0 -0
  118. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_ino/bad_platformio/platformio.ini +0 -0
  119. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_ino/embedded/data/bigdata.dat +0 -0
  120. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_ino/embedded/wasm.ino +0 -0
  121. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_ino/wasm/wasm.ino +0 -0
  122. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_manual_api_invocation.py +0 -0
  123. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_no_platformio_compile.py +0 -0
  124. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_project_init.py +0 -0
  125. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_server_and_client_seperatly.py +0 -0
  126. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_session_compile.py +0 -0
  127. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_string_diff.py +0 -0
  128. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_string_diff_comprehensive.py +0 -0
  129. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_version.py +0 -0
  130. {fastled-1.4.3 → fastled-1.4.4}/tests/unit/test_webcompile.py +0 -0
  131. {fastled-1.4.3 → fastled-1.4.4}/upload_package.sh +0 -0
  132. {fastled-1.4.3 → fastled-1.4.4}/vscode-plugin/readme +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastled
3
- Version: 1.4.3
3
+ Version: 1.4.4
4
4
  Summary: FastLED Wasm Compiler
5
5
  Home-page: https://github.com/zackees/fastled-wasm
6
6
  Maintainer: Zachary Vorhies
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: fastled-wasm-server>=1.1.0
13
+ Requires-Dist: httpx>=0.28.1
14
14
  Requires-Dist: watchdog>=6.0.0
15
15
  Requires-Dist: docker>=7.1.0
16
16
  Requires-Dist: filelock>=3.16.1
@@ -4,6 +4,7 @@ import subprocess
4
4
  import sys
5
5
  import warnings
6
6
  from pathlib import Path
7
+ from typing import Tuple
7
8
 
8
9
  from fastled_wasm_compiler import Compiler
9
10
  from fastled_wasm_compiler.paths import VOLUME_MAPPED_SRC
@@ -15,7 +16,7 @@ _CHOICES = ["compile", "server"]
15
16
  HERE = Path(__file__).parent
16
17
 
17
18
 
18
- def _parse_args() -> tuple[argparse.Namespace, list[str]]:
19
+ def _parse_args() -> Tuple[argparse.Namespace, list[str]]:
19
20
  parser = argparse.ArgumentParser(
20
21
  description="Run compile.py with additional arguments"
21
22
  )
@@ -17,7 +17,7 @@ if ! command -v uv &> /dev/null; then
17
17
  fi
18
18
 
19
19
  uv venv --python 3.11 --seed
20
- uv pip install -e .[full] --refresh
20
+ uv pip install -e .[full]
21
21
 
22
22
  uv run pip install -r requirements.testing.txt
23
23
  uv run pip install -r requirements.docker.txt
@@ -11,7 +11,7 @@ keywords = ["template-python-cmd"]
11
11
  license = { text = "BSD 3-Clause License" }
12
12
  classifiers = ["Programming Language :: Python :: 3"]
13
13
  dependencies = [
14
- "fastled-wasm-server>=1.1.0", # Just use the Client Code
14
+ "httpx>=0.28.1",
15
15
  "watchdog>=6.0.0",
16
16
  ########## Begin Docker Manager Dependencies
17
17
  "docker>=7.1.0",
@@ -43,13 +43,6 @@ version = { attr = "fastled.__version__" }
43
43
  [tool.ruff]
44
44
  line-length = 200
45
45
 
46
- [tool.ruff.lint]
47
- select = ["F", "E", "W", "UP", "PYI"]
48
-
49
- [tool.ruff.lint.per-file-ignores]
50
- "__init__.py" = ["F401"]
51
- "src/fastled/site/build.py" = ["W291", "W293"] # Ignore whitespace in multi-line strings
52
-
53
46
  [tool.pylint."MESSAGES CONTROL"]
54
47
  good-names = [
55
48
  "c",
@@ -0,0 +1 @@
1
+ fastled-wasm-server>=1.1.17
@@ -1,9 +1,9 @@
1
1
  """FastLED Wasm Compiler package."""
2
2
 
3
- from collections.abc import Generator
4
3
  from contextlib import contextmanager
5
4
  from multiprocessing import Process
6
5
  from pathlib import Path
6
+ from typing import Generator
7
7
 
8
8
  from .__version__ import __version__
9
9
  from .compile_server import CompileServer
@@ -41,18 +41,29 @@ class Api:
41
41
  profile: bool = False, # When true then profile information will be enabled and included in the zip.
42
42
  no_platformio: bool = False,
43
43
  ) -> CompileResult:
44
+ from fastled.sketch import looks_like_fastled_repo
44
45
  from fastled.web_compile import web_compile
45
46
 
46
47
  if isinstance(host, CompileServer):
47
48
  host = host.url()
48
49
  if isinstance(directory, str):
49
50
  directory = Path(directory)
51
+
52
+ # Guard: libfastled compilation requires volume source mapping
53
+ # Only allow libcompile if we're in a FastLED repository
54
+ allow_libcompile = looks_like_fastled_repo(Path(".").resolve())
55
+ if not allow_libcompile:
56
+ print(
57
+ "⚠️ libfastled compilation disabled: not running in FastLED repository"
58
+ )
59
+
50
60
  out: CompileResult = web_compile(
51
61
  directory,
52
62
  host,
53
63
  build_mode=build_mode,
54
64
  profile=profile,
55
65
  no_platformio=no_platformio,
66
+ allow_libcompile=allow_libcompile,
56
67
  )
57
68
  return out
58
69
 
@@ -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.3"
4
+ __version__ = "1.4.4"
5
5
 
6
6
  __version_url_latest__ = "https://raw.githubusercontent.com/zackees/fastled-wasm/refs/heads/main/src/fastled/__version__.py"
@@ -11,6 +11,7 @@ from pathlib import Path
11
11
  from fastled.compile_server import CompileServer
12
12
  from fastled.docker_manager import DockerManager
13
13
  from fastled.filewatcher import DebouncedFileWatcherProcess, FileWatcherProcess
14
+ from fastled.find_good_connection import ConnectionResult
14
15
  from fastled.keyboard import SpaceBarWatcher
15
16
  from fastled.open_browser import spawn_http_server
16
17
  from fastled.parse_args import Args
@@ -19,7 +20,6 @@ from fastled.sketch import looks_like_sketch_directory
19
20
  from fastled.types import BuildMode, CompileResult, CompileServerError
20
21
  from fastled.web_compile import (
21
22
  SERVER_PORT,
22
- ConnectionResult,
23
23
  find_good_connection,
24
24
  web_compile,
25
25
  )
@@ -101,9 +101,17 @@ def _run_web_compiler(
101
101
  profile: bool,
102
102
  last_hash_value: str | None,
103
103
  no_platformio: bool = False,
104
+ allow_libcompile: bool = False,
104
105
  ) -> CompileResult:
106
+ # Remove the import and libcompile detection logic from here
107
+ # since it will now be passed as a parameter
105
108
  input_dir = Path(directory)
106
109
  output_dir = input_dir / "fastled_js"
110
+
111
+ # Guard: libfastled compilation requires volume source mapping
112
+ if not allow_libcompile:
113
+ print("⚠️ libfastled compilation disabled: not running in FastLED repository")
114
+
107
115
  start = time.time()
108
116
  web_result = web_compile(
109
117
  directory=input_dir,
@@ -111,6 +119,7 @@ def _run_web_compiler(
111
119
  build_mode=build_mode,
112
120
  profile=profile,
113
121
  no_platformio=no_platformio,
122
+ allow_libcompile=allow_libcompile,
114
123
  )
115
124
  diff = time.time() - start
116
125
  if not web_result.success:
@@ -310,6 +319,11 @@ def run_client(
310
319
  # Assume default port for www
311
320
  port = 80
312
321
 
322
+ # Auto-detect libcompile capability on first call
323
+ from fastled.sketch import looks_like_fastled_repo
324
+
325
+ allow_libcompile = looks_like_fastled_repo(Path(".").resolve())
326
+
313
327
  try:
314
328
 
315
329
  def compile_function(
@@ -318,6 +332,7 @@ def run_client(
318
332
  profile: bool = profile,
319
333
  last_hash_value: str | None = None,
320
334
  no_platformio: bool = no_platformio,
335
+ allow_libcompile: bool = allow_libcompile,
321
336
  ) -> CompileResult:
322
337
  TEST_BEFORE_COMPILE(url)
323
338
  return _run_web_compiler(
@@ -327,6 +342,7 @@ def run_client(
327
342
  profile=profile,
328
343
  last_hash_value=last_hash_value,
329
344
  no_platformio=no_platformio,
345
+ allow_libcompile=allow_libcompile,
330
346
  )
331
347
 
332
348
  result: CompileResult = compile_function(last_hash_value=None)
@@ -454,6 +470,10 @@ def run_client(
454
470
  if changed_files:
455
471
  print(f"\nChanges detected in FastLED source code: {changed_files}")
456
472
  print("Press space bar to trigger compile.")
473
+
474
+ # Re-evaluate libcompile capability when source code changes
475
+ allow_libcompile = True
476
+
457
477
  while True:
458
478
  space_bar_pressed = SpaceBarWatcher.watch_space_bar_pressed(
459
479
  timeout=1.0
@@ -476,8 +496,9 @@ def run_client(
476
496
  f"Changes detected in {','.join(sketch_files_changed)}, triggering recompile..."
477
497
  )
478
498
  last_compiled_result = compile_function(
479
- last_hash_value=None
499
+ last_hash_value=None, allow_libcompile=allow_libcompile
480
500
  )
501
+ allow_libcompile = False
481
502
  print("Finished recompile.")
482
503
  # Drain the space bar queue
483
504
  SpaceBarWatcher.watch_space_bar_pressed()
@@ -16,6 +16,7 @@ class CompileServer:
16
16
  platform: Platform = Platform.WASM,
17
17
  remove_previous: bool = False,
18
18
  no_platformio: bool = False,
19
+ allow_libcompile: bool = True,
19
20
  ) -> None:
20
21
  from fastled.compile_server_impl import ( # avoid circular import
21
22
  CompileServerImpl,
@@ -31,6 +32,7 @@ class CompileServer:
31
32
  auto_start=auto_start,
32
33
  remove_previous=remove_previous,
33
34
  no_platformio=no_platformio,
35
+ allow_libcompile=allow_libcompile,
34
36
  )
35
37
 
36
38
  # May throw CompileServerError if server could not be started.
@@ -51,6 +51,7 @@ class CompileServerImpl:
51
51
  container_name: str | None = None,
52
52
  remove_previous: bool = False,
53
53
  no_platformio: bool = False,
54
+ allow_libcompile: bool = True,
54
55
  ) -> None:
55
56
  container_name = container_name or DEFAULT_CONTAINER_NAME
56
57
  if interactive and not mapped_dir:
@@ -70,6 +71,17 @@ class CompileServerImpl:
70
71
  self.auto_updates = auto_updates
71
72
  self.remove_previous = remove_previous
72
73
  self.no_platformio = no_platformio
74
+
75
+ # Guard: libfastled compilation requires volume source mapping
76
+ # If we don't have fastled_src_dir (not in FastLED repo), disable libcompile
77
+ if allow_libcompile and self.fastled_src_dir is None:
78
+ print(
79
+ "⚠️ libfastled compilation disabled: volume source mapping not available"
80
+ )
81
+ print(" (not running in FastLED repository)")
82
+ allow_libcompile = False
83
+
84
+ self.allow_libcompile = allow_libcompile
73
85
  self._port = 0 # 0 until compile server is started
74
86
  if auto_start:
75
87
  self.start()
@@ -112,6 +124,7 @@ class CompileServerImpl:
112
124
  build_mode=build_mode,
113
125
  profile=profile,
114
126
  no_platformio=self.no_platformio,
127
+ allow_libcompile=self.allow_libcompile,
115
128
  )
116
129
  return out
117
130
 
@@ -10,6 +10,7 @@ from contextlib import redirect_stdout
10
10
  from multiprocessing import Process, Queue
11
11
  from pathlib import Path
12
12
  from queue import Empty
13
+ from typing import Dict, Set
13
14
 
14
15
  from watchdog.events import FileSystemEvent, FileSystemEventHandler
15
16
  from watchdog.observers import Observer
@@ -34,8 +35,8 @@ class MyEventHandler(FileSystemEventHandler):
34
35
  def __init__(
35
36
  self,
36
37
  change_queue: queue.Queue,
37
- excluded_patterns: set[str],
38
- file_hashes: dict[str, str],
38
+ excluded_patterns: Set[str],
39
+ file_hashes: Dict[str, str],
39
40
  ) -> None:
40
41
  super().__init__()
41
42
  self.change_queue = change_queue
@@ -93,8 +94,8 @@ class FileChangedNotifier(threading.Thread):
93
94
  )
94
95
  self.stopped = False
95
96
  self.change_queue: queue.Queue = queue.Queue()
96
- self.last_notification: dict[str, float] = {}
97
- self.file_hashes: dict[str, str] = {}
97
+ self.last_notification: Dict[str, float] = {}
98
+ self.file_hashes: Dict[str, str] = {}
98
99
  self.debounce_seconds = debounce_seconds
99
100
 
100
101
  def stop(self) -> None:
@@ -0,0 +1,105 @@
1
+ import _thread
2
+ import time
3
+ from concurrent.futures import Future, ThreadPoolExecutor, as_completed
4
+ from dataclasses import dataclass
5
+ from typing import Dict, Tuple
6
+
7
+ import httpx
8
+
9
+ _TIMEOUT = 30.0
10
+
11
+ _EXECUTOR = ThreadPoolExecutor(max_workers=8)
12
+
13
+ # In-memory cache for connection results
14
+ # Key: (tuple of urls, filter_out_bad, use_ipv6)
15
+ # Value: (ConnectionResult | None, timestamp)
16
+ _CONNECTION_CACHE: Dict[
17
+ Tuple[tuple, bool, bool], Tuple["ConnectionResult | None", float]
18
+ ] = {}
19
+ _CACHE_TTL = 60.0 * 60.0 # Cache results for 1 hour
20
+
21
+
22
+ @dataclass
23
+ class ConnectionResult:
24
+ host: str
25
+ success: bool
26
+ ipv4: bool
27
+
28
+
29
+ def _sanitize_host(host: str) -> str:
30
+ if host.startswith("http"):
31
+ return host
32
+ is_local_host = "localhost" in host or "127.0.0.1" in host or "0.0.0.0" in host
33
+ use_https = not is_local_host
34
+ if use_https:
35
+ return host if host.startswith("https://") else f"https://{host}"
36
+ return host if host.startswith("http://") else f"http://{host}"
37
+
38
+
39
+ def _test_connection(host: str, use_ipv4: bool) -> ConnectionResult:
40
+ # Function static cache
41
+ host = _sanitize_host(host)
42
+ transport = httpx.HTTPTransport(local_address="0.0.0.0") if use_ipv4 else None
43
+ result: ConnectionResult | None = None
44
+ try:
45
+ with httpx.Client(
46
+ timeout=_TIMEOUT,
47
+ transport=transport,
48
+ ) as test_client:
49
+ test_response = test_client.get(
50
+ f"{host}/healthz", timeout=3, follow_redirects=True
51
+ )
52
+ result = ConnectionResult(host, test_response.status_code == 200, use_ipv4)
53
+ except KeyboardInterrupt:
54
+ _thread.interrupt_main()
55
+ result = ConnectionResult(host, False, use_ipv4)
56
+ except TimeoutError:
57
+ result = ConnectionResult(host, False, use_ipv4)
58
+ except Exception:
59
+ result = ConnectionResult(host, False, use_ipv4)
60
+ return result
61
+
62
+
63
+ def find_good_connection(
64
+ urls: list[str], filter_out_bad=True, use_ipv6: bool = True
65
+ ) -> ConnectionResult | None:
66
+ # Create cache key from parameters
67
+ cache_key = (tuple(sorted(urls)), filter_out_bad, use_ipv6)
68
+ current_time = time.time()
69
+
70
+ # Check if we have a cached result
71
+ if cache_key in _CONNECTION_CACHE:
72
+ cached_result, cached_time = _CONNECTION_CACHE[cache_key]
73
+ if current_time - cached_time < _CACHE_TTL:
74
+ return cached_result
75
+ else:
76
+ # Remove expired cache entry
77
+ del _CONNECTION_CACHE[cache_key]
78
+
79
+ # No valid cache entry, perform the actual connection test
80
+ futures: list[Future] = []
81
+ for url in urls:
82
+
83
+ f = _EXECUTOR.submit(_test_connection, url, use_ipv4=True)
84
+ futures.append(f)
85
+ if use_ipv6 and "localhost" not in url:
86
+ f_v6 = _EXECUTOR.submit(_test_connection, url, use_ipv4=False)
87
+ futures.append(f_v6)
88
+
89
+ result = None
90
+ try:
91
+ # Return first successful result
92
+ for future in as_completed(futures):
93
+ connection_result: ConnectionResult = future.result()
94
+ if connection_result.success or not filter_out_bad:
95
+ result = connection_result
96
+ break
97
+ finally:
98
+ # Cancel any remaining futures
99
+ for future in futures:
100
+ future.cancel()
101
+
102
+ # Cache the result (even if None)
103
+ _CONNECTION_CACHE[cache_key] = (result, current_time)
104
+
105
+ return result
@@ -79,8 +79,83 @@ class PlaywrightBrowser:
79
79
  )
80
80
 
81
81
  if self.page is None and self.browser is not None:
82
- # Create a new browser context and page
83
- context = await self.browser.new_context()
82
+ # Detect system device scale factor to match normal browser behavior
83
+ import platform
84
+
85
+ device_scale_factor = 1.0
86
+
87
+ # Try to detect system display scaling
88
+ try:
89
+ if platform.system() == "Windows":
90
+ # On Windows, try to get the DPI scaling factor
91
+ import ctypes
92
+
93
+ try:
94
+ # Get DPI awareness and scale factor
95
+ user32 = ctypes.windll.user32
96
+ user32.SetProcessDPIAware()
97
+ dc = user32.GetDC(0)
98
+ dpi = ctypes.windll.gdi32.GetDeviceCaps(dc, 88) # LOGPIXELSX
99
+ user32.ReleaseDC(0, dc)
100
+ device_scale_factor = dpi / 96.0 # 96 DPI is 100% scaling
101
+ except Exception:
102
+ # Fallback: try alternative method
103
+ try:
104
+ import tkinter as tk
105
+
106
+ root = tk.Tk()
107
+ device_scale_factor = root.winfo_fpixels("1i") / 96.0
108
+ root.destroy()
109
+ except Exception:
110
+ pass
111
+ elif platform.system() == "Darwin": # macOS
112
+ # On macOS, try to get the display scaling
113
+ try:
114
+ import subprocess
115
+
116
+ result = subprocess.run(
117
+ ["system_profiler", "SPDisplaysDataType"],
118
+ capture_output=True,
119
+ text=True,
120
+ timeout=5,
121
+ )
122
+ if "Retina" in result.stdout or "2x" in result.stdout:
123
+ device_scale_factor = 2.0
124
+ elif "3x" in result.stdout:
125
+ device_scale_factor = 3.0
126
+ except Exception:
127
+ pass
128
+ elif platform.system() == "Linux":
129
+ # On Linux, try to get display scaling from environment or system
130
+ try:
131
+ import os
132
+
133
+ # Try GDK scaling first
134
+ gdk_scale = os.environ.get("GDK_SCALE")
135
+ if gdk_scale:
136
+ device_scale_factor = float(gdk_scale)
137
+ else:
138
+ # Try QT scaling
139
+ qt_scale = os.environ.get("QT_SCALE_FACTOR")
140
+ if qt_scale:
141
+ device_scale_factor = float(qt_scale)
142
+ except Exception:
143
+ pass
144
+ except Exception:
145
+ # If all detection methods fail, default to 1.0
146
+ device_scale_factor = 1.0
147
+
148
+ # Ensure device scale factor is reasonable (between 0.5 and 4.0)
149
+ device_scale_factor = max(0.5, min(4.0, device_scale_factor))
150
+
151
+ print(f"[PYTHON] Detected device scale factor: {device_scale_factor}")
152
+
153
+ # Create a new browser context with proper device scale factor
154
+ context = await self.browser.new_context(
155
+ device_scale_factor=device_scale_factor,
156
+ # Also ensure viewport scaling is handled properly
157
+ viewport={"width": 1280, "height": 720} if not self.headless else None,
158
+ )
84
159
  self.page = await context.new_page()
85
160
 
86
161
  async def open_url(self, url: str) -> None:
@@ -265,6 +265,7 @@ INDEX_TEMPLATE = """<!DOCTYPE html>
265
265
  checkmark.style.color = '#E0E0E0';
266
266
  link.appendChild(checkmark);
267
267
  }});
268
+
268
269
  // Now load first example and show its checkmark
269
270
  if (links.length > 0) {{
270
271
  // Try to find SdCard example first
@@ -273,6 +274,7 @@ INDEX_TEMPLATE = """<!DOCTYPE html>
273
274
  startLink.classList.add('active');
274
275
  startLink.querySelector('.fa-check').style.display = 'inline-block';
275
276
  }}
277
+
276
278
  // Add click handlers
277
279
  links.forEach(link => {{
278
280
  link.addEventListener('click', function(e) {{
@@ -311,10 +313,11 @@ INDEX_TEMPLATE = """<!DOCTYPE html>
311
313
  showNav();
312
314
  }}
313
315
  }});
316
+
314
317
  // Close menu when clicking anywhere in the document
315
318
  document.addEventListener('click', (e) => {{
316
- if (navPane.classList.contains('visible') &&
317
- !navPane.contains(e.target) &&
319
+ if (navPane.classList.contains('visible') &&
320
+ !navPane.contains(e.target) &&
318
321
  !navTrigger.contains(e.target)) {{
319
322
  hideNav();
320
323
  }}