Skip to content

Commit fd09f36

Browse files
Merge pull request #188 from logicallysynced/chromatics-4.x
v4.1.26: PlayStation + LIFX (Beta) device libraries + cross-language status checks + effect-layer master toggle + smoother Hue + cumulative update changelog + harness raid/weather presets
2 parents f968f2a + 80387b1 commit fd09f36

66 files changed

Lines changed: 4905 additions & 125 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
All notable changes to Chromatics are documented here.
44

5+
## 4.1.26
6+
7+
- **New:** PlayStation DS4 and DS5 controller lighting support (Beta). The DualShock 4 lightbar, the DualSense lightbar and the five player-indicator LEDs can all be mapped from the Mappings tab. Both USB and Bluetooth controllers are supported, and your controller's input still works normally in games while Chromatics drives the lights.
8+
Tip: if your controller is connected but Chromatics says it can't open it, another tool (like Steam Input, DS4Windows, or reWASD) is probably holding it in exclusive mode. Close it or disable its exclusive mode before launching Chromatics.
9+
- **New:** LIFX light support (Beta). Discovers LIFX bulbs, strips and tiles on your local network (uses Local LAN protocol — no LIFX cloud account required). When you enable LIFX in Settings, Chromatics shows a picker so you can choose which devices it should control. Multi-zone strips (Z, Beam, String, Neon) light up per-zone, and matrix devices (Tile, Candle Color) are mapped as a grid. When Chromatics releases control (you disable LIFX in Settings, or close the app), each device is restored to the colour and on/off state it was in before Chromatics took over.
10+
- The Effect Layer enable checkbox on the Mappings tab is now a per-device master toggle for everything on the Effects tab. Turning it off for a device silences raid effects, duty-finder bell, damage flash, cutscene animation, vegas mode, the startup and title-screen animations, and any reactive-weather animation overlays just for that device. Base layer painting (static, job-class colours, weather colour, screen capture) and dynamic layers (HP, target, key bindings) keep running normally.
11+
- Smoother Hue colour transitions. Fast-changing effects (Vegas mode, cutscenes, certain weather animations) used to look strobe-like on Hue bulbs that lack the Hue Play's hardware fade - Chromatics now asks the bridge to interpolate between colour frames so every supported bulb gets smoother motion on effects.
12+
- Updated underlying dependencies for stability and bug fixes.
13+
14+
## 4.0.157
15+
16+
- The first-run welcome screen now lets you pick your Chromatics language right away — and the screen re-translates itself instantly so you can see the change before continuing.
17+
- Tank stance highlighting (Iron Will, Defiance, Royal Guard, Grit) and Summon Seraph now work correctly when the FFXIV client is set to Japanese, German or French.
18+
- Updated the FFXIV memory reader (Sharlayan) with several stability and accuracy fixes — most notably, status effect slots no longer occasionally show the wrong status, and stack counts only display for statuses that actually stack.
19+
520
## 4.0.156
621

722
- Fixed an issue where Chromatics could close itself with a crash dialog if a connected device (Hue, OpenRGB, etc.) had a brief network hiccup. These transient errors are now ignored.

Chromatics.DecoratorHarnessUI/MainViewModel.cs

Lines changed: 330 additions & 2 deletions
Large diffs are not rendered by default.

Chromatics.Tests/Chromatics.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
</ItemGroup>
2727

2828
<ItemGroup>
29-
<PackageReference Include="Sharlayan" Version="9.0.24-prerelease.35" />
29+
<PackageReference Include="Sharlayan" Version="9.0.31-prerelease.41" />
3030
</ItemGroup>
3131

3232
</Project>

Chromatics/App.axaml.cs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,16 +80,28 @@ public override void OnFrameworkInitializationCompleted()
8080
var wizard = new FirstRunDialog();
8181
wizard.Closed += (_, _) =>
8282
{
83-
try
83+
// Construct MainWindow on the next dispatcher tick
84+
// rather than synchronously inside Closed. Avalonia's
85+
// avares:// resource pipeline can race against the
86+
// wizard teardown — if MainWindow.axaml is parsed
87+
// mid-shutdown the icon resource sometimes resolves
88+
// empty, leaving a blank taskbar entry. Posting at
89+
// Background priority lets the wizard finish its
90+
// close cycle and lets the resource manager settle
91+
// before the new window registers its icon.
92+
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
8493
{
85-
var mainWindow = new MainWindow();
86-
desktop.MainWindow = mainWindow;
87-
mainWindow.Show();
88-
}
89-
catch (Exception ex)
90-
{
91-
CrashHandler.HandleCrash(ex);
92-
}
94+
try
95+
{
96+
var mainWindow = new MainWindow();
97+
desktop.MainWindow = mainWindow;
98+
mainWindow.Show();
99+
}
100+
catch (Exception ex)
101+
{
102+
CrashHandler.HandleCrash(ex);
103+
}
104+
}, Avalonia.Threading.DispatcherPriority.Background);
93105
};
94106
wizard.Show();
95107
}

