chore: refactor keybinds to be in new KeybindManager
This commit is contained in:
@@ -1,15 +1,11 @@
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Memory;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Application.Network;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
@@ -29,10 +25,6 @@ namespace ChatTwo.GameFunctions;
|
||||
internal sealed unsafe class Chat : IDisposable
|
||||
{
|
||||
// Functions
|
||||
// Replace with <https://github.com/aers/FFXIVClientStructs/pull/1036>
|
||||
[Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")]
|
||||
private readonly delegate* unmanaged<UIInputData*, Utf8String*, nint, uint> GetKeybindNative = null!;
|
||||
|
||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D B9 ?? ?? ?? ?? 33 C0")]
|
||||
private readonly delegate* unmanaged<RaptureLogModule*, ushort, Utf8String*, Utf8String*, ulong, ulong, ushort, byte, int, byte, void> PrintTellNative = null!;
|
||||
|
||||
@@ -54,11 +46,6 @@ internal sealed unsafe class Chat : IDisposable
|
||||
[Signature("48 8D 35 ?? ?? ?? ?? 8B 05", ScanType = ScanType.StaticAddress)]
|
||||
private readonly char* CurrentCharacter = null!;
|
||||
|
||||
// Events
|
||||
|
||||
internal event ChatActivatedEventDelegate? Activated;
|
||||
internal delegate void ChatActivatedEventDelegate(ChatActivatedArgs args);
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -70,9 +57,6 @@ internal sealed unsafe class Chat : IDisposable
|
||||
internal bool UsesTellTempChannel { get; set; }
|
||||
internal InputChannel? PreviousChannel { get; private set; }
|
||||
|
||||
private bool DirectChat;
|
||||
private long LastRefresh;
|
||||
|
||||
internal Chat(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
@@ -92,7 +76,6 @@ internal sealed unsafe class Chat : IDisposable
|
||||
// EurekaContextMenuTellHook = Plugin.GameInteropProvider.HookFromAddress<RaptureShellModule.Delegates.SetContextTellTargetInForay>(RaptureShellModule.MemberFunctionPointers.SetContextTellTargetInForay, SetContextTellTargetInForay);
|
||||
// EurekaContextMenuTellHook.Enable();
|
||||
|
||||
Plugin.Framework.Update += InterceptKeybinds;
|
||||
Plugin.ClientState.Login += Login;
|
||||
Login();
|
||||
}
|
||||
@@ -100,15 +83,12 @@ internal sealed unsafe class Chat : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin.ClientState.Login -= Login;
|
||||
Plugin.Framework.Update -= InterceptKeybinds;
|
||||
|
||||
SetChatLogTellTargetHook?.Dispose();
|
||||
ReplyInSelectedChatModeHook?.Dispose();
|
||||
ChangeChannelNameHook?.Dispose();
|
||||
ChatLogRefreshHook?.Dispose();
|
||||
EurekaContextMenuTellHook?.Dispose();
|
||||
|
||||
Activated = null;
|
||||
}
|
||||
|
||||
internal string? GetLinkshellName(uint idx)
|
||||
@@ -164,182 +144,6 @@ internal sealed unsafe class Chat : IDisposable
|
||||
return 0xFF | (rgb << 8);
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, Keybind> _keybinds = new();
|
||||
internal IReadOnlyDictionary<string, Keybind> Keybinds => _keybinds;
|
||||
|
||||
internal static readonly IReadOnlyDictionary<string, ChannelSwitchInfo> KeybindsToIntercept = new Dictionary<string, ChannelSwitchInfo>
|
||||
{
|
||||
["CMD_CHAT"] = new(null),
|
||||
["CMD_COMMAND"] = new(null, text: "/"),
|
||||
["CMD_REPLY"] = new(InputChannel.Tell, rotate: RotateMode.Forward),
|
||||
["CMD_REPLY_REV"] = new(InputChannel.Tell, rotate: RotateMode.Reverse),
|
||||
["CMD_SAY"] = new(InputChannel.Say),
|
||||
["CMD_YELL"] = new(InputChannel.Yell),
|
||||
["CMD_SHOUT"] = new(InputChannel.Shout),
|
||||
["CMD_PARTY"] = new(InputChannel.Party),
|
||||
["CMD_ALLIANCE"] = new(InputChannel.Alliance),
|
||||
["CMD_FREECOM"] = new(InputChannel.FreeCompany),
|
||||
["PVPTEAM_CHAT"] = new(InputChannel.PvpTeam),
|
||||
["CMD_CWLINKSHELL"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Forward),
|
||||
["CMD_CWLINKSHELL_REV"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Reverse),
|
||||
["CMD_CWLINKSHELL_1"] = new(InputChannel.CrossLinkshell1),
|
||||
["CMD_CWLINKSHELL_2"] = new(InputChannel.CrossLinkshell2),
|
||||
["CMD_CWLINKSHELL_3"] = new(InputChannel.CrossLinkshell3),
|
||||
["CMD_CWLINKSHELL_4"] = new(InputChannel.CrossLinkshell4),
|
||||
["CMD_CWLINKSHELL_5"] = new(InputChannel.CrossLinkshell5),
|
||||
["CMD_CWLINKSHELL_6"] = new(InputChannel.CrossLinkshell6),
|
||||
["CMD_CWLINKSHELL_7"] = new(InputChannel.CrossLinkshell7),
|
||||
["CMD_CWLINKSHELL_8"] = new(InputChannel.CrossLinkshell8),
|
||||
["CMD_LINKSHELL"] = new(InputChannel.Linkshell1, rotate: RotateMode.Forward),
|
||||
["CMD_LINKSHELL_REV"] = new(InputChannel.Linkshell1, rotate: RotateMode.Reverse),
|
||||
["CMD_LINKSHELL_1"] = new(InputChannel.Linkshell1),
|
||||
["CMD_LINKSHELL_2"] = new(InputChannel.Linkshell2),
|
||||
["CMD_LINKSHELL_3"] = new(InputChannel.Linkshell3),
|
||||
["CMD_LINKSHELL_4"] = new(InputChannel.Linkshell4),
|
||||
["CMD_LINKSHELL_5"] = new(InputChannel.Linkshell5),
|
||||
["CMD_LINKSHELL_6"] = new(InputChannel.Linkshell6),
|
||||
["CMD_LINKSHELL_7"] = new(InputChannel.Linkshell7),
|
||||
["CMD_LINKSHELL_8"] = new(InputChannel.Linkshell8),
|
||||
["CMD_BEGINNER"] = new(InputChannel.NoviceNetwork),
|
||||
["CMD_REPLY_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Forward),
|
||||
["CMD_REPLY_REV_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Reverse),
|
||||
["CMD_SAY_ALWAYS"] = new(InputChannel.Say, true),
|
||||
["CMD_YELL_ALWAYS"] = new(InputChannel.Yell, true),
|
||||
["CMD_PARTY_ALWAYS"] = new(InputChannel.Party, true),
|
||||
["CMD_ALLIANCE_ALWAYS"] = new(InputChannel.Alliance, true),
|
||||
["CMD_FREECOM_ALWAYS"] = new(InputChannel.FreeCompany, true),
|
||||
["PVPTEAM_CHAT_ALWAYS"] = new(InputChannel.PvpTeam, true),
|
||||
["CMD_CWLINKSHELL_ALWAYS"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Forward),
|
||||
["CMD_CWLINKSHELL_ALWAYS_REV"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Reverse),
|
||||
["CMD_CWLINKSHELL_1_ALWAYS"] = new(InputChannel.CrossLinkshell1, true),
|
||||
["CMD_CWLINKSHELL_2_ALWAYS"] = new(InputChannel.CrossLinkshell2, true),
|
||||
["CMD_CWLINKSHELL_3_ALWAYS"] = new(InputChannel.CrossLinkshell3, true),
|
||||
["CMD_CWLINKSHELL_4_ALWAYS"] = new(InputChannel.CrossLinkshell4, true),
|
||||
["CMD_CWLINKSHELL_5_ALWAYS"] = new(InputChannel.CrossLinkshell5, true),
|
||||
["CMD_CWLINKSHELL_6_ALWAYS"] = new(InputChannel.CrossLinkshell6, true),
|
||||
["CMD_CWLINKSHELL_7_ALWAYS"] = new(InputChannel.CrossLinkshell7, true),
|
||||
["CMD_CWLINKSHELL_8_ALWAYS"] = new(InputChannel.CrossLinkshell8, true),
|
||||
["CMD_LINKSHELL_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Forward),
|
||||
["CMD_LINKSHELL_REV_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Reverse),
|
||||
["CMD_LINKSHELL_1_ALWAYS"] = new(InputChannel.Linkshell1, true),
|
||||
["CMD_LINKSHELL_2_ALWAYS"] = new(InputChannel.Linkshell2, true),
|
||||
["CMD_LINKSHELL_3_ALWAYS"] = new(InputChannel.Linkshell3, true),
|
||||
["CMD_LINKSHELL_4_ALWAYS"] = new(InputChannel.Linkshell4, true),
|
||||
["CMD_LINKSHELL_5_ALWAYS"] = new(InputChannel.Linkshell5, true),
|
||||
["CMD_LINKSHELL_6_ALWAYS"] = new(InputChannel.Linkshell6, true),
|
||||
["CMD_LINKSHELL_7_ALWAYS"] = new(InputChannel.Linkshell7, true),
|
||||
["CMD_LINKSHELL_8_ALWAYS"] = new(InputChannel.Linkshell8, true),
|
||||
["CMD_BEGINNER_ALWAYS"] = new(InputChannel.NoviceNetwork, true),
|
||||
};
|
||||
|
||||
private void UpdateKeybinds()
|
||||
{
|
||||
foreach (var name in KeybindsToIntercept.Keys)
|
||||
{
|
||||
var keybind = GetKeybind(name);
|
||||
if (keybind is null)
|
||||
continue;
|
||||
|
||||
_keybinds[name] = keybind;
|
||||
}
|
||||
}
|
||||
|
||||
private void InterceptKeybinds(IFramework framework1)
|
||||
{
|
||||
// Refresh current keybinds every 5s
|
||||
if (LastRefresh + 5 * 1000 < Environment.TickCount64)
|
||||
{
|
||||
UpdateKeybinds();
|
||||
DirectChat = Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option;
|
||||
LastRefresh = Environment.TickCount64;
|
||||
}
|
||||
|
||||
// Vanilla text input has focus
|
||||
if (RaptureAtkModule.Instance()->AtkModule.IsTextInputActive())
|
||||
return;
|
||||
|
||||
var modifierState = (ModifierFlag) 0;
|
||||
foreach (var modifier in Enum.GetValues<ModifierFlag>())
|
||||
{
|
||||
var modifierKey = GetKeyForModifier(modifier);
|
||||
if (modifierKey != VirtualKey.NO_KEY && Plugin.KeyState[modifierKey])
|
||||
modifierState |= modifier;
|
||||
}
|
||||
|
||||
bool KeyPressed(VirtualKey key, ModifierFlag modifier)
|
||||
{
|
||||
if (!Plugin.KeyState.IsVirtualKeyValid(key))
|
||||
return false;
|
||||
|
||||
var modifierPressed = Plugin.Config.KeybindMode switch
|
||||
{
|
||||
KeybindMode.Strict => modifier == modifierState,
|
||||
KeybindMode.Flexible => modifierState.HasFlag(modifier),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
return modifierPressed && Plugin.KeyState[key];
|
||||
}
|
||||
|
||||
// Test for custom keybinds for changing chat tabs before checking
|
||||
// vanilla keybinds.
|
||||
if (Plugin.Config.ChatTabBackward != null && KeyPressed(Plugin.Config.ChatTabBackward.Key, Plugin.Config.ChatTabBackward.Modifier))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabBackward.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(-1);
|
||||
return;
|
||||
}
|
||||
if (Plugin.Config.ChatTabForward != null && KeyPressed(Plugin.Config.ChatTabForward.Key, Plugin.Config.ChatTabForward.Modifier))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabForward.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(1);
|
||||
return;
|
||||
}
|
||||
|
||||
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
|
||||
foreach (var toIntercept in KeybindsToIntercept.Keys)
|
||||
{
|
||||
if (!Keybinds.TryGetValue(toIntercept, out var keybind))
|
||||
continue;
|
||||
|
||||
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND")
|
||||
{
|
||||
// Direct chat option is selected
|
||||
if (DirectChat)
|
||||
continue;
|
||||
}
|
||||
|
||||
void Intercept(VirtualKey key, ModifierFlag modifier)
|
||||
{
|
||||
if (!KeyPressed(key, modifier))
|
||||
return;
|
||||
|
||||
var bits = BitOperations.PopCount((uint) modifier);
|
||||
if (!turnedOff.TryGetValue(key, out var previousBits) || previousBits.Item1 < bits)
|
||||
turnedOff[key] = ((uint) bits, toIntercept);
|
||||
}
|
||||
|
||||
Intercept(keybind.Key1, keybind.Modifier1);
|
||||
Intercept(keybind.Key2, keybind.Modifier2);
|
||||
}
|
||||
|
||||
foreach (var (key, (_, keybind)) in turnedOff)
|
||||
{
|
||||
Plugin.KeyState[key] = false;
|
||||
if (!KeybindsToIntercept.TryGetValue(keybind, out var info))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
Activated?.Invoke(new ChatActivatedArgs(info) { TellReason = TellReason.Reply, });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Log.Error(ex, "Error in chat Activated event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Login()
|
||||
{
|
||||
var agent = AgentChatLog.Instance();
|
||||
@@ -357,7 +161,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
if (eventId != 0x31 || value == null || value->UInt is not (0x05 or 0x0C))
|
||||
return ChatLogRefreshHook!.Original(log, eventId, value);
|
||||
|
||||
if (DirectChat && CurrentCharacter != null)
|
||||
if (Plugin.Functions.KeybindManager.DirectChat && CurrentCharacter != null)
|
||||
{
|
||||
// FIXME: this whole system sucks
|
||||
// FIXME v2: I hate everything about this, but it works
|
||||
@@ -376,7 +180,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { Input = input, });
|
||||
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { Input = input, });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -397,7 +201,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { AddIfNotPresent = addIfNotPresent, });
|
||||
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(null)) { AddIfNotPresent = addIfNotPresent, });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -463,7 +267,7 @@ internal sealed unsafe class Chat : IDisposable
|
||||
try
|
||||
{
|
||||
var target = new TellTarget(playerName->ToString(), worldId, contentId, (TellReason) reason);
|
||||
Activated?.Invoke(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell))
|
||||
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(new ChannelSwitchInfo(InputChannel.Tell))
|
||||
{
|
||||
TellReason = (TellReason) reason,
|
||||
TellTarget = target,
|
||||
@@ -547,38 +351,6 @@ internal sealed unsafe class Chat : IDisposable
|
||||
utfWorld->Dtor(true);
|
||||
}
|
||||
|
||||
private static VirtualKey GetKeyForModifier(ModifierFlag modifierFlag) => modifierFlag switch
|
||||
{
|
||||
ModifierFlag.Shift => VirtualKey.SHIFT,
|
||||
ModifierFlag.Ctrl => VirtualKey.CONTROL,
|
||||
ModifierFlag.Alt => VirtualKey.MENU,
|
||||
_ => VirtualKey.NO_KEY,
|
||||
};
|
||||
|
||||
private Keybind GetKeybind(string id)
|
||||
{
|
||||
var outData = stackalloc byte[11];
|
||||
var idString = Utf8String.FromString(id);
|
||||
GetKeybindNative(UIInputData.Instance(), idString, (nint) outData);
|
||||
idString->Dtor(true);
|
||||
|
||||
var key1 = (VirtualKey) outData[0];
|
||||
if (key1 is VirtualKey.F23)
|
||||
key1 = VirtualKey.OEM_2;
|
||||
|
||||
var key2 = (VirtualKey) outData[2];
|
||||
if (key2 is VirtualKey.F23)
|
||||
key2 = VirtualKey.OEM_2;
|
||||
|
||||
return new Keybind
|
||||
{
|
||||
Key1 = key1,
|
||||
Modifier1 = (ModifierFlag) outData[1],
|
||||
Key2 = key2,
|
||||
Modifier2 = (ModifierFlag) outData[3],
|
||||
};
|
||||
}
|
||||
|
||||
internal TellHistoryInfo? GetTellHistoryInfo(int index)
|
||||
{
|
||||
var acquaintance = AcquaintanceModule.Instance()->GetTellHistory(index);
|
||||
|
||||
@@ -25,11 +25,13 @@ internal unsafe class GameFunctions : IDisposable
|
||||
#endregion
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
internal KeybindManager KeybindManager { get; }
|
||||
internal Chat Chat { get; }
|
||||
|
||||
internal GameFunctions(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
KeybindManager = new KeybindManager(plugin);
|
||||
Chat = new Chat(Plugin);
|
||||
|
||||
Plugin.GameInteropProvider.InitializeFromAttributes(this);
|
||||
@@ -40,6 +42,7 @@ internal unsafe class GameFunctions : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Chat.Dispose();
|
||||
KeybindManager.Dispose();
|
||||
|
||||
ResolveTextCommandPlaceholderHook?.Dispose();
|
||||
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Config;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.String;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace ChatTwo.GameFunctions;
|
||||
|
||||
internal enum KeyboardSource {
|
||||
Game,
|
||||
ImGui
|
||||
}
|
||||
|
||||
internal unsafe class KeybindManager : IDisposable {
|
||||
// Functions
|
||||
// Replace with <https://github.com/aers/FFXIVClientStructs/pull/1036>
|
||||
[Signature("E8 ?? ?? ?? ?? 48 8D 4D A0 8B F8")]
|
||||
private readonly delegate* unmanaged<UIInputData*, Utf8String*, nint, uint> GetKeybindNative = null!;
|
||||
|
||||
private Plugin Plugin { get; }
|
||||
|
||||
internal bool DirectChat;
|
||||
private long LastRefresh;
|
||||
|
||||
private readonly Dictionary<string, Keybind> Keybinds = new();
|
||||
private static readonly IReadOnlyDictionary<string, ChannelSwitchInfo> KeybindsToIntercept = new Dictionary<string, ChannelSwitchInfo>
|
||||
{
|
||||
["CMD_CHAT"] = new(null),
|
||||
["CMD_COMMAND"] = new(null, text: "/"),
|
||||
["CMD_REPLY"] = new(InputChannel.Tell, rotate: RotateMode.Forward),
|
||||
["CMD_REPLY_REV"] = new(InputChannel.Tell, rotate: RotateMode.Reverse),
|
||||
["CMD_SAY"] = new(InputChannel.Say),
|
||||
["CMD_YELL"] = new(InputChannel.Yell),
|
||||
["CMD_SHOUT"] = new(InputChannel.Shout),
|
||||
["CMD_PARTY"] = new(InputChannel.Party),
|
||||
["CMD_ALLIANCE"] = new(InputChannel.Alliance),
|
||||
["CMD_FREECOM"] = new(InputChannel.FreeCompany),
|
||||
["PVPTEAM_CHAT"] = new(InputChannel.PvpTeam),
|
||||
["CMD_CWLINKSHELL"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Forward),
|
||||
["CMD_CWLINKSHELL_REV"] = new(InputChannel.CrossLinkshell1, rotate: RotateMode.Reverse),
|
||||
["CMD_CWLINKSHELL_1"] = new(InputChannel.CrossLinkshell1),
|
||||
["CMD_CWLINKSHELL_2"] = new(InputChannel.CrossLinkshell2),
|
||||
["CMD_CWLINKSHELL_3"] = new(InputChannel.CrossLinkshell3),
|
||||
["CMD_CWLINKSHELL_4"] = new(InputChannel.CrossLinkshell4),
|
||||
["CMD_CWLINKSHELL_5"] = new(InputChannel.CrossLinkshell5),
|
||||
["CMD_CWLINKSHELL_6"] = new(InputChannel.CrossLinkshell6),
|
||||
["CMD_CWLINKSHELL_7"] = new(InputChannel.CrossLinkshell7),
|
||||
["CMD_CWLINKSHELL_8"] = new(InputChannel.CrossLinkshell8),
|
||||
["CMD_LINKSHELL"] = new(InputChannel.Linkshell1, rotate: RotateMode.Forward),
|
||||
["CMD_LINKSHELL_REV"] = new(InputChannel.Linkshell1, rotate: RotateMode.Reverse),
|
||||
["CMD_LINKSHELL_1"] = new(InputChannel.Linkshell1),
|
||||
["CMD_LINKSHELL_2"] = new(InputChannel.Linkshell2),
|
||||
["CMD_LINKSHELL_3"] = new(InputChannel.Linkshell3),
|
||||
["CMD_LINKSHELL_4"] = new(InputChannel.Linkshell4),
|
||||
["CMD_LINKSHELL_5"] = new(InputChannel.Linkshell5),
|
||||
["CMD_LINKSHELL_6"] = new(InputChannel.Linkshell6),
|
||||
["CMD_LINKSHELL_7"] = new(InputChannel.Linkshell7),
|
||||
["CMD_LINKSHELL_8"] = new(InputChannel.Linkshell8),
|
||||
["CMD_BEGINNER"] = new(InputChannel.NoviceNetwork),
|
||||
["CMD_REPLY_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Forward),
|
||||
["CMD_REPLY_REV_ALWAYS"] = new(InputChannel.Tell, true, RotateMode.Reverse),
|
||||
["CMD_SAY_ALWAYS"] = new(InputChannel.Say, true),
|
||||
["CMD_YELL_ALWAYS"] = new(InputChannel.Yell, true),
|
||||
["CMD_PARTY_ALWAYS"] = new(InputChannel.Party, true),
|
||||
["CMD_ALLIANCE_ALWAYS"] = new(InputChannel.Alliance, true),
|
||||
["CMD_FREECOM_ALWAYS"] = new(InputChannel.FreeCompany, true),
|
||||
["PVPTEAM_CHAT_ALWAYS"] = new(InputChannel.PvpTeam, true),
|
||||
["CMD_CWLINKSHELL_ALWAYS"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Forward),
|
||||
["CMD_CWLINKSHELL_ALWAYS_REV"] = new(InputChannel.CrossLinkshell1, true, RotateMode.Reverse),
|
||||
["CMD_CWLINKSHELL_1_ALWAYS"] = new(InputChannel.CrossLinkshell1, true),
|
||||
["CMD_CWLINKSHELL_2_ALWAYS"] = new(InputChannel.CrossLinkshell2, true),
|
||||
["CMD_CWLINKSHELL_3_ALWAYS"] = new(InputChannel.CrossLinkshell3, true),
|
||||
["CMD_CWLINKSHELL_4_ALWAYS"] = new(InputChannel.CrossLinkshell4, true),
|
||||
["CMD_CWLINKSHELL_5_ALWAYS"] = new(InputChannel.CrossLinkshell5, true),
|
||||
["CMD_CWLINKSHELL_6_ALWAYS"] = new(InputChannel.CrossLinkshell6, true),
|
||||
["CMD_CWLINKSHELL_7_ALWAYS"] = new(InputChannel.CrossLinkshell7, true),
|
||||
["CMD_CWLINKSHELL_8_ALWAYS"] = new(InputChannel.CrossLinkshell8, true),
|
||||
["CMD_LINKSHELL_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Forward),
|
||||
["CMD_LINKSHELL_REV_ALWAYS"] = new(InputChannel.Linkshell1, true, RotateMode.Reverse),
|
||||
["CMD_LINKSHELL_1_ALWAYS"] = new(InputChannel.Linkshell1, true),
|
||||
["CMD_LINKSHELL_2_ALWAYS"] = new(InputChannel.Linkshell2, true),
|
||||
["CMD_LINKSHELL_3_ALWAYS"] = new(InputChannel.Linkshell3, true),
|
||||
["CMD_LINKSHELL_4_ALWAYS"] = new(InputChannel.Linkshell4, true),
|
||||
["CMD_LINKSHELL_5_ALWAYS"] = new(InputChannel.Linkshell5, true),
|
||||
["CMD_LINKSHELL_6_ALWAYS"] = new(InputChannel.Linkshell6, true),
|
||||
["CMD_LINKSHELL_7_ALWAYS"] = new(InputChannel.Linkshell7, true),
|
||||
["CMD_LINKSHELL_8_ALWAYS"] = new(InputChannel.Linkshell8, true),
|
||||
["CMD_BEGINNER_ALWAYS"] = new(InputChannel.NoviceNetwork, true)
|
||||
};
|
||||
|
||||
// List of keys that can be used as a part of keybinds while the chat is
|
||||
// focused WITHOUT modifiers. All other keys can only be used if their
|
||||
// configured keybind contains modifiers. This allows for using e.g. F11 to
|
||||
// change chat channel while typing.
|
||||
private static readonly IReadOnlyCollection<VirtualKey> ModifierlessChatKeys = new[]
|
||||
{
|
||||
// VirtualKey.NO_KEY,
|
||||
VirtualKey.LBUTTON,
|
||||
VirtualKey.RBUTTON,
|
||||
VirtualKey.CANCEL,
|
||||
VirtualKey.MBUTTON,
|
||||
VirtualKey.XBUTTON1,
|
||||
VirtualKey.XBUTTON2,
|
||||
VirtualKey.BACK,
|
||||
VirtualKey.TAB,
|
||||
VirtualKey.CLEAR,
|
||||
// VirtualKey.RETURN, // handled by imgui
|
||||
// VirtualKey.SHIFT,
|
||||
// VirtualKey.CONTROL,
|
||||
// VirtualKey.MENU,
|
||||
VirtualKey.PAUSE,
|
||||
// VirtualKey.CAPITAL,
|
||||
// VirtualKey.KANA,
|
||||
// VirtualKey.HANGUL,
|
||||
// VirtualKey.JUNJA,
|
||||
// VirtualKey.FINAL,
|
||||
// VirtualKey.HANJA,
|
||||
// VirtualKey.KANJI,
|
||||
VirtualKey.ESCAPE,
|
||||
// VirtualKey.CONVERT,
|
||||
// VirtualKey.NONCONVERT,
|
||||
// VirtualKey.ACCEPT,
|
||||
// VirtualKey.MODECHANGE,
|
||||
// VirtualKey.SPACE,
|
||||
VirtualKey.PRIOR,
|
||||
VirtualKey.NEXT,
|
||||
VirtualKey.END,
|
||||
VirtualKey.HOME,
|
||||
// VirtualKey.LEFT, // handled by imgui
|
||||
VirtualKey.UP,
|
||||
// VirtualKey.RIGHT, // handled by imgui
|
||||
VirtualKey.DOWN,
|
||||
VirtualKey.SELECT,
|
||||
VirtualKey.PRINT,
|
||||
VirtualKey.EXECUTE,
|
||||
VirtualKey.SNAPSHOT,
|
||||
VirtualKey.INSERT,
|
||||
VirtualKey.DELETE,
|
||||
VirtualKey.HELP,
|
||||
// VirtualKey.KEY_0,
|
||||
// VirtualKey.KEY_1,
|
||||
// VirtualKey.KEY_2,
|
||||
// VirtualKey.KEY_3,
|
||||
// VirtualKey.KEY_4,
|
||||
// VirtualKey.KEY_5,
|
||||
// VirtualKey.KEY_6,
|
||||
// VirtualKey.KEY_7,
|
||||
// VirtualKey.KEY_8,
|
||||
// VirtualKey.KEY_9,
|
||||
// VirtualKey.A,
|
||||
// VirtualKey.B,
|
||||
// VirtualKey.C,
|
||||
// VirtualKey.D,
|
||||
// VirtualKey.E,
|
||||
// VirtualKey.F,
|
||||
// VirtualKey.G,
|
||||
// VirtualKey.H,
|
||||
// VirtualKey.I,
|
||||
// VirtualKey.J,
|
||||
// VirtualKey.K,
|
||||
// VirtualKey.L,
|
||||
// VirtualKey.M,
|
||||
// VirtualKey.N,
|
||||
// VirtualKey.O,
|
||||
// VirtualKey.P,
|
||||
// VirtualKey.Q,
|
||||
// VirtualKey.R,
|
||||
// VirtualKey.S,
|
||||
// VirtualKey.T,
|
||||
// VirtualKey.U,
|
||||
// VirtualKey.V,
|
||||
// VirtualKey.W,
|
||||
// VirtualKey.X,
|
||||
// VirtualKey.Y,
|
||||
// VirtualKey.Z,
|
||||
// VirtualKey.LWIN,
|
||||
// VirtualKey.RWIN,
|
||||
VirtualKey.APPS,
|
||||
VirtualKey.SLEEP,
|
||||
// VirtualKey.NUMPAD0,
|
||||
// VirtualKey.NUMPAD1,
|
||||
// VirtualKey.NUMPAD2,
|
||||
// VirtualKey.NUMPAD3,
|
||||
// VirtualKey.NUMPAD4,
|
||||
// VirtualKey.NUMPAD5,
|
||||
// VirtualKey.NUMPAD6,
|
||||
// VirtualKey.NUMPAD7,
|
||||
// VirtualKey.NUMPAD8,
|
||||
// VirtualKey.NUMPAD9,
|
||||
// VirtualKey.MULTIPLY,
|
||||
// VirtualKey.ADD,
|
||||
// VirtualKey.SEPARATOR,
|
||||
// VirtualKey.SUBTRACT,
|
||||
// VirtualKey.DECIMAL,
|
||||
// VirtualKey.DIVIDE,
|
||||
VirtualKey.F1,
|
||||
VirtualKey.F2,
|
||||
VirtualKey.F3,
|
||||
VirtualKey.F4,
|
||||
VirtualKey.F5,
|
||||
VirtualKey.F6,
|
||||
VirtualKey.F7,
|
||||
VirtualKey.F8,
|
||||
VirtualKey.F9,
|
||||
VirtualKey.F10,
|
||||
VirtualKey.F11,
|
||||
VirtualKey.F12,
|
||||
VirtualKey.F13,
|
||||
VirtualKey.F14,
|
||||
VirtualKey.F15,
|
||||
VirtualKey.F16,
|
||||
VirtualKey.F17,
|
||||
VirtualKey.F18,
|
||||
VirtualKey.F19,
|
||||
VirtualKey.F20,
|
||||
VirtualKey.F21,
|
||||
VirtualKey.F22,
|
||||
VirtualKey.F23,
|
||||
VirtualKey.F24,
|
||||
VirtualKey.NUMLOCK,
|
||||
VirtualKey.SCROLL,
|
||||
// VirtualKey.OEM_FJ_JISHO,
|
||||
// VirtualKey.OEM_NEC_EQUAL,
|
||||
// VirtualKey.OEM_FJ_MASSHOU,
|
||||
// VirtualKey.OEM_FJ_TOUROKU,
|
||||
// VirtualKey.OEM_FJ_LOYA,
|
||||
// VirtualKey.OEM_FJ_ROYA,
|
||||
// VirtualKey.LSHIFT,
|
||||
// VirtualKey.RSHIFT,
|
||||
// VirtualKey.LCONTROL,
|
||||
// VirtualKey.RCONTROL,
|
||||
// VirtualKey.LMENU,
|
||||
// VirtualKey.RMENU,
|
||||
VirtualKey.BROWSER_BACK,
|
||||
VirtualKey.BROWSER_FORWARD,
|
||||
VirtualKey.BROWSER_REFRESH,
|
||||
VirtualKey.BROWSER_STOP,
|
||||
VirtualKey.BROWSER_SEARCH,
|
||||
VirtualKey.BROWSER_FAVORITES,
|
||||
VirtualKey.BROWSER_HOME,
|
||||
VirtualKey.VOLUME_MUTE,
|
||||
VirtualKey.VOLUME_DOWN,
|
||||
VirtualKey.VOLUME_UP,
|
||||
VirtualKey.MEDIA_NEXT_TRACK,
|
||||
VirtualKey.MEDIA_PREV_TRACK,
|
||||
VirtualKey.MEDIA_STOP,
|
||||
VirtualKey.MEDIA_PLAY_PAUSE,
|
||||
VirtualKey.LAUNCH_MAIL,
|
||||
VirtualKey.LAUNCH_MEDIA_SELECT,
|
||||
VirtualKey.LAUNCH_APP1,
|
||||
VirtualKey.LAUNCH_APP2,
|
||||
// VirtualKey.OEM_1,
|
||||
// VirtualKey.OEM_PLUS,
|
||||
// VirtualKey.OEM_COMMA,
|
||||
// VirtualKey.OEM_MINUS,
|
||||
// VirtualKey.OEM_PERIOD,
|
||||
// VirtualKey.OEM_2,
|
||||
// VirtualKey.OEM_3,
|
||||
// VirtualKey.OEM_4, // [{
|
||||
// VirtualKey.OEM_5, // \"
|
||||
// VirtualKey.OEM_6, // ]}
|
||||
// VirtualKey.OEM_7, // '"
|
||||
// VirtualKey.OEM_8,
|
||||
// VirtualKey.OEM_AX,
|
||||
// VirtualKey.OEM_102,
|
||||
// VirtualKey.ICO_HELP,
|
||||
// VirtualKey.ICO_00,
|
||||
// VirtualKey.PROCESSKEY,
|
||||
// VirtualKey.ICO_CLEAR,
|
||||
// VirtualKey.PACKET,
|
||||
// VirtualKey.OEM_RESET,
|
||||
// VirtualKey.OEM_JUMP,
|
||||
// VirtualKey.OEM_PA1,
|
||||
// VirtualKey.OEM_PA2,
|
||||
// VirtualKey.OEM_PA3,
|
||||
// VirtualKey.OEM_WSCTRL,
|
||||
// VirtualKey.OEM_CUSEL,
|
||||
// VirtualKey.OEM_ATTN,
|
||||
// VirtualKey.OEM_FINISH,
|
||||
// VirtualKey.OEM_COPY,
|
||||
// VirtualKey.OEM_AUTO,
|
||||
// VirtualKey.OEM_ENLW,
|
||||
// VirtualKey.OEM_BACKTAB,
|
||||
// VirtualKey.ATTN,
|
||||
// VirtualKey.CRSEL,
|
||||
// VirtualKey.EXSEL,
|
||||
// VirtualKey.EREOF,
|
||||
// VirtualKey.PLAY,
|
||||
// VirtualKey.ZOOM,
|
||||
// VirtualKey.NONAME,
|
||||
// VirtualKey.PA1,
|
||||
// VirtualKey.OEM_CLEAR,
|
||||
};
|
||||
|
||||
internal KeybindManager(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Plugin.GameInteropProvider.InitializeFromAttributes(this);
|
||||
|
||||
// Handle keybinds from the game on every tick.
|
||||
Plugin.Framework.Update += HandleKeybinds;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Plugin.Framework.Update -= HandleKeybinds;
|
||||
}
|
||||
|
||||
private void UpdateKeybinds()
|
||||
{
|
||||
foreach (var name in KeybindsToIntercept.Keys)
|
||||
{
|
||||
Keybinds[name] = GetKeybind(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static ModifierFlag GetModifiers(KeyboardSource source)
|
||||
{
|
||||
var modifierState = ModifierFlag.None;
|
||||
if (source == KeyboardSource.Game)
|
||||
{
|
||||
if (Plugin.KeyState[VirtualKey.MENU])
|
||||
modifierState |= ModifierFlag.Alt;
|
||||
if (Plugin.KeyState[VirtualKey.CONTROL])
|
||||
modifierState |= ModifierFlag.Ctrl;
|
||||
if (Plugin.KeyState[VirtualKey.SHIFT])
|
||||
modifierState |= ModifierFlag.Shift;
|
||||
return modifierState;
|
||||
}
|
||||
|
||||
if (ImGui.GetIO().KeyAlt)
|
||||
modifierState |= ModifierFlag.Alt;
|
||||
if (ImGui.GetIO().KeyCtrl)
|
||||
modifierState |= ModifierFlag.Ctrl;
|
||||
if (ImGui.GetIO().KeyShift)
|
||||
modifierState |= ModifierFlag.Shift;
|
||||
|
||||
return modifierState;
|
||||
}
|
||||
|
||||
private static bool KeyPressed(KeyboardSource source, VirtualKey key)
|
||||
{
|
||||
if (key == VirtualKey.NO_KEY)
|
||||
return false;
|
||||
if (source == KeyboardSource.Game)
|
||||
return Plugin.KeyState[key];
|
||||
|
||||
return key.TryToImGui(out var imguiKey) && ImGui.IsKeyPressed(imguiKey);
|
||||
}
|
||||
|
||||
private static bool ComboPressed(KeyboardSource source, VirtualKey key, ModifierFlag modifier, ModifierFlag? modifierState = null, bool modifiersOnly = false)
|
||||
{
|
||||
// When we're in an input, we don't want to process any keybinds that
|
||||
// don't have a modifier (or only use shift) and are not explicitly
|
||||
// whitelisted.
|
||||
if (modifiersOnly && !ModifierlessChatKeys.Contains(key) && modifier is ModifierFlag.None or ModifierFlag.Shift)
|
||||
return false;
|
||||
|
||||
modifierState ??= GetModifiers(source);
|
||||
var modifierPressed = Plugin.Config.KeybindMode switch
|
||||
{
|
||||
KeybindMode.Strict => modifier == modifierState.Value,
|
||||
KeybindMode.Flexible => modifierState.Value.HasFlag(modifier),
|
||||
_ => false
|
||||
};
|
||||
|
||||
return KeyPressed(source, key) && modifierPressed;
|
||||
}
|
||||
|
||||
private static bool ConfigKeybindPressed(KeyboardSource source, ConfigKeyBind? bind, ModifierFlag? modifierState = null, bool modifiersOnly = false)
|
||||
{
|
||||
return bind != null && ComboPressed(source, bind.Key, bind.Modifier, modifierState: modifierState, modifiersOnly: modifiersOnly);
|
||||
}
|
||||
|
||||
private void HandleKeybinds(IFramework _ ) => HandleKeybinds(KeyboardSource.Game);
|
||||
|
||||
internal void HandleKeybinds(KeyboardSource source, bool ignoreChatOpen = false, bool modifiersOnly = false)
|
||||
{
|
||||
// Refresh current keybinds every 5s
|
||||
if (LastRefresh + 5 * 1000 < Environment.TickCount64)
|
||||
{
|
||||
UpdateKeybinds();
|
||||
DirectChat = Plugin.GameConfig.TryGet(UiControlOption.DirectChat, out bool option) && option;
|
||||
LastRefresh = Environment.TickCount64;
|
||||
}
|
||||
|
||||
// Vanilla text input has focus
|
||||
if (RaptureAtkModule.Instance()->AtkModule.IsTextInputActive())
|
||||
return;
|
||||
|
||||
var modifierState = GetModifiers(source);
|
||||
|
||||
// Test for custom keybinds for changing chat tabs before checking
|
||||
// vanilla keybinds.
|
||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabForward))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabForward!.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(1);
|
||||
return;
|
||||
}
|
||||
if (ConfigKeybindPressed(source, Plugin.Config.ChatTabBackward))
|
||||
{
|
||||
Plugin.KeyState[Plugin.Config.ChatTabBackward!.Key] = false;
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(-1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process the active combo with the most modifiers.
|
||||
var currentBest = (VirtualKey.NO_KEY, "", 0);
|
||||
foreach (var (toIntercept, keybind) in Keybinds)
|
||||
{
|
||||
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND" && (ignoreChatOpen || DirectChat))
|
||||
continue;
|
||||
|
||||
void Intercept(VirtualKey vk, ModifierFlag modifier)
|
||||
{
|
||||
if (!ComboPressed(source, vk, modifier, modifierState: modifierState, modifiersOnly: modifiersOnly))
|
||||
return;
|
||||
|
||||
var bits = BitOperations.PopCount((uint) modifier);
|
||||
if (bits < currentBest.Item3)
|
||||
return;
|
||||
|
||||
currentBest = (vk, toIntercept, bits);
|
||||
}
|
||||
|
||||
Intercept(keybind.Key1, keybind.Modifier1);
|
||||
Intercept(keybind.Key2, keybind.Modifier2);
|
||||
}
|
||||
|
||||
if (currentBest.Item1 == VirtualKey.NO_KEY)
|
||||
return;
|
||||
|
||||
Plugin.KeyState[currentBest.Item1] = false;
|
||||
if (!KeybindsToIntercept.TryGetValue(currentBest.Item2, out var info))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
TellReason? reason = info.Channel == InputChannel.Tell ? TellReason.Reply : null;
|
||||
Plugin.ChatLogWindow.Activated(new ChatActivatedArgs(info) { TellReason = reason, });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Log.Error(ex, "Error in chat Activated event");
|
||||
}
|
||||
}
|
||||
|
||||
private Keybind GetKeybind(string id)
|
||||
{
|
||||
var outData = stackalloc byte[11];
|
||||
var idString = Utf8String.FromString(id);
|
||||
GetKeybindNative(UIInputData.Instance(), idString, (nint) outData);
|
||||
idString->Dtor(true);
|
||||
|
||||
var key1 = RemapInvalidVirtualKey((VirtualKey) outData[0]);
|
||||
var key2 = RemapInvalidVirtualKey((VirtualKey) outData[2]);
|
||||
return new Keybind
|
||||
{
|
||||
Key1 = key1,
|
||||
Modifier1 = (ModifierFlag) outData[1],
|
||||
Key2 = key2,
|
||||
Modifier2 = (ModifierFlag) outData[3],
|
||||
};
|
||||
}
|
||||
|
||||
private static VirtualKey RemapInvalidVirtualKey(VirtualKey key)
|
||||
{
|
||||
return key switch
|
||||
{
|
||||
VirtualKey.F23 => VirtualKey.OEM_2, // /?
|
||||
(VirtualKey) 140 => VirtualKey.OEM_7, // '"
|
||||
_ => key
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,12 @@ using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Addon.Lifecycle;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface;
|
||||
@@ -121,7 +121,6 @@ public sealed class ChatLogWindow : Window
|
||||
TextCommandSheet = Plugin.DataManager.GetExcelSheet<TextCommand>()!;
|
||||
FontIcon = Plugin.TextureProvider.CreateFromTexFile(Plugin.DataManager.GetFile<TexFile>("common/font/fonticon_ps5.tex")!);
|
||||
|
||||
Plugin.Functions.Chat.Activated += Activated;
|
||||
Plugin.ClientState.Login += Login;
|
||||
Plugin.ClientState.Logout += Logout;
|
||||
|
||||
@@ -133,7 +132,6 @@ public sealed class ChatLogWindow : Window
|
||||
Plugin.AddonLifecycle.UnregisterListener(AddonEvent.PostRequestedUpdate, "ItemDetail", PayloadHandler.MoveTooltip);
|
||||
Plugin.ClientState.Logout -= Logout;
|
||||
Plugin.ClientState.Login -= Login;
|
||||
Plugin.Functions.Chat.Activated -= Activated;
|
||||
FontIcon?.Dispose();
|
||||
Plugin.Commands.Register("/chat2").Execute -= ToggleChat;
|
||||
Plugin.Commands.Register("/clearlog2").Execute -= ClearLog;
|
||||
@@ -149,7 +147,7 @@ public sealed class ChatLogWindow : Window
|
||||
Plugin.MessageManager.FilterAllTabsAsync();
|
||||
}
|
||||
|
||||
private void Activated(ChatActivatedArgs args)
|
||||
internal void Activated(ChatActivatedArgs args)
|
||||
{
|
||||
Activate = true;
|
||||
PlayedClosingSound = false;
|
||||
@@ -337,83 +335,6 @@ public sealed class ChatLogWindow : Window
|
||||
return height;
|
||||
}
|
||||
|
||||
private void HandleKeybinds(bool modifiersOnly = false)
|
||||
{
|
||||
var modifierState = (ModifierFlag) 0;
|
||||
if (ImGui.GetIO().KeyAlt)
|
||||
modifierState |= ModifierFlag.Alt;
|
||||
|
||||
if (ImGui.GetIO().KeyCtrl)
|
||||
modifierState |= ModifierFlag.Ctrl;
|
||||
|
||||
if (ImGui.GetIO().KeyShift)
|
||||
modifierState |= ModifierFlag.Shift;
|
||||
|
||||
bool KeyPressed(VirtualKey vk, ModifierFlag modifier)
|
||||
{
|
||||
if (!vk.TryToImGui(out var key))
|
||||
return false;
|
||||
|
||||
var modifierPressed = Plugin.Config.KeybindMode switch
|
||||
{
|
||||
KeybindMode.Strict => modifier == modifierState,
|
||||
KeybindMode.Flexible => modifierState.HasFlag(modifier),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
return ImGui.IsKeyPressed(key) && modifierPressed && (modifier != 0 || !modifiersOnly);
|
||||
}
|
||||
|
||||
// Test for custom keybinds for changing chat tabs before checking
|
||||
// vanilla keybinds.
|
||||
if (Plugin.Config.ChatTabBackward != null && KeyPressed(Plugin.Config.ChatTabBackward.Key, Plugin.Config.ChatTabBackward.Modifier))
|
||||
{
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(-1);
|
||||
return;
|
||||
}
|
||||
if (Plugin.Config.ChatTabForward != null && KeyPressed(Plugin.Config.ChatTabForward.Key, Plugin.Config.ChatTabForward.Modifier))
|
||||
{
|
||||
Plugin.ChatLogWindow.ChangeTabDelta(1);
|
||||
return;
|
||||
}
|
||||
|
||||
var turnedOff = new Dictionary<VirtualKey, (uint, string)>();
|
||||
foreach (var (toIntercept, keybind) in Plugin.Functions.Chat.Keybinds)
|
||||
{
|
||||
if (toIntercept is "CMD_CHAT" or "CMD_COMMAND")
|
||||
continue;
|
||||
|
||||
void Intercept(VirtualKey vk, ModifierFlag modifier)
|
||||
{
|
||||
if (!KeyPressed(vk, modifier))
|
||||
return;
|
||||
|
||||
var bits = BitOperations.PopCount((uint) modifier);
|
||||
if (!turnedOff.TryGetValue(vk, out var previousBits) || previousBits.Item1 < bits)
|
||||
turnedOff[vk] = ((uint) bits, toIntercept);
|
||||
}
|
||||
|
||||
Intercept(keybind.Key1, keybind.Modifier1);
|
||||
Intercept(keybind.Key2, keybind.Modifier2);
|
||||
}
|
||||
|
||||
foreach (var (_, (_, keybind)) in turnedOff)
|
||||
{
|
||||
if (!GameFunctions.Chat.KeybindsToIntercept.TryGetValue(keybind, out var info))
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
TellReason? reason = info.Channel == InputChannel.Tell ? TellReason.Reply : null;
|
||||
Activated(new ChatActivatedArgs(info) { TellReason = reason, });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Plugin.Log.Error(ex, "Error in chat Activated event");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void ChangeTab(int index) => WantedTab = index;
|
||||
|
||||
internal void ChangeTabDelta(int offset)
|
||||
@@ -801,9 +722,10 @@ public sealed class ChatLogWindow : Window
|
||||
}
|
||||
}
|
||||
|
||||
// Process keybinds that have modifiers while the chat is focused.
|
||||
if (ImGui.IsItemActive())
|
||||
{
|
||||
HandleKeybinds(true);
|
||||
Plugin.Functions.KeybindManager.HandleKeybinds(KeyboardSource.ImGui, true, true);
|
||||
LastActivityTime = FrameTime;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user