mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
Better WndProc handling
This commit is contained in:
parent
4be635be67
commit
0afb3d2c8a
6 changed files with 294 additions and 290 deletions
144
Dalamud/Hooking/WndProcHook/WndProcEventArgs.cs
Normal file
144
Dalamud/Hooking/WndProcHook/WndProcEventArgs.cs
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Dalamud.Hooking.WndProcHook;
|
||||
|
||||
/// <summary>
|
||||
/// Event arguments for <see cref="WndProcEventDelegate"/>,
|
||||
/// and the manager for individual WndProc hook.
|
||||
/// </summary>
|
||||
internal sealed unsafe class WndProcEventArgs
|
||||
{
|
||||
private readonly WndProcHookManager owner;
|
||||
private readonly delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT> oldWndProcW;
|
||||
private readonly WndProcDelegate myWndProc;
|
||||
|
||||
private GCHandle gcHandle;
|
||||
private bool released;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WndProcEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="owner">The owner.</param>
|
||||
/// <param name="hwnd">The handle of the target window of the message.</param>
|
||||
/// <param name="viewportId">The viewport ID.</param>
|
||||
internal WndProcEventArgs(WndProcHookManager owner, HWND hwnd, int viewportId)
|
||||
{
|
||||
this.Hwnd = hwnd;
|
||||
this.owner = owner;
|
||||
this.ViewportId = viewportId;
|
||||
this.myWndProc = this.WndProcDetour;
|
||||
this.oldWndProcW = (delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)SetWindowLongPtrW(
|
||||
hwnd,
|
||||
GWLP.GWLP_WNDPROC,
|
||||
Marshal.GetFunctionPointerForDelegate(this.myWndProc));
|
||||
this.gcHandle = GCHandle.Alloc(this);
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate LRESULT WndProcDelegate(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle of the target window of the message.
|
||||
/// </summary>
|
||||
public HWND Hwnd { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ImGui viewport ID.
|
||||
/// </summary>
|
||||
public int ViewportId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
public uint Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the WPARAM.
|
||||
/// </summary>
|
||||
public WPARAM WParam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LPARAM.
|
||||
/// </summary>
|
||||
public LPARAM LParam { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to suppress calling the next WndProc in the chain.<br />
|
||||
/// Does nothing if changed from <see cref="WndProcHookManager.PostWndProc"/>.
|
||||
/// </summary>
|
||||
public bool SuppressCall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the return value.<br />
|
||||
/// Has the return value from next window procedure, if accessed from <see cref="WndProcHookManager.PostWndProc"/>.
|
||||
/// </summary>
|
||||
public LRESULT ReturnValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="SuppressCall"/> to <c>true</c> and sets <see cref="ReturnValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="returnValue">The new return value.</param>
|
||||
public void SuppressWithValue(LRESULT returnValue)
|
||||
{
|
||||
this.ReturnValue = returnValue;
|
||||
this.SuppressCall = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="SuppressCall"/> to <c>true</c> and sets <see cref="ReturnValue"/> from the result of
|
||||
/// <see cref="DefWindowProcW"/>.
|
||||
/// </summary>
|
||||
public void SuppressWithDefault()
|
||||
{
|
||||
this.ReturnValue = DefWindowProcW(this.Hwnd, this.Message, this.WParam, this.LParam);
|
||||
this.SuppressCall = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IDisposable.Dispose"/>
|
||||
internal void InternalRelease()
|
||||
{
|
||||
if (this.released)
|
||||
return;
|
||||
|
||||
this.released = true;
|
||||
SendMessageW(this.Hwnd, WM.WM_NULL, 0, 0);
|
||||
this.FinalRelease();
|
||||
}
|
||||
|
||||
private void FinalRelease()
|
||||
{
|
||||
if (!this.gcHandle.IsAllocated)
|
||||
return;
|
||||
|
||||
this.gcHandle.Free();
|
||||
SetWindowLongPtrW(this.Hwnd, GWLP.GWLP_WNDPROC, (nint)this.oldWndProcW);
|
||||
this.owner.OnHookedWindowRemoved(this);
|
||||
}
|
||||
|
||||
private LRESULT WndProcDetour(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
if (hwnd != this.Hwnd)
|
||||
return CallWindowProcW(this.oldWndProcW, hwnd, uMsg, wParam, lParam);
|
||||
|
||||
this.SuppressCall = false;
|
||||
this.ReturnValue = 0;
|
||||
this.Message = uMsg;
|
||||
this.WParam = wParam;
|
||||
this.LParam = lParam;
|
||||
this.owner.InvokePreWndProc(this);
|
||||
|
||||
if (!this.SuppressCall)
|
||||
this.ReturnValue = CallWindowProcW(this.oldWndProcW, hwnd, uMsg, wParam, lParam);
|
||||
|
||||
this.owner.InvokePostWndProc(this);
|
||||
|
||||
if (uMsg == WM.WM_NCDESTROY || this.released)
|
||||
this.FinalRelease();
|
||||
|
||||
return this.ReturnValue;
|
||||
}
|
||||
}
|
||||
7
Dalamud/Hooking/WndProcHook/WndProcEventDelegate.cs
Normal file
7
Dalamud/Hooking/WndProcHook/WndProcEventDelegate.cs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
namespace Dalamud.Hooking.WndProcHook;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for overriding WndProc.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
internal delegate void WndProcEventDelegate(WndProcEventArgs args);
|
||||
115
Dalamud/Hooking/WndProcHook/WndProcHookManager.cs
Normal file
115
Dalamud/Hooking/WndProcHook/WndProcHookManager.cs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Dalamud.Hooking.WndProcHook;
|
||||
|
||||
/// <summary>
|
||||
/// Manages WndProc hooks for game main window and extra ImGui viewport windows.
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class WndProcHookManager : IServiceType, IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new(nameof(WndProcHookManager));
|
||||
|
||||
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||
private readonly Dictionary<HWND, WndProcEventArgs> wndProcOverrides = new();
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private unsafe WndProcHookManager()
|
||||
{
|
||||
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
||||
null,
|
||||
"user32.dll",
|
||||
"DispatchMessageW",
|
||||
0,
|
||||
this.DispatchMessageWDetour);
|
||||
this.dispatchMessageWHook.Enable();
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private unsafe delegate nint DispatchMessageWDelegate(MSG* msg);
|
||||
|
||||
/// <summary>
|
||||
/// Called before WndProc.
|
||||
/// </summary>
|
||||
public event WndProcEventDelegate? PreWndProc;
|
||||
|
||||
/// <summary>
|
||||
/// Called after WndProc.
|
||||
/// </summary>
|
||||
public event WndProcEventDelegate? PostWndProc;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.dispatchMessageWHook.Dispose();
|
||||
foreach (var v in this.wndProcOverrides.Values)
|
||||
v.InternalRelease();
|
||||
this.wndProcOverrides.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <see cref="PreWndProc"/>.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
internal void InvokePreWndProc(WndProcEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.PreWndProc?.Invoke(args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(this.PreWndProc)} error");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes <see cref="PostWndProc"/>.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
internal void InvokePostWndProc(WndProcEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.PostWndProc?.Invoke(args);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(this.PostWndProc)} error");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes <paramref name="args"/> from the list of known WndProc overrides.
|
||||
/// </summary>
|
||||
/// <param name="args">Object to remove.</param>
|
||||
internal void OnHookedWindowRemoved(WndProcEventArgs args)
|
||||
{
|
||||
if (!this.dispatchMessageWHook.IsDisposed)
|
||||
this.wndProcOverrides.Remove(args.Hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detour for <see cref="DispatchMessageW"/>. Used to discover new windows to hook.
|
||||
/// </summary>
|
||||
/// <param name="msg">The message.</param>
|
||||
/// <returns>The original return value.</returns>
|
||||
private unsafe nint DispatchMessageWDetour(MSG* msg)
|
||||
{
|
||||
if (!this.wndProcOverrides.ContainsKey(msg->hwnd)
|
||||
&& ImGuiHelpers.FindViewportId(msg->hwnd) is var vpid and >= 0)
|
||||
{
|
||||
this.wndProcOverrides[msg->hwnd] = new(this, msg->hwnd, vpid);
|
||||
}
|
||||
|
||||
return this.dispatchMessageWHook.Original(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using System.Text;
|
|||
using System.Text.Unicode;
|
||||
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Hooking.WndProcHook;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
|
@ -77,6 +78,8 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
{
|
||||
get
|
||||
{
|
||||
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||
return true;
|
||||
if (!ImGui.GetIO().ConfigInputTextCursorBlink)
|
||||
return true;
|
||||
ref var textState = ref TextState;
|
||||
|
|
@ -185,7 +188,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
/// Processes window messages.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
public void ProcessImeMessage(ref WndProcHookManager.WndProcOverrideEventArgs args)
|
||||
public void ProcessImeMessage(WndProcEventArgs args)
|
||||
{
|
||||
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||
return;
|
||||
|
|
@ -208,11 +211,11 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
when (nint)args.WParam is IMN.IMN_OPENCANDIDATE or IMN.IMN_CLOSECANDIDATE
|
||||
or IMN.IMN_CHANGECANDIDATE:
|
||||
this.UpdateImeWindowStatus(hImc);
|
||||
args.SuppressAndReturn(0);
|
||||
args.SuppressWithValue(0);
|
||||
break;
|
||||
|
||||
case WM.WM_IME_STARTCOMPOSITION:
|
||||
args.SuppressAndReturn(0);
|
||||
args.SuppressWithValue(0);
|
||||
break;
|
||||
|
||||
case WM.WM_IME_COMPOSITION:
|
||||
|
|
@ -222,22 +225,22 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
this.ReplaceCompositionString(hImc, (uint)args.LParam);
|
||||
|
||||
// Log.Verbose($"{nameof(WM.WM_IME_COMPOSITION)}({(nint)args.LParam:X}): {this.ImmComp}");
|
||||
args.SuppressAndReturn(0);
|
||||
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}");
|
||||
args.SuppressAndReturn(0);
|
||||
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}");
|
||||
args.SuppressAndReturn(0);
|
||||
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}");
|
||||
args.SuppressAndReturn(0);
|
||||
args.SuppressWithValue(0);
|
||||
break;
|
||||
|
||||
case WM.WM_IME_SETCONTEXT:
|
||||
|
|
@ -298,7 +301,11 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
return new(data, 0, numBytes / 2);
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources() => ImGui.GetIO().SetPlatformImeDataFn = nint.Zero;
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
if (ImGuiHelpers.IsImGuiInitialized)
|
||||
ImGui.GetIO().SetPlatformImeDataFn = nint.Zero;
|
||||
}
|
||||
|
||||
private void UpdateInputLanguage(HIMC hImc)
|
||||
{
|
||||
|
|
@ -492,8 +499,16 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType
|
|||
}
|
||||
|
||||
[ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui.dll to become available.")]
|
||||
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene) =>
|
||||
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
|
||||
{
|
||||
if (!ImGuiHelpers.IsImGuiInitialized)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Expected {nameof(InterfaceManager.InterfaceManagerWithScene)} to have initialized ImGui.");
|
||||
}
|
||||
|
||||
ImGui.GetIO().SetPlatformImeDataFn = Marshal.GetFunctionPointerForDelegate(this.setPlatformImeDataDelegate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ported from imstb_textedit.h.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ using Dalamud.Game.ClientState.GamePad;
|
|||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Internal.DXGI;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Hooking.WndProcHook;
|
||||
using Dalamud.Interface.GameFonts;
|
||||
using Dalamud.Interface.Internal.ManagedAsserts;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
|
|
@ -659,16 +660,13 @@ internal class InterfaceManager : IDisposable, IServiceType
|
|||
this.wndProcHookManager.PreWndProc += this.WndProcHookManagerOnPreWndProc;
|
||||
}
|
||||
|
||||
private unsafe void WndProcHookManagerOnPreWndProc(ref WndProcHookManager.WndProcOverrideEventArgs args)
|
||||
private unsafe void WndProcHookManagerOnPreWndProc(WndProcEventArgs args)
|
||||
{
|
||||
var r = this.scene?.ProcessWndProcW(args.Hwnd, (User32.WindowMessage)args.Message, args.WParam, args.LParam);
|
||||
if (r is not null)
|
||||
{
|
||||
args.ReturnValue = r.Value;
|
||||
args.SuppressCall = true;
|
||||
}
|
||||
args.SuppressWithValue(r.Value);
|
||||
|
||||
this.dalamudIme.ProcessImeMessage(ref args);
|
||||
this.dalamudIme.ProcessImeMessage(args);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -1,275 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Logging.Internal;
|
||||
|
||||
using TerraFX.Interop.Windows;
|
||||
|
||||
using static TerraFX.Interop.Windows.Windows;
|
||||
|
||||
namespace Dalamud.Interface.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// A manifestation of "I can't believe this is required".
|
||||
/// </summary>
|
||||
[ServiceManager.BlockingEarlyLoadedService]
|
||||
internal sealed class WndProcHookManager : IServiceType, IDisposable
|
||||
{
|
||||
private static readonly ModuleLog Log = new("WPHM");
|
||||
|
||||
private readonly Hook<DispatchMessageWDelegate> dispatchMessageWHook;
|
||||
private readonly Dictionary<HWND, nint> wndProcNextDict = new();
|
||||
private readonly WndProcDelegate wndProcDelegate;
|
||||
private readonly uint unhookSelfMessage;
|
||||
private bool disposed;
|
||||
|
||||
[ServiceManager.ServiceConstructor]
|
||||
private unsafe WndProcHookManager()
|
||||
{
|
||||
this.wndProcDelegate = this.WndProcDetour;
|
||||
this.dispatchMessageWHook = Hook<DispatchMessageWDelegate>.FromImport(
|
||||
null, "user32.dll", "DispatchMessageW", 0, this.DispatchMessageWDetour);
|
||||
this.dispatchMessageWHook.Enable();
|
||||
fixed (void* pMessageName = $"{nameof(WndProcHookManager)}.{nameof(this.unhookSelfMessage)}")
|
||||
this.unhookSelfMessage = RegisterWindowMessageW((ushort*)pMessageName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="WndProcHookManager"/> class.
|
||||
/// </summary>
|
||||
~WndProcHookManager() => this.ReleaseUnmanagedResources();
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for overriding WndProc.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments.</param>
|
||||
public delegate void WndProcOverrideDelegate(ref WndProcOverrideEventArgs args);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate LRESULT WndProcDelegate(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
|
||||
private delegate nint DispatchMessageWDelegate(ref MSG msg);
|
||||
|
||||
/// <summary>
|
||||
/// Called before WndProc.
|
||||
/// </summary>
|
||||
public event WndProcOverrideDelegate? PreWndProc;
|
||||
|
||||
/// <summary>
|
||||
/// Called after WndProc.
|
||||
/// </summary>
|
||||
public event WndProcOverrideDelegate? PostWndProc;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
this.disposed = true;
|
||||
this.dispatchMessageWHook.Dispose();
|
||||
this.ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detour for <see cref="DispatchMessageW"/>. Used to discover new windows to hook.
|
||||
/// </summary>
|
||||
/// <param name="msg">The message.</param>
|
||||
/// <returns>The original return value.</returns>
|
||||
private unsafe nint DispatchMessageWDetour(ref MSG msg)
|
||||
{
|
||||
lock (this.wndProcNextDict)
|
||||
{
|
||||
if (!this.disposed && ImGuiHelpers.FindViewportId(msg.hwnd) >= 0 &&
|
||||
!this.wndProcNextDict.ContainsKey(msg.hwnd))
|
||||
{
|
||||
this.wndProcNextDict[msg.hwnd] = SetWindowLongPtrW(
|
||||
msg.hwnd,
|
||||
GWLP.GWLP_WNDPROC,
|
||||
Marshal.GetFunctionPointerForDelegate(this.wndProcDelegate));
|
||||
}
|
||||
}
|
||||
|
||||
return this.dispatchMessageWHook.IsDisposed
|
||||
? DispatchMessageW((MSG*)Unsafe.AsPointer(ref msg))
|
||||
: this.dispatchMessageWHook.Original(ref msg);
|
||||
}
|
||||
|
||||
private unsafe LRESULT WndProcDetour(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
nint nextProc;
|
||||
lock (this.wndProcNextDict)
|
||||
{
|
||||
if (!this.wndProcNextDict.TryGetValue(hwnd, out nextProc))
|
||||
{
|
||||
// Something went wrong; prevent crash. Things will, regardless of the effort, break.
|
||||
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
if (uMsg == this.unhookSelfMessage)
|
||||
{
|
||||
// Even though this message is dedicated for our processing,
|
||||
// satisfy the expectations by calling the next window procedure.
|
||||
var rv = CallWindowProcW(
|
||||
(delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)nextProc,
|
||||
hwnd,
|
||||
uMsg,
|
||||
wParam,
|
||||
lParam);
|
||||
|
||||
// Remove self from the chain.
|
||||
SetWindowLongPtrW(hwnd, GWLP.GWLP_WNDPROC, nextProc);
|
||||
lock (this.wndProcNextDict)
|
||||
this.wndProcNextDict.Remove(hwnd);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
var arg = new WndProcOverrideEventArgs(hwnd, ref uMsg, ref wParam, ref lParam);
|
||||
try
|
||||
{
|
||||
this.PreWndProc?.Invoke(ref arg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(this.PostWndProc)} error");
|
||||
}
|
||||
|
||||
if (!arg.SuppressCall)
|
||||
{
|
||||
try
|
||||
{
|
||||
arg.ReturnValue = CallWindowProcW(
|
||||
(delegate* unmanaged<HWND, uint, WPARAM, LPARAM, LRESULT>)nextProc,
|
||||
hwnd,
|
||||
uMsg,
|
||||
wParam,
|
||||
lParam);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(CallWindowProcW)} error; probably some other software's fault");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
this.PostWndProc?.Invoke(ref arg);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"{nameof(this.PostWndProc)} error");
|
||||
}
|
||||
}
|
||||
|
||||
if (uMsg == WM.WM_NCDESTROY)
|
||||
{
|
||||
// The window will cease to exist, once we return.
|
||||
SetWindowLongPtrW(hwnd, GWLP.GWLP_WNDPROC, nextProc);
|
||||
lock (this.wndProcNextDict)
|
||||
this.wndProcNextDict.Remove(hwnd);
|
||||
}
|
||||
|
||||
return arg.ReturnValue;
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources()
|
||||
{
|
||||
this.disposed = true;
|
||||
|
||||
// As wndProcNextDict will be touched on each SendMessageW call, make a copy of window list first.
|
||||
HWND[] windows;
|
||||
lock (this.wndProcNextDict)
|
||||
windows = this.wndProcNextDict.Keys.ToArray();
|
||||
|
||||
// Unregister our hook from all the windows we hooked.
|
||||
foreach (var v in windows)
|
||||
SendMessageW(v, this.unhookSelfMessage, default, default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parameters for <see cref="WndProcOverrideDelegate"/>.
|
||||
/// </summary>
|
||||
public ref struct WndProcOverrideEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The handle of the target window of the message.
|
||||
/// </summary>
|
||||
public readonly HWND Hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// The message.
|
||||
/// </summary>
|
||||
public ref uint Message;
|
||||
|
||||
/// <summary>
|
||||
/// The WPARAM.
|
||||
/// </summary>
|
||||
public ref WPARAM WParam;
|
||||
|
||||
/// <summary>
|
||||
/// The LPARAM.
|
||||
/// </summary>
|
||||
public ref LPARAM LParam;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WndProcOverrideEventArgs"/> struct.
|
||||
/// </summary>
|
||||
/// <param name="hwnd">The handle of the target window of the message.</param>
|
||||
/// <param name="msg">The message.</param>
|
||||
/// <param name="wParam">The WPARAM.</param>
|
||||
/// <param name="lParam">The LPARAM.</param>
|
||||
public WndProcOverrideEventArgs(HWND hwnd, ref uint msg, ref WPARAM wParam, ref LPARAM lParam)
|
||||
{
|
||||
this.Hwnd = hwnd;
|
||||
this.LParam = ref lParam;
|
||||
this.WParam = ref wParam;
|
||||
this.Message = ref msg;
|
||||
this.ViewportId = ImGuiHelpers.FindViewportId(hwnd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to suppress calling the next WndProc in the chain.<br />
|
||||
/// Does nothing if changed from <see cref="WndProcHookManager.PostWndProc"/>.
|
||||
/// </summary>
|
||||
public bool SuppressCall { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the return value.<br />
|
||||
/// Has the return value from next window procedure, if accessed from <see cref="WndProcHookManager.PostWndProc"/>.
|
||||
/// </summary>
|
||||
public LRESULT ReturnValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ImGui viewport ID.
|
||||
/// </summary>
|
||||
public int ViewportId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this message is for the game window (the first viewport).
|
||||
/// </summary>
|
||||
public bool IsGameWindow => this.ViewportId == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="SuppressCall"/> to <c>true</c> and sets <see cref="ReturnValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="returnValue">The new return value.</param>
|
||||
public void SuppressAndReturn(LRESULT returnValue)
|
||||
{
|
||||
this.ReturnValue = returnValue;
|
||||
this.SuppressCall = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="SuppressCall"/> to <c>true</c> and calls <see cref="DefWindowProcW"/>.
|
||||
/// </summary>
|
||||
public void SuppressWithDefault()
|
||||
{
|
||||
this.ReturnValue = DefWindowProcW(this.Hwnd, this.Message, this.WParam, this.LParam);
|
||||
this.SuppressCall = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue