From c326537f9f21574af97323bc8a0503f37c0ef399 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Thu, 7 Mar 2024 00:37:46 +0900 Subject: [PATCH 1/3] test --- Dalamud/Interface/Internal/DalamudCommands.cs | 11 - Dalamud/Interface/Internal/DalamudIme.cs | 512 +++++++++++++----- .../Interface/Internal/DalamudInterface.cs | 18 - .../Interface/Internal/InterfaceManager.cs | 5 - .../Internal/Windows/DalamudImeWindow.cs | 266 --------- Dalamud/ServiceManager.cs | 15 +- 6 files changed, 383 insertions(+), 444 deletions(-) delete mode 100644 Dalamud/Interface/Internal/Windows/DalamudImeWindow.cs diff --git a/Dalamud/Interface/Internal/DalamudCommands.cs b/Dalamud/Interface/Internal/DalamudCommands.cs index ace8887f1..b64df8f19 100644 --- a/Dalamud/Interface/Internal/DalamudCommands.cs +++ b/Dalamud/Interface/Internal/DalamudCommands.cs @@ -96,12 +96,6 @@ internal class DalamudCommands : IServiceType ShowInHelp = false, }); - commandManager.AddHandler("/xlime", new CommandInfo(this.OnDebugDrawIMEPanel) - { - HelpMessage = Loc.Localize("DalamudIMEPanelHelp", "Draw IME panel"), - ShowInHelp = false, - }); - commandManager.AddHandler("/xllog", new CommandInfo(this.OnOpenLog) { HelpMessage = Loc.Localize("DalamudDevLogHelp", "Open dev log DEBUG"), @@ -308,11 +302,6 @@ internal class DalamudCommands : IServiceType dalamudInterface.ToggleDataWindow(arguments); } - private void OnDebugDrawIMEPanel(string command, string arguments) - { - Service.Get().OpenImeWindow(); - } - private void OnOpenLog(string command, string arguments) { Service.Get().ToggleLogWindow(); diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index 1ee248b17..6c01b74d7 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -17,6 +18,8 @@ using Dalamud.Interface.Utility; using ImGuiNET; +using Serilog; + using TerraFX.Interop.Windows; using static TerraFX.Interop.Windows.Windows; @@ -26,12 +29,21 @@ namespace Dalamud.Interface.Internal; /// /// This class handles CJK IME. /// -[ServiceManager.BlockingEarlyLoadedService] +[ServiceManager.EarlyLoadedService] internal sealed unsafe class DalamudIme : IDisposable, IServiceType { private const int CImGuiStbTextCreateUndoOffset = 0xB57A0; private const int CImGuiStbTextUndoOffset = 0xB59C0; + private const int ImePageSize = 9; + + private static readonly Dictionary WmNames = + typeof(WM).GetFields(BindingFlags.Public | BindingFlags.Static) + .Where(x => x.IsLiteral && !x.IsInitOnly && x.FieldType == typeof(int)) + .Select(x => ((int)x.GetRawConstantValue()!, x.Name)) + .DistinctBy(x => x.Item1) + .ToDictionary(x => x.Item1, x => x.Name); + private static readonly UnicodeRange[] HanRange = { UnicodeRanges.CjkRadicalsSupplement, @@ -57,8 +69,41 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType private static readonly delegate* unmanaged StbTextUndo; + [ServiceManager.ServiceDependency] + private readonly WndProcHookManager wndProcHookManager = Service.Get(); + + private readonly InterfaceManager interfaceManager; + private readonly ImGuiSetPlatformImeDataDelegate setPlatformImeDataDelegate; + /// The candidates. + private readonly List candidateStrings = new(); + + /// The selected imm component. + private string compositionString = string.Empty; + + /// The cursor position in screen coordinates. + private Vector2 cursorScreenPos; + + /// The associated viewport. + private ImGuiViewportPtr associatedViewport; + + /// The index of the first imm candidate in relation to the full list. + private CANDIDATELIST immCandNative; + + /// The partial conversion from-range. + private int partialConversionFrom; + + /// The partial conversion to-range. + private int partialConversionTo; + + /// The cursor offset in the composition string. + private int compositionCursorOffset; + + /// The input mode icon from . + private char inputModeIcon; + + /// Undo range for modifying the buffer while composition is in progress. private (int Start, int End, int Cursor)? temporaryUndoSelection; [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1003:Symbols should be spaced correctly", Justification = ".")] @@ -87,7 +132,17 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } [ServiceManager.ServiceConstructor] - private DalamudIme() => this.setPlatformImeDataDelegate = this.ImGuiSetPlatformImeData; + private DalamudIme(InterfaceManager.InterfaceManagerWithScene imws) + { + Debug.Assert(ImGuiHelpers.IsImGuiInitialized, "IMWS initialized but IsImGuiInitialized is false?"); + + this.interfaceManager = imws.Manager; + this.setPlatformImeDataDelegate = this.ImGuiSetPlatformImeData; + + ImGui.GetIO().SetPlatformImeDataFn = Marshal.GetFunctionPointerForDelegate(this.setPlatformImeDataDelegate); + this.interfaceManager.Draw += this.Draw; + this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc; + } /// /// Finalizes an instance of the class. @@ -109,7 +164,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType /// /// Gets a value indicating whether to display the cursor in input text. This also deals with blinking. /// - internal static bool ShowCursorInInputText + private static bool ShowCursorInInputText { get { @@ -126,63 +181,21 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } } - /// - /// Gets the cursor position, in screen coordinates. - /// - internal Vector2 CursorPos { get; private set; } - - /// - /// Gets the associated viewport. - /// - internal ImGuiViewportPtr AssociatedViewport { get; private set; } - - /// - /// Gets the index of the first imm candidate in relation to the full list. - /// - internal CANDIDATELIST ImmCandNative { get; private set; } - - /// - /// Gets the imm candidates. - /// - internal List ImmCand { get; private set; } = new(); - - /// - /// Gets the selected imm component. - /// - internal string ImmComp { get; private set; } = string.Empty; - - /// - /// Gets the partial conversion from-range. - /// - internal int PartialConversionFrom { get; private set; } - - /// - /// Gets the partial conversion to-range. - /// - internal int PartialConversionTo { get; private set; } - - /// - /// Gets the cursor offset in the composition string. - /// - internal int CompositionCursorOffset { get; private set; } - - /// - /// Gets a value indicating whether to display partial conversion status. - /// - internal bool ShowPartialConversion => this.PartialConversionFrom != 0 || - this.PartialConversionTo != this.ImmComp.Length; - - /// - /// Gets the input mode icon from . - /// - internal char InputModeIcon { get; private set; } - private static ImGuiInputTextState* TextState => (ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextOffsets.TextStateOffset); + /// Gets a value indicating whether to display partial conversion status. + private bool ShowPartialConversion => this.partialConversionFrom != 0 || + this.partialConversionTo != this.compositionString.Length; + + /// Gets a value indicating whether to draw. + private bool ShouldDraw => + this.candidateStrings.Count != 0 || this.ShowPartialConversion || this.inputModeIcon != default; + /// public void Dispose() { + this.interfaceManager.Draw -= this.Draw; this.ReleaseUnmanagedResources(); GC.SuppressFinalize(this); } @@ -195,13 +208,13 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType { foreach (var chr in str) { - if (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length)) + if (!this.EncounteredHan) { - if (Service.Get() - ?.GetFdtReader(GameFontFamilyAndSize.Axis12) - .FindGlyph(chr) is null) + if (HanRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length)) { - if (!this.EncounteredHan) + if (Service.Get() + ?.GetFdtReader(GameFontFamilyAndSize.Axis12) + .FindGlyph(chr) is null) { this.EncounteredHan = true; Service.Get().RebuildFonts(); @@ -209,9 +222,9 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } } - if (HangulRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length)) + if (!this.EncounteredHangul) { - if (!this.EncounteredHangul) + if (HangulRange.Any(x => x.FirstCodePoint <= chr && chr < x.FirstCodePoint + x.Length)) { this.EncounteredHangul = true; Service.Get().RebuildFonts(); @@ -220,11 +233,24 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } } - /// - /// Processes window messages. - /// - /// The arguments. - public void ProcessImeMessage(WndProcEventArgs args) + private static string ImmGetCompositionString(HIMC hImc, uint comp) + { + var numBytes = ImmGetCompositionStringW(hImc, comp, null, 0); + if (numBytes == 0) + return string.Empty; + + var data = stackalloc char[numBytes / 2]; + _ = ImmGetCompositionStringW(hImc, comp, data, (uint)numBytes); + return new(data, 0, numBytes / 2); + } + + private void ReleaseUnmanagedResources() + { + if (ImGuiHelpers.IsImGuiInitialized) + ImGui.GetIO().SetPlatformImeDataFn = nint.Zero; + } + + private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args) { if (!ImGuiHelpers.IsImGuiInitialized) return; @@ -246,7 +272,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType case WM.WM_IME_NOTIFY when (nint)args.WParam is IMN.IMN_OPENCANDIDATE or IMN.IMN_CLOSECANDIDATE or IMN.IMN_CHANGECANDIDATE: - this.UpdateImeWindowStatus(hImc); + this.UpdateCandidates(hImc); args.SuppressWithValue(0); break; @@ -260,22 +286,22 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType else this.ReplaceCompositionString(hImc, (uint)args.LParam); - // Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.ImmComp}"); + // Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithValue(0); break; case WM.WM_IME_ENDCOMPOSITION: - // Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}"); + // Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithValue(0); break; case WM.WM_IME_CONTROL: - // Log.Verbose($"{nameof(WM.WM_IME_CONTROL)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}"); + // Log.Verbose($"{nameof(WM.WM_IME_CONTROL)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithValue(0); break; case WM.WM_IME_REQUEST: - // Log.Verbose($"{nameof(WM.WM_IME_REQUEST)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}"); + // Log.Verbose($"{nameof(WM.WM_IME_REQUEST)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithValue(0); break; @@ -283,12 +309,12 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType // Hide candidate and composition windows. args.LParam = (LPARAM)((nint)args.LParam & ~(ISC_SHOWUICOMPOSITIONWINDOW | 0xF)); - // Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.ImmComp}"); + // Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithDefault(); break; case WM.WM_IME_NOTIFY: - // Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({(nint)args.WParam:X}): {this.ImmComp}"); + // Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({(nint)args.WParam:X}): {this.compositionString}"); break; case WM.WM_KEYDOWN when (int)args.WParam is @@ -302,12 +328,14 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType or VK.VK_RIGHT or VK.VK_DOWN or VK.VK_RETURN: - if (this.ImmCand.Count != 0) + if (this.candidateStrings.Count != 0) { this.ClearState(hImc); args.WParam = VK.VK_PROCESSKEY; } + this.UpdateCandidates(hImc); + break; case WM.WM_LBUTTONDOWN: @@ -316,9 +344,15 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType case WM.WM_XBUTTONDOWN: ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0); break; + + // default: + // Log.Verbose($"{(WmNames.TryGetValue((int)args.Message, out var v) ? v : args.Message.ToString())}({(nint)args.WParam:X}, {(nint)args.LParam:X})"); + // break; } this.UpdateInputLanguage(hImc); + if (this.inputModeIcon == (char)SeIconChar.ImeKoreanHangul) + this.UpdateCandidates(hImc); } finally { @@ -326,23 +360,6 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } } - private static string ImmGetCompositionString(HIMC hImc, uint comp) - { - var numBytes = ImmGetCompositionStringW(hImc, comp, null, 0); - if (numBytes == 0) - return string.Empty; - - var data = stackalloc char[numBytes / 2]; - _ = ImmGetCompositionStringW(hImc, comp, data, (uint)numBytes); - return new(data, 0, numBytes / 2); - } - - private void ReleaseUnmanagedResources() - { - if (ImGuiHelpers.IsImGuiInitialized) - ImGui.GetIO().SetPlatformImeDataFn = nint.Zero; - } - private void UpdateInputLanguage(HIMC hImc) { uint conv, sent; @@ -359,41 +376,39 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType { case LANG.LANG_KOREAN: if (native) - this.InputModeIcon = (char)SeIconChar.ImeKoreanHangul; + this.inputModeIcon = (char)SeIconChar.ImeKoreanHangul; else if (fullwidth) - this.InputModeIcon = (char)SeIconChar.ImeAlphanumeric; + this.inputModeIcon = (char)SeIconChar.ImeAlphanumeric; else - this.InputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth; + this.inputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth; break; case LANG.LANG_JAPANESE: // wtf // see the function called from: 48 8b 0d ?? ?? ?? ?? e8 ?? ?? ?? ?? 8b d8 e9 ?? 00 00 0 if (open && native && katakana && fullwidth) - this.InputModeIcon = (char)SeIconChar.ImeKatakana; + this.inputModeIcon = (char)SeIconChar.ImeKatakana; else if (open && native && katakana) - this.InputModeIcon = (char)SeIconChar.ImeKatakanaHalfWidth; + this.inputModeIcon = (char)SeIconChar.ImeKatakanaHalfWidth; else if (open && native) - this.InputModeIcon = (char)SeIconChar.ImeHiragana; + this.inputModeIcon = (char)SeIconChar.ImeHiragana; else if (open && fullwidth) - this.InputModeIcon = (char)SeIconChar.ImeAlphanumeric; + this.inputModeIcon = (char)SeIconChar.ImeAlphanumeric; else - this.InputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth; + this.inputModeIcon = (char)SeIconChar.ImeAlphanumericHalfWidth; break; case LANG.LANG_CHINESE: if (native) - this.InputModeIcon = (char)SeIconChar.ImeChineseHan; + this.inputModeIcon = (char)SeIconChar.ImeChineseHan; else - this.InputModeIcon = (char)SeIconChar.ImeChineseLatin; + this.inputModeIcon = (char)SeIconChar.ImeChineseLatin; break; default: - this.InputModeIcon = default; + this.inputModeIcon = default; break; } - - this.UpdateImeWindowStatus(hImc); } private void ReplaceCompositionString(HIMC hImc, uint comp) @@ -425,14 +440,14 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType return; } - this.ImmComp = newString; - this.CompositionCursorOffset = ImmGetCompositionStringW(hImc, GCS.GCS_CURSORPOS, null, 0); + this.compositionString = newString; + this.compositionCursorOffset = ImmGetCompositionStringW(hImc, GCS.GCS_CURSORPOS, null, 0); if ((comp & GCS.GCS_COMPATTR) != 0) { var attrLength = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, null, 0); var attrPtr = stackalloc byte[attrLength]; - var attr = new Span(attrPtr, Math.Min(this.ImmComp.Length, attrLength)); + var attr = new Span(attrPtr, Math.Min(this.compositionString.Length, attrLength)); _ = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, attrPtr, (uint)attrLength); var l = 0; while (l < attr.Length && attr[l] is not ATTR_TARGET_CONVERTED and not ATTR_TARGET_NOTCONVERTED) @@ -442,37 +457,37 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType while (r < attr.Length && attr[r] is ATTR_TARGET_CONVERTED or ATTR_TARGET_NOTCONVERTED) r++; - if (r == 0 || l == this.ImmComp.Length) - (l, r) = (0, this.ImmComp.Length); + if (r == 0 || l == this.compositionString.Length) + (l, r) = (0, this.compositionString.Length); - (this.PartialConversionFrom, this.PartialConversionTo) = (l, r); + (this.partialConversionFrom, this.partialConversionTo) = (l, r); } else { - this.PartialConversionFrom = 0; - this.PartialConversionTo = this.ImmComp.Length; + this.partialConversionFrom = 0; + this.partialConversionTo = this.compositionString.Length; } - this.UpdateImeWindowStatus(hImc); + this.UpdateCandidates(hImc); } private void ClearState(HIMC hImc) { - this.ImmComp = string.Empty; - this.PartialConversionFrom = this.PartialConversionTo = 0; - this.CompositionCursorOffset = 0; + this.compositionString = string.Empty; + this.partialConversionFrom = this.partialConversionTo = 0; + this.compositionCursorOffset = 0; this.temporaryUndoSelection = null; TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd; ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0); - this.UpdateImeWindowStatus(default); + this.UpdateCandidates(default); // Log.Information($"{nameof(this.ClearState)}"); } - private void LoadCand(HIMC hImc) + private void UpdateCandidates(HIMC hImc) { - this.ImmCand.Clear(); - this.ImmCandNative = default; + this.candidateStrings.Clear(); + this.immCandNative = default; if (hImc == default) return; @@ -486,7 +501,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType return; ref var candlist = ref *(CANDIDATELIST*)pStorage; - this.ImmCandNative = candlist; + this.immCandNative = candlist; if (candlist.dwPageSize == 0 || candlist.dwCount == 0) return; @@ -495,39 +510,250 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType (int)candlist.dwPageStart, (int)Math.Min(candlist.dwCount - candlist.dwPageStart, candlist.dwPageSize))) { - this.ImmCand.Add(new((char*)(pStorage + candlist.dwOffset[i]))); - this.ReflectCharacterEncounters(this.ImmCand[^1]); + this.candidateStrings.Add(new((char*)(pStorage + candlist.dwOffset[i]))); + this.ReflectCharacterEncounters(this.candidateStrings[^1]); } } - private void UpdateImeWindowStatus(HIMC hImc) - { - if (Service.GetNullable() is not { } di) - return; - - this.LoadCand(hImc); - if (this.ImmCand.Count != 0 || this.ShowPartialConversion || this.InputModeIcon != default) - di.OpenImeWindow(); - else - di.CloseImeWindow(); - } - private void ImGuiSetPlatformImeData(ImGuiViewportPtr viewport, ImGuiPlatformImeDataPtr data) { - this.CursorPos = data.InputPos; - this.AssociatedViewport = data.WantVisible ? viewport : default; + this.cursorScreenPos = data.InputPos; + this.associatedViewport = data.WantVisible ? viewport : default; } - [ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui context initialization.")] - private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene) + private void Draw() { - if (!ImGuiHelpers.IsImGuiInitialized) + if (!this.ShouldDraw) + return; + + if (Service.GetNullable() is not { } ime) + return; + + var viewport = ime.associatedViewport; + if (viewport.NativePtr is null) + return; + + var drawCand = ime.candidateStrings.Count != 0; + var drawConv = drawCand || ime.ShowPartialConversion; + var drawIme = ime.inputModeIcon != 0; + var imeIconFont = InterfaceManager.DefaultFont; + + var pad = ImGui.GetStyle().WindowPadding; + var candTextSize = ImGui.CalcTextSize(ime.compositionString == string.Empty ? " " : ime.compositionString); + + var native = ime.immCandNative; + var totalIndex = native.dwSelection + 1; + var totalSize = native.dwCount; + + var pageStart = native.dwPageStart; + var pageIndex = (pageStart / ImePageSize) + 1; + var pageCount = (totalSize / ImePageSize) + 1; + var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"; + + // Calc the window size. + var maxTextWidth = 0f; + for (var i = 0; i < ime.candidateStrings.Count; i++) { - throw new InvalidOperationException( - $"Expected {nameof(InterfaceManager.InterfaceManagerWithScene)} to have initialized ImGui."); + var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.candidateStrings[i]}"); + maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X; } - ImGui.GetIO().SetPlatformImeDataFn = Marshal.GetFunctionPointerForDelegate(this.setPlatformImeDataDelegate); + maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X; + maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.compositionString).X + ? maxTextWidth + : ImGui.CalcTextSize(ime.compositionString).X; + + var numEntries = (drawCand ? ime.candidateStrings.Count + 1 : 0) + 1 + (drawIme ? 1 : 0); + var spaceY = ImGui.GetStyle().ItemSpacing.Y; + var imeWindowHeight = (spaceY * (numEntries - 1)) + (candTextSize.Y * numEntries); + var windowSize = new Vector2(maxTextWidth, imeWindowHeight) + (pad * 2); + + // 1. Figure out the expanding direction. + var expandUpward = ime.cursorScreenPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y; + var windowPos = ime.cursorScreenPos - pad; + if (expandUpward) + { + windowPos.Y -= windowSize.Y - candTextSize.Y - (pad.Y * 2); + if (drawIme) + windowPos.Y += candTextSize.Y + spaceY; + } + else + { + if (drawIme) + windowPos.Y -= candTextSize.Y + spaceY; + } + + // 2. Contain within the viewport. Do not use clamp, as the target window might be too small. + if (windowPos.X < viewport.WorkPos.X) + windowPos.X = viewport.WorkPos.X; + else if (windowPos.X + windowSize.X > viewport.WorkPos.X + viewport.WorkSize.X) + windowPos.X = (viewport.WorkPos.X + viewport.WorkSize.X) - windowSize.X; + if (windowPos.Y < viewport.WorkPos.Y) + windowPos.Y = viewport.WorkPos.Y; + else if (windowPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y) + windowPos.Y = (viewport.WorkPos.Y + viewport.WorkSize.Y) - windowSize.Y; + + var cursor = windowPos + pad; + + // Draw the ime window. + var drawList = ImGui.GetForegroundDrawList(viewport); + + // Draw the background rect for candidates. + if (drawCand) + { + Vector2 candRectLt, candRectRb; + if (!expandUpward) + { + candRectLt = windowPos + candTextSize with { X = 0 } + pad with { X = 0 }; + candRectRb = windowPos + windowSize; + if (drawIme) + candRectLt.Y += spaceY + candTextSize.Y; + } + else + { + candRectLt = windowPos; + candRectRb = windowPos + (windowSize - candTextSize with { X = 0 } - pad with { X = 0 }); + if (drawIme) + candRectRb.Y -= spaceY + candTextSize.Y; + } + + drawList.AddRectFilled( + candRectLt, + candRectRb, + ImGui.GetColorU32(ImGuiCol.WindowBg), + ImGui.GetStyle().WindowRounding); + } + + if (!expandUpward && drawIme) + { + for (var dx = -2; dx <= 2; dx++) + { + for (var dy = -2; dy <= 2; dy++) + { + if (dx != 0 || dy != 0) + { + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor + new Vector2(dx, dy), + ImGui.GetColorU32(ImGuiCol.WindowBg), + ime.inputModeIcon); + } + } + } + + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor, + ImGui.GetColorU32(ImGuiCol.Text), + ime.inputModeIcon); + cursor.Y += candTextSize.Y + spaceY; + } + + if (!expandUpward && drawConv) + { + DrawTextBeingConverted(); + cursor.Y += candTextSize.Y + spaceY; + + // Add a separator. + drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator)); + } + + if (drawCand) + { + // Add the candidate words. + for (var i = 0; i < ime.candidateStrings.Count; i++) + { + var selected = i == (native.dwSelection % ImePageSize); + var color = ImGui.GetColorU32(ImGuiCol.Text); + if (selected) + color = ImGui.GetColorU32(ImGuiCol.NavHighlight); + + drawList.AddText(cursor, color, $"{i + 1}. {ime.candidateStrings[i]}"); + cursor.Y += candTextSize.Y + spaceY; + } + + // Add a separator + drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator)); + + // Add the pages infomation. + drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), pageInfo); + cursor.Y += candTextSize.Y + spaceY; + } + + if (expandUpward && drawConv) + { + // Add a separator. + drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator)); + + DrawTextBeingConverted(); + cursor.Y += candTextSize.Y + spaceY; + } + + if (expandUpward && drawIme) + { + for (var dx = -2; dx <= 2; dx++) + { + for (var dy = -2; dy <= 2; dy++) + { + if (dx != 0 || dy != 0) + { + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor + new Vector2(dx, dy), + ImGui.GetColorU32(ImGuiCol.WindowBg), + ime.inputModeIcon); + } + } + } + + imeIconFont.RenderChar( + drawList, + imeIconFont.FontSize, + cursor, + ImGui.GetColorU32(ImGuiCol.Text), + ime.inputModeIcon); + } + + return; + + void DrawTextBeingConverted() + { + // Draw the text background. + drawList.AddRectFilled( + cursor - (pad / 2), + cursor + candTextSize + (pad / 2), + ImGui.GetColorU32(ImGuiCol.WindowBg)); + + // If only a part of the full text is marked for conversion, then draw background for the part being edited. + if (ime.partialConversionFrom != 0 || ime.partialConversionTo != ime.compositionString.Length) + { + var part1 = ime.compositionString[..ime.partialConversionFrom]; + var part2 = ime.compositionString[..ime.partialConversionTo]; + var size1 = ImGui.CalcTextSize(part1); + var size2 = ImGui.CalcTextSize(part2); + drawList.AddRectFilled( + cursor + size1 with { Y = 0 }, + cursor + size2, + ImGui.GetColorU32(ImGuiCol.TextSelectedBg)); + } + + // Add the text being converted. + drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), ime.compositionString); + + // Draw the caret inside the composition string. + if (DalamudIme.ShowCursorInInputText) + { + var partBeforeCaret = ime.compositionString[..ime.compositionCursorOffset]; + var sizeBeforeCaret = ImGui.CalcTextSize(partBeforeCaret); + drawList.AddLine( + cursor + sizeBeforeCaret with { Y = 0 }, + cursor + sizeBeforeCaret, + ImGui.GetColorU32(ImGuiCol.Text)); + } + } } /// diff --git a/Dalamud/Interface/Internal/DalamudInterface.cs b/Dalamud/Interface/Internal/DalamudInterface.cs index 00bef19af..1a07cd6ae 100644 --- a/Dalamud/Interface/Internal/DalamudInterface.cs +++ b/Dalamud/Interface/Internal/DalamudInterface.cs @@ -61,7 +61,6 @@ internal class DalamudInterface : IDisposable, IServiceType private readonly ComponentDemoWindow componentDemoWindow; private readonly DataWindow dataWindow; private readonly GamepadModeNotifierWindow gamepadModeNotifierWindow; - private readonly DalamudImeWindow imeWindow; private readonly ConsoleWindow consoleWindow; private readonly PluginStatWindow pluginStatWindow; private readonly PluginInstallerWindow pluginWindow; @@ -114,7 +113,6 @@ internal class DalamudInterface : IDisposable, IServiceType this.componentDemoWindow = new ComponentDemoWindow() { IsOpen = false }; this.dataWindow = new DataWindow() { IsOpen = false }; this.gamepadModeNotifierWindow = new GamepadModeNotifierWindow() { IsOpen = false }; - this.imeWindow = new DalamudImeWindow() { IsOpen = false }; this.consoleWindow = new ConsoleWindow(configuration) { IsOpen = configuration.LogOpenAtStartup }; this.pluginStatWindow = new PluginStatWindow() { IsOpen = false }; this.pluginWindow = new PluginInstallerWindow(pluginImageCache, configuration) { IsOpen = false }; @@ -142,7 +140,6 @@ internal class DalamudInterface : IDisposable, IServiceType this.WindowSystem.AddWindow(this.componentDemoWindow); this.WindowSystem.AddWindow(this.dataWindow); this.WindowSystem.AddWindow(this.gamepadModeNotifierWindow); - this.WindowSystem.AddWindow(this.imeWindow); this.WindowSystem.AddWindow(this.consoleWindow); this.WindowSystem.AddWindow(this.pluginStatWindow); this.WindowSystem.AddWindow(this.pluginWindow); @@ -265,11 +262,6 @@ internal class DalamudInterface : IDisposable, IServiceType /// public void OpenGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.IsOpen = true; - /// - /// Opens the . - /// - public void OpenImeWindow() => this.imeWindow.IsOpen = true; - /// /// Opens the . /// @@ -365,11 +357,6 @@ internal class DalamudInterface : IDisposable, IServiceType #region Close - /// - /// Closes the . - /// - public void CloseImeWindow() => this.imeWindow.IsOpen = false; - /// /// Closes the . /// @@ -417,11 +404,6 @@ internal class DalamudInterface : IDisposable, IServiceType /// public void ToggleGamepadModeNotifierWindow() => this.gamepadModeNotifierWindow.Toggle(); - /// - /// Toggles the . - /// - public void ToggleImeWindow() => this.imeWindow.Toggle(); - /// /// Toggles the . /// diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs index 3db799be0..126097ed3 100644 --- a/Dalamud/Interface/Internal/InterfaceManager.cs +++ b/Dalamud/Interface/Internal/InterfaceManager.cs @@ -67,9 +67,6 @@ internal class InterfaceManager : IDisposable, IServiceType [ServiceManager.ServiceDependency] private readonly WndProcHookManager wndProcHookManager = Service.Get(); - - [ServiceManager.ServiceDependency] - private readonly DalamudIme dalamudIme = Service.Get(); private readonly SwapChainVtableResolver address = new(); private readonly Hook setCursorHook; @@ -627,8 +624,6 @@ internal class InterfaceManager : IDisposable, IServiceType var r = this.scene?.ProcessWndProcW(args.Hwnd, (User32.WindowMessage)args.Message, args.WParam, args.LParam); if (r is not null) args.SuppressWithValue(r.Value); - - this.dalamudIme.ProcessImeMessage(args); } /* diff --git a/Dalamud/Interface/Internal/Windows/DalamudImeWindow.cs b/Dalamud/Interface/Internal/Windows/DalamudImeWindow.cs deleted file mode 100644 index ecaa522e5..000000000 --- a/Dalamud/Interface/Internal/Windows/DalamudImeWindow.cs +++ /dev/null @@ -1,266 +0,0 @@ -using System.Numerics; - -using Dalamud.Interface.Windowing; - -using ImGuiNET; - -namespace Dalamud.Interface.Internal.Windows; - -/// -/// A window for displaying IME details. -/// -internal unsafe class DalamudImeWindow : Window -{ - private const int ImePageSize = 9; - - /// - /// Initializes a new instance of the class. - /// - public DalamudImeWindow() - : base( - "Dalamud IME", - ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoBackground) - { - this.Size = default(Vector2); - - this.RespectCloseHotkey = false; - } - - /// - public override void Draw() - { - } - - /// - public override void PostDraw() - { - if (Service.GetNullable() is not { } ime) - return; - - var viewport = ime.AssociatedViewport; - if (viewport.NativePtr is null) - return; - - var drawCand = ime.ImmCand.Count != 0; - var drawConv = drawCand || ime.ShowPartialConversion; - var drawIme = ime.InputModeIcon != 0; - var imeIconFont = InterfaceManager.DefaultFont; - - var pad = ImGui.GetStyle().WindowPadding; - var candTextSize = ImGui.CalcTextSize(ime.ImmComp == string.Empty ? " " : ime.ImmComp); - - var native = ime.ImmCandNative; - var totalIndex = native.dwSelection + 1; - var totalSize = native.dwCount; - - var pageStart = native.dwPageStart; - var pageIndex = (pageStart / ImePageSize) + 1; - var pageCount = (totalSize / ImePageSize) + 1; - var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"; - - // Calc the window size. - var maxTextWidth = 0f; - for (var i = 0; i < ime.ImmCand.Count; i++) - { - var textSize = ImGui.CalcTextSize($"{i + 1}. {ime.ImmCand[i]}"); - maxTextWidth = maxTextWidth > textSize.X ? maxTextWidth : textSize.X; - } - - maxTextWidth = maxTextWidth > ImGui.CalcTextSize(pageInfo).X ? maxTextWidth : ImGui.CalcTextSize(pageInfo).X; - maxTextWidth = maxTextWidth > ImGui.CalcTextSize(ime.ImmComp).X - ? maxTextWidth - : ImGui.CalcTextSize(ime.ImmComp).X; - - var numEntries = (drawCand ? ime.ImmCand.Count + 1 : 0) + 1 + (drawIme ? 1 : 0); - var spaceY = ImGui.GetStyle().ItemSpacing.Y; - var imeWindowHeight = (spaceY * (numEntries - 1)) + (candTextSize.Y * numEntries); - var windowSize = new Vector2(maxTextWidth, imeWindowHeight) + (pad * 2); - - // 1. Figure out the expanding direction. - var expandUpward = ime.CursorPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y; - var windowPos = ime.CursorPos - pad; - if (expandUpward) - { - windowPos.Y -= windowSize.Y - candTextSize.Y - (pad.Y * 2); - if (drawIme) - windowPos.Y += candTextSize.Y + spaceY; - } - else - { - if (drawIme) - windowPos.Y -= candTextSize.Y + spaceY; - } - - // 2. Contain within the viewport. Do not use clamp, as the target window might be too small. - if (windowPos.X < viewport.WorkPos.X) - windowPos.X = viewport.WorkPos.X; - else if (windowPos.X + windowSize.X > viewport.WorkPos.X + viewport.WorkSize.X) - windowPos.X = (viewport.WorkPos.X + viewport.WorkSize.X) - windowSize.X; - if (windowPos.Y < viewport.WorkPos.Y) - windowPos.Y = viewport.WorkPos.Y; - else if (windowPos.Y + windowSize.Y > viewport.WorkPos.Y + viewport.WorkSize.Y) - windowPos.Y = (viewport.WorkPos.Y + viewport.WorkSize.Y) - windowSize.Y; - - var cursor = windowPos + pad; - - // Draw the ime window. - var drawList = ImGui.GetForegroundDrawList(viewport); - - // Draw the background rect for candidates. - if (drawCand) - { - Vector2 candRectLt, candRectRb; - if (!expandUpward) - { - candRectLt = windowPos + candTextSize with { X = 0 } + pad with { X = 0 }; - candRectRb = windowPos + windowSize; - if (drawIme) - candRectLt.Y += spaceY + candTextSize.Y; - } - else - { - candRectLt = windowPos; - candRectRb = windowPos + (windowSize - candTextSize with { X = 0 } - pad with { X = 0 }); - if (drawIme) - candRectRb.Y -= spaceY + candTextSize.Y; - } - - drawList.AddRectFilled( - candRectLt, - candRectRb, - ImGui.GetColorU32(ImGuiCol.WindowBg), - ImGui.GetStyle().WindowRounding); - } - - if (!expandUpward && drawIme) - { - for (var dx = -2; dx <= 2; dx++) - { - for (var dy = -2; dy <= 2; dy++) - { - if (dx != 0 || dy != 0) - { - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor + new Vector2(dx, dy), - ImGui.GetColorU32(ImGuiCol.WindowBg), - ime.InputModeIcon); - } - } - } - - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor, - ImGui.GetColorU32(ImGuiCol.Text), - ime.InputModeIcon); - cursor.Y += candTextSize.Y + spaceY; - } - - if (!expandUpward && drawConv) - { - DrawTextBeingConverted(); - cursor.Y += candTextSize.Y + spaceY; - - // Add a separator. - drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator)); - } - - if (drawCand) - { - // Add the candidate words. - for (var i = 0; i < ime.ImmCand.Count; i++) - { - var selected = i == (native.dwSelection % ImePageSize); - var color = ImGui.GetColorU32(ImGuiCol.Text); - if (selected) - color = ImGui.GetColorU32(ImGuiCol.NavHighlight); - - drawList.AddText(cursor, color, $"{i + 1}. {ime.ImmCand[i]}"); - cursor.Y += candTextSize.Y + spaceY; - } - - // Add a separator - drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator)); - - // Add the pages infomation. - drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), pageInfo); - cursor.Y += candTextSize.Y + spaceY; - } - - if (expandUpward && drawConv) - { - // Add a separator. - drawList.AddLine(cursor, cursor + new Vector2(maxTextWidth, 0), ImGui.GetColorU32(ImGuiCol.Separator)); - - DrawTextBeingConverted(); - cursor.Y += candTextSize.Y + spaceY; - } - - if (expandUpward && drawIme) - { - for (var dx = -2; dx <= 2; dx++) - { - for (var dy = -2; dy <= 2; dy++) - { - if (dx != 0 || dy != 0) - { - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor + new Vector2(dx, dy), - ImGui.GetColorU32(ImGuiCol.WindowBg), - ime.InputModeIcon); - } - } - } - - imeIconFont.RenderChar( - drawList, - imeIconFont.FontSize, - cursor, - ImGui.GetColorU32(ImGuiCol.Text), - ime.InputModeIcon); - } - - return; - - void DrawTextBeingConverted() - { - // Draw the text background. - drawList.AddRectFilled( - cursor - (pad / 2), - cursor + candTextSize + (pad / 2), - ImGui.GetColorU32(ImGuiCol.WindowBg)); - - // If only a part of the full text is marked for conversion, then draw background for the part being edited. - if (ime.PartialConversionFrom != 0 || ime.PartialConversionTo != ime.ImmComp.Length) - { - var part1 = ime.ImmComp[..ime.PartialConversionFrom]; - var part2 = ime.ImmComp[..ime.PartialConversionTo]; - var size1 = ImGui.CalcTextSize(part1); - var size2 = ImGui.CalcTextSize(part2); - drawList.AddRectFilled( - cursor + size1 with { Y = 0 }, - cursor + size2, - ImGui.GetColorU32(ImGuiCol.TextSelectedBg)); - } - - // Add the text being converted. - drawList.AddText(cursor, ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp); - - // Draw the caret inside the composition string. - if (DalamudIme.ShowCursorInInputText) - { - var partBeforeCaret = ime.ImmComp[..ime.CompositionCursorOffset]; - var sizeBeforeCaret = ImGui.CalcTextSize(partBeforeCaret); - drawList.AddLine( - cursor + sizeBeforeCaret with { Y = 0 }, - cursor + sizeBeforeCaret, - ImGui.GetColorU32(ImGuiCol.Text)); - } - } - } -} diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs index 3ff7cde76..acd7c2b6f 100644 --- a/Dalamud/ServiceManager.cs +++ b/Dalamud/ServiceManager.cs @@ -165,6 +165,7 @@ internal static class ServiceManager var earlyLoadingServices = new HashSet(); var blockingEarlyLoadingServices = new HashSet(); + var providedServices = new HashSet(); var dependencyServicesMap = new Dictionary>(); var getAsyncTaskMap = new Dictionary(); @@ -197,7 +198,10 @@ internal static class ServiceManager // We don't actually need to load provided services, something else does if (serviceKind.HasFlag(ServiceKind.ProvidedService)) + { + providedServices.Add(serviceType); continue; + } Debug.Assert( serviceKind.HasFlag(ServiceKind.EarlyLoadedService) || @@ -340,7 +344,16 @@ internal static class ServiceManager } if (!tasks.Any()) - throw new InvalidOperationException("Unresolvable dependency cycle detected"); + { + // No more services we can start loading for now. + // Either we're waiting for provided services, or there's a dependency cycle. + providedServices.RemoveWhere(x => getAsyncTaskMap[x].IsCompleted); + if (providedServices.Any()) + await Task.WhenAny(providedServices.Select(x => getAsyncTaskMap[x])); + else + throw new InvalidOperationException("Unresolvable dependency cycle detected"); + continue; + } if (servicesToLoad.Any()) { From 637ba78956553714d66004913b4c939527c580bf Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sat, 9 Mar 2024 00:01:20 +0900 Subject: [PATCH 2/3] At least make it not drop character after conversion with google IME --- Dalamud/Interface/Internal/DalamudIme.cs | 166 +++++++++++++++++++---- 1 file changed, 138 insertions(+), 28 deletions(-) diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index 6c01b74d7..bbfe819a8 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -18,7 +18,9 @@ using Dalamud.Interface.Utility; using ImGuiNET; +#if IMEDEBUG using Serilog; +#endif using TerraFX.Interop.Windows; @@ -267,6 +269,50 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType { var invalidTarget = TextState->Id == 0 || (TextState->Flags & ImGuiInputTextFlags.ReadOnly) != 0; +#if IMEDEBUG + switch (args.Message) + { + case WM.WM_IME_NOTIFY: + Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({ImeDebug.ImnName((int)args.WParam)}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_CONTROL: + Log.Verbose( + $"{nameof(WM.WM_IME_CONTROL)}({ImeDebug.ImcName((int)args.WParam)}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_REQUEST: + Log.Verbose( + $"{nameof(WM.WM_IME_REQUEST)}({ImeDebug.ImrName((int)args.WParam)}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_SELECT: + Log.Verbose($"{nameof(WM.WM_IME_SELECT)}({(int)args.WParam != 0}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_STARTCOMPOSITION: + Log.Verbose($"{nameof(WM.WM_IME_STARTCOMPOSITION)}()"); + break; + case WM.WM_IME_COMPOSITION: + Log.Verbose( + $"{nameof(WM.WM_IME_COMPOSITION)}({(char)args.WParam}, {ImeDebug.GcsName((int)args.LParam)})"); + break; + case WM.WM_IME_COMPOSITIONFULL: + Log.Verbose($"{nameof(WM.WM_IME_COMPOSITIONFULL)}()"); + break; + case WM.WM_IME_ENDCOMPOSITION: + Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}()"); + break; + case WM.WM_IME_CHAR: + Log.Verbose($"{nameof(WM.WM_IME_CHAR)}({(char)args.WParam}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_KEYDOWN: + Log.Verbose($"{nameof(WM.WM_IME_KEYDOWN)}({(char)args.WParam}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_KEYUP: + Log.Verbose($"{nameof(WM.WM_IME_KEYUP)}({(char)args.WParam}, 0x{args.LParam:X})"); + break; + case WM.WM_IME_SETCONTEXT: + Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(int)args.WParam != 0}, 0x{args.LParam:X})"); + break; + } +#endif switch (args.Message) { case WM.WM_IME_NOTIFY @@ -286,22 +332,15 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType else this.ReplaceCompositionString(hImc, (uint)args.LParam); - // Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithValue(0); break; case WM.WM_IME_ENDCOMPOSITION: - // Log.Verbose($"{nameof(WM.WM_IME_ENDCOMPOSITION)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); + this.ClearState(hImc, false); args.SuppressWithValue(0); break; - case WM.WM_IME_CONTROL: - // Log.Verbose($"{nameof(WM.WM_IME_CONTROL)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); - args.SuppressWithValue(0); - break; - case WM.WM_IME_REQUEST: - // Log.Verbose($"{nameof(WM.WM_IME_REQUEST)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithValue(0); break; @@ -309,14 +348,9 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType // Hide candidate and composition windows. args.LParam = (LPARAM)((nint)args.LParam & ~(ISC_SHOWUICOMPOSITIONWINDOW | 0xF)); - // Log.Verbose($"{nameof(WM.WM_IME_SETCONTEXT)}({(nint)args.WParam:X}, {(nint)args.LParam:X}): {this.compositionString}"); args.SuppressWithDefault(); break; - case WM.WM_IME_NOTIFY: - // Log.Verbose($"{nameof(WM.WM_IME_NOTIFY)}({(nint)args.WParam:X}): {this.compositionString}"); - break; - case WM.WM_KEYDOWN when (int)args.WParam is VK.VK_TAB or VK.VK_PRIOR @@ -335,7 +369,11 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } this.UpdateCandidates(hImc); + break; + case WM.WM_KEYDOWN when (int)args.WParam is VK.VK_ESCAPE && this.candidateStrings.Count != 0: + this.ClearState(hImc); + args.SuppressWithDefault(); break; case WM.WM_LBUTTONDOWN: @@ -344,15 +382,14 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType case WM.WM_XBUTTONDOWN: ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0); break; - - // default: - // Log.Verbose($"{(WmNames.TryGetValue((int)args.Message, out var v) ? v : args.Message.ToString())}({(nint)args.WParam:X}, {(nint)args.LParam:X})"); - // break; } - this.UpdateInputLanguage(hImc); - if (this.inputModeIcon == (char)SeIconChar.ImeKoreanHangul) - this.UpdateCandidates(hImc); + if (args.Message != WM.WM_MOUSEMOVE) + { + this.UpdateInputLanguage(hImc); + if (this.inputModeIcon == (char)SeIconChar.ImeKoreanHangul) + this.UpdateCandidates(hImc); + } } finally { @@ -367,8 +404,6 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType var lang = GetKeyboardLayout(0); var open = ImmGetOpenStatus(hImc) != false; - // Log.Verbose($"{nameof(this.UpdateInputLanguage)}: conv={conv:X} sent={sent:X} open={open} lang={lang:X}"); - var native = (conv & 1) != 0; var katakana = (conv & 2) != 0; var fullwidth = (conv & 8) != 0; @@ -418,6 +453,10 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType ? ImmGetCompositionString(hImc, GCS.GCS_RESULTSTR) : ImmGetCompositionString(hImc, GCS.GCS_COMPSTR); +#if IMEDEBUG + Log.Verbose($"{nameof(this.ReplaceCompositionString)}({newString})"); +#endif + this.ReflectCharacterEncounters(newString); if (this.temporaryUndoSelection is not null) @@ -436,8 +475,8 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType if (finalCommit) { - this.ClearState(hImc); - return; + this.ClearState(hImc, false); + newString = string.Empty; } this.compositionString = newString; @@ -471,17 +510,21 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType this.UpdateCandidates(hImc); } - private void ClearState(HIMC hImc) + private void ClearState(HIMC hImc, bool invokeCancel = true) { this.compositionString = string.Empty; this.partialConversionFrom = this.partialConversionTo = 0; this.compositionCursorOffset = 0; this.temporaryUndoSelection = null; TextState->Stb.SelectStart = TextState->Stb.Cursor = TextState->Stb.SelectEnd; - ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0); - this.UpdateCandidates(default); + this.candidateStrings.Clear(); + this.immCandNative = default; + if (invokeCancel) + ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0); - // Log.Information($"{nameof(this.ClearState)}"); +#if IMEDEBUG + Log.Information($"{nameof(this.ClearState)}({invokeCancel})"); +#endif } private void UpdateCandidates(HIMC hImc) @@ -932,4 +975,71 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType return true; } } + +#if IMEDEBUG + private static class ImeDebug + { + private static readonly (int Value, string Name)[] GcsFields = + { + (GCS.GCS_COMPREADSTR, nameof(GCS.GCS_COMPREADSTR)), + (GCS.GCS_COMPREADATTR, nameof(GCS.GCS_COMPREADATTR)), + (GCS.GCS_COMPREADCLAUSE, nameof(GCS.GCS_COMPREADCLAUSE)), + (GCS.GCS_COMPSTR, nameof(GCS.GCS_COMPSTR)), + (GCS.GCS_COMPATTR, nameof(GCS.GCS_COMPATTR)), + (GCS.GCS_COMPCLAUSE, nameof(GCS.GCS_COMPCLAUSE)), + (GCS.GCS_CURSORPOS, nameof(GCS.GCS_CURSORPOS)), + (GCS.GCS_DELTASTART, nameof(GCS.GCS_DELTASTART)), + (GCS.GCS_RESULTREADSTR, nameof(GCS.GCS_RESULTREADSTR)), + (GCS.GCS_RESULTREADCLAUSE, nameof(GCS.GCS_RESULTREADCLAUSE)), + (GCS.GCS_RESULTSTR, nameof(GCS.GCS_RESULTSTR)), + (GCS.GCS_RESULTCLAUSE, nameof(GCS.GCS_RESULTCLAUSE)), + }; + + private static readonly IReadOnlyDictionary ImnFields = + typeof(IMN) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .Where(x => x.IsLiteral) + .ToDictionary(x => (int)x.GetRawConstantValue()!, x => x.Name); + + public static string GcsName(int val) + { + var sb = new StringBuilder(); + foreach (var (value, name) in GcsFields) + { + if ((val & value) != 0) + { + if (sb.Length != 0) + sb.Append(" | "); + sb.Append(name); + val &= ~value; + } + } + + if (val != 0) + { + if (sb.Length != 0) + sb.Append(" | "); + sb.Append($"0x{val:X}"); + } + + return sb.ToString(); + } + + public static string ImcName(int val) => ImnFields.TryGetValue(val, out var name) ? name : $"0x{val:X}"; + + public static string ImnName(int val) => ImnFields.TryGetValue(val, out var name) ? name : $"0x{val:X}"; + + public static string ImrName(int val) => val switch + { + IMR_CANDIDATEWINDOW => nameof(IMR_CANDIDATEWINDOW), + IMR_COMPOSITIONFONT => nameof(IMR_COMPOSITIONFONT), + IMR_COMPOSITIONWINDOW => nameof(IMR_COMPOSITIONWINDOW), + IMR_CONFIRMRECONVERTSTRING => nameof(IMR_CONFIRMRECONVERTSTRING), + IMR_DOCUMENTFEED => nameof(IMR_DOCUMENTFEED), + IMR_QUERYCHARPOSITION => nameof(IMR_QUERYCHARPOSITION), + IMR_RECONVERTSTRING => nameof(IMR_RECONVERTSTRING), + _ => $"0x{val:X}", + }; + } +#endif } From e7815c59d551645e3a5fe5a5ecfd9d189101202b Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Sat, 9 Mar 2024 00:16:46 +0900 Subject: [PATCH 3/3] fix? --- Dalamud/Interface/Internal/DalamudIme.cs | 64 +++++++++++++++++++----- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index bbfe819a8..caf014885 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -1,3 +1,5 @@ +// #define IMEDEBUG + using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -108,6 +110,9 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType /// Undo range for modifying the buffer while composition is in progress. private (int Start, int End, int Cursor)? temporaryUndoSelection; + private bool updateInputLanguage = true; + private bool updateImeStatusAgain; + [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1003:Symbols should be spaced correctly", Justification = ".")] static DalamudIme() { @@ -255,15 +260,24 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType private void WndProcHookManagerOnPreWndProc(WndProcEventArgs args) { if (!ImGuiHelpers.IsImGuiInitialized) + { + this.updateInputLanguage = true; return; + } // Are we not the target of text input? if (!ImGui.GetIO().WantTextInput) + { + this.updateInputLanguage = true; return; + } var hImc = ImmGetContext(args.Hwnd); if (hImc == nint.Zero) + { + this.updateInputLanguage = true; return; + } try { @@ -313,16 +327,36 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType break; } #endif + if (this.updateInputLanguage + || (args.Message == WM.WM_IME_NOTIFY + && (int)args.WParam + is IMN.IMN_SETCONVERSIONMODE + or IMN.IMN_OPENSTATUSWINDOW + or IMN.IMN_CLOSESTATUSWINDOW)) + { + this.UpdateInputLanguage(hImc); + this.updateInputLanguage = false; + } + + if (this.updateImeStatusAgain) + { + this.ReplaceCompositionString(hImc, false); + this.UpdateCandidates(hImc); + this.updateImeStatusAgain = false; + } + switch (args.Message) { case WM.WM_IME_NOTIFY when (nint)args.WParam is IMN.IMN_OPENCANDIDATE or IMN.IMN_CLOSECANDIDATE or IMN.IMN_CHANGECANDIDATE: this.UpdateCandidates(hImc); + this.updateImeStatusAgain = true; args.SuppressWithValue(0); break; case WM.WM_IME_STARTCOMPOSITION: + this.updateImeStatusAgain = true; args.SuppressWithValue(0); break; @@ -330,17 +364,24 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType if (invalidTarget) ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_CANCEL, 0); else - this.ReplaceCompositionString(hImc, (uint)args.LParam); + this.ReplaceCompositionString(hImc, ((int)args.LParam & GCS.GCS_RESULTSTR) != 0); + this.updateImeStatusAgain = true; args.SuppressWithValue(0); break; case WM.WM_IME_ENDCOMPOSITION: this.ClearState(hImc, false); + this.updateImeStatusAgain = true; args.SuppressWithValue(0); break; + + case WM.WM_IME_CHAR: + case WM.WM_IME_KEYDOWN: + case WM.WM_IME_KEYUP: case WM.WM_IME_CONTROL: case WM.WM_IME_REQUEST: + this.updateImeStatusAgain = true; args.SuppressWithValue(0); break; @@ -348,9 +389,16 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType // Hide candidate and composition windows. args.LParam = (LPARAM)((nint)args.LParam & ~(ISC_SHOWUICOMPOSITIONWINDOW | 0xF)); + this.updateImeStatusAgain = true; args.SuppressWithDefault(); break; + case WM.WM_IME_NOTIFY: + case WM.WM_IME_COMPOSITIONFULL: + case WM.WM_IME_SELECT: + this.updateImeStatusAgain = true; + break; + case WM.WM_KEYDOWN when (int)args.WParam is VK.VK_TAB or VK.VK_PRIOR @@ -383,13 +431,6 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType ImmNotifyIME(hImc, NI.NI_COMPOSITIONSTR, CPS_COMPLETE, 0); break; } - - if (args.Message != WM.WM_MOUSEMOVE) - { - this.UpdateInputLanguage(hImc); - if (this.inputModeIcon == (char)SeIconChar.ImeKoreanHangul) - this.UpdateCandidates(hImc); - } } finally { @@ -446,9 +487,8 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType } } - private void ReplaceCompositionString(HIMC hImc, uint comp) + private void ReplaceCompositionString(HIMC hImc, bool finalCommit) { - var finalCommit = (comp & GCS.GCS_RESULTSTR) != 0; var newString = finalCommit ? ImmGetCompositionString(hImc, GCS.GCS_RESULTSTR) : ImmGetCompositionString(hImc, GCS.GCS_COMPSTR); @@ -482,9 +522,9 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType this.compositionString = newString; this.compositionCursorOffset = ImmGetCompositionStringW(hImc, GCS.GCS_CURSORPOS, null, 0); - if ((comp & GCS.GCS_COMPATTR) != 0) + var attrLength = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, null, 0); + if (attrLength > 0) { - var attrLength = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, null, 0); var attrPtr = stackalloc byte[attrLength]; var attr = new Span(attrPtr, Math.Min(this.compositionString.Length, attrLength)); _ = ImmGetCompositionStringW(hImc, GCS.GCS_COMPATTR, attrPtr, (uint)attrLength);