Skip to content

Improve IME handling, add public method owns_ime_events on Memory#7983

Open
umajho wants to merge 17 commits intoemilk:mainfrom
umajho:fix-7485
Open

Improve IME handling, add public method owns_ime_events on Memory#7983
umajho wants to merge 17 commits intoemilk:mainfrom
umajho:fix-7485

Conversation

@umajho
Copy link
Copy Markdown
Contributor

@umajho umajho commented Mar 18, 2026

This PR started as a fix for #7485, but has since evolved into a broader rewrite of IME-related logic.

Overview

This PR primarily introduces a new public method, owns_ime_events, on Memory, and refactors parts of TextEdit to integrate with it.

Previously, each TextEdit widget independently determined whether to handle IME events and stored its own IME-related state. This approach made ownership-handling fragmented and was therefore error-prone.

With this PR:

  • IME event ownership is centralized, ensuring that at most a single widget owns IME events per frame.
  • PlatformOutput's ime field can be set to None for at least one frame when IME composition is interrupted, allowing the IME to be properly dismissed.

Details

Two new public methods are introduced on Memory:

  • fn owns_ime_events(&self, id: Id) -> bool: check IME event ownership for the current frame for the widget with the given id.
  • fn interrupt_ime(&mut self): interrupt the current IME composition, if any.

Since the newly added methods on Memory are public, other widgets can also participate in IME handling without risking ownership conflicts of IME events.

I also added an internal (pub(crate)) field on TextEditState, called cursor_purpose, to distinguish the role of the TextEdit cursor.

Additionally, egui::ImeEvent::Enabled and egui::ImeEvent::Disabled have been removed, as they are no longer used anywhere.

Demonstrations

Windows: The Korean IME text duplication bug fixed in #4137 does not reappear.

With this PR Without this PR
Behavior Correct (no regression) Correct
Screencast

win-kor-after

win-kor-before

Windows: Chinese and Japanese IMEs now behave more consistently with the Korean IME in similar scenarios.

This change does not matter much, as composition is rarely interrupted mid-process with these IMEs in typical usage.

With this PR Without this PR
Behavior Composition can be interrupted by clicking (like Korean IMEs) Composition can not interrupted by clicking
Screencast (Builtin Chinese IME)

win-cmn-after

win-cmn-before

Screencast (Builtin Japanese IME)

win-jpn-after

win-jpn-before

macOS: was buggy, still buggy

Likely due to this upstream bug in winit: rust-windowing/winit#4432
Once winit is updated to a version that includes the fix, the behavior should become correct with this PR.

With this PR Without this PR
Behavior Buggy as before Buggy: Characters are duplicated
Screencast

mac-kor-after

mac-kor-before

Wayland + iBus: Korean IME duplication bug fixed

With this PR Without this PR
Behavior Correct Buggy: Characters are duplicated
Screencast

wayland-kor-after-2

wayland-kor-before-2

Wayland + iBus: #7485 is fixed

With this PR Without this PR
Behavior Correct Buggy: Only a single ASCII character can be typed after TextEdit is focused
Screencast

wayland-7485-after

wayland-7485-before

Wayland + iBus: selection is also not broken

This PR does not reintroduce the selection bug fixed in #7973.

With this PR
Behavior Correct
Screencast

wayland-focus-after

X11 + Fcitx5: IME composition can be interrupted

But due to #7975, the experience is still subpar. (Uncommitted text is lost after interruption.)

With this PR Without this PR
Screencast

x11-after

x11-before

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 18, 2026

Preview available at https://egui-pr-preview.github.io/pr/7983-fix-7485
Note that it might take a couple seconds for the update to show up after the preview_build workflow has completed.

View snapshot changes at kitdiff

@umajho umajho marked this pull request as draft March 18, 2026 09:00
@umajho umajho mentioned this pull request Mar 19, 2026
1 task
@umajho
Copy link
Copy Markdown
Contributor Author

umajho commented Mar 20, 2026

@rustbasic Let's continue the discussion in #7967 here, since we are now talking about what comes after it, which is what I planned for this PR.

Relevant comments from the earlier discussion:

Since it is already quite late night here, although this PR needs updates, I’ll take care of them tomorrow.

@umajho
Copy link
Copy Markdown
Contributor Author

umajho commented Mar 20, 2026

Note

The content of this comment is moved from #7967 (comment)

Regarding Cheonjiin

I think I get the general idea.

While trying to reproduce it, I started with the iPhone simulator and noticed that Korean IMEs (neither the standard one nor the 10-keys one) do not seem to work on iOS at all:

Details

