diff --git a/selective-vpn-gui/svpn_run_profile.py b/selective-vpn-gui/svpn_run_profile.py index 0fc7a49..6715fe1 100644 --- a/selective-vpn-gui/svpn_run_profile.py +++ b/selective-vpn-gui/svpn_run_profile.py @@ -102,16 +102,74 @@ def get_profile(profile_id: str) -> dict: def infer_app_key(cmdline: str) -> str: + return canonicalize_app_key("", cmdline) + + +def canonicalize_app_key(app_key: str, cmdline: str) -> str: + key = (app_key or "").strip() cmd = (cmdline or "").strip() - if not cmd: - return "" try: - args = shlex.split(cmd) - if args: - return str(args[0] or "").strip() + tokens = shlex.split(cmd) if cmd else [] except Exception: - pass - return (cmd.split() or [""])[0].strip() + tokens = cmd.split() if cmd else [] + if not tokens and key: + tokens = [key] + tokens = [str(x or "").strip() for x in tokens if str(x or "").strip()] + if not tokens: + return "" + + def base(t: str) -> str: + return os.path.basename(str(t or "").strip()) + + def extract_run_target(toks: list[str]) -> str: + idx = -1 + for i, t in enumerate(toks): + if t == "run": + idx = i + break + if idx < 0: + return "" + for j in range(idx + 1, len(toks)): + t = toks[j].strip() + if not t or t == "--": + continue + if t.startswith("-"): + continue + return t + return "" + + primary = tokens[0] + b = base(primary).lower() + + if b == "env": + for j in range(1, len(tokens)): + t = tokens[j].strip() + if not t or t == "--": + continue + if t.startswith("-"): + continue + if "=" in t: + continue + return canonicalize_app_key("", " ".join(tokens[j:])) + return "env" + + if b == "flatpak": + appid = extract_run_target(tokens) + return f"flatpak:{appid}" if appid else "flatpak" + + if b == "snap": + name = extract_run_target(tokens) + return f"snap:{name}" if name else "snap" + + if b == "gtk-launch" and len(tokens) >= 2: + did = tokens[1].strip() + if did and not did.startswith("-"): + return f"desktop:{did}" + + if "/" in primary: + return base(primary) or primary + + return primary def systemctl_user(args: list[str], *, timeout: float = 4.0) -> tuple[int, str]: @@ -268,7 +326,8 @@ def main(argv: list[str]) -> int: if target not in ("vpn", "direct"): target = "vpn" - app_key = str(prof.get("app_key") or "").strip() or infer_app_key(cmd) + app_key_raw = str(prof.get("app_key") or "").strip() + app_key = canonicalize_app_key(app_key_raw, cmd) or canonicalize_app_key("", cmd) ttl = int(prof.get("ttl_sec", 0) or 0) if ttl <= 0: ttl = 24 * 60 * 60 diff --git a/selective-vpn-gui/traffic_mode_dialog.py b/selective-vpn-gui/traffic_mode_dialog.py index f1970c7..2f60de0 100644 --- a/selective-vpn-gui/traffic_mode_dialog.py +++ b/selective-vpn-gui/traffic_mode_dialog.py @@ -1108,17 +1108,74 @@ RU: Применяет policy-rules и проверяет health. При оши self._emit_log(line) def _infer_app_key_from_cmdline(self, cmdline: str) -> str: + # Keep this aligned with backend canonicalization (flatpak/snap/env/path). cmd = (cmdline or "").strip() if not cmd: return "" try: args = shlex.split(cmd) - if args: - return str(args[0] or "").strip() except Exception: - pass - # Fallback: first token - return (cmd.split() or [""])[0].strip() + args = cmd.split() + return self._canonical_app_key_from_tokens(args) + + def _canonical_app_key_from_tokens(self, tokens: list[str]) -> str: + toks = [str(x or "").strip() for x in (tokens or []) if str(x or "").strip()] + if not toks: + return "" + + def base(t: str) -> str: + return os.path.basename(str(t or "").strip()) + + def extract_run_target(toks2: list[str]) -> str: + idx = -1 + for i, t in enumerate(toks2): + if t == "run": + idx = i + break + if idx < 0: + return "" + for j in range(idx + 1, len(toks2)): + t = toks2[j].strip() + if not t or t == "--": + continue + if t.startswith("-"): + continue + return t + return "" + + primary = toks[0] + b = base(primary).lower() + + if b == "env": + # env VAR=1 /usr/bin/app ... + for j in range(1, len(toks)): + t = toks[j].strip() + if not t or t == "--": + continue + if t.startswith("-"): + continue + if "=" in t: # VAR=VAL + continue + return self._canonical_app_key_from_tokens(toks[j:]) + return "env" + + if b == "flatpak": + appid = extract_run_target(toks) + return f"flatpak:{appid}" if appid else "flatpak" + + if b == "snap": + name = extract_run_target(toks) + return f"snap:{name}" if name else "snap" + + if b == "gtk-launch" and len(toks) >= 2: + did = toks[1].strip() + if did and not did.startswith("-"): + return f"desktop:{did}" + + if "/" in primary: + return base(primary) or primary + + return primary def _launch_and_mark( self,