Skip to content
Open
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
10 changes: 8 additions & 2 deletions emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type WindowOpts = {
unamePlatform: NodeJS.Platform;
isPrimaryStartupWindow?: boolean;
foregroundWindow?: boolean;
cwd?: string;
};

export const MinWindowWidth = 800;
Expand Down Expand Up @@ -724,6 +725,9 @@ export async function createBrowserWindow(
if (!waveWindow) {
console.log("createBrowserWindow: no waveWindow");
waveWindow = await WindowService.CreateWindow(null, "");
if (opts.cwd && waveWindow?.workspaceid) {
await ObjectService.UpdateObjectMeta(`workspace:${waveWindow.workspaceid}`, { "cmd:cwd": opts.cwd } as MetaType);
}
}
let workspace = await WorkspaceService.GetWorkspace(waveWindow.workspaceid);
if (!workspace) {
Expand Down Expand Up @@ -847,8 +851,8 @@ ipcMain.on("delete-workspace", (event, workspaceId) => {
});
});

export async function createNewWaveWindow() {
log("createNewWaveWindow");
export async function createNewWaveWindow(cwd?: string) {
log("createNewWaveWindow" + (cwd ? ` cwd=${cwd}` : ""));
const clientData = await ClientService.GetClientData();
const fullConfig = await RpcApi.GetFullConfigCommand(ElectronWshClient);
let recreatedWindow = false;
Expand All @@ -862,6 +866,7 @@ export async function createNewWaveWindow() {
const win = await createBrowserWindow(existingWindowData, fullConfig, {
unamePlatform,
isPrimaryStartupWindow: false,
cwd,
});
if (quakeWindow == null) {
quakeWindow = win;
Expand All @@ -878,6 +883,7 @@ export async function createNewWaveWindow() {
const newBrowserWindow = await createBrowserWindow(null, fullConfig, {
unamePlatform,
isPrimaryStartupWindow: false,
cwd,
});
if (quakeWindow == null) {
quakeWindow = newBrowserWindow;
Expand Down
3 changes: 2 additions & 1 deletion emain/emain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,8 @@ async function appMain() {
}
electronApp.on("second-instance", (_event, argv, workingDirectory) => {
console.log("second-instance event, argv:", argv, "workingDirectory:", workingDirectory);
fireAndForget(createNewWaveWindow);
const cwd = workingDirectory || undefined;
fireAndForget(() => createNewWaveWindow(cwd));
});
Comment on lines 389 to 393
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Electron second-instance workingDirectory vs argv for Windows Explorer right-click Directory context menu

💡 Result:

Electron single-instance relaunch: use the second-instance event’s argv (and workingDirectory) rather than process.argv. 1) What Electron passes to the primary instance - When a second process starts and the first instance called app.requestSingleInstanceLock, Electron emits: - second-instance with parameters (event, argv, workingDirectory, additionalData). - argv is an array of the second instance’s command-line arguments, and workingDirectory is the second instance’s current working directory. - additionalData is JSON/object data you passed to app.requestSingleInstanceLock(additionalData); Electron documentation notes that argv may not be exactly the same as what you launched with (order may change; additional args may be appended). - Electron also notes Chromium may add extra arguments (e.g. --original-process-start-time). So, for your Windows Explorer right-click Directory context menu, the value you care about (the selected directory path) should be read from the argv array provided to the second-instance handler, and/or (more robustly) you should move that data into additionalData. 2) workingDirectory vs argv (which one to use) - workingDirectory: - It is the “current working directory” of the second launch. - This is generally not the selected directory path you get from Explorer context menus. - argv: - Contains the command-line arguments that Explorer (via your registered command) passed to your Electron executable for the second launch. - This is what you should parse to obtain the selected folder path. 3) Important caveats on Windows/Chromium argument handling - Electron/Chromium may manipulate the second-instance argv: - The documentation explicitly warns that argv “will not be exactly the same list of arguments as those passed to the second instance”; order can change and additional args can be appended. - Chromium command-line parsing can also split/move values such as switches vs positional arguments (documented in electron/electron issues). This can make “exact argv reproduction” unreliable. Practical implication for your question: - Don’t rely on workingDirectory to provide the right-click target. - Don’t rely on argv matching process.argv exactly. 4) Recommended robust pattern - In main process, register a single-instance lock and in second-instance: - Parse the directory path from the argv array you receive. - Focus/notify your existing BrowserWindow. - If you need strict data fidelity (exact path, including tricky quoting), prefer passing your payload through additionalData (Electron supports an object passed via app.requestSingleInstanceLock(additionalData) and received in the second-instance handler as additionalData). Code skeleton: const { app, BrowserWindow } = require('electron'); let win; const gotTheLock = app.requestSingleInstanceLock(/* optional initial data /); if (!gotTheLock) app.quit; else { app.on('second-instance', (event, argv, workingDirectory /, additionalData */) => { // argv: array of second instance’s command-line args // workingDirectory: cwd of second instance // Example: find your custom flag and read the following value // const path = extractFrom(argv); if (win) { if (win.isMinimized) win.restore; win.focus; } // send the extracted folder path to renderer / update tabs }); app.whenReady.then( => { win = new BrowserWindow({}); // load your app }); } 5) Bottom line answer to the “workingDirectory vs argv” part - Use argv for the Explorer right-click “Directory” target path you pass via your registry “command” line. - workingDirectory is only the second process’s current working directory and usually won’t be the selected directory. - Treat argv as parsed-by-Chromium and not guaranteed to equal your original process.argv; if you need exactness, use additionalData.

Citations:


workingDirectory is only correct for Directory\Background; the Directory and Drive entries will silently open the wrong folder.

workingDirectory is the second instance's current working directory, not necessarily the folder that was right-clicked. For Directory\Background, Windows Explorer sets the process CWD to the browsed folder, so workingDirectory is correct. However, for Directory and Drive, the target path is passed as a command-line argument (%1 in the registry command) and the process CWD is elsewhere — workingDirectory will silently resolve to the wrong location.

The robust approach is to scan argv for the first non-flag, absolute-path argument and fall back to workingDirectory. Note that Electron's argv may not exactly match what was passed to the second instance — order may change and Chromium flags (e.g., --original-process-start-time) may be appended, which is why scanning for a non-flag absolute path (not relying on a fixed position) is necessary.

Proposed fix
  electronApp.on("second-instance", (_event, argv, workingDirectory) => {
      console.log("second-instance event, argv:", argv, "workingDirectory:", workingDirectory);
-     const cwd = workingDirectory || undefined;
+     // For Directory/Drive context menu, the target path is passed as an explicit
+     // %1/%V argument in argv. For Directory\Background, workingDirectory is the folder.
+     // Electron may inject Chromium flags into argv, so scan for a non-flag absolute path.
+     const pathArg = argv?.find(
+         (arg) => !arg.startsWith("-") && /^[a-zA-Z]:[\\\/]/.test(arg)
+     );
+     const cwd = pathArg || workingDirectory || undefined;
      fireAndForget(() => createNewWaveWindow(cwd));
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
electronApp.on("second-instance", (_event, argv, workingDirectory) => {
console.log("second-instance event, argv:", argv, "workingDirectory:", workingDirectory);
fireAndForget(createNewWaveWindow);
const cwd = workingDirectory || undefined;
fireAndForget(() => createNewWaveWindow(cwd));
});
electronApp.on("second-instance", (_event, argv, workingDirectory) => {
console.log("second-instance event, argv:", argv, "workingDirectory:", workingDirectory);
// For Directory/Drive context menu, the target path is passed as an explicit
// %1/%V argument in argv. For Directory\Background, workingDirectory is the folder.
// Electron may inject Chromium flags into argv, so scan for a non-flag absolute path.
const pathArg = argv?.find(
(arg) => !arg.startsWith("-") && /^[a-zA-Z]:[\\\/]/.test(arg)
);
const cwd = pathArg || workingDirectory || undefined;
fireAndForget(() => createNewWaveWindow(cwd));
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@emain/emain.ts` around lines 389 - 393, The second-instance handler currently
uses workingDirectory which is only reliable for Directory\\Background; instead,
in the electronApp.on("second-instance", ...) callback scan the argv array for
the first non-flag absolute path (skip entries that start with "-" or are
relative) and use that path as cwd when calling createNewWaveWindow; if none
found, fall back to the provided workingDirectory/undefined. Update the handler
around electronApp.on("second-instance", ...) to perform this argv scan and then
call fireAndForget(() => createNewWaveWindow(resolvedCwd)) so Directory and
Drive launch targets open the correct folder.

try {
await runWaveSrv(handleWSEvent);
Expand Down
9 changes: 9 additions & 0 deletions wave-context-menu-remove.reg
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Windows Registry Editor Version 5.00

; ============================================================
; Remove Wave Terminal Context Menu
; ============================================================

[-HKEY_CLASSES_ROOT\Directory\Background\shell\WaveTerminal]
[-HKEY_CLASSES_ROOT\Directory\shell\WaveTerminal]
[-HKEY_CLASSES_ROOT\Drive\shell\WaveTerminal]
33 changes: 33 additions & 0 deletions wave-context-menu.reg
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Windows Registry Editor Version 5.00

; ============================================================
; Wave Terminal - Windows Right-Click Context Menu
; ============================================================
; Adds "Open in Wave Terminal" to:
; 1. Right-click on empty space in a folder (Directory\Background)
; 2. Right-click on a folder itself (Directory)
; ============================================================

; --- Folder background (right-click empty space) ---
[HKEY_CLASSES_ROOT\Directory\Background\shell\WaveTerminal]
@="Open in Wave Terminal"
"Icon"="C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe"

[HKEY_CLASSES_ROOT\Directory\Background\shell\WaveTerminal\command]
@="\"C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe\" \"%V\""

; --- Folder itself (right-click on folder) ---
[HKEY_CLASSES_ROOT\Directory\shell\WaveTerminal]
@="Open in Wave Terminal"
"Icon"="C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe"

[HKEY_CLASSES_ROOT\Directory\shell\WaveTerminal\command]
@="\"C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe\" \"%1\""

; --- Drive root (right-click on drive in This PC) ---
[HKEY_CLASSES_ROOT\Drive\shell\WaveTerminal]
@="Open in Wave Terminal"
"Icon"="C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe"

[HKEY_CLASSES_ROOT\Drive\shell\WaveTerminal\command]
@="\"C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe\" \"%1\""
Comment on lines +14 to +33
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Hardcoded developer path makes this file non-functional for any other user.

All six path entries reference C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe — a personal installation that will not exist on any other machine. Merging this as-is means the feature ships completely broken for every user who tries to apply it.

Since .reg files don't expand %LOCALAPPDATA% in plain REG_SZ values, the recommended fix is to replace this static .reg file with a PowerShell script that builds the paths dynamically:

$wavePath = "$env:LOCALAPPDATA\Programs\waveterm\Wave.exe"

@(
    "HKCU:\SOFTWARE\Classes\Directory\Background\shell\WaveTerminal",
    "HKCU:\SOFTWARE\Classes\Directory\shell\WaveTerminal",
    "HKCU:\SOFTWARE\Classes\Drive\shell\WaveTerminal"
) | ForEach-Object {
    New-Item -Path "$_\command" -Force | Out-Null
    Set-ItemProperty -Path $_ -Name "(default)" -Value "Open in Wave Terminal"
    Set-ItemProperty -Path $_ -Name "Icon"       -Value $wavePath
    Set-ItemProperty -Path "$_\command" -Name "(default)" -Value "`"$wavePath`" `"%V`""
}

Using HKCU:\SOFTWARE\Classes\... rather than HKEY_CLASSES_ROOT\... also avoids the administrator-elevation requirement.

Note: for the Directory and Drive cases you'll also want to validate which shell variable (%V vs %1) correctly delivers the target path (see the comment on emain.ts).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wave-context-menu.reg` around lines 14 - 33, The .reg file hardcodes a
developer path (C:\\Users\\azurr\\AppData\\Local\\Programs\\waveterm\\Wave.exe)
in all Icon and command values so it will fail for other users; replace the
static .reg with a PowerShell installer that constructs the executable path from
$env:LOCALAPPDATA (e.g. $env:LOCALAPPDATA\Programs\waveterm\Wave.exe), writes
entries under HKCU:\SOFTWARE\Classes for the keys
Directory\Background\shell\WaveTerminal, Directory\shell\WaveTerminal and
Drive\shell\WaveTerminal (setting the default value, Icon and the \command
default), and ensure the command default uses the correct shell variable (%V vs
%1) for Directory vs Folder/Drive as noted so the context-menu opens the
intended target without requiring elevation.