From 39588b36fe63417dd50e9d3738e50d9429f2f4fe Mon Sep 17 00:00:00 2001 From: O6lvl4 Date: Thu, 1 Oct 2026 18:02:23 +0900 Subject: [PATCH] inject: an input queued as if typed into the focused window, safe from inside the event loop (a native menu's item) --- native/window.rs | 27 +++++++++++++++++++++++++++ src/native/window.almd | 5 +++++ 2 files changed, 32 insertions(+) diff --git a/native/window.rs b/native/window.rs index 87f91f1..13f0d7d 100644 --- a/native/window.rs +++ b/native/window.rs @@ -448,6 +448,21 @@ impl ApplicationHandler for App { thread_local! { static HOST: RefCell> = const { RefCell::new(None) }; + /// Inputs handed in from outside the event loop's own events (see + /// `inject`): (kind, code, mods). Kept apart from the host, which is + /// borrowed while the loop runs — when a menu item fires. + static INJECTED: RefCell> = const { RefCell::new(Vec::new()) }; +} + +/// Queue an input as if typed into the window with the focus (else the +/// current one): `kind` TEXT or KEY, `code` and `mods` as `next_event`'s +/// accessors give them. For a native menu whose items do what their keys +/// do. Safe to call while the event loop runs. +pub fn inject(kind: i64, code: i64, mods: i64) { + INJECTED.with(|q| q.borrow_mut().push((kind, code, mods))); + if let Some(proxy) = PROXY.get() { + let _ = proxy.send_event(()); + } } /// Run `f` against the open window, or return `default` when there is none. @@ -459,6 +474,16 @@ fn with_host(default: R, f: impl FnOnce(&mut Host) -> R) -> R { } impl Host { + /// The inputs `inject` queued, into the window with the focus. + fn take_injected(&mut self) { + let injected = INJECTED.with(|q| std::mem::take(&mut *q.borrow_mut())); + let win = self.app.wins.iter().find(|w| w.focused).map_or(self.app.current, |w| w.id); + for (kind, code, mods) in injected { + let input = Input { mods, ..self.app.at_cursor(win, kind, code) }; + self.app.push(input); + } + } + /// Process events, blocking for at most `timeout` (`None`: until one /// arrives). Returns whether the window is still wanted. fn pump(&mut self, timeout: Option) -> bool { @@ -478,6 +503,7 @@ impl Host { if let PumpStatus::Exit(_) = self.event_loop.pump_app_events(timeout, &mut self.app) { return false; } + self.take_injected(); #[cfg(target_os = "macos")] if layer::QUIT_ASKED.swap(false, std::sync::atomic::Ordering::AcqRel) { self.app.close_requested = true; @@ -490,6 +516,7 @@ impl Host { if let PumpStatus::Exit(_) = self.event_loop.pump_app_events(Some(Duration::ZERO), &mut self.app) { return false; } + self.take_injected(); } !self.app.close_requested } diff --git a/src/native/window.almd b/src/native/window.almd index 0270643..ebb74a4 100644 --- a/src/native/window.almd +++ b/src/native/window.almd @@ -65,6 +65,11 @@ fn set_position(x: Float, y: Float) -> Unit = _ @extern(rust, "crate::window", "set_size") fn set_size(width: Float, height: Float) -> Unit = _ +/// Queue an input as if typed into the window with the focus: `kind` TEXT +/// or KEY, `code` and `mods` as the event accessors give them. +@extern(rust, "crate::window", "inject") +fn inject(kind: Int, code: Int, mods: Int) -> Unit = _ + /// The window the current input happened in. @extern(rust, "crate::window", "event_window") fn event_window() -> Int = _