2026-03-20 11 33 03 PM
(Same on egui.rs. Though, since Hangul cannot be rendered on egui.rs, they appear as three missing-glyph blocks instead. Chinese and Japanese work as expected.)

It seems GBoard on Android Chrome works fine (tested on c09a872 + GNU unifont):

Details

Gboard

And SwiftKey is entirely broken (tested on c09a872 + GNU unifont):

Details

SwiftKey

Since even for the layout, different IMEs exhibit different behaviors, i think it is important to specify which exact IME is used.

Regarding #4137

Good point. I didn't consider the scenario of clicking within the same TextEdit.

The “IME-disabling-and-re-enabling” process is more about on some OS (e.g. macOS), where clicking elsewhere does not interrupt composition. This can result in the composed text being duplicated into the newly focused TextEdit when composition starts:
2026-03-21 12 19 22 AM

I will investigate on it later.

What I have found:

  • clicking causing IME composition being committed on Windows, but not on macOS.

Regarding #7983 (this PR)

The only changes I made are:

  • adding let wants_ime_events = ui.memory(|mem| mem.had_focus_last_frame(id));, and
  • changing Event::Ime(ime_event) to Event::Ime(ime_event) if wants_ime_events.

Since these changes are minimal, you can just copy and paste them directly.

@umajho
Copy link
Copy Markdown
Contributor Author

umajho commented Mar 20, 2026

Regarding #4137

I observed the following event order from winit when composing Korean and then clicking somewhere on Windows:

Ime(Preedit("", None))
Ime(Commit("한"))
Ime(Disabled)
MouseInput { device_id: DeviceId(DeviceId(0)), state: Pressed, button: Left }

Given this, the Korean text should be committed before the mouse click moves the cursor.

However, the cursor update associated with MouseInput is processed first, which causes the committed text to be duplicated in the same TextEdit.

This suggests the events are being handled out of order.
In my opinion, to fix this bug properly, we need to find out why it occurs.

@rustbasic
Copy link
Copy Markdown
Contributor

To: @umajho

Regarding the issue where IME characters are duplicated when moving to another TextEdit via mouse click or the TAB key:

I'm glad to see someone else interested in this problem. I have reviewed several approaches:

  1. The simplest and most reliable method was to trigger a re-render for the next frame after the current one finishes when an Ime::Disabled event occurs in window_event() of eframe/src/native/run.rs. While effective, it causes the TAB key to behave as if it were pressed twice. Furthermore, since we cannot guarantee that the TAB key is the only way to move focus between TextEdits, this approach leads to increasingly complex follow-up configurations.

  2. Managing a variable in egui-winit/src/lib.rs to track whether an Ime::Disabled event has occurred and handling it in on_ime(). This method might require a significant amount of platform-specific handling. Additionally, it doesn't solve the problem within the same TextEdit as described in Fix IME bug where currently typed characters got copied #4137. Compared to the fix in Improve IME handling, add public method owns_ime_events on Memory #7983 which only requires two lines, this approach would necessitate far too much code.

In conclusion, I have found that the combination of #4137 + #7983 is the best solution.

@umajho
Copy link
Copy Markdown
Contributor Author

umajho commented Mar 21, 2026

Good points. Then, I'll look for an alternative solution for #7485.
Meanwhile, I'm still interested in why IME events are processed after the cursor move, so I plan to continue investigating that as well.

I won't have much availability today or tomorrow, so my activity may be limited until 2026/03/23.

emilk pushed a commit that referenced this pull request Mar 24, 2026
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* Closes #7809
* Closes #7876
* Closes #7908
* Supersedes #7877
* Supersedes #7898
* The author of the PR above replaced it with #7914, which additionally
fixes another IME issue. I believe that fix deserves a separate PR.
* Reverts #4794  
* [x] I have followed the instructions in the PR template

This approach is better than #7898 (#7914) because it correctly handles
all three major IME types (Chinese, Japanese, and Korean) without
requiring a predefined “IME mode”.

## Environments I haved tested this PR in

<details><summary>macOS 15.7.3 (AArch64, Host of other virtual
machines)</summary>

Run command: `cargo run -p egui_demo_app --release`

Tested IMEs:

- builtin Chinese IME (Shuangpin - Simplified)
- builtin Japanese IME (Romaji)
- builtin Korean IME (2-Set)
</details>

<details><summary>Windows 11 25H2 (AArch64, Virtual Machine)</summary>

Build command: `cargo build --release -p egui_demo_app
--target=x86_64-pc-windows-gnu --features=glow --no-default-features`

