Allow unlocking the minimum window size (WM_WINDOWPOSCHANGING)
This commit is contained in:
parent
ab5d130bb5
commit
e8d8f34ce7
5 changed files with 128 additions and 17 deletions
|
@ -23,6 +23,8 @@ public class Configuration : IPluginConfiguration
|
||||||
|
|
||||||
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off;
|
||||||
|
|
||||||
|
public MinSizeMode MinSizeMode = MinSizeMode.Unchanged;
|
||||||
|
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
private IDalamudPluginInterface? pluginInterface;
|
private IDalamudPluginInterface? pluginInterface;
|
||||||
|
|
||||||
|
@ -123,3 +125,26 @@ public static class ModifierKeyExt
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MinSizeMode
|
||||||
|
{
|
||||||
|
Unchanged = 0,
|
||||||
|
Unlocked = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class MinSizeModeExt
|
||||||
|
{
|
||||||
|
public static string ToHumanNameString(this MinSizeMode mode) => mode switch
|
||||||
|
{
|
||||||
|
MinSizeMode.Unchanged => "Unchanged (1024x768)",
|
||||||
|
MinSizeMode.Unlocked => "Unlocked",
|
||||||
|
_ => mode.ToString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string? ToHumanInfoString(this MinSizeMode mode) => mode switch
|
||||||
|
{
|
||||||
|
MinSizeMode.Unchanged => "The game normally doesn't allow resizing to smaller than 1024x768.",
|
||||||
|
MinSizeMode.Unlocked => "Allow resizing the game in windowed mode to smaller than 1024x768. Works best with scaling over 100%.",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using CustomResolution.WndProcHookManagerProxyApi;
|
using CustomResolution.WndProcHookManagerProxyApi;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using TerraFX.Interop.Windows;
|
using TerraFX.Interop.Windows;
|
||||||
|
@ -15,8 +16,10 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
private readonly WndProcHookManager _manager;
|
private readonly WndProcHookManager _manager;
|
||||||
|
|
||||||
private bool _applied = false;
|
private bool _applied = false;
|
||||||
private DynamicMethod? _genMethod;
|
private DynamicMethod? _genMethodPre;
|
||||||
private Delegate? _genDelegate;
|
private Delegate? _genDelegatePre;
|
||||||
|
private DynamicMethod? _genMethodPost;
|
||||||
|
private Delegate? _genDelegatePost;
|
||||||
|
|
||||||
public WndProcHook()
|
public WndProcHook()
|
||||||
{
|
{
|
||||||
|
@ -34,23 +37,40 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_manager.PreWndProc is not { } preWndProcProp)
|
if (_manager.PreWndProc is { } preWndProcProp)
|
||||||
|
{
|
||||||
|
preWndProcProp.RemoveEventHandler(_manager.ProxiedValue, _genDelegatePre);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Service.PluginLog.Information("CustomResolution couldn't obtain the PreWndProc event.");
|
Service.PluginLog.Information("CustomResolution couldn't obtain the PreWndProc event.");
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preWndProcProp.RemoveEventHandler(_manager.ProxiedValue, _genDelegate);
|
if (_manager.PostWndProc is { } postWndProcProp)
|
||||||
|
{
|
||||||
|
postWndProcProp.RemoveEventHandler(_manager.ProxiedValue, _genDelegatePost);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Service.PluginLog.Information("CustomResolution couldn't obtain the PostWndProc event.");
|
||||||
|
}
|
||||||
|
|
||||||
_applied = false;
|
_applied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InvokeStatic(object args)
|
public static void InvokeStaticPre(object args)
|
||||||
{
|
{
|
||||||
if (_instance is { } instance)
|
if (_instance is { } instance)
|
||||||
{
|
{
|
||||||
instance.Invoke(new WndProcEventArgs(instance._manager, args));
|
instance.InvokePre(new WndProcEventArgs(instance._manager, args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void InvokeStaticPost(object args)
|
||||||
|
{
|
||||||
|
if (_instance is { } instance)
|
||||||
|
{
|
||||||
|
instance.InvokePost(new WndProcEventArgs(instance._manager, args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,13 +104,19 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Invoke(WndProcEventArgs args)
|
private void InvokePre(WndProcEventArgs args)
|
||||||
{
|
{
|
||||||
if (Service.Plugin is not { } plugin)
|
if (Service.Plugin is not { } plugin)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (args.Message == WM.WM_WINDOWPOSCHANGING &&
|
||||||
|
Service.Config.MinSizeMode == MinSizeMode.Unlocked)
|
||||||
|
{
|
||||||
|
args.SuppressCall = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.Message == WM.WM_SIZE)
|
if (args.Message == WM.WM_SIZE)
|
||||||
{
|
{
|
||||||
ParamToCoords(args.LParam, out int x, out int y);
|
ParamToCoords(args.LParam, out int x, out int y);
|
||||||
|
@ -151,6 +177,14 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void InvokePost(WndProcEventArgs args)
|
||||||
|
{
|
||||||
|
if (Service.Plugin is not { } plugin)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Apply()
|
private void Apply()
|
||||||
{
|
{
|
||||||
if (!_manager.Refresh())
|
if (!_manager.Refresh())
|
||||||
|
@ -163,34 +197,56 @@ public sealed unsafe class WndProcHook : IDisposable
|
||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_manager.PreWndProc is not { } preWndProcProp)
|
if (_manager.PreWndProc is not { } preWndProcProp ||
|
||||||
|
_manager.PostWndProc is not { } postWndProcProp)
|
||||||
{
|
{
|
||||||
Service.PluginLog.Information("CustomResolution couldn't obtain the PreWndProc event.");
|
Service.PluginLog.Information("CustomResolution couldn't obtain the PreWndProc / PostWndProc events.");
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_genDelegate is null)
|
if (_genDelegatePre is null)
|
||||||
{
|
{
|
||||||
var delegateType = preWndProcProp.EventHandlerType!;
|
var delegateType = preWndProcProp.EventHandlerType!;
|
||||||
var delegateInvoke = delegateType.GetMethod("Invoke")!;
|
var delegateInvoke = delegateType.GetMethod("Invoke")!;
|
||||||
|
|
||||||
_genMethod = new DynamicMethod(
|
_genMethodPre = new DynamicMethod(
|
||||||
"CustomResolution_PreWndProc",
|
"CustomResolution_PreWndProc",
|
||||||
delegateInvoke.ReturnType,
|
delegateInvoke.ReturnType,
|
||||||
delegateInvoke.GetParameters().Select(p => p.ParameterType).ToArray()
|
delegateInvoke.GetParameters().Select(p => p.ParameterType).ToArray()
|
||||||
);
|
);
|
||||||
|
|
||||||
var il = _genMethod.GetILGenerator();
|
var il = _genMethodPre.GetILGenerator();
|
||||||
|
|
||||||
il.Emit(OpCodes.Ldarg_0);
|
il.Emit(OpCodes.Ldarg_0);
|
||||||
il.Emit(OpCodes.Call, typeof(WndProcHook).GetMethod("InvokeStatic")!);
|
il.Emit(OpCodes.Call, typeof(WndProcHook).GetMethod(nameof(InvokeStaticPre))!);
|
||||||
il.Emit(OpCodes.Ret);
|
il.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
_genDelegate = _genMethod.CreateDelegate(delegateType);
|
_genDelegatePre = _genMethodPre.CreateDelegate(delegateType);
|
||||||
}
|
}
|
||||||
|
|
||||||
preWndProcProp.AddEventHandler(_manager.ProxiedValue, _genDelegate);
|
if (_genDelegatePost is null)
|
||||||
|
{
|
||||||
|
var delegateType = preWndProcProp.EventHandlerType!;
|
||||||
|
var delegateInvoke = delegateType.GetMethod("Invoke")!;
|
||||||
|
|
||||||
|
_genMethodPost = new DynamicMethod(
|
||||||
|
"CustomResolution_PostWndProc",
|
||||||
|
delegateInvoke.ReturnType,
|
||||||
|
delegateInvoke.GetParameters().Select(p => p.ParameterType).ToArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
var il = _genMethodPost.GetILGenerator();
|
||||||
|
|
||||||
|
il.Emit(OpCodes.Ldarg_0);
|
||||||
|
il.Emit(OpCodes.Call, typeof(WndProcHook).GetMethod(nameof(InvokeStaticPost))!);
|
||||||
|
il.Emit(OpCodes.Ret);
|
||||||
|
|
||||||
|
_genDelegatePost = _genMethodPost.CreateDelegate(delegateType);
|
||||||
|
}
|
||||||
|
|
||||||
|
preWndProcProp.AddEventHandler(_manager.ProxiedValue, _genDelegatePre);
|
||||||
|
postWndProcProp.AddEventHandler(_manager.ProxiedValue, _genDelegatePost);
|
||||||
|
|
||||||
_applied = true;
|
_applied = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class ConfigWindow : Window, IDisposable
|
||||||
private float _configScale;
|
private float _configScale;
|
||||||
private int[] _configWH = new int[2];
|
private int[] _configWH = new int[2];
|
||||||
private DXVKDWMHackMode _configDXVKDWMHackMode;
|
private DXVKDWMHackMode _configDXVKDWMHackMode;
|
||||||
|
private MinSizeMode _configMinSizeMode;
|
||||||
|
|
||||||
public ConfigWindow() : base(
|
public ConfigWindow() : base(
|
||||||
"CustomResolution",
|
"CustomResolution",
|
||||||
|
@ -45,6 +46,7 @@ public class ConfigWindow : Window, IDisposable
|
||||||
_configWH[0] = (int) config.Width;
|
_configWH[0] = (int) config.Width;
|
||||||
_configWH[1] = (int) config.Height;
|
_configWH[1] = (int) config.Height;
|
||||||
_configDXVKDWMHackMode = config.DXVKDWMHackMode;
|
_configDXVKDWMHackMode = config.DXVKDWMHackMode;
|
||||||
|
_configMinSizeMode = config.MinSizeMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateToConfig()
|
public void UpdateToConfig()
|
||||||
|
@ -59,6 +61,7 @@ public class ConfigWindow : Window, IDisposable
|
||||||
config.Width = (uint) _configWH[0];
|
config.Width = (uint) _configWH[0];
|
||||||
config.Height = (uint) _configWH[1];
|
config.Height = (uint) _configWH[1];
|
||||||
config.DXVKDWMHackMode = _configDXVKDWMHackMode;
|
config.DXVKDWMHackMode = _configDXVKDWMHackMode;
|
||||||
|
config.MinSizeMode = _configMinSizeMode;
|
||||||
|
|
||||||
config.Save();
|
config.Save();
|
||||||
}
|
}
|
||||||
|
@ -182,6 +185,24 @@ Works even with the scaling above disabled.
|
||||||
Not intended to be used with proper fullscreen.");
|
Not intended to be used with proper fullscreen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginCombo("Minimum window size", _configMinSizeMode.ToHumanNameString()))
|
||||||
|
{
|
||||||
|
foreach (var mode in Enum.GetValues<MinSizeMode>())
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable(mode.ToHumanNameString(), _configMinSizeMode == mode, ImGuiSelectableFlags.None))
|
||||||
|
{
|
||||||
|
_configMinSizeMode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered() && mode.ToHumanInfoString() is { } info)
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.Button("Save and apply"))
|
if (ImGui.Button("Save and apply"))
|
||||||
{
|
{
|
||||||
save = true;
|
save = true;
|
||||||
|
|
|
@ -32,4 +32,10 @@ public readonly record struct WndProcEventArgs(WndProcHookManager WndProcHookMan
|
||||||
get => (bool) WndProcHookManager.GetValue(ProxiedValue, nameof(SuppressCall))!;
|
get => (bool) WndProcHookManager.GetValue(ProxiedValue, nameof(SuppressCall))!;
|
||||||
set => WndProcHookManager.SetValue(ProxiedValue, nameof(SuppressCall), value);
|
set => WndProcHookManager.SetValue(ProxiedValue, nameof(SuppressCall), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LRESULT ReturnValue
|
||||||
|
{
|
||||||
|
get => (LRESULT) WndProcHookManager.GetValue(ProxiedValue, nameof(ReturnValue))!;
|
||||||
|
set => WndProcHookManager.SetValue(ProxiedValue, nameof(ReturnValue), value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ public class WndProcHookManager
|
||||||
public object? ProxiedValue => _realManager;
|
public object? ProxiedValue => _realManager;
|
||||||
|
|
||||||
public EventInfo? PreWndProc { get; private set; }
|
public EventInfo? PreWndProc { get; private set; }
|
||||||
|
public EventInfo? PostWndProc { get; private set; }
|
||||||
|
|
||||||
public bool Refresh()
|
public bool Refresh()
|
||||||
{
|
{
|
||||||
|
@ -95,6 +96,7 @@ public class WndProcHookManager
|
||||||
}
|
}
|
||||||
|
|
||||||
PreWndProc = _realManagerType.GetEvent("PreWndProc");
|
PreWndProc = _realManagerType.GetEvent("PreWndProc");
|
||||||
|
PostWndProc = _realManagerType.GetEvent("PostWndProc");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -276,6 +278,7 @@ public class WndProcHookManager
|
||||||
_realManagerType = default;
|
_realManagerType = default;
|
||||||
|
|
||||||
PreWndProc = default;
|
PreWndProc = default;
|
||||||
|
PostWndProc = default;
|
||||||
|
|
||||||
ClearCache();
|
ClearCache();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue