Sync from upstream: ncp is Python now, runs on Windows too

management/ncp.py replaces the bash CLI's logic; nexus-cli.sh and the new
ncp.ps1 are thin wrappers, so Linux keeps its entry point and Windows gains one.
psutil handles process and port work on both platforms.

install-windows.ps1 registers ncp in the PowerShell profile. The panel VPN
switch now resolves its WireGuard connection through NetworkManager instead of
a hardcoded name, and the .ps1 ASCII guard globs rather than naming files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jon
2026-07-22 11:34:15 -05:00
co-authored by Claude Opus 4.8
parent bcad43502c
commit ca8bc7425c
6 changed files with 845 additions and 681 deletions
+30 -4
View File
@@ -59,6 +59,21 @@ def nmcli(*args):
# load failed and each SSID row fell back to a "?" label.
ICON_DIR = str(Path(__file__).resolve().parent.parent.parent / "assets" / "panel-icons")
def wg_connection():
"""(name, is_up) for the WireGuard tunnel, or (None, False) if there is
none. Asked of NetworkManager rather than hardcoded: this was a literal
'wgs_client', so reissuing the tunnel under a new name left the switch
silently dead - it read Disconnected forever and only errored on click.
Prefers an active tunnel, else the first configured one."""
wgs = [p for p in (line.split(":") for line
in nmcli("-f", "NAME,TYPE,STATE", "connection", "show").splitlines())
if len(p) >= 3 and p[1] == "wireguard"]
for name, _, state in wgs:
if state == "activated":
return name, True
return (wgs[0][0], False) if wgs else (None, False)
def signal_icon(sig):
if sig >= 80: return f"{ICON_DIR}/network-wireless-signal-excellent.png"
if sig >= 60: return f"{ICON_DIR}/network-wireless-signal-good.png"
@@ -73,6 +88,7 @@ class NetworkPopup:
self._click_x = None
self.vpn_switch = None
self.vpn_status_lbl = None
self.vpn_conn = None
self._build_window()
def _on_sig(s, f):
@@ -201,12 +217,17 @@ class NetworkPopup:
self._vpn_handler = self.vpn_switch.connect('state-set', self._on_vpn_toggle)
vpn_row.pack_start(self.vpn_switch, False, False, 0)
# Set switch state immediately from sysfs — no nmcli round-trip needed
vpn_up = os.path.exists('/sys/class/net/wgs_client')
# show() rebuilds this content every time the popup opens, so the
# tunnel is re-resolved each time rather than cached at startup.
self.vpn_conn, vpn_up = wg_connection()
self.vpn_switch.handler_block(self._vpn_handler)
self.vpn_switch.set_active(vpn_up)
self.vpn_switch.set_sensitive(self.vpn_conn is not None)
self.vpn_switch.handler_unblock(self._vpn_handler)
self.vpn_status_lbl.set_text("Connected" if vpn_up else "Disconnected")
self.vpn_status_lbl.set_text(
"Connected" if vpn_up
else "Disconnected" if self.vpn_conn
else "No tunnel configured")
outer.pack_start(vpn_row, False, False, 0)
outer.pack_start(Gtk.Separator(), False, False, 0)
@@ -343,10 +364,15 @@ class NetworkPopup:
return False # let GTK move the switch immediately; revert on failure
def _do_vpn_toggle(self, enable):
if not self.vpn_conn:
subprocess.Popen(["notify-send", "-u", "critical", "WireGuard",
"No WireGuard connection configured"])
GLib.idle_add(self._apply_vpn_state, False, "No tunnel configured")
return
action = "up" if enable else "down"
try:
subprocess.check_output(
["nmcli", "connection", action, "wgs_client"],
["nmcli", "connection", action, self.vpn_conn],
stderr=subprocess.STDOUT, text=True
)
label = "Connected" if enable else "Disconnected"