haybale-protokol 0.0.3__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 (48) hide show
  1. haybale_protokol-0.0.3/.gitignore +42 -0
  2. haybale_protokol-0.0.3/PKG-INFO +11 -0
  3. haybale_protokol-0.0.3/README.md +108 -0
  4. haybale_protokol-0.0.3/haybale_protokol/NOTES.md +75 -0
  5. haybale_protokol-0.0.3/haybale_protokol/OVERVIEW.md +18 -0
  6. haybale_protokol-0.0.3/haybale_protokol/QUICKREF.md +16 -0
  7. haybale_protokol-0.0.3/haybale_protokol/__init__.py +103 -0
  8. haybale_protokol-0.0.3/haybale_protokol/adapters/__init__.py +0 -0
  9. haybale_protokol-0.0.3/haybale_protokol/devices.py +82 -0
  10. haybale_protokol-0.0.3/haybale_protokol/dispatch.py +70 -0
  11. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.editor.DeviceEditor.md +21 -0
  12. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.editor.GatesEditor.md +20 -0
  13. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.editor.MonitorEditor.md +20 -0
  14. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.setting.ProtokolSettings.md +9 -0
  15. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.state.ProtokolSelection.md +9 -0
  16. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.state.ProtokolState.md +12 -0
  17. haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.widget.GateSelectWidget.md +26 -0
  18. haybale_protokol-0.0.3/haybale_protokol/drivers.py +142 -0
  19. haybale_protokol-0.0.3/haybale_protokol/editor_layout.py +37 -0
  20. haybale_protokol-0.0.3/haybale_protokol/editors/__init__.py +0 -0
  21. haybale_protokol-0.0.3/haybale_protokol/editors/device_editor.py +303 -0
  22. haybale_protokol-0.0.3/haybale_protokol/editors/gates_editor.py +265 -0
  23. haybale_protokol-0.0.3/haybale_protokol/editors/monitor_editor.py +607 -0
  24. haybale_protokol-0.0.3/haybale_protokol/gate_form.py +155 -0
  25. haybale_protokol-0.0.3/haybale_protokol/gate_names.py +25 -0
  26. haybale_protokol-0.0.3/haybale_protokol/gate_status.py +24 -0
  27. haybale_protokol-0.0.3/haybale_protokol/gates.py +172 -0
  28. haybale_protokol-0.0.3/haybale_protokol/haybale.toml +48 -0
  29. haybale_protokol-0.0.3/haybale_protokol/icons.py +23 -0
  30. haybale_protokol-0.0.3/haybale_protokol/line.py +203 -0
  31. haybale_protokol-0.0.3/haybale_protokol/macros/.gitkeep +0 -0
  32. haybale_protokol-0.0.3/haybale_protokol/monitor_export.py +69 -0
  33. haybale_protokol-0.0.3/haybale_protokol/nodes/__init__.py +0 -0
  34. haybale_protokol-0.0.3/haybale_protokol/panels/__init__.py +0 -0
  35. haybale_protokol-0.0.3/haybale_protokol/receive.py +125 -0
  36. haybale_protokol-0.0.3/haybale_protokol/settings/__init__.py +0 -0
  37. haybale_protokol-0.0.3/haybale_protokol/settings/protokol_settings.py +20 -0
  38. haybale_protokol-0.0.3/haybale_protokol/signals.py +18 -0
  39. haybale_protokol-0.0.3/haybale_protokol/skins/__init__.py +0 -0
  40. haybale_protokol-0.0.3/haybale_protokol/states/__init__.py +0 -0
  41. haybale_protokol-0.0.3/haybale_protokol/states/protokol_selection.py +45 -0
  42. haybale_protokol-0.0.3/haybale_protokol/states/protokol_state.py +397 -0
  43. haybale_protokol-0.0.3/haybale_protokol/taps.py +76 -0
  44. haybale_protokol-0.0.3/haybale_protokol/themes/__init__.py +0 -0
  45. haybale_protokol-0.0.3/haybale_protokol/types/__init__.py +0 -0
  46. haybale_protokol-0.0.3/haybale_protokol/widgets/__init__.py +0 -0
  47. haybale_protokol-0.0.3/haybale_protokol/widgets/gate_select_widget.py +159 -0
  48. haybale_protokol-0.0.3/pyproject.toml +33 -0
