feat(ui): add per-tab notification sound for inactive tabs

This commit is contained in:
2026-05-21 10:39:09 +02:00
parent 246f0e2511
commit 36ea8ddcfc
4 changed files with 68 additions and 0 deletions
+6
View File
@@ -446,6 +446,10 @@ public class Tab
public bool AllSenderMessages;
public TellTarget TellTarget = TellTarget.Empty();
// UI-3: per-tab notification sound for messages arriving in an inactive tab.
public bool EnableNotificationSound;
public uint NotificationSoundId = 1;
[NonSerialized]
public uint Unread;
@@ -564,6 +568,8 @@ public class Tab
IsPinned = IsPinned,
AllSenderMessages = AllSenderMessages,
TellTarget = TellTarget.Clone(),
EnableNotificationSound = EnableNotificationSound,
NotificationSoundId = NotificationSoundId,
IsGreeted = IsGreeted,
};
}
+27
View File
@@ -7,7 +7,9 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Hooking;
using Dalamud.Interface.ImGuiNotification;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using HellionChat._Helpers;
using HellionChat.Code;
using HellionChat.Resources;
using HellionChat.Util;
@@ -330,6 +332,7 @@ internal class MessageManager : IAsyncDisposable
Store.UpsertMessage(message);
var currentMatches = Plugin.CurrentTab.Matches(message);
uint? notificationSound = null;
foreach (var tab in Plugin.Config.Tabs)
{
var unread = !(
@@ -337,7 +340,31 @@ internal class MessageManager : IAsyncDisposable
);
if (tab.Matches(message))
{
tab.AddMessage(message, unread);
// UI-3: per-tab notification sound. Fire once for the first
// inactive tab that wants it — keeps a message matching several
// background tabs from stacking sounds.
// TEST-MIRROR: ../_Helpers/TabSoundDecision.cs
if (notificationSound is null
&& TabSoundDecision.ShouldPlay(
Plugin.CurrentTab == tab,
tab.EnableNotificationSound,
Plugin.Config.PlaySounds))
{
notificationSound = tab.NotificationSoundId;
}
}
}
if (notificationSound is { } soundId)
{
// ProcessMessage runs on the PendingMessageThread worker; the native
// UIGlobals.PlaySoundEffect must be marshalled onto the framework
// thread (reference_dalamud_framework_thread).
Plugin.Framework.RunOnFrameworkThread(
() => { unsafe { UIGlobals.PlaySoundEffect(soundId); } });
}
MessageProcessed?.Invoke(message);
+21
View File
@@ -165,6 +165,27 @@ internal sealed class Tabs : ISettingsTab
}
ImGui.Checkbox(Language.Options_Tabs_ShowTimestamps, ref tab.DisplayTimestamp);
ImGui.Checkbox(
HellionStrings.Tabs_NotificationSound_Enable_Name,
ref tab.EnableNotificationSound
);
ImGuiUtil.HelpMarker(HellionStrings.Tabs_NotificationSound_Description);
if (tab.EnableNotificationSound)
{
using var indent = ImRaii.PushIndent(10.0f);
var soundPreview = $"{HellionStrings.Tabs_NotificationSound_Option} {tab.NotificationSoundId}";
using var combo = ImRaii.Combo($"##notif-sound-{i}", soundPreview);
if (combo.Success)
{
for (uint s = 1; s <= 16; s++)
{
if (ImGui.Selectable(
$"{HellionStrings.Tabs_NotificationSound_Option} {s}",
tab.NotificationSoundId == s))
tab.NotificationSoundId = s;
}
}
}
ImGui.Checkbox(Language.Options_Tabs_PopOut, ref tab.PopOut);
if (tab.PopOut)
{
+14
View File
@@ -0,0 +1,14 @@
namespace HellionChat._Helpers;
// UI-3 pure decision helper: should an incoming message play a per-tab
// notification sound? Kept Dalamud-free so the Build Suite can test the
// "inactive + enabled + global-allowed" rule in isolation.
// TEST-MIRROR: ../../../Hellion Build test/Ui/TabSoundDecisionTests.cs
public static class TabSoundDecision
{
// True only when the message landed in a tab the user is not looking at,
// that tab has its own sound switched on, and the global sound master is
// not muted.
public static bool ShouldPlay(bool isActiveTab, bool tabSoundEnabled, bool globalSoundsEnabled)
=> !isActiveTab && tabSoundEnabled && globalSoundsEnabled;
}