Chromatics/Chromatics.csproj

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<OutputType>WinExe</OutputType>
55
<TargetFramework>net10.0-windows7.0</TargetFramework>
66
<StartupObject>Chromatics.Program</StartupObject>
7-
<Version>4.0.156.0</Version>
7+
<Version>4.1.26.0</Version>
88
<Authors>Danielle Thompson</Authors>
99
<ApplicationManifest>app.manifest</ApplicationManifest>
1010
<Copyright>logicallysynced 2026</Copyright>
@@ -54,10 +54,11 @@
5454
<PackageReference Include="Avalonia.Fonts.Inter" Version="12.0.2" />
5555
<PackageReference Include="Avalonia.Controls.DataGrid" Version="12.0.0" />
5656
<PackageReference Include="Avalonia.Controls.ColorPicker" Version="12.0.2" />
57+
<PackageReference Include="Markdown.Avalonia.Tight" Version="12.0.0-a3" />
5758
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
5859
<PackageReference Include="Velopack" Version="0.0.1298" />
59-
<PackageReference Include="Sentry" Version="6.4.1" />
60-
<PackageReference Include="Sentry.Profiling" Version="6.4.1" />
60+
<PackageReference Include="Sentry" Version="6.5.0" />
61+
<PackageReference Include="Sentry.Profiling" Version="6.5.0" />
6162
</ItemGroup>
6263

6364
<ItemGroup>
@@ -68,7 +69,7 @@
6869
<PackageReference Include="System.Drawing.Common" Version="10.0.7" />
6970
<PackageReference Include="NAudio" Version="2.3.0" />
7071
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
71-
<PackageReference Include="NLog" Version="6.1.2" />
72+
<PackageReference Include="NLog" Version="6.1.3" />
7273
<PackageReference Include="RGB.NET.Core" Version="3.2.0" />
7374
<PackageReference Include="RGB.NET.Devices.Asus" Version="3.2.0" />
7475
<PackageReference Include="RGB.NET.Devices.CoolerMaster" Version="3.2.0" />
@@ -83,7 +84,7 @@
8384
<PackageReference Include="RGB.NET.HID" Version="3.2.0" />
8485
<PackageReference Include="RGB.NET.Layout" Version="3.2.0" />
8586
<PackageReference Include="RGB.NET.Presets" Version="3.2.0" />
86-
<PackageReference Include="Sharlayan" Version="9.0.24-prerelease.35" />
87+
<PackageReference Include="Sharlayan" Version="9.0.31-prerelease.41" />
8788
</ItemGroup>
8889

8990
<ItemGroup>

Chromatics/Core/GameController.cs

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,27 @@ public static void BuildTitleScreenAnimation()
9090

9191
foreach (var device in devices)
9292
{
93+
// Per-device EffectLayer toggle from the Mappings tab silences
94+
// the title-screen animation on devices the user has unticked.
95+
// The earlier title-detection bug (chat-log race) is gone, so
96+
// it's safe to re-apply this filter at the initial build —
97+
// the title animation actually runs long enough now for the
98+
// filter to matter.
99+
var deviceGuid = RGBController.GetDeviceGuid(device);
100+
if (deviceGuid != Guid.Empty
101+
&& !MappingLayers.IsDeviceEffectsEnabled(deviceGuid))
102+
continue;
103+
93104
var ledgroup = new ListLedGroup(surface, device);
94105

95-
var starfield = new StarfieldDecorator(ledgroup, (ledgroup.Count() / 4), 10, 500, highlightColors, surface, false, baseColor);
106+
// numberOfLeds = ceil(count/4) with a floor of 1 — makes the
107+
// animation actually pick a "star" on 1–3 LED devices (Hue,
108+
// DualShock 4 lightbar, single-LED accessories) where the
109+
// old `count/4` rounded down to 0 and no LED ever entered
110+
// the fade cycle. With 1 star on a 1-LED device, that LED
111+
// becomes the star and cycles through highlight colours.
112+
int starCount = Math.Max(1, ledgroup.Count() / 4);
113+
var starfield = new StarfieldDecorator(ledgroup, starCount, 10, 500, highlightColors, surface, false, baseColor);
96114
ledgroup.ZIndex = 1000;
97115

98116
foreach (var led in device)
@@ -103,10 +121,47 @@ public static void BuildTitleScreenAnimation()
103121
ledgroup.Brush = new SolidColorBrush(baseColor);
104122
ledgroup.AddDecorator(starfield);
105123

106-
RGBController.RegisterTaggedEffect("title", ledgroup);
124+
RGBController.RegisterTaggedEffect("title", deviceGuid, ledgroup);
107125
}
108126
}
109127

