diff --git a/ControlApp/Views/Pages/DevicesPage.xaml b/ControlApp/Views/Pages/DevicesPage.xaml index b020bc4e2..4b07f9c9d 100644 --- a/ControlApp/Views/Pages/DevicesPage.xaml +++ b/ControlApp/Views/Pages/DevicesPage.xaml @@ -1,4 +1,4 @@ - + NavigateUri="https://docs.nefarius.at/projects/DsHidMini/v3/How-to-Install/#troubleshooting"> diff --git a/README.md b/README.md index 31bfe3a79..b49928b18 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ Virtual HID Mini user-mode driver for Sony DualShock 3 controllers on Windows 10/11. -## Version 3 (Beta) +## Version 3 (Stable) -The next major version is [available for Beta-testing](https://github.com/nefarius/DsHidMini/releases). Highlights: new installer and configuration app, **ARM64** and **Windows 11** support, LED/dead-zone/rumble customization, Xbox One emulation, and more. Follow progress on [Discord](https://discord.nefarius.at/) or [Mastodon](https://fosstodon.org/@Nefarius). +Version 3 is the current stable release. Get the latest build from [releases](https://github.com/nefarius/DsHidMini/releases). Highlights: new installer and configuration app, **ARM64** and **Windows 11** support, LED/dead-zone/rumble customization, Xbox One emulation, and more. Support and updates on [Discord](https://discord.nefarius.at/) or [Mastodon](https://fosstodon.org/@Nefarius). ## Repository activity @@ -69,7 +69,7 @@ This solution contains **BSD-3-Clause** and other licensed components; see the i ## Installation -Pre-built binaries and instructions: [releases](https://github.com/nefarius/DsHidMini/releases). Installation steps: [How to Install](https://docs.nefarius.at/projects/DsHidMini/v2/How-to-Install/). +Pre-built binaries and instructions: [releases](https://github.com/nefarius/DsHidMini/releases). Installation steps: [How to Install](https://docs.nefarius.at/projects/DsHidMini/v3/How-to-Install/). ## Support diff --git a/setup/Dialogs/BetaArticleDialog.xaml.cs b/setup/Dialogs/BetaArticleDialog.xaml.cs deleted file mode 100644 index 6485b62af..000000000 --- a/setup/Dialogs/BetaArticleDialog.xaml.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; - -using WixSharp; -using WixSharp.UI.Forms; -using WixSharp.UI.WPF; - -using IO = System.IO; - -namespace Nefarius.DsHidMini.Setup.Dialogs; - -/// -/// The standard LicenceDialog. -/// -/// -/// -/// -public partial class BetaArticleDialog : WpfDialog, IWpfDialog -{ - /// - /// Initializes a new instance of the class. - /// - public BetaArticleDialog() - { - InitializeComponent(); - } - - /// - /// This method is invoked by WixSHarp runtime when the custom dialog content is internally fully initialized. - /// This is a convenient place to do further initialization activities (e.g. localization). - /// - public void Init() - { - DataContext = _model = new BetaArticleDialogModel { Host = ManagedFormHost }; - } - - private BetaArticleDialogModel _model; - - private void GoNext_Click(object sender, RoutedEventArgs e) - { - _model.GoNext(); - } - - private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - try - { - Process.Start(InstallScript.BetaArticleUrl.ToString()); - } - catch - { - // welp - } - } -} - -/// -/// ViewModel for standard LicenceDialog. -/// -internal class BetaArticleDialogModel : NotifyPropertyChangedBase -{ - private ManagedForm _host; - private ISession Session => Host?.Runtime.Session; - private IManagedUIShell Shell => Host?.Shell; - - public ManagedForm Host - { - get => _host; - set - { - _host = value; - - NotifyOfPropertyChange(nameof(Banner)); - NotifyOfPropertyChange(nameof(CanGoNext)); - } - } - - public BitmapImage Banner => Session?.GetResourceBitmap("WixSharpUI_Bmp_Banner").ToImageSource() ?? - Session?.GetResourceBitmap("WixUI_Bmp_Banner").ToImageSource(); - - - public bool CanGoNext - => true; - - public void GoNext() - { - Shell?.GoNext(); - } -} \ No newline at end of file diff --git a/setup/Dialogs/BetaArticleDialog.xaml b/setup/Dialogs/OnlineDocumentationDialog.xaml similarity index 92% rename from setup/Dialogs/BetaArticleDialog.xaml rename to setup/Dialogs/OnlineDocumentationDialog.xaml index f3ab765cf..4ec6dfcea 100644 --- a/setup/Dialogs/BetaArticleDialog.xaml +++ b/setup/Dialogs/OnlineDocumentationDialog.xaml @@ -1,5 +1,5 @@ - A web article should have opened in your default Browser by now + A web page should have opened in your default browser by now. @@ -57,8 +57,8 @@ - - Open Beta Article + + Open documentation @@ -100,4 +100,4 @@ - \ No newline at end of file + diff --git a/setup/Dialogs/OnlineDocumentationDialog.xaml.cs b/setup/Dialogs/OnlineDocumentationDialog.xaml.cs new file mode 100644 index 000000000..c279ed457 --- /dev/null +++ b/setup/Dialogs/OnlineDocumentationDialog.xaml.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; + +using WixSharp; +using WixSharp.UI.Forms; +using WixSharp.UI.WPF; + +namespace Nefarius.DsHidMini.Setup.Dialogs; + +public partial class OnlineDocumentationDialog : WpfDialog, IWpfDialog +{ + private OnlineDocumentationDialogModel _model; + + /// + /// Initializes a new instance of OnlineDocumentationDialog and initializes its WPF UI components. + /// + public OnlineDocumentationDialog() + { + InitializeComponent(); + } + + /// + /// Initializes the dialog's view model and sets it as the dialog's DataContext. + /// + /// + /// Creates a new , assigns to its Host property, stores it in the backing field, and sets DataContext to the created model. + /// + public void Init() + { + DataContext = _model = new OnlineDocumentationDialogModel { Host = ManagedFormHost }; + } + + /// + /// Handles the Next button click and advances the installer to the next dialog. + /// + /// The control that raised the click event. + /// Event data for the routed event. + private void GoNext_Click(object sender, RoutedEventArgs e) + { + _model.GoNext(); + } + + /// + /// Attempts to open the application's online documentation URL in the user's default web browser. + /// + /// + /// Any exception thrown while launching the browser is caught and ignored so setup does not fail if the browser cannot be started. + /// + private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + try + { + Process.Start(InstallScript.OnlineDocumentationUrl.ToString()); + } + catch + { + // Not failing setup because a browser couldn't be launched + } + } +} + +internal class OnlineDocumentationDialogModel : NotifyPropertyChangedBase +{ + private ManagedForm _host; + private ISession Session => Host?.Runtime.Session; + private IManagedUIShell Shell => Host?.Shell; + + public ManagedForm Host + { + get => _host; + set + { + _host = value; + + NotifyOfPropertyChange(nameof(Banner)); + NotifyOfPropertyChange(nameof(CanGoNext)); + } + } + + public BitmapImage Banner => Session?.GetResourceBitmap("WixSharpUI_Bmp_Banner").ToImageSource() ?? + Session?.GetResourceBitmap("WixUI_Bmp_Banner").ToImageSource(); + + public bool CanGoNext => true; + + /// + /// Advances the installer to the next step in the dialog sequence. + /// + /// + /// If a hosting shell is available, requests it to navigate to the next page; otherwise does nothing. + /// + public void GoNext() + { + Shell?.GoNext(); + } +} diff --git a/setup/InstallScript.cs b/setup/InstallScript.cs index d8e1819a1..090a1f9d9 100644 --- a/setup/InstallScript.cs +++ b/setup/InstallScript.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable using System; using System.Buffers; using System.Collections.Generic; @@ -36,8 +36,14 @@ internal class InstallScript { public const string ProductName = "Nefarius DsHidMini Driver"; - public static Uri BetaArticleUrl = new("https://docs.nefarius.at/projects/DsHidMini/Experimental/Version-3-Beta/"); + public static Uri OnlineDocumentationUrl = new("https://docs.nefarius.at/projects/DsHidMini/v3/How-to-Install/"); + /// + /// Builds and emits the MSI installer for the Nefarius DsHidMini drivers and packaged artifacts. + /// + /// + /// Reads setup, driver, and filter versions from build variables and artifact file metadata; defines installer features and package contents; configures managed actions, custom actions, registry writes, UI dialogs, embedded reference assemblies, and control panel metadata; hooks post-install handling; and finally generates the MSI file. + /// private static void Main() { // grab main app version @@ -89,8 +95,16 @@ private static void Main() new Files(driversFeature, @"..\artifacts\drivers\*.*"), new Files(driversFeature, @"..\artifacts\igfilter\*.*") ), - new File(driversFeature, "nefarius_DsHidMini_Updater.exe") + new File(driversFeature, "nefarius_DsHidMini_Updater.exe"), + new File(driversFeature, @"..\artifacts\bin\ControlApp.exe", + new FileShortcut("DsHidMini Control App", + @"%ProgramMenu%\Nefarius Software Solutions\DsHidMini")) ), + // check for .NET 9 Desktop Runtime before any files are laid down + new ManagedAction(CustomActions.CheckDotNetRuntime, Return.check, + When.Before, + Step.LaunchConditions, + Condition.NOT_Installed), // install drivers new ElevatedManagedAction(CustomActions.InstallDrivers, Return.check, When.After, @@ -100,6 +114,11 @@ private static void Main() new Error("9000", "Driver installation succeeded but a reboot is required to be fully operational. " + "After the setup is finished, please reboot the system before using the software."), + // .NET 9 Desktop Runtime missing + new Error("9001", + "The .NET 9 Desktop Runtime (x64) is required by DsHidMini Control App. " + + "Please download and install it from https://dotnet.microsoft.com/download/dotnet/9.0 " + + "and then re-run this installer."), // install BthPS3 new ManagedAction(CustomActions.InstallBthPS3, Return.check, When.After, @@ -110,8 +129,8 @@ private static void Main() When.After, Step.InstallFinalize, Condition.NOT_Installed), - // open beta article - new ManagedAction(CustomActions.OpenBetaArticle, Return.check, + // open online documentation + new ManagedAction(CustomActions.OpenOnlineDocumentation, Return.check, When.After, Step.InstallFinalize, Condition.NOT_Installed), @@ -152,7 +171,7 @@ private static void Main() .Add() .Add() .Add() - .Add() + .Add() .Add(); project.ManagedUI.ModifyDialogs.Add() @@ -205,6 +224,60 @@ private static void ProjectOnAfterInstall(SetupEventArgs e) public static class CustomActions { + /// + /// Verifies that the .NET 9 Desktop Runtime (x64) is installed before setup proceeds. + /// Aborts the install with a user-facing message when the runtime is absent. + /// + /// + /// Detection uses the filesystem rather than the registry: modern .NET installers + /// do not reliably populate HKLM\SOFTWARE\dotnet\Setup\InstalledVersions, but they + /// always create a versioned subdirectory under + /// %ProgramFiles%\dotnet\shared\Microsoft.WindowsDesktop.App. + /// + [CustomAction] + public static ActionResult CheckDotNetRuntime(Session session) + { + string runtimeDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), + "dotnet", "shared", "Microsoft.WindowsDesktop.App"); + + session.Log($"Probing .NET Desktop Runtime directory: {runtimeDir}"); + + try + { + if (Directory.Exists(runtimeDir)) + { + foreach (string versionDir in Directory.GetDirectories(runtimeDir)) + { + string dirName = Path.GetFileName(versionDir); + // strip pre-release suffix (e.g. "9.0.0-preview.3") before parsing + int dashIndex = dirName.IndexOf('-'); + string numericPart = dashIndex >= 0 ? dirName.Substring(0, dashIndex) : dirName; + if (Version.TryParse(numericPart, out Version? installedVersion) && + installedVersion.Major >= 9) + { + session.Log($".NET Desktop Runtime {installedVersion} found - prerequisite satisfied."); + return ActionResult.Success; + } + } + } + } + catch (Exception ex) + { + session.Log($"Failed to probe .NET 9 Desktop Runtime directory: {ex}"); + } + + session.Log(".NET 9 Desktop Runtime not found, aborting installation."); + + Record record = new(1); + record[1] = "9001"; + session.Message( + InstallMessage.User | (InstallMessage)MessageButtons.OK | (InstallMessage)MessageIcon.Error, + record); + + return ActionResult.Failure; + } + /// /// Put install logic here. /// @@ -294,7 +367,11 @@ public static ActionResult InstallDrivers(Session session) /// /// Download and install BthPS3. + /// + /// Downloads metadata for the latest BthPS3 update and opens its download URL when the BthPS3 feature is enabled. /// + /// The MSI session used to check whether the BthPS3 feature is enabled and to record logs. + /// `ActionResult.Success` on completion. [CustomAction] [SuppressMessage("ReSharper", "InconsistentNaming")] public static ActionResult InstallBthPS3(Session session) @@ -339,19 +416,23 @@ public static ActionResult InstallBthPS3(Session session) } /// - /// Open beta article in default browser. + /// Open online documentation in default browser. + /// + /// Opens the product's online documentation URL in the user's default browser. /// + /// The current MSI session used for logging. + /// `ActionResult.Success` to indicate the custom action completed; if launching the URL fails the exception is logged and the action still returns `ActionResult.Success`. [CustomAction] - public static ActionResult OpenBetaArticle(Session session) + public static ActionResult OpenOnlineDocumentation(Session session) { try { - Process.Start(InstallScript.BetaArticleUrl.ToString()); + Process.Start(InstallScript.OnlineDocumentationUrl.ToString()); } catch (Exception ex) { session.Log( - $"Beta article launch failed, exception: {ex}"); + $"Online documentation launch failed, exception: {ex}"); } return ActionResult.Success; @@ -359,7 +440,11 @@ public static ActionResult OpenBetaArticle(Session session) /// /// Open donations page in default browser. + /// + /// Opens the donation web page in the user's default browser when the DonationFeature is enabled. /// + /// MSI session used to check feature state and to record failures to the installer log. + /// on completion. [CustomAction] public static ActionResult OpenDonationPage(Session session) { @@ -375,7 +460,7 @@ public static ActionResult OpenDonationPage(Session session) catch (Exception ex) { session.Log( - $"Beta article launch failed, exception: {ex}"); + $"Donation page launch failed, exception: {ex}"); } return ActionResult.Success;