from __future__ import annotations import json from PySide6.QtWidgets import QApplication, QInputDialog, QMenu from main_window.constants import SINGBOX_EDITOR_PROTOCOL_OPTIONS class SingBoxLinksActionsMixin: def _apply_singbox_editor_values(self, values: dict[str, Any]) -> None: incoming = dict(values or {}) target_protocol = str(incoming.get("protocol") or self._current_editor_protocol() or "vless").strip().lower() or "vless" payload = self._seed_editor_values_for_protocol( target_protocol, profile_name=str(incoming.get("profile_name") or "").strip(), ) payload.update(incoming) self._singbox_editor_loading = True try: name = str(payload.get("profile_name") or "").strip() self.ent_singbox_proto_name.setText(name) self.chk_singbox_proto_enabled.setChecked(bool(payload.get("enabled", True))) protocol = str(payload.get("protocol") or "").strip().lower() if protocol: pidx = self.cmb_singbox_proto_protocol.findData(protocol) self.cmb_singbox_proto_protocol.setCurrentIndex(pidx if pidx >= 0 else self.cmb_singbox_proto_protocol.currentIndex()) self.ent_singbox_vless_server.setText(str(payload.get("server") or "").strip()) try: self.spn_singbox_vless_port.setValue(int(payload.get("port") or 443)) except Exception: self.spn_singbox_vless_port.setValue(443) self.ent_singbox_vless_uuid.setText(str(payload.get("uuid") or "").strip()) self.ent_singbox_proto_password.setText(str(payload.get("password") or "").strip()) flow_v = str(payload.get("flow") or "").strip() flow_idx = self.cmb_singbox_vless_flow.findData(flow_v) if flow_idx >= 0: self.cmb_singbox_vless_flow.setCurrentIndex(flow_idx) else: self.cmb_singbox_vless_flow.setEditText(flow_v) packet_v = str(payload.get("packet_encoding") or "").strip().lower() if packet_v in ("none", "off", "false"): packet_v = "" packet_idx = self.cmb_singbox_vless_packet_encoding.findData(packet_v) self.cmb_singbox_vless_packet_encoding.setCurrentIndex(packet_idx if packet_idx >= 0 else 0) transport_v = str(payload.get("transport") or "tcp").strip().lower() transport_idx = self.cmb_singbox_vless_transport.findData(transport_v) self.cmb_singbox_vless_transport.setCurrentIndex(transport_idx if transport_idx >= 0 else 0) self.ent_singbox_vless_path.setText(str(payload.get("path") or "").strip()) self.ent_singbox_vless_grpc_service.setText(str(payload.get("grpc_service") or "").strip()) sec_v = str(payload.get("security") or "none").strip().lower() sec_idx = self.cmb_singbox_vless_security.findData(sec_v) self.cmb_singbox_vless_security.setCurrentIndex(sec_idx if sec_idx >= 0 else 0) self.ent_singbox_vless_sni.setText(str(payload.get("sni") or "").strip()) fp_v = str(payload.get("utls_fp") or "").strip().lower() fp_idx = self.cmb_singbox_vless_utls_fp.findData(fp_v) self.cmb_singbox_vless_utls_fp.setCurrentIndex(fp_idx if fp_idx >= 0 else 0) self.ent_singbox_vless_reality_pk.setText(str(payload.get("reality_public_key") or "").strip()) self.ent_singbox_vless_reality_sid.setText(str(payload.get("reality_short_id") or "").strip()) self.chk_singbox_vless_insecure.setChecked(bool(payload.get("tls_insecure", False))) self.chk_singbox_vless_sniff.setChecked(bool(payload.get("sniff", True))) ss_method = str(payload.get("ss_method") or "").strip().lower() if ss_method: idx = self.cmb_singbox_ss_method.findData(ss_method) if idx >= 0: self.cmb_singbox_ss_method.setCurrentIndex(idx) else: self.cmb_singbox_ss_method.setEditText(ss_method) else: self.cmb_singbox_ss_method.setCurrentIndex(0) self.ent_singbox_ss_plugin.setText(str(payload.get("ss_plugin") or "").strip()) try: self.spn_singbox_hy2_up_mbps.setValue(int(payload.get("hy2_up_mbps") or 0)) except Exception: self.spn_singbox_hy2_up_mbps.setValue(0) try: self.spn_singbox_hy2_down_mbps.setValue(int(payload.get("hy2_down_mbps") or 0)) except Exception: self.spn_singbox_hy2_down_mbps.setValue(0) self.ent_singbox_hy2_obfs.setText(str(payload.get("hy2_obfs") or "").strip()) self.ent_singbox_hy2_obfs_password.setText(str(payload.get("hy2_obfs_password") or "").strip()) tuic_cc = str(payload.get("tuic_congestion") or "").strip() idx = self.cmb_singbox_tuic_congestion.findData(tuic_cc) self.cmb_singbox_tuic_congestion.setCurrentIndex(idx if idx >= 0 else 0) tuic_udp = str(payload.get("tuic_udp_mode") or "").strip() idx = self.cmb_singbox_tuic_udp_mode.findData(tuic_udp) self.cmb_singbox_tuic_udp_mode.setCurrentIndex(idx if idx >= 0 else 0) self.chk_singbox_tuic_zero_rtt.setChecked(bool(payload.get("tuic_zero_rtt", False))) self.ent_singbox_wg_private_key.setText(str(payload.get("wg_private_key") or "").strip()) self.ent_singbox_wg_peer_public_key.setText(str(payload.get("wg_peer_public_key") or "").strip()) self.ent_singbox_wg_psk.setText(str(payload.get("wg_psk") or "").strip()) wg_local = payload.get("wg_local_address") or [] if isinstance(wg_local, list): self.ent_singbox_wg_local_address.setText( ",".join([str(x).strip() for x in wg_local if str(x).strip()]) ) else: self.ent_singbox_wg_local_address.setText(str(wg_local or "").strip()) wg_reserved = payload.get("wg_reserved") or [] if isinstance(wg_reserved, list): self.ent_singbox_wg_reserved.setText( ",".join([str(x).strip() for x in wg_reserved if str(x).strip()]) ) else: self.ent_singbox_wg_reserved.setText(str(wg_reserved or "").strip()) try: self.spn_singbox_wg_mtu.setValue(int(payload.get("wg_mtu") or 0)) except Exception: self.spn_singbox_wg_mtu.setValue(0) finally: self._singbox_editor_loading = False self.on_singbox_vless_editor_changed() def _create_singbox_connection( self, *, profile_name: str, protocol: str = "vless", raw_config: dict[str, Any] | None = None, editor_values: dict[str, Any] | None = None, auto_save: bool = False, ) -> str: name = str(profile_name or "").strip() or "SingBox connection" client_id = self._next_free_transport_client_id(name) proto = self._normalized_seed_protocol(protocol) config = self._default_new_singbox_client_config(client_id, protocol=proto) created = self.ctrl.transport_client_create_action( client_id=client_id, kind="singbox", name=name, enabled=True, config=config, ) line = (created.pretty_text or "").strip() or f"create {client_id}" self._append_transport_log(f"[engine] {line}") self.ctrl.log_gui(f"[transport-engine] {line}") if not created.ok: raise RuntimeError(line) self.refresh_transport_engines(silent=True) if not self._select_transport_engine_by_id(client_id): raise RuntimeError(f"created client '{client_id}' was not found after refresh") self._sync_selected_singbox_profile_link(silent=False) client, _eid, pid = self._selected_singbox_profile_context() seed_raw = raw_config if isinstance(raw_config, dict) else self._seed_raw_config_for_protocol(proto) saved_seed = self.ctrl.singbox_profile_save_raw_for_client( client, profile_id=pid, name=name, enabled=True, protocol=proto, raw_config=seed_raw, ) seed_line = (saved_seed.pretty_text or "").strip() or f"save profile {pid}" self._append_transport_log(f"[profile] {seed_line}") self.ctrl.log_gui(f"[singbox-profile] {seed_line}") self._load_singbox_editor_for_selected(silent=True) if editor_values: payload = dict(editor_values) seeded = self._seed_editor_values_for_protocol(proto, profile_name=name) seeded.update(payload) payload = seeded if not str(payload.get("profile_name") or "").strip(): payload["profile_name"] = name self._apply_singbox_editor_values(payload) if auto_save: saved = self._save_singbox_editor_draft(client, profile_id=pid) save_line = (saved.pretty_text or "").strip() or f"save profile {pid}" self._append_transport_log(f"[profile] {save_line}") self.ctrl.log_gui(f"[singbox-profile] {save_line}") return client_id def on_singbox_create_connection_click(self) -> None: menu = QMenu(self) act_clip = menu.addAction("Create from clipboard") act_link = menu.addAction("Create from link...") act_manual = menu.addAction("Create manual") pos = self.btn_singbox_profile_create.mapToGlobal( self.btn_singbox_profile_create.rect().bottomLeft() ) chosen = menu.exec(pos) if chosen is None: return if chosen == act_clip: self._safe(self.on_singbox_create_connection_from_clipboard, title="Create connection error") return if chosen == act_link: self._safe(self.on_singbox_create_connection_from_link, title="Create connection error") return if chosen == act_manual: self._safe(self.on_singbox_create_connection_manual, title="Create connection error") def on_singbox_create_connection_from_clipboard(self) -> None: raw = str(QApplication.clipboard().text() or "").strip() if not raw: raise RuntimeError("Clipboard is empty") payload = self._parse_connection_link_payload(raw) profile_name = str(payload.get("profile_name") or "").strip() or "SingBox Clipboard" cid = self._create_singbox_connection( profile_name=profile_name, protocol=str(payload.get("protocol") or "vless"), raw_config=payload.get("raw_config") if isinstance(payload.get("raw_config"), dict) else None, editor_values=payload.get("editor_values") if isinstance(payload.get("editor_values"), dict) else None, auto_save=True, ) self.on_singbox_profile_edit_dialog(cid) def on_singbox_create_connection_from_link(self) -> None: raw, ok = QInputDialog.getText( self, "Create connection from link", "Paste connection link (vless:// trojan:// ss:// hysteria2:// hy2:// tuic:// wireguard://):", ) if not ok: return payload = self._parse_connection_link_payload(raw) profile_name = str(payload.get("profile_name") or "").strip() or "SingBox Link" cid = self._create_singbox_connection( profile_name=profile_name, protocol=str(payload.get("protocol") or "vless"), raw_config=payload.get("raw_config") if isinstance(payload.get("raw_config"), dict) else None, editor_values=payload.get("editor_values") if isinstance(payload.get("editor_values"), dict) else None, auto_save=True, ) self.on_singbox_profile_edit_dialog(cid) def on_singbox_create_connection_manual(self) -> None: name, ok = QInputDialog.getText( self, "Create manual connection", "Connection name:", ) if not ok: return profile_name = str(name or "").strip() or "SingBox Manual" proto_title, ok = QInputDialog.getItem( self, "Create manual connection", "Protocol:", [label for label, _pid in SINGBOX_EDITOR_PROTOCOL_OPTIONS], 0, False, ) if not ok: return proto_map = {label.lower(): pid for label, pid in SINGBOX_EDITOR_PROTOCOL_OPTIONS} proto = self._normalized_seed_protocol(proto_map.get(str(proto_title or "").strip().lower(), "vless")) cid = self._create_singbox_connection( profile_name=profile_name, protocol=proto, editor_values=self._seed_editor_values_for_protocol(proto, profile_name=profile_name), auto_save=False, ) self.on_singbox_profile_edit_dialog(cid)