From 63af7bde01e32b6784e31ccd929d161db88faaa3 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 11 Jul 2026 20:45:34 -0500 Subject: [PATCH] Phase 8C: RioJoy.Tray multi-targets net48 (x64) + net40 (x86, Windows XP) The XP flavor keeps the full runtime AND the mapping editor (decided: editor everywhere). Gated per flavor: - Wallpaper maker + WallpaperCanvas + generation (SkiaSharp) are net48-only; the XP coordinator applies the profile's pre-rendered wallpaper instead, and WallpaperApplier converts PNG->BMP on net40 (XP SPI_SETDESKWALLPAPER accepts only BMP). - Joystick chain: ViGEm branch compiled out on net40 (HID feeder stays, ready for RioGamepadXP.sys); the editor button picker offers the full 96 HID buttons on net40 vs the 11 named Xbox buttons on net48. Verified: net40/x86 exe boots on Win10; the editor renders offline in a 32-bit host with a Button-50 joystick binding resolving correctly and live gauges working (screenshot harness). Co-Authored-By: Claude Fable 5 --- src/RioJoy.Tray/Editor/ProfileEditorForm.cs | 6 +++++ src/RioJoy.Tray/RioCoordinator.cs | 21 ++++++++++++++- src/RioJoy.Tray/RioJoy.Tray.csproj | 30 +++++++++++++++++---- src/RioJoy.Tray/TrayApplicationContext.cs | 4 +++ src/RioJoy.Tray/WallpaperApplier.cs | 16 +++++++++++ 5 files changed, 71 insertions(+), 6 deletions(-) diff --git a/src/RioJoy.Tray/Editor/ProfileEditorForm.cs b/src/RioJoy.Tray/Editor/ProfileEditorForm.cs index 25d185a..4d2c379 100644 --- a/src/RioJoy.Tray/Editor/ProfileEditorForm.cs +++ b/src/RioJoy.Tray/Editor/ProfileEditorForm.cs @@ -261,9 +261,15 @@ public sealed class ProfileEditorForm : Form _valueCombo.Items.Add(new ValueItem(k.Name, k.Value)); break; case RioRouteKind.Joystick: +#if NET40 + // XP: RioGamepadXP exposes the full 96-button HID layout. + for (int btn = 1; btn <= RioJoy.Core.Hid.RioHidReport.ButtonCount; btn++) + _valueCombo.Items.Add(new ValueItem($"Button {btn}", (byte)btn)); +#else // The signed ViGEmBus Xbox 360 pad exposes 11 buttons; show their names. for (int btn = 1; btn <= ViGEmJoystickSink.MappableButtonCount; btn++) _valueCombo.Items.Add(new ValueItem($"Button {btn} ({ViGEmJoystickSink.ButtonNames[btn - 1]})", (byte)btn)); +#endif break; case RioRouteKind.Hat: foreach (RioHat h in new[] { RioHat.Up, RioHat.Right, RioHat.Down, RioHat.Left }) diff --git a/src/RioJoy.Tray/RioCoordinator.cs b/src/RioJoy.Tray/RioCoordinator.cs index 035a0ce..f005f57 100644 --- a/src/RioJoy.Tray/RioCoordinator.cs +++ b/src/RioJoy.Tray/RioCoordinator.cs @@ -5,7 +5,9 @@ using RioJoy.Core.Output; using RioJoy.Core.Overlay; using RioJoy.Core.Profiles; using RioJoy.Core.Serial; -using RioJoy.Overlay; +#if !NET40 +using RioJoy.Overlay; // SkiaSharp wallpaper generation — modern flavor only +#endif namespace RioJoy.Tray; @@ -149,12 +151,14 @@ public sealed class RioCoordinator : IDisposable // feeder, then a no-op when neither driver is present. private IJoystickSink CreateJoystickSink(out string note) { +#if !NET40 // ViGEmBus is Win10+; on XP the HID feeder drives RioGamepadXP.sys if (ViGEmJoystickSink.TryCreate(out ViGEmJoystickSink? vigem)) { _joystick = vigem; note = string.Empty; return vigem!; } +#endif if (HidFeederJoystickSink.TryCreate(out HidFeederJoystickSink? feeder)) { @@ -241,6 +245,20 @@ public sealed class RioCoordinator : IDisposable /// private void ApplyWallpaper(RioProfile profile) { +#if NET40 + // XP flavor: no SkiaSharp, so no generation — apply the profile's + // pre-rendered wallpaper (authored on a modern machine) if it exists. + if (string.IsNullOrWhiteSpace(profile.WallpaperPath) || !File.Exists(profile.WallpaperPath)) + return; + try + { + WallpaperApplier.Apply(profile.WallpaperPath!); + } + catch (Exception ex) + { + SetStatus($"{Status} [wallpaper: {ex.Message}]"); + } +#else string? templatePath = _config().OverlayTemplatePath; if (string.IsNullOrWhiteSpace(templatePath) || !File.Exists(templatePath)) return; @@ -263,6 +281,7 @@ public sealed class RioCoordinator : IDisposable { SetStatus($"{Status} [wallpaper: {ex.Message}]"); } +#endif } private static string SafeFileName(string name) diff --git a/src/RioJoy.Tray/RioJoy.Tray.csproj b/src/RioJoy.Tray/RioJoy.Tray.csproj index 4bcbb12..b19dce7 100644 --- a/src/RioJoy.Tray/RioJoy.Tray.csproj +++ b/src/RioJoy.Tray/RioJoy.Tray.csproj @@ -2,9 +2,12 @@ WinExe - net48 + + net48;net40 x64 - x64 enable true enable @@ -12,12 +15,29 @@ app.manifest - - - + + x64 + + + + x86 + + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/RioJoy.Tray/TrayApplicationContext.cs b/src/RioJoy.Tray/TrayApplicationContext.cs index 9eae12f..deca113 100644 --- a/src/RioJoy.Tray/TrayApplicationContext.cs +++ b/src/RioJoy.Tray/TrayApplicationContext.cs @@ -74,10 +74,12 @@ internal sealed class TrayApplicationContext : ApplicationContext RebuildEditMenu(editMenu); menu.Items.Add(editMenu); +#if !NET40 // wallpaper maker needs SkiaSharp — modern flavor only (XP applies pre-rendered) var wallpaperMenu = new ToolStripMenuItem("Wallpaper maker"); wallpaperMenu.DropDownOpening += (_, _) => RebuildWallpaperMenu(wallpaperMenu); RebuildWallpaperMenu(wallpaperMenu); menu.Items.Add(wallpaperMenu); +#endif menu.Items.Add("Import .ini…", null, (_, _) => ImportIniProfiles()); @@ -155,6 +157,7 @@ internal sealed class TrayApplicationContext : ApplicationContext editMenu.DropDownItems.Add(new ToolStripMenuItem("New profile…", null, (_, _) => OpenEditor(NewProfile()))); } +#if !NET40 // Rebuild the "Wallpaper maker" submenu: one entry per profile. private void RebuildWallpaperMenu(ToolStripMenuItem wallpaperMenu) { @@ -205,6 +208,7 @@ internal sealed class TrayApplicationContext : ApplicationContext "RIOJoy", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } +#endif // Create, register and persist a fresh empty profile with a unique name. private RioProfile NewProfile() diff --git a/src/RioJoy.Tray/WallpaperApplier.cs b/src/RioJoy.Tray/WallpaperApplier.cs index 9370f48..29d995e 100644 --- a/src/RioJoy.Tray/WallpaperApplier.cs +++ b/src/RioJoy.Tray/WallpaperApplier.cs @@ -30,6 +30,22 @@ public static class WallpaperApplier if (!File.Exists(full)) throw new FileNotFoundException("Wallpaper image not found.", full); +#if NET40 + // Windows XP's SPI_SETDESKWALLPAPER accepts only BMP. Wallpapers are + // authored as PNG on a modern machine, so convert beside the source on + // apply (idempotent: reuses an up-to-date .bmp). + if (!full.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase)) + { + string bmp = Path.ChangeExtension(full, ".bmp"); + if (!File.Exists(bmp) || File.GetLastWriteTimeUtc(bmp) < File.GetLastWriteTimeUtc(full)) + { + using var image = System.Drawing.Image.FromFile(full); + image.Save(bmp, System.Drawing.Imaging.ImageFormat.Bmp); + } + full = bmp; + } +#endif + return SystemParametersInfo( SPI_SETDESKWALLPAPER, 0, full, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE); }