Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .github/assets/open-in-terax-context-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src-tauri/installer-hooks.nsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
; "Open in Terax" shell verbs for folders, folder backgrounds, and drives.
; HKCU matches installer currentUser scope. %V = clicked path.
; NoWorkingDirectory keeps Explorer from overriding %V (System32 on Drive).

!macro NSIS_HOOK_POSTINSTALL
WriteRegStr HKCU "Software\Classes\Directory\shell\OpenInTerax" "" "Open in Terax"
WriteRegStr HKCU "Software\Classes\Directory\shell\OpenInTerax" "Icon" '"$INSTDIR\terax.exe",0'
WriteRegStr HKCU "Software\Classes\Directory\shell\OpenInTerax" "NoWorkingDirectory" ""
WriteRegStr HKCU "Software\Classes\Directory\shell\OpenInTerax\command" "" '"$INSTDIR\terax.exe" "%V"'

WriteRegStr HKCU "Software\Classes\Directory\Background\shell\OpenInTerax" "" "Open in Terax"
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\OpenInTerax" "Icon" '"$INSTDIR\terax.exe",0'
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\OpenInTerax" "NoWorkingDirectory" ""
WriteRegStr HKCU "Software\Classes\Directory\Background\shell\OpenInTerax\command" "" '"$INSTDIR\terax.exe" "%V"'

WriteRegStr HKCU "Software\Classes\Drive\shell\OpenInTerax" "" "Open in Terax"
WriteRegStr HKCU "Software\Classes\Drive\shell\OpenInTerax" "Icon" '"$INSTDIR\terax.exe",0'
WriteRegStr HKCU "Software\Classes\Drive\shell\OpenInTerax" "NoWorkingDirectory" ""
WriteRegStr HKCU "Software\Classes\Drive\shell\OpenInTerax\command" "" '"$INSTDIR\terax.exe" "%V"'
!macroend

!macro NSIS_HOOK_POSTUNINSTALL
DeleteRegKey HKCU "Software\Classes\Directory\shell\OpenInTerax"
DeleteRegKey HKCU "Software\Classes\Directory\Background\shell\OpenInTerax"
DeleteRegKey HKCU "Software\Classes\Drive\shell\OpenInTerax"
!macroend
29 changes: 28 additions & 1 deletion src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
mod modules;

use modules::{fs, git, net, pty, secrets, shell, workspace};
use tauri::{Emitter, Manager, WebviewUrl, WebviewWindowBuilder};
use std::sync::Mutex;
use tauri::{Emitter, Manager, State, WebviewUrl, WebviewWindowBuilder};
use tauri_plugin_window_state::StateFlags;

/// Drained on first read so HMR / re-mounts can't replay the launch dir.
#[derive(Default)]
struct LaunchDir(Mutex<Option<String>>);

#[tauri::command]
fn get_launch_dir(state: State<'_, LaunchDir>) -> Option<String> {
state.0.lock().expect("LaunchDir mutex poisoned").take()
}

fn parse_launch_dir() -> Option<String> {
for arg in std::env::args().skip(1) {
if arg.starts_with('-') {
continue;
}
let Ok(canon) = std::fs::canonicalize(&arg) else { continue };
if !canon.is_dir() {
continue;
}
let s = canon.to_string_lossy();
return Some(s.strip_prefix(r"\\?\").unwrap_or(&s).to_string());
}
None
}

#[tauri::command]
async fn open_settings_window(app: tauri::AppHandle, tab: Option<String>) -> Result<(), String> {
let url_path = match tab.as_deref() {
Expand Down Expand Up @@ -89,6 +114,7 @@ pub fn run() {
workspace::bootstrap_registry(&registry);
registry
})
.manage(LaunchDir(Mutex::new(parse_launch_dir())))
.invoke_handler(tauri::generate_handler![
pty::pty_open,
pty::pty_write,
Expand Down Expand Up @@ -138,6 +164,7 @@ pub fn run() {
workspace::wsl_home,
workspace::workspace_authorize,
workspace::workspace_current_dir,
get_launch_dir,
open_settings_window,
secrets::secrets_get,
secrets::secrets_set,
Expand Down
3 changes: 2 additions & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
"nsis": {
"installMode": "currentUser",
"languages": ["English"],
"displayLanguageSelector": false
"displayLanguageSelector": false,
"installerHooks": "./installer-hooks.nsh"
}
},
"category": "DeveloperTool",
Expand Down
3 changes: 2 additions & 1 deletion src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
type EditorPaneHandle,
} from "@/modules/editor";
import { GitHistoryStack } from "@/modules/git-history";
import { getLaunchDir } from "@/lib/launchDir";
import { useZoom } from "@/lib/useZoom";
import { FileExplorer, type FileExplorerHandle } from "@/modules/explorer";
import {
Expand Down Expand Up @@ -161,7 +162,7 @@ export default function App() {
closeActivePane,
closePaneByLeaf,
resetWorkspace,
} = useTabs();
} = useTabs(getLaunchDir() ? { cwd: getLaunchDir() } : undefined);

// Mirror `tabs` into a ref so callbacks scheduled with `setTimeout`
// (e.g. cdInNewTab) read the latest pane state instead of a stale closure.
Expand Down
12 changes: 12 additions & 0 deletions src/lib/launchDir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { invoke } from "@tauri-apps/api/core";

let cached: string | undefined;

export async function initLaunchDir(): Promise<void> {
const dir = await invoke<string | null>("get_launch_dir").catch(() => null);
cached = dir ? dir.replace(/\\/g, "/") : undefined;
}

export function getLaunchDir(): string | undefined {
return cached;
}
4 changes: 4 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@ import "./styles/globals.css";
import { getCurrentWindow } from "@tauri-apps/api/window";
import ReactDOM from "react-dom/client";
import App from "./app/App";
import { initLaunchDir } from "./lib/launchDir";
import { USE_CUSTOM_WINDOW_CONTROLS } from "./lib/platform";

if (USE_CUSTOM_WINDOW_CONTROLS) {
document.documentElement.dataset.chrome = "borderless";
}

// Seed before first paint so default tab mounts at target cwd (no flicker).
await initLaunchDir();

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<App />,
);
Expand Down
Loading