leaf-framework 0.9.4__tar.gz → 0.9.6__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 (104) hide show
  1. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/PKG-INFO +1 -1
  2. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/desktop_launcher.py +62 -5
  3. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/pyproject.toml +1 -1
  4. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/LICENSE +0 -0
  5. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/README.md +0 -0
  6. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/__init__.py +0 -0
  7. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/adapters/__init__.py +0 -0
  8. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/adapters/core_adapters/__init__.py +0 -0
  9. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/adapters/core_adapters/continuous_experiment_adapter.py +0 -0
  10. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/adapters/core_adapters/discrete_experiment_adapter.py +0 -0
  11. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/adapters/core_adapters/upload_adapter.py +0 -0
  12. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/adapters/equipment_adapter.py +0 -0
  13. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/config/configuration.yaml +0 -0
  14. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/data/marketplace.yaml +0 -0
  15. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/error_handler/__init__.py +0 -0
  16. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/error_handler/error_holder.py +0 -0
  17. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/error_handler/exceptions.py +0 -0
  18. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/measurement_handler/__init__.py +0 -0
  19. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/measurement_handler/handler.py +0 -0
  20. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/measurement_handler/measurements.yaml +0 -0
  21. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/measurement_handler/terms.py +0 -0
  22. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/.gitkeep +0 -0
  23. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/__init__.py +0 -0
  24. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/__init__.py +0 -0
  25. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/ble_watcher.py +0 -0
  26. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/db_watcher.py +0 -0
  27. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/event_watcher.py +0 -0
  28. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/external_api_watcher.py +0 -0
  29. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/external_event_watcher.py +0 -0
  30. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/file_watcher.py +0 -0
  31. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/http_watcher.py +0 -0
  32. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/modbus_watcher.py +0 -0
  33. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/mqtt_external_event_watcher.py +0 -0
  34. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/mqtt_watcher.py +0 -0
  35. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/opc_watcher.py +0 -0
  36. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/polling_watcher.py +0 -0
  37. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/input_modules/simple_watcher.py +0 -0
  38. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/__init__.py +0 -0
  39. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/carbon_dioxide.py +0 -0
  40. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/dissolved_oxygen.py +0 -0
  41. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/fluorescence.py +0 -0
  42. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/measurement_module.py +0 -0
  43. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/optical_density.py +0 -0
  44. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/ph.py +0 -0
  45. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/measurement_modules/temperature.py +0 -0
  46. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/__init__.py +0 -0
  47. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/file.py +0 -0
  48. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/keydb.py +0 -0
  49. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/mqtt.py +0 -0
  50. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/output_module.py +0 -0
  51. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/sql.py +0 -0
  52. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/output_modules/topic_store.py +0 -0
  53. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/__init__.py +0 -0
  54. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/control.py +0 -0
  55. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/error.py +0 -0
  56. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/external_event_phase.py +0 -0
  57. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/initialisation.py +0 -0
  58. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/measure.py +0 -0
  59. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/phase.py +0 -0
  60. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/start.py +0 -0
  61. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/phase_modules/stop.py +0 -0
  62. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/process_modules/__init__.py +0 -0
  63. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/process_modules/continous_module.py +0 -0
  64. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/process_modules/discrete_module.py +0 -0
  65. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/process_modules/external_event_process.py +0 -0
  66. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/process_modules/process_module.py +0 -0
  67. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/modules/process_modules/upload_module.py +0 -0
  68. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/__init__.py +0 -0
  69. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/abstract_leaf.py +0 -0
  70. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/equipment_actions.yaml +0 -0
  71. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/equipment_data.yaml +0 -0
  72. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/metadata.py +0 -0
  73. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/py.typed +0 -0
  74. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/register/topic_utilities.py +0 -0
  75. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/registry/discovery.py +0 -0
  76. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/registry/loader.py +0 -0
  77. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/registry/registry.py +0 -0
  78. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/registry/utils.py +0 -0
  79. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/start.py +0 -0
  80. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/__init__.py +0 -0
  81. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/adapter_ui_extension.py +0 -0
  82. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/components/__init__.py +0 -0
  83. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/components/header.py +0 -0
  84. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/constants.py +0 -0
  85. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/images/icon.icns +0 -0
  86. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/images/icon.ico +0 -0
  87. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/images/icon.svg +0 -0
  88. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/interface.py +0 -0
  89. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/markdown/configuration_help.md +0 -0
  90. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/markdown/documentation_main.md +0 -0
  91. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/markdown/documentation_protips.md +0 -0
  92. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/scripts.js +0 -0
  93. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/styles.css +0 -0
  94. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/tabs/__init__.py +0 -0
  95. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/tabs/adapters.py +0 -0
  96. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/tabs/configuration.py +0 -0
  97. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/tabs/documentation.py +0 -0
  98. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/tabs/logs.py +0 -0
  99. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/tabs/monitoring.py +0 -0
  100. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/ui/utils.py +0 -0
  101. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/utility/config_utils.py +0 -0
  102. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/utility/logger/__init__.py +0 -0
  103. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/utility/logger/logger_utils.py +0 -0
  104. {leaf_framework-0.9.4 → leaf_framework-0.9.6}/leaf/utility/running_utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: leaf-framework
3
- Version: 0.9.4
3
+ Version: 0.9.6
4
4
  Summary: Lightweight Adapter Framework (LEAF)
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -12,6 +12,28 @@ from leaf.utility.logger.logger_utils import get_logger
12
12
 
