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 <noreply@anthropic.com>
This commit is contained in:
@@ -261,9 +261,15 @@ public sealed class ProfileEditorForm : Form
|
|||||||
_valueCombo.Items.Add(new ValueItem(k.Name, k.Value));
|
_valueCombo.Items.Add(new ValueItem(k.Name, k.Value));
|
||||||
break;
|
break;
|
||||||
case RioRouteKind.Joystick:
|
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.
|
// The signed ViGEmBus Xbox 360 pad exposes 11 buttons; show their names.
|
||||||
for (int btn = 1; btn <= ViGEmJoystickSink.MappableButtonCount; btn++)
|
for (int btn = 1; btn <= ViGEmJoystickSink.MappableButtonCount; btn++)
|
||||||
_valueCombo.Items.Add(new ValueItem($"Button {btn} ({ViGEmJoystickSink.ButtonNames[btn - 1]})", (byte)btn));
|
_valueCombo.Items.Add(new ValueItem($"Button {btn} ({ViGEmJoystickSink.ButtonNames[btn - 1]})", (byte)btn));
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case RioRouteKind.Hat:
|
case RioRouteKind.Hat:
|
||||||
foreach (RioHat h in new[] { RioHat.Up, RioHat.Right, RioHat.Down, RioHat.Left })
|
foreach (RioHat h in new[] { RioHat.Up, RioHat.Right, RioHat.Down, RioHat.Left })
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ using RioJoy.Core.Output;
|
|||||||
using RioJoy.Core.Overlay;
|
using RioJoy.Core.Overlay;
|
||||||
using RioJoy.Core.Profiles;
|
using RioJoy.Core.Profiles;
|
||||||
using RioJoy.Core.Serial;
|
using RioJoy.Core.Serial;
|
||||||
using RioJoy.Overlay;
|
#if !NET40
|
||||||
|
using RioJoy.Overlay; // SkiaSharp wallpaper generation — modern flavor only
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace RioJoy.Tray;
|
namespace RioJoy.Tray;
|
||||||
|
|
||||||
@@ -149,12 +151,14 @@ public sealed class RioCoordinator : IDisposable
|
|||||||
// feeder, then a no-op when neither driver is present.
|
// feeder, then a no-op when neither driver is present.
|
||||||
private IJoystickSink CreateJoystickSink(out string note)
|
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))
|
if (ViGEmJoystickSink.TryCreate(out ViGEmJoystickSink? vigem))
|
||||||
{
|
{
|
||||||
_joystick = vigem;
|
_joystick = vigem;
|
||||||
note = string.Empty;
|
note = string.Empty;
|
||||||
return vigem!;
|
return vigem!;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (HidFeederJoystickSink.TryCreate(out HidFeederJoystickSink? feeder))
|
if (HidFeederJoystickSink.TryCreate(out HidFeederJoystickSink? feeder))
|
||||||
{
|
{
|
||||||
@@ -241,6 +245,20 @@ public sealed class RioCoordinator : IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ApplyWallpaper(RioProfile profile)
|
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;
|
string? templatePath = _config().OverlayTemplatePath;
|
||||||
if (string.IsNullOrWhiteSpace(templatePath) || !File.Exists(templatePath))
|
if (string.IsNullOrWhiteSpace(templatePath) || !File.Exists(templatePath))
|
||||||
return;
|
return;
|
||||||
@@ -263,6 +281,7 @@ public sealed class RioCoordinator : IDisposable
|
|||||||
{
|
{
|
||||||
SetStatus($"{Status} [wallpaper: {ex.Message}]");
|
SetStatus($"{Status} [wallpaper: {ex.Message}]");
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string SafeFileName(string name)
|
private static string SafeFileName(string name)
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net48</TargetFramework>
|
<!-- net48 = Windows 10/11 (x64, full app incl. wallpaper maker/SkiaSharp).
|
||||||
|
net40 = Windows XP SP3 (x86): full runtime + mapping editor; wallpaper
|
||||||
|
GENERATION is excluded (SkiaSharp has no XP support) — XP applies
|
||||||
|
pre-rendered wallpapers only. See docs/PLAN.md §Phase 8. -->
|
||||||
|
<TargetFrameworks>net48;net40</TargetFrameworks>
|
||||||
<Platforms>x64</Platforms>
|
<Platforms>x64</Platforms>
|
||||||
<PlatformTarget>x64</PlatformTarget>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@@ -12,12 +15,29 @@
|
|||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net48'">
|
||||||
<ProjectReference Include="..\RioJoy.Core\RioJoy.Core.csproj" />
|
<PlatformTarget>x64</PlatformTarget>
|
||||||
<ProjectReference Include="..\RioJoy.Overlay\RioJoy.Overlay.csproj" />
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||||
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net40'">
|
||||||
|
<!-- net40 has no System.Net.Http assembly for the implicit global using. -->
|
||||||
|
<Using Remove="System.Net.Http" />
|
||||||
|
<!-- Wallpaper maker needs the SkiaSharp renderer (RioJoy.Overlay) — modern only. -->
|
||||||
|
<Compile Remove="Editor\WallpaperMakerForm.cs" />
|
||||||
|
<Compile Remove="Editor\WallpaperCanvas.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\RioJoy.Core\RioJoy.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\RioJoy.Overlay\RioJoy.Overlay.csproj" Condition="'$(TargetFramework)' == 'net48'" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="all" />
|
||||||
<PackageReference Include="PolySharp" Version="1.14.1">
|
<PackageReference Include="PolySharp" Version="1.14.1">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@@ -74,10 +74,12 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
|||||||
RebuildEditMenu(editMenu);
|
RebuildEditMenu(editMenu);
|
||||||
menu.Items.Add(editMenu);
|
menu.Items.Add(editMenu);
|
||||||
|
|
||||||
|
#if !NET40 // wallpaper maker needs SkiaSharp — modern flavor only (XP applies pre-rendered)
|
||||||
var wallpaperMenu = new ToolStripMenuItem("Wallpaper maker");
|
var wallpaperMenu = new ToolStripMenuItem("Wallpaper maker");
|
||||||
wallpaperMenu.DropDownOpening += (_, _) => RebuildWallpaperMenu(wallpaperMenu);
|
wallpaperMenu.DropDownOpening += (_, _) => RebuildWallpaperMenu(wallpaperMenu);
|
||||||
RebuildWallpaperMenu(wallpaperMenu);
|
RebuildWallpaperMenu(wallpaperMenu);
|
||||||
menu.Items.Add(wallpaperMenu);
|
menu.Items.Add(wallpaperMenu);
|
||||||
|
#endif
|
||||||
|
|
||||||
menu.Items.Add("Import .ini…", null, (_, _) => ImportIniProfiles());
|
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())));
|
editMenu.DropDownItems.Add(new ToolStripMenuItem("New profile…", null, (_, _) => OpenEditor(NewProfile())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !NET40
|
||||||
// Rebuild the "Wallpaper maker" submenu: one entry per profile.
|
// Rebuild the "Wallpaper maker" submenu: one entry per profile.
|
||||||
private void RebuildWallpaperMenu(ToolStripMenuItem wallpaperMenu)
|
private void RebuildWallpaperMenu(ToolStripMenuItem wallpaperMenu)
|
||||||
{
|
{
|
||||||
@@ -205,6 +208,7 @@ internal sealed class TrayApplicationContext : ApplicationContext
|
|||||||
"RIOJoy", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
"RIOJoy", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Create, register and persist a fresh empty profile with a unique name.
|
// Create, register and persist a fresh empty profile with a unique name.
|
||||||
private RioProfile NewProfile()
|
private RioProfile NewProfile()
|
||||||
|
|||||||
@@ -30,6 +30,22 @@ public static class WallpaperApplier
|
|||||||
if (!File.Exists(full))
|
if (!File.Exists(full))
|
||||||
throw new FileNotFoundException("Wallpaper image not found.", 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(
|
return SystemParametersInfo(
|
||||||
SPI_SETDESKWALLPAPER, 0, full, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
|
SPI_SETDESKWALLPAPER, 0, full, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user