iOS remote client: native UIKit surface, WS logging, client-side layout persistence
- Add remote-client-ios: full native UIKit app with Objects/View/UILayout/Functions pattern - Widget hierarchy: BaseWidget → Fader, Toggle, Transport, Title, Feedback, LogWidget - LoggingVC: child VC embedded in LogWidget for live WS message inspection - Codable model: ModelPresetLoadToRemoteDevice with snake_case decoder strategy - Layout persistence: UserDefaults keyed by preset_uuid, fully client-side - WS logging: dedicated WS In/Out floating panels on desktop app - Remove HTTP server and JS client (replaced by native iOS app) - Add Xcode noise to .gitignore (xcuserstate, xcuserdata) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+135
-10
@@ -72,7 +72,6 @@ from osc_sender import (
|
||||
)
|
||||
|
||||
from ws_server import WSServer
|
||||
from http_server import start_http_server, stop_http_server
|
||||
|
||||
def set_window(w):
|
||||
global _window
|
||||
@@ -83,7 +82,6 @@ _osc_server = None
|
||||
_osc_thread = None
|
||||
|
||||
ws_server = None
|
||||
_http_server = None
|
||||
|
||||
|
||||
|
||||
@@ -366,6 +364,11 @@ button_tablet_log = QPushButton("Tablet")
|
||||
button_tablet_log.setFixedWidth(65)
|
||||
button_tablet_log.setCheckable(True)
|
||||
|
||||
# Widget: button_ws_log
|
||||
button_ws_log = QPushButton("WS")
|
||||
button_ws_log.setFixedWidth(50)
|
||||
button_ws_log.setCheckable(True)
|
||||
|
||||
# Widget: label_tablet_status
|
||||
label_tablet_status = QLabel("")
|
||||
label_tablet_status.setStyleSheet("color: #8e24aa; font-size: 11px; font-family: monospace;")
|
||||
@@ -587,6 +590,62 @@ textedit_midi_out_log.setStyleSheet("""
|
||||
}
|
||||
""")
|
||||
|
||||
# Container: container_ws_in_log
|
||||
container_ws_in_log = QWidget(None, Qt.WindowType.Window)
|
||||
container_ws_in_log.setWindowTitle("WS In — Incoming from Tablet")
|
||||
container_ws_in_log.setStyleSheet("background-color: #1a1a1a;")
|
||||
container_ws_in_log.setFixedWidth(420)
|
||||
|
||||
vstack_ws_in_log = QVBoxLayout(container_ws_in_log)
|
||||
vstack_ws_in_log.setContentsMargins(6, 6, 6, 6)
|
||||
vstack_ws_in_log.setSpacing(4)
|
||||
|
||||
hstack_ws_in_log_header = QHBoxLayout()
|
||||
label_ws_in_log_title = QLabel("WS In — Incoming from Tablet")
|
||||
label_ws_in_log_title.setStyleSheet("color: #00acc1; font-weight: bold; font-size: 11px;")
|
||||
button_ws_in_clear = QPushButton("Clear")
|
||||
button_ws_in_clear.setFixedSize(45, 18)
|
||||
|
||||
textedit_ws_in_log = QTextEdit()
|
||||
textedit_ws_in_log.setReadOnly(True)
|
||||
textedit_ws_in_log.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #111111;
|
||||
color: #00e5ff;
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
border: none;
|
||||
}
|
||||
""")
|
||||
|
||||
# Container: container_ws_out_log
|
||||
container_ws_out_log = QWidget(None, Qt.WindowType.Window)
|
||||
container_ws_out_log.setWindowTitle("WS Out — Outgoing to Tablet")
|
||||
container_ws_out_log.setStyleSheet("background-color: #1a1a1a;")
|
||||
container_ws_out_log.setFixedWidth(420)
|
||||
|
||||
vstack_ws_out_log = QVBoxLayout(container_ws_out_log)
|
||||
vstack_ws_out_log.setContentsMargins(6, 6, 6, 6)
|
||||
vstack_ws_out_log.setSpacing(4)
|
||||
|
||||
hstack_ws_out_log_header = QHBoxLayout()
|
||||
label_ws_out_log_title = QLabel("WS Out — Outgoing to Tablet")
|
||||
label_ws_out_log_title.setStyleSheet("color: #ff6f00; font-weight: bold; font-size: 11px;")
|
||||
button_ws_out_clear = QPushButton("Clear")
|
||||
button_ws_out_clear.setFixedSize(45, 18)
|
||||
|
||||
textedit_ws_out_log = QTextEdit()
|
||||
textedit_ws_out_log.setReadOnly(True)
|
||||
textedit_ws_out_log.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #111111;
|
||||
color: #ffb300;
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
border: none;
|
||||
}
|
||||
""")
|
||||
|
||||
# Container: container_tablet_log
|
||||
container_tablet_log = QWidget(None, Qt.WindowType.Window)
|
||||
container_tablet_log.setWindowTitle("Tablet Server")
|
||||
@@ -1045,6 +1104,7 @@ class MainWindow(QMainWindow):
|
||||
hstack_osc_buttons.addSpacing(16)
|
||||
hstack_osc_buttons.addWidget(button_tablet_server)
|
||||
hstack_osc_buttons.addWidget(button_tablet_log)
|
||||
hstack_osc_buttons.addWidget(button_ws_log)
|
||||
hstack_osc_buttons.addWidget(label_tablet_status)
|
||||
hstack_osc_buttons.addStretch()
|
||||
|
||||
@@ -1177,6 +1237,24 @@ class MainWindow(QMainWindow):
|
||||
button_midi_out_clear.clicked.connect(lambda: textedit_midi_out_log.clear())
|
||||
container_midi_out_log.setVisible(False)
|
||||
|
||||
#MARK: WS IN LOG CONTAINER
|
||||
hstack_ws_in_log_header.addWidget(label_ws_in_log_title)
|
||||
hstack_ws_in_log_header.addStretch()
|
||||
hstack_ws_in_log_header.addWidget(button_ws_in_clear)
|
||||
vstack_ws_in_log.addLayout(hstack_ws_in_log_header)
|
||||
vstack_ws_in_log.addWidget(textedit_ws_in_log)
|
||||
button_ws_in_clear.clicked.connect(lambda: textedit_ws_in_log.clear())
|
||||
container_ws_in_log.setVisible(False)
|
||||
|
||||
#MARK: WS OUT LOG CONTAINER
|
||||
hstack_ws_out_log_header.addWidget(label_ws_out_log_title)
|
||||
hstack_ws_out_log_header.addStretch()
|
||||
hstack_ws_out_log_header.addWidget(button_ws_out_clear)
|
||||
vstack_ws_out_log.addLayout(hstack_ws_out_log_header)
|
||||
vstack_ws_out_log.addWidget(textedit_ws_out_log)
|
||||
button_ws_out_clear.clicked.connect(lambda: textedit_ws_out_log.clear())
|
||||
container_ws_out_log.setVisible(False)
|
||||
|
||||
#MARK: TABLET LOG CONTAINER
|
||||
hstack_tablet_log_header.addWidget(label_tablet_log_title)
|
||||
hstack_tablet_log_header.addStretch()
|
||||
@@ -1188,6 +1266,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
button_tablet_server.clicked.connect(toggle_tablet_server)
|
||||
button_tablet_log.clicked.connect(tablet_log_show_hide)
|
||||
button_ws_log.clicked.connect(ws_log_show_hide)
|
||||
|
||||
#MARK: LAYOUT PANEL
|
||||
container_layout_panel.setVisible(True)
|
||||
@@ -1701,6 +1780,32 @@ def log_osc_outgoing(addr, val):
|
||||
line = f"<span style='color:#555'>{ts}</span> <span style='color:#1e88e5'>{addr}</span> <span style='color:#d4d4d4'>{val}</span>"
|
||||
_append_log(textedit_osc_out_log, line)
|
||||
|
||||
def log_ws_incoming(message):
|
||||
import json
|
||||
from datetime import datetime
|
||||
ts = datetime.now().strftime("%H:%M:%S")
|
||||
try:
|
||||
obj = json.loads(message)
|
||||
pretty = json.dumps(obj, indent=2)
|
||||
except Exception:
|
||||
pretty = message
|
||||
header = f"<span style='color:#555'>{ts}</span> <span style='color:#00acc1'>◀ WS In</span>"
|
||||
body = f"<pre style='color:#00e5ff; margin:0; font-size:10px'>{pretty}</pre>"
|
||||
textedit_ws_in_log.append(header)
|
||||
textedit_ws_in_log.append(body)
|
||||
textedit_ws_in_log.append("<span style='color:#333'>───────────────────</span>")
|
||||
|
||||
def log_ws_outgoing(data):
|
||||
import json
|
||||
from datetime import datetime
|
||||
ts = datetime.now().strftime("%H:%M:%S")
|
||||
pretty = json.dumps(data, indent=2)
|
||||
header = f"<span style='color:#555'>{ts}</span> <span style='color:#ff6f00'>▶ WS Out</span>"
|
||||
body = f"<pre style='color:#ffb300; margin:0; font-size:10px'>{pretty}</pre>"
|
||||
textedit_ws_out_log.append(header)
|
||||
textedit_ws_out_log.append(body)
|
||||
textedit_ws_out_log.append("<span style='color:#333'>───────────────────</span>")
|
||||
|
||||
def update_osc_server_ip(ip):
|
||||
set_osc_target(ip=ip.strip() or "127.0.0.1")
|
||||
save_io_config()
|
||||
@@ -2245,6 +2350,7 @@ def _build_tablet_snapshot():
|
||||
snapshot = {
|
||||
"preset_uuid": label_preset_uuid.text().strip(),
|
||||
"preset_name": name,
|
||||
"big_title": big_title_edit.text() if big_title_edit else "",
|
||||
"faders": [{"uid": f.uid, "label": f.title_edit.text(), "value": f.fader.value(), "visible": True, "color": PALETTE_HEX.get(f.color_name, "#00e676")} for f in faders],
|
||||
"toggles": [{"uid": t.uid, "label": t.title_edit.text(), "state": t.toggle_state, "visible": True, "color": PALETTE_HEX.get(t.color_name, "#00e676")} for t in toggles],
|
||||
"transports": [{"uid": t.uid, "label": t.title_edit.text(), "visible": True} for t in transports],
|
||||
@@ -2256,6 +2362,7 @@ def _on_tablet_connected():
|
||||
if ws_server:
|
||||
snapshot, layout = _build_tablet_snapshot()
|
||||
ws_server.broadcast_preset(snapshot, layout)
|
||||
log_ws_outgoing(snapshot)
|
||||
|
||||
|
||||
def _on_tablet_layout_saved(preset_uuid, layout):
|
||||
@@ -2267,6 +2374,9 @@ def _on_tablet_layout_saved(preset_uuid, layout):
|
||||
|
||||
|
||||
def preset_load(preset):
|
||||
global selected_strips
|
||||
selected_strips = []
|
||||
|
||||
fader_states = preset.get("faders", [])
|
||||
while len(faders) < len(fader_states):
|
||||
fader_add()
|
||||
@@ -2391,6 +2501,7 @@ def preset_load(preset):
|
||||
if ws_server:
|
||||
snapshot, layout = _build_tablet_snapshot()
|
||||
ws_server.broadcast_preset(snapshot, layout)
|
||||
log_ws_outgoing(snapshot)
|
||||
|
||||
def export_preset():
|
||||
import json as _json
|
||||
@@ -2714,6 +2825,24 @@ def tablet_log_window_calc():
|
||||
container_tablet_log.move(geo.right() + 732, geo.top())
|
||||
|
||||
|
||||
def ws_log_show_hide(checked):
|
||||
container_ws_in_log.setVisible(checked)
|
||||
container_ws_out_log.setVisible(checked)
|
||||
if checked:
|
||||
ws_log_window_calc()
|
||||
|
||||
|
||||
def ws_log_window_calc():
|
||||
geo = app_window.frameGeometry()
|
||||
half = geo.height() // 2 - 2
|
||||
container_ws_in_log.setFixedWidth(360)
|
||||
container_ws_in_log.setFixedHeight(half)
|
||||
container_ws_in_log.move(geo.right() + 4, geo.top())
|
||||
container_ws_out_log.setFixedWidth(360)
|
||||
container_ws_out_log.setFixedHeight(half)
|
||||
container_ws_out_log.move(geo.right() + 4, geo.top() + half + 4)
|
||||
|
||||
|
||||
def log_tablet(line):
|
||||
from datetime import datetime
|
||||
ts = datetime.now().strftime("%H:%M:%S")
|
||||
@@ -2729,14 +2858,14 @@ def toggle_tablet_server():
|
||||
|
||||
|
||||
def _start_tablet_server():
|
||||
global ws_server, _http_server
|
||||
global ws_server
|
||||
import socket as _socket
|
||||
ws_server = WSServer(port=8765, parent=app_window)
|
||||
ws_server.log_signal.connect(log_tablet)
|
||||
ws_server.raw_in_signal.connect(log_ws_incoming)
|
||||
ws_server.control_received.connect(lambda uid, val: midi_receiver.hw_cc_signal.emit(uid, val))
|
||||
ws_server.layout_saved.connect(_on_tablet_layout_saved)
|
||||
ws_server.client_connected.connect(_on_tablet_connected)
|
||||
_http_server = start_http_server(port=8080)
|
||||
try:
|
||||
s = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM)
|
||||
s.connect(("8.8.8.8", 80))
|
||||
@@ -2745,19 +2874,15 @@ def _start_tablet_server():
|
||||
except Exception:
|
||||
ip = "localhost"
|
||||
button_tablet_server.setText("Stop Tablet")
|
||||
label_tablet_status.setText(f"http://{ip}:8080")
|
||||
log_tablet(f"HTTP http://{ip}:8080")
|
||||
label_tablet_status.setText(f"ws://{ip}:8765")
|
||||
log_tablet(f"WS ws://{ip}:8765")
|
||||
|
||||
|
||||
def _stop_tablet_server():
|
||||
global ws_server, _http_server
|
||||
global ws_server
|
||||
if ws_server:
|
||||
ws_server.stop()
|
||||
ws_server = None
|
||||
if _http_server:
|
||||
stop_http_server(_http_server)
|
||||
_http_server = None
|
||||
button_tablet_server.setText("Start Tablet")
|
||||
label_tablet_status.setText("")
|
||||
log_tablet("server stopped")
|
||||
|
||||
Reference in New Issue
Block a user