Skip to content

feat(windows): add 'Open in Terax' shell integration#265

Open
amaralkaff wants to merge 7 commits into
crynta:mainfrom
amaralkaff:feat/open-in-terax
Open

feat(windows): add 'Open in Terax' shell integration#265
amaralkaff wants to merge 7 commits into
crynta:mainfrom
amaralkaff:feat/open-in-terax

Conversation

@amaralkaff
Copy link
Copy Markdown
Contributor

@amaralkaff amaralkaff commented May 15, 2026

What

Adds a Windows Explorer right-click entry — "Open in Terax" — for folders, folder backgrounds, and drives. Clicking it launches Terax with that path as the workspace cwd (explorer rooted there, first terminal pty spawned there).

Open in Terax context menu

Why

Common terminal-emulator integration. Mirrors "Open in Terminal" / "Open with Visual Studio" entries other Windows tools register.

How

NSIS installer (src-tauri/installer-hooks.nsh, wired via tauri.conf.jsonbundle.windows.nsis.installerHooks):

  • POSTINSTALL writes registry keys to HKCU\Software\Classes\Directory\shell\OpenInTerax, Directory\Background\shell\OpenInTerax, and Drive\shell\OpenInTerax — name, icon (from terax.exe,0), and command "$INSTDIR\terax.exe" "%V".
  • POSTUNINSTALL deletes those keys.
  • HKCU keeps it user-scoped, matching the installer's currentUser mode (no admin needed).

CLI handling (src-tauri/src/lib.rs):

  • parse_launch_dir() scans std::env::args() for the first non-flag arg that resolves to an existing directory.
  • Stored in a LaunchDir(Mutex<Option<String>>) state, drained once via a new get_launch_dir command (drain semantics live in the backend so HMR / re-mounts can't replay it).

Frontend (src/app/App.tsx):

  • One useEffect invokes get_launch_dir. If a directory comes back, calls the existing resetWorkspace(dir) to replace the default tab with a fresh terminal at that path. Explorer root + new-tab cwd inheritance follow from the active tab's cwd as they already do.

Testing

  • pnpm exec tsc --noEmit clean
  • Manual smoke-test: launched target/debug/terax.exe "C:\some\folder" — terminal spawns at that path, explorer rooted there.
  • cd src-tauri && cargo check clean
  • pnpm tauri build --bundles nsis produces Terax_0.6.5_x64-setup.exe — makensis accepts the hook file (signing step fails locally on missing TAURI_SIGNING_PRIVATE_KEY, unrelated to this change).
  • End-to-end install on Windows 10 — "Open in Terax" entry appears in folder right-click menu alongside "Open in Terminal" / "Open with Visual Studio" (see screenshot above).

Screenshots / GIFs

See screenshot at top — Windows Explorer right-click in Documents\Ophion\asm shows the new "Open in Terax" entry.

Notes for reviewer

  • Windows-only by construction (NSIS hook + Windows shell registry). macOS and Linux integrations would be separate PRs.
  • get_launch_dir consumes on read by design — second invocation in the same process returns None. This prevents HMR or future re-mounts from re-triggering resetWorkspace. If we later add tauri-plugin-single-instance for focus-existing-window behavior, the single-instance handler can re-fill the LaunchDir slot before re-emitting.
  • Tab 1's PTY mounts moments before get_launch_dir resolves and is then disposed by resetWorkspace — visible as one extra pty opened … pty closed cycle in the log when launched with a path. Acceptable trade-off for keeping useTabs synchronous; happy to refactor if you'd prefer launch-time blocking.
  • Did not bump tauri.windows.conf.json since the integration is configured under the shared bundle.windows block in tauri.conf.json.

@amaralkaff amaralkaff requested a review from crynta as a code owner May 15, 2026 10:28
@crynta
Copy link
Copy Markdown
Owner

crynta commented May 16, 2026

Thanks. only a few things I'd want before merging:

  1. Add NoWorkingDirectory (empty string) to all three verb keys. Without it Explorer sets the
    spawned process CWD on top of %V, and on Drive that CWD is sometimes System32 instead of the drive
    root, makes behavior deterministic.
  2. Canonicalize the path in parse_launch_dir (dunce::canonicalize or strip the \?\ prefix after
    fs::canonicalize). Otherwise trailing slashes / short names can leak into OSC 7 and explorer-root
    comparisons downstream.
  3. .catch(() => {}) on the invoke in App.tsx, unhandled rejection otherwise.
  4. state.0.lock().ok() silently swallows poison. It's structurally impossible to poison here (set
    once, read once), so .expect("LaunchDir mutex poisoned") is fine, or switch to
    OnceLock<Option> and skip the mutex entirely.

Happy to merge once these four land

- Set NoWorkingDirectory on shell verbs (Explorer was overriding %V)
- Canonicalize launch dir and strip \?\ prefix
- Replace silent poison swallow with explicit expect
- Catch invoke rejection in App.tsx
Resolve get_launch_dir before ReactDOM.render so useTabs seeds the
default tab at the target cwd, eliminating the default-cwd PTY mount
and resetWorkspace teardown that caused a brief white flash on launch.
@amaralkaff
Copy link
Copy Markdown
Contributor Author

amaralkaff commented May 16, 2026

Four asks landed in 33f9a37. bc702a5 fixes the launch flicker — resolves get_launch_dir before render so useTabs seeds the default tab at the target cwd. Can split it out if you want.

@amaralkaff
Copy link
Copy Markdown
Contributor Author

@crynta u can check this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants