diff --git a/CustomResolution2782/Configuration.cs b/CustomResolution2782/Configuration.cs index 802aa6b..98b76c4 100644 --- a/CustomResolution2782/Configuration.cs +++ b/CustomResolution2782/Configuration.cs @@ -23,6 +23,8 @@ public class Configuration : IPluginConfiguration public DXVKDWMHackMode DXVKDWMHackMode = DXVKDWMHackMode.Off; + public MinSizeMode MinSizeMode = MinSizeMode.Unchanged; + [NonSerialized] private IDalamudPluginInterface? pluginInterface; @@ -123,3 +125,26 @@ public static class ModifierKeyExt _ => 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 + }; +} diff --git a/CustomResolution2782/Hooks/WndProcHook.cs b/CustomResolution2782/Hooks/WndProcHook.cs index 819adec..42de09b 100644 --- a/CustomResolution2782/Hooks/WndProcHook.cs +++ b/CustomResolution2782/Hooks/WndProcHook.cs @@ -1,5 +1,6 @@ using CustomResolution.WndProcHookManagerProxyApi; using System; +using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using TerraFX.Interop.Windows; @@ -15,8 +16,10 @@ public sealed unsafe class WndProcHook : IDisposable private readonly WndProcHookManager _manager; private bool _applied = false; - private DynamicMethod? _genMethod; - private Delegate? _genDelegate; + private DynamicMethod? _genMethodPre; + private Delegate? _genDelegatePre; + private DynamicMethod? _genMethodPost; + private Delegate? _genDelegatePost; public WndProcHook() { @@ -34,23 +37,40 @@ public sealed unsafe class WndProcHook : IDisposable 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."); - - 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; } - public static void InvokeStatic(object args) + public static void InvokeStaticPre(object args) { 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; } - private void Invoke(WndProcEventArgs args) + private void InvokePre(WndProcEventArgs args) { if (Service.Plugin is not { } plugin) { return; } + if (args.Message == WM.WM_WINDOWPOSCHANGING && + Service.Config.MinSizeMode == MinSizeMode.Unlocked) + { + args.SuppressCall = true; + } + if (args.Message == WM.WM_SIZE) { 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() { if (!_manager.Refresh()) @@ -163,34 +197,56 @@ public sealed unsafe class WndProcHook : IDisposable 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; } - if (_genDelegate is null) + if (_genDelegatePre is null) { var delegateType = preWndProcProp.EventHandlerType!; var delegateInvoke = delegateType.GetMethod("Invoke")!; - _genMethod = new DynamicMethod( + _genMethodPre = new DynamicMethod( "CustomResolution_PreWndProc", delegateInvoke.ReturnType, delegateInvoke.GetParameters().Select(p => p.ParameterType).ToArray() ); - var il = _genMethod.GetILGenerator(); + var il = _genMethodPre.GetILGenerator(); 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); - _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; } diff --git a/CustomResolution2782/Windows/ConfigWindow.cs b/CustomResolution2782/Windows/ConfigWindow.cs index f523360..b9556af 100644 --- a/CustomResolution2782/Windows/ConfigWindow.cs +++ b/CustomResolution2782/Windows/ConfigWindow.cs @@ -21,6 +21,7 @@ public class ConfigWindow : Window, IDisposable private float _configScale; private int[] _configWH = new int[2]; private DXVKDWMHackMode _configDXVKDWMHackMode; + private MinSizeMode _configMinSizeMode; public ConfigWindow() : base( "CustomResolution", @@ -45,6 +46,7 @@ public class ConfigWindow : Window, IDisposable _configWH[0] = (int) config.Width; _configWH[1] = (int) config.Height; _configDXVKDWMHackMode = config.DXVKDWMHackMode; + _configMinSizeMode = config.MinSizeMode; } public void UpdateToConfig() @@ -59,6 +61,7 @@ public class ConfigWindow : Window, IDisposable config.Width = (uint) _configWH[0]; config.Height = (uint) _configWH[1]; config.DXVKDWMHackMode = _configDXVKDWMHackMode; + config.MinSizeMode = _configMinSizeMode; config.Save(); } @@ -182,6 +185,24 @@ Works even with the scaling above disabled. Not intended to be used with proper fullscreen."); } + if (ImGui.BeginCombo("Minimum window size", _configMinSizeMode.ToHumanNameString())) + { + foreach (var mode in Enum.GetValues()) + { + 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")) { save = true; diff --git a/CustomResolution2782/WndProcHookManagerProxyApi/WndProcEventArgs.cs b/CustomResolution2782/WndProcHookManagerProxyApi/WndProcEventArgs.cs index 6cef981..e1abe36 100644 --- a/CustomResolution2782/WndProcHookManagerProxyApi/WndProcEventArgs.cs +++ b/CustomResolution2782/WndProcHookManagerProxyApi/WndProcEventArgs.cs @@ -32,4 +32,10 @@ public readonly record struct WndProcEventArgs(WndProcHookManager WndProcHookMan get => (bool) WndProcHookManager.GetValue(ProxiedValue, nameof(SuppressCall))!; set => WndProcHookManager.SetValue(ProxiedValue, nameof(SuppressCall), value); } + + public LRESULT ReturnValue + { + get => (LRESULT) WndProcHookManager.GetValue(ProxiedValue, nameof(ReturnValue))!; + set => WndProcHookManager.SetValue(ProxiedValue, nameof(ReturnValue), value); + } } diff --git a/CustomResolution2782/WndProcHookManagerProxyApi/WndProcHookManager.cs b/CustomResolution2782/WndProcHookManagerProxyApi/WndProcHookManager.cs index 4c4dd16..5f666ac 100644 --- a/CustomResolution2782/WndProcHookManagerProxyApi/WndProcHookManager.cs +++ b/CustomResolution2782/WndProcHookManagerProxyApi/WndProcHookManager.cs @@ -28,6 +28,7 @@ public class WndProcHookManager public object? ProxiedValue => _realManager; public EventInfo? PreWndProc { get; private set; } + public EventInfo? PostWndProc { get; private set; } public bool Refresh() { @@ -95,6 +96,7 @@ public class WndProcHookManager } PreWndProc = _realManagerType.GetEvent("PreWndProc"); + PostWndProc = _realManagerType.GetEvent("PostWndProc"); return true; } @@ -276,6 +278,7 @@ public class WndProcHookManager _realManagerType = default; PreWndProc = default; + PostWndProc = default; ClearCache(); }