diff --git a/.editorconfig b/.editorconfig
index 5b6acadf..143210d2 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -31,6 +31,10 @@ csharp_new_line_before_finally = true
# The codebase uses block-scoped namespaces; keep new files consistent.
csharp_style_namespace_declarations = block_scoped:suggestion
dotnet_sort_system_directives_first = true
+# Namespace must equal project root + relative folder path (issue #102).
+# Deliberate exceptions disable IDE0130 in-file with a one-line reason.
+dotnet_style_namespace_match_folder = true
+dotnet_diagnostic.IDE0130.severity = error
# ── this. / var ──────────────────────────────────────────────────────
dotnet_style_qualification_for_field = false:suggestion
diff --git a/Directory.Build.props b/Directory.Build.props
index 76e59ecb..8c78a6bb 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -7,6 +7,10 @@
IsExternalInit shim). -->
latest
+
+ true
+
0.7.0
$(VersionPrefix)
diff --git a/docs/README.md b/docs/README.md
index e56b96e8..83e9ac23 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -18,6 +18,7 @@ Documentation specific to the FanaBridge plugin:
| Document | Description |
|----------|-------------|
+| [Architecture](architecture.md) | Layers, namespace rule, test-tree convention, and frozen names |
| [Supported Devices](supported-devices.md) | Wheels and hub + module combos with built-in FanaBridge profiles — tested vs. unverified, and their LED/display capabilities |
| [Device Settings Lifecycle](device-settings-lifecycle.md) | How a device decides what to store, why the LED editor is built up front, and the rules that keep a save from erasing settings |
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 00000000..0a0724ce
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,66 @@
+# Architecture
+
+Rules and frozen names for the FanaBridge solution. Folder inventories and history live elsewhere; this page is the contract.
+
+## Layers
+
+Three source projects, layered by dependency:
+
+| Project | Role |
+|---------|------|
+| **FanaBridge.Core** | SimHub-free device stack: HID transport, Fanatec protocol, profiles, LEDs, display encoding, tuning, diagnostics. References only non-SimHub libraries (HidSharp, Newtonsoft.Json). |
+| **FanaBridge** (plugin) | SimHub / WPF shell: plugin entrypoint, device registry, Control Mapper bridge, settings, UI, display drivers. References Core and Updater. |
+| **FanaBridge.Updater** | Isolated self-updater (release feed, download, file swap). References neither Core nor the plugin — audit boundary for code that rewrites files next to SimHub. |
+
+**Reference rules:** Core and Updater must not reference SimHub assemblies or each other. The plugin may reference both. Packaging uses **ILRepack** to merge Core + Updater into the shipped **`FanaBridge.dll`** (development builds keep separate assemblies for layering).
+
+## Namespace rule
+
+```
+namespace = project namespace root + relative folder path
+```
+
+| Project | Namespace root |
+|---------|----------------|
+| `src/FanaBridge.Core` | `FanaBridge.Core` |
+| `src/FanaBridge` | `FanaBridge` |
+| `src/FanaBridge.Updater` | `FanaBridge.Updater` |
+| `tests/FanaBridge.Tests` | `FanaBridge.Tests` |
+
+Enforced at compile time via **IDE0130** (`dotnet_style_namespace_match_folder = true`, severity error, `EnforceCodeStyleInBuild`). Deliberate exceptions use an in-file `#pragma warning disable IDE0130` with a one-line reason.
+
+**Product exceptions:**
+
+1. **`Log` in namespace `FanaBridge`** (`FanaBridge.Core/Logging/Log.cs`) — unqualified `Log.*` call sites in both Core and plugin resolve only when the type lives on the shared root ancestor.
+2. **`ModuleInitializerAttribute`** (`FanaBridge/Properties/ModuleInitializerAttribute.cs`) — net48 polyfill; must live in `System.Runtime.CompilerServices` by definition.
+
+XAML `x:Class` values follow the same path rule and are checked by a contract test (the C# analyzer does not cover markup).
+
+Domain folders and their matching namespaces are a **navigational taxonomy**, not directional boundaries. Cross-domain references within a project are expected (e.g. Devices ↔ Transport in Core). The only compiler-enforced directional boundaries are the assembly references: Core references nothing internal, Updater references nothing internal, and the plugin references both.
+
+## Test tree
+
+`tests/FanaBridge.Tests` mirrors the product projects:
+
+- `Core/` → `FanaBridge.Tests.Core…`
+- `Plugin/` → `FanaBridge.Tests.Plugin…`
+- `Updater/` → `FanaBridge.Tests.Updater…`
+- `Contracts/` → repo-wide / external-contract tests (XAML layout, SimHub enum snapshot)
+
+Domain-local fakes live next to their domain; multi-domain doubles live under `TestDoubles/`. A third layout exception lives in the test tree: the Control Mapper reflection shim (see `tests/FanaBridge.Tests/README.md` for conventions and details).
+
+## Frozen names
+
+These strings are invisible to the C# namespace analyzer but **breaking to rename** (SimHub persistence, embedded resources, dashboards, or the updater whitelist):
+
+| Name | Why frozen |
+|------|------------|
+| **`FanaBridge.dll`** | Shipped assembly file name; updater package whitelist and install path. |
+| **`FanaBridge.FanatecPlugin`** | Fully-qualified plugin type name persisted by SimHub. |
+| **`Profiles` path segment** (under Core's embedded resources) | The built-in wheel-profile loader matches manifest names on a `.Profiles.` substring and `.json` suffix; keep profile JSON under a `Profiles/` folder. |
+| **`FanatecPlugin.FanaBridgeSettings.json`** | On-disk settings file under SimHub `PluginsData/Common/`. |
+| **`AttachDelegate` / `AddEvent` keys** | Property and event names registered with SimHub (`FanaBridge.*` properties, `DeviceConnected`, `DeviceDisconnected`, `WheelChanged`, …). Dashboards and automations bind to these strings. |
+| **`Fanatec_[_]`** | `DeviceTypeID` format for SimHub device descriptors (and `Fanatec_Module_` parents for hub logos). |
+| **`FS_WHEEL_SWTYPE_`** | Control Mapper variant ids (`FanaBridgeVariantProvider`); persisted in users' Control Mapper settings. |
+
+Not frozen, for the record: SimHub's `ResolveCache.json` stores plugin/registry FQNs, but the cache is hash-invalidated whenever `FanaBridge.dll` changes on disk, so those entries rebuild on every update — cache state, not durable state.
diff --git a/src/FanaBridge.Core/Transport/ConnectionMonitor.cs b/src/FanaBridge.Core/Devices/ConnectionMonitor.cs
similarity index 99%
rename from src/FanaBridge.Core/Transport/ConnectionMonitor.cs
rename to src/FanaBridge.Core/Devices/ConnectionMonitor.cs
index d28843d0..1e03c472 100644
--- a/src/FanaBridge.Core/Transport/ConnectionMonitor.cs
+++ b/src/FanaBridge.Core/Devices/ConnectionMonitor.cs
@@ -1,6 +1,6 @@
using System;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Devices
{
///
/// Encapsulates the Fanatec device connection state machine:
diff --git a/src/FanaBridge.Core/Transport/FanatecWheelbase.cs b/src/FanaBridge.Core/Devices/FanatecWheelbase.cs
similarity index 99%
rename from src/FanaBridge.Core/Transport/FanatecWheelbase.cs
rename to src/FanaBridge.Core/Devices/FanatecWheelbase.cs
index 5c2a4b4e..1b45ab93 100644
--- a/src/FanaBridge.Core/Transport/FanatecWheelbase.cs
+++ b/src/FanaBridge.Core/Devices/FanatecWheelbase.cs
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using FanaBridge.Profiles;
-using FanaBridge.Protocol;
+using FanaBridge.Core.Devices.Identity;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Devices
{
///
/// Represents a connected Fanatec wheelbase — the root of all communication.
diff --git a/src/FanaBridge.Core/Transport/IWheelbaseConnection.cs b/src/FanaBridge.Core/Devices/IWheelbaseConnection.cs
similarity index 96%
rename from src/FanaBridge.Core/Transport/IWheelbaseConnection.cs
rename to src/FanaBridge.Core/Devices/IWheelbaseConnection.cs
index b231c27a..03c5051d 100644
--- a/src/FanaBridge.Core/Transport/IWheelbaseConnection.cs
+++ b/src/FanaBridge.Core/Devices/IWheelbaseConnection.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Devices
{
///
/// Connection-check surface of a , used by
diff --git a/src/FanaBridge.Core/Protocol/FanatecDeviceTables.cs b/src/FanaBridge.Core/Devices/Identity/FanatecDeviceTables.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/FanatecDeviceTables.cs
rename to src/FanaBridge.Core/Devices/Identity/FanatecDeviceTables.cs
index eba62efe..cbd0aa88 100644
--- a/src/FanaBridge.Core/Protocol/FanatecDeviceTables.cs
+++ b/src/FanaBridge.Core/Devices/Identity/FanatecDeviceTables.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Devices.Identity
{
///
/// Reference tables mapping the raw FF 08 system-report bytes to FanaBridge
diff --git a/src/FanaBridge.Core/Protocol/FanatecIdentity.cs b/src/FanaBridge.Core/Devices/Identity/FanatecIdentity.cs
similarity index 98%
rename from src/FanaBridge.Core/Protocol/FanatecIdentity.cs
rename to src/FanaBridge.Core/Devices/Identity/FanatecIdentity.cs
index 5d035e2b..6f0c797b 100644
--- a/src/FanaBridge.Core/Protocol/FanatecIdentity.cs
+++ b/src/FanaBridge.Core/Devices/Identity/FanatecIdentity.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Devices.Identity
{
///
/// Decodes wheelbase + attachment + module identity from the col03 FF 08
diff --git a/src/FanaBridge.Core/Transport/IdentitySettler.cs b/src/FanaBridge.Core/Devices/Identity/IdentitySettler.cs
similarity index 98%
rename from src/FanaBridge.Core/Transport/IdentitySettler.cs
rename to src/FanaBridge.Core/Devices/Identity/IdentitySettler.cs
index 509f29b9..5ee283a4 100644
--- a/src/FanaBridge.Core/Transport/IdentitySettler.cs
+++ b/src/FanaBridge.Core/Devices/Identity/IdentitySettler.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Devices.Identity
{
///
/// Settles raw wheel/hub + module identity readings before they are committed.
diff --git a/src/FanaBridge.Core/Protocol/SrmConverterIdentity.cs b/src/FanaBridge.Core/Devices/Identity/SrmConverterIdentity.cs
similarity index 98%
rename from src/FanaBridge.Core/Protocol/SrmConverterIdentity.cs
rename to src/FanaBridge.Core/Devices/Identity/SrmConverterIdentity.cs
index 5faacd6f..c0103a82 100644
--- a/src/FanaBridge.Core/Protocol/SrmConverterIdentity.cs
+++ b/src/FanaBridge.Core/Devices/Identity/SrmConverterIdentity.cs
@@ -1,7 +1,7 @@
using System;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Devices.Identity
{
///
/// SRM Conversion Kit identity, recovered from the kit's private DE FA AD → 0xDD
diff --git a/src/FanaBridge.Core/Protocol/SystemReportReader.cs b/src/FanaBridge.Core/Devices/Identity/SystemReportReader.cs
similarity index 98%
rename from src/FanaBridge.Core/Protocol/SystemReportReader.cs
rename to src/FanaBridge.Core/Devices/Identity/SystemReportReader.cs
index fe52d08f..cb230916 100644
--- a/src/FanaBridge.Core/Protocol/SystemReportReader.cs
+++ b/src/FanaBridge.Core/Devices/Identity/SystemReportReader.cs
@@ -1,7 +1,7 @@
using System;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Devices.Identity
{
///
/// The col03 FF 08 system-report codec — the wire-level side of identity.
diff --git a/src/FanaBridge.Core/Protocol/WheelEngage.cs b/src/FanaBridge.Core/Devices/Identity/WheelEngage.cs
similarity index 97%
rename from src/FanaBridge.Core/Protocol/WheelEngage.cs
rename to src/FanaBridge.Core/Devices/Identity/WheelEngage.cs
index 6c4486a6..59788f49 100644
--- a/src/FanaBridge.Core/Protocol/WheelEngage.cs
+++ b/src/FanaBridge.Core/Devices/Identity/WheelEngage.cs
@@ -1,6 +1,6 @@
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Devices.Identity
{
///
/// Replicates the kernel filter's engage sequence (FWFUProtocolUsbBulkOrInterruptTransfer)
diff --git a/src/FanaBridge.Core/Profiles/ColorFormat.cs b/src/FanaBridge.Core/Devices/Profiles/ColorFormat.cs
similarity index 92%
rename from src/FanaBridge.Core/Profiles/ColorFormat.cs
rename to src/FanaBridge.Core/Devices/Profiles/ColorFormat.cs
index c36d4d89..86e5aa51 100644
--- a/src/FanaBridge.Core/Profiles/ColorFormat.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/ColorFormat.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Pixel encoding for the Color LED channel (subcmd 0x02).
diff --git a/src/FanaBridge.Core/Profiles/DeviceConfig.cs b/src/FanaBridge.Core/Devices/Profiles/DeviceConfig.cs
similarity index 98%
rename from src/FanaBridge.Core/Profiles/DeviceConfig.cs
rename to src/FanaBridge.Core/Devices/Profiles/DeviceConfig.cs
index eb35ce18..9523b408 100644
--- a/src/FanaBridge.Core/Profiles/DeviceConfig.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/DeviceConfig.cs
@@ -1,6 +1,6 @@
using System;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Identifies a specific selectable device configuration — a standalone
diff --git a/src/FanaBridge.Core/Profiles/DisplayType.cs b/src/FanaBridge.Core/Devices/Profiles/DisplayType.cs
similarity index 92%
rename from src/FanaBridge.Core/Profiles/DisplayType.cs
rename to src/FanaBridge.Core/Devices/Profiles/DisplayType.cs
index b446adbc..385d909c 100644
--- a/src/FanaBridge.Core/Profiles/DisplayType.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/DisplayType.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// The type of display available on a wheel or button module.
diff --git a/src/FanaBridge.Core/Profiles/InputMapping.cs b/src/FanaBridge.Core/Devices/Profiles/InputMapping.cs
similarity index 97%
rename from src/FanaBridge.Core/Profiles/InputMapping.cs
rename to src/FanaBridge.Core/Devices/Profiles/InputMapping.cs
index 783f8a0f..0fab0aa3 100644
--- a/src/FanaBridge.Core/Profiles/InputMapping.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/InputMapping.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Newtonsoft.Json;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Structured input association for a single LED.
diff --git a/src/FanaBridge.Core/Profiles/LedChannelConverter.cs b/src/FanaBridge.Core/Devices/Profiles/LedChannelConverter.cs
similarity index 97%
rename from src/FanaBridge.Core/Profiles/LedChannelConverter.cs
rename to src/FanaBridge.Core/Devices/Profiles/LedChannelConverter.cs
index fc216327..c325e696 100644
--- a/src/FanaBridge.Core/Profiles/LedChannelConverter.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/LedChannelConverter.cs
@@ -1,7 +1,7 @@
using System;
using Newtonsoft.Json;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// JSON converter for that accepts both v1 and v2
diff --git a/src/FanaBridge.Core/Profiles/LedColorLimitation.cs b/src/FanaBridge.Core/Devices/Profiles/LedColorLimitation.cs
similarity index 99%
rename from src/FanaBridge.Core/Profiles/LedColorLimitation.cs
rename to src/FanaBridge.Core/Devices/Profiles/LedColorLimitation.cs
index 2ca84379..076abcbf 100644
--- a/src/FanaBridge.Core/Profiles/LedColorLimitation.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/LedColorLimitation.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// A way in which a wheel's LEDs cannot show what SimHub's color picker offers.
diff --git a/src/FanaBridge.Core/Profiles/LedDefinition.cs b/src/FanaBridge.Core/Devices/Profiles/LedDefinition.cs
similarity index 97%
rename from src/FanaBridge.Core/Profiles/LedDefinition.cs
rename to src/FanaBridge.Core/Devices/Profiles/LedDefinition.cs
index 19aede41..1a1f88bd 100644
--- a/src/FanaBridge.Core/Profiles/LedDefinition.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/LedDefinition.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Describes a single physical LED on the device.
diff --git a/src/FanaBridge.Core/Profiles/LedEnums.cs b/src/FanaBridge.Core/Devices/Profiles/LedEnums.cs
similarity index 97%
rename from src/FanaBridge.Core/Profiles/LedEnums.cs
rename to src/FanaBridge.Core/Devices/Profiles/LedEnums.cs
index eacf9906..2b6aea68 100644
--- a/src/FanaBridge.Core/Profiles/LedEnums.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/LedEnums.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Hardware communication channel for a single LED.
diff --git a/src/FanaBridge.Core/Profiles/ProfileMatch.cs b/src/FanaBridge.Core/Devices/Profiles/ProfileMatch.cs
similarity index 95%
rename from src/FanaBridge.Core/Profiles/ProfileMatch.cs
rename to src/FanaBridge.Core/Devices/Profiles/ProfileMatch.cs
index 0e59b200..85cd33f9 100644
--- a/src/FanaBridge.Core/Profiles/ProfileMatch.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/ProfileMatch.cs
@@ -1,6 +1,6 @@
using Newtonsoft.Json;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Matching criteria to associate a profile with the connected hardware.
diff --git a/src/FanaBridge.Core/Profiles/ProfileSource.cs b/src/FanaBridge.Core/Devices/Profiles/ProfileSource.cs
similarity index 89%
rename from src/FanaBridge.Core/Profiles/ProfileSource.cs
rename to src/FanaBridge.Core/Devices/Profiles/ProfileSource.cs
index 65083c4d..034f7985 100644
--- a/src/FanaBridge.Core/Profiles/ProfileSource.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/ProfileSource.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Indicates whether a profile was shipped with the plugin or created by the user.
diff --git a/src/FanaBridge.Core/Profiles/WheelCapabilities.cs b/src/FanaBridge.Core/Devices/Profiles/WheelCapabilities.cs
similarity index 99%
rename from src/FanaBridge.Core/Profiles/WheelCapabilities.cs
rename to src/FanaBridge.Core/Devices/Profiles/WheelCapabilities.cs
index b10de2eb..829dbe00 100644
--- a/src/FanaBridge.Core/Profiles/WheelCapabilities.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/WheelCapabilities.cs
@@ -1,6 +1,6 @@
using System.Linq;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Runtime view of a wheel's hardware capabilities, computed from a
diff --git a/src/FanaBridge.Core/Profiles/WheelProfile.cs b/src/FanaBridge.Core/Devices/Profiles/WheelProfile.cs
similarity index 96%
rename from src/FanaBridge.Core/Profiles/WheelProfile.cs
rename to src/FanaBridge.Core/Devices/Profiles/WheelProfile.cs
index 507f9e39..762d03a6 100644
--- a/src/FanaBridge.Core/Profiles/WheelProfile.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/WheelProfile.cs
@@ -3,7 +3,7 @@
using System.Linq;
using Newtonsoft.Json;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// A complete wheel profile — the single source of truth for a device's
@@ -104,17 +104,17 @@ public DisplayType DisplayType
{
if (Enum.TryParse(Display, true, out DisplayType dt))
return dt;
- return FanaBridge.Profiles.DisplayType.None;
+ return FanaBridge.Core.Devices.Profiles.DisplayType.None;
}
}
///
/// ITM display wire id (which device the wheel's ITM screen is: 1 base, 3 wheel OLED,
- /// 4 Bentley). Defaults to
+ /// 4 Bentley). Defaults to
/// (3) when omitted — correct for PBME and GTSWX.
///
[JsonIgnore]
- public byte ItmDeviceId => ItmDeviceIdRaw ?? FanaBridge.Protocol.ItmEncoder.DefaultDeviceId;
+ public byte ItmDeviceId => ItmDeviceIdRaw ?? FanaBridge.Core.Display.Protocol.ItmEncoder.DefaultDeviceId;
/// Total LED count across all channels.
[JsonIgnore]
diff --git a/src/FanaBridge.Core/Profiles/WheelProfileStore.cs b/src/FanaBridge.Core/Devices/Profiles/WheelProfileStore.cs
similarity index 99%
rename from src/FanaBridge.Core/Profiles/WheelProfileStore.cs
rename to src/FanaBridge.Core/Devices/Profiles/WheelProfileStore.cs
index c520486f..8741f145 100644
--- a/src/FanaBridge.Core/Profiles/WheelProfileStore.cs
+++ b/src/FanaBridge.Core/Devices/Profiles/WheelProfileStore.cs
@@ -5,7 +5,7 @@
using System.Threading;
using Newtonsoft.Json;
-namespace FanaBridge.Profiles
+namespace FanaBridge.Core.Devices.Profiles
{
///
/// Loads definitions and provides lookup by
@@ -126,7 +126,7 @@ private static void LoadFromEmbeddedResources(
foreach (string resourceName in assembly.GetManifestResourceNames())
{
// Embedded resource names follow: {RootNamespace}.{RelativePath}.{filename}
- // e.g. "FanaBridge.Resources.Profiles.PSWBMW.json"
+ // e.g. "FanaBridge.Core.Resources.Profiles.PSWBMW.json"
if (!resourceName.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
continue;
if (resourceName.IndexOf(".Profiles.", StringComparison.OrdinalIgnoreCase) < 0)
diff --git a/src/FanaBridge/Diagnostics/ConverterCaptureProbe.cs b/src/FanaBridge.Core/Diagnostics/ConverterCaptureProbe.cs
similarity index 99%
rename from src/FanaBridge/Diagnostics/ConverterCaptureProbe.cs
rename to src/FanaBridge.Core/Diagnostics/ConverterCaptureProbe.cs
index 081f9f52..17301db7 100644
--- a/src/FanaBridge/Diagnostics/ConverterCaptureProbe.cs
+++ b/src/FanaBridge.Core/Diagnostics/ConverterCaptureProbe.cs
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using FanaBridge.Transport;
+using FanaBridge.Core.Devices.Identity;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Diagnostics
{
///
/// One-shot, read-only identity capture run at diagnostics time so a SINGLE detection report from
diff --git a/src/FanaBridge.Core/Diagnostics/FanatecSoftwareMonitor.cs b/src/FanaBridge.Core/Diagnostics/FanatecSoftwareMonitor.cs
index 8776c15d..b3e4a420 100644
--- a/src/FanaBridge.Core/Diagnostics/FanatecSoftwareMonitor.cs
+++ b/src/FanaBridge.Core/Diagnostics/FanatecSoftwareMonitor.cs
@@ -1,6 +1,6 @@
using System;
-namespace FanaBridge.Diagnostics
+namespace FanaBridge.Core.Diagnostics
{
///
/// Detects whether the Fanatec app/service is running alongside FanaBridge. Both drive
diff --git a/src/FanaBridge.Core/Protocol/ItmDeviceCatalog.cs b/src/FanaBridge.Core/Display/Catalog/ItmDeviceCatalog.cs
similarity index 97%
rename from src/FanaBridge.Core/Protocol/ItmDeviceCatalog.cs
rename to src/FanaBridge.Core/Display/Catalog/ItmDeviceCatalog.cs
index b0fa7ff8..5a516bbc 100644
--- a/src/FanaBridge.Core/Protocol/ItmDeviceCatalog.cs
+++ b/src/FanaBridge.Core/Display/Catalog/ItmDeviceCatalog.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
+using FanaBridge.Core.Display.Protocol;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Display.Catalog
{
///
/// One slot in an ITM device's page set: the on-wire page number, which page content
diff --git a/src/FanaBridge.Core/Protocol/DisplayEncoder.cs b/src/FanaBridge.Core/Display/Protocol/DisplayEncoder.cs
similarity index 98%
rename from src/FanaBridge.Core/Protocol/DisplayEncoder.cs
rename to src/FanaBridge.Core/Display/Protocol/DisplayEncoder.cs
index f51f8db2..f947977a 100644
--- a/src/FanaBridge.Core/Protocol/DisplayEncoder.cs
+++ b/src/FanaBridge.Core/Display/Protocol/DisplayEncoder.cs
@@ -1,7 +1,7 @@
using System;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Display.Protocol
{
///
/// Encodes and sends display control reports for the Fanatec
diff --git a/src/FanaBridge.Core/Protocol/ItmEncoder.cs b/src/FanaBridge.Core/Display/Protocol/ItmEncoder.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/ItmEncoder.cs
rename to src/FanaBridge.Core/Display/Protocol/ItmEncoder.cs
index f43befe4..6ea7819d 100644
--- a/src/FanaBridge.Core/Protocol/ItmEncoder.cs
+++ b/src/FanaBridge.Core/Display/Protocol/ItmEncoder.cs
@@ -2,9 +2,9 @@
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Display.Protocol
{
///
/// A single telemetry value for an ITM ValueUpdate entry (col03 FF 05 01).
@@ -129,7 +129,7 @@ public static ItmParamDef WithSuffix(byte slotId, string suffix, ushort position
/// reference: Enable (FF 02 02), ParamDefs (FF 05 03), ValueUpdate
/// (FF 05 01), and the PageSet frame (FF 05 04).
///
- /// This is a pure framing layer — like and
+ /// This is a pure framing layer — like and
/// , it builds and writes reports but holds no display
/// state. Page selection, telemetry-to-parameter mapping, and the firmware-safety rate
/// limits (e.g. value-update pacing) are the caller's
diff --git a/src/FanaBridge.Core/Protocol/ItmTelemetry.cs b/src/FanaBridge.Core/Display/Protocol/ItmTelemetry.cs
similarity index 98%
rename from src/FanaBridge.Core/Protocol/ItmTelemetry.cs
rename to src/FanaBridge.Core/Display/Protocol/ItmTelemetry.cs
index d2d2f743..6c48d006 100644
--- a/src/FanaBridge.Core/Protocol/ItmTelemetry.cs
+++ b/src/FanaBridge.Core/Display/Protocol/ItmTelemetry.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using FanaBridge.Core.Display.Catalog;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Display.Protocol
{
///
/// A page's content identity — the fixed parameter layout the firmware renders (SPEED
@@ -100,7 +102,7 @@ public ItmSubscription(byte firmwareHandle, ushort paramId, byte dataType = 0)
/// Wire-side ITM protocol vocabulary: the per-page parameter catalog (which
/// parameter IDs a page carries, in order) and firmware subscription-report parsing.
/// This is pure wire — no SimHub GameData. The SimHub telemetry → value/suffix
- /// mapping lives in ItmTelemetryMapper (Adapters), which knows both sides.
+ /// mapping lives in ItmTelemetryMapper (Display.Drivers), which knows both sides.
///
/// This declares each page's parameter list () and display name
/// (), keyed by the content identity. Which pages a
diff --git a/src/FanaBridge.Core/Protocol/SevenSegment.cs b/src/FanaBridge.Core/Display/Protocol/SevenSegment.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/SevenSegment.cs
rename to src/FanaBridge.Core/Display/Protocol/SevenSegment.cs
index fbaa8f49..e580e3d2 100644
--- a/src/FanaBridge.Core/Protocol/SevenSegment.cs
+++ b/src/FanaBridge.Core/Display/Protocol/SevenSegment.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Display.Protocol
{
///
/// 7-segment display encoding table.
diff --git a/src/FanaBridge.Core/Protocol/ItmLifecycleController.cs b/src/FanaBridge.Core/Display/Session/ItmLifecycleController.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/ItmLifecycleController.cs
rename to src/FanaBridge.Core/Display/Session/ItmLifecycleController.cs
index 2dc221bd..1d405f24 100644
--- a/src/FanaBridge.Core/Protocol/ItmLifecycleController.cs
+++ b/src/FanaBridge.Core/Display/Session/ItmLifecycleController.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using FanaBridge.Core.Display.Catalog;
+using FanaBridge.Core.Display.Protocol;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Display.Session
{
/// The ITM lifecycle states. See .
public enum ItmLifecycleState
diff --git a/src/FanaBridge.Core/FanaBridge.Core.csproj b/src/FanaBridge.Core/FanaBridge.Core.csproj
index 256e80d9..6a7809fd 100644
--- a/src/FanaBridge.Core/FanaBridge.Core.csproj
+++ b/src/FanaBridge.Core/FanaBridge.Core.csproj
@@ -3,11 +3,12 @@
net48
Library
-
- FanaBridge
+
+ FanaBridge.Core
FanaBridge.Core
FanaBridge device stack (SimHub-free): HID transport, Fanatec protocol, wheel profiles
FanaBridge
@@ -45,11 +46,8 @@
-
-
+
diff --git a/src/FanaBridge.Core/Protocol/ColorHelper.cs b/src/FanaBridge.Core/Leds/ColorHelper.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/ColorHelper.cs
rename to src/FanaBridge.Core/Leds/ColorHelper.cs
index 8f88bc3b..1e1039d9 100644
--- a/src/FanaBridge.Core/Protocol/ColorHelper.cs
+++ b/src/FanaBridge.Core/Leds/ColorHelper.cs
@@ -1,6 +1,6 @@
using System;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Leds
{
///
/// RGB color helper with RGB565 conversion and predefined colors.
diff --git a/src/FanaBridge.Core/Protocol/LedEncoder.cs b/src/FanaBridge.Core/Leds/LedEncoder.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/LedEncoder.cs
rename to src/FanaBridge.Core/Leds/LedEncoder.cs
index 1ab8b10a..7b897479 100644
--- a/src/FanaBridge.Core/Protocol/LedEncoder.cs
+++ b/src/FanaBridge.Core/Leds/LedEncoder.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Leds
{
///
/// Encodes and sends LED control reports for Fanatec wheels.
diff --git a/src/FanaBridge.Core/Protocol/LegacyLedEncoder.cs b/src/FanaBridge.Core/Leds/LegacyLedEncoder.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/LegacyLedEncoder.cs
rename to src/FanaBridge.Core/Leds/LegacyLedEncoder.cs
index 626a66ef..007d69e2 100644
--- a/src/FanaBridge.Core/Protocol/LegacyLedEncoder.cs
+++ b/src/FanaBridge.Core/Leds/LegacyLedEncoder.cs
@@ -1,7 +1,7 @@
using System;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Leds
{
///
/// Encodes and sends LED control reports for legacy Fanatec wheels
diff --git a/src/FanaBridge.Core/Logging/ILogSink.cs b/src/FanaBridge.Core/Logging/ILogSink.cs
new file mode 100644
index 00000000..fed0d66b
--- /dev/null
+++ b/src/FanaBridge.Core/Logging/ILogSink.cs
@@ -0,0 +1,15 @@
+namespace FanaBridge.Core.Logging
+{
+ ///
+ /// Log output contract for the SimHub-free core. The plugin shell installs a
+ /// sink that forwards to SimHub's logger; standalone hosts (unit tests) leave
+ /// it unset and core logging is a silent no-op.
+ ///
+ public interface ILogSink
+ {
+ void Info(string message);
+ void Warn(string message);
+ void Error(string message);
+ void Debug(string message);
+ }
+}
diff --git a/src/FanaBridge.Core/Logging/Log.cs b/src/FanaBridge.Core/Logging/Log.cs
index 270c20b6..43b25050 100644
--- a/src/FanaBridge.Core/Logging/Log.cs
+++ b/src/FanaBridge.Core/Logging/Log.cs
@@ -1,18 +1,9 @@
+// Exception: namespace FanaBridge (not FanaBridge.Core.Logging) so unqualified Log.* from both assemblies resolves via the shared root ancestor.
+#pragma warning disable IDE0130 // Log must live in root FanaBridge so unqualified Log.* resolves from both Core and plugin
+using FanaBridge.Core.Logging;
+
namespace FanaBridge
{
- ///
- /// Log output contract for the SimHub-free core. The plugin shell installs a
- /// sink that forwards to SimHub's logger; standalone hosts (unit tests) leave
- /// it unset and core logging is a silent no-op.
- ///
- public interface ILogSink
- {
- void Info(string message);
- void Warn(string message);
- void Error(string message);
- void Debug(string message);
- }
-
///
/// Process-wide log seam for the core device stack. Kept static (not
/// injected) because logging is the one cross-cutting concern threaded
diff --git a/src/FanaBridge.Core/Transport/Col03Family.cs b/src/FanaBridge.Core/Transport/Col03Family.cs
index 6d7b42a0..87aadaa5 100644
--- a/src/FanaBridge.Core/Transport/Col03Family.cs
+++ b/src/FanaBridge.Core/Transport/Col03Family.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// The frame families multiplexed on the col03 input endpoint, as routed by
diff --git a/src/FanaBridge.Core/Transport/Col03FrameClassifier.cs b/src/FanaBridge.Core/Transport/Col03FrameClassifier.cs
index d9258c7d..814108a5 100644
--- a/src/FanaBridge.Core/Transport/Col03FrameClassifier.cs
+++ b/src/FanaBridge.Core/Transport/Col03FrameClassifier.cs
@@ -1,6 +1,6 @@
-using FanaBridge.Transport;
+using FanaBridge.Core.Devices.Identity;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Transport
{
///
/// The single home for col03 frame-family wire signatures. The transport's
diff --git a/src/FanaBridge.Core/Transport/Col03QueueSet.cs b/src/FanaBridge.Core/Transport/Col03QueueSet.cs
index f97d0de0..a6f8b61b 100644
--- a/src/FanaBridge.Core/Transport/Col03QueueSet.cs
+++ b/src/FanaBridge.Core/Transport/Col03QueueSet.cs
@@ -1,6 +1,5 @@
-using FanaBridge.Protocol;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// The per-family input queues behind one col03 connection. The transport's
diff --git a/src/FanaBridge.Core/Transport/FanatecTransport.cs b/src/FanaBridge.Core/Transport/FanatecTransport.cs
index a6d85857..bc983b62 100644
--- a/src/FanaBridge.Core/Transport/FanatecTransport.cs
+++ b/src/FanaBridge.Core/Transport/FanatecTransport.cs
@@ -2,10 +2,11 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
+using FanaBridge.Core.Devices;
using HidSharp;
using Microsoft.Win32.SafeHandles;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// HID transport layer for Fanatec wheel hardware.
diff --git a/src/FanaBridge.Core/Transport/HidReportQueue.cs b/src/FanaBridge.Core/Transport/HidReportQueue.cs
index d9a5b98f..46963afe 100644
--- a/src/FanaBridge.Core/Transport/HidReportQueue.cs
+++ b/src/FanaBridge.Core/Transport/HidReportQueue.cs
@@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Threading;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// Bounded FIFO of HID input reports, bridging the blocking reader thread to
diff --git a/src/FanaBridge.Core/Transport/IConnectableTransport.cs b/src/FanaBridge.Core/Transport/IConnectableTransport.cs
index 29bfd877..27875547 100644
--- a/src/FanaBridge.Core/Transport/IConnectableTransport.cs
+++ b/src/FanaBridge.Core/Transport/IConnectableTransport.cs
@@ -1,11 +1,11 @@
using System;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// The connect-lifecycle surface of a device transport, layered on top of
/// (the send/read surface the encoders and
- /// protocol readers use). orchestrates against
+ /// protocol readers use). orchestrates against
/// this interface rather than the concrete ,
/// so its identity state machine (drain → settle → commit, SRM precedence,
/// disconnect reset) is unit-testable with a fake transport — the layer where
diff --git a/src/FanaBridge.Core/Transport/IDeviceTransport.cs b/src/FanaBridge.Core/Transport/IDeviceTransport.cs
index 82578ae3..8eded348 100644
--- a/src/FanaBridge.Core/Transport/IDeviceTransport.cs
+++ b/src/FanaBridge.Core/Transport/IDeviceTransport.cs
@@ -1,6 +1,6 @@
using System;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// Low-level HID transport abstraction for all protocol encoders
diff --git a/src/FanaBridge.Core/Transport/IHidBusEnumerator.cs b/src/FanaBridge.Core/Transport/IHidBusEnumerator.cs
index fb5dc6b3..9310ce7e 100644
--- a/src/FanaBridge.Core/Transport/IHidBusEnumerator.cs
+++ b/src/FanaBridge.Core/Transport/IHidBusEnumerator.cs
@@ -2,7 +2,7 @@
using System.Linq;
using HidSharp;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// A lightweight snapshot of one HID device on the bus, with the descriptor
@@ -34,7 +34,7 @@ public HidDeviceInfo(int productId, int maxOutputReportLength, int maxInputRepor
///
/// Seam over HID bus enumeration (DeviceList.Local is a process-global
- /// static that cannot be faked). consumes this
+ /// static that cannot be faked). consumes this
/// for discovery so base-PID selection — which has historically bitten on
/// interface-shape edge cases — is table-testable.
///
diff --git a/src/FanaBridge.Core/Transport/IReportStream.cs b/src/FanaBridge.Core/Transport/IReportStream.cs
index b3debd69..d4e9ddee 100644
--- a/src/FanaBridge.Core/Transport/IReportStream.cs
+++ b/src/FanaBridge.Core/Transport/IReportStream.cs
@@ -1,6 +1,6 @@
using System;
-namespace FanaBridge.Transport
+namespace FanaBridge.Core.Transport
{
///
/// One family of inbound HID reports (e.g. col03 FF 08 identity), fed by the
diff --git a/src/FanaBridge.Core/Protocol/ReportElicit.cs b/src/FanaBridge.Core/Transport/ReportElicit.cs
similarity index 98%
rename from src/FanaBridge.Core/Protocol/ReportElicit.cs
rename to src/FanaBridge.Core/Transport/ReportElicit.cs
index d805dcf1..bb1c3ffe 100644
--- a/src/FanaBridge.Core/Protocol/ReportElicit.cs
+++ b/src/FanaBridge.Core/Transport/ReportElicit.cs
@@ -1,7 +1,6 @@
using System;
-using FanaBridge.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Transport
{
///
/// The one request→response primitive over a per-family report stream:
diff --git a/src/FanaBridge.Core/Protocol/EncoderMode.cs b/src/FanaBridge.Core/Tuning/EncoderMode.cs
similarity index 95%
rename from src/FanaBridge.Core/Protocol/EncoderMode.cs
rename to src/FanaBridge.Core/Tuning/EncoderMode.cs
index a8a71d83..70d635d5 100644
--- a/src/FanaBridge.Core/Protocol/EncoderMode.cs
+++ b/src/FanaBridge.Core/Tuning/EncoderMode.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Tuning
{
///
/// Encoder operating mode for Fanatec button modules.
diff --git a/src/FanaBridge.Core/Protocol/FanatecTuningController.cs b/src/FanaBridge.Core/Tuning/FanatecTuningController.cs
similarity index 99%
rename from src/FanaBridge.Core/Protocol/FanatecTuningController.cs
rename to src/FanaBridge.Core/Tuning/FanatecTuningController.cs
index f4f9001f..17093dd6 100644
--- a/src/FanaBridge.Core/Protocol/FanatecTuningController.cs
+++ b/src/FanaBridge.Core/Tuning/FanatecTuningController.cs
@@ -1,8 +1,8 @@
using System;
using System.Threading;
-using FanaBridge.Transport;
+using FanaBridge.Core.Transport;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Core.Tuning
{
///
/// Manages the Fanatec tuning configuration protocol (col03, cmd class 0x03).
diff --git a/src/FanaBridge/Adapters/ControlMapperBridge.cs b/src/FanaBridge/ControlMapper/ControlMapperBridge.cs
similarity index 99%
rename from src/FanaBridge/Adapters/ControlMapperBridge.cs
rename to src/FanaBridge/ControlMapper/ControlMapperBridge.cs
index 8326cbad..e36a2c1e 100644
--- a/src/FanaBridge/Adapters/ControlMapperBridge.cs
+++ b/src/FanaBridge/ControlMapper/ControlMapperBridge.cs
@@ -6,7 +6,7 @@
using System.Text;
using SimHub.Plugins.OutputPlugins.ControlRemapper.Variants;
-namespace FanaBridge.Adapters
+namespace FanaBridge.ControlMapper
{
///
/// Reflection plumbing that registers a
diff --git a/src/FanaBridge/Adapters/FanaBridgeVariantProvider.cs b/src/FanaBridge/ControlMapper/FanaBridgeVariantProvider.cs
similarity index 97%
rename from src/FanaBridge/Adapters/FanaBridgeVariantProvider.cs
rename to src/FanaBridge/ControlMapper/FanaBridgeVariantProvider.cs
index dd850c82..9314c406 100644
--- a/src/FanaBridge/Adapters/FanaBridgeVariantProvider.cs
+++ b/src/FanaBridge/ControlMapper/FanaBridgeVariantProvider.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
-using FanaBridge.Protocol;
+using FanaBridge.Core.Devices.Identity;
using SimHub.Plugins.OutputPlugins.ControlRemapper.Variants;
-namespace FanaBridge.Adapters
+namespace FanaBridge.ControlMapper
{
///
/// Reports the currently-attached Fanatec rim to SimHub's Control Mapper as an
@@ -40,7 +40,7 @@ public class FanaBridgeVariantProvider : IVariantProvider
///
/// Fanatec USB vendor id (0x0EB7 = 3767). Matches the value the stock
/// FanatecVariantProvider gates on, and
- /// .
+ /// .
///
public const int FanatecVendorId = 0x0EB7;
@@ -77,7 +77,7 @@ public class FanaBridgeVariantProvider : IVariantProvider
/// provider, or no variant at all, decide).
///
/// Gated on the Fanatec vendor id AND the connected wheelbase's product id
- /// (): we only speak for
+ /// (): we only speak for
/// the device we actually have open over HID. A standalone Fanatec peripheral on
/// its own PID (pedals, handbrake) — or another Fanatec base we aren't driving —
/// is therefore left alone and falls through to SimHub instead of being tagged with
diff --git a/src/FanaBridge/Adapters/FanatecDevicesRegistry.cs b/src/FanaBridge/Devices/FanatecDevicesRegistry.cs
similarity index 98%
rename from src/FanaBridge/Adapters/FanatecDevicesRegistry.cs
rename to src/FanaBridge/Devices/FanatecDevicesRegistry.cs
index 723b235c..3a1532a1 100644
--- a/src/FanaBridge/Adapters/FanatecDevicesRegistry.cs
+++ b/src/FanaBridge/Devices/FanatecDevicesRegistry.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using FanaBridge.Profiles;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Settings;
+using FanaBridge.UI.Devices;
using SimHub.Plugins.Devices;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Devices
{
///
/// Registers one DeviceDescriptor per loaded so each
@@ -25,7 +27,7 @@ public class FanatecDevicesRegistry : IDeviceDescriptorsRegistry
/// plugin: SimHub shows a device's settings whether or not FanaBridge is
/// running. Overridable so tests can build instances without WPF.
///
- internal static readonly IDevicePanelFactory PanelFactory = new UI.DevicePanelFactory();
+ internal static readonly IDevicePanelFactory PanelFactory = new DevicePanelFactory();
public IEnumerable GetDevices()
{
diff --git a/src/FanaBridge/Adapters/FanatecWheelDeviceInstance.cs b/src/FanaBridge/Devices/FanatecWheelDeviceInstance.cs
similarity index 99%
rename from src/FanaBridge/Adapters/FanatecWheelDeviceInstance.cs
rename to src/FanaBridge/Devices/FanatecWheelDeviceInstance.cs
index d067b0db..a1008e17 100644
--- a/src/FanaBridge/Adapters/FanatecWheelDeviceInstance.cs
+++ b/src/FanaBridge/Devices/FanatecWheelDeviceInstance.cs
@@ -3,16 +3,20 @@
using System.ComponentModel;
using System.Linq;
using FanaBridge;
-using FanaBridge.Profiles;
-using FanaBridge.Protocol;
-using FanaBridge.Transport;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Core.Display.Session;
+using FanaBridge.Display;
+using FanaBridge.Display.Drivers;
+using FanaBridge.Leds;
+using FanaBridge.Settings;
+using FanaBridge.UI.Devices;
using GameReaderCommon;
using Newtonsoft.Json.Linq;
using SimHub.Plugins;
using SimHub.Plugins.Devices;
using SimHub.Plugins.OutputPlugins.GraphicalDash.LedModules;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Devices
{
///
/// DeviceInstance for a specific Fanatec wheel type.
diff --git a/src/FanaBridge/Adapters/SimHubDevicesGateway.cs b/src/FanaBridge/Devices/SimHubDevicesGateway.cs
similarity index 98%
rename from src/FanaBridge/Adapters/SimHubDevicesGateway.cs
rename to src/FanaBridge/Devices/SimHubDevicesGateway.cs
index 97c51cca..ed182b1f 100644
--- a/src/FanaBridge/Adapters/SimHubDevicesGateway.cs
+++ b/src/FanaBridge/Devices/SimHubDevicesGateway.cs
@@ -3,7 +3,7 @@
using SimHub.Plugins;
using SimHub.Plugins.Devices;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Devices
{
///
/// Read-and-check access to SimHub's Devices plugin for the settings
diff --git a/src/FanaBridge/Diagnostics/DiagnosticsReport.cs b/src/FanaBridge/Diagnostics/DiagnosticsReport.cs
index 4380e383..4a9f3fed 100644
--- a/src/FanaBridge/Diagnostics/DiagnosticsReport.cs
+++ b/src/FanaBridge/Diagnostics/DiagnosticsReport.cs
@@ -1,10 +1,11 @@
using System;
using System.Linq;
using System.Text;
-using FanaBridge.Transport;
+using FanaBridge.Core.Devices;
+using FanaBridge.Core.Diagnostics;
using HidSharp;
-namespace FanaBridge.Protocol
+namespace FanaBridge.Diagnostics
{
///
/// Builds a human-readable, GitHub-ready snapshot of device detection — the
diff --git a/src/FanaBridge/Adapters/DisplaySettings.cs b/src/FanaBridge/Display/DisplaySettings.cs
similarity index 98%
rename from src/FanaBridge/Adapters/DisplaySettings.cs
rename to src/FanaBridge/Display/DisplaySettings.cs
index 457e8534..7c1627d6 100644
--- a/src/FanaBridge/Adapters/DisplaySettings.cs
+++ b/src/FanaBridge/Display/DisplaySettings.cs
@@ -1,4 +1,4 @@
-namespace FanaBridge.Adapters
+namespace FanaBridge.Display
{
///
/// Type-safe display configuration.
diff --git a/src/FanaBridge/Adapters/FanatecDisplayDriver.cs b/src/FanaBridge/Display/Drivers/FanatecDisplayDriver.cs
similarity index 98%
rename from src/FanaBridge/Adapters/FanatecDisplayDriver.cs
rename to src/FanaBridge/Display/Drivers/FanatecDisplayDriver.cs
index 55dac741..3df69521 100644
--- a/src/FanaBridge/Adapters/FanatecDisplayDriver.cs
+++ b/src/FanaBridge/Display/Drivers/FanatecDisplayDriver.cs
@@ -1,8 +1,9 @@
-using FanaBridge.Protocol;
-using GameReaderCommon;
using System;
+using FanaBridge.Core.Display.Protocol;
+using FanaBridge.Display;
+using GameReaderCommon;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Display.Drivers
{
///
/// Maps telemetry data to the Fanatec 3-digit 7-segment display.
diff --git a/src/FanaBridge/Adapters/ItmDisplayDriver.cs b/src/FanaBridge/Display/Drivers/ItmDisplayDriver.cs
similarity index 99%
rename from src/FanaBridge/Adapters/ItmDisplayDriver.cs
rename to src/FanaBridge/Display/Drivers/ItmDisplayDriver.cs
index f1c96fb8..5ae602b9 100644
--- a/src/FanaBridge/Adapters/ItmDisplayDriver.cs
+++ b/src/FanaBridge/Display/Drivers/ItmDisplayDriver.cs
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
-using FanaBridge.Protocol;
+using FanaBridge.Core.Display.Protocol;
+using FanaBridge.Core.Display.Session;
using GameReaderCommon;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Display.Drivers
{
///
/// Drives a Fanatec ITM telemetry display: maps SimHub telemetry to the parameters the
diff --git a/src/FanaBridge/Adapters/ItmTelemetryMapper.cs b/src/FanaBridge/Display/Drivers/ItmTelemetryMapper.cs
similarity index 98%
rename from src/FanaBridge/Adapters/ItmTelemetryMapper.cs
rename to src/FanaBridge/Display/Drivers/ItmTelemetryMapper.cs
index 258d9e3b..e34f733c 100644
--- a/src/FanaBridge/Adapters/ItmTelemetryMapper.cs
+++ b/src/FanaBridge/Display/Drivers/ItmTelemetryMapper.cs
@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
+using FanaBridge.Core.Display.Protocol;
using GameReaderCommon;
-using FanaBridge.Protocol;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Display.Drivers
{
///
/// Maps SimHub telemetry to encoded entries
@@ -14,8 +14,8 @@ namespace FanaBridge.Adapters
/// The wire-side vocabulary — the page catalog (which params a page carries) and
/// subscription-report parsing — lives in (Protocol, no SimHub).
/// This class knows both sides (wire paramId + SimHub GameData), which is why
- /// it belongs in Adapters, not Protocol. It holds no wire framing and no state — the pure,
- /// per-frame translation step, the ITM analogue of FanatecDisplayDriver's reads.
+ /// it belongs in Display.Drivers (plugin), not Protocol (Core). It holds no wire framing and
+ /// no state — the pure, per-frame translation step, the ITM analogue of FanatecDisplayDriver's reads.
///
public static class ItmTelemetryMapper
{
diff --git a/src/FanaBridge/FanatecPlugin.cs b/src/FanaBridge/FanatecPlugin.cs
index 23e29494..3835c050 100644
--- a/src/FanaBridge/FanatecPlugin.cs
+++ b/src/FanaBridge/FanatecPlugin.cs
@@ -1,18 +1,24 @@
-using FanaBridge.Adapters;
-using FanaBridge.Profiles;
-using FanaBridge.Protocol;
-using FanaBridge.Transport;
-using FanaBridge.UI;
-using FanaBridge.Updater;
-using FanaBridge.Updates;
-using GameReaderCommon;
-using SimHub.Plugins;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
+using FanaBridge.ControlMapper;
+using FanaBridge.Core.Devices;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Core.Display.Protocol;
+using FanaBridge.Core.Leds;
+using FanaBridge.Core.Transport;
+using FanaBridge.Core.Tuning;
+using FanaBridge.Devices;
+using FanaBridge.Diagnostics;
+using FanaBridge.Settings;
+using FanaBridge.UI.Settings;
+using FanaBridge.Updater;
+using FanaBridge.Updates;
+using GameReaderCommon;
+using SimHub.Plugins;
namespace FanaBridge
{
@@ -61,7 +67,7 @@ public class FanatecPlugin : IPlugin, IDataPlugin, IWPFSettingsV2, IReusable
// volatile: written on the DataUpdate thread (UpdateControlMapperIntegration),
// read in the WheelChanged handler, which may fire from any thread. Ensures the
// handler observes the constructed bridge; a missed read self-heals next tick.
- private volatile FanaBridge.Adapters.ControlMapperBridge _controlMapperBridge;
+ private volatile ControlMapperBridge _controlMapperBridge;
/// Frame counter that throttles the Control Mapper reconcile (see ).
private int _cmReconcileTick;
@@ -71,8 +77,8 @@ public class FanatecPlugin : IPlugin, IDataPlugin, IWPFSettingsV2, IReusable
// connected wheel's SimHub device name — the user's rename if set, else the
// short name — and label the mapped controller consistently with the Devices view.
private readonly object _deviceInstancesLock = new object();
- private readonly List _deviceInstances =
- new List();
+ private readonly List _deviceInstances =
+ new List();
/// Fired when connection status or wheel identity changes. May fire from any thread.
public event Action StateChanged;
@@ -214,10 +220,10 @@ internal void InstallWheelbaseForTest(FanatecWheelbase wheelbase, bool withDispl
_display = new DisplayEncoder(wheelbase.Transport);
}
- /// Called by each so the
+ /// Called by each so the
/// plugin can read the connected wheel's SimHub device name for the Control Mapper
/// integration. Idempotent.
- internal void RegisterDeviceInstance(Adapters.FanatecWheelDeviceInstance instance)
+ internal void RegisterDeviceInstance(FanatecWheelDeviceInstance instance)
{
if (instance == null) return;
lock (_deviceInstancesLock)
@@ -226,7 +232,7 @@ internal void RegisterDeviceInstance(Adapters.FanatecWheelDeviceInstance instanc
}
/// Removes a DeviceInstance from the registry (on its End).
- internal void UnregisterDeviceInstance(Adapters.FanatecWheelDeviceInstance instance)
+ internal void UnregisterDeviceInstance(FanatecWheelDeviceInstance instance)
{
if (instance == null) return;
lock (_deviceInstancesLock)
@@ -264,8 +270,8 @@ public string GetConnectedWheelDisplayName()
// Detects the vendor's app/service running alongside FanaBridge — the same
// display, no arbitration, so it's a warning the user needs to see instead of
// chasing phantom ITM flicker. TTL-cached; edges are logged once.
- private readonly Diagnostics.FanatecSoftwareMonitor _fanatecSoftware =
- new Diagnostics.FanatecSoftwareMonitor(
+ private readonly Core.Diagnostics.FanatecSoftwareMonitor _fanatecSoftware =
+ new Core.Diagnostics.FanatecSoftwareMonitor(
log: msg => SimHub.Logging.Current.Warn("FanaBridge: " + msg));
///
@@ -320,7 +326,7 @@ internal void ProbeItmCoDriver()
{
try
{
- var bridge = _controlMapperBridge ?? new FanaBridge.Adapters.ControlMapperBridge();
+ var bridge = _controlMapperBridge ?? new ControlMapperBridge();
return bridge.IsRecognizeIndividualWheelsOn(PluginManager);
}
catch { return null; }
@@ -408,7 +414,7 @@ public void Init(PluginManager pluginManager)
// dashboard while testing the experimental Control Mapper integration.
// Cheap (no reflection) — it only reads live wheel state.
this.AttachDelegate("FanaBridge.ControlMapperVariant",
- () => Adapters.FanaBridgeVariantProvider.ComputeFriendlyName() ?? "");
+ () => FanaBridgeVariantProvider.ComputeFriendlyName() ?? "");
// --- Events ---
this.AddEvent("DeviceConnected");
@@ -628,7 +634,7 @@ private void UpdateControlMapperIntegration()
if (Settings.EnableControlMapperIntegration)
{
if (_controlMapperBridge == null)
- _controlMapperBridge = new FanaBridge.Adapters.ControlMapperBridge();
+ _controlMapperBridge = new ControlMapperBridge();
_controlMapperBridge.EnsureRegistered(PluginManager);
// Stamp a friendly CustomName on the connected wheel's source(s) so
// the UI shows a readable name while the match key stays the stable id.
@@ -714,9 +720,9 @@ public void FinalizePlugin()
// serializes this against any frame still in flight; see
// docs/device-settings-lifecycle.md. Copied out of the lock so
// no device work runs while holding the instance list.
- List instances;
+ List instances;
lock (_deviceInstancesLock)
- instances = new List(_deviceInstances);
+ instances = new List(_deviceInstances);
foreach (var inst in instances)
{
@@ -773,7 +779,7 @@ public string BuildDiagnosticsReport()
string controlMapperSection;
try
{
- var bridge = _controlMapperBridge ?? new FanaBridge.Adapters.ControlMapperBridge();
+ var bridge = _controlMapperBridge ?? new ControlMapperBridge();
controlMapperSection = bridge.DescribeResolution(PluginManager);
}
catch (Exception ex)
diff --git a/src/FanaBridge/Adapters/FanatecLedDriver.cs b/src/FanaBridge/Leds/FanatecLedDriver.cs
similarity index 99%
rename from src/FanaBridge/Adapters/FanatecLedDriver.cs
rename to src/FanaBridge/Leds/FanatecLedDriver.cs
index 7f8c1805..8fd5b9eb 100644
--- a/src/FanaBridge/Adapters/FanatecLedDriver.cs
+++ b/src/FanaBridge/Leds/FanatecLedDriver.cs
@@ -5,10 +5,10 @@
using BA63Driver.Base;
using BA63Driver.Interfaces;
using BA63Driver.Mapper;
-using FanaBridge.Profiles;
-using FanaBridge.Protocol;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Core.Leds;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Leds
{
///
/// Unified BA63-compatible driver for all Fanatec LED types.
diff --git a/src/FanaBridge/Adapters/FanatecLedManager.cs b/src/FanaBridge/Leds/FanatecLedManager.cs
similarity index 98%
rename from src/FanaBridge/Adapters/FanatecLedManager.cs
rename to src/FanaBridge/Leds/FanatecLedManager.cs
index dc1d5ba5..cf20b408 100644
--- a/src/FanaBridge/Adapters/FanatecLedManager.cs
+++ b/src/FanaBridge/Leds/FanatecLedManager.cs
@@ -1,15 +1,14 @@
using System;
-using FanaBridge.Profiles;
-using FanaBridge.Protocol;
+using FanaBridge.Core.Devices.Profiles;
using SimHub.Plugins.OutputPlugins.GraphicalDash.PSE;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Leds
{
///
/// Bridges into SimHub's
/// ILedDeviceManager pipeline via LedsGenericManager<T>.
///
- /// Each creates one of these with
+ /// Each creates one of these with
/// the appropriate and passes it to
/// LedModuleSettings<FanatecLedManager> as a pre-created driver
/// (via LedModuleOptions.LedDriver or the constructor overload).
diff --git a/src/FanaBridge/Adapters/FanatecLedModuleHost.cs b/src/FanaBridge/Leds/FanatecLedModuleHost.cs
similarity index 98%
rename from src/FanaBridge/Adapters/FanatecLedModuleHost.cs
rename to src/FanaBridge/Leds/FanatecLedModuleHost.cs
index 8dc7e887..5b35ccac 100644
--- a/src/FanaBridge/Adapters/FanatecLedModuleHost.cs
+++ b/src/FanaBridge/Leds/FanatecLedModuleHost.cs
@@ -2,14 +2,14 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
-using FanaBridge.Profiles;
-using FanaBridge.Transport;
+using FanaBridge.Core.Devices;
+using FanaBridge.Core.Devices.Profiles;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SimHub.Plugins;
using SimHub.Plugins.OutputPlugins.GraphicalDash.LedModules;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Leds
{
///
/// The real LED settings module, built from registered (override-resolved)
@@ -51,7 +51,7 @@ public FanatecLedModuleHost(
// resolves capabilities when the tab is opened, so it reflects
// the wheel that is actually connected.
ExtraSettingsControlFactory =
- _ => new UI.LedColorLimitationNotice(currentCapabilities),
+ _ => new UI.Devices.LedColorLimitationNotice(currentCapabilities),
};
_module = new LedModuleSettings(options)
diff --git a/src/FanaBridge/Adapters/IFanatecLedModuleHost.cs b/src/FanaBridge/Leds/IFanatecLedModuleHost.cs
similarity index 94%
rename from src/FanaBridge/Adapters/IFanatecLedModuleHost.cs
rename to src/FanaBridge/Leds/IFanatecLedModuleHost.cs
index b87de454..4ce298f1 100644
--- a/src/FanaBridge/Adapters/IFanatecLedModuleHost.cs
+++ b/src/FanaBridge/Leds/IFanatecLedModuleHost.cs
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
using System.Windows.Controls;
+using FanaBridge.Core.Devices.Profiles;
using Newtonsoft.Json.Linq;
using SimHub.Plugins;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Leds
{
///
/// The LED settings module, behind an interface so persistence is testable
@@ -52,7 +53,7 @@ internal interface IFanatecLedModuleHost : IDisposable
void StopDriving();
/// Tells the module the active profile may have changed.
- void HotSwapIfNeeded(Profiles.WheelCapabilities currentCaps);
+ void HotSwapIfNeeded(WheelCapabilities currentCaps);
/// The module's dynamic button actions (brightness, etc.).
IEnumerable GetDynamicActions();
diff --git a/src/FanaBridge/Adapters/NoLedModuleHost.cs b/src/FanaBridge/Leds/NoLedModuleHost.cs
similarity index 93%
rename from src/FanaBridge/Adapters/NoLedModuleHost.cs
rename to src/FanaBridge/Leds/NoLedModuleHost.cs
index 1199feb4..17c8f53f 100644
--- a/src/FanaBridge/Adapters/NoLedModuleHost.cs
+++ b/src/FanaBridge/Leds/NoLedModuleHost.cs
@@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Controls;
-using FanaBridge.Profiles;
+using FanaBridge.Core.Devices.Profiles;
using Newtonsoft.Json.Linq;
using SimHub.Plugins;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Leds
{
///
/// Stands in for the LED module on devices whose registered capabilities
diff --git a/src/FanaBridge/Logging/SimHubLogSink.cs b/src/FanaBridge/Logging/SimHubLogSink.cs
index 4273b77a..b9a215b6 100644
--- a/src/FanaBridge/Logging/SimHubLogSink.cs
+++ b/src/FanaBridge/Logging/SimHubLogSink.cs
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;
+using FanaBridge.Core.Logging;
-namespace FanaBridge
+namespace FanaBridge.Logging
{
/// Forwards the core's seam to SimHub's logger.
internal sealed class SimHubLogSink : ILogSink
@@ -23,11 +24,3 @@ internal static class CoreLogBridge
internal static void Install() => Log.Sink = new SimHubLogSink();
}
}
-
-// The C# 9 module-initializer feature is compiler-only; net48 just lacks the
-// attribute type. Declaring it internally is the standard polyfill.
-namespace System.Runtime.CompilerServices
-{
- [AttributeUsage(AttributeTargets.Method, Inherited = false)]
- internal sealed class ModuleInitializerAttribute : Attribute { }
-}
diff --git a/src/FanaBridge/Properties/ModuleInitializerAttribute.cs b/src/FanaBridge/Properties/ModuleInitializerAttribute.cs
new file mode 100644
index 00000000..55fa1e36
--- /dev/null
+++ b/src/FanaBridge/Properties/ModuleInitializerAttribute.cs
@@ -0,0 +1,10 @@
+// net48 polyfill for the C# 9 module-initializer attribute (compiler-only; type missing on TFM).
+// Exception to directory/namespace literalness: System.Runtime.CompilerServices by definition.
+#pragma warning disable IDE0130 // Polyfill attribute must live in System.Runtime.CompilerServices by definition
+using System;
+
+namespace System.Runtime.CompilerServices
+{
+ [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+ internal sealed class ModuleInitializerAttribute : Attribute { }
+}
diff --git a/src/FanaBridge/Adapters/FanatecDeviceSettings.cs b/src/FanaBridge/Settings/FanatecDeviceSettings.cs
similarity index 98%
rename from src/FanaBridge/Adapters/FanatecDeviceSettings.cs
rename to src/FanaBridge/Settings/FanatecDeviceSettings.cs
index 89a1f477..c2d315c5 100644
--- a/src/FanaBridge/Adapters/FanatecDeviceSettings.cs
+++ b/src/FanaBridge/Settings/FanatecDeviceSettings.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using FanaBridge.Profiles;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Display;
+using FanaBridge.Leds;
using Newtonsoft.Json.Linq;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Settings
{
///
/// Owns everything a device persists: FanaBridge's own settings, the LED
diff --git a/src/FanaBridge/FanatecPluginSettings.cs b/src/FanaBridge/Settings/FanatecPluginSettings.cs
similarity index 96%
rename from src/FanaBridge/FanatecPluginSettings.cs
rename to src/FanaBridge/Settings/FanatecPluginSettings.cs
index 8c64ae6f..f6d4f339 100644
--- a/src/FanaBridge/FanatecPluginSettings.cs
+++ b/src/FanaBridge/Settings/FanatecPluginSettings.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace FanaBridge
+namespace FanaBridge.Settings
{
///
/// Plugin settings. Must be JSON-serializable (public fields/properties, no complex types).
@@ -49,7 +49,7 @@ public class FanatecPluginSettings
/// (surfaced in the settings UI when it isn't). Gap-filler: SimHub wins for any
/// wheel it already recognizes, so existing mappings are never disturbed, and it's
/// a no-op unless the user has opted into per-rim mapping at the SimHub level. See
- /// .
+ /// .
///
public bool EnableControlMapperIntegration { get; set; } = true;
diff --git a/src/FanaBridge/Adapters/FanatecSettingsSnapshot.cs b/src/FanaBridge/Settings/FanatecSettingsSnapshot.cs
similarity index 98%
rename from src/FanaBridge/Adapters/FanatecSettingsSnapshot.cs
rename to src/FanaBridge/Settings/FanatecSettingsSnapshot.cs
index ca537f64..54f919ba 100644
--- a/src/FanaBridge/Adapters/FanatecSettingsSnapshot.cs
+++ b/src/FanaBridge/Settings/FanatecSettingsSnapshot.cs
@@ -1,6 +1,7 @@
-using Newtonsoft.Json.Linq;
+using FanaBridge.Display;
+using Newtonsoft.Json.Linq;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Settings
{
///
/// The FanaBridge-owned settings of one device, as an immutable value.
diff --git a/src/FanaBridge/Adapters/PersistedPluginSettings.cs b/src/FanaBridge/Settings/PersistedPluginSettings.cs
similarity index 99%
rename from src/FanaBridge/Adapters/PersistedPluginSettings.cs
rename to src/FanaBridge/Settings/PersistedPluginSettings.cs
index e272ff3a..3e250e7b 100644
--- a/src/FanaBridge/Adapters/PersistedPluginSettings.cs
+++ b/src/FanaBridge/Settings/PersistedPluginSettings.cs
@@ -3,7 +3,7 @@
using System.IO;
using Newtonsoft.Json.Linq;
-namespace FanaBridge.Adapters
+namespace FanaBridge.Settings
{
///
/// Reads the plugin's persisted settings without a running plugin.
diff --git a/src/FanaBridge/UI/DevicePanelFactory.cs b/src/FanaBridge/UI/Devices/DevicePanelFactory.cs
similarity index 83%
rename from src/FanaBridge/UI/DevicePanelFactory.cs
rename to src/FanaBridge/UI/Devices/DevicePanelFactory.cs
index 64e906b6..4a3da7d7 100644
--- a/src/FanaBridge/UI/DevicePanelFactory.cs
+++ b/src/FanaBridge/UI/Devices/DevicePanelFactory.cs
@@ -1,9 +1,11 @@
using System;
using System.Windows.Controls;
-using FanaBridge.Adapters;
-using FanaBridge.Profiles;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Display;
+using FanaBridge.Settings;
+using FanaBridge.UI.Settings;
-namespace FanaBridge.UI
+namespace FanaBridge.UI.Devices
{
/// UI-side implementation of the device settings-panel factory.
internal sealed class DevicePanelFactory : IDevicePanelFactory
diff --git a/src/FanaBridge/Adapters/IDevicePanelFactory.cs b/src/FanaBridge/UI/Devices/IDevicePanelFactory.cs
similarity index 88%
rename from src/FanaBridge/Adapters/IDevicePanelFactory.cs
rename to src/FanaBridge/UI/Devices/IDevicePanelFactory.cs
index 14285667..d2a58a3b 100644
--- a/src/FanaBridge/Adapters/IDevicePanelFactory.cs
+++ b/src/FanaBridge/UI/Devices/IDevicePanelFactory.cs
@@ -1,8 +1,10 @@
using System;
using System.Windows.Controls;
-using FanaBridge.Profiles;
+using FanaBridge.Core.Devices.Profiles;
+using FanaBridge.Display;
+using FanaBridge.Settings;
-namespace FanaBridge.Adapters
+namespace FanaBridge.UI.Devices
{
///
/// Constructs the per-device WPF settings panels.
diff --git a/src/FanaBridge/UI/LedColorLimitationNotice.xaml b/src/FanaBridge/UI/Devices/LedColorLimitationNotice.xaml
similarity index 95%
rename from src/FanaBridge/UI/LedColorLimitationNotice.xaml
rename to src/FanaBridge/UI/Devices/LedColorLimitationNotice.xaml
index ebbdcdec..150df6d3 100644
--- a/src/FanaBridge/UI/LedColorLimitationNotice.xaml
+++ b/src/FanaBridge/UI/Devices/LedColorLimitationNotice.xaml
@@ -1,4 +1,4 @@
-