13
13
  logger = get_logger(__name__, log_file="desktop_launcher.log")
14
14
 
15
+ # Works around a libXcursor/Xlib bug that aborts the whole process on Linux,
16
+ # the first time a tk.Button is created: ButtonCreate -> TkButtonWorldChanged
17
+ # -> Tk_GetBitmap -> ... -> libXcursor's XcursorSupportsARGB ->
18
+ # XRenderQueryExtension -> XQueryExtension -> Xlib's _XSend, which hits
19
+ # `assert(!xcb_xlib_unknown_seq_number)` and aborts. Root-caused via a core
20
+ # dump (gdb, `thread apply all bt`) on a genuine Debian/Xorg reproduction --
21
+ # NOT a threading race (every other thread was idle, blocked on condition
22
+ # variables); the single main thread aborts inside libXcursor's lazy
23
+ # ARGB-cursor/display-info detection, which corrupts Xlib's XCB
24
+ # sequence-number tracking. XLIB_SKIP_ARGB_VISUALS=1 is libXcursor's own
25
+ # documented escape hatch for exactly this, and was confirmed to fix it:
26
+ # without it, LEAF aborted within ~1s of "Starting desktop control panel..."
27
+ # on every run; with it, the full GUI lifecycle (button creation, 40s+
28
+ # runtime, clean shutdown) ran with no crash, in the same environment.
29
+ #
30
+ # Must be set here, before tkinter is ever imported below -- libXcursor
31
+ # reads this once via a load-time constructor and caches it, so setting it
32
+ # via os.environ any later (even right before the first tk.Tk()) is too
33
+ # late and has no effect. Confirmed by direct reproduction.
34
+ if sys.platform.startswith("linux"):
35
+ os.environ.setdefault("XLIB_SKIP_ARGB_VISUALS", "1")
36
+
15
37
  # Check if tkinter is available
16
38
  try:
17
39
  import tkinter as tk
@@ -23,6 +45,28 @@ except ImportError:
23
45
  logger.warning("tkinter not available - desktop GUI will not be available")
24
46
 
25
47
 
48
+ def _init_x11_threads() -> None:
49
+ """Tell Xlib this process is multi-threaded, before it ever touches X11.
50
+
51
+ LEAF runs several other threads by the time this GUI starts (the
52
+ NiceGUI web server thread, adapter/simulation threads -- see start.py).
53
+ None of them were shown to touch Tk/X11 in the libXcursor crash
54
+ reproduction above (see the XLIB_SKIP_ARGB_VISUALS comment near the top
55
+ of this file for that actual root cause), but Xlib documents
56
+ XInitThreads() as required before its first use in any multi-threaded
57
+ process regardless of which thread issues X calls, so there's no reason
58
+ to skip it. No equivalent exists on macOS (Cocoa) or Windows.
59
+ """
60
+ if not sys.platform.startswith("linux"):
61
+ return
62
+ try:
63
+ import ctypes
64
+
65
+ ctypes.CDLL("libX11.so.6").XInitThreads()
66
+ except Exception as e:
67
+ logger.debug(f"Could not call XInitThreads: {e}")
68
+
69
+
26
70
  def is_desktop_environment() -> bool:
27
71
  """
28
72
  Detect if we're running in a desktop environment.
@@ -82,6 +126,7 @@ class LeafDesktopLauncher:
82
126
  self.is_running = True
83
127
 
84
128
  # Create main window
129
+ _init_x11_threads()
85
130
  self.root = tk.Tk()
86
131
  self.root.title("LEAF - Lightweight Equipment Adapter Framework")
87
132
  self.root.geometry("500x350")
@@ -281,6 +326,7 @@ class LeafDesktopLauncher:
281
326
 
282
327
  # Call the stop callback in a separate thread
283
328
  if self.on_stop_callback:
329
+ stop_finished = threading.Event()
284
330
 
285
331
  def stop_thread():
286
332
  try:
@@ -288,13 +334,24 @@ class LeafDesktopLauncher:
288
334
  except Exception as e:
289
335
  logger.error(f"Error in stop callback: {e}")
290
336
  finally:
291
- # Close the window after stopping
292
- try:
293
- self.root.after(1000, self.root.destroy)
294
- except Exception:
295
- pass
337
+ stop_finished.set()
296
338
 
297
339
  threading.Thread(target=stop_thread, daemon=True).start()
340
+
341
+ # Tk/Tcl calls (root.after, root.destroy) must only ever happen
342
+ # on the main thread that's running mainloop() -- on Linux,
343
+ # Xlib can abort the whole process ("XInitThreads has not been
344
+ # called") if a background thread issues X11 calls concurrently
345
+ # with it. Polling here (itself scheduled via root.after, so it
346
+ # always runs on the main thread) instead of destroying directly
347
+ # from stop_thread keeps every Tk call on that one thread.
348
+ def wait_and_destroy():
349
+ if stop_finished.is_set():
350
+ self.root.destroy()
351
+ else:
352
+ self.root.after(100, wait_and_destroy)
353
+
354
+ self.root.after(1000, wait_and_destroy)
298
355
  else:
299
356
  # If no callback, just close
300
357
  self.root.after(500, self.root.destroy)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "leaf-framework"
3
- version = "0.9.4"
3
+ version = "0.9.6"
4
4
  description = "Lightweight Adapter Framework (LEAF)"
5
5
  authors = [
6
6
  "Matthew Crowther <nmc215@ncl.ac.uk>",
File without changes
File without changes