mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-01-03 14:23:40 +01:00
IME implementation
This commit is contained in:
parent
ee44e6885a
commit
3e3757d30c
10 changed files with 1131 additions and 2 deletions
|
|
@ -8,6 +8,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Gui
|
||||
|
|
@ -32,6 +33,7 @@ namespace Dalamud.Game.Gui
|
|||
private readonly Hook<HandleItemOutDelegate> handleItemOutHook;
|
||||
private readonly Hook<HandleActionHoverDelegate> handleActionHoverHook;
|
||||
private readonly Hook<HandleActionOutDelegate> handleActionOutHook;
|
||||
private readonly Hook<HandleImmDelegate> handleImmHook;
|
||||
private readonly Hook<ToggleUiHideDelegate> toggleUiHideHook;
|
||||
|
||||
private GetUIMapObjectDelegate getUIMapObject;
|
||||
|
|
@ -57,6 +59,7 @@ namespace Dalamud.Game.Gui
|
|||
Log.Verbose($"SetGlobalBgm address 0x{this.address.SetGlobalBgm.ToInt64():X}");
|
||||
Log.Verbose($"HandleItemHover address 0x{this.address.HandleItemHover.ToInt64():X}");
|
||||
Log.Verbose($"HandleItemOut address 0x{this.address.HandleItemOut.ToInt64():X}");
|
||||
Log.Verbose($"HandleImm address 0x{this.address.HandleImm.ToInt64():X}");
|
||||
Log.Verbose($"GetUIObject address 0x{this.address.GetUIObject.ToInt64():X}");
|
||||
Log.Verbose($"GetAgentModule address 0x{this.address.GetAgentModule.ToInt64():X}");
|
||||
|
||||
|
|
@ -65,13 +68,15 @@ namespace Dalamud.Game.Gui
|
|||
this.Toast = new ToastGui(scanner, dalamud);
|
||||
|
||||
this.setGlobalBgmHook = new Hook<SetGlobalBgmDelegate>(this.address.SetGlobalBgm, this.HandleSetGlobalBgmDetour);
|
||||
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
||||
|
||||
this.handleItemHoverHook = new Hook<HandleItemHoverDelegate>(this.address.HandleItemHover, this.HandleItemHoverDetour);
|
||||
this.handleItemOutHook = new Hook<HandleItemOutDelegate>(this.address.HandleItemOut, this.HandleItemOutDetour);
|
||||
|
||||
this.handleActionHoverHook = new Hook<HandleActionHoverDelegate>(this.address.HandleActionHover, this.HandleActionHoverDetour);
|
||||
this.handleActionOutHook = new Hook<HandleActionOutDelegate>(this.address.HandleActionOut, this.HandleActionOutDetour);
|
||||
|
||||
this.handleImmHook = new Hook<HandleImmDelegate>(this.address.HandleImm, this.HandleImmDetour);
|
||||
|
||||
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(this.address.GetUIObject);
|
||||
|
||||
this.getMatrixSingleton = Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(this.address.GetMatrixSingleton);
|
||||
|
|
@ -135,6 +140,9 @@ namespace Dalamud.Game.Gui
|
|||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr HandleActionOutDelegate(IntPtr agentActionDetail, IntPtr a2, IntPtr a3, int a4);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate char HandleImmDelegate(IntPtr framework, char a2, byte a3);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr ToggleUiHideDelegate(IntPtr thisPtr, byte unknownByte);
|
||||
|
||||
|
|
@ -494,6 +502,7 @@ namespace Dalamud.Game.Gui
|
|||
this.setGlobalBgmHook.Enable();
|
||||
this.handleItemHoverHook.Enable();
|
||||
this.handleItemOutHook.Enable();
|
||||
this.handleImmHook.Enable();
|
||||
this.toggleUiHideHook.Enable();
|
||||
this.handleActionHoverHook.Enable();
|
||||
this.handleActionOutHook.Enable();
|
||||
|
|
@ -510,6 +519,7 @@ namespace Dalamud.Game.Gui
|
|||
this.setGlobalBgmHook.Dispose();
|
||||
this.handleItemHoverHook.Dispose();
|
||||
this.handleItemOutHook.Dispose();
|
||||
this.handleImmHook.Dispose();
|
||||
this.toggleUiHideHook.Dispose();
|
||||
this.handleActionHoverHook.Dispose();
|
||||
this.handleActionOutHook.Dispose();
|
||||
|
|
@ -641,5 +651,13 @@ namespace Dalamud.Game.Gui
|
|||
|
||||
return this.toggleUiHideHook.Original(thisPtr, unknownByte);
|
||||
}
|
||||
|
||||
private char HandleImmDetour(IntPtr framework, char a2, byte a3)
|
||||
{
|
||||
var result = this.handleImmHook.Original(framework, a2, a3);
|
||||
return ImGui.GetIO().WantTextInput
|
||||
? (char)0
|
||||
: result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,11 @@ namespace Dalamud.Game.Gui
|
|||
/// </summary>
|
||||
public IntPtr HandleActionOut { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native HandleImm method.
|
||||
/// </summary>
|
||||
public IntPtr HandleImm { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the address of the native GetUIObject method.
|
||||
/// </summary>
|
||||
|
|
@ -100,6 +105,7 @@ namespace Dalamud.Game.Gui
|
|||
this.HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
|
||||
this.HandleActionHover = sig.ScanText("E8 ?? ?? ?? ?? E9 ?? ?? ?? ?? 83 F8 0F");
|
||||
this.HandleActionOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B DA 48 8B F9 4D 85 C0 74 1F");
|
||||
this.HandleImm = sig.ScanText("E8 ?? ?? ?? ?? 84 C0 75 10 48 83 FF 09");
|
||||
this.GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
|
||||
this.GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
|
||||
this.ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
|
||||
|
|
|
|||
234
Dalamud/Game/Gui/Internal/DalamudIME.cs
Normal file
234
Dalamud/Game/Gui/Internal/DalamudIME.cs
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Logging.Internal;
|
||||
using ImGuiNET;
|
||||
|
||||
using static Dalamud.NativeFunctions;
|
||||
|
||||
namespace Dalamud.Game.Gui.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles IME for non-English users.
|
||||
/// </summary>
|
||||
internal class DalamudIME : IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("IME");
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private IntPtr interfaceHandle;
|
||||
private IntPtr wndProcPtr;
|
||||
private IntPtr oldWndProcPtr;
|
||||
private WndProcDelegate wndProcDelegate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DalamudIME"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">The Dalamud instance.</param>
|
||||
internal DalamudIME(Dalamud dalamud)
|
||||
{
|
||||
this.dalamud = dalamud;
|
||||
}
|
||||
|
||||
private delegate long WndProcDelegate(IntPtr hWnd, uint msg, ulong wParam, long lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the module is enabled.
|
||||
/// </summary>
|
||||
internal bool IsEnabled { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the imm candidates.
|
||||
/// </summary>
|
||||
internal List<string> ImmCand { get; private set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the imm component.
|
||||
/// </summary>
|
||||
internal string ImmComp { get; private set; } = string.Empty;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.oldWndProcPtr != IntPtr.Zero)
|
||||
{
|
||||
SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.oldWndProcPtr);
|
||||
this.oldWndProcPtr = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the IME module.
|
||||
/// </summary>
|
||||
internal void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.wndProcDelegate = this.WndProcDetour;
|
||||
this.interfaceHandle = this.dalamud.InterfaceManager.WindowHandlePtr;
|
||||
this.wndProcPtr = Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate);
|
||||
this.oldWndProcPtr = SetWindowLongPtrW(this.interfaceHandle, WindowLongType.WndProc, this.wndProcPtr);
|
||||
this.IsEnabled = true;
|
||||
Log.Information("Enabled!");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Information(ex, "Enable failed");
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleWindow(bool visible)
|
||||
{
|
||||
if (visible)
|
||||
this.dalamud.DalamudUi.OpenIMEWindow();
|
||||
else
|
||||
this.dalamud.DalamudUi.CloseIMEWindow();
|
||||
}
|
||||
|
||||
private long WndProcDetour(IntPtr hWnd, uint msg, ulong wParam, long lParam)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (hWnd == this.interfaceHandle && ImGui.GetCurrentContext() != IntPtr.Zero && ImGui.GetIO().WantTextInput)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
var wmsg = (WindowsMessage)msg;
|
||||
|
||||
switch (wmsg)
|
||||
{
|
||||
case WindowsMessage.WM_IME_NOTIFY:
|
||||
switch ((IMECommand)wParam)
|
||||
{
|
||||
case IMECommand.ChangeCandidate:
|
||||
this.ToggleWindow(true);
|
||||
|
||||
if (hWnd == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var size = ImmGetCandidateListW(hIMC, 0, IntPtr.Zero, 0);
|
||||
if (size > 0)
|
||||
{
|
||||
var candlistPtr = Marshal.AllocHGlobal((int)size);
|
||||
size = ImmGetCandidateListW(hIMC, 0, candlistPtr, (uint)size);
|
||||
|
||||
var candlist = Marshal.PtrToStructure<CandidateList>(candlistPtr);
|
||||
var pageSize = candlist.PageSize;
|
||||
var candCount = candlist.Count;
|
||||
if (pageSize > 0 && candCount > 1)
|
||||
{
|
||||
var dwOffsets = new int[candCount];
|
||||
for (var i = 0; i < candCount; i++)
|
||||
dwOffsets[i] = Marshal.ReadInt32(candlistPtr + ((i + 6) * sizeof(int)));
|
||||
|
||||
var pageStart = candlist.PageStart;
|
||||
// var pageEnd = pageStart + pageSize;
|
||||
|
||||
var cand = new string[pageSize];
|
||||
this.ImmCand.Clear();
|
||||
|
||||
for (var i = 0; i < pageSize; i++)
|
||||
{
|
||||
var offStart = dwOffsets[i + pageStart];
|
||||
var offEnd = i + pageStart + 1 < candCount ? dwOffsets[i + pageStart + 1] : size;
|
||||
|
||||
var pStrStart = candlistPtr + (int)offStart;
|
||||
var pStrEnd = candlistPtr + (int)offEnd;
|
||||
|
||||
var len = (int)(pStrEnd.ToInt64() - pStrStart.ToInt64());
|
||||
if (len > 0)
|
||||
{
|
||||
var candBytes = new byte[len];
|
||||
Marshal.Copy(pStrStart, candBytes, 0, len);
|
||||
|
||||
var candStr = Encoding.Unicode.GetString(candBytes);
|
||||
cand[i] = candStr;
|
||||
|
||||
this.ImmCand.Add(candStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Marshal.FreeHGlobal(candlistPtr);
|
||||
}
|
||||
|
||||
break;
|
||||
case IMECommand.OpenCandidate:
|
||||
this.ToggleWindow(true);
|
||||
this.ImmCand.Clear();
|
||||
break;
|
||||
case IMECommand.CloseCandidate:
|
||||
this.ToggleWindow(false);
|
||||
this.ImmCand.Clear();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case WindowsMessage.WM_IME_COMPOSITION:
|
||||
if ((lParam & (long)IMEComposition.ResultStr) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.ResultStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
io.AddInputCharactersUTF8(lpstr);
|
||||
|
||||
this.ImmComp = string.Empty;
|
||||
this.ImmCand.Clear();
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
if (((long)(IMEComposition.CompStr | IMEComposition.CompAttr | IMEComposition.CompClause |
|
||||
IMEComposition.CompReadAttr | IMEComposition.CompReadClause | IMEComposition.CompReadStr) & lParam) > 0)
|
||||
{
|
||||
var hIMC = ImmGetContext(hWnd);
|
||||
if (hIMC == IntPtr.Zero)
|
||||
return 0;
|
||||
|
||||
var dwSize = ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, IntPtr.Zero, 0);
|
||||
var unmanagedPointer = Marshal.AllocHGlobal((int)dwSize);
|
||||
ImmGetCompositionStringW(hIMC, IMEComposition.CompStr, unmanagedPointer, (uint)dwSize);
|
||||
|
||||
var bytes = new byte[dwSize];
|
||||
Marshal.Copy(unmanagedPointer, bytes, 0, (int)dwSize);
|
||||
Marshal.FreeHGlobal(unmanagedPointer);
|
||||
|
||||
var lpstr = Encoding.Unicode.GetString(bytes);
|
||||
this.ImmComp = lpstr;
|
||||
if (lpstr == string.Empty)
|
||||
this.ToggleWindow(false);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Prevented a crash in an IME hook");
|
||||
}
|
||||
|
||||
return CallWindowProcW(this.oldWndProcPtr, hWnd, msg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue