From c5d8715e1e049738754a040b74450279a0b7f4bc Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 9 Jun 2023 08:38:37 +0200 Subject: [PATCH 1/2] Rework Imm32InputMethod WM handling --- .../Avalonia.Win32/Input/Imm32InputMethod.cs | 83 +++++++++++++++++++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 56 ++----------- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 3 files changed, 93 insertions(+), 48 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index aabf361844..4c8299bf8f 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Text; +using Avalonia.Input; +using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Threading; @@ -297,6 +299,87 @@ namespace Avalonia.Win32.Input return ImmGetCompositionString(himc, flag); } + public void HandleCompositionStart() + { + Composition = null; + + if (IsActive) + { + Client.SetPreeditText(null); + } + + IsComposing = true; + } + + public void HandleCompositionEnd(WindowImpl windowImpl, uint timestamp) + { + var currentComposition = Composition; + + //In case composition has not been comitted yet we need to do that here. + if (!string.IsNullOrEmpty(currentComposition)) + { + var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, windowImpl.Owner, currentComposition); + + if(windowImpl.Input != null) + { + windowImpl.Input(e); + } + } + + //Cleanup composition state. + IsComposing = false; + Composition = null; + + if (IsActive) + { + Client.SetPreeditText(null); + } + } + + public void HandleComposition(WindowImpl windowImpl, IntPtr wParam, IntPtr lParam, uint timestamp, ref bool ignoreWmChar) + { + var flags = (GCS)ToInt32(lParam); + + if ((flags & GCS.GCS_RESULTSTR) != 0) + { + var resultString = GetCompositionString(GCS.GCS_RESULTSTR); + + if (!string.IsNullOrEmpty(resultString)) + { + Composition = null; + + if (IsActive) + { + Client.SetPreeditText(null); + } + + var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, windowImpl.Owner, resultString); + + if(windowImpl.Input != null) + { + windowImpl.Input(e); + + ignoreWmChar = true; + } + } + } + + if ((flags & GCS.GCS_COMPSTR) != 0) + { + var compositionString = GetCompositionString(GCS.GCS_COMPSTR); + + CompositionChanged(compositionString); + } + } + + private static int ToInt32(IntPtr ptr) + { + if (IntPtr.Size == 4) + return ptr.ToInt32(); + + return (int)(ptr.ToInt64() & 0xffffffff); + } + ~Imm32InputMethod() { _caretManager.TryDestroy(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index b256a9433d..252410264e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -87,7 +87,7 @@ namespace Avalonia.Win32 { // The first and foremost thing to do - notify the TopLevel Closed?.Invoke(); - + if (UiaCoreTypesApi.IsNetComInteropAvailable) { UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); @@ -98,7 +98,7 @@ namespace Avalonia.Win32 { Imm32InputMethod.Current.ClearLanguageAndWindow(); } - + // Cleanup render targets (_gl as IDisposable)?.Dispose(); @@ -724,26 +724,7 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_IME_COMPOSITION: { - var flags = (GCS)ToInt32(lParam); - - if ((flags & GCS.GCS_COMPSTR) != 0) - { - var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR); - - Imm32InputMethod.Current.CompositionChanged(currentComposition); - } - - if ((flags & GCS.GCS_RESULTSTR) != 0) - { - var result = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_RESULTSTR); - - if (!string.IsNullOrEmpty(result)) - { - Imm32InputMethod.Current.Composition = result; - - _ignoreWmChar = true; - } - } + Imm32InputMethod.Current.HandleComposition(this, wParam, lParam, timestamp, ref _ignoreWmChar); break; } @@ -757,35 +738,16 @@ namespace Avalonia.Win32 case WindowsMessage.WM_IME_NOTIFY: break; case WindowsMessage.WM_IME_STARTCOMPOSITION: - Imm32InputMethod.Current.Composition = null; - - if (Imm32InputMethod.Current.IsActive) { - Imm32InputMethod.Current.Client.SetPreeditText(null); - } + Imm32InputMethod.Current.HandleCompositionStart(); - Imm32InputMethod.Current.IsComposing = true; - return IntPtr.Zero; + return IntPtr.Zero; + } case WindowsMessage.WM_IME_ENDCOMPOSITION: { - var currentComposition = Imm32InputMethod.Current.Composition; - - //In case composition has not been comitted yet we need to do that here. - if (!string.IsNullOrEmpty(currentComposition)) - { - e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, Owner, currentComposition); - } - - //Cleanup composition state. - Imm32InputMethod.Current.IsComposing = false; - Imm32InputMethod.Current.Composition = null; + Imm32InputMethod.Current.HandleCompositionEnd(this, timestamp); - if (Imm32InputMethod.Current.IsActive) - { - Imm32InputMethod.Current.Client.SetPreeditText(null); - } - - break; + return IntPtr.Zero; } case WindowsMessage.WM_GETOBJECT: if ((long)lParam == uiaRootObjectId && UiaCoreTypesApi.IsNetComInteropAvailable && _owner is Control control) @@ -830,7 +792,7 @@ namespace Avalonia.Win32 return IntPtr.Zero; } } - + return DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4260f90e9f..81257667af 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -186,7 +186,7 @@ namespace Avalonia.Win32 s_instances.Add(this); } - private IInputRoot Owner + internal IInputRoot Owner => _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called"); public Action? Activated { get; set; } From 281979d80ddcf7d731843f328f1e48ecad049a39 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 9 Jun 2023 09:32:28 +0200 Subject: [PATCH 2/2] Properly reset IMM32 state --- .../Input/TextInput/InputMethodManager.cs | 10 ++- .../Avalonia.Win32/Input/Imm32InputMethod.cs | 61 +++++++++++-------- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 4 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 4 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 1c61334888..c7fca04ea9 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -22,11 +22,18 @@ namespace Avalonia.Input.TextInput set { if(_client == value) + { return; + } + if (_client != null) { _client.CursorRectangleChanged -= OnCursorRectangleChanged; _client.TextViewVisualChanged -= OnTextViewVisualChanged; + + _client = null; + + _im?.Reset(); } _client = value; @@ -35,8 +42,6 @@ namespace Avalonia.Input.TextInput { _client.CursorRectangleChanged += OnCursorRectangleChanged; _client.TextViewVisualChanged += OnTextViewVisualChanged; - - _im?.Reset(); if (_focusedElement is StyledElement target) { @@ -50,6 +55,7 @@ namespace Avalonia.Input.TextInput _transformTracker.SetVisual(_client?.TextViewVisual); _im?.SetClient(_client); + UpdateCursorRect(); } else diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 4c8299bf8f..05074cc82e 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -24,6 +24,8 @@ namespace Avalonia.Win32.Input private ushort _langId; private const int CaretMargin = 1; + private bool _ignoreComposition; + public ITextInputMethodClient? Client { get; private set; } [MemberNotNullWhen(true, nameof(Client))] @@ -123,19 +125,35 @@ namespace Avalonia.Win32.Input { var himc = ImmGetContext(Hwnd); - if (IsComposing) + if (himc != IntPtr.Zero) { + _ignoreComposition = true; + + if (_parent != null) + { + _parent._ignoreWmChar = true; + } + ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - + + ImmReleaseContext(Hwnd, himc); + IsComposing = false; - } - ImmReleaseContext(Hwnd, himc); + Composition = null; + } }); } public void SetClient(ITextInputMethodClient? client) { + if(Client != null) + { + Composition = null; + + Client.SetPreeditText(null); + } + Client = client; Dispatcher.UIThread.Post(() => @@ -311,23 +329,11 @@ namespace Avalonia.Win32.Input IsComposing = true; } - public void HandleCompositionEnd(WindowImpl windowImpl, uint timestamp) + public void HandleCompositionEnd() { - var currentComposition = Composition; - - //In case composition has not been comitted yet we need to do that here. - if (!string.IsNullOrEmpty(currentComposition)) - { - var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, windowImpl.Owner, currentComposition); - - if(windowImpl.Input != null) - { - windowImpl.Input(e); - } - } - //Cleanup composition state. IsComposing = false; + Composition = null; if (IsActive) @@ -336,15 +342,22 @@ namespace Avalonia.Win32.Input } } - public void HandleComposition(WindowImpl windowImpl, IntPtr wParam, IntPtr lParam, uint timestamp, ref bool ignoreWmChar) + public void HandleComposition(IntPtr wParam, IntPtr lParam, uint timestamp) { + if (_ignoreComposition) + { + _ignoreComposition = false; + + return; + } + var flags = (GCS)ToInt32(lParam); if ((flags & GCS.GCS_RESULTSTR) != 0) { var resultString = GetCompositionString(GCS.GCS_RESULTSTR); - if (!string.IsNullOrEmpty(resultString)) + if (_parent != null && !string.IsNullOrEmpty(resultString)) { Composition = null; @@ -353,13 +366,13 @@ namespace Avalonia.Win32.Input Client.SetPreeditText(null); } - var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, windowImpl.Owner, resultString); + var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _parent.Owner, resultString); - if(windowImpl.Input != null) + if (_parent.Input != null) { - windowImpl.Input(e); + _parent.Input(e); - ignoreWmChar = true; + _parent._ignoreWmChar = true; } } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 252410264e..9a7f4b62e3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -724,7 +724,7 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_IME_COMPOSITION: { - Imm32InputMethod.Current.HandleComposition(this, wParam, lParam, timestamp, ref _ignoreWmChar); + Imm32InputMethod.Current.HandleComposition(wParam, lParam, timestamp); break; } @@ -745,7 +745,7 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_IME_ENDCOMPOSITION: { - Imm32InputMethod.Current.HandleCompositionEnd(this, timestamp); + Imm32InputMethod.Current.HandleCompositionEnd(); return IntPtr.Zero; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 81257667af..057cdb2db0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -97,7 +97,7 @@ namespace Avalonia.Win32 private bool _shown; private bool _hiddenWindowIsParent; private uint _langid; - private bool _ignoreWmChar; + internal bool _ignoreWmChar; private WindowTransparencyLevel _transparencyLevel; private const int MaxPointerHistorySize = 512;