@@ -0,0 +1,42 @@
1
+ # Patterns below are anchored with a leading slash (/build/) so they match only
2
+ # at the repo root. An unanchored pattern (build/) matches at EVERY depth —
3
+ # including inside barn/, where it would silently exclude your library's own
4
+ # files. See the note at the end of this file before adding patterns.
5
+
6
+ # Byte-compiled / optimized / DLL files
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+
11
+ # Distribution / packaging (root only)
12
+ /build/
13
+ /dist/
14
+ *.egg-info/
15
+ *.egg
16
+
17
+ # Virtual environments (root only)
18
+ /.venv/
19
+ /venv/
20
+ /env/
21
+
22
+ # Test / type-check caches
23
+ .pytest_cache/
24
+ .ruff_cache/
25
+ .mypy_cache/
26
+ .tox/
27
+ .coverage
28
+ htmlcov/
29
+
30
+ # Editor / OS
31
+ .DS_Store
32
+ .idea/
33
+ .vscode/
34
+
35
+ # Haywire per-session UI state (open graph, pan/zoom) — not project state
36
+ .haywire/workspace_state.json
37
+
38
+ # ── Before you add a pattern ────────────────────────────────────────────────
39
+ # Anything ignored inside barn/<your-library>/ will be MISSING for everyone who
40
+ # installs your library — haywire share publishes by git URL, so consumers get
41
+ # a clone of this repo, not a built package. If a pattern is only meant for the
42
+ # repo root, anchor it with a leading slash: /build/ not build/.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.5
2
+ Name: haybale-protokol
3
+ Version: 0.0.3
4
+ Summary: Control protocols for graphs: gates, shared lines and the base for OSC, MIDI and gamepad
5
+ Project-URL: Source, https://github.com/going-haywire/haybale-protokol
6
+ License: MIT
7
+ Keywords: control,gamepad,midi,osc,protocol
8
+ Requires-Python: >=3.12
9
+ Requires-Dist: haywire-core>=0.5.0
10
+ Requires-Dist: nicegui
11
+ Requires-Dist: tomlkit
@@ -0,0 +1,108 @@
1
+ # Protokol
2
+
3
+ # Protokol
4
+
5
+ Control protocols for Haywire graphs: OSC now, then MIDI and gamepads, each added by its own
6
+ protocol haybale (`haybale-protokol-osc`, ...). This haybale is the shared base. It has no
7
+ nodes of its own.
8
+
9
+ ## Gates
10
+
11
+ A graph refers to a **Gate** by name only. The project's Gate file,
12
+ `.haywire/protokol.toml`, is committed with the project and defines each gate:
13
+
14
+ ```toml
15
+ [gates.touchosc-in]
16
+ protocol = "osc"
17
+ direction = "in" # in, out, or two-way where the protocol has one shared channel
18
+ transport = "udp"
19
+ port = 8000
20
+ bind = "0.0.0.0" # default: all interfaces
21
+
22
+ [gates.lights-out]
23
+ protocol = "osc"
24
+ direction = "out"
25
+ port = 9000
26
+ host = "10.0.0.2"
27
+
28
+ [gates.desk] # OSC over TCP: we connect to the peer
29
+ protocol = "osc"
30
+ direction = "two-way" # TCP has one shared channel, so a gate may go both ways
31
+ transport = "tcp"
32
+ role = "client" # or "server": peers connect to us (on bind, default all interfaces)
33
+ host = "10.0.0.5"
34
+ port = 3032
35
+ framing = "1.1" # "1.0" length prefix, or "1.1" SLIP (default)
36
+ ```
37
+
38
+ MIDI and gamepad gates name a **Device** (`device = "desk"`). The machine's Device list,
39
+ `~/.haywire/protokol-devices.toml`, maps that name to what the OS calls the hardware.
40
+
41
+ ## Lines
42
+
43
+ Gates that resolve to the same connection (the same protocol, transport, address and port)
44
+ share one **Line**, the only thing that opens a port. Two gates on one port with different
45
+ bind addresses are two Lines, and the second cannot open (`cannot open: ...`). A Line opens
46
+ with its first user and closes once the last one has left and the grace period has passed
47
+ (setting **Line grace period**, default 2 s), so a graph restart keeps its peers. Lines run on
48
+ their own threads; a slow flow never holds a Line up.
49
+
50
+ ## Editors
51
+
52
+ | Editor | Slot | What it does | Who may change things |
53
+ |---|---|---|---|
54
+ | **Gates** | left icon bar | The project's gates, with each one's Line and status. Add, edit, delete; "Bind…" for a gate whose device this machine does not know; open the Monitor on a gate. | Edit access |
55
+ | **Monitor** | centre tab | Click a line's eye to watch it; the eye then hides or shows its messages in the output without losing them. Each watched line has its own Clear, Export (JSONL or CSV) and Stop watching; the toolbar's act on all shown lines. Filter by text, pause. Test-send to output gates. "New line…" watches a Line no gate uses yet, and "Save as gate…" keeps it. | Watching open Lines: view access. Opening Lines, test-send, saving gates: edit access |
56
+ | **Device** | right icon bar | Follows the selection: a device, a gate (its options, Line and last values) or a Line. With nothing selected, the machine's Device list. | Editing the Device list: admin access |
57
+
58
+ The Monitor never slows a Line: each watched source keeps the last 2000 messages (older ones
59
+ drop and are counted), and the output redraws at most five times a second, showing the newest
60
+ 300 messages: newest first, or oldest first like a log (the toolbar toggles it; a log follows the
61
+ newest message unless you scroll up to read). Export writes everything the filter keeps.
62
+
63
+ A gate edited while graphs use it applies when those graphs next start.
64
+
65
+ ## Status texts
66
+
67
+ Receive and Send nodes report problems on their `status` outlet; nothing crashes the graph:
68
+
69
+ | Status | Meaning |
70
+ |---|---|
71
+ | `no gate named 'x'` | The Gate file has no such gate |
72
+ | `gate file: ...` | The Gate file cannot be read |
73
+ | `protocol not installed` | No enabled haybale provides the gate's protocol |
74
+ | `unbound gate: no device 'x' on this machine` | The Device list does not know the gate's device |
75
+ | `'x' is an input gate` / `an output gate` | The node uses the gate in the wrong direction |
76
+ | `cannot open: ...` | The port or device could not be opened (for example, already in use) |
77
+ | `gate 'x': ...` | The gate's options are incomplete or wrong, or disagree with another gate on the same Line |
78
+
79
+ <!-- marketstall:share-url:start -->
80
+ Always the latest (tracks the current branch):
81
+
82
+ ```sh
83
+ https://github.com/going-haywire/haybale-protokol/blob/master/marketstall.toml
84
+ ```
85
+
86
+ Frozen to this version:
87
+
88
+ ```sh
89
+ https://github.com/going-haywire/haybale-protokol/blob/v0.0.3/marketstall.toml
90
+ ```
91
+ <!-- marketstall:share-url:end -->
92
+
93
+ Control protocols for graphs: gates, shared lines and the base for OSC, MIDI and gamepad
94
+
95
+ ## Widgets
96
+ - **GateSelectWidget** — Pick gates as chips from the gates that fit the node, each coloured by its state
97
+
98
+ ## Settings
99
+ - **Protokol** — Lines shared by gates and the Monitor
100
+
101
+ ## States
102
+ - **Protokol** — Protocol drivers, the project's gates and the Lines they share
103
+ - **Protokol selection** — The gate, Line or Device this session has selected
104
+
105
+ ## Editors
106
+ - **Device** — The device or Line in the Protokol selection
107
+ - **Gates** — The project's gates and their Lines
108
+ - **Monitor** — Live traffic on Lines
@@ -0,0 +1,75 @@
1
+ # Protokol
2
+
3
+ Control protocols for Haywire graphs: OSC now, then MIDI and gamepads, each added by its own
4
+ protocol haybale (`haybale-protokol-osc`, ...). This haybale is the shared base. It has no
5
+ nodes of its own.
6
+
7
+ ## Gates
8
+
9
+ A graph refers to a **Gate** by name only. The project's Gate file,
10
+ `.haywire/protokol.toml`, is committed with the project and defines each gate:
11
+
12
+ ```toml
13
+ [gates.touchosc-in]
14
+ protocol = "osc"
15
+ direction = "in" # in, out, or two-way where the protocol has one shared channel
16
+ transport = "udp"
17
+ port = 8000
18
+ bind = "0.0.0.0" # default: all interfaces
19
+
20
+ [gates.lights-out]
21
+ protocol = "osc"
22
+ direction = "out"
23
+ port = 9000
24
+ host = "10.0.0.2"
25
+
26
+ [gates.desk] # OSC over TCP: we connect to the peer
27
+ protocol = "osc"
28
+ direction = "two-way" # TCP has one shared channel, so a gate may go both ways
29
+ transport = "tcp"
30
+ role = "client" # or "server": peers connect to us (on bind, default all interfaces)
31
+ host = "10.0.0.5"
32
+ port = 3032
33
+ framing = "1.1" # "1.0" length prefix, or "1.1" SLIP (default)
34
+ ```
35
+
36
+ MIDI and gamepad gates name a **Device** (`device = "desk"`). The machine's Device list,
37
+ `~/.haywire/protokol-devices.toml`, maps that name to what the OS calls the hardware.
38
+
39
+ ## Lines
40
+
41
+ Gates that resolve to the same connection (the same protocol, transport, address and port)
42
+ share one **Line**, the only thing that opens a port. Two gates on one port with different
43
+ bind addresses are two Lines, and the second cannot open (`cannot open: ...`). A Line opens
44
+ with its first user and closes once the last one has left and the grace period has passed
45
+ (setting **Line grace period**, default 2 s), so a graph restart keeps its peers. Lines run on
46
+ their own threads; a slow flow never holds a Line up.
47
+
48
+ ## Editors
49
+
50
+ | Editor | Slot | What it does | Who may change things |
51
+ |---|---|---|---|
52
+ | **Gates** | left icon bar | The project's gates, with each one's Line and status. Add, edit, delete; "Bind…" for a gate whose device this machine does not know; open the Monitor on a gate. | Edit access |
53
+ | **Monitor** | centre tab | Click a line's eye to watch it; the eye then hides or shows its messages in the output without losing them. Each watched line has its own Clear, Export (JSONL or CSV) and Stop watching; the toolbar's act on all shown lines. Filter by text, pause. Test-send to output gates. "New line…" watches a Line no gate uses yet, and "Save as gate…" keeps it. | Watching open Lines: view access. Opening Lines, test-send, saving gates: edit access |
54
+ | **Device** | right icon bar | Follows the selection: a device, a gate (its options, Line and last values) or a Line. With nothing selected, the machine's Device list. | Editing the Device list: admin access |
55
+
56
+ The Monitor never slows a Line: each watched source keeps the last 2000 messages (older ones
57
+ drop and are counted), and the output redraws at most five times a second, showing the newest
58
+ 300 messages: newest first, or oldest first like a log (the toolbar toggles it; a log follows the
59
+ newest message unless you scroll up to read). Export writes everything the filter keeps.
60
+
61
+ A gate edited while graphs use it applies when those graphs next start.
62
+
63
+ ## Status texts
64
+
65
+ Receive and Send nodes report problems on their `status` outlet; nothing crashes the graph:
66
+
67
+ | Status | Meaning |
68
+ |---|---|
69
+ | `no gate named 'x'` | The Gate file has no such gate |
70
+ | `gate file: ...` | The Gate file cannot be read |
71
+ | `protocol not installed` | No enabled haybale provides the gate's protocol |
72
+ | `unbound gate: no device 'x' on this machine` | The Device list does not know the gate's device |
73
+ | `'x' is an input gate` / `an output gate` | The node uses the gate in the wrong direction |
74
+ | `cannot open: ...` | The port or device could not be opened (for example, already in use) |
75
+ | `gate 'x': ...` | The gate's options are incomplete or wrong, or disagree with another gate on the same Line |
@@ -0,0 +1,18 @@
1
+ # Protokol
2
+
3
+ Control protocols for graphs: gates, shared lines and the base for OSC, MIDI and gamepad
4
+
5
+ ## Widgets
6
+ - **GateSelectWidget** — Pick gates as chips from the gates that fit the node, each coloured by its state
7
+
8
+ ## Settings
9
+ - **Protokol** — Lines shared by gates and the Monitor
10
+
11
+ ## States
12
+ - **Protokol** — Protocol drivers, the project's gates and the Lines they share
13
+ - **Protokol selection** — The gate, Line or Device this session has selected
14
+
15
+ ## Editors
16
+ - **Device** — The device or Line in the Protokol selection
17
+ - **Gates** — The project's gates and their Lines
18
+ - **Monitor** — Live traffic on Lines
@@ -0,0 +1,16 @@
1
+ # haybale-protokol — component index (v0.0.3)
2
+
3
+ ## widget
4
+ - `haybale-protokol:widget:GateSelectWidget` — GateSelectWidget — Pick gates as chips from the gates that fit the node, each coloured by its state
5
+
6
+ ## setting
7
+ - `haybale-protokol:setting:ProtokolSettings` — Protokol — Lines shared by gates and the Monitor
8
+
9
+ ## state
10
+ - `haybale-protokol:state:ProtokolSelection` — Protokol selection — The gate, Line or Device this session has selected
11
+ - `haybale-protokol:state:ProtokolState` — Protokol — Protocol drivers, the project's gates and the Lines they share
12
+
13
+ ## editor
14
+ - `haybale-protokol:editor:DeviceEditor` — Device — The device or Line in the Protokol selection
15
+ - `haybale-protokol:editor:GatesEditor` — Gates — The project's gates and their Lines
16
+ - `haybale-protokol:editor:MonitorEditor` — Monitor — Live traffic on Lines
@@ -0,0 +1,103 @@
1
+ """
2
+ Local haybale library for the haybale-protokol project.
3
+
4
+ Add your custom components in the corresponding folders:
5
+ - nodes/ — node definitions
6
+ - macros/ — macro documents (.hwm)
7
+ - types/ — custom data types
8
+ - widgets/ — UI widgets for data types
9
+ - skins/ — custom node skins
10
+ - adapters/ — type-to-type conversion adapters
11
+ - settings/ — library settings definitions
12
+ - states/ — library app and session states
13
+ - themes/ — workbench and node themes
14
+ - panels/ — custom UI panels
15
+ - editors/ — custom UI editors
16
+ """
17
+
18
+ from pathlib import Path
19
+
20
+ from haywire.api import (
21
+ AdapterRegistry,
22
+ BaseLibrary,
23
+ MacroRegistry,
24
+ NodeRegistry,
25
+ SettingsRegistry,
26
+ SkinRegistry,
27
+ ThemeRegistry,
28
+ TypeRegistry,
29
+ WidgetRegistry,
30
+ library,
31
+ )
32
+
33
+ from haywire.api.workbench import EditorRegistry, PanelRegistry, StateRegistry
34
+
35
+
36
+ @library(
37
+ file_watcher=True,
38
+ )
39
+ class Library(BaseLibrary):
40
+ """Local project library — add your components in the subfolders."""
41
+
42
+ def register_components(self):
43
+ """Register all components with the global registries."""
44
+ base_path = Path(__file__).parent
45
+
46
+ self.add_folder_to_registry(
47
+ folder_path=str(base_path / "settings"),
48
+ registry_cls=SettingsRegistry,
49
+ )
50
+
51
+ self.add_folder_to_registry(
52
+ folder_path=str(base_path / "states"),
53
+ registry_cls=StateRegistry,
54
+ )
55
+
56
+ self.add_folder_to_registry(
57
+ folder_path=str(base_path / "themes"),
58
+ registry_cls=ThemeRegistry,
59
+ )
60
+
61
+ self.add_folder_to_registry(
62
+ folder_path=str(base_path / "types"),
63
+ registry_cls=TypeRegistry,
64
+ )
65
+
66
+ self.add_folder_to_registry(
67
+ folder_path=str(base_path / "adapters"),
68
+ registry_cls=AdapterRegistry,
69
+ )
70
+
71
+ self.add_folder_to_registry(
72
+ folder_path=str(base_path / "widgets"),
73
+ registry_cls=WidgetRegistry,
74
+ )
75
+
76
+ self.add_folder_to_registry(
77
+ folder_path=str(base_path / "skins"),
78
+ registry_cls=SkinRegistry,
79
+ )
80
+
81
+ self.add_folder_to_registry(
82
+ folder_path=str(base_path / "nodes"),
83
+ registry_cls=NodeRegistry,
84
+ )
85
+
86
+ self.add_folder_to_registry(
87
+ folder_path=str(base_path / "macros"),
88
+ registry_cls=MacroRegistry,
89
+ )
90
+
91
+ self.add_folder_to_registry(
92
+ folder_path=str(base_path / "panels"),
93
+ registry_cls=PanelRegistry,
94
+ )
95
+
96
+ self.add_folder_to_registry(
97
+ folder_path=str(base_path / "editors"),
98
+ registry_cls=EditorRegistry,
99
+ )
100
+
101
+ def validate(self) -> bool:
102
+ """Validate library structure."""
103
+ return True
@@ -0,0 +1,82 @@
1
+ """Devices and the machine's Device list, ``~/.haywire/protokol-devices.toml``.
2
+
3
+ A Device is hardware this machine knows, under a name the user chose, mapped to what the OS
4
+ calls it. Every project on the machine shares the list; it holds nothing about network peers::
5
+
6
+ [devices.desk]
7
+ protocol = "midi"
8
+ os_id = "IAC Driver Bus 1"
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Iterable
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+
17
+ import tomlkit
18
+
19
+ from haywire.core.tomlio import plain, read_toml, write_toml # noqa: TID251 -- not in the Author API yet, reported (issue #3)
20
+
21
+
22
+ class DeviceListError(ValueError):
23
+ """The Device list cannot be read; the message names the file and, where known, the device."""
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class Device:
28
+ """A piece of hardware under the user's name for it."""
29
+
30
+ name: str
31
+ protocol: str
32
+ os_id: str
33
+ """The OS's identifier: a MIDI port name, an SDL GUID."""
34
+
35
+
36
+ def device_list_path() -> Path:
37
+ """Return where this machine's Device list lives (read at call time, so HOME may change)."""
38
+ return Path.home() / ".haywire" / "protokol-devices.toml"
39
+
40
+
41
+ def load_devices(path: Path) -> dict[str, Device]:
42
+ """Read every device in ``path``, by name; a missing file has none.
43
+
44
+ Raises:
45
+ DeviceListError: The file is not valid TOML, or a device is malformed.
46
+ """
47
+ if not path.is_file():
48
+ return {}
49
+ try:
50
+ data = plain(read_toml(path))
51
+ except (ValueError, OSError) as exc: # read_toml raises toml.TomlDecodeError, a ValueError
52
+ raise DeviceListError(f"{path}: {exc}") from exc
53
+ tables = data.get("devices", {})
54
+ if not isinstance(tables, dict):
55
+ raise DeviceListError(f"{path}: [devices] must be a table")
56
+ devices: dict[str, Device] = {}
57
+ for name, table in tables.items():
58
+ if not isinstance(table, dict):
59
+ raise DeviceListError(f"{path}: device '{name}' must be a table")
60
+ protocol, os_id = table.get("protocol"), table.get("os_id")
61
+ if not isinstance(protocol, str) or not protocol or not isinstance(os_id, str) or not os_id:
62
+ raise DeviceListError(f"{path}: device '{name}' needs a protocol and an os_id")
63
+ devices[name] = Device(name, protocol, os_id)
64
+ return devices
65
+
66
+
67
+ def save_devices(path: Path, devices: Iterable[Device]) -> None:
68
+ """Write ``devices`` to ``path`` as the whole Device list, keeping the file's comments."""
69
+ document = read_toml(path) if path.is_file() else tomlkit.document()
70
+ if "devices" not in document:
71
+ document["devices"] = tomlkit.table(is_super_table=True)
72
+ tables = document["devices"]
73
+ wanted = {device.name: device for device in devices}
74
+ for name in [name for name in tables if name not in wanted]:
75
+ del tables[name]
76
+ for name, device in wanted.items():
77
+ if name not in tables:
78
+ tables[name] = tomlkit.table()
79
+ tables[name]["protocol"] = device.protocol
80
+ tables[name]["os_id"] = device.os_id
81
+ path.parent.mkdir(parents=True, exist_ok=True)
82
+ write_toml(path, document)
@@ -0,0 +1,70 @@
1
+ """How a Receive node's flow queues the messages its Line delivers.
2
+
3
+ Stream kinds (OSC, MIDI CC, gamepad axes) default to Drop with a queue of one and fire their
4
+ Start value; event kinds (notes, buttons) default to Block and do not. A MIDI node's kind can
5
+ change, so the defaults are an "Auto" choice resolved by kind, not fixed per class.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from haywire.api import CHOICES, INT, CallbackEvent, NodeSettings, Promotable, QueueMode, setting
11
+
12
+ AUTO = "Auto (by message kind)"
13
+ BLOCK = "Block (every message)"
14
+ DROP = "Drop (newest only)"
15
+ ON = "On"
16
+ OFF = "Off"
17
+
18
+
19
+ class ReceiveDispatchSettings(NodeSettings):
20
+ """How incoming messages queue, and whether the Line cache fires at start. Applies on the next start."""
21
+
22
+ queue_mode = setting[CHOICES](
23
+ AUTO,
24
+ label="Queue Mode",
25
+ category="Dispatch",
26
+ description=(
27
+ "Block runs the flow for every message; a slow flow then falls behind, never the Line. "
28
+ "Drop keeps only the newest message. Auto uses Drop for values (faders, axes, OSC) and "
29
+ "Block for events (notes, buttons)."
30
+ ),
31
+ widget_config={"options": [AUTO, BLOCK, DROP]},
32
+ promotable=Promotable.CONFIG,
33
+ )
34
+ max_queue_size = setting[INT](
35
+ 100,
36
+ min=1,
37
+ max=1024,
38
+ label="Max Queue Size",
39
+ category="Dispatch",
40
+ description="How many messages Block holds while the flow runs behind. Drop always holds one.",
41
+ promotable=Promotable.CONFIG,
42
+ )
43
+ start_value = setting[CHOICES](
44
+ AUTO,
45
+ label="Start Value",
46
+ category="Dispatch",
47
+ description=(
48
+ "Fire once with the last message the Line saw when the graph starts. "
49
+ "Auto: on for values, off for events."
50
+ ),
51
+ widget_config={"options": [AUTO, ON, OFF]},
52
+ promotable=Promotable.CONFIG,
53
+ )
54
+
55
+
56
+ def resolve_dispatch(dispatch: ReceiveDispatchSettings, *, stream: bool) -> tuple[QueueMode, int, bool]:
57
+ """Return ``(queue mode, queue size, start value)`` for a node receiving a stream or event kind."""
58
+ mode = str(dispatch.queue_mode)
59
+ if mode == AUTO:
60
+ mode = DROP if stream else BLOCK
61
+ start = str(dispatch.start_value)
62
+ start_value = stream if start == AUTO else start == ON
63
+ if mode == DROP:
64
+ return QueueMode.DROP, 1, start_value
65
+ return QueueMode.BLOCK, int(dispatch.max_queue_size), start_value
66
+
67
+
68
+ def event_source_for(node_id: str, queue_mode: QueueMode, max_queue_size: int) -> CallbackEvent:
69
+ """The event subscription of a Receive node: a callback named after the node."""
70
+ return CallbackEvent(event_name=node_id, queue_mode=queue_mode, max_queue_size=max_queue_size)
@@ -0,0 +1,21 @@
1
+ # Device
2
+
3
+ `haybale-protokol:editor:DeviceEditor` · kind: editor
4
+
5
+ The device or Line in the Protokol selection
6
+
7
+ ## Details
8
+
9
+ - **default_slot**: `context`
10
+ - **opens**: `OpenBehavior.REQUIRED`
11
+ - **order**: `100`
12
+
13
+ ## Notes
14
+
15
+ Follows the Protokol selection.
16
+
17
+ - A device: its OS name, and whether the protocol sees it connected. With ADMIN: edit or
18
+ delete it, or map a gate's device name that this machine does not know yet.
19
+ - A gate: its options, its Line's status and users, and the Line cache.
20
+ - A Line: its key, status, users and cache.
21
+ - Nothing: the Device list.
@@ -0,0 +1,20 @@
1
+ # Gates
2
+
3
+ `haybale-protokol:editor:GatesEditor` · kind: editor
4
+
5
+ The project's gates and their Lines
6
+
7
+ ## Details
8
+
9
+ - **default_slot**: `action`
10
+ - **opens**: `OpenBehavior.REQUIRED`
11
+ - **order**: `100`
12
+
13
+ ## Notes
14
+
15
+ One row per gate: its name, protocol and direction, and below them its status.
16
+
17
+ The status says whether its Line is open (and by how many users), closed, or why the gate
18
+ cannot be used: protocol not installed, unbound device, or a malformed entry. With EDIT a
19
+ row can be edited or deleted and the header adds a gate. An unbound gate offers "Bind…",
20
+ which selects its device for the Device editor.
@@ -0,0 +1,20 @@
1
+ # Monitor
2
+
3
+ `haybale-protokol:editor:MonitorEditor` · kind: editor
4
+
5
+ Live traffic on Lines
6
+
7
+ ## Details
8
+
9
+ - **default_slot**: `edit`
10
+ - **opens**: `OpenBehavior.ON_CONTEXT`
11
+ - **order**: `100`
12
+
13
+ ## Notes
14
+
15
+ Tick a gate or an open Line to watch it; the table shows the newest messages first.
16
+
17
+ The filter matches the message text, case-insensitively. "New line…" opens a Line no gate
18
+ uses yet, and "Save as gate…" turns it into a gate. A ticked output gate gets a test-send
19
+ row: type a message in the protocol's own form (for OSC: ``/address 0.5 3 word``).
20
+ Export writes every message the filter keeps, as JSONL or CSV.
@@ -0,0 +1,9 @@
1
+ # Protokol
2
+
3
+ `haybale-protokol:setting:ProtokolSettings` · kind: setting
4
+
5
+ Lines shared by gates and the Monitor
6
+
7
+ ## Notes
8
+
9
+ How long a Line stays open after its last user leaves.
@@ -0,0 +1,9 @@
1
+ # Protokol selection
2
+
3
+ `haybale-protokol:state:ProtokolSelection` · kind: state
4
+
5
+ The gate, Line or Device this session has selected
6
+
7
+ ## Notes
8
+
9
+ What the Device editor follows. At most one of the three is set.
@@ -0,0 +1,12 @@
1
+ # Protokol
2
+
3
+ `haybale-protokol:state:ProtokolState` · kind: state
4
+
5
+ Protocol drivers, the project's gates and the Lines they share
6
+
7
+ ## Notes
8
+
9
+ Owns the Protocol drivers and the open Lines; reads the Gate file and the Device list.
10
+
11
+ One per app, shared by every session and by execution. Lines run on their own threads, so
12
+ every change is published as a payload-free ``ProtokolChanged`` scheduled onto the loop.
@@ -0,0 +1,26 @@
1
+ # GateSelectWidget
2
+
3
+ `haybale-protokol:widget:GateSelectWidget` · kind: widget
4
+
5
+ Pick gates as chips from the gates that fit the node, each coloured by its state
6
+
7
+ ## Details
8
+
9
+
10
+ ## Notes
11
+
12
+ A select of gates; its behaviour is set by plain properties, as the port saves them.
13
+
14
+ Each chosen gate shows as a chip coloured by its state: success when usable, warning when this
15
+ machine cannot use it now (protocol not installed, device unbound), danger when there is no such
16
+ gate or it is of another protocol or direction. The select takes input only in edit mode.
17
+
18
+ Properties (``GateSelectWidget.config(properties={...})``):
19
+
20
+ - ``protocol``: the gates to offer and check, e.g. ``"osc"``.
21
+ - ``direction``: ``"in"`` or ``"out"``; two-way gates fit both.
22
+ - ``multiple`` (default ``True``): several gates, stored as ``"a, b"``; ``False`` for exactly one.
23
+ - ``typed`` (default ``True``): a name typed and confirmed with Enter is kept even when no such
24
+ gate exists yet.
25
+
26
+ The offered gates are read from the project each time the widget is drawn, so new gates appear.