Files
HellionChat/HellionChat/Ipc/TypingIpc.cs
T
JonKazama-Hellion 5b738e6885 chore: comments say what the code does, not which task produced it
A comment that reads "MUST stay in lockstep with TryGetActiveCrossfade (K8)"
helps nobody outside the plan that used to have a K8 in it, and the plans are
not in this repo. Same for "Spec FR-4", "plan §B.2", "Sub-Task 4.4" and the
F/R/M/A/S round codes scattered through the style engine and the self-tests.

Personal names go too. "tester feedback from Jin (v1.4.7)" and "Flo decision
2026-06-15" carry the reason fine without naming anyone -- the version and the
reason are the parts a reader can act on, and a public repo should not need a
cast list to be read.

The rule applied throughout: keep the why, drop the reference. Version numbers
stay, since those resolve through the changelog. 77 files.

ChunkUtil also carried 281 lines of commented-out code -- an older ToChunks
variant and two helpers with no callers, inherited and never removed. Deleted;
git remembers them.
2026-08-19 21:50:31 +02:00

115 lines
4.0 KiB
C#

using Dalamud.Plugin.Ipc;
using HellionChat.Code;
using Microsoft.Extensions.Logging;
namespace HellionChat.Ipc;
using ChatInputState = (
bool InputVisible,
bool InputFocused,
bool HasText,
bool IsTyping,
int TextLength,
ChatType ChannelType
);
internal sealed class TypingIpc : IDisposable
{
private Plugin Plugin { get; }
private ICallGateProvider<ChatInputState> StateQueryGate { get; }
private ICallGateProvider<ChatInputState, object?> StateChangedGate { get; }
// v1.4.9: ChatTwo IPC compatibility mirror. Some third-party plugins
// have a no-fork policy and subscribe only to ChatTwo.*-prefixed IPC
// gates. HellionChat replaces ChatTwo (conflict detection prevents
// parallel loading), so mirroring the ChatTwo provider slots lets those
// plugins keep working without code changes on their side. The tuple
// shape is textually identical to ChatTwo's IPC surface (same member
// order, same underlying types — ChatType is `ushort` in both repos)
// so Dalamud's IPC marshalling matches across plugin boundaries.
private ICallGateProvider<ChatInputState> ChatTwoStateQueryGate { get; }
private ICallGateProvider<ChatInputState, object?> ChatTwoStateChangedGate { get; }
private ChatInputState LastState;
private bool HasState;
private readonly Ui.Components.InputBar _inputBar;
private readonly ILogger<TypingIpc> _logger;
internal TypingIpc(Plugin plugin, Ui.Components.InputBar inputBar, ILogger<TypingIpc> logger)
{
Plugin = plugin;
_inputBar = inputBar;
_logger = logger;
StateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
"HellionChat.GetChatInputState"
);
StateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
"HellionChat.ChatInputStateChanged"
);
// v1.4.9: ChatTwo-prefixed compatibility slots (see class-level comment).
ChatTwoStateQueryGate = Plugin.Interface.GetIpcProvider<ChatInputState>(
"ChatTwo.GetChatInputState"
);
ChatTwoStateChangedGate = Plugin.Interface.GetIpcProvider<ChatInputState, object?>(
"ChatTwo.ChatInputStateChanged"
);
StateQueryGate.RegisterFunc(GetState);
ChatTwoStateQueryGate.RegisterFunc(GetState);
}
private ChatInputState BuildState()
{
var usedChannel = Plugin.CurrentTab.CurrentChannel;
var inputChannel = usedChannel.UseTempChannel
? usedChannel.TempChannel
: usedChannel.Channel;
var channelType = inputChannel.ToChatType();
// MainWindow is Phase-1-resolved and never reassigned;
// the `?.` is defense-in-depth for pre-Phase-1 IPC-pulls.
var mainWindowOpen = Plugin.MainWindow?.IsOpen ?? false;
// Stale-state guard: InputBar's focus and pending-buffer fields are
// only written by DrawInputField. Closing MainWindow freezes them, so
// gate all four state fields on mainWindowOpen.
var inputFocused = mainWindowOpen && _inputBar.IsFocused;
var hasText = mainWindowOpen && _inputBar.PendingLength > 0;
var textLength = mainWindowOpen ? _inputBar.PendingLength : 0;
return (
InputVisible: mainWindowOpen,
InputFocused: inputFocused,
HasText: hasText,
IsTyping: hasText,
TextLength: textLength,
ChannelType: channelType
);
}
internal ChatInputState GetState() => BuildState();
internal void Update()
{
var state = BuildState();
if (HasState && state.Equals(LastState))
return;
HasState = true;
LastState = state;
StateChangedGate.SendMessage(state);
// v1.4.9: mirror on ChatTwo-prefixed slot for no-fork-policy plugins.
ChatTwoStateChangedGate.SendMessage(state);
}
public void Dispose()
{
StateQueryGate.UnregisterFunc();
ChatTwoStateQueryGate.UnregisterFunc();
}
}