import json from PyQt6.QtCore import QObject, pyqtSignal from PyQt6.QtWebSockets import QWebSocketServer from PyQt6.QtNetwork import QHostAddress class WSServer(QObject): control_received = pyqtSignal(str, int) layout_saved = pyqtSignal(str, object) client_connected = pyqtSignal() log_signal = pyqtSignal(str) def __init__(self, port=8765, parent=None): super().__init__(parent) self._clients = [] self._port = port self._server = QWebSocketServer("VC", QWebSocketServer.SslMode.NonSecureMode, self) if self._server.listen(QHostAddress.SpecialAddress.AnyIPv4, port): self.log_signal.emit(f"WS listening on :{port}") else: self.log_signal.emit(f"WS failed to bind :{port}") self._server.newConnection.connect(self._on_new_connection) def _on_new_connection(self): client = self._server.nextPendingConnection() peer = client.peerAddress().toString() self._clients.append(client) client.textMessageReceived.connect(self._on_message) client.disconnected.connect(lambda c=client, p=peer: self._on_disconnect(c, p)) self.log_signal.emit(f"client connected {peer} ({len(self._clients)} total)") self.client_connected.emit() def _on_disconnect(self, client, peer): if client in self._clients: self._clients.remove(client) client.deleteLater() self.log_signal.emit(f"client disconnected {peer} ({len(self._clients)} remaining)") def _on_message(self, message): try: data = json.loads(message) ev = data.get("event") if ev == "control": uid = data["uid"] val = int(data["value"]) self.log_signal.emit(f"control uid={uid[:8]}… val={val}") self.control_received.emit(uid, val) elif ev == "save_layout": n = len(data.get("layout", {})) self.log_signal.emit(f"layout saved preset={data['preset_uuid'][:8]}… {n} widgets") self.layout_saved.emit(data["preset_uuid"], data["layout"]) else: self.log_signal.emit(f"unknown event: {ev}") except Exception as e: self.log_signal.emit(f"bad message: {e}") def broadcast(self, data: dict): if not self._clients: return msg = json.dumps(data) for client in list(self._clients): client.sendTextMessage(msg) def broadcast_preset(self, snapshot: dict, layout: dict): name = snapshot.get("preset_name", "") self.log_signal.emit(f"broadcast preset '{name}' → {len(self._clients)} client(s)") self.broadcast({"event": "preset", "data": snapshot, "layout": layout}) def broadcast_widget_update(self, uid: str, value: int): self.broadcast({"event": "widget_update", "uid": uid, "value": value}) def broadcast_daw_state(self, **kwargs): self.broadcast({"event": "daw_state", **kwargs}) def stop(self): self._server.close() for client in list(self._clients): client.close() self._clients.clear() self.log_signal.emit("WS server stopped")