137 lines
5.8 KiB
Python
137 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
from PySide6.QtCore import Qt
|
|
from PySide6.QtWidgets import QMenu, QMessageBox
|
|
|
|
|
|
class SingBoxRuntimeCardsMixin:
|
|
def on_singbox_profile_card_context_menu(self, pos) -> None:
|
|
item = self.lst_singbox_profile_cards.itemAt(pos)
|
|
if item is None:
|
|
return
|
|
cid = str(item.data(Qt.UserRole) or "").strip()
|
|
if not cid:
|
|
return
|
|
|
|
menu = QMenu(self)
|
|
act_run = menu.addAction("Run")
|
|
act_edit = menu.addAction("Edit")
|
|
act_delete = menu.addAction("Delete")
|
|
chosen = menu.exec(self.lst_singbox_profile_cards.viewport().mapToGlobal(pos))
|
|
if chosen is None:
|
|
return
|
|
|
|
if not self._select_transport_engine_by_id(cid):
|
|
QMessageBox.warning(self, "SingBox profile", f"Profile '{cid}' is no longer available.")
|
|
return
|
|
|
|
if chosen == act_run:
|
|
self.on_transport_engine_action("start")
|
|
return
|
|
if chosen == act_edit:
|
|
self.on_singbox_profile_edit_dialog(cid)
|
|
return
|
|
if chosen == act_delete:
|
|
self.on_transport_engine_delete(cid)
|
|
return
|
|
|
|
def on_singbox_profile_card_selected(self) -> None:
|
|
if self._syncing_singbox_selection:
|
|
return
|
|
items = self.lst_singbox_profile_cards.selectedItems()
|
|
if not items:
|
|
return
|
|
cid = str(items[0].data(Qt.UserRole) or "").strip()
|
|
if not cid:
|
|
return
|
|
idx = self.cmb_transport_engine.findData(cid)
|
|
if idx < 0:
|
|
return
|
|
if idx != self.cmb_transport_engine.currentIndex():
|
|
self._syncing_singbox_selection = True
|
|
try:
|
|
self.cmb_transport_engine.setCurrentIndex(idx)
|
|
finally:
|
|
self._syncing_singbox_selection = False
|
|
return
|
|
self._refresh_singbox_profile_card_styles()
|
|
self._sync_selected_singbox_profile_link(silent=True)
|
|
self._load_singbox_editor_for_selected(silent=True)
|
|
self._update_transport_engine_view()
|
|
|
|
def _singbox_value_label(self, key: str, value: str) -> str:
|
|
v = str(value or "").strip().lower()
|
|
if key == "routing":
|
|
if v == "full":
|
|
return "Full tunnel"
|
|
return "Selective"
|
|
if key == "dns":
|
|
if v == "singbox_dns":
|
|
return "SingBox DNS"
|
|
return "System resolver"
|
|
if key == "killswitch":
|
|
if v == "off":
|
|
return "Disabled"
|
|
return "Enabled"
|
|
return v or "—"
|
|
|
|
def _effective_singbox_policy(self) -> tuple[str, str, str]:
|
|
route = str(self.cmb_singbox_global_routing.currentData() or "selective").strip().lower()
|
|
dns = str(self.cmb_singbox_global_dns.currentData() or "system_resolver").strip().lower()
|
|
killswitch = str(self.cmb_singbox_global_killswitch.currentData() or "on").strip().lower()
|
|
|
|
if not self.chk_singbox_profile_use_global_routing.isChecked():
|
|
route = str(self.cmb_singbox_profile_routing.currentData() or route).strip().lower()
|
|
if route == "global":
|
|
route = str(self.cmb_singbox_global_routing.currentData() or "selective").strip().lower()
|
|
if not self.chk_singbox_profile_use_global_dns.isChecked():
|
|
dns = str(self.cmb_singbox_profile_dns.currentData() or dns).strip().lower()
|
|
if dns == "global":
|
|
dns = str(self.cmb_singbox_global_dns.currentData() or "system_resolver").strip().lower()
|
|
if not self.chk_singbox_profile_use_global_killswitch.isChecked():
|
|
killswitch = str(self.cmb_singbox_profile_killswitch.currentData() or killswitch).strip().lower()
|
|
if killswitch == "global":
|
|
killswitch = str(self.cmb_singbox_global_killswitch.currentData() or "on").strip().lower()
|
|
return route, dns, killswitch
|
|
|
|
def _refresh_singbox_profile_effective(self) -> None:
|
|
route, dns, killswitch = self._effective_singbox_policy()
|
|
route_txt = self._singbox_value_label("routing", route)
|
|
dns_txt = self._singbox_value_label("dns", dns)
|
|
kill_txt = self._singbox_value_label("killswitch", killswitch)
|
|
self.lbl_singbox_profile_effective.setText(
|
|
f"Effective: routing={route_txt} | dns={dns_txt} | kill-switch={kill_txt}"
|
|
)
|
|
self.lbl_singbox_profile_effective.setStyleSheet("color: gray;")
|
|
|
|
def _apply_singbox_profile_controls(self) -> None:
|
|
self.cmb_singbox_profile_routing.setEnabled(
|
|
not self.chk_singbox_profile_use_global_routing.isChecked()
|
|
)
|
|
self.cmb_singbox_profile_dns.setEnabled(
|
|
not self.chk_singbox_profile_use_global_dns.isChecked()
|
|
)
|
|
self.cmb_singbox_profile_killswitch.setEnabled(
|
|
not self.chk_singbox_profile_use_global_killswitch.isChecked()
|
|
)
|
|
self._refresh_singbox_profile_effective()
|
|
|
|
def _apply_singbox_compact_visibility(self) -> None:
|
|
show_profile = bool(self.btn_singbox_toggle_profile_settings.isChecked())
|
|
self.grp_singbox_profile_settings.setVisible(show_profile)
|
|
self.btn_singbox_toggle_profile_settings.setText(
|
|
"Hide profile settings" if show_profile else "Profile settings"
|
|
)
|
|
|
|
show_global = bool(self.btn_singbox_toggle_global_defaults.isChecked())
|
|
self.grp_singbox_global_defaults.setVisible(show_global)
|
|
self.btn_singbox_toggle_global_defaults.setText(
|
|
"Hide global defaults" if show_global else "Global defaults"
|
|
)
|
|
|
|
show_log = bool(self.btn_singbox_toggle_activity.isChecked())
|
|
self.grp_singbox_activity.setVisible(show_log)
|
|
self.btn_singbox_toggle_activity.setText(
|
|
"Hide activity log" if show_log else "Activity log"
|
|
)
|