128+
// Per-device builder used by RGBController.SyncTaggedEffectsForDevice
129+
// when the user re-enables effects on a specific device while the
130+
// title-screen animation is currently running. Mirrors the foreach
131+
// body of BuildTitleScreenAnimation but for one device only.
132+
internal static void BuildTitleEffectForDeviceInternal(RGB.NET.Core.IRGBDevice device, Guid deviceGuid)
133+
{
134+
if (!RGBController.GetEffectsSettings().effect_titlescreen) return;
135+
136+
var surface = RGBController.GetLiveSurfaces();
137+
if (surface == null || device == null) return;
138+
139+
var palette = RGBController.GetActivePalette();
140+
var baseColor = ColorHelper.ColorToRGBColor(palette.MenuBase.Color);
141+
var highlightColors = new Color[] {
142+
ColorHelper.ColorToRGBColor(palette.MenuHighlight1.Color),
143+
ColorHelper.ColorToRGBColor(palette.MenuHighlight2.Color),
144+
ColorHelper.ColorToRGBColor(palette.MenuHighlight3.Color)
145+
};
146+
147+
var ledgroup = new ListLedGroup(surface, device);
148+
149+
// See BuildTitleScreenAnimation for the floor-of-1 rationale.
150+
int starCount = Math.Max(1, ledgroup.Count() / 4);
151+
var starfield = new StarfieldDecorator(ledgroup, starCount, 10, 500, highlightColors, surface, false, baseColor);
152+
ledgroup.ZIndex = 1000;
153+
154+
foreach (var led in device)
155+
{
156+
ledgroup.AddLed(led);
157+
}
158+
159+
ledgroup.Brush = new SolidColorBrush(baseColor);
160+
ledgroup.AddDecorator(starfield);
161+
162+
RGBController.RegisterTaggedEffect("title", deviceGuid, ledgroup);
163+
}
164+
110165
public static void Setup()
111166
{
112167
if (gameSetup) return;
@@ -490,11 +545,20 @@ private static void GameProcessLayers()
490545
if (handler.Reader != null && handler.Reader.CanGetActors() && handler.Reader.CanGetChatLog())
491546
{
492547
var getCurrentPlayer = handler.Reader.GetCurrentPlayer();
493-
var chatLogCount = handler.Reader.GetChatLog().ChatLogItems.Count;
548+
var isLoggedIn = handler.Reader.GetGameState().IsLoggedIn;
494549

495550
var runningEffects = RGBController.GetRunningEffects();
496551

497-
if (getCurrentPlayer.Entity == null && chatLogCount <= 0 && !handler.Reader.GetGameState().IsLoggedIn)
552+
// Title-screen detection: player entity not loaded AND
553+
// not logged in. We deliberately do NOT also require
554+
// chatLogCount == 0 — Sharlayan's chat reader picks up
555+
// system messages ("Welcome to FFXIV", etc.) on the
556+
// title screen, which would flip the count to 1 within
557+
// a frame of the title animation building and
558+
// incorrectly re-classify the user as in-game. Entity
559+
// + login state alone are unambiguous for title vs
560+
// in-game.
561+
if (getCurrentPlayer.Entity == null && !isLoggedIn)
498562
{
499563
//Game is still on Main Menu or Character Screen
500564
if (!_onTitle || wasPreviewed)
@@ -620,6 +684,29 @@ private static void GameProcessLayers()
620684
break;
621685

622686
case LayerType.EffectLayer:
687+
// requestUpdate-driven detach for effect-class
688+
// ledgroups. Mirrors the equivalent block on
689+
// BaseLayer / DynamicLayer above. Without this,
690+
// toggling the per-device EffectLayer enable
691+
// checkbox left effect groups (DutyFinderBell,
692+
// DamageFlash, Cutscene, Vegas, …) sitting on the
693+
// surface with their last brush state — visible
694+
// as a "frozen effect" obscuring the underlying
695+
// base / dynamic painting until something else
696+
// detached them.
697+
if (layer.requestUpdate)
698+
{
699+
var effectLiveGroups = RGBController.GetLiveLayerGroups();
700+
if (effectLiveGroups.TryGetValue(layer.layerID, out var prevEffectGroups))
701+
{
702+
foreach (var g in prevEffectGroups)
703+
{
704+
g?.RemoveAllDecorators();
705+
g?.Detach();
706+
}
707+
effectLiveGroups.Remove(layer.layerID);
708+
}
709+
}
623710
var effectProcessors = EffectLayerProcessorFactory.GetProcessors();
624711
foreach (var effectProcessor in effectProcessors)
625712
{

0 commit comments

Comments
 (0)