From bdc8314235a73ef8b6079efdef00bdfdd39c13ca Mon Sep 17 00:00:00 2001 From: Shun-ichi Goto Date: Wed, 24 Jun 2026 19:30:36 +0900 Subject: [PATCH] Respect IME preedit cursor position on Win32 (#21632) --- .../Avalonia.Win32/Input/Imm32InputMethod.cs | 80 +++++++++++++++---- 1 file changed, 63 insertions(+), 17 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 65827607c1..588e7a134b 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -25,6 +25,7 @@ namespace Avalonia.Win32.Input private const int CaretMargin = 1; private bool _ignoreComposition; + private int? _compositionCursorPosition; public TextInputMethodClient? Client { get; private set; } @@ -110,6 +111,7 @@ namespace Avalonia.Win32.Input _langId = 0; IsComposing = false; + _compositionCursorPosition = null; } //Dependant on CurrentThread. When Avalonia will support Multiple Dispatchers - @@ -118,6 +120,8 @@ namespace Avalonia.Win32.Input public void Reset() { + _compositionCursorPosition = null; + Dispatcher.UIThread.Post(() => { var himc = ImmGetContext(Hwnd); @@ -141,6 +145,7 @@ namespace Avalonia.Win32.Input IsComposing = false; Composition = null; + _compositionCursorPosition = null; } }); } @@ -150,8 +155,9 @@ namespace Avalonia.Win32.Input if(Client != null) { Composition = null; + _compositionCursorPosition = null; - Client.SetPreeditText(null); + Client.SetPreeditText(null, null); } Client = client; @@ -266,16 +272,17 @@ namespace Avalonia.Win32.Input // we're skipping this. not usable on windows } - public void CompositionChanged(string? composition) + public void CompositionChanged(string? composition, int? cursorPosition) { Composition = composition; + _compositionCursorPosition = cursorPosition; if (!IsActive || !Client.SupportsPreedit) { return; } - Client.SetPreeditText(composition); + Client.SetPreeditText(composition, cursorPosition); } public string? GetCompositionString(GCS flag) @@ -290,13 +297,40 @@ namespace Avalonia.Win32.Input return ImmGetCompositionString(himc, flag); } + private int? GetCompositionCursorPosition() + { + if (!IsComposing) + { + return null; + } + + var himc = ImmGetContext(Hwnd); + + if (himc == IntPtr.Zero) + { + return null; + } + + try + { + var cursorPosition = ImmGetCompositionString(himc, GCS.GCS_CURSORPOS, IntPtr.Zero, 0); + + return cursorPosition >= 0 ? cursorPosition : null; + } + finally + { + ImmReleaseContext(Hwnd, himc); + } + } + public void HandleCompositionStart() { Composition = null; + _compositionCursorPosition = null; if (IsActive) { - Client.SetPreeditText(null); + Client.SetPreeditText(null, null); if (Client.SupportsSurroundingText && Client.Selection.Start != Client.Selection.End) { @@ -325,10 +359,11 @@ namespace Avalonia.Win32.Input } Composition = null; + _compositionCursorPosition = null; if (IsActive) { - Client.SetPreeditText(null); + Client.SetPreeditText(null, null); } } @@ -342,25 +377,27 @@ namespace Avalonia.Win32.Input } var flags = (GCS)ToInt32(lParam); + var resultChanged = (flags & GCS.GCS_RESULTSTR) != 0; if (flags == 0) { - CompositionChanged(""); + CompositionChanged("", null); } - if ((flags & GCS.GCS_RESULTSTR) != 0) + if (resultChanged) { var resultString = GetCompositionString(GCS.GCS_RESULTSTR); - if (_parent != null && !string.IsNullOrEmpty(resultString)) - { - Composition = null; + Composition = null; + _compositionCursorPosition = null; - if (IsActive) - { - Client.SetPreeditText(null); - } + if (IsActive) + { + Client.SetPreeditText(null, null); + } + if (_parent != null && !string.IsNullOrEmpty(resultString)) + { var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _parent.Owner, resultString); if (_parent.Input != null) @@ -372,11 +409,20 @@ namespace Avalonia.Win32.Input } } - if ((flags & GCS.GCS_COMPSTR) != 0) + var compositionChanged = (flags & GCS.GCS_COMPSTR) != 0; + var cursorPositionChanged = (flags & GCS.GCS_CURSORPOS) != 0; + + if (compositionChanged || (cursorPositionChanged && !resultChanged)) { - var compositionString = GetCompositionString(GCS.GCS_COMPSTR); + var compositionString = compositionChanged + ? GetCompositionString(GCS.GCS_COMPSTR) + : Composition; + + var cursorPosition = cursorPositionChanged + ? GetCompositionCursorPosition() + : _compositionCursorPosition; - CompositionChanged(compositionString); + CompositionChanged(compositionString, cursorPosition); } }