Fix IME input handling (#20)

bevy_winit does not call `set_ime_allowed()` during initial window
creation when `Window::ime_enabled` is `true`. The `changed_windows`
system only fires when the value differs from cache, but the cache
is initialized from the window itself so they start equal. Since
winit 0.27+ requires explicit `set_ime_allowed(true)`, no Ime events
were ever generated.
This commit is contained in:
elm
2026-02-06 23:52:05 +09:00
committed by GitHub
parent 2afc7bb0de
commit 40a3455f55
5 changed files with 33 additions and 32 deletions

View File

@@ -68,6 +68,7 @@ impl ImplBrowserProcessHandler for BrowserProcessHandlerBuilder {
command_line.append_switch(Some(&"ignore-certificate-errors".into())); command_line.append_switch(Some(&"ignore-certificate-errors".into()));
command_line.append_switch(Some(&"ignore-ssl-errors".into())); command_line.append_switch(Some(&"ignore-ssl-errors".into()));
command_line.append_switch(Some(&"enable-logging=stderr".into())); command_line.append_switch(Some(&"enable-logging=stderr".into()));
command_line.append_switch(Some(&"disable-web-security".into()));
// Pass extensions to render process via command line // Pass extensions to render process via command line
if !self.extensions.is_empty() if !self.extensions.is_empty()
&& let Ok(json) = serde_json::to_string(&self.extensions.0) && let Ok(json) = serde_json::to_string(&self.extensions.0)

View File

@@ -31,7 +31,6 @@ pub struct WebviewBrowser {
pub client: Browser, pub client: Browser,
pub host: BrowserHost, pub host: BrowserHost,
pub size: SharedViewSize, pub size: SharedViewSize,
pub ime_caret: SharedImeCaret,
} }
pub struct Browsers { pub struct Browsers {
@@ -67,7 +66,6 @@ impl Browsers {
) { ) {
let mut context = Self::request_context(requester); let mut context = Self::request_context(requester);
let size = Rc::new(Cell::new(webview_size)); let size = Rc::new(Cell::new(webview_size));
let ime_caret = Rc::new(Cell::new(0));
let browser = browser_host_create_browser_sync( let browser = browser_host_create_browser_sync(
Some(&WindowInfo { Some(&WindowInfo {
windowless_rendering_enabled: true as _, windowless_rendering_enabled: true as _,
@@ -86,7 +84,6 @@ impl Browsers {
Some(&mut self.client_handler( Some(&mut self.client_handler(
webview, webview,
size.clone(), size.clone(),
ime_caret.clone(),
ipc_event_sender, ipc_event_sender,
brp_sender, brp_sender,
system_cursor_icon_sender, system_cursor_icon_sender,
@@ -105,7 +102,6 @@ impl Browsers {
host, host,
client: browser, client: browser,
size, size,
ime_caret,
}; };
self.browsers.insert(webview, webview_browser); self.browsers.insert(webview, webview_browser);
@@ -332,7 +328,7 @@ impl Browsers {
.values() .values()
.filter(|b| b.client.focused_frame().is_some()) .filter(|b| b.client.focused_frame().is_some())
{ {
let replacement_range = Self::ime_caret_range_for(browser); let replacement_range = Self::ime_caret_range_for();
browser.host.ime_set_composition( browser.host.ime_set_composition(
Some(&text.into()), Some(&text.into()),
Some(&underlines), Some(&underlines),
@@ -342,6 +338,19 @@ impl Browsers {
} }
} }
/// ## Reference
///
/// [`ImeCancelComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#ac12a8076859d0c1e58e55080f698e7a9)
pub fn ime_cancel_composition(&self) {
for browser in self
.browsers
.values()
.filter(|b| b.client.focused_frame().is_some())
{
browser.host.ime_cancel_composition();
}
}
/// ## Reference /// ## Reference
/// ///
/// [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe) /// [`ImeSetComposition`](https://cef-builds.spotifycdn.com/docs/122.0/classCefBrowserHost.html#a567b41fb2d3917843ece3b57adc21ebe)
@@ -361,10 +370,10 @@ impl Browsers {
.values() .values()
.filter(|b| b.client.focused_frame().is_some()) .filter(|b| b.client.focused_frame().is_some())
{ {
let replacement_range = Self::ime_caret_range_for(browser); let replacement_range = Self::ime_caret_range_for();
browser browser
.host .host
.ime_commit_text(Some(&text.into()), Some(&replacement_range), 0) .ime_commit_text(Some(&text.into()), Some(&replacement_range), 0);
} }
} }
@@ -387,7 +396,6 @@ impl Browsers {
&self, &self,
webview: Entity, webview: Entity,
size: SharedViewSize, size: SharedViewSize,
ime_caret: SharedImeCaret,
ipc_event_sender: Sender<IpcEventRaw>, ipc_event_sender: Sender<IpcEventRaw>,
brp_sender: Sender<BrpMessage>, brp_sender: Sender<BrpMessage>,
system_cursor_icon_sender: SystemCursorIconSenderInner, system_cursor_icon_sender: SystemCursorIconSenderInner,
@@ -396,7 +404,6 @@ impl Browsers {
webview, webview,
self.sender.clone(), self.sender.clone(),
size.clone(), size.clone(),
ime_caret,
)) ))
.with_display_handler(DisplayHandlerBuilder::build(system_cursor_icon_sender)) .with_display_handler(DisplayHandlerBuilder::build(system_cursor_icon_sender))
.with_message_handler(JsEmitEventHandler::new(webview, ipc_event_sender)) .with_message_handler(JsEmitEventHandler::new(webview, ipc_event_sender))
@@ -405,11 +412,11 @@ impl Browsers {
} }
#[inline] #[inline]
fn ime_caret_range_for(browser: &WebviewBrowser) -> Range { fn ime_caret_range_for() -> Range {
let caret = browser.ime_caret.get(); // Use sentinel replacement range to indicate caret position
Range { Range {
from: caret, from: u32::MAX,
to: caret, to: u32::MAX,
} }
} }

View File

@@ -33,8 +33,7 @@ pub enum RenderPaintElementType {
Popup, Popup,
} }
pub type SharedViewSize = std::rc::Rc<Cell<bevy::prelude::Vec2>>; pub type SharedViewSize = std::rc::Rc<Cell<Vec2>>;
pub type SharedImeCaret = std::rc::Rc<Cell<u32>>;
/// ## Reference /// ## Reference
/// ///
@@ -44,7 +43,6 @@ pub struct RenderHandlerBuilder {
webview: Entity, webview: Entity,
texture_sender: TextureSender, texture_sender: TextureSender,
size: SharedViewSize, size: SharedViewSize,
ime_caret: SharedImeCaret,
} }
impl RenderHandlerBuilder { impl RenderHandlerBuilder {
@@ -52,14 +50,12 @@ impl RenderHandlerBuilder {
webview: Entity, webview: Entity,
texture_sender: TextureSender, texture_sender: TextureSender,
size: SharedViewSize, size: SharedViewSize,
ime_caret: SharedImeCaret,
) -> RenderHandler { ) -> RenderHandler {
RenderHandler::new(Self { RenderHandler::new(Self {
object: std::ptr::null_mut(), object: std::ptr::null_mut(),
webview, webview,
texture_sender, texture_sender,
size, size,
ime_caret,
}) })
} }
} }
@@ -91,7 +87,6 @@ impl Clone for RenderHandlerBuilder {
webview: self.webview, webview: self.webview,
texture_sender: self.texture_sender.clone(), texture_sender: self.texture_sender.clone(),
size: self.size.clone(), size: self.size.clone(),
ime_caret: self.ime_caret.clone(),
} }
} }
} }
@@ -131,17 +126,6 @@ impl ImplRenderHandler for RenderHandlerBuilder {
let _ = self.texture_sender.send_blocking(texture); let _ = self.texture_sender.send_blocking(texture);
} }
fn on_text_selection_changed(
&self,
_browser: Option<&mut Browser>,
_: Option<&CefString>,
selected_range: Option<&Range>,
) {
if let Some(selected_range) = selected_range {
self.ime_caret.set(selected_range.to);
}
}
#[inline] #[inline]
fn get_raw(&self) -> *mut sys::_cef_render_handler_t { fn get_raw(&self) -> *mut sys::_cef_render_handler_t {
self.object.cast() self.object.cast()

View File

@@ -5,7 +5,16 @@ use bevy_cef::prelude::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins((DefaultPlugins, CefPlugin::default())) .add_plugins((
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
ime_enabled: true,
..default()
}),
..default()
}),
CefPlugin::default(),
))
.add_systems( .add_systems(
Startup, Startup,
(spawn_camera, spawn_directional_light, spawn_webview), (spawn_camera, spawn_directional_light, spawn_webview),

View File

@@ -104,7 +104,7 @@ fn ime_event(
is_ime_commiting.0 = true; is_ime_commiting.0 = true;
} }
Ime::Disabled { .. } => { Ime::Disabled { .. } => {
browsers.ime_finish_composition(false); browsers.ime_cancel_composition();
} }
_ => {} _ => {}
} }