diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs index 4aafa5f3b..e955997fd 100644 --- a/Dalamud/Game/Gui/Internal/DalamudIME.cs +++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Numerics; using System.Runtime.InteropServices; using System.Text; +using Dalamud.Hooking; using Dalamud.Interface.Internal; using Dalamud.Logging.Internal; using ImGuiNET; @@ -14,7 +18,7 @@ namespace Dalamud.Game.Gui.Internal /// /// This class handles IME for non-English users. /// - internal class DalamudIME : IDisposable + internal unsafe class DalamudIME : IDisposable { private static readonly ModuleLog Log = new("IME"); @@ -22,6 +26,8 @@ namespace Dalamud.Game.Gui.Internal private IntPtr wndProcPtr; private IntPtr oldWndProcPtr; private WndProcDelegate wndProcDelegate; + private AsmHook imguiTextInputCursorHook; + private Vector2* cursorPos; /// /// Initializes a new instance of the class. @@ -60,6 +66,18 @@ namespace Dalamud.Game.Gui.Internal SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr); this.oldWndProcPtr = IntPtr.Zero; } + + this.imguiTextInputCursorHook?.Dispose(); + Marshal.FreeHGlobal((IntPtr)this.cursorPos); + } + + /// + /// Get the position of the cursor. + /// + /// The position of the cursor. + internal Vector2 GetCursorPos() + { + return new Vector2(this.cursorPos->X, this.cursorPos->Y); } /// @@ -73,6 +91,31 @@ namespace Dalamud.Game.Gui.Internal this.interfaceHandle = Service.Get().WindowHandlePtr; this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate); this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr); + + var module = Process.GetCurrentProcess().Modules.Cast().First(m => m.ModuleName == "cimgui.dll"); + var scanner = new SigScanner(module); + var cursorDrawingPtr = scanner.ScanModule("F3 0F 11 75 ?? 0F 28 CF"); + Log.Debug($"Found cursorDrawingPtr at {cursorDrawingPtr:X}"); + + this.cursorPos = (Vector2*)Marshal.AllocHGlobal(sizeof(Vector2)); + this.cursorPos->X = 0f; + this.cursorPos->Y = 0f; + + var asm = new[] + { + "use64", + $"push rax", + $"mov rax, {(IntPtr)this.cursorPos + sizeof(float)}", + $"movss [rax],xmm7", + $"mov rax, {(IntPtr)this.cursorPos}", + $"movss [rax],xmm6", + $"pop rax", + }; + + Log.Debug($"Asm Code:\n{string.Join("\n", asm)}"); + this.imguiTextInputCursorHook = new AsmHook(cursorDrawingPtr, asm, "ImguiTextInputCursorHook"); + this.imguiTextInputCursorHook?.Enable(); + this.IsEnabled = true; Log.Information("Enabled!"); } diff --git a/Dalamud/Interface/Internal/Windows/IMEWindow.cs b/Dalamud/Interface/Internal/Windows/IMEWindow.cs index 5c936d4e9..e987047a1 100644 --- a/Dalamud/Interface/Internal/Windows/IMEWindow.cs +++ b/Dalamud/Interface/Internal/Windows/IMEWindow.cs @@ -1,5 +1,5 @@ using System.Numerics; - +using Dalamud.Game.ClientState.Keys; using Dalamud.Game.Gui.Internal; using Dalamud.Interface.Colors; using Dalamud.Interface.Windowing; @@ -10,7 +10,7 @@ namespace Dalamud.Interface.Internal.Windows /// /// A window for displaying IME details. /// - internal class IMEWindow : Window + internal unsafe class IMEWindow : Window { private const int ImePageSize = 9; @@ -18,7 +18,7 @@ namespace Dalamud.Interface.Internal.Windows /// Initializes a new instance of the class. /// public IMEWindow() - : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize) + : base("Dalamud IME", ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoBackground) { this.Size = new Vector2(100, 200); this.SizeCondition = ImGuiCond.FirstUseEver; @@ -29,6 +29,7 @@ namespace Dalamud.Interface.Internal.Windows /// public override void Draw() { + if (this.IsOpen && Service.Get()[VirtualKey.SHIFT]) Service.Get().CloseIMEWindow(); var ime = Service.GetNullable(); if (ime == null || !ime.IsEnabled) @@ -36,34 +37,70 @@ namespace Dalamud.Interface.Internal.Windows ImGui.Text("IME is unavailable."); return; } + } - ImGui.Text(ime.ImmComp); + /// + public override void PostDraw() + { + if (this.IsOpen && Service.Get()[VirtualKey.SHIFT]) Service.Get().CloseIMEWindow(); + var ime = Service.GetNullable(); - ImGui.Separator(); + if (ime == null || !ime.IsEnabled) + return; + + var cursorPos = ime.GetCursorPos(); + + var nextDrawPosY = cursorPos.Y; + var maxTextWidth = 0f; + var textHeight = ImGui.CalcTextSize(ime.ImmComp).Y; + var drawAreaPosX = cursorPos.X + ImGui.GetStyle().WindowPadding.X; var native = ime.ImmCandNative; - for (var i = 0; i < ime.ImmCand.Count; i++) - { - var selected = i == (native.Selection % ImePageSize); - - if (selected) - ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.HealerGreen); - - ImGui.Text($"{i + 1}. {ime.ImmCand[i]}"); - - if (selected) - ImGui.PopStyleColor(); - } - var totalIndex = native.Selection + 1; var totalSize = native.Count; var pageStart = native.PageStart; var pageIndex = (pageStart / ImePageSize) + 1; var pageCount = (totalSize / ImePageSize) + 1; + var pageInfo = $"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"; - ImGui.Separator(); - ImGui.Text($"{totalIndex}/{totalSize} ({pageIndex}/{pageCount})"); + // Calc the window size + 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 imeWindowMinPos = new Vector2(cursorPos.X, cursorPos.Y); + var imeWindowMaxPos = new Vector2(cursorPos.X + maxTextWidth + (2 * ImGui.GetStyle().WindowPadding.X), cursorPos.Y + (textHeight * (ime.ImmCand.Count + 2)) + (5 * (ime.ImmCand.Count - 1)) + (2 * ImGui.GetStyle().WindowPadding.Y)); + + var drawList = ImGui.GetForegroundDrawList(); + // Draw the background rect + drawList.AddRectFilled(imeWindowMinPos, imeWindowMaxPos, ImGui.GetColorU32(ImGuiCol.WindowBg), ImGui.GetStyle().WindowRounding); + // Add component text + drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), ime.ImmComp); + nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y; + // Add separator + drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator)); + // Add candidate words + for (var i = 0; i < ime.ImmCand.Count; i++) + { + var selected = i == (native.Selection % ImePageSize); + var color = ImGui.GetColorU32(ImGuiCol.Text); + if (selected) + color = ImGui.GetColorU32(ImGuiCol.NavHighlight); + + drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), color, $"{i + 1}. {ime.ImmCand[i]}"); + nextDrawPosY += textHeight + ImGui.GetStyle().ItemSpacing.Y; + } + + // Add separator + drawList.AddLine(new Vector2(drawAreaPosX, nextDrawPosY), new Vector2(drawAreaPosX + maxTextWidth, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Separator)); + // Add pages infomation + drawList.AddText(new Vector2(drawAreaPosX, nextDrawPosY), ImGui.GetColorU32(ImGuiCol.Text), pageInfo); } } }