plexi-sdk 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plexi_sdk/__init__.py +477 -0
- plexi_sdk/_app.py +1077 -0
- plexi_sdk/_constants.py +52 -0
- plexi_sdk/_emitter.py +1466 -0
- plexi_sdk/_pipe.py +92 -0
- plexi_sdk/_protocol.py +48 -0
- plexi_sdk/_render_context.py +976 -0
- plexi_sdk/_types.py +139 -0
- plexi_sdk/midi.py +222 -0
- plexi_sdk/py.typed +1 -0
- plexi_sdk/templates/__init__.py +0 -0
- plexi_sdk/templates/app_init.py +72 -0
- plexi_sdk/testing.py +451 -0
- plexi_sdk/ui.py +1535 -0
- plexi_sdk/widgets/__init__.py +27 -0
- plexi_sdk/widgets/button.py +60 -0
- plexi_sdk/widgets/keymap.py +51 -0
- plexi_sdk/widgets/list_view.py +159 -0
- plexi_sdk/widgets/scroll.py +100 -0
- plexi_sdk/widgets/text_area.py +218 -0
- plexi_sdk/widgets/text_buffer.py +337 -0
- plexi_sdk/widgets/text_input.py +70 -0
- plexi_sdk-0.4.0.dist-info/METADATA +127 -0
- plexi_sdk-0.4.0.dist-info/RECORD +25 -0
- plexi_sdk-0.4.0.dist-info/WHEEL +4 -0
plexi_sdk/_pipe.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
import struct
|
|
5
|
+
import threading
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
from ._emitter import _emit
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from ._app import App
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ── Pipe ──────────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
class Pipe:
|
|
17
|
+
"""Handle for a typed pipe. For binary mode, call connect() after PipeOpened."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, pipe_id: str, mode: str, direction: str, app: "App"):
|
|
20
|
+
self.pipe_id = pipe_id
|
|
21
|
+
self.mode = mode
|
|
22
|
+
self.direction = direction
|
|
23
|
+
self._app = app
|
|
24
|
+
self._sock: "socket.socket | None" = None
|
|
25
|
+
self._connected = threading.Event()
|
|
26
|
+
|
|
27
|
+
def _on_opened(self, socket_path: str) -> None:
|
|
28
|
+
"""Called by App when PipeOpened arrives for this pipe_id."""
|
|
29
|
+
if self.mode == "binary":
|
|
30
|
+
try:
|
|
31
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
32
|
+
sock.connect(socket_path)
|
|
33
|
+
self._sock = sock
|
|
34
|
+
self._connected.set()
|
|
35
|
+
except OSError as e:
|
|
36
|
+
self._app.emit.error(f"pipe_open failed pipe_id={self.pipe_id}: {e}")
|
|
37
|
+
|
|
38
|
+
def connect(self, timeout: float = 5.0) -> bool:
|
|
39
|
+
"""Wait for the pipe to be connected. Returns True on success."""
|
|
40
|
+
return self._connected.wait(timeout=timeout)
|
|
41
|
+
|
|
42
|
+
def read_frame(self) -> "bytes | None":
|
|
43
|
+
"""Read one length-prefixed frame. Blocks. Returns None on EOF/error/EOS."""
|
|
44
|
+
if not self._sock:
|
|
45
|
+
return None
|
|
46
|
+
try:
|
|
47
|
+
header = self._recv_exact(4)
|
|
48
|
+
if header is None:
|
|
49
|
+
return None
|
|
50
|
+
length = struct.unpack(">I", header)[0]
|
|
51
|
+
if length == 0:
|
|
52
|
+
return None # EOS sentinel from host
|
|
53
|
+
return self._recv_exact(length)
|
|
54
|
+
except OSError as e:
|
|
55
|
+
self._app.emit.error(f"pipe read_frame error pipe_id={self.pipe_id}: {e}")
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def write_frame(self, data: bytes) -> None:
|
|
59
|
+
"""Write one length-prefixed frame."""
|
|
60
|
+
if not self._sock:
|
|
61
|
+
return
|
|
62
|
+
try:
|
|
63
|
+
header = struct.pack(">I", len(data))
|
|
64
|
+
self._sock.sendall(header + data)
|
|
65
|
+
except OSError as e:
|
|
66
|
+
self._app.emit.error(f"pipe write_frame error pipe_id={self.pipe_id}: {e}")
|
|
67
|
+
|
|
68
|
+
def send(self, payload: Any) -> None:
|
|
69
|
+
"""JSON-mode pipe send."""
|
|
70
|
+
_emit({"type": "pipe_send", "pipe_id": self.pipe_id, "payload": payload})
|
|
71
|
+
|
|
72
|
+
def _recv_exact(self, n: int) -> "bytes | None":
|
|
73
|
+
buf = b""
|
|
74
|
+
while len(buf) < n:
|
|
75
|
+
chunk = self._sock.recv(n - len(buf)) # type: ignore[union-attr]
|
|
76
|
+
if not chunk:
|
|
77
|
+
return None
|
|
78
|
+
buf += chunk
|
|
79
|
+
return buf
|
|
80
|
+
|
|
81
|
+
def close(self) -> None:
|
|
82
|
+
if self._sock:
|
|
83
|
+
try:
|
|
84
|
+
# shutdown unblocks any thread blocked in recv() before close().
|
|
85
|
+
self._sock.shutdown(socket.SHUT_RDWR)
|
|
86
|
+
except OSError:
|
|
87
|
+
pass
|
|
88
|
+
try:
|
|
89
|
+
self._sock.close()
|
|
90
|
+
except OSError:
|
|
91
|
+
pass
|
|
92
|
+
self._sock = None
|
plexi_sdk/_protocol.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# AUTO-GENERATED — do not edit by hand.
|
|
2
|
+
# Regenerate with: just gen-schema
|
|
3
|
+
# Protocol: pgap/3
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
PROTOCOL_VERSION = "pgap/3"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class AiResponse:
|
|
14
|
+
"""Result of Emitter.ai_query. tokens_in/tokens_out are zero on error."""
|
|
15
|
+
tokens_in: int
|
|
16
|
+
tokens_out: int
|
|
17
|
+
content: Optional[str] = None
|
|
18
|
+
error: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class MidiPortInfo:
|
|
23
|
+
"""One MIDI port. Mirrors MidiPortWire in the Rust protocol."""
|
|
24
|
+
id: str
|
|
25
|
+
name: str
|
|
26
|
+
default: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class MidiDeviceList:
|
|
31
|
+
"""Result of Emitter.list_midi_devices."""
|
|
32
|
+
inputs: list
|
|
33
|
+
outputs: list
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class AudioDeviceInfo:
|
|
38
|
+
"""One audio device. Mirrors AudioDeviceWire in the Rust protocol."""
|
|
39
|
+
id: str
|
|
40
|
+
name: str
|
|
41
|
+
default: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class AudioDeviceList:
|
|
46
|
+
"""Result of Emitter.list_audio_devices."""
|
|
47
|
+
inputs: list
|
|
48
|
+
outputs: list
|