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);
}