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.
- haybale_protokol-0.0.3/.gitignore +42 -0
- haybale_protokol-0.0.3/PKG-INFO +11 -0
- haybale_protokol-0.0.3/README.md +108 -0
- haybale_protokol-0.0.3/haybale_protokol/NOTES.md +75 -0
- haybale_protokol-0.0.3/haybale_protokol/OVERVIEW.md +18 -0
- haybale_protokol-0.0.3/haybale_protokol/QUICKREF.md +16 -0
- haybale_protokol-0.0.3/haybale_protokol/__init__.py +103 -0
- haybale_protokol-0.0.3/haybale_protokol/adapters/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/devices.py +82 -0
- haybale_protokol-0.0.3/haybale_protokol/dispatch.py +70 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.editor.DeviceEditor.md +21 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.editor.GatesEditor.md +20 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.editor.MonitorEditor.md +20 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.setting.ProtokolSettings.md +9 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.state.ProtokolSelection.md +9 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.state.ProtokolState.md +12 -0
- haybale_protokol-0.0.3/haybale_protokol/docs/haybale-protokol.widget.GateSelectWidget.md +26 -0
- haybale_protokol-0.0.3/haybale_protokol/drivers.py +142 -0
- haybale_protokol-0.0.3/haybale_protokol/editor_layout.py +37 -0
- haybale_protokol-0.0.3/haybale_protokol/editors/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/editors/device_editor.py +303 -0
- haybale_protokol-0.0.3/haybale_protokol/editors/gates_editor.py +265 -0
- haybale_protokol-0.0.3/haybale_protokol/editors/monitor_editor.py +607 -0
- haybale_protokol-0.0.3/haybale_protokol/gate_form.py +155 -0
- haybale_protokol-0.0.3/haybale_protokol/gate_names.py +25 -0
- haybale_protokol-0.0.3/haybale_protokol/gate_status.py +24 -0
- haybale_protokol-0.0.3/haybale_protokol/gates.py +172 -0
- haybale_protokol-0.0.3/haybale_protokol/haybale.toml +48 -0
- haybale_protokol-0.0.3/haybale_protokol/icons.py +23 -0
- haybale_protokol-0.0.3/haybale_protokol/line.py +203 -0
- haybale_protokol-0.0.3/haybale_protokol/macros/.gitkeep +0 -0
- haybale_protokol-0.0.3/haybale_protokol/monitor_export.py +69 -0
- haybale_protokol-0.0.3/haybale_protokol/nodes/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/panels/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/receive.py +125 -0
- haybale_protokol-0.0.3/haybale_protokol/settings/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/settings/protokol_settings.py +20 -0
- haybale_protokol-0.0.3/haybale_protokol/signals.py +18 -0
- haybale_protokol-0.0.3/haybale_protokol/skins/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/states/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/states/protokol_selection.py +45 -0
- haybale_protokol-0.0.3/haybale_protokol/states/protokol_state.py +397 -0
- haybale_protokol-0.0.3/haybale_protokol/taps.py +76 -0
- haybale_protokol-0.0.3/haybale_protokol/themes/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/types/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/widgets/__init__.py +0 -0
- haybale_protokol-0.0.3/haybale_protokol/widgets/gate_select_widget.py +159 -0
- 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
|
|
File without changes
|
|
@@ -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,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.
|