From 0a1b1f88a5914d12e4b218fdb9ceeab7f5c433b7 Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 31 Aug 2026 22:21:14 +0200 Subject: [PATCH 1/2] Prefer .bat spawn and give BEAM a hidden console on Windows. CreateProcess must not run the Mix release shell script (error 193). OTP 26 also needs a real console for user/logger; spawn with a hidden CREATE_NEW_CONSOLE instead of sharing invalid or NUL stdio handles. Co-authored-by: Cursor --- native/windows/src/host_controller.cpp | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/native/windows/src/host_controller.cpp b/native/windows/src/host_controller.cpp index ea7f339..a14492e 100644 --- a/native/windows/src/host_controller.cpp +++ b/native/windows/src/host_controller.cpp @@ -317,11 +317,16 @@ void HostController::spawn_beam() { return; } std::string script = join_path(join_path(beam_dir, "bin"), app_name); - if (!file_exists(script)) { - if (file_exists(script + ".bat")) - script += ".bat"; - else if (file_exists(script + ".cmd")) - script += ".cmd"; + // Mix releases ship both a Unix `bin/` shell script and `bin/.bat`. + // Prefer the batch file when present so CreateProcess does not attempt to + // execute the extensionless shell script (Windows error 193). + if (file_exists(script + ".bat")) + script += ".bat"; + else if (file_exists(script + ".cmd")) + script += ".cmd"; + else if (!file_exists(script)) { + fprintf(stderr, "edw: beam script not found: %s\n", script.c_str()); + return; } std::string wd = config_.beam_working_dir @@ -367,8 +372,12 @@ void HostController::spawn_beam() { } env_block.push_back(L'\0'); + // OTP 26's user/logger need a real console (NUL triggers `nouser`; missing + // handles crash logger). Give BEAM a hidden private console. STARTUPINFOW si{}; si.cb = sizeof(si); + si.dwFlags = STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; PROCESS_INFORMATION pi{}; std::wstring wcmd = utf8_to_wide(cmdline); std::wstring wwd = utf8_to_wide(wd); @@ -381,8 +390,9 @@ void HostController::spawn_beam() { beam_process_ = nullptr; } - if (!CreateProcessW(nullptr, mutable_cmd.data(), nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, - env_block.data(), wwd.c_str(), &si, &pi)) { + DWORD flags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE; + if (!CreateProcessW(nullptr, mutable_cmd.data(), nullptr, nullptr, FALSE, flags, env_block.data(), + wwd.c_str(), &si, &pi)) { fprintf(stderr, "edw: failed to spawn beam (%lu): %s\n", GetLastError(), cmdline.c_str()); return; } From 29937f15fdd3bce8324d47038ac72bf3c47dcbbc Mon Sep 17 00:00:00 2001 From: Dominic Letz Date: Mon, 31 Aug 2026 22:45:18 +0200 Subject: [PATCH 2/2] Windows host: GUI subsystem and basename.ini lookup. Build DesktopWebView as a WIN32 (wWinMain) app so packaged installs do not open a console. Resolve config as .ini first so renamed hosts such as dDrive.exe still load dDrive.ini. Co-authored-by: Cursor --- docs/packaging.md | 17 +++++++++++------ native/windows/CMakeLists.txt | 2 +- native/windows/src/config.cpp | 16 +++++++++++++++- native/windows/src/main.cpp | 2 +- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/packaging.md b/docs/packaging.md index 5983d46..286bc9a 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -32,17 +32,21 @@ Default when the host runs as a normal Win32 process (installer or portable zip) ``` MyApp/ - DesktopWebView.exe - DesktopWebView.ini # optional, beside the exe + MyApp.exe # native host (package.name.exe or host_executable) + MyApp.ini # optional, beside the exe (.ini) beam/ bin/ my_app.bat # or my_app (escript/release) ... ``` -- Ini discovery: `--edw-config` → `DesktopWebView.ini` beside the executable. +- Installed host name defaults to `package.name` + `.exe` on Windows host-first + (override with `package.host_executable`). The source binary may still be + `DesktopWebView.exe` from `desktop_webview` / `DESKTOP_HOST_BINARY`. +- Ini discovery: `--edw-config` → `.ini` beside the executable → + `DesktopWebView.ini` beside the executable. - Relative `beam.path` / `working_dir` resolve against the directory containing - `DesktopWebView.exe`. + the host executable. - Forwarded argv and `EDW_PORT` / `EDW_HOST` are unchanged. - Release asset name: `DesktopWebView-windows-x64.exe` (see Binaries). - WebView2: document Evergreen Runtime dependency in the app installer; the host @@ -82,8 +86,9 @@ MyApp/ ## Config discovery 1. `--edw-config=/path/to.ini` -2. `DesktopWebView.ini` beside the executable -3. macOS only: `Contents/Resources/DesktopWebView.ini` (app bundle) +2. `.ini` beside the executable (e.g. `dDrive.ini` for `dDrive.exe`) +3. `DesktopWebView.ini` beside the executable +4. macOS only: `Contents/Resources/DesktopWebView.ini` (app bundle) ### Example ini diff --git a/native/windows/CMakeLists.txt b/native/windows/CMakeLists.txt index 3f65034..04b56b3 100644 --- a/native/windows/CMakeLists.txt +++ b/native/windows/CMakeLists.txt @@ -30,7 +30,7 @@ else() set(WEBVIEW2_LIB_DIR "${WEBVIEW2_ROOT}/build/native/x86") endif() -add_executable(DesktopWebView +add_executable(DesktopWebView WIN32 src/main.cpp src/config.cpp src/json_util.cpp diff --git a/native/windows/src/config.cpp b/native/windows/src/config.cpp index fa4f37e..8bf3461 100644 --- a/native/windows/src/config.cpp +++ b/native/windows/src/config.cpp @@ -120,7 +120,21 @@ std::string HostConfig::resources_root() const { std::optional HostConfig::resolve_ini_path() const { if (config_path) return *config_path; - auto beside = join_path(resources_root(), "DesktopWebView.ini"); + auto root = resources_root(); + // Prefer .ini so renamed hosts (e.g. dDrive.exe → dDrive.ini) + // pick up the packaged config; fall back to the conventional name. + char buf[MAX_PATH]; + DWORD n = GetModuleFileNameA(nullptr, buf, MAX_PATH); + if (n > 0 && n < MAX_PATH) { + std::string exe = buf; + auto slash = exe.find_last_of("/\\"); + std::string base = (slash == std::string::npos) ? exe : exe.substr(slash + 1); + auto dot = base.find_last_of('.'); + if (dot != std::string::npos) base = base.substr(0, dot); + auto beside_exe = join_path(root, base + ".ini"); + if (file_exists(beside_exe)) return beside_exe; + } + auto beside = join_path(root, "DesktopWebView.ini"); if (file_exists(beside)) return beside; return std::nullopt; } diff --git a/native/windows/src/main.cpp b/native/windows/src/main.cpp index b18a698..86f213d 100644 --- a/native/windows/src/main.cpp +++ b/native/windows/src/main.cpp @@ -22,7 +22,7 @@ std::vector argv_utf8() { } // namespace -int main(int, char**) { +int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) { HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); if (FAILED(hr)) { fprintf(stderr, "edw: CoInitializeEx failed\n");