(I cannot use `wgpu` due to [this
bug](#4381), which prevents
debugging inside the VM. Anyways, the rendering backend should be
irrelevant here.)

Tested IMEs:

- builtin Chinese IME (Shuangpin)
- Sogou IME (Chinese Shuangpin)
- WeType IME (Chinese Shuangpin)
- builtin Japanese IME (Hiragana)
- builtin Korean IME (2 Beolsik)
</details>

<details><summary>Linux [Wayland + IBus] (AArch64, Virtual
Machine)</summary>

Fedora KDE Plasma Desktop 43 [Wayland + IBus 1.5.33-rc2]

(Not working at the moment because of [another
issue](#7485) that will be fixed by
#7983. It is [a complicated
story](#7973 (comment)).
)

> [!NOTE]
>
> IBus is partially broken in this system. The Input Method Selector
refuses to select IBus. As a workaround, I have to open System Settings
-> Virtual Keyboard and select “IBus Wayland” to start an IBus instance
that works in egui.
>
> The funny thing is: the Chinese Intelligent Pinyin IME is broken in
native Apps like System Settings and KWrite, but works correctly in
egui!
>
> <details><summary>Screencast: What</summary>
>
> ![2026-03-13 3 10
11 AM](https://github.com/user-attachments/assets/4001cf12-8089-46f5-9cf4-e41d8f77ee24)
> </details>

Build command: `cross build --release -p egui_demo_app
--target=aarch64-unknown-linux-gnu --features=wayland,wgpu
--no-default-features`

(The Linux toolchain on my mac is somehow broken, so I used `cross`
instead.)

Tested IMEs:

- Chinese Intelligent Pinyin IME (Shuangpin)
- Japanese Anthy IME (Hiragana)
- Korean Hangul IME
</details>

<details><summary>Linux [X11 + Fcitx5] (AArch64, Virtual
Machine)</summary>

Debian 13 [Cinnamon 6.4.10 + X11 + Fcitx5 5.1.2]

Build command: `cross build --release -p egui_demo_app
--target=aarch64-unknown-linux-gnu --features=x11,wgpu
--no-default-features`

Tested IMEs:

- Chinese Shuangpin IME
- Chinese Rime IME with `luna-pinyin`
- Japanese Mozc IME (Hiragana)
- Korean Hangul IME

Unlike macOS and Linux + Wayland, key-release events for keys processed
by the IME are still forwarded to `egui`. These appear to be harmless in
practice.
Unlike on Windows, however, they cannot be filtered reliably because
there are no corresponding key-press events marked as “processed by
IME”.

</details>

---

There are too many possible combinations to test (Operating Systems ×
[Desktop
Environment](https://en.wikipedia.org/wiki/Desktop_environment)s ×
[Windowing System](https://en.wikipedia.org/wiki/Windowing_system)s ×
[IMF](https://wiki.archlinux.org/title/Input_method#Input_method_framework)s
× [IME](https://en.wikipedia.org/wiki/Input_method)s × …), and I only
have access to a limited subset. For example, Google Japanese Input
refused to install on my Windows VM, and some paid Japanese IMEs are not
accessible to me. Therefore, I would appreciate feedback from people
other than me using all kinds of environments.

## Details

There are two possible approaches to removing keyboard events that have
already been processed by an IME:

* Approach 1: Filter out events inside `egui` that appear to have been
received during IME composition.
* Approach 2: Filter out such events in the platform backend
(terminology [borrowed from
imgui](https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md#using-standard-backends),
e.g. the `egui-winit` crate or the code under `web/` in the `eframe`
crate.).

Both approaches already exist in `egui`:

* #4794 uses the first approach, filtering these events in the
`TextEdit`-related code.
* `eframe` uses the second approach in its web integration. See:
<https://github.com/emilk/egui/blob/14afefa2521d1baaf4fd02105eec2d3727a7ac36/crates/eframe/src/web/events.rs#L173-L176>

Compared to the first approach, the second has a clear advantage: when
events are passed from the platform backends into `egui`, they are
simplified and lose information. In contrast, events in the platform
backends are the original events, which allows them to be handled more
flexibly. This is also why #7898 (#7914), which attempts to address the
issue from within the `egui` crate, struggles to make all IMEs work
correctly at the same time and requires manually selecting an “IME
mode”: the events received by `egui` have already been reduced and
therefore lack necessary information.

A more appropriate solution is to consistently follow the second
approach, explicitly requiring platform backends not to forward events
that have already been processed by the IME to `egui`. This is the
method used in this PR. Specifically, this PR works within the
`egui-winit` crate, where the original `KeyboardInput` events can be
accessed. At least for key press events, these can be used directly to
determine whether the event has already been processed by the IME on
Windows (by checking whether `logical_key` equals
`winit::keyboard::NamedKey::Process`). This makes it straightforward to
ensure that all IMEs work correctly at the same time.

This PR also reverts #4794, which took the first approach. It filters
out some events that merely look like they were received during IME
composition but actually are not. It also messes up the order of those
events along the way.
As a result, it caused several IME-related issues. One of the sections
in the Demonstrations below will illustrate these problems.

## Demonstrations

<details><summary>Changes not included in this PR for displaying Unicode
characters in demonstrations</summary>

Download `unifont-17.0.03.otf` from
<https://unifoundry.com/pub/unifont/unifont-17.0.03/font-builds/>, and
place it at `crates/egui_demo_app/src/unifont-17.0.03.otf`.

In `crates/egui_demo_app/src/wrap_app.rs`, add these lines at the
beginning of `impl WrapApp`'s `pub fn new`:
```rust
        {
            const MAIN_FONT: &'static [u8] = include_bytes!("./unifont-17.0.03.otf");

            let mut fonts = egui::FontDefinitions::default();

            fonts.font_data.insert(
                "main-font".to_owned(),
                std::sync::Arc::new(egui::FontData::from_static(MAIN_FONT)),
            );

            let proportional = fonts
                .families
                .entry(egui::FontFamily::Proportional)
                .or_default();
            proportional.insert(0, "main-font".to_owned());

            cc.egui_ctx.set_fonts(fonts);
        }
```

(I took this from somewhere, but I forgot where it is. Sorry…)
</details>

[GNU Unifont](https://unifoundry.com/unifont/index.html) is licensed
under [OFL-1.1](https://unifoundry.com/OFL-1.1.txt).

### This PR Fixes: Focus on a single-line `TextEdit` is lost after
completing candidate selection with Japanese IME on Windows (#7809)

<details><summary>Screencast: ✅ Japanese IME now behaves correctly while
Korean IME behaves as before</summary>


![7809](https://github.com/user-attachments/assets/6e92f6e6-fed4-46f4-96e1-e0e12dadc360)
</details>

### This PR Fixes: Committing Japanese IME text with <kbd>Enter</kbd>
inserts an unintended newline in multiline `TextEdit` on Windows (#7876)

<details><summary>Screencast: ✅ Japanese IME now behaves correctly while
Korean IME behaves as before</summary>


![7876](https://github.com/user-attachments/assets/03d2cb22-fd0c-45fe-9132-e59fa39bfcf3)
</details>

### This PR Fixes: Backspacing deletes characters during composition in
certain Chinese IMEs (e.g., Sogou) on Windows (#7908)

<details><summary>Screencast: ✅ Sogou IME now behaves
correctly</summary>


![7908](https://github.com/user-attachments/assets/2c63de28-26f0-4387-9c50-dceabfdbe99d)
</details>

### This PR Obsoletes #4794, because `egui` receives only IME events
during composition from now on

On Windows, “incompatible” events are filtered in `egui-winit`, aligning
the behavior with other systems.

<details><summary>Screencasts</summary>

Some Chinese IMEs on Windows:
![2026-03-13 12 25
37 AM](https://github.com/user-attachments/assets/064fd1c7-244b-4053-bd24-c65d768cd943)

The default Japanese IMEs on Windows:
![2026-03-13 12 28
33 AM](https://github.com/user-attachments/assets/f799b0b5-350b-4b05-a769-bcef16255bdb)
</details>

The 2-set Korean IMEs handle arrow keys differently. It will be
discussed in the next section.

### This PR Reverts #4794, because it introduced several bugs

Some of its bugs have already been worked around in the past, but those
workarounds might also be problematic. For example, #4912 is a
workaround for a bug (#4908) introduced by #4794, and that workaround is
in fact the root cause of the macOS backspacing bug I have worked around
with #7810. (The reversion of #4912 is out of the scope of this PR, I
will do that in #7983.)

#### It Caused: Arrow keys are incorrectly blocked during typical Korean
IME composition

When composing Korean text using 2-Set IMEs, users should still be able
to move the cursor with arrow keys regardless if the composition is
committed.

##### Correct behavior

<details><summary>Screencasts</summary>

macOS TextEdit:
![2026-03-12 8 04
15 PM](https://github.com/user-attachments/assets/24383568-f51c-4a74-9251-adfd942cad8f)

Windows Notepad:
![2026-03-12 8 05
08 PM](https://github.com/user-attachments/assets/5a29a5b5-69b8-407b-b1a4-84fdb8f8847d)

With #4794 reverted, `egui` also behaves correctly (tested on Linux +
Wayland, macOS, and Windows):
![2026-03-12 8 03
51 PM](https://github.com/user-attachments/assets/fcb7f25c-1329-4eb1-82f2-1cea33dcca73)
</details>

##### Incorrect behavior caused by #4794

`remove_ime_incompatible_events` removed arrow-key events in such cases.
As a result, the first arrow key press only commits the composition, and
users need to press the arrow key again to move the cursor:

<details><summary>Screencast</summary>

![2026-03-12 8 06
40 PM](https://github.com/user-attachments/assets/6760c6bd-b6ce-44ea-b192-6bd165191c01)
</details>

This is essentially the same issue described here:
#7877 (comment)

#### It Caused: Backspacing leaves the last character in Korean IME
pre-edit text not removed on macOS

<details><summary>Screencasts</summary>

Before this PR:
![2026-03-17 10 48
12 PM](https://github.com/user-attachments/assets/88021e7e-caf6-4aa9-8f73-ecffc63cda06)

After this PR:
![2026-03-17 10 47
23 PM](https://github.com/user-attachments/assets/379cd0db-24e0-4c0e-a5b4-edb37d3e1df7)
</details>

### Korean IMEs also use <kbd>Enter</kbd> to confirm Hanja selections,
and will not work properly in the Korean “IME mode” proposed by #7898
(#7914)

<details><summary>Screencast: Korean IME using <kbd>Enter</kbd> and
<kbd>Space</kbd> for confirmation (IBus Korean Hangul IME)</summary>

The screencast below demonstrates that some Korean IMEs handle Hanja
selection in a way similar to Japanese IMEs: the
<kbd>Up</kbd>/<kbd>Down</kbd> arrow keys are used to navigate
candidates, and <kbd>Enter</kbd> confirms the selected candidate.

![2026-03-13 6 39
17 AM](https://github.com/user-attachments/assets/0b054cc6-2251-4689-95a4-d69a9be36371)
</details>

<details><summary>Screencasts: Another example</summary>

Using the built-in Korean IME on Windows, I type two lines: the first
line in Hangul, and the second line as the same word converted to Hanja.

Correct behavior in Notepad (reference):

![7914-ref](https://github.com/user-attachments/assets/1e9f9315-eb71-497a-b6e2-2b11eb6bbf7f)

Behavior after applying this PR, which matches the Notepad behavior:

![7914-7967](https://github.com/user-attachments/assets/26f12b9f-9354-45b8-b2a8-ede28c34c5b1)

Behavior after applying #7914 with the “IME mode” set to Korean (which
is also the behavior before this PR being applied):

![7914-7914](https://github.com/user-attachments/assets/0f82a019-c491-4b64-a92a-d88d62dfbd84)
On the second line, each time a Hanja character is confirmed, an
unintended newline is inserted. This mirrors the Japanese IME issues
that are supposed to be fixed by setting the “IME mode” to Japanese.
(These Japanese IME issues are fixed in this PR as mentioned before.)

</details>
umajho and others added 2 commits March 24, 2026 19:38
…ated data

(cherry picked from commit 3f0052f3db7b35b54809361b6b8250ef14a194ea)

# Conflicts:
#	crates/egui/src/widgets/text_edit/builder.rs
@umajho
Copy link
Copy Markdown
Contributor Author

umajho commented Mar 24, 2026

I decided to take a different approach:
moving IME-related state out of individual TextEdits into a single ImeManager owned by Context.
Since IME is inherently global in nature, managing it at the context level seems more appropriate.

I've tested the changes, and it appears to avoid reintroducing the duplication issue fixed by #4137.

@rustbasic I'd appreciate your thoughts on this. Thanks!

…emilk#4794

We should not assume that "it isn't broken, so it's fine to leave it", because
this workaround doesn't align with `winit`'s intended behavior, therefore
could lead to further issues.
@umajho umajho changed the title Fix IbusWayland IME Issue Introduce ImeManager on Context to improve IME handling Mar 26, 2026
@umajho umajho marked this pull request as ready for review March 28, 2026 10:27
@umajho
Copy link
Copy Markdown
Contributor Author

umajho commented Mar 28, 2026

This PR is ready for review.


@Alvenster @skykooler @zty012

Hello. Since you were involved in the relevant issues (#7485 and #7906), would you mind taking a look at this PR?

Thanks!

@rustbasic
Copy link
Copy Markdown
Contributor

@umajho

I just saw your comment asking for my opinion.
I briefly tested this on Windows 10, WASM (Chrome), and Android.
It seems that #7983 may be able to replace #4137,
although I have not reviewed the details closely yet.

@umajho umajho changed the title Introduce ImeManager on Context to improve IME handling Improve IME handling, add public method owns_ime_events on Memory Apr 2, 2026
Masterchef365 pushed a commit to Masterchef365/egui that referenced this pull request Apr 3, 2026
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/main/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

* Closes emilk#7809
* Closes emilk#7876
* Closes emilk#7908
* Supersedes emilk#7877
* Supersedes emilk#7898
* The author of the PR above replaced it with emilk#7914, which additionally
fixes another IME issue. I believe that fix deserves a separate PR.
* Reverts emilk#4794  
* [x] I have followed the instructions in the PR template

This approach is better than emilk#7898 (emilk#7914) because it correctly handles
all three major IME types (Chinese, Japanese, and Korean) without
requiring a predefined “IME mode”.

## Environments I haved tested this PR in

<details><summary>macOS 15.7.3 (AArch64, Host of other virtual
machines)</summary>

Run command: `cargo run -p egui_demo_app --release`

Tested IMEs:

- builtin Chinese IME (Shuangpin - Simplified)
- builtin Japanese IME (Romaji)
- builtin Korean IME (2-Set)
</details>

<details><summary>Windows 11 25H2 (AArch64, Virtual Machine)</summary>

Build command: `cargo build --release -p egui_demo_app
--target=x86_64-pc-windows-gnu --features=glow --no-default-features`

(I cannot use `wgpu` due to [this
bug](emilk#4381), which prevents
debugging inside the VM. Anyways, the rendering backend should be
irrelevant here.)

Tested IMEs:

- builtin Chinese IME (Shuangpin)
- Sogou IME (Chinese Shuangpin)
- WeType IME (Chinese Shuangpin)
- builtin Japanese IME (Hiragana)
- builtin Korean IME (2 Beolsik)
</details>

<details><summary>Linux [Wayland + IBus] (AArch64, Virtual
Machine)</summary>

Fedora KDE Plasma Desktop 43 [Wayland + IBus 1.5.33-rc2]

(Not working at the moment because of [another
issue](emilk#7485) that will be fixed by
emilk#7983. It is [a complicated
story](emilk#7973 (comment)).
)

> [!NOTE]
>
> IBus is partially broken in this system. The Input Method Selector
refuses to select IBus. As a workaround, I have to open System Settings
-> Virtual Keyboard and select “IBus Wayland” to start an IBus instance
that works in egui.
>
> The funny thing is: the Chinese Intelligent Pinyin IME is broken in
native Apps like System Settings and KWrite, but works correctly in
egui!
>
> <details><summary>Screencast: What</summary>
>
> ![2026-03-13 3 10
11 AM](https://github.com/user-attachments/assets/4001cf12-8089-46f5-9cf4-e41d8f77ee24)
> </details>

Build command: `cross build --release -p egui_demo_app
--target=aarch64-unknown-linux-gnu --features=wayland,wgpu
--no-default-features`

(The Linux toolchain on my mac is somehow broken, so I used `cross`
instead.)

Tested IMEs:

- Chinese Intelligent Pinyin IME (Shuangpin)
- Japanese Anthy IME (Hiragana)
- Korean Hangul IME
</details>

<details><summary>Linux [X11 + Fcitx5] (AArch64, Virtual
Machine)</summary>

Debian 13 [Cinnamon 6.4.10 + X11 + Fcitx5 5.1.2]

Build command: `cross build --release -p egui_demo_app
--target=aarch64-unknown-linux-gnu --features=x11,wgpu
--no-default-features`

Tested IMEs:

- Chinese Shuangpin IME
- Chinese Rime IME with `luna-pinyin`
- Japanese Mozc IME (Hiragana)
- Korean Hangul IME

Unlike macOS and Linux + Wayland, key-release events for keys processed
by the IME are still forwarded to `egui`. These appear to be harmless in
practice.
Unlike on Windows, however, they cannot be filtered reliably because
there are no corresponding key-press events marked as “processed by
IME”.

</details>

---

There are too many possible combinations to test (Operating Systems ×
[Desktop
Environment](https://en.wikipedia.org/wiki/Desktop_environment)s ×
[Windowing System](https://en.wikipedia.org/wiki/Windowing_system)s ×
[IMF](https://wiki.archlinux.org/title/Input_method#Input_method_framework)s
× [IME](https://en.wikipedia.org/wiki/Input_method)s × …), and I only
have access to a limited subset. For example, Google Japanese Input
refused to install on my Windows VM, and some paid Japanese IMEs are not
accessible to me. Therefore, I would appreciate feedback from people
other than me using all kinds of environments.

## Details

There are two possible approaches to removing keyboard events that have
already been processed by an IME:

* Approach 1: Filter out events inside `egui` that appear to have been
received during IME composition.
* Approach 2: Filter out such events in the platform backend
(terminology [borrowed from
imgui](https://github.com/ocornut/imgui/blob/master/docs/BACKENDS.md#using-standard-backends),
e.g. the `egui-winit` crate or the code under `web/` in the `eframe`
crate.).

Both approaches already exist in `egui`:

* emilk#4794 uses the first approach, filtering these events in the
`TextEdit`-related code.
* `eframe` uses the second approach in its web integration. See:
<https://github.com/emilk/egui/blob/14afefa2521d1baaf4fd02105eec2d3727a7ac36/crates/eframe/src/web/events.rs#L173-L176>

Compared to the first approach, the second has a clear advantage: when
events are passed from the platform backends into `egui`, they are
simplified and lose information. In contrast, events in the platform
backends are the original events, which allows them to be handled more
flexibly. This is also why emilk#7898 (emilk#7914), which attempts to address the
issue from within the `egui` crate, struggles to make all IMEs work
correctly at the same time and requires manually selecting an “IME
mode”: the events received by `egui` have already been reduced and
therefore lack necessary information.

A more appropriate solution is to consistently follow the second
approach, explicitly requiring platform backends not to forward events
that have already been processed by the IME to `egui`. This is the
method used in this PR. Specifically, this PR works within the
`egui-winit` crate, where the original `KeyboardInput` events can be
accessed. At least for key press events, these can be used directly to
determine whether the event has already been processed by the IME on
Windows (by checking whether `logical_key` equals
`winit::keyboard::NamedKey::Process`). This makes it straightforward to
ensure that all IMEs work correctly at the same time.

This PR also reverts emilk#4794, which took the first approach. It filters
out some events that merely look like they were received during IME
composition but actually are not. It also messes up the order of those
events along the way.
As a result, it caused several IME-related issues. One of the sections
in the Demonstrations below will illustrate these problems.

## Demonstrations

<details><summary>Changes not included in this PR for displaying Unicode
characters in demonstrations</summary>

Download `unifont-17.0.03.otf` from
<https://unifoundry.com/pub/unifont/unifont-17.0.03/font-builds/>, and
place it at `crates/egui_demo_app/src/unifont-17.0.03.otf`.

In `crates/egui_demo_app/src/wrap_app.rs`, add these lines at the
beginning of `impl WrapApp`'s `pub fn new`:
```rust
        {
            const MAIN_FONT: &'static [u8] = include_bytes!("./unifont-17.0.03.otf");

            let mut fonts = egui::FontDefinitions::default();

            fonts.font_data.insert(
                "main-font".to_owned(),
                std::sync::Arc::new(egui::FontData::from_static(MAIN_FONT)),
            );

            let proportional = fonts
                .families
                .entry(egui::FontFamily::Proportional)
                .or_default();
            proportional.insert(0, "main-font".to_owned());

            cc.egui_ctx.set_fonts(fonts);
        }
```

(I took this from somewhere, but I forgot where it is. Sorry…)
</details>

[GNU Unifont](https://unifoundry.com/unifont/index.html) is licensed
under [OFL-1.1](https://unifoundry.com/OFL-1.1.txt).

### This PR Fixes: Focus on a single-line `TextEdit` is lost after
completing candidate selection with Japanese IME on Windows (emilk#7809)

<details><summary>Screencast: ✅ Japanese IME now behaves correctly while
Korean IME behaves as before</summary>


![7809](https://github.com/user-attachments/assets/6e92f6e6-fed4-46f4-96e1-e0e12dadc360)
</details>

### This PR Fixes: Committing Japanese IME text with <kbd>Enter</kbd>
inserts an unintended newline in multiline `TextEdit` on Windows (emilk#7876)

<details><summary>Screencast: ✅ Japanese IME now behaves correctly while
Korean IME behaves as before</summary>


![7876](https://github.com/user-attachments/assets/03d2cb22-fd0c-45fe-9132-e59fa39bfcf3)
</details>

### This PR Fixes: Backspacing deletes characters during composition in
certain Chinese IMEs (e.g., Sogou) on Windows (emilk#7908)

<details><summary>Screencast: ✅ Sogou IME now behaves
correctly</summary>


![7908](https://github.com/user-attachments/assets/2c63de28-26f0-4387-9c50-dceabfdbe99d)
</details>

### This PR Obsoletes emilk#4794, because `egui` receives only IME events
during composition from now on

On Windows, “incompatible” events are filtered in `egui-winit`, aligning
the behavior with other systems.

<details><summary>Screencasts</summary>

Some Chinese IMEs on Windows:
![2026-03-13 12 25
37 AM](https://github.com/user-attachments/assets/064fd1c7-244b-4053-bd24-c65d768cd943)

The default Japanese IMEs on Windows:
![2026-03-13 12 28
33 AM](https://github.com/user-attachments/assets/f799b0b5-350b-4b05-a769-bcef16255bdb)
</details>

The 2-set Korean IMEs handle arrow keys differently. It will be
discussed in the next section.

### This PR Reverts emilk#4794, because it introduced several bugs

Some of its bugs have already been worked around in the past, but those
workarounds might also be problematic. For example, emilk#4912 is a
workaround for a bug (emilk#4908) introduced by emilk#4794, and that workaround is
in fact the root cause of the macOS backspacing bug I have worked around
with emilk#7810. (The reversion of emilk#4912 is out of the scope of this PR, I
will do that in emilk#7983.)

#### It Caused: Arrow keys are incorrectly blocked during typical Korean
IME composition

When composing Korean text using 2-Set IMEs, users should still be able
to move the cursor with arrow keys regardless if the composition is
committed.

##### Correct behavior

<details><summary>Screencasts</summary>

macOS TextEdit:
![2026-03-12 8 04
15 PM](https://github.com/user-attachments/assets/24383568-f51c-4a74-9251-adfd942cad8f)

Windows Notepad:
![2026-03-12 8 05
08 PM](https://github.com/user-attachments/assets/5a29a5b5-69b8-407b-b1a4-84fdb8f8847d)

With emilk#4794 reverted, `egui` also behaves correctly (tested on Linux +
Wayland, macOS, and Windows):
![2026-03-12 8 03
51 PM](https://github.com/user-attachments/assets/fcb7f25c-1329-4eb1-82f2-1cea33dcca73)
</details>

##### Incorrect behavior caused by emilk#4794

`remove_ime_incompatible_events` removed arrow-key events in such cases.
As a result, the first arrow key press only commits the composition, and
users need to press the arrow key again to move the cursor:

<details><summary>Screencast</summary>

![2026-03-12 8 06
40 PM](https://github.com/user-attachments/assets/6760c6bd-b6ce-44ea-b192-6bd165191c01)
</details>

This is essentially the same issue described here:
emilk#7877 (comment)

#### It Caused: Backspacing leaves the last character in Korean IME
pre-edit text not removed on macOS

<details><summary>Screencasts</summary>

Before this PR:
![2026-03-17 10 48
12 PM](https://github.com/user-attachments/assets/88021e7e-caf6-4aa9-8f73-ecffc63cda06)

After this PR:
![2026-03-17 10 47
23 PM](https://github.com/user-attachments/assets/379cd0db-24e0-4c0e-a5b4-edb37d3e1df7)
</details>

### Korean IMEs also use <kbd>Enter</kbd> to confirm Hanja selections,
and will not work properly in the Korean “IME mode” proposed by emilk#7898
(emilk#7914)

<details><summary>Screencast: Korean IME using <kbd>Enter</kbd> and
<kbd>Space</kbd> for confirmation (IBus Korean Hangul IME)</summary>

The screencast below demonstrates that some Korean IMEs handle Hanja
selection in a way similar to Japanese IMEs: the
<kbd>Up</kbd>/<kbd>Down</kbd> arrow keys are used to navigate
candidates, and <kbd>Enter</kbd> confirms the selected candidate.

![2026-03-13 6 39
17 AM](https://github.com/user-attachments/assets/0b054cc6-2251-4689-95a4-d69a9be36371)
</details>

<details><summary>Screencasts: Another example</summary>

Using the built-in Korean IME on Windows, I type two lines: the first
line in Hangul, and the second line as the same word converted to Hanja.

Correct behavior in Notepad (reference):

![7914-ref](https://github.com/user-attachments/assets/1e9f9315-eb71-497a-b6e2-2b11eb6bbf7f)

Behavior after applying this PR, which matches the Notepad behavior:

![7914-7967](https://github.com/user-attachments/assets/26f12b9f-9354-45b8-b2a8-ede28c34c5b1)

Behavior after applying emilk#7914 with the “IME mode” set to Korean (which
is also the behavior before this PR being applied):

![7914-7914](https://github.com/user-attachments/assets/0f82a019-c491-4b64-a92a-d88d62dfbd84)
On the second line, each time a Hanja character is confirmed, an
unintended newline is inserted. This mirrors the Japanese IME issues
that are supposed to be fixed by setting the “IME mode” to Japanese.
(These Japanese IME issues are fixed in this PR as mentioned before.)

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect typing behavior with TextEdit singleline and multiline with IbusWayland enabled Cannot use IME when cursor position != 0

3 participants