aboutsummaryrefslogtreecommitdiff
path: root/src/platform
diff options
context:
space:
mode:
authorppggff <pgf00a@gmail.com>2026-07-05 08:25:47 +0800
committerGitHub <noreply@github.com>2026-07-05 03:25:47 +0300
commit2bc1724c2dae72184a5d2ec070e30c70dc519f9b (patch)
tree573a960e82f832455d303ca97ea1783587482ad6 /src/platform
parente713949ca51facf1e40c584b271f812f527bc522 (diff)
fix: switch prefix ASCII input source on the foreground client (#1016)
* fix: switch prefix ASCII input source on the foreground client The macOS prefix ASCII-switch ran in the headless server, whose TISCopyCurrentKeyboardInputSource read is a stale per-process cache (refreshed only via a main-run-loop notification the server never pumps), so prefix could leave the terminal on the wrong input source. Forward it to the foreground client, like clipboard: sync_prefix_input_source emits AppEvent::PrefixInputSource as an intent; the server forwards it to the foreground client and the monolithic app applies it in-process via RealPrefixInputSource, which pumps its main run loop before the stale TIS read. Key it on Mode::wants_ascii_input so multi-level prefix commands keep ASCII until returning to the terminal, and restore the IME for rename text entry. Adds ServerMessage::PrefixInputSource to the already-unreleased protocol 15. refs #774 * fix: keep prefix input-source switch out of the headless server process An App-internal drain (the exhaustive drain at the top of handle_api_request, reachable from prefix-key runtime mutations) can consume a queued PrefixInputSource intent before the server's forwarding drain sees it, applying the TIS switch in the headless server process and stranding the restore state. Gate the in-process switch on App.local_input_source_switch, disabled by the headless server alongside the other local side effects, so a swallowed intent degrades to a skipped switch. Widen the forwarding-bypass maintenance test to the handle_internal_event_with_prefix_sync wrapper. refs #774 * docs: clarify prefix input-source comments and flag docs refs #774 * fix: bump protocol for prefix input source refs #774 --------- Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'src/platform')
-rw-r--r--src/platform/macos.rs28
-rw-r--r--src/platform/mod.rs6
2 files changed, 34 insertions, 0 deletions
diff --git a/src/platform/macos.rs b/src/platform/macos.rs
index 3770a3be..e72d32fe 100644
--- a/src/platform/macos.rs
+++ b/src/platform/macos.rs
@@ -97,6 +97,34 @@ extern "C" {
buffer_size: CfIndex,
encoding: u32,
) -> Boolean;
+
+ #[link_name = "kCFRunLoopDefaultMode"]
+ static CF_RUN_LOOP_DEFAULT_MODE: CfStringRef;
+
+ #[link_name = "CFRunLoopRunInMode"]
+ fn cf_run_loop_run_in_mode(
+ mode: CfStringRef,
+ seconds: f64,
+ return_after_source_handled: Boolean,
+ ) -> libc::c_int;
+}
+
+/// Pump the main thread's run loop once (non-blocking) so the process receives the
+/// `kTISNotifySelectedKeyboardInputSourceChanged` notification and refreshes the per-process cache
+/// that `TISCopyCurrentKeyboardInputSource` reads. That notification arrives only via the main
+/// thread's run loop, so a process that never runs a CFRunLoop (the headless server) reads a stale
+/// source. Must run on the main thread.
+pub(crate) fn pump_input_source_runloop() {
+ debug_assert!(
+ // SAFETY: `pthread_main_np` is always safe to call.
+ unsafe { libc::pthread_main_np() } != 0,
+ "pump_input_source_runloop must run on the main thread"
+ );
+ // SAFETY: `CFRunLoopRunInMode` is thread-safe; a 0-second call drains the ready sources and
+ // returns immediately (no blocking). `CF_RUN_LOOP_DEFAULT_MODE` is a framework-owned constant.
+ unsafe {
+ let _ = cf_run_loop_run_in_mode(CF_RUN_LOOP_DEFAULT_MODE, 0.0, 0);
+ }
}
#[derive(Debug)]
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index 169b7c1c..889c4ce9 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -154,6 +154,9 @@ pub(crate) fn switch_to_ascii_input_source() -> Option<InputSourceRestore> {
None
}
+#[cfg(not(target_os = "macos"))]
+pub(crate) fn pump_input_source_runloop() {}
+
/// Switches the host keyboard input source while prefix mode is active.
///
/// `App` drives this through a trait so the prefix-mode transitions can be
@@ -179,6 +182,9 @@ pub(crate) struct RealPrefixInputSource {
impl PrefixInputSource for RealPrefixInputSource {
fn switch_to_ascii(&mut self) {
if self.restore.is_none() {
+ // Drain pending input-source-change notifications so the read below is fresh (see
+ // `pump_input_source_runloop`); a no-op on non-macOS.
+ pump_input_source_runloop();
self.restore = switch_to_ascii_input_source();
}
}