mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-24 05:40:15 +00:00
Command: let CreateProcessW resolve the program via PATH (#12387)
Windows users often set bare command names in the Ghostty config (`command = bash`) or pass them via `-e`, matching how they would on Linux/macOS. Today that fails because `CreateProcessW` does not do program search for `lpApplicationName` on its own. Thanks to @qwerasd205 for pointing out that passing `NULL` for `lpApplicationName` is exactly how Windows docs say to get program search for free. This PR does that: drop the explicit utf16 conversion for `lpApplicationName`, pass `null`, and make sure the program name is the first token of `lpCommandLine`. Windows then walks parent-app dir, CWD, system dirs, and PATH (and appends `.exe` for extensionless names). The child also sees its `argv[0]` exactly as we wrote it rather than a resolved absolute path, which is less surprising. Net change is +15 / -7 in `src/Command.zig`; no new helpers, no changes outside that file. The earlier version of this PR (which added PATH/PATHEXT handling in `internal_os.path.expand`) is obsoleted by this approach and has been force-pushed away. --- AI usage disclosure: developed with Claude Code (Claude Opus 4.7). Claude drafted the implementation based on my direction after @qwerasd205's review suggested the NULL-lpApplicationName approach. I reviewed the diff, built and verified it on the Windows GNU-ABI target, and am responsible for the code landing here. Part of the Win32 apprt upstreaming series (see discussion #2563 / mattn/ghostty#1).
This commit is contained in:
@@ -258,12 +258,20 @@ fn startPosix(self: *Command, arena: Allocator) !void {
|
||||
}
|
||||
|
||||
fn startWindows(self: *Command, arena: Allocator) !void {
|
||||
const application_w = try std.unicode.utf8ToUtf16LeAllocZ(arena, self.path);
|
||||
const cwd_w = if (self.cwd) |cwd| try std.unicode.utf8ToUtf16LeAllocZ(arena, cwd) else null;
|
||||
const command_line_w = if (self.args.len > 0) b: {
|
||||
const command_line = try windowsCreateCommandLine(arena, self.args);
|
||||
break :b try std.unicode.utf8ToUtf16LeAllocZ(arena, command_line);
|
||||
} else null;
|
||||
|
||||
// Pass null for lpApplicationName and put the program as the first
|
||||
// token of lpCommandLine. This lets CreateProcessW perform the
|
||||
// standard program search (parent-app dir, CWD, system dirs, PATH)
|
||||
// and append ".exe" when the name has no extension, which is what
|
||||
// users expect for bare commands like `wsl ~` or `pwsh.exe`.
|
||||
// It also preserves the child's argv[0] as written by the caller
|
||||
// rather than replacing it with the resolved absolute path.
|
||||
const command_line = if (self.args.len > 0)
|
||||
try windowsCreateCommandLine(arena, self.args)
|
||||
else
|
||||
try windowsCreateCommandLine(arena, &.{self.path});
|
||||
const command_line_w = try std.unicode.utf8ToUtf16LeAllocZ(arena, command_line);
|
||||
const env_w = if (self.env) |env_map| try createWindowsEnvBlock(arena, env_map) else null;
|
||||
|
||||
const any_null_fd = self.stdin == null or self.stdout == null or self.stderr == null;
|
||||
@@ -345,8 +353,8 @@ fn startWindows(self: *Command, arena: Allocator) !void {
|
||||
|
||||
var process_information: windows.PROCESS_INFORMATION = undefined;
|
||||
if (windows.exp.kernel32.CreateProcessW(
|
||||
application_w.ptr,
|
||||
if (command_line_w) |w| w.ptr else null,
|
||||
null,
|
||||
command_line_w.ptr,
|
||||
null,
|
||||
null,
|
||||
windows.TRUE,
|
||||
|
||||
Reference in New Issue
Block a user