Environment
- INCY desktop 3.2.3, Windows portable build (
incy-desktop-3.2.3.jar, Compose Desktop wrapper over Xray-core 26.6.1 via incycore.dll)
- Windows 11 Pro 22621
- Single-instance IPC delivery of deep links works (each command is logged as
Processing deep link from IPC: ... in config/cache/incy-app.log)
Summary
The developer docs (ru/dev-docs/deep-links.md) document incy://connect / incy://open / incy://disconnect / incy://close / incy://toggle with no platform caveats. On the Windows desktop build, only the disconnect half actually works:
incy://disconnect (and close) tears the tunnel down reliably within seconds — works as documented.
incy://connect (and open) is received and dispatched, but never starts the VPN. The only visible effect is (at most) a "Connecting…" toast.
incy://toggle can therefore only ever disconnect: its connect branch is the same dead path.
Clicking the Connect button in the GUI works normally, so this is specific to the deep-link path.
Steps to reproduce
- Start INCY and connect via the GUI (tunnel up, TUN adapter present).
- Run:
Start-Process incy.exe -ArgumentList 'incy://disconnect' → tunnel goes down within seconds. OK.
- Wait for the tunnel to be fully down, then run:
Start-Process incy.exe -ArgumentList 'incy://connect' (also tried incy://toggle, and incy://open followed by incy://connect, with generous waits between steps).
- Observe:
incy-app.log shows Processing deep link from IPC: incy://connect — but no connection attempt follows. No VpnVM/engine start lines appear in the log (a manual GUI click at the same moment produces the full connect() called → … → Connected! chain in ~4 s). The tunnel never comes up.
Reproduced consistently across multiple runs.
Analysis (from inspecting the 3.2.3 desktop jar bytecode with javap)
The deep-link dispatcher (DeepLinkHandler) recognizes all five commands, but the two branches are asymmetric:
- handleDisconnect directly calls the VPN controller's teardown method — that's why
disconnect works (and why it bypasses VpnVM, matching the missing teardown log lines).
- handleConnect only emits
DeepLinkResult.VpnAction("Connecting..."), whose sole consumer is the ToastManager. It never calls the engine-start path (VpnVM.connect() → controller start) that the GUI button uses. There are zero references from the deep-link chain to the connect path.
So on the desktop build, the connect branch of the deep-link surface is effectively a toast-only stub. (There is also an isConnected guard that silently no-ops connect when the app still believes it is connected, but the primary issue is the missing engine call.)
Expected
incy://connect / incy://open start the VPN (same path as the GUI Connect button), and incy://toggle toggles in both directions — as the deep-links documentation implies.
Actual
Only disconnect/close affect the tunnel; connect/open show at most a toast; toggle can only disconnect.
Impact
Deep links are the natural automation/watchdog surface (e.g. recover a hung tunnel by cycling the VPN from a script). With connect missing, programmatic recovery is impossible without GUI automation or a full app restart, while disconnect being scriptable makes the asymmetry surprising.
If desktop connect-by-deep-link is intentionally unsupported, a note in deep-links.md would also resolve this — but implementing the connect branch would be far more useful.
Environment
incy-desktop-3.2.3.jar, Compose Desktop wrapper over Xray-core 26.6.1 viaincycore.dll)Processing deep link from IPC: ...inconfig/cache/incy-app.log)Summary
The developer docs (
ru/dev-docs/deep-links.md) documentincy://connect/incy://open/incy://disconnect/incy://close/incy://togglewith no platform caveats. On the Windows desktop build, only the disconnect half actually works:incy://disconnect(andclose) tears the tunnel down reliably within seconds — works as documented.incy://connect(andopen) is received and dispatched, but never starts the VPN. The only visible effect is (at most) a "Connecting…" toast.incy://togglecan therefore only ever disconnect: its connect branch is the same dead path.Clicking the Connect button in the GUI works normally, so this is specific to the deep-link path.
Steps to reproduce
Start-Process incy.exe -ArgumentList 'incy://disconnect'→ tunnel goes down within seconds. OK.Start-Process incy.exe -ArgumentList 'incy://connect'(also triedincy://toggle, andincy://openfollowed byincy://connect, with generous waits between steps).incy-app.logshowsProcessing deep link from IPC: incy://connect— but no connection attempt follows. NoVpnVM/engine start lines appear in the log (a manual GUI click at the same moment produces the fullconnect() called → … → Connected!chain in ~4 s). The tunnel never comes up.Reproduced consistently across multiple runs.
Analysis (from inspecting the 3.2.3 desktop jar bytecode with
javap)The deep-link dispatcher (
DeepLinkHandler) recognizes all five commands, but the two branches are asymmetric:disconnectworks (and why it bypassesVpnVM, matching the missing teardown log lines).DeepLinkResult.VpnAction("Connecting..."), whose sole consumer is the ToastManager. It never calls the engine-start path (VpnVM.connect()→ controller start) that the GUI button uses. There are zero references from the deep-link chain to the connect path.So on the desktop build, the connect branch of the deep-link surface is effectively a toast-only stub. (There is also an
isConnectedguard that silently no-opsconnectwhen the app still believes it is connected, but the primary issue is the missing engine call.)Expected
incy://connect/incy://openstart the VPN (same path as the GUI Connect button), andincy://toggletoggles in both directions — as the deep-links documentation implies.Actual
Only
disconnect/closeaffect the tunnel;connect/openshow at most a toast;togglecan only disconnect.Impact
Deep links are the natural automation/watchdog surface (e.g. recover a hung tunnel by cycling the VPN from a script). With connect missing, programmatic recovery is impossible without GUI automation or a full app restart, while disconnect being scriptable makes the asymmetry surprising.
If desktop connect-by-deep-link is intentionally unsupported, a note in
deep-links.mdwould also resolve this — but implementing the connect branch would be far more useful.