-
Notifications
You must be signed in to change notification settings - Fork 2.6k
fix(windows): wire CEF keyboard input routing on cold launch #1528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e643e9
ca79b68
100b50f
47abaa3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -835,6 +835,26 @@ fn show_main_window(app: &AppHandle<AppRuntime>) -> Result<(), String> { | |
| window | ||
| .set_focus() | ||
| .map_err(|err| format!("failed to focus main window: {err}"))?; | ||
| // `WebviewWindow::set_focus` only dispatches `WindowMessage::SetFocus` | ||
| // (vendor/tauri-cef cef_impl.rs `WindowMessage::SetFocus` → `window.request_focus()`), | ||
| // which lifts the OS window but does NOT call `CefBrowserHost::SetFocus(true)`. | ||
| // Without that CEF-level focus call the renderer never gets wired as the | ||
| // keyboard input target on cold launch: the chat textarea accepts focus | ||
| // (cursor blinks) but `WM_KEYDOWN` messages aren't forwarded to it, so | ||
| // typing is silently dead until the user click-outside / click-back | ||
| // triggers `WM_KILLFOCUS`+`WM_SETFOCUS` and CEF's window handler routes | ||
| // through `host.set_focus(1)` internally. | ||
| // | ||
| // Explicitly dispatch `WebviewMessage::SetFocus` (cef_impl.rs handler | ||
| // for that variant), which is what actually invokes | ||
| // `CefBrowserHost::SetFocus(true)`. | ||
| let webview: &tauri::Webview<AppRuntime> = window.as_ref(); | ||
| if let Err(err) = webview.set_focus() { | ||
| log::warn!( | ||
| "[show_main_window] CEF webview set_focus failed (non-fatal — \ | ||
| keyboard routing may not initialize until user click-outside-and-back): {err}" | ||
| ); | ||
| } | ||
| Ok(()) | ||
| } | ||
| #[cfg(target_os = "linux")] | ||
|
|
@@ -1595,6 +1615,89 @@ pub fn run() { | |
| if let Err(err) = window.show() { | ||
| log::warn!("[window-state] show main window failed: {err}"); | ||
| } | ||
| // CEF keyboard routing fix — cold launch: | ||
| // | ||
| // `window.show()` does not wire the renderer as the | ||
| // keyboard input target. `Window::set_focus` only | ||
| // dispatches `WindowMessage::SetFocus` → `request_focus`, | ||
| // which lifts the OS window but does not call | ||
| // `CefBrowserHost::SetFocus(true)`. Without that | ||
| // CEF-level focus, the textarea accepts focus on cold | ||
| // launch (cursor blinks) but `WM_KEYDOWN` messages | ||
| // never reach the renderer — typing is silently dead | ||
| // until the user click-outside / click-back triggers | ||
| // `WM_KILLFOCUS`+`WM_SETFOCUS`, which CEF's window | ||
| // handler routes through `host.set_focus(1)` internally. | ||
| // | ||
| // We need to call `webview.set_focus()` (which dispatches | ||
| // `WebviewMessage::SetFocus` → `host.set_focus(1)`) | ||
| // *after* CEF has finished creating the browser — too | ||
| // early and `browser()`/`host()` return None and the | ||
| // call silently no-ops. Defer the call to a spawned | ||
| // task with a small delay so CEF's browser-create | ||
| // settles. Then call it again after another delay as | ||
| // belt-and-suspenders for slower init paths. | ||
| // Previous attempts at calling `webview.set_focus()` alone | ||
| // confirmed the dispatch reaches CEF (both returned Ok), | ||
| // but keyboard routing stayed broken. `host.set_focus(1)` | ||
| // alone is insufficient — CEF's internal focus state | ||
| // needs a blur-then-focus *cycle* to wire keyboard | ||
| // routing on cold launch (matches the user-discovered | ||
| // workaround: click outside the window, then click back). | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [major] Focus cycle fires on all platforms, not just Windows The minimize→unminimize cycle is unconditional — it runs on macOS and Linux too. On macOS this triggers a visible Genie/scale dock animation on every cold launch, which is a noticeable UX regression. The bug is Windows-only per the PR description ("macOS and Linux CEF integrations route focus differently and don't exhibit this bug"). This file already has plenty of #[cfg(target_os = "windows")]
{
log::info!("[focus-fix] scheduling deferred CEF focus-cycle (Windows only)");
let webview_window_clone = window.clone();
tauri::async_runtime::spawn(async move {
// ... existing cycle code ...
});
} |
||
| // | ||
| // The vendored tauri-cef doesn't expose `set_focus(false)`, | ||
| // so we mimic the cycle at the OS-window level: | ||
| // minimize triggers `WM_KILLFOCUS` (CEF's window handler | ||
| // propagates this to `host.set_focus(0)`), unminimize | ||
| // restores the window and triggers `WM_SETFOCUS` → | ||
| // `host.set_focus(1)`. Pair with explicit `set_focus` | ||
| // calls on both Window and Webview to cover the case | ||
| // where minimize/unminimize raced ahead of CEF's | ||
| // browser-create. | ||
| // Windows-only: the bug class (CEF host-renderer focus | ||
| // desync after a `visible: false` → `show()` transition | ||
| // without a real `WM_KILLFOCUS`+`WM_SETFOCUS` edge) | ||
| // manifests on the Windows CEF integration. macOS and | ||
| // Linux CEF use different focus propagation paths and | ||
| // don't exhibit the symptom, so running the | ||
| // minimize/unminimize cycle there would just be a | ||
| // visible flicker for no benefit. (Per CodeRabbit | ||
| // review on PR #1528.) | ||
| #[cfg(target_os = "windows")] | ||
| { | ||
| log::info!("[focus-fix] scheduling deferred CEF focus-cycle"); | ||
| let webview_window_clone = window.clone(); | ||
| tauri::async_runtime::spawn(async move { | ||
| // Wait for CEF to finish creating the browser host | ||
| // (synchronous setup() returns before this completes). | ||
| tokio::time::sleep(std::time::Duration::from_millis(300)).await; | ||
| // Blur-then-focus cycle via minimize/unminimize. | ||
| // This is what the manual click-outside / click-back | ||
| // workaround does at the Win32 level. | ||
| log::info!("[focus-fix] starting minimize→unminimize focus cycle"); | ||
| if let Err(err) = webview_window_clone.minimize() { | ||
| log::warn!("[focus-fix] minimize failed: {err}"); | ||
| } | ||
| // Tiny pause so Windows actually processes the | ||
| // minimize before we ask to restore. | ||
| tokio::time::sleep(std::time::Duration::from_millis(80)).await; | ||
| if let Err(err) = webview_window_clone.unminimize() { | ||
| log::warn!("[focus-fix] unminimize failed: {err}"); | ||
| } | ||
| // Belt-and-suspenders: explicit Window + Webview focus | ||
| // after the cycle in case the minimize→restore path | ||
| // didn't propagate. | ||
| tokio::time::sleep(std::time::Duration::from_millis(40)).await; | ||
| if let Err(err) = webview_window_clone.set_focus() { | ||
| log::warn!("[focus-fix] post-cycle window.set_focus failed: {err}"); | ||
| } | ||
| let webview: &tauri::Webview<AppRuntime> = webview_window_clone.as_ref(); | ||
| if let Err(err) = webview.set_focus() { | ||
| log::warn!("[focus-fix] post-cycle webview.set_focus failed: {err}"); | ||
| } | ||
| log::info!("[focus-fix] focus cycle complete"); | ||
| }); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The reference to
cef_impl.rs line ~2081will drift as the vendor evolves. Consider referencing by message name instead:cef_impl.rs→WebviewMessage::SetFocushandler.