diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index c19c277b4..08c66f56b 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -74,6 +74,7 @@ "BuildCSharpSamples", "BuildInDevelopmentServicePlugins", "BuildInDevelopmentServicePluginsInstaller", + "BuildPowerShellProjection", "BuildServiceAndPlugins", "BuildServiceAndPluginsInstaller", "BuildSettingsApp", @@ -101,6 +102,7 @@ "BuildCSharpSamples", "BuildInDevelopmentServicePlugins", "BuildInDevelopmentServicePluginsInstaller", + "BuildPowerShellProjection", "BuildServiceAndPlugins", "BuildServiceAndPluginsInstaller", "BuildSettingsApp", diff --git a/build/nuke_build/Build.cs b/build/nuke_build/Build.cs index 78426a5ca..116ddc07b 100644 --- a/build/nuke_build/Build.cs +++ b/build/nuke_build/Build.cs @@ -104,6 +104,11 @@ class Build : NukeBuild AbsolutePath UserToolsRootFolder => SourceRootFolder / "user-tools"; + + AbsolutePath MidiPowerShellSolutionFolder => AppSdkSolutionFolder / "projections" / "powershell" ; + AbsolutePath MidiPowerShellStagingFolder => StagingRootFolder / "midi-powershell"; + + AbsolutePath MidiConsoleSolutionFolder => UserToolsRootFolder / "midi-console"; AbsolutePath MidiConsoleStagingFolder => StagingRootFolder / "midi-console"; AbsolutePath ConsoleSetupSolutionFolder => UserToolsRootFolder / "midi-console-setup"; @@ -126,6 +131,7 @@ class Build : NukeBuild AbsolutePath SamplesCSWinRTSolutionFolder => SamplesRootFolder / "csharp-net"; + MSBuildVerbosity BuildVerbosity => MSBuildVerbosity.Quiet; string[] SdkPlatforms => new string[] { "x64", "Arm64EC" }; string[] ServiceAndApiPlatforms => new string[] { "x64", "Arm64" }; @@ -255,7 +261,7 @@ class Build : NukeBuild /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) - .SetVerbosity(MSBuildVerbosity.Minimal) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -353,7 +359,7 @@ class Build : NukeBuild /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) - .SetVerbosity(MSBuildVerbosity.Minimal) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -415,7 +421,7 @@ class Build : NukeBuild /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) - //.SetVerbosity(MSBuildVerbosity.Minimal) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -515,7 +521,13 @@ class Build : NukeBuild Path.Combine(solutionDir, "mididiag"), Path.Combine(solutionDir, "midiusbinfo"), Path.Combine(solutionDir, "midimdnsinfo"), + Path.Combine(solutionDir, @"tests\InitializationExe"), + Path.Combine(solutionDir, @"tests\Benchmarks"), + Path.Combine(solutionDir, @"tests\Offline.unittests"), + Path.Combine(solutionDir, @"tests\SdkInitialization.unittests"), + Path.Combine(solutionDir, @"tests\Service.unittests"), /* Path.Combine(solutionDir, "midi1monitor"), */ + /* Path.Combine(solutionDir, "midiksinfo"), */ }; foreach (var projectFolder in toolsDirectoriesNeedingSdkPackageUpdates) @@ -557,7 +569,7 @@ class Build : NukeBuild /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) - //.SetVerbosity(MSBuildVerbosity.Minimal) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -587,7 +599,8 @@ class Build : NukeBuild .DependsOn(Prerequisites) .DependsOn(CreateVersionIncludes) .DependsOn(BuildConsoleApp) - //.DependsOn(BuildSettingsApp) + .DependsOn(BuildSettingsApp) + .DependsOn(BuildPowerShellProjection) .DependsOn(BuildAndPackAllAppSDKs) .DependsOn(BuildAppSDKToolsAndTests) .Executes(() => @@ -620,6 +633,7 @@ class Build : NukeBuild /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -679,6 +693,7 @@ void UpdateSetupBundleInfoIncludeFile(string platform) /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -725,6 +740,7 @@ void UpdateSetupBundleInfoIncludeFile(string platform) /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -1034,6 +1050,101 @@ void UpdateSetupBundleInfoIncludeFile(string platform) }); + Target BuildPowerShellProjection => _ => _ + .DependsOn(Prerequisites) + .DependsOn(BuildAndPackAllAppSDKs) + .Executes(() => + { + var solution = MidiPowerShellSolutionFolder / "midi-powershell.sln"; + + // for the MIDI nuget package + NuGetTasks.NuGetInstall(_ => _ + .SetProcessWorkingDirectory(MidiPowerShellSolutionFolder) + .SetPreRelease(true) + .SetSource(AppSdkNugetOutputFolder) + .SetPackageID(NugetPackageId) + .SetDependencyVersion(DependencyVersion.Highest) + ); + + NuGetTasks.NuGetRestore(_ => _ + .SetProcessWorkingDirectory(MidiPowerShellSolutionFolder) + .SetSolutionDirectory(MidiPowerShellSolutionFolder) + .SetSource(AppSdkNugetOutputFolder) + ); + + // build x64 and Arm64, no Arm64EC + foreach (var platform in ToolsPlatforms) + { + string solutionDir = MidiPowerShellSolutionFolder.ToString() + @"\"; + + string rid = platform.ToLower() == "arm64" ? "win-arm64" : "win-x64"; + + + //var msbuildProperties = new Dictionary(); + //msbuildProperties.Add("Platform", platform); + //msbuildProperties.Add("SolutionDir", solutionDir); // to include trailing slash + //msbuildProperties.Add("RuntimeIdentifier", rid); + ////msbuildProperties.Add("NoWarn", "MSB3271"); // winmd and dll platform mismatch with Arm64EC + + //Console.Out.WriteLine($"----------------------------------------------------------------------"); + //Console.Out.WriteLine($"Solution: {solution}"); + //Console.Out.WriteLine($"SolutionDir: {solutionDir}"); + //Console.Out.WriteLine($"Platform: {platform}"); + //Console.Out.WriteLine($"RID: {rid}"); + + + DotNetTasks.DotNetBuild(_ => _ + .SetProjectFile(MidiPowerShellSolutionFolder / "WindowsMidiServices.csproj") + .SetConfiguration(Configuration.Release) + .SetPublishSingleFile(false) + .SetPublishTrimmed(false) + .SetSelfContained(false) + .SetRuntime(rid) + ); + + // copy output to staging folder + + // TODO: This doesn't deal with any localization content + + var psOutputFolder = MidiPowerShellSolutionFolder / "bin" / Configuration.Release / "net8.0-windows10.0.20348.0" / rid; + //var runtimesFolder = consoleOutputFolder / "runtimes" / rid / "native"; + var runtimesFolder = psOutputFolder; + + var stagingFolder = MidiPowerShellStagingFolder / platform; + + FileSystemTasks.CopyFileToDirectory(MidiPowerShellSolutionFolder / "WindowsMidiServices.psd1", stagingFolder, FileExistsPolicy.Overwrite, true); + + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "WindowsMidiServices.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "WindowsMidiServices.deps.json", stagingFolder, FileExistsPolicy.Overwrite, true); + + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "WinRT.Runtime.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "Microsoft.Windows.Devices.Midi2.NetProjection.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "Microsoft.Windows.SDK.NET.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "Microsoft.ApplicationInsights.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "Microsoft.Win32.Registry.AccessControl.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "Newtonsoft.Json.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.CodeDom.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Configuration.ConfigurationManager.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Diagnostics.EventLog.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.DirectoryServices.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Management.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Security.Cryptography.Pkcs.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Security.Cryptography.ProtectedData.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Security.Permissions.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + FileSystemTasks.CopyFileToDirectory(psOutputFolder / "System.Windows.Extensions.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + + + // FileSystemTasks.CopyFileToDirectory(runtimesFolder / "Microsoft.Windows.Devices.Midi2.Initialization.dll", stagingFolder, FileExistsPolicy.Overwrite, true); + // FileSystemTasks.CopyFileToDirectory(runtimesFolder / "Microsoft.Windows.Devices.Midi2.Initialization.pri", stagingFolder, FileExistsPolicy.Overwrite, true); + //FileSystemTasks.CopyFileToDirectory(runtimesFolder / ns + ".winmd", stagingFolder, FileExistsPolicy.Overwrite, true); + + } + + }); + + void RestoreNuGetPackagesForCPPProject(string vcxprojFilePath, string solutionDir, string packagesConfigFullPath) { var projectDir = Path.GetDirectoryName(vcxprojFilePath); @@ -1225,7 +1336,7 @@ void UpdatePackagesConfigForCPPProject(string configFilePath) /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) - //.SetVerbosity(MSBuildVerbosity.Minimal) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); @@ -1304,7 +1415,7 @@ void UpdatePackagesConfigForCPPProject(string configFilePath) /*.SetTargets("Build") */ .SetProperties(msbuildProperties) .SetConfiguration(Configuration.Release) - //.SetVerbosity(MSBuildVerbosity.Minimal) + .SetVerbosity(BuildVerbosity) .EnableNodeReuse() ); } diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index 3045d1731..b98b29c38 100644 --- a/build/staging/version/BundleInfo.wxi +++ b/build/staging/version/BundleInfo.wxi @@ -1,4 +1,4 @@ - + diff --git a/build/staging/version/WindowsMidiServicesVersion.cs b/build/staging/version/WindowsMidiServicesVersion.cs index 466ab5697..561f5f670 100644 --- a/build/staging/version/WindowsMidiServicesVersion.cs +++ b/build/staging/version/WindowsMidiServicesVersion.cs @@ -6,12 +6,12 @@ public static class MidiBuildInformation { public const string Source = "GitHub Preview"; public const string Name = "Customer Preview 2"; - public const string BuildFullVersion = "1.0.3-preview-11.250218-1526"; + public const string BuildFullVersion = "1.0.3-preview-11.250226-2321"; public const string VersionMajor = "1"; public const string VersionMinor = "0"; public const string VersionRevision = "3"; - public const string VersionDateNumber = "250218"; - public const string VersionTimeNumber = "1526"; + public const string VersionDateNumber = "250226"; + public const string VersionTimeNumber = "2321"; } } diff --git a/build/staging/version/WindowsMidiServicesVersion.h b/build/staging/version/WindowsMidiServicesVersion.h index 6f0eddc23..848ba9bcc 100644 --- a/build/staging/version/WindowsMidiServicesVersion.h +++ b/build/staging/version/WindowsMidiServicesVersion.h @@ -5,12 +5,12 @@ #define WINDOWS_MIDI_SERVICES_BUILD_SOURCE L"GitHub Preview" #define WINDOWS_MIDI_SERVICES_BUILD_VERSION_NAME L"Customer Preview 2" -#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.3-preview-11.250218-1526" +#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.3-preview-11.250226-2321" #define WINDOWS_MIDI_SERVICES_BUILD_VERSION_MAJOR L"1" #define WINDOWS_MIDI_SERVICES_BUILD_VERSION_MINOR L"0" #define WINDOWS_MIDI_SERVICES_BUILD_VERSION_REVISION L"3" -#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250218" -#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"1526" +#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250226" +#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"2321" #endif diff --git a/docs/other-developer-docs/consuming-midi-api.md b/docs/other-developer-docs/consuming-midi-api.md index d8b14d563..b413cba16 100644 --- a/docs/other-developer-docs/consuming-midi-api.md +++ b/docs/other-developer-docs/consuming-midi-api.md @@ -77,13 +77,11 @@ You will follow a similar approach to C++ using windows-rs instead of C++/WinRT. ## Consuming from C++ without Visual Studio (using cmake or other tools) -The C++/WinRT tool `cppwinrt.exe` will generate a set of standard C++ header files including `Microsoft.Windows.Devices.Midi2.h` which you can pull in and include in your project. The header file projections for WinRT types outside of `Microsoft::Windows::Devices::Midi2` are generated from the Windows SDK and included in a subfolder. When we ship Windows MIDI Services in-box in Windows, this new MIDI API will be projected in the same way as all the others in the Windows SDK. +The C++/WinRT tool `cppwinrt.exe` will generate a set of standard C++ header files including `Microsoft.Windows.Devices.Midi2.h` which you can pull in and include in your project. The header file projections for WinRT types outside of `Microsoft::Windows::Devices::Midi2` are generated from the Windows SDK and included in a subfolder. First, install the latest Windows SDK. You can get the SDK from the [Windows Dev Center](https://developer.microsoft.com/windows/downloads/windows-sdk/) -The SDK install includes the `cppwinrt.exe` tool. For the 10.0.22621.0 version of the SDK, it is found here on my PC: -`C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64` and -`C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\arm64` . Pick the version appropriate for your development PC architecture. +The SDK install includes a version of the `cppwinrt.exe` tool. However, the latest version of this tool is [available via NuGet](https://www.nuget.org/packages/Microsoft.Windows.CppWinRT/), and is preferable to the older versions shipping in the Windows SDK. You can end up with version mismatches if you use an older version. Normally, all SDK header files, on my PC with the 10.0.22621.0 version of the SDK installed, are located here `C:\Program Files (x86)\Windows Kits\10\Include\10.0.22621.0\cppwinrt\winrt` diff --git a/samples/cpp-winrt/basics/client-basics-cpp.vcxproj b/samples/cpp-winrt/basics/client-basics-cpp.vcxproj index 2847fd6d3..582880247 100644 --- a/samples/cpp-winrt/basics/client-basics-cpp.vcxproj +++ b/samples/cpp-winrt/basics/client-basics-cpp.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true false diff --git a/samples/cpp-winrt/basics/packages.config b/samples/cpp-winrt/basics/packages.config index 0f06cbf0e..471dae37d 100644 --- a/samples/cpp-winrt/basics/packages.config +++ b/samples/cpp-winrt/basics/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj b/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj index 0d13e97b1..fce9b470d 100644 --- a/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj +++ b/samples/cpp-winrt/loopback-endpoints/loopback-endpoints-cpp.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/samples/cpp-winrt/loopback-endpoints/packages.config b/samples/cpp-winrt/loopback-endpoints/packages.config index 0f06cbf0e..471dae37d 100644 --- a/samples/cpp-winrt/loopback-endpoints/packages.config +++ b/samples/cpp-winrt/loopback-endpoints/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/cpp-winrt/send-speed/packages.config b/samples/cpp-winrt/send-speed/packages.config index 0f06cbf0e..471dae37d 100644 --- a/samples/cpp-winrt/send-speed/packages.config +++ b/samples/cpp-winrt/send-speed/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj b/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj index 510e4fbea..05aaaa49b 100644 --- a/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj +++ b/samples/cpp-winrt/send-speed/send-speed-cpp.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/samples/cpp-winrt/simple-app-to-app-midi/main.cpp b/samples/cpp-winrt/simple-app-to-app-midi/main.cpp index abeb2a6aa..2fb5479ea 100644 --- a/samples/cpp-winrt/simple-app-to-app-midi/main.cpp +++ b/samples/cpp-winrt/simple-app-to-app-midi/main.cpp @@ -42,8 +42,7 @@ bool CreateVirtualDevice() std::cout << "Creating virtual device." << std::endl; MidiDeclaredEndpointInfo endpointInfo; - endpointInfo.DeclaredFunctionBlockCount = functionBlockCount; - endpointInfo.HasStaticFunctionBlocks = false; // this allows us to change them after the fact + endpointInfo.HasStaticFunctionBlocks = false; // this allows us to change them after the fact. The count cannot change, however, per spec. endpointInfo.Name = L"CPP Virtual Device"; endpointInfo.ProductInstanceId = L"3263827-8675309-5150"; endpointInfo.SupportsMidi10Protocol = true; @@ -102,21 +101,6 @@ bool CreateVirtualDevice() block3.Direction(MidiFunctionBlockDirection::BlockInput); // a midi message destination blocks.push_back(block3); - if (blocks.size() != endpointInfo.DeclaredFunctionBlockCount) - { - std::cout << "Mismatch between declared and actual function block count. No function blocks added." << std::endl; - return false; - } - else - { - for (auto const& fb : blocks) - { - creationConfig.FunctionBlocks().Append(fb); - } - - std::cout << "Added " << creationConfig.FunctionBlocks().Size() << " function blocks." << std::endl; - } - // creates the device using the endpoint info provided above m_virtualDevice = MidiVirtualDeviceManager::CreateVirtualDevice(creationConfig); if (m_virtualDevice == nullptr) @@ -249,8 +233,8 @@ int main() std::cout << "Received UMP" << std::endl; std::cout << "- Current Timestamp: " << std::dec << MidiClock::Now() << std::endl; std::cout << "- UMP Timestamp: " << std::dec << ump.Timestamp() << std::endl; - std::cout << "- UMP Msg Type: 0x" << std::hex << (uint32_t)ump.MessageType() << std::endl; - std::cout << "- UMP Packet Type: 0x" << std::hex << (uint32_t)ump.PacketType() << std::endl; + std::cout << "- UMP Msg Type: 0x" << std::hex << static_cast(ump.MessageType()) << std::endl; + std::cout << "- UMP Packet Type: 0x" << std::hex << static_cast(ump.PacketType()) << std::endl; std::cout << "- Data " << winrt::to_string(args.GetMessagePacket().as().ToString()) << std::endl; std::cout << "- Message: " << winrt::to_string(MidiMessageHelper::GetMessageDisplayNameFromFirstWord(args.PeekFirstWord())) << std::endl; }; diff --git a/samples/cpp-winrt/simple-app-to-app-midi/packages.config b/samples/cpp-winrt/simple-app-to-app-midi/packages.config index 0f06cbf0e..471dae37d 100644 --- a/samples/cpp-winrt/simple-app-to-app-midi/packages.config +++ b/samples/cpp-winrt/simple-app-to-app-midi/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj b/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj index 9bdd753c2..70b87ccd6 100644 --- a/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj +++ b/samples/cpp-winrt/simple-app-to-app-midi/simple-app-to-app-cpp.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/samples/cpp-winrt/static-enum-endpoints/packages.config b/samples/cpp-winrt/static-enum-endpoints/packages.config index 0f06cbf0e..471dae37d 100644 --- a/samples/cpp-winrt/static-enum-endpoints/packages.config +++ b/samples/cpp-winrt/static-enum-endpoints/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj b/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj index 21434c870..6e0010df6 100644 --- a/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj +++ b/samples/cpp-winrt/static-enum-endpoints/static-enum-endpoints-cpp.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/samples/cpp-winrt/watch-endpoints/packages.config b/samples/cpp-winrt/watch-endpoints/packages.config index 0f06cbf0e..471dae37d 100644 --- a/samples/cpp-winrt/watch-endpoints/packages.config +++ b/samples/cpp-winrt/watch-endpoints/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj b/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj index fd1aaa108..1bf6b301e 100644 --- a/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj +++ b/samples/cpp-winrt/watch-endpoints/watch-endpoints-cpp.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/samples/csharp-net/virtual-device-app-winui/MainWindow.xaml.cs b/samples/csharp-net/virtual-device-app-winui/MainWindow.xaml.cs index 755d83f11..70aaa3296 100644 --- a/samples/csharp-net/virtual-device-app-winui/MainWindow.xaml.cs +++ b/samples/csharp-net/virtual-device-app-winui/MainWindow.xaml.cs @@ -226,8 +226,6 @@ MidiVirtualDeviceCreationConfig DefineDevice() declaredEndpointInfo.SupportsSendingJitterReductionTimestamps = false; declaredEndpointInfo.HasStaticFunctionBlocks = false; // this makes it possible for us to update them later - declaredEndpointInfo.DeclaredFunctionBlockCount = 3; // needs to match the number of function blocks in the collection - // todo: set any device identity values if you want. This is optional // The SysEx id, if used, needs to be a valid one diff --git a/samples/powershell/test-midi.ps1 b/samples/powershell/test-midi.ps1 new file mode 100644 index 000000000..5947abfc4 --- /dev/null +++ b/samples/powershell/test-midi.ps1 @@ -0,0 +1,67 @@ +#Requires -Version 7.4 +import-module WindowsMidiServices + +$sdkinfo = Start-Midi | Write-Output + +# List all available endpoints. Enumeration functions do not require an active session. +Write-Host "Available MIDI Endpoints" -ForegroundColor Cyan +(Get-MidiEndpointDeviceInfoList) | Sort-Object -Property Name | Format-Table -AutoSize + +# I'm using the default loopback that is created when you set up MIDI through the MIDI Settings app +$endpointDeviceId = "\\?\swd#midisrv#midiu_loop_a_default_loopback_a#{e7cce071-3c03-423f-88d3-f1045d02552b}" + +# show some info about the device we're interested in +Write-Host "Endpoint we intend to connect to" -ForegroundColor Cyan +(Get-MidiEndpointDeviceInfo $endpointDeviceId) | Format-List + +# create a new session so we can send and receive messages +$session = Start-MidiSession "Powershell Demo Session" + +if ($session -ne $null) +{ + Write-Host "This session" -ForegroundColor Cyan + Write-Output $session | Format-List + + # list all the active sessions + Write-Host "All active MIDI sessions" -ForegroundColor Cyan + (Get-MidiSessionList) | Sort-Object -Property Name | Format-Table -AutoSize + + # open a connection to the endpoint + $connection = Open-MidiEndpointConnection $session $endpointDeviceId + + if ($connection -ne $null) + { + # each sub-array is a complete MIDI UMP + $messages = (0x40905252, 0x02001111), (0x40805252, 0x02000000), 0x25971234 + + foreach ($message in $messages) + { + Write-Host "Sending MIDI message" -ForegroundColor Cyan + + # this gets / displays information about the MIDI message we're sending + Get-MidiMessageInfo $message | Format-List + Send-MidiMessage $connection $message -Timestamp 0 + } + + # You don't need to close the connection if you plan to just destroy the session + # but it's a good practice to clean up what you create + Write-Host "Closing Endpoint connection" -ForegroundColor Cyan + Close-MidiEndpointConnection $session $connection + } + else + { + Write-Host "Unable to open MIDI connection. Is the Endpoint Device Id correct?" -ForegroundColor Red + } + + # Terminate the session, and any connections it has open + Write-Host "Stopping the MIDI session" -ForegroundColor Cyan + Stop-MidiSession $session +} +else +{ + Write-Host "Unable to create new MIDI Session" -ForegroundColor Red +} + +# Cleanly shut down the SDK, stop WinRT activation redirection, etc. +Write-Host "Shutting down the SDK" -ForegroundColor Cyan +Stop-Midi \ No newline at end of file diff --git a/src/api/Client/WinMM/MidiSrvPort.cpp b/src/api/Client/WinMM/MidiSrvPort.cpp index 8b1c10128..3e7fb32f7 100644 --- a/src/api/Client/WinMM/MidiSrvPort.cpp +++ b/src/api/Client/WinMM/MidiSrvPort.cpp @@ -682,14 +682,14 @@ CMidiPort::Callback(_In_ PVOID data, _In_ UINT size, _In_ LONGLONG position, LON } } - // We fell out of the loop. If callback data remaining is 0 and we have some sysex in the buffer, send it - if (buffer && started) - { - if (buffer->dwBytesRecorded > 0 && callbackDataRemaining == 0) - { - RETURN_IF_FAILED(CompleteLongBuffer(MIM_LONGDATA, position)); - } - } + //// We fell out of the loop. If callback data remaining is 0 and we have some sysex in the buffer, send it + //if (buffer && started) + //{ + // if (buffer->dwBytesRecorded > 0 && callbackDataRemaining == 0) + // { + // RETURN_IF_FAILED(CompleteLongBuffer(MIM_LONGDATA, position)); + // } + //} } return S_OK; diff --git a/src/api/Inc/midi1_message_defs.h b/src/api/Inc/midi1_message_defs.h index d62f67f6c..792c51ea0 100644 --- a/src/api/Inc/midi1_message_defs.h +++ b/src/api/Inc/midi1_message_defs.h @@ -84,7 +84,13 @@ (b & MIDI_STATUSBYTEFILTER) == MIDI_STATUSBYTEFILTER) #define MIDI_BYTE_IS_SYSTEM_REALTIME_STATUS(b) (\ - (b & MIDI_SYSTEM_REALTIME_FILTER) == MIDI_SYSTEM_REALTIME_FILTER) + (b == MIDI_TIMINGCLOCK) || \ + (b == MIDI_START) || \ + (b == MIDI_CONTINUE) || \ + (b == MIDI_STOP) || \ + (b == MIDI_ACTIVESENSE) || \ + (b == MIDI_RESET) \ + ) #define MIDI_BYTE_IS_SYSEX_START_STATUS(b) (\ b == MIDI_SYSEX) @@ -95,6 +101,8 @@ #define MIDI_BYTE_IS_DATA_BYTE(b) (\ (b & MIDI_STATUSBYTEFILTER) == 0) - +#define MIDI_STATUS_IS_CHANNEL_VOICE_MESSAGE(b) (\ + b < MIDI_SYSEX) + #endif \ No newline at end of file diff --git a/src/api/Inc/midi_naming.h b/src/api/Inc/midi_naming.h index 6f37d1d51..965880f19 100644 --- a/src/api/Inc/midi_naming.h +++ b/src/api/Inc/midi_naming.h @@ -26,10 +26,13 @@ namespace WindowsMidiServicesInternal::Midi1PortNaming // Used by ESI, MOTU, and others. We don't want to mess up other names, so check only // for whole word. We do other removal in the next step - if (pinName == L"MIDI" || - pinName == L"Out" || pinName == L"OUT" || pinName == L"out" || - pinName == L"In" || pinName == L"IN" || pinName == L"in" || - pinName == L"IO" + + auto checkPinName = internal::ToLowerTrimmedWStringCopy(pinName); + + if (checkPinName == L"midi" || + checkPinName == L"out" || + checkPinName == L"in" || + checkPinName == L"io" ) { cleanedPinName = L""; @@ -267,6 +270,8 @@ namespace WindowsMidiServicesInternal::Midi1PortNaming if (truncateToWinMMLimit) { + // if the name is too long, try using just the pin name or just the filter name + if (name.length() + 1 > MAXPNAMELEN) { if (!cleanedPinName.empty()) diff --git a/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp b/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp index 51684485c..7664420a4 100644 --- a/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp +++ b/src/api/Service/Exe/MidiEndpointProtocolWorker.cpp @@ -301,7 +301,6 @@ HRESULT CMidiEndpointProtocolWorker::CheckIfDiscoveryComplete() { if (m_taskEndpointInfoReceived && - /*m_taskFinalStreamNegotiationResponseReceived && */ m_taskEndpointNameReceived && m_taskEndpointProductInstanceIdReceived && m_taskDeviceIdentityReceived && diff --git a/src/api/Transport/DiagnosticsTransport/transport_defs.h b/src/api/Transport/DiagnosticsTransport/transport_defs.h index 82a079f0c..cd1d5dba9 100644 --- a/src/api/Transport/DiagnosticsTransport/transport_defs.h +++ b/src/api/Transport/DiagnosticsTransport/transport_defs.h @@ -20,19 +20,19 @@ // TODO: Names should be moved to .rc for localization #define TRANSPORT_PARENT_ID L"MIDIU_DIAG_TRANSPORT" -#define TRANSPORT_PARENT_DEVICE_NAME L"MIDI 2.0 Diagnostics Devices" +#define TRANSPORT_PARENT_DEVICE_NAME L"MIDI 2.0 Service Tests" #define DEFAULT_LOOPBACK_BIDI_A_ID L"MIDIU_DIAG_LOOPBACK_A" #define LOOPBACK_BIDI_A_UNIQUE_ID L"LOOPBACK_A" -#define DEFAULT_LOOPBACK_BIDI_A_NAME L"Diagnostics Loopback A" +#define DEFAULT_LOOPBACK_BIDI_A_NAME L"Service Test Loopback A" #define DEFAULT_LOOPBACK_BIDI_B_ID L"MIDIU_DIAG_LOOPBACK_B" #define LOOPBACK_BIDI_B_UNIQUE_ID L"LOOPBACK_B" -#define DEFAULT_LOOPBACK_BIDI_B_NAME L"Diagnostics Loopback B" +#define DEFAULT_LOOPBACK_BIDI_B_NAME L"Service Test Loopback B" #define DEFAULT_PING_BIDI_ID L"MIDIU_DIAG_PING" #define PING_BIDI_UNIQUE_ID L"PING" -#define DEFAULT_PING_BIDI_NAME L"Diagnostics Ping (Internal)" +#define DEFAULT_PING_BIDI_NAME L"Service Test Ping (Internal)" diff --git a/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.cpp b/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.cpp index 1a4ce00eb..e2858fabb 100644 --- a/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.cpp +++ b/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.cpp @@ -17,22 +17,6 @@ using namespace winrt::Windows::Foundation::Collections; using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; -// if this is defined, then KSMidiEndpointManager will publish a BiDi endpoint -// for pairs of midi in & out endpoints on the same filter. -// Filters which do not have a a single pair of midi in and out, -// separate midi in and out SWD's will always be created. -//#define CREATE_KS_BIDI_SWDS - -// If this is defined, we will skip building midi in and midi out -// SWD's for endpoints where BIDI SWD's are created. -// Otherwise, MidiIn, Out, and BiDi will be created. Creating all 3 -// is OK for unit testing one at a time, however is not valid for -// normal usage because MidiIn and MidiOut use the same pins as -// MidiBidi, so only MidiIn and MidiOut or MidiBidi can be used, -// never all 3 at the same time. -//#define BIDI_REPLACES_INOUT_SWDS - - _Use_decl_annotations_ HRESULT @@ -90,56 +74,6 @@ CMidi2KSAggregateMidiEndpointManager::Initialize( } - -//// this will be used for the group terminal block name but also for the WinMM port name -//HRESULT -//CreateGroupTerminalBlockName( -// _In_ std::wstring parentDeviceName, -// _In_ std::wstring filterName, -// _In_ std::wstring currentPinName, -// _Inout_ std::wstring& newGroupTerminalBlockName) -//{ -// std::wstring cleanedPinName{}; -// -// // Used by ESI, MOTU, and others. We don't want to mess up other names, so check only for whole word -// if (currentPinName == L"MIDI") -// { -// cleanedPinName = L""; -// } -// else -// { -// cleanedPinName = currentPinName; -// } -// -// // the double and triple space entries need to be last -// // there are other ways to do this with pattern matching, -// // but just banging this through for this version -// std::wstring wordsToRemove[] = -// { -// parentDeviceName, filterName, -// L"[0]", L"[1]", L"[2]", L"[3]", L"[4]", L"[5]", L"[6]", L"[7]", L"[8]", L"[9]", L"[10]", L"[11]", L"[12]", L"[13]", L"[14]", L"[15]", L"[16]", -// L" ", L" ", L" " -// }; -// -// for (auto const& word : wordsToRemove) -// { -// if (cleanedPinName.length() >= word.length()) -// { -// auto idx = cleanedPinName.find(word); -// -// if (idx != std::wstring::npos) -// { -// cleanedPinName = cleanedPinName.erase(idx, word.length()); -// } -// } -// } -// -// newGroupTerminalBlockName = internal::TrimmedWStringCopy(filterName + L" " + internal::TrimmedWStringCopy(cleanedPinName)); -// -// return S_OK; -//} - - typedef struct { BYTE GroupIndex; // index (0-15) of the group this pin maps to UINT32 PinId; // KS Pin number @@ -241,7 +175,7 @@ CMidi2KSAggregateMidiEndpointManager::CreateMidiUmpEndpoint( std::wstring userSuppliedPortName{}; // TODO: This should come from config file - bool useOldStyleNaming{ true }; // TODO: This should come from registry and property + bool useOldStyleNaming{ true }; // TODO: This should come from registry and property std::wstring deviceContainerName{}; // TODO: Need to get this from the device info std::wstring deviceManufacturerName{}; // TODO: Need to get this from the parent device bool isUsingVendorDriver{ false }; // TODO: Need to get this from the device information @@ -469,6 +403,7 @@ CMidi2KSAggregateMidiEndpointManager::CreateMidiUmpEndpoint( // todo: return new device interface id + auto lock = m_availableEndpointDefinitionsLock.lock(); // Add to internal endpoint manager m_availableEndpointDefinitions.insert_or_assign(masterEndpointDefinition.ParentDeviceInstanceId, masterEndpointDefinition); @@ -493,7 +428,8 @@ CMidi2KSAggregateMidiEndpointManager::CreateMidiUmpEndpoint( } -winrt::hstring GetStringProperty(_In_ DeviceInformation di, _In_ winrt::hstring propertyName, _In_ winrt::hstring defaultValue) +winrt::hstring +GetStringProperty(_In_ DeviceInformation di, _In_ winrt::hstring propertyName, _In_ winrt::hstring defaultValue) { auto prop = di.Properties().Lookup(propertyName); @@ -512,7 +448,8 @@ winrt::hstring GetStringProperty(_In_ DeviceInformation di, _In_ winrt::hstring return value; } -HRESULT GetPinName(_In_ HANDLE const hFilter, _In_ UINT const pinIndex, _Inout_ std::wstring& pinName) +HRESULT +GetPinName(_In_ HANDLE const hFilter, _In_ UINT const pinIndex, _Inout_ std::wstring& pinName) { std::unique_ptr pinNameData; ULONG pinNameDataSize{ 0 }; @@ -540,7 +477,8 @@ HRESULT GetPinName(_In_ HANDLE const hFilter, _In_ UINT const pinIndex, _Inout_ return E_FAIL; } -HRESULT GetPinDataFlow(_In_ HANDLE const hFilter, _In_ UINT const pinIndex, _Inout_ KSPIN_DATAFLOW& dataFlow) +HRESULT +GetPinDataFlow(_In_ HANDLE const hFilter, _In_ UINT const pinIndex, _Inout_ KSPIN_DATAFLOW& dataFlow) { auto dataFlowHR = PinPropertySimple( hFilter, @@ -564,6 +502,15 @@ _Use_decl_annotations_ HRESULT CMidi2KSAggregateMidiEndpointManager::GetKSDriverSuppliedName(HANDLE hInstantiatedFilter, std::wstring& name) { + TraceLoggingWrite( + MidiKSAggregateTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + // get the name GUID KSCOMPONENTID componentId{}; @@ -592,7 +539,6 @@ CMidi2KSAggregateMidiEndpointManager::GetKSDriverSuppliedName(HANDLE hInstantiat { // we have the GUID where this name is stored, so get the driver-supplied name from the registry - WCHAR nameFromRegistry[MAX_PATH]{ 0 }; // this should only be MAXPNAMELEN, but if someone tampered with it, could be larger, hence MAX_PATH std::wstring regKey = L"SYSTEM\\CurrentControlSet\\Control\\MediaCategories\\" + internal::GuidToString(componentId.Name); @@ -877,7 +823,8 @@ CMidi2KSAggregateMidiEndpointManager::OnDeviceAdded( _Use_decl_annotations_ -HRESULT CMidi2KSAggregateMidiEndpointManager::OnDeviceRemoved(DeviceWatcher, DeviceInformationUpdate device) +HRESULT +CMidi2KSAggregateMidiEndpointManager::OnDeviceRemoved(DeviceWatcher, DeviceInformationUpdate device) { TraceLoggingWrite( MidiKSAggregateTransportTelemetryProvider::Provider(), @@ -894,6 +841,8 @@ HRESULT CMidi2KSAggregateMidiEndpointManager::OnDeviceRemoved(DeviceWatcher, Dev // the interface is no longer active, search through our m_AvailableMidiPins to identify // every entry with this filter interface id, and remove the SWD and remove the pin(s) from // the m_AvailableMidiPins list. + + auto lock = m_availableEndpointDefinitionsLock.lock(); do { auto item = m_availableEndpointDefinitions.find((std::wstring)device.Id()); @@ -925,16 +874,28 @@ HRESULT CMidi2KSAggregateMidiEndpointManager::OnDeviceRemoved(DeviceWatcher, Dev } _Use_decl_annotations_ -HRESULT CMidi2KSAggregateMidiEndpointManager::OnDeviceUpdated(DeviceWatcher, DeviceInformationUpdate update) +HRESULT +CMidi2KSAggregateMidiEndpointManager::OnDeviceUpdated(DeviceWatcher, DeviceInformationUpdate update) { + TraceLoggingWrite( + MidiKSAggregateTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Device properties updated", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(update.Id().c_str(), "device id"), + TraceLoggingUInt32(update.Properties().Size(), "count updated properties") + ); + //see this function for info on the IDeviceInformationUpdate object: https://learn.microsoft.com/en-us/windows/uwp/devices-sensors/enumerate-devices#enumerate-and-watch-devices // NOTE: When you change the assigned driver for the device, instead of sending - // separate remove/add events, this gets a couple of OnDeviceUpdate notifications. + // separate remove/add events, this SOMETIMES gets a couple of OnDeviceUpdate notifications. for (auto const& prop : update.Properties()) { - OutputDebugString((std::wstring(L"KSA: ") + std::wstring(prop.Key().c_str())).c_str()); + OutputDebugString((std::wstring(L"KSA Updated Property with Key: ") + std::wstring(prop.Key().c_str())).c_str()); } @@ -942,14 +903,16 @@ HRESULT CMidi2KSAggregateMidiEndpointManager::OnDeviceUpdated(DeviceWatcher, Dev } _Use_decl_annotations_ -HRESULT CMidi2KSAggregateMidiEndpointManager::OnDeviceStopped(DeviceWatcher, winrt::Windows::Foundation::IInspectable) +HRESULT +CMidi2KSAggregateMidiEndpointManager::OnDeviceStopped(DeviceWatcher, winrt::Windows::Foundation::IInspectable) { m_EnumerationCompleted.SetEvent(); return S_OK; } _Use_decl_annotations_ -HRESULT CMidi2KSAggregateMidiEndpointManager::OnEnumerationCompleted(DeviceWatcher, winrt::Windows::Foundation::IInspectable) +HRESULT +CMidi2KSAggregateMidiEndpointManager::OnEnumerationCompleted(DeviceWatcher, winrt::Windows::Foundation::IInspectable) { m_EnumerationCompleted.SetEvent(); return S_OK; diff --git a/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.h b/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.h index 401f36c17..306711e0b 100644 --- a/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.h +++ b/src/api/Transport/KSAggregateTransport/Midi2.KSAggregateMidiEndpointManager.h @@ -62,6 +62,7 @@ class CMidi2KSAggregateMidiEndpointManager : wil::com_ptr_nothrow m_midiDeviceManager; wil::com_ptr_nothrow m_midiProtocolManager; + wil::critical_section m_availableEndpointDefinitionsLock; std::map m_availableEndpointDefinitions; DeviceWatcher m_watcher{0}; diff --git a/src/app-sdk/app-sdk-tools-and-tests.sln b/src/app-sdk/app-sdk-tools-and-tests.sln index 5d0e37bed..9042944d2 100644 --- a/src/app-sdk/app-sdk-tools-and-tests.sln +++ b/src/app-sdk/app-sdk-tools-and-tests.sln @@ -25,6 +25,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "midi1monitor", "midi1monito EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "midiupdatedriver", "midiupdatedriver\midiupdatedriver.vcxproj", "{631E4512-699C-4BC8-AE31-942498CB55FA}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdk-init-tests-exe", "tests\InitializationExe\sdkinittest.vcxproj", "{26E8B9E3-1C56-4BCB-A635-676F5525EA65}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -181,6 +183,22 @@ Global {631E4512-699C-4BC8-AE31-942498CB55FA}.Release|ARM64EC.Build.0 = Release|ARM64EC {631E4512-699C-4BC8-AE31-942498CB55FA}.Release|x64.ActiveCfg = Release|x64 {631E4512-699C-4BC8-AE31-942498CB55FA}.Release|x64.Build.0 = Release|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|Any CPU.ActiveCfg = Debug|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|Any CPU.Build.0 = Debug|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|ARM64.Build.0 = Debug|ARM64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|ARM64EC.ActiveCfg = Debug|ARM64EC + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|ARM64EC.Build.0 = Debug|ARM64EC + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|x64.ActiveCfg = Debug|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Debug|x64.Build.0 = Debug|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|Any CPU.ActiveCfg = Release|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|Any CPU.Build.0 = Release|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|ARM64.ActiveCfg = Release|ARM64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|ARM64.Build.0 = Release|ARM64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|ARM64EC.ActiveCfg = Release|ARM64EC + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|ARM64EC.Build.0 = Release|ARM64EC + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|x64.ActiveCfg = Release|x64 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -195,6 +213,7 @@ Global {C787073C-50F0-5CA0-D53C-12107196F2F0} = {C8B31D71-0226-4D2D-AC78-53D5C84F472F} {357A35F3-D207-43C5-8F56-B3A374F9EBF9} = {C8B31D71-0226-4D2D-AC78-53D5C84F472F} {631E4512-699C-4BC8-AE31-942498CB55FA} = {C8B31D71-0226-4D2D-AC78-53D5C84F472F} + {26E8B9E3-1C56-4BCB-A635-676F5525EA65} = {900D72D2-91F0-4E6C-B694-192FD48393D2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AE1DF8B9-9FAB-4E20-8AC6-FF316882222F} diff --git a/src/app-sdk/client-initialization-redist/Microsoft.Windows.Devices.Midi2.Initialization.hpp b/src/app-sdk/client-initialization-redist/Microsoft.Windows.Devices.Midi2.Initialization.hpp index fa433c9e0..a29c887c8 100644 --- a/src/app-sdk/client-initialization-redist/Microsoft.Windows.Devices.Midi2.Initialization.hpp +++ b/src/app-sdk/client-initialization-redist/Microsoft.Windows.Devices.Midi2.Initialization.hpp @@ -21,7 +21,6 @@ #include // for wstring #include // for COM activation CoCreateInstance etc. -// we also use macros like SUCCEEDED #define SAFE_COTASKMEMFREE(p) \ @@ -43,10 +42,10 @@ namespace Microsoft::Windows::Devices::Midi2::Initialization struct __declspec(uuid("8087b303-d551-bce2-1ead-a2500d50c580")) IMidiClientInitializer : ::IUnknown { // initialize the SDK runtime, and set up WinRT detours - STDMETHOD(Initialize)() = 0; + //STDMETHOD(Initialize)() = 0; // clean up - STDMETHOD(Shutdown)() = 0; + //STDMETHOD(Shutdown)() = 0; // returns the SDK version info. Supply nullptr for arguments you don't care about STDMETHOD(GetInstalledWindowsMidiServicesSdkVersion)( @@ -107,6 +106,25 @@ namespace Microsoft::Windows::Devices::Midi2::Initialization } } + // This was used in unit tests for checking refcounting + // Feel free to remove it in your own use + //ULONG TESTGetCurrentRefCount() + //{ + // if (!m_initializer) + // { + // return 0; + // } + + // ULONG count = m_initializer->AddRef(); + + // if (count > 1) + // { + // m_initializer->Release(); + // } + + // return count - 1; + //} + // IMPORTANT caller note: this is assuming that the caller has made the appropriate // winrt::init_apartment() (C++/WinRT) or Windows::Foundation::Initialize // call before calling this function. Do not call CoInitializeEx because @@ -116,28 +134,19 @@ namespace Microsoft::Windows::Devices::Midi2::Initialization // check if already initialized if (m_initializer != nullptr) return false; + // creating the initializer will also initialize it if we are the first + // consumer in this process. if (SUCCEEDED(CoCreateInstance( __uuidof(MidiClientInitializerUuid), NULL, CLSCTX::CLSCTX_INPROC_SERVER | CLSCTX::CLSCTX_FROM_DEFAULT_CONTEXT, __uuidof(IMidiClientInitializer), - (LPVOID*)&m_initializer + reinterpret_cast(&m_initializer) ))) { if (m_initializer != nullptr) { - if (SUCCEEDED(m_initializer->Initialize())) - { - return true; - } - else - { - // SDK failed to initialize. Possible cause would be a corrupted - // install of some sort, this is being called from inside a packaged - // app (this bootstrapper is for desktop apps only) or perhaps there's - // other code in this process which is redirecting WinRT type activation? - return false; - } + return true; } else { @@ -204,19 +213,20 @@ namespace Microsoft::Windows::Devices::Midi2::Initialization } + // TODO: This should be named "ReleaseSdkRuntime" or similar. + // and it should also unload the DLL void ShutdownSdkRuntime() { if (m_initializer != nullptr) { - // release the COM component. When it shuts down, it will clean up - // the activation hooks. - // m_initializer->Shutdown(); // if you are using wil error logging, this is a good place to use LOG_IF_FAILED(m_initializer->Shutdown()) - m_initializer->Release(); // if using wil::com_ptr_nothrow, you can remove this + // release the COM component. After the last client releases the + // initializer referenced, it will clean up the hooks. + + m_initializer->Release(); // if using wil::com_ptr_nothrow, winrt::com_ptr, or equivalent, you can remove this m_initializer = nullptr; } - } MidiDesktopAppSdkInitializer() = default; diff --git a/src/app-sdk/midi1monitor/main.cpp b/src/app-sdk/midi1monitor/main.cpp index 6f12ba711..08589386c 100644 --- a/src/app-sdk/midi1monitor/main.cpp +++ b/src/app-sdk/midi1monitor/main.cpp @@ -14,6 +14,11 @@ #include "color.hpp" +bool m_showActiveSense{ false }; +bool m_showClock{ false }; + + + void WriteInfo(std::string info) { std::cout << dye::aqua(info) << std::endl; @@ -37,10 +42,20 @@ void WriteLabel(std::string label) std::cout << std::left << std::setw(25) << std::setfill(' ') << dye::grey(fullLabel); } -std::map m_midiInputDevices; + +struct MidiInputPort +{ + uint16_t Index; + std::wstring Name; +}; + +std::vector m_midiInputs{}; + void LoadWinMMDevices() { + //std::map midiInputDevices; + auto inputDeviceCount = midiInGetNumDevs(); for (uint16_t i = 0; i < inputDeviceCount; i++) @@ -51,26 +66,34 @@ void LoadWinMMDevices() if (result == MMSYSERR_NOERROR) { - m_midiInputDevices.insert_or_assign(i, inputCaps); + MidiInputPort port{}; + port.Index = i; + port.Name = inputCaps.szPname; + + m_midiInputs.push_back(port); } } + + std::sort(m_midiInputs.begin(), m_midiInputs.end(), + [](MidiInputPort a, MidiInputPort b) + { + return internal::ToLowerWStringCopy(a.Name) < internal::ToLowerWStringCopy(b.Name); + }); + } void DisplayAllWinMMInputs() { - WriteInfo("Available Input Ports"); - - // todo: should sort this - + WriteInfo(std::to_string(m_midiInputs.size()) + " Available Input Ports"); - for (auto const& capsEntry : m_midiInputDevices) + for (auto const& port : m_midiInputs) { std::cout - << std::setw(3) << dye::yellow(capsEntry.first) + << std::setw(3) << dye::yellow(port.Index) << dye::grey(" : "); std::wcout - << capsEntry.second.szPname + << port.Name << std::endl; } } @@ -82,44 +105,49 @@ uint32_t m_countStatusBytesReceived{ 0 }; void DisplayStatusByte(byte status, bool isError) { - if (status == MIDI_SYSEX) + if (status == MIDI_EOX) + { + std::cout << " "; + } + else { std::cout << std::endl; - if (isError) - { - std::cout << std::hex << std::setw(2) << dye::light_red((uint16_t)status); - } - else - { - std::cout << std::hex << std::setw(2) << dye::yellow((uint16_t)status); - } } - else if (status == MIDI_EOX) + + if (isError) { - std::cout << " "; + std::cout << std::hex << std::setw(2) << dye::light_red((uint16_t)status); + return; + } - if (isError) - { - std::cout << std::hex << std::setw(2) << dye::light_red((uint16_t)status); - } - else + + if (MIDI_STATUS_IS_CHANNEL_VOICE_MESSAGE(status)) + { + switch (status & 0xF0) { + case MIDI_NOTEOFF: + std::cout << std::hex << std::setw(2) << dye::aqua((uint16_t)status); + break; + case MIDI_NOTEON: + std::cout << std::hex << std::setw(2) << dye::light_aqua((uint16_t)status); + break; + case MIDI_MONOAFTERTOUCH: std::cout << std::hex << std::setw(2) << dye::yellow((uint16_t)status); + break; + case MIDI_CONTROLCHANGE: + std::cout << std::hex << std::setw(2) << dye::light_blue((uint16_t)status); + break; + default: + std::cout << std::hex << std::setw(2) << dye::light_purple((uint16_t)status); } } - else + else if (MIDI_BYTE_IS_SYSTEM_REALTIME_STATUS(status)) { - std::cout << std::endl; - if (isError) - { - std::cout << std::hex << std::setw(2) << dye::light_red((uint16_t)status); - } - else - { - std::cout << std::hex << std::setw(2) << dye::light_purple((uint16_t)status); - } + + std::cout << std::hex << std::setw(2) << dye::grey((uint16_t)status); } + } void DisplayDataByte(byte data, bool isError) @@ -137,18 +165,49 @@ void DisplayDataByte(byte data, bool isError) } +void DisplayDecodedChannelVoiceMessage(std::string messageName, uint8_t channel, std::string labelForByte1, uint8_t byte1) +{ + std::cout << std::left << std::setw(18) << std::setfill(' ') << dye::aqua(messageName) << " "; + std::cout << dye::grey("Channel: ") << std::setw(2) << std::right << std::dec << dye::yellow((uint16_t)channel) << ", "; + std::cout << std::setw(12) << dye::grey(labelForByte1) << ": " << std::setw(3) << std::right << std::dec << dye::yellow((uint16_t)byte1); + +} + +void DisplayDecodedChannelVoiceMessage(std::string messageName, uint8_t channel, std::string labelForByte1, uint8_t byte1, std::string labelForByte2, uint8_t byte2) +{ + DisplayDecodedChannelVoiceMessage(messageName, channel, labelForByte1, byte1); + + std::cout << ", "; + std::cout << std::setw(12) << dye::grey(labelForByte2) << ": " << std::setw(3) << std::right << std::dec << dye::yellow((uint16_t)byte2); + +} + void DisplayMidiMessage(DWORD dwMidiMessage, DWORD dwTimestamp, bool isError) { + UNREFERENCED_PARAMETER(dwTimestamp); + // message format 0 | data 2 | data 1 | status - byte status = dwMidiMessage & 0x000000FF; - byte data1 = (dwMidiMessage & 0x0000FF00) >> 8; - byte data2 = (dwMidiMessage & 0x00FF0000) >> 16; + byte status = static_cast(dwMidiMessage & 0x000000FF); + byte data1 = static_cast((dwMidiMessage & 0x0000FF00) >> 8); + byte data2 = static_cast((dwMidiMessage & 0x00FF0000) >> 16); - DisplayStatusByte(status, isError); m_countStatusBytesReceived++; m_countAllBytesReceived++; + if (status == MIDI_ACTIVESENSE && !m_showActiveSense) + { + return; + } + + if (status == MIDI_TIMINGCLOCK && !m_showClock) + { + return; + } + + DisplayStatusByte(status, isError); + + if (MIDI_MESSAGE_IS_TWO_BYTES(status) || MIDI_MESSAGE_IS_THREE_BYTES(status)) { @@ -161,10 +220,91 @@ void DisplayMidiMessage(DWORD dwMidiMessage, DWORD dwTimestamp, bool isError) DisplayDataByte(data2, isError); m_countAllBytesReceived++; } + + // display a decoding of the message to the right + + if (status != MIDI_SYSEX && status != MIDI_EOX) + { + uint16_t spaces{ 0 }; + + if (MIDI_MESSAGE_IS_ONE_BYTE(status)) + { + spaces = 6; + } + else if (MIDI_MESSAGE_IS_TWO_BYTES(status)) + { + spaces = 3; + } + else + { + spaces = 1; + } + + std::cout << std::setw(spaces + 2) << std::setfill(' ') << ""; + + if (MIDI_STATUS_IS_CHANNEL_VOICE_MESSAGE(status)) + { + uint8_t channel = (status & 0x0F) + 1; + + switch (status & 0xF0) + { + case MIDI_NOTEOFF: + DisplayDecodedChannelVoiceMessage("Note Off", channel, "Note", data1, "Velocity", data2); + break; + case MIDI_NOTEON: + DisplayDecodedChannelVoiceMessage("Note On", channel, "Note", data1, "Velocity", data2); + break; + case MIDI_POLYAFTERTOUCH: + DisplayDecodedChannelVoiceMessage("Poly Aftertouch", channel, "Note", data1, "Pressure", data2); + break; + case MIDI_CONTROLCHANGE: + DisplayDecodedChannelVoiceMessage("Control Change", channel, "Controller", data1, "Value", data2); + break; + case MIDI_PROGRAMCHANGE: + DisplayDecodedChannelVoiceMessage("Program Change", channel, "Program", data1); + break; + case MIDI_MONOAFTERTOUCH: + DisplayDecodedChannelVoiceMessage("Channel Pressure", channel, "Pressure", data1); + break; + case MIDI_PITCHBEND: + DisplayDecodedChannelVoiceMessage("Pitch Bend", channel, "LSB", data1, "MSB", data2); + break; + default: + break; + } + } + else if (MIDI_BYTE_IS_SYSTEM_REALTIME_STATUS(status)) + { + switch (status) + { + case MIDI_TIMINGCLOCK: + std::cout << dye::light_aqua("System Real-Time: Clock"); + break; + case MIDI_START: + std::cout << dye::green("System Real-Time: Start"); + break; + case MIDI_CONTINUE: + std::cout << dye::light_yellow("System Real-Time: Continue"); + break; + case MIDI_STOP: + std::cout << dye::red("System Real-Time: Stop"); + break; + case MIDI_ACTIVESENSE: + std::cout << dye::grey("System Real-Time: Active Sense"); + break; + case MIDI_RESET: + std::cout << dye::light_red("System Real-Time: Reset"); + break; + } + } + } + } void DisplayMidiLongMessage(LPMIDIHDR header, DWORD dwTimestamp, bool error) { + UNREFERENCED_PARAMETER(dwTimestamp); + if (header) { byte* current = (byte*)(header->lpData); @@ -209,6 +349,9 @@ void CALLBACK OnMidiMessageReceived( DWORD_PTR dwParam2 ) { + UNREFERENCED_PARAMETER(dwInstance); + UNREFERENCED_PARAMETER(hMidiIn); + switch (wMsg) { case MIM_OPEN: @@ -216,17 +359,17 @@ void CALLBACK OnMidiMessageReceived( case MIM_CLOSE: break; case MIM_DATA: - DisplayMidiMessage(dwParam1, dwParam2, false); + DisplayMidiMessage(static_cast(dwParam1), static_cast(dwParam2), false); break; case MIM_LONGDATA: - DisplayMidiLongMessage((LPMIDIHDR)dwParam1, dwParam2, false); + DisplayMidiLongMessage((LPMIDIHDR)dwParam1, static_cast(dwParam2), false); midiInAddBuffer(m_hMidiIn, &m_header, sizeof(MIDIHDR)); break; case MIM_ERROR: - DisplayMidiMessage(dwParam1, dwParam2, true); + DisplayMidiMessage(static_cast(dwParam1), static_cast(dwParam2), true); break; case MIM_LONGERROR: - DisplayMidiLongMessage((LPMIDIHDR)dwParam1, dwParam2, true); + DisplayMidiLongMessage((LPMIDIHDR)dwParam1, static_cast(dwParam2), true); midiInAddBuffer(m_hMidiIn, &m_header, sizeof(MIDIHDR)); break; case MIM_MOREDATA: @@ -256,7 +399,7 @@ int __cdecl main(int argc, char* argv[]) { try { - portNumber = std::stoi(argv[1]); + portNumber = static_cast(std::stoi(argv[1])); portNumberProvided = true; } catch (...) @@ -266,6 +409,10 @@ int __cdecl main(int argc, char* argv[]) } } + // todo: need to take a command-line arg to show active sense messages. Defaults to false. + + // todo: need to take a command-line arg to show timing clock messages. Defaults to false. + if (!portNumberProvided) { DisplayAllWinMMInputs(); @@ -276,13 +423,27 @@ int __cdecl main(int argc, char* argv[]) } - if (auto const& port = m_midiInputDevices.find(portNumber); port != m_midiInputDevices.end()) + if (auto const& port = std::find_if(m_midiInputs.begin(), m_midiInputs.end(), + [&portNumber](const MidiInputPort& p) { return p.Index == portNumber; }); + port != m_midiInputs.end()) { + std::cout << std::endl; std::cout << dye::aqua("Monitoring "); - std::wcout << port->second.szPname; + std::wcout << port->Name; std::cout << dye::aqua(" for input. Hit escape to cancel."); - std::cout << std::endl << std::endl; + std::cout << std::endl; + if (!m_showActiveSense) + { + std::cout << dye::aqua("Hiding") << dye::light_red(" active sense ") << dye::aqua("messages. "); + } + + if (!m_showClock) + { + std::cout << dye::aqua("Hiding") << dye::light_red(" clock ") << dye::aqua("messages."); + } + + std::cout << std::endl << std::endl; } else { diff --git a/src/app-sdk/midi1monitor/pch.h b/src/app-sdk/midi1monitor/pch.h index df0f98309..9986e2028 100644 --- a/src/app-sdk/midi1monitor/pch.h +++ b/src/app-sdk/midi1monitor/pch.h @@ -9,13 +9,13 @@ #pragma once +#pragma warning (push) +#pragma warning (disable: 4005) + #include //#include -//#pragma warning (disable: 4005) //#include -//#pragma warning (pop) - #include #include @@ -57,3 +57,4 @@ namespace internal = ::WindowsMidiServicesInternal; +#pragma warning (pop) diff --git a/src/app-sdk/mididiag/main.cpp b/src/app-sdk/mididiag/main.cpp index cd6e98d1f..5ce26b9a0 100644 --- a/src/app-sdk/mididiag/main.cpp +++ b/src/app-sdk/mididiag/main.cpp @@ -622,6 +622,23 @@ bool DoSectionMidi2ApiEndpoints(_In_ bool const verbose) OutputStringField(MIDIDIAG_FIELD_LABEL_GTB_NAME, gtb.Name()); OutputNumericField(MIDIDIAG_FIELD_LABEL_GTB_FIRST_GROUP, gtb.FirstGroup().DisplayValue()); OutputNumericField(MIDIDIAG_FIELD_LABEL_GTB_GROUP_COUNT, gtb.GroupCount()); + + std::wstring gtbDirection{}; + + if (gtb.Direction() == midi2::MidiGroupTerminalBlockDirection::Bidirectional) + { + gtbDirection = L"Bidirectional"; + } + else if (gtb.Direction() == midi2::MidiGroupTerminalBlockDirection::BlockInput) + { + gtbDirection = L"Message Destination"; + } + else if (gtb.Direction() == midi2::MidiGroupTerminalBlockDirection::BlockOutput) + { + gtbDirection = L"Message Source"; + } + + OutputStringField(MIDIDIAG_FIELD_LABEL_GTB_DIRECTION, gtbDirection); } if (device.GetGroupTerminalBlocks().Size() > 0) @@ -649,7 +666,9 @@ bool DoSectionMidi2ApiEndpoints(_In_ bool const verbose) } else { - OutputError("Enumerating devices returned no matches. This is not expected and indicates an installation problem or that the service is not running."); + OutputError("Enumerating devices returned no matches. This is not expected and indicates an installation"); + OutputError("problem, the service couldn't start, or you are running developer service components and do"); + OutputError("not have developer mode set in Windows Settings."); return false; } diff --git a/src/app-sdk/mididiag/mididiag.vcxproj b/src/app-sdk/mididiag/mididiag.vcxproj index 92354e737..271a37216 100644 --- a/src/app-sdk/mididiag/mididiag.vcxproj +++ b/src/app-sdk/mididiag/mididiag.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/src/app-sdk/mididiag/mididiag_field_defs.h b/src/app-sdk/mididiag/mididiag_field_defs.h index be8100900..d02a383d2 100644 --- a/src/app-sdk/mididiag/mididiag_field_defs.h +++ b/src/app-sdk/mididiag/mididiag_field_defs.h @@ -82,6 +82,7 @@ #define MIDIDIAG_FIELD_LABEL_GTB_NAME "gtb_name" #define MIDIDIAG_FIELD_LABEL_GTB_FIRST_GROUP "gtb_first_group_number" #define MIDIDIAG_FIELD_LABEL_GTB_GROUP_COUNT "gtb_group_count" +#define MIDIDIAG_FIELD_LABEL_GTB_DIRECTION "gtb_direction" diff --git a/src/app-sdk/mididiag/packages.config b/src/app-sdk/mididiag/packages.config index cd1c48eb4..b6db04bde 100644 --- a/src/app-sdk/mididiag/packages.config +++ b/src/app-sdk/mididiag/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/app-sdk/midimdnsinfo/midimdnsinfo.vcxproj b/src/app-sdk/midimdnsinfo/midimdnsinfo.vcxproj index 78f6a21d9..aefe59e0c 100644 --- a/src/app-sdk/midimdnsinfo/midimdnsinfo.vcxproj +++ b/src/app-sdk/midimdnsinfo/midimdnsinfo.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/src/app-sdk/midimdnsinfo/packages.config b/src/app-sdk/midimdnsinfo/packages.config index cd1c48eb4..b6db04bde 100644 --- a/src/app-sdk/midimdnsinfo/packages.config +++ b/src/app-sdk/midimdnsinfo/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/app-sdk/midiusbinfo/main.cpp b/src/app-sdk/midiusbinfo/main.cpp index 1bf0fceb9..9b6fabf67 100644 --- a/src/app-sdk/midiusbinfo/main.cpp +++ b/src/app-sdk/midiusbinfo/main.cpp @@ -29,7 +29,8 @@ void WriteLabel(std::string label) -winrt::hstring GetStringProperty(_In_ DeviceInformation di, _In_ winrt::hstring propertyName, _In_ winrt::hstring defaultValue) +winrt::hstring +GetStringProperty(_In_ DeviceInformation di, _In_ winrt::hstring propertyName, _In_ winrt::hstring defaultValue) { auto prop = di.Properties().Lookup(propertyName); @@ -48,7 +49,7 @@ winrt::hstring GetStringProperty(_In_ DeviceInformation di, _In_ winrt::hstring return value; } -#define LINE_LENGTH 125 +#define LINE_LENGTH 131 HRESULT @@ -86,7 +87,7 @@ GetKSDriverSuppliedName(_In_ HANDLE hInstantiatedFilter, _Inout_ std::wstring& n if (SUCCEEDED(wil::reg::get_value_string_nothrow(HKEY_LOCAL_MACHINE, regKey.c_str(), L"Name", nameFromRegistry))) { - name = nameFromRegistry; + name = std::wstring(nameFromRegistry) + L" (From: " + regKey + L")"; return S_OK; } } @@ -100,31 +101,347 @@ GetKSDriverSuppliedName(_In_ HANDLE hInstantiatedFilter, _Inout_ std::wstring& n } +struct MidiKsPinInformation +{ + uint32_t Number; + std::wstring Name; + MidiDataFormats DataFormat{ MidiDataFormats::MidiDataFormats_Invalid }; + KSPIN_DATAFLOW PinFlow{ }; +}; + +struct MidiKsFilterInformation +{ + std::wstring Id; + std::wstring Name; + std::wstring NameFromRegistry; + + std::vector Pins; +}; + +struct MidiKsDeviceInformation +{ + std::wstring Name; + std::wstring DeviceInstanceId; + + bool IsMidi1Device{ false }; + bool IsMidi2Device{ false }; + + std::vector Filters; +}; + + +std::vector m_devices{ }; + + + + + + +void DisplayMidiDevices() +{ + std::cout << std::endl; + + if (m_devices.size() == 0) + { + std::cout + << dye::light_red("No devices with MIDI pins found. This can happen if all devices are MIDI 2.0 and the service is running.") + << std::endl; + + return; + } + + + for (auto const& device : m_devices) + { + uint16_t indent{ 0 }; + + std::cout + << std::string(indent, ' ') + << dye::grey("Device Name") + << " " + << dye::light_aqua(winrt::to_string(device.Name)) + << std::endl; + + std::cout + << std::string(indent, ' ') + << dye::grey("Instance Id") + << " " + << dye::yellow(winrt::to_string(device.DeviceInstanceId)) + << std::endl; + + // we list all the filters once in a short format, to make it easier to read for some devices + // and then we list each filter and its pins. Only do this if there's more than one filter. + + if (device.Filters.size() > 1) + { + uint16_t FilterNameColumnWidth{ 0 }; + uint16_t FilterIdColumnWidth{ 0 }; + + for (auto const& filter : device.Filters) + { + FilterNameColumnWidth = static_cast(max(FilterNameColumnWidth, filter.Name.length() + 1)); + FilterIdColumnWidth = static_cast(max(FilterIdColumnWidth, filter.Id.length() + 1)); + } + + bool firstFilter{ true }; + for (auto const& filter : device.Filters) + { + indent = 5; + + if (firstFilter) + { + std::cout << std::endl; + + std::cout + << std::string(indent, ' ') + << dye::aqua("Device") + << " " + << dye::light_aqua(winrt::to_string(device.Name)) + << " " + << dye::aqua("includes") + << " " + << dye::light_aqua(device.Filters.size()) + << " " + << dye::aqua(device.Filters.size() == 1 ? "filter" : "filters") + << " " + << dye::aqua("with MIDI Format Pins") + << std::endl; + + std::cout << std::endl; + + // header row + + std::cout + << std::string(indent, ' ') + << std::setw(FilterNameColumnWidth) << std::left << dye::grey("Name") + << std::setw(FilterIdColumnWidth) << std::left << dye::grey("Instance Id") + << std::endl; + + std::cout + << std::string(indent, ' ') + << std::setw(FilterNameColumnWidth) << std::left << dye::grey(std::string(FilterNameColumnWidth - 1, '-')) + << std::setw(FilterIdColumnWidth) << std::left << dye::grey(std::string(FilterIdColumnWidth - 1, '-')) + << std::endl; + + firstFilter = false; + } + + + std::cout + << std::string(indent, ' ') + << std::setw(FilterNameColumnWidth) << std::left << dye::light_aqua(winrt::to_string(filter.Name)) + << std::setw(FilterIdColumnWidth) << std::left << dye::yellow(winrt::to_string(filter.Id)) + << std::endl; + + } + } + + // now we list all filters and their pins in more detail + + bool firstFilter{ true }; + for (auto const& filter : device.Filters) + { + indent = 5; + + if (firstFilter) + { + std::cout << std::endl; + + std::cout + << std::string(indent, ' ') + << dye::aqua("Details and MIDI Pins for each Filter") + << std::endl; + + std::cout << std::endl; + + firstFilter = false; + } + + + std::cout + << std::string(indent, ' ') + << std::setw(19) << std::left + << dye::grey("Filter Id") + << " " + << dye::yellow(winrt::to_string(filter.Id)) + << std::endl; + + std::cout + << std::string(indent, ' ') + << std::setw(19) << std::left + << dye::grey("Filter Name") + << " " + << dye::light_aqua(winrt::to_string(filter.Name)) + << std::endl; + std::string nameFromRegistry{}; + + if (filter.NameFromRegistry.empty()) + { + nameFromRegistry = "(Not provided. This is normal for MIDI 2.0 drivers and common with some devices using MIDI 1.0 drivers.)"; + } + else + { + nameFromRegistry = winrt::to_string(filter.NameFromRegistry); + } + + std::cout + << std::string(indent, ' ') + << std::setw(19) << std::left + << dye::grey("Name from Registry") + << " " + << (filter.NameFromRegistry.empty() ? dye::grey(nameFromRegistry) : dye::light_aqua(nameFromRegistry)) + << std::endl; + + + bool firstPin = true; + for (auto const& pin : filter.Pins) + { + indent = 10; + const uint16_t PinIndexColumnWidth = 6; + const uint16_t PinDataFormatColumnWidth = 14; + const uint16_t PinDataFlowColumnWidth = 22; + const uint16_t PinDataFlowExplanationColumnWidth = 22; + const uint16_t PinNameColumnWidth = 32; + + if (firstPin) + { + std::cout << std::endl; + + std::cout + << std::string(indent, ' ') + << dye::aqua("Filter") + << " " + << dye::light_aqua(winrt::to_string(filter.Name)) + << " " + << dye::aqua("includes") + << " " + << dye::light_aqua(filter.Pins.size()) + << " " + << dye::aqua("MIDI Format") + << " " + << dye::aqua(filter.Pins.size() == 1 ? "pin" : "pins") + << std::endl; + + std::cout << std::endl; + + // header row + + std::cout + << std::string(indent, ' ') + << std::setw(PinIndexColumnWidth) << std::left << dye::grey("Index") + << std::setw(PinDataFormatColumnWidth) << std::left << dye::grey("Data Format") + << std::setw(PinDataFlowColumnWidth) << std::left << dye::grey("Data Flow") + << std::setw(PinDataFlowExplanationColumnWidth) << std::left << dye::grey("Port Type") + << std::setw(PinNameColumnWidth) << std::left << dye::grey("Pin Name (MIDI 1 drivers only)") + << std::endl; + + std::cout + << std::string(indent, ' ') + << std::setw(PinIndexColumnWidth) << std::left << dye::grey(std::string(PinIndexColumnWidth-1, '-')) + << std::setw(PinDataFormatColumnWidth) << std::left << dye::grey(std::string(PinDataFormatColumnWidth - 1, '-')) + << std::setw(PinDataFlowColumnWidth) << std::left << dye::grey(std::string(PinDataFlowColumnWidth-1, '-')) + << std::setw(PinDataFlowExplanationColumnWidth) << std::left << dye::grey(std::string(PinDataFlowExplanationColumnWidth - 1, '-')) + << std::setw(PinNameColumnWidth) << std::left << dye::grey(std::string(PinNameColumnWidth-1, '-')) + << std::endl; + + firstPin = false; + } + + std::wstring stringDataFlow{ L"Unknown" }; + std::wstring stringDataFlowExplanation{}; + + if (pin.PinFlow == KSPIN_DATAFLOW_IN) + { + stringDataFlow = L"Message Destination"; + stringDataFlowExplanation = L"MIDI Output from PC"; + + } + else if (pin.PinFlow == KSPIN_DATAFLOW_OUT) + { + stringDataFlow = L"Message Source"; + stringDataFlowExplanation = L"MIDI Input to PC"; + } + + std::wstring stringDataFormat{ L"Unknown" }; + if (WI_AreAllFlagsSet(pin.DataFormat,MidiDataFormats::MidiDataFormats_ByteStream)) + { + stringDataFormat = L"MIDI 1 bytes"; + } + else if (WI_AreAllFlagsSet(pin.DataFormat, MidiDataFormats::MidiDataFormats_UMP)) + { + stringDataFormat = L"MIDI 2 UMP"; + } + + std::cout + << std::string(indent, ' ') + << std::setw(PinIndexColumnWidth) << std::left << dye::yellow(pin.Number) + << std::setw(PinDataFormatColumnWidth) << std::left << dye::aqua(winrt::to_string(stringDataFormat)) + << std::setw(PinDataFlowColumnWidth) << std::left << dye::aqua(winrt::to_string(stringDataFlow)) + << std::setw(PinDataFlowExplanationColumnWidth) << std::left << dye::aqua(winrt::to_string(stringDataFlowExplanation)); + + if (pin.Name.empty()) + { + std::cout + << std::setw(PinNameColumnWidth) << std::left << dye::grey("(not provided)") + << std::endl; + } + else + { + std::cout + << std::setw(PinNameColumnWidth) << std::left << dye::light_aqua(winrt::to_string(pin.Name)) + << std::endl; + } + } + + if (filter.Pins.size() > 0) + { + std::cout << std::endl; + } + + } + + std::cout << std::endl; + std::cout << dye::grey(std::string(LINE_LENGTH, '=')) << std::endl; + + } + + std::cout << std::endl; + std::cout << "-- End of Information --" << std::endl << std::endl; + +} + int __cdecl main() { winrt::init_apartment(); std::cout << dye::grey(std::string(LINE_LENGTH, '=')) << std::endl; - std::cout << dye::aqua(" Enumerating MIDI 1.0 kernel streaming devices to discover MIDI pins") << std::endl; - std::cout << dye::aqua(" Typically, these are USB, but other KS drivers will be included") << std::endl; + std::cout << dye::aqua(" Enumerating MIDI kernel streaming devices, using the MIDI class drivers or a third-party MIDI 1.0/2.0 driver.") << std::endl; + std::cout << dye::aqua(" Typically, these devices are USB, but other KS drivers will be included in the enumeration.") << std::endl; std::cout << dye::grey(std::string(LINE_LENGTH, '-')) << std::endl; - std::cout << dye::aqua(" If the MIDI service is running when you run this utility, some") << std::endl; - std::cout << dye::aqua(" devices may not report all pin properties because they are in-use.") << std::endl; - std::cout << dye::aqua(" It is recommended that you stop the midisrv service before running.") << std::endl; - std::cout << dye::aqua(" this utility.") << std::endl; - std::cout << dye::grey(std::string(LINE_LENGTH, '=')) << std::endl; + std::cout << dye::aqua(" If the MIDI service is running when you run this utility, some devices may not report all pin properties because they are in-use.") << std::endl; + std::cout << dye::aqua(" To see MIDI 2.0 devices, you must stop midisrv (the MIDI service) before running this.") << std::endl << std::endl; + std::cout << dye::aqua(" Use "); + std::cout << dye::light_green("midi service stop"); + std::cout << dye::aqua(" or "); + std::cout << dye::light_green("net stop midisrv"); + std::cout << dye::aqua(" from an Administrator command prompt to stop the service.") << std::endl; - std::wcout - << WINDOWS_MIDI_SERVICES_BUILD_VERSION_NAME - << L" (" - << WINDOWS_MIDI_SERVICES_BUILD_SOURCE - << L")" + std::cout << dye::grey(std::string(LINE_LENGTH, '-')) << std::endl; + + std::cout + << " " + << dye::aqua(winrt::to_string(WINDOWS_MIDI_SERVICES_BUILD_VERSION_NAME)) + << dye::grey(" (") + << dye::aqua(winrt::to_string(WINDOWS_MIDI_SERVICES_BUILD_SOURCE)) + << dye::grey(")") + << " -- " + << dye::aqua(winrt::to_string(WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL)) << std::endl; - std::wcout << WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL << std::endl; + std::cout << dye::grey(std::string(LINE_LENGTH, '=')) << std::endl; // {4d36e96c-e325-11ce-bfc1-08002be10318} is the MEDIA class guid @@ -138,6 +455,9 @@ int __cdecl main() { for (auto const& parentDevice : mediaDevices) { + bool isMidi1Device{ false }; + bool isMidi2Device{ false }; + auto deviceInstanceId = GetStringProperty(parentDevice, L"System.Devices.DeviceInstanceId", L""); if (deviceInstanceId.empty()) @@ -146,10 +466,10 @@ int __cdecl main() continue; } - WriteLabel("Parent Device"); - std::cout << dye::light_aqua(winrt::to_string(parentDevice.Name())) << std::endl; - WriteLabel(" - Instance Id"); - std::cout << dye::yellow(winrt::to_string(deviceInstanceId)) << std::endl; + MidiKsDeviceInformation deviceInfo{}; + + deviceInfo.DeviceInstanceId = deviceInstanceId; + deviceInfo.Name = parentDevice.Name(); // enumerate all KS_CATEGORY_AUDIO filters for this parent media device winrt::hstring filterDeviceSelector( @@ -161,75 +481,49 @@ int __cdecl main() if (filterDevices.Size() > 0) { - WriteLabel(" - Filter Count"); - std::cout << filterDevices.Size() << std::endl; - std::cout << std::endl; - for (auto const& filterDevice : filterDevices) { - bool isMidi1Filter{ false }; + MidiKsFilterInformation filterInfo{}; - WriteLabel(" - Filter Id"); - std::cout << dye::yellow(winrt::to_string(filterDevice.Id())) << std::endl; + filterInfo.Id = filterDevice.Id(); + filterInfo.Name = filterDevice.Name(); - WriteLabel(" - KS Filter Name"); - std::cout << dye::light_aqua(winrt::to_string(filterDevice.Name())) << std::endl; + bool isMidi1Filter{ false }; + bool isMidi2Filter{ false }; // instantiate the filter and then enumerate the pins wil::unique_handle hFilter; if (FAILED(FilterInstantiate(filterDevice.Id().c_str(), &hFilter))) { - std::cout << " - Failed to instantiate filter." << std::endl; + // can't instantiate the filter continue; } - std::wstring nameFromDriver{}; - auto driverNameHR = GetKSDriverSuppliedName(hFilter.get(), nameFromDriver); - - WriteLabel(" - KS Reported Name"); - if (SUCCEEDED(driverNameHR)) - { - std::cout << hue::light_aqua; - std::wcout << nameFromDriver; - std::cout << hue::reset << std::endl; - - } - else - { - std::cout << dye::grey("(not available)") << std::endl; - } - - ULONG cPins{ 0 }; - if (FAILED(PinPropertySimple(hFilter.get(), 0, KSPROPSETID_Pin, KSPROPERTY_PIN_CTYPES, &cPins, sizeof(cPins)))) { - std::cout << " - Failed to get pin count." << std::endl; - + // couldn't get pin info continue; } for (UINT pinIndex = 0; pinIndex < cPins; pinIndex++) { bool isMidi1Pin{ false }; + bool isMidi2Pin{ false }; wil::unique_handle hPin; if (SUCCEEDED(InstantiateMidiPin(hFilter.get(), pinIndex, MidiTransport_CyclicUMP, &hPin))) { - std::cout << dye::grey(" Pin ") << dye::grey(pinIndex) << std::endl; - std::cout << dye::grey(" - UMP Cyclic pin (MIDI 2.0 driver). Ignoring for this utility.") << std::endl; - continue; - } - else if (SUCCEEDED(InstantiateMidiPin(hFilter.get(), pinIndex, MidiTransport_StandardByteStream, &hPin))) - { - isMidi1Pin = true; - isMidi1Filter = true; - std::cout << dye::yellow(" Pin ") << dye::aqua(pinIndex) << std::endl; + // MIDI 2 pin + isMidi2Pin = true; + isMidi2Filter = true; + isMidi2Device = true; - // WriteLabel(" - Data Format"); - // std::cout << "MIDI 1.0 byte format" << std::endl; + MidiKsPinInformation pinInfo{}; + pinInfo.Number = pinIndex; + pinInfo.DataFormat = MidiDataFormats::MidiDataFormats_UMP; std::unique_ptr pinNameData; ULONG pinNameDataSize{ 0 }; @@ -245,23 +539,16 @@ int __cdecl main() if (SUCCEEDED(pinNameHR) || pinNameHR == HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND)) { - WriteLabel(" - Name"); - // Check to see if the pin has an iJack name if (pinNameDataSize > 0) { std::wstring pinName{ pinNameData.get() }; - std::cout << hue::light_aqua; - std::wcout << pinName; - std::cout << hue::reset << std::endl; - } - else - { - // no pin name provided - std::cout << "Not provided" << std::endl; + pinInfo.Name = pinName; } } + pinNameData.reset(); + KSPIN_DATAFLOW dataFlow = (KSPIN_DATAFLOW)0; auto dataFlowHR = PinPropertySimple( @@ -273,303 +560,110 @@ int __cdecl main() sizeof(KSPIN_DATAFLOW) ); - - WriteLabel(" - Data Flow"); - if (SUCCEEDED(dataFlowHR)) { - if (dataFlow == KSPIN_DATAFLOW_IN) - { - std::cout << dye::aqua("Message Destination") << dye::grey(" (KSPIN_DATAFLOW_IN: MIDI 1.0 Out from PC to device)") << std::endl; - - } - else if (dataFlow == KSPIN_DATAFLOW_OUT) - { - std::cout << dye::aqua("Message Source") << dye::grey(" (KSPIN_DATAFLOW_OUT: MIDI 1.0 In to PC from device)") << std::endl; - - } - else - { - std::cout << "Unknown" << std::endl; - } + pinInfo.PinFlow = dataFlow; } - else + + if (isMidi2Pin) { - std::cout << dye::light_red("Failed to get data flow") << std::endl; + filterInfo.Pins.push_back(pinInfo); } } - else + else if (SUCCEEDED(InstantiateMidiPin(hFilter.get(), pinIndex, MidiTransport_StandardByteStream, &hPin))) { - // std::cout << "Not a MIDI Pin. Ignoring for this utility." << std::endl; - } - - hPin.reset(); - } + // MIDI 1 pin - if (isMidi1Filter) - { - std::cout << std::endl; - } + MidiKsPinInformation pinInfo{}; + pinInfo.Number = pinIndex; + pinInfo.DataFormat = MidiDataFormats::MidiDataFormats_ByteStream; - } - } - else - { - std::cout << dye::grey(" - Device has no enabled KS_CATEGORY_AUDIO filters available at this time.") << std::endl; - } - - std::cout << std::endl; - std::cout << dye::light_blue(std::string(LINE_LENGTH, '-')) << std::endl; - - } - } - else - { - std::cout << "No media devices found" << std::endl; - } + isMidi1Pin = true; + isMidi1Filter = true; + isMidi1Device = true; + std::unique_ptr pinNameData; + ULONG pinNameDataSize{ 0 }; + auto pinNameHR = PinPropertyAllocate( + hFilter.get(), + pinIndex, + KSPROPSETID_Pin, + KSPROPERTY_PIN_NAME, + (PVOID*)&pinNameData, + &pinNameDataSize + ); + if (SUCCEEDED(pinNameHR) || pinNameHR == HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND)) + { + // Check to see if the pin has an iJack name + if (pinNameDataSize > 0) + { + std::wstring pinName{ pinNameData.get() }; + pinInfo.Name = pinName; + } + } + pinNameData.reset(); + KSPIN_DATAFLOW dataFlow = (KSPIN_DATAFLOW)0; + auto dataFlowHR = PinPropertySimple( + hFilter.get(), + pinIndex, + KSPROPSETID_Pin, + KSPROPERTY_PIN_DATAFLOW, + &dataFlow, + sizeof(KSPIN_DATAFLOW) + ); + if (SUCCEEDED(dataFlowHR)) + { + pinInfo.PinFlow = dataFlow; + } + if (isMidi1Pin) + { + filterInfo.Pins.push_back(pinInfo); + } + } - // std::cout << std::endl; - // std::cout << dye::grey("----------------------------------------------------------------------") << std::endl; + hPin.reset(); + } - // wil::unique_handle hFilter; - // std::string deviceName; - // std::wstring deviceId; - // std::wstring deviceInstanceId; - // ULONG cPins{ 0 }; - // auto additionalProperties = winrt::single_threaded_vector(); + // get the name that the device reported during installation. This is often empty - // auto properties = device.Properties(); + std::wstring nameFromDriver{}; + auto driverNameHR = GetKSDriverSuppliedName(hFilter.get(), nameFromDriver); - // // retrieve the device instance id from the DeviceInformation property store - // auto prop = properties.Lookup(winrt::to_hstring(L"System.Devices.DeviceInstanceId")); - // RETURN_HR_IF_NULL(E_INVALIDARG, prop); - // deviceInstanceId = winrt::unbox_value(prop).c_str(); - // deviceId = device.Id().c_str(); + if (SUCCEEDED(driverNameHR)) + { + filterInfo.NameFromRegistry = nameFromDriver; + } - // // Get the parent device name so it doesn't show as just the PC name - // auto parentDeviceInfo = DeviceInformation::CreateFromIdAsync(deviceInstanceId, - // additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::Device).get(); - // deviceName = winrt::to_string(parentDeviceInfo.Name()); + if (isMidi1Filter || isMidi2Filter) + { + deviceInfo.Filters.push_back(filterInfo); + } - // WriteLabel("Filter Name"); - // std::cout << dye::light_aqua(winrt::to_string(device.Name())) << std::endl; - // WriteLabel("Filter Id"); - // std::cout << dye::yellow(winrt::to_string(device.Id())) << std::endl; + hFilter.reset(); + } + } - // WriteLabel("Parent Name"); - // std::cout << dye::aqua(deviceName) << std::endl; - // WriteLabel("Parent Id"); - // std::cout << dye::green(winrt::to_string(parentDeviceInfo.Id())) << std::endl; + if (isMidi1Device || isMidi2Device) + { + deviceInfo.IsMidi1Device = isMidi1Device; + deviceInfo.IsMidi2Device = isMidi2Device; - // // instantiate the interface - // if (FAILED(FilterInstantiate(deviceId.c_str(), &hFilter))) - // { - // std::cout << " - Failed to instantiate filter." << std::endl; - - // continue; - // } - - // if (FAILED(PinPropertySimple(hFilter.get(), 0, KSPROPSETID_Pin, KSPROPERTY_PIN_CTYPES, &cPins, sizeof(cPins)))) - // { - // std::cout << " - Failed to get pin count." << std::endl; - - // continue; - // } - - // // Enumerate all the pins - - // WriteLabel("Pin Count"); - // std::cout << cPins << std::endl; + m_devices.push_back(deviceInfo); + } + } + } - // for (UINT i = 0; i < cPins; i++) - // { - // std::cout << std::endl; - - // bool isMidiPin{ false }; - - // wil::unique_handle hPin; - // KSPIN_DATAFLOW dataFlow = (KSPIN_DATAFLOW)0; - // KSPIN_COMMUNICATION communication = (KSPIN_COMMUNICATION)0; - // GUID nativeDataFormat{ 0 }; - - // std::cout << dye::yellow("Pin ") << dye::aqua(i) << std::endl; - - // WriteLabel(" - Communication"); - - // if (FAILED(PinPropertySimple(hFilter.get(), i, KSPROPSETID_Pin, KSPROPERTY_PIN_COMMUNICATION, &communication, sizeof(KSPIN_COMMUNICATION)))) - // { - // std::cout << dye::red("Failed to get pin communication property.") << std::endl; - // continue; - // } - - // if (communication == KSPIN_COMMUNICATION_NONE) - // { - // std::cout << "None" << std::endl; - // continue; - // } - // else if (communication == KSPIN_COMMUNICATION_SINK) - // { - // std::cout << "Sink" << std::endl; - // } - // else if (communication == KSPIN_COMMUNICATION_SOURCE) - // { - // std::cout << "Source" << std::endl; - // } - // else if (communication == KSPIN_COMMUNICATION_BOTH) - // { - // std::cout << "Source and Sink" << std::endl; - // } - // else if (communication == KSPIN_COMMUNICATION_BRIDGE) - // { - // std::cout << "Bridge" << std::endl; - // continue; - // } - // else - // { - // std::cout << "Unknown (" << communication << ")" << std::endl; - // continue; - // } - - - // // try to instantiate as UMP - // if (SUCCEEDED(InstantiateMidiPin(hFilter.get(), i, MidiTransport_CyclicUMP, &hPin))) - // { - // isMidiPin = true; - - // WriteLabel(" - Communication Format"); - // std::cout << dye::light_purple("MIDI UMP cyclic format") << std::endl; - - // auto hr = PinPropertySimple(hPin.get(), - // i, - // KSPROPSETID_MIDI2_ENDPOINT_INFORMATION, - // KSPROPERTY_MIDI2_NATIVEDATAFORMAT, - // &nativeDataFormat, - // sizeof(nativeDataFormat)); - - // WriteLabel(" - Native Data Format"); - - // if (SUCCEEDED(hr) || hr == HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND)) - // { - // if (nativeDataFormat == KSDATAFORMAT_SUBTYPE_UNIVERSALMIDIPACKET) - // { - // std::cout << "Universal MIDI Packet (UMP)" << std::endl; - // } - // else if (nativeDataFormat == KSDATAFORMAT_SUBTYPE_MIDI) - // { - // std::cout << "MIDI 1.0 Data Format (bytes)" << std::endl; - // } - // else - // { - // std::cout << "Unknown" << std::endl; - // } - // } - // else - // { - // std::cout << dye::red("Unable to get native data format") << std::endl; - // } - // } - - // hPin.reset(); - - // if (SUCCEEDED(InstantiateMidiPin(hFilter.get(), i, MidiTransport_StandardByteStream, &hPin))) - // { - // isMidiPin = true; - - // std::unique_ptr pinNameData; - // ULONG pinNameDataSize{ 0 }; - - // WriteLabel(" - Communication Format"); - // std::cout << dye::light_purple("MIDI 1.0 byte format") << std::endl; - - // auto pinNameHR = PinPropertyAllocate( - // hFilter.get(), - // i, - // KSPROPSETID_Pin, - // KSPROPERTY_PIN_NAME, - // (PVOID*)&pinNameData, - // &pinNameDataSize); - - // if (SUCCEEDED(pinNameHR) || pinNameHR == HRESULT_FROM_WIN32(ERROR_SET_NOT_FOUND)) - // { - // WriteLabel(" - Pin Name"); - - // // Check to see if the pin has an iJack name - // if (pinNameDataSize > 0) - // { - // std::wstring pinName{ pinNameData.get() }; - // std::cout << hue::light_aqua; - // std::wcout << pinName; - // std::cout << hue::reset << std::endl; - // } - // else - // { - // // no pin name provided - // std::cout << "Not provided" << std::endl; - // } - // } - // } - - // hPin.reset(); - - - // if (isMidiPin) - // { - - // auto dataFlowHR = PinPropertySimple( - // hFilter.get(), - // i, - // KSPROPSETID_Pin, - // KSPROPERTY_PIN_DATAFLOW, - // &dataFlow, - // sizeof(KSPIN_DATAFLOW) - // ); - - // WriteLabel(" - Data Flow"); - - // if (SUCCEEDED(dataFlowHR)) - // { - // if (dataFlow == KSPIN_DATAFLOW_IN) - // { - // std::cout << "KSPIN_DATAFLOW_IN " << dye::purple("(MIDI Out from PC to device)") << std::endl; - - // } - // else if (dataFlow == KSPIN_DATAFLOW_OUT) - // { - // std::cout << "KSPIN_DATAFLOW_OUT " << dye::purple("(MIDI In to PC from device)") << std::endl; - - // } - // else - // { - // std::cout << "Unknown" << std::endl; - // } - // } - // else - // { - // std::cout << dye::red("Failed to get data flow") << std::endl; - // } - // } - - // } - - // } - //} - //else - //{ - // std::cout << std::endl << dye::red("No compatible kernel streaming devices found") << std::endl; - //} - std::cout << std::endl; - std::cout << "-- End of Information --" << std::endl << std::endl; + DisplayMidiDevices(); return 0; } diff --git a/src/app-sdk/midiusbinfo/midiusbinfo.vcxproj b/src/app-sdk/midiusbinfo/midiusbinfo.vcxproj index 0271834a2..4c74cb72a 100644 --- a/src/app-sdk/midiusbinfo/midiusbinfo.vcxproj +++ b/src/app-sdk/midiusbinfo/midiusbinfo.vcxproj @@ -2,7 +2,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250218-1526 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 true true true diff --git a/src/app-sdk/midiusbinfo/packages.config b/src/app-sdk/midiusbinfo/packages.config index cd1c48eb4..b6db04bde 100644 --- a/src/app-sdk/midiusbinfo/packages.config +++ b/src/app-sdk/midiusbinfo/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/app-sdk/projections/dotnet-and-cpp/MidiDesktopAppSdkInitializer.cs b/src/app-sdk/projections/dotnet-and-cpp/MidiDesktopAppSdkInitializer.cs index 5bf63b6da..5b4665718 100644 --- a/src/app-sdk/projections/dotnet-and-cpp/MidiDesktopAppSdkInitializer.cs +++ b/src/app-sdk/projections/dotnet-and-cpp/MidiDesktopAppSdkInitializer.cs @@ -36,9 +36,6 @@ public enum MidiAppSDKPlatform : UInt32 [Guid("8087b303-d551-bce2-1ead-a2500d50c580")] internal partial interface IMidiClientInitializer { - void Initialize(); - void Shutdown(); - void GetInstalledWindowsMidiServicesSdkVersion( ref MidiAppSDKPlatform buildPlatform, ref UInt32 versionMajor, @@ -87,7 +84,7 @@ private MidiDesktopAppSdkInitializer(IMidiClientInitializer initializer) try { - cominit.Initialize(); + // cominit.Initialize(); var initializer = new MidiDesktopAppSdkInitializer(cominit); @@ -122,17 +119,19 @@ public bool EnsureServiceAvailable() public bool InitializeSdkRuntime() { - try - { - _initializer.Initialize(); - - return true; - } - catch (Exception) - { - // todo: Log - return false; - } + return true; + + //try + //{ + // _initializer.Initialize(); + + // return true; + //} + //catch (Exception) + //{ + // // todo: Log + // return false; + //} } public bool CheckForMinimumRequiredSdkVersion( @@ -250,7 +249,8 @@ public void ShutdownSdkRuntime() { if (_initializer != null ) { - _initializer.Shutdown(); + // _initializer.Shutdown(); + _initializer = null; } } diff --git a/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.nuspec b/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.nuspec index 4202a0787..f09473d63 100644 --- a/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.nuspec +++ b/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.nuspec @@ -77,12 +77,18 @@ - - + + + + + + + + - + diff --git a/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.targets b/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.targets index 215069437..37054601d 100644 --- a/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.targets +++ b/src/app-sdk/projections/dotnet-and-cpp/nuget/Microsoft.Windows.Devices.Midi2.targets @@ -14,8 +14,8 @@ - + BeforeTargets="BeforeResolveReferences"> + diff --git a/src/app-sdk/projections/dotnet-and-cpp/nuget/dotnet/Microsoft.Windows.Devices.Midi2.targets b/src/app-sdk/projections/dotnet-and-cpp/nuget/dotnet/Microsoft.Windows.Devices.Midi2.targets new file mode 100644 index 000000000..a88a305fc --- /dev/null +++ b/src/app-sdk/projections/dotnet-and-cpp/nuget/dotnet/Microsoft.Windows.Devices.Midi2.targets @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app-sdk/projections/powershell/Commands/Connections/CommandCloseMidiEndpointConnection.cs b/src/app-sdk/projections/powershell/Commands/Connections/CommandCloseMidiEndpointConnection.cs new file mode 100644 index 000000000..52ba17c97 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Connections/CommandCloseMidiEndpointConnection.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsCommon.Close, "MidiEndpointConnection")] + public class CommandCloseMidiEndpointConnection : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public MidiSession Session + { + get; set; + } + + [Parameter(Mandatory = true, Position = 1)] + public MidiEndpointConnection Connection + { + get; set; + } + + protected override void ProcessRecord() + { + if (Session.BackingSession == null) + { + // todo: throw + return; + } + + Guid id = Connection.ConnectionId; + + Session.BackingSession.DisconnectEndpointConnection(id); + + WriteVerbose($"MIDI Endpoint Connection {id} closed."); + } + } + + +} diff --git a/src/app-sdk/projections/powershell/Commands/Connections/CommandOpenMidiEndpointConnection.cs b/src/app-sdk/projections/powershell/Commands/Connections/CommandOpenMidiEndpointConnection.cs new file mode 100644 index 000000000..ee9f27e10 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Connections/CommandOpenMidiEndpointConnection.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + + + [Cmdlet(VerbsCommon.Open, "MidiEndpointConnection")] + public class CommandOpenMidiEndpointConnection : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public MidiSession Session + { + get;set; + } + + [Parameter(Mandatory = true, Position = 1)] + public string EndpointDeviceId + { + get; set; + } + + + protected override void ProcessRecord() + { + if (Session == null || !Session.IsValid) + { + // invalid + return; + } + + if (string.IsNullOrWhiteSpace(EndpointDeviceId)) + { + // need an endpoint id + return; + } + + var backingConnection = Session.BackingSession!.CreateEndpointConnection(EndpointDeviceId); + + if (backingConnection == null) + { + // failed to create connection + return; + } + + if (backingConnection.Open()) + { + var conn = new MidiEndpointConnection(backingConnection); + + WriteObject(conn); + } + else + { + // unable to open session + } + + + + } + + + } + + + +} diff --git a/src/app-sdk/projections/powershell/Commands/Connections/CommandSendMidiMessage.cs b/src/app-sdk/projections/powershell/Commands/Connections/CommandSendMidiMessage.cs new file mode 100644 index 000000000..a3d701425 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Connections/CommandSendMidiMessage.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsCommunications.Send, "MidiMessage")] + public class CommandSendMidiMessage : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public MidiEndpointConnection Connection { get; set; } + + [Parameter(Mandatory = true, Position = 1)] + public UInt32[] Words { get; set; } + + [Parameter()] + public UInt64 Timestamp { get; set; } = 0; + + protected override void ProcessRecord() + { + if (Words.Length > 4 || Words.Length < 1) + { + throw new ArgumentException("MIDI Words must comprise one and only one valid MIDI UMP message (1-4 32-bit words)"); + } + + // todo: some data validation to ensure the words are a single message only + + // todo: return value + var result = Connection.BackingConnection.SendSingleMessageWordArray(Timestamp, 0, (byte)Words.Length, Words); + + if (Microsoft.Windows.Devices.Midi2.MidiEndpointConnection.SendMessageSucceeded(result)) + { + WriteVerbose($"MIDI Message with {Words.Length.ToString()} UMP words sent with timestamp {Timestamp}."); + } + else + { + WriteVerbose("MIDI Message failed to send"); + WriteObject(result); + } + + } + } +} diff --git a/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointDeviceInfo.cs b/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointDeviceInfo.cs new file mode 100644 index 000000000..cc3d201f8 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointDeviceInfo.cs @@ -0,0 +1,25 @@ +using System.Management.Automation; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsCommon.Get, "MidiEndpointDeviceInfo")] + public class CommandGetMidiEndpointDeviceInfo : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public string EndpointDeviceId + { + get; set; + } + + protected override void ProcessRecord() + { + var sdkDevice = Microsoft.Windows.Devices.Midi2.MidiEndpointDeviceInformation.CreateFromEndpointDeviceId(EndpointDeviceId); + + var device = new MidiEndpointDeviceInfo(sdkDevice); + + WriteObject(device); + } + } + +} diff --git a/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointDeviceInfoList.cs b/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointDeviceInfoList.cs new file mode 100644 index 000000000..9f9c1afa0 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointDeviceInfoList.cs @@ -0,0 +1,29 @@ +using System.Management.Automation; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsCommon.Get, "MidiEndpointDeviceInfoList")] + public class CommandGetMidiEndpointDeviceInfoList : Cmdlet + { + protected override void BeginProcessing() + { + + } + + protected override void ProcessRecord() + { + var sdkDevices = Microsoft.Windows.Devices.Midi2.MidiEndpointDeviceInformation.FindAll(); + + List devices = []; + + foreach (var sdkDevice in sdkDevices) + { + devices.Add(new MidiEndpointDeviceInfo(sdkDevice)); + } + + WriteObject(devices); + } + } + +} diff --git a/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointGroups.cs b/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointGroups.cs new file mode 100644 index 000000000..92e28dcf9 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Enumeration/CommandGetMidiEndpointGroups.cs @@ -0,0 +1,17 @@ +using System.Management.Automation; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsCommon.Get, "MidiEndpointGroups")] + public class CommandGetMidiEndpointGroups : Cmdlet + { + protected override void ProcessRecord() + { + // take in the id, return all the active groups with their GTB or function block names + + + } + } + +} diff --git a/src/app-sdk/projections/powershell/Commands/Initialization/CommandStartMidi.cs b/src/app-sdk/projections/powershell/Commands/Initialization/CommandStartMidi.cs new file mode 100644 index 000000000..5ee1ab8d6 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Initialization/CommandStartMidi.cs @@ -0,0 +1,41 @@ +using Microsoft.Windows.Devices.Midi2.Initialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + // there's an initialize verb, but no corresponding + // terminate or shutdown, so we'll just use start/stop + + [Cmdlet(VerbsLifecycle.Start, "Midi")] + public class InitializeCommand : Cmdlet + { + protected override void ProcessRecord() + { + if (Initializer.CreateInitializer()) + { + if (Initializer.Initialize()) + { + WriteVerbose("MIDI SDK: " + Initializer.GetSdkDescription()); + WriteVerbose("MIDI Initialized."); + } + else + { + // unable to initialize + //WriteError(new ErrorRecord())'' + } + } + else + { + // unable to create initializer + } + + } + + } + +} diff --git a/src/app-sdk/projections/powershell/Commands/Initialization/CommandStopMidi.cs b/src/app-sdk/projections/powershell/Commands/Initialization/CommandStopMidi.cs new file mode 100644 index 000000000..ecd47fccf --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Initialization/CommandStopMidi.cs @@ -0,0 +1,28 @@ +using Microsoft.Windows.Devices.Midi2.Initialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + // there's an initialize verb, but no corresponding + // terminate or shutdown, so we'll just use start/stop + + [Cmdlet(VerbsLifecycle.Stop, "Midi")] + public class CommandStopMidi : Cmdlet + { + protected override void ProcessRecord() + { + if (Initializer.Shutdown()) + { + WriteVerbose("MIDI Shut down."); + } + } + } + + + +} diff --git a/src/app-sdk/projections/powershell/Commands/Initialization/Initializer.cs b/src/app-sdk/projections/powershell/Commands/Initialization/Initializer.cs new file mode 100644 index 000000000..a1e6b1034 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Initialization/Initializer.cs @@ -0,0 +1,88 @@ +using Microsoft.Windows.Devices.Midi2.Initialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + internal class Initializer + { + private static MidiDesktopAppSdkInitializer? m_initializer = null; + + internal bool IsInitialized => (bool)(m_initializer != null); + + // todo: this could throw exceptions to pwsh. That may be better + internal static bool CreateInitializer() + { + if (m_initializer != null) + { + // already created + return false; + } + + m_initializer = MidiDesktopAppSdkInitializer.Create(); + + if (m_initializer == null) + { + return false; + } + + return true; + } + + internal static string GetSdkDescription() + { + if (m_initializer == null) + { + return string.Empty; + } + + return m_initializer.GetInstalledSdkDescription(true, true, true); + } + + + internal static bool Initialize() + { + if (m_initializer == null) + { + return false; + } + + if (!m_initializer.InitializeSdkRuntime()) + { + return false; + } + + if (!m_initializer.EnsureServiceAvailable()) + { + return false; + } + + return true; + } + + internal static bool Shutdown() + { + if (m_initializer == null) + { + return false; + } + + try + { + m_initializer.ShutdownSdkRuntime(); + + m_initializer.Dispose(); + m_initializer = null; + + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/src/app-sdk/projections/powershell/Commands/Messages/CommandGetMidiMessageInfo.cs b/src/app-sdk/projections/powershell/Commands/Messages/CommandGetMidiMessageInfo.cs new file mode 100644 index 000000000..a9f5ef863 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Messages/CommandGetMidiMessageInfo.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + [Cmdlet(VerbsCommon.Get, "MidiMessageInfo")] + public class CommandGetMidiMessageInfo : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public UInt32[] Words { get; set; } + protected override void ProcessRecord() + { + if (Words.Length > 4 || Words.Length < 1) + { + throw new ArgumentException("MIDI Words must comprise one and only one valid MIDI UMP message (1-4 32-bit words)"); + } + + var info = new MidiMessageInfo(); + + info.MessageType = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.GetMessageTypeFromMessageFirstWord(Words[0]); + info.PacketType = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.GetPacketTypeFromMessageFirstWord(Words[0]); + + info.MessageTypeHasGroupField = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.MessageTypeHasGroupField(info.MessageType); + info.MessageTypeHasChannelField = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.MessageTypeHasChannelField(info.MessageType); + + if (info.MessageTypeHasGroupField) + { + info.Group = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.GetGroupFromMessageFirstWord(Words[0]); + } + + if (info.MessageTypeHasChannelField) + { + info.Channel = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.GetChannelFromMessageFirstWord(Words[0]); + } + + info.MessageName = Microsoft.Windows.Devices.Midi2.Messages.MidiMessageHelper.GetMessageDisplayNameFromFirstWord(Words[0]); + + info.ExpectedWordCount = (UInt16)info.PacketType; + + info.Words = Words; + + string wordsHex = string.Empty; + + foreach (var word in Words) + { + wordsHex += word.ToString("X8") + " "; + } + + info.WordsHex = wordsHex.Trim(); + + WriteObject(info); + } + } +} diff --git a/src/app-sdk/projections/powershell/Commands/Session/CommandGetMidiSessionList.cs b/src/app-sdk/projections/powershell/Commands/Session/CommandGetMidiSessionList.cs new file mode 100644 index 000000000..c2ff72e76 --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Session/CommandGetMidiSessionList.cs @@ -0,0 +1,25 @@ +using System.Management.Automation; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsCommon.Get, "MidiSessionList")] + public class CommandGetMidiSessionList : Cmdlet + { + protected override void ProcessRecord() + { + var sdkSessions = Microsoft.Windows.Devices.Midi2.Reporting.MidiReporting.GetActiveSessions(); + + List sessions = []; + + foreach (var sdkSession in sdkSessions) + { + sessions.Add(new MidiSessionInfo(sdkSession)); + } + + WriteObject(sessions.AsReadOnly()); + } + } + + +} \ No newline at end of file diff --git a/src/app-sdk/projections/powershell/Commands/Session/CommandStartMidiSession.cs b/src/app-sdk/projections/powershell/Commands/Session/CommandStartMidiSession.cs new file mode 100644 index 000000000..b96d1bcbe --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Session/CommandStartMidiSession.cs @@ -0,0 +1,40 @@ +using Microsoft.Windows.Devices.Midi2.Initialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + + + [Cmdlet(VerbsLifecycle.Start, "MidiSession")] + public class CommandStartMidiSession : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public string Name { get; set; } + + protected override void ProcessRecord() + { + // todo: check to see if initialized. If not, throw + + var backingSession = Microsoft.Windows.Devices.Midi2.MidiSession.Create(Name); + + if (backingSession != null) + { + var session = new MidiSession(backingSession); + + WriteVerbose("MIDI Session started."); + WriteObject(session); + } + else + { + throw new ArgumentException("Unable to create session. Has MIDI been initialized with Start-Midi?"); + } + } + } + + +} diff --git a/src/app-sdk/projections/powershell/Commands/Session/CommandStopMidiSession.cs b/src/app-sdk/projections/powershell/Commands/Session/CommandStopMidiSession.cs new file mode 100644 index 000000000..8c37d80ac --- /dev/null +++ b/src/app-sdk/projections/powershell/Commands/Session/CommandStopMidiSession.cs @@ -0,0 +1,39 @@ +using Microsoft.Windows.Devices.Midi2.Initialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + + [Cmdlet(VerbsLifecycle.Stop, "MidiSession")] + public class CommandStopMidiSession : Cmdlet + { + [Parameter(Mandatory = true, Position = 0)] + public MidiSession Session { get; set; } + + protected override void ProcessRecord() + { + if (Session.BackingSession != null) + { + WriteVerbose("MIDI Session stopped."); + Session.BackingSession.Dispose(); + } + else + { + WriteVerbose("MIDI Session was not previously started."); + } + + Session = null; + } + + + } + + + + +} diff --git a/src/app-sdk/projections/powershell/Types/MidiEndpointConnection.cs b/src/app-sdk/projections/powershell/Types/MidiEndpointConnection.cs new file mode 100644 index 000000000..7363fcb7a --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiEndpointConnection.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + public class MidiEndpointConnection + { + public Guid ConnectionId => BackingConnection.ConnectionId; + public string EndpointDeviceId => BackingConnection.ConnectedEndpointDeviceId; + + internal Microsoft.Windows.Devices.Midi2.MidiEndpointConnection? BackingConnection { get; set; } + + + public event EventHandler? MessageReceived; + + + + public MidiEndpointConnection(Microsoft.Windows.Devices.Midi2.MidiEndpointConnection backingConnection) + { + BackingConnection = backingConnection; + + // ideally, we wouldn't wire this up unless a command told us to + BackingConnection.MessageReceived += BackingConnection_MessageReceived; + } + + // todo: May need to wrap the event args as well + private void BackingConnection_MessageReceived(Microsoft.Windows.Devices.Midi2.IMidiMessageReceivedEventSource sender, Microsoft.Windows.Devices.Midi2.MidiMessageReceivedEventArgs args) + { + if (MessageReceived != null) + { + MessageReceived(this, args); + } + } + + ~MidiEndpointConnection() + { + if (BackingConnection != null) + { + BackingConnection.MessageReceived -= BackingConnection_MessageReceived; + BackingConnection = null; + } + } + + } +} diff --git a/src/app-sdk/projections/powershell/Types/MidiEndpointDeviceInfo.cs b/src/app-sdk/projections/powershell/Types/MidiEndpointDeviceInfo.cs new file mode 100644 index 000000000..902fa9db1 --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiEndpointDeviceInfo.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + public class MidiEndpointDeviceInfo + { + public string Name => BackingDeviceInformation.Name; + public string EndpointDeviceId => BackingDeviceInformation.EndpointDeviceId; + // public string DeviceInstanceId => BackingDeviceInformation.DeviceInstanceId; + public string TransportCode => BackingDeviceInformation.GetTransportSuppliedInfo().TransportCode; + public Microsoft.Windows.Devices.Midi2.MidiEndpointNativeDataFormat NativeDataFormat => BackingDeviceInformation.GetTransportSuppliedInfo().NativeDataFormat; + + internal Microsoft.Windows.Devices.Midi2.MidiEndpointDeviceInformation BackingDeviceInformation { get; set; } + + public MidiEndpointDeviceInfo(Microsoft.Windows.Devices.Midi2.MidiEndpointDeviceInformation backingDeviceInformation) + { + BackingDeviceInformation = backingDeviceInformation; + } + + } +} diff --git a/src/app-sdk/projections/powershell/Types/MidiInfo.cs b/src/app-sdk/projections/powershell/Types/MidiInfo.cs new file mode 100644 index 000000000..62d11021b --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiInfo.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices.Types +{ + public class MidiInfo + { + + + + + + } +} diff --git a/src/app-sdk/projections/powershell/Types/MidiMessageInfo.cs b/src/app-sdk/projections/powershell/Types/MidiMessageInfo.cs new file mode 100644 index 000000000..5230f9ff8 --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiMessageInfo.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + public class MidiMessageInfo + { + public string MessageName { get; internal set; } + + public Microsoft.Windows.Devices.Midi2.MidiMessageType MessageType { get; internal set; } + + public Microsoft.Windows.Devices.Midi2.MidiPacketType PacketType { get; internal set; } + + + public UInt16 ExpectedWordCount { get; internal set; } + + public UInt32[] Words { get; internal set; } + + public string WordsHex { get; internal set; } + + public bool MessageTypeHasGroupField { get; internal set; } + public bool MessageTypeHasChannelField { get; internal set; } + + public Microsoft.Windows.Devices.Midi2.MidiGroup? Group { get; internal set; } = null; + public Microsoft.Windows.Devices.Midi2.MidiChannel? Channel { get; internal set; } = null; + } +} diff --git a/src/app-sdk/projections/powershell/Types/MidiSession.cs b/src/app-sdk/projections/powershell/Types/MidiSession.cs new file mode 100644 index 000000000..7aee2abf8 --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiSession.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + public class MidiSession + { + public Guid SessionId => BackingSession.SessionId; + public string Name => BackingSession.Name; + + public bool IsValid => (bool)(BackingSession != null && BackingSession.IsOpen == true); + + internal Microsoft.Windows.Devices.Midi2.MidiSession? BackingSession { get; set; } + + public MidiSession(Microsoft.Windows.Devices.Midi2.MidiSession backingSession) + { + BackingSession = backingSession; + } + + ~MidiSession() + { + if (BackingSession != null) + { + //BackingSession.Dispose(); + BackingSession = null; + } + } + } +} diff --git a/src/app-sdk/projections/powershell/Types/MidiSessionInfo.cs b/src/app-sdk/projections/powershell/Types/MidiSessionInfo.cs new file mode 100644 index 000000000..e1e780b16 --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiSessionInfo.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + public class MidiSessionInfo + { + public string Name => BackingSessionInfo.SessionName; + public UInt64 ProcessIdentifier => BackingSessionInfo.ProcessId; + public string ProcessName => BackingSessionInfo.ProcessName; + + public IReadOnlyList Connections; + + + internal Microsoft.Windows.Devices.Midi2.Reporting.MidiServiceSessionInfo BackingSessionInfo { get; set; } + + public MidiSessionInfo(Microsoft.Windows.Devices.Midi2.Reporting.MidiServiceSessionInfo backingSessionInfo) + { + BackingSessionInfo = backingSessionInfo; + + // connections + var list = new List(); + + foreach (var conn in backingSessionInfo.Connections) + { + var wrap = new MidiSessionInfoOpenConnection(conn); + list.Add(wrap); + } + + Connections = list.AsReadOnly(); + } + } +} diff --git a/src/app-sdk/projections/powershell/Types/MidiSessionInfoOpenConnection.cs b/src/app-sdk/projections/powershell/Types/MidiSessionInfoOpenConnection.cs new file mode 100644 index 000000000..fb9feca5d --- /dev/null +++ b/src/app-sdk/projections/powershell/Types/MidiSessionInfoOpenConnection.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WindowsMidiServices +{ + public class MidiSessionInfoOpenConnection + { + public string EndpointDeviceId => BackingSessionConnectionInfo.EndpointDeviceId; + public UInt16 InstanceCount => BackingSessionConnectionInfo.InstanceCount; + + internal Microsoft.Windows.Devices.Midi2.Reporting.MidiServiceSessionConnectionInfo BackingSessionConnectionInfo { get; set; } + + public MidiSessionInfoOpenConnection(Microsoft.Windows.Devices.Midi2.Reporting.MidiServiceSessionConnectionInfo backingSessionConnectionInfo) + { + BackingSessionConnectionInfo = backingSessionConnectionInfo; + } + } +} diff --git a/src/app-sdk/projections/powershell/WindowsMidiServices.csproj b/src/app-sdk/projections/powershell/WindowsMidiServices.csproj new file mode 100644 index 000000000..4467c2166 --- /dev/null +++ b/src/app-sdk/projections/powershell/WindowsMidiServices.csproj @@ -0,0 +1,53 @@ + + + + net8.0-windows10.0.20348.0 + enable + enable + + WindowsMidiServices + x64, ARM64 + False + + false + false + false + false + false + + ARM64;x64 + win-x64;win-arm64 + + + + + true + + + + + true + + LatestMinor + + 10.0.20348.38 + + true + + + + + + + + + + + + + + + + + + diff --git a/src/app-sdk/projections/powershell/WindowsMidiServices.psd1 b/src/app-sdk/projections/powershell/WindowsMidiServices.psd1 new file mode 100644 index 000000000..f02a5c5db --- /dev/null +++ b/src/app-sdk/projections/powershell/WindowsMidiServices.psd1 @@ -0,0 +1,126 @@ +# +# Module manifest for module 'WindowsMidiServices' +# +# Generated by: Pete Brown +# +# Generated on: 2/23/2025 +# + +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'WindowsMidiServices.dll' + +# Version number of this module. +ModuleVersion = '0.0.1' + +# Supported PSEditions +# CompatiblePSEditions = @() + +# ID used to uniquely identify this module +GUID = 'd5a61bed-e196-4171-b6a4-6e6021be63db' + +# Author of this module +Author = 'Pete Brown' + +# Company or vendor of this module +CompanyName = 'Microsoft' + +# Copyright statement for this module +Copyright = '(c) Microsoft. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Integration with Windows MIDI Services' + +# Minimum version of the PowerShell engine required by this module +PowerShellVersion = '7.4' + +# Name of the PowerShell host required by this module +# PowerShellHostName = '' + +# Minimum version of the PowerShell host required by this module +# PowerShellHostVersion = '' + +# Processor architecture (None, X86, Amd64) required by this module +# ProcessorArchitecture = '' + +# Modules that must be imported into the global environment prior to importing this module +# RequiredModules = @() + +# Assemblies that must be loaded prior to importing this module +# RequiredAssemblies = @() + +# Script files (.ps1) that are run in the caller's environment prior to importing this module. +# ScriptsToProcess = @() + +# Type files (.ps1xml) to be loaded when importing this module +# TypesToProcess = @() + +# Format files (.ps1xml) to be loaded when importing this module +# FormatsToProcess = @() + +# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess +# NestedModules = @() + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = @() + +# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. +CmdletsToExport = @("Start-Midi", "Stop-Midi", "Start-MidiSession", "Stop-MidiSession", "Get-MidiSessionList", "Get-MidiEndpointDeviceInfo", "Get-MidiEndpointDeviceInfoList", "Get-MidiEndpointGroups", "Open-MidiEndpointConnection", "Close-MidiEndpointConnection", "Send-MidiMessage", "Get-MidiMessageInfo") + +# Variables to export from this module +VariablesToExport = '*' + +# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. +AliasesToExport = @() + +# DSC resources to export from this module +# DscResourcesToExport = @() + +# List of all modules packaged with this module +# ModuleList = @() + +# List of all files packaged with this module +# FileList = @() + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://aka.ms/midi' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + # Prerelease = '' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. +# DefaultCommandPrefix = '' + +} + diff --git a/src/app-sdk/projections/powershell/midi-powershell.sln b/src/app-sdk/projections/powershell/midi-powershell.sln new file mode 100644 index 000000000..f0a978856 --- /dev/null +++ b/src/app-sdk/projections/powershell/midi-powershell.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35723.152 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsMidiServices", "WindowsMidiServices.csproj", "{037D9D12-DE3D-41D5-90AA-24A5931D2BE7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Debug|ARM64.Build.0 = Debug|ARM64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Debug|x64.ActiveCfg = Debug|x64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Debug|x64.Build.0 = Debug|x64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Release|Any CPU.Build.0 = Release|Any CPU + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Release|ARM64.ActiveCfg = Release|ARM64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Release|ARM64.Build.0 = Release|ARM64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Release|x64.ActiveCfg = Release|x64 + {037D9D12-DE3D-41D5-90AA-24A5931D2BE7}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {423F67CD-6FC0-41F0-973C-BFCF69D89AA4} + EndGlobalSection +EndGlobal diff --git a/src/app-sdk/sdk-runtime-installer/main-bundle/Bundle.wxs b/src/app-sdk/sdk-runtime-installer/main-bundle/Bundle.wxs index a033b957d..7e9d40249 100644 --- a/src/app-sdk/sdk-runtime-installer/main-bundle/Bundle.wxs +++ b/src/app-sdk/sdk-runtime-installer/main-bundle/Bundle.wxs @@ -14,7 +14,7 @@ - @@ -23,6 +23,8 @@ @@ -89,9 +91,8 @@ SourceFile="$(DependenciesSourceRootFolder)\$(var.Platform)\WindowsAppRuntimeInstall-$(var.Platform).exe" Compressed="yes" /> - - + - - + + + + diff --git a/src/app-sdk/sdk-runtime-installer/main-bundle/main-bundle.wixproj b/src/app-sdk/sdk-runtime-installer/main-bundle/main-bundle.wixproj index 7cef7f2e9..c1d03a5c1 100644 --- a/src/app-sdk/sdk-runtime-installer/main-bundle/main-bundle.wixproj +++ b/src/app-sdk/sdk-runtime-installer/main-bundle/main-bundle.wixproj @@ -21,6 +21,7 @@ + diff --git a/src/app-sdk/sdk-runtime-installer/midi-services-app-sdk-runtime-setup.sln b/src/app-sdk/sdk-runtime-installer/midi-services-app-sdk-runtime-setup.sln index 1eb0017fa..3f7f034d8 100644 --- a/src/app-sdk/sdk-runtime-installer/midi-services-app-sdk-runtime-setup.sln +++ b/src/app-sdk/sdk-runtime-installer/midi-services-app-sdk-runtime-setup.sln @@ -11,6 +11,8 @@ Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "console-package", "console- EndProject Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "settings-package", "settings-package\settings-package.wixproj", "{8DDBE5CB-508C-4FAC-AC65-4B2ADAA1455B}" EndProject +Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "powershell-package", "powershell-package\powershell-package.wixproj", "{64F78541-F086-E55B-E80B-17FA72C35E48}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|ARM64 = Debug|ARM64 @@ -69,6 +71,18 @@ Global {8DDBE5CB-508C-4FAC-AC65-4B2ADAA1455B}.Release|x64.Build.0 = Release|x64 {8DDBE5CB-508C-4FAC-AC65-4B2ADAA1455B}.Release|x86.ActiveCfg = Release|x86 {8DDBE5CB-508C-4FAC-AC65-4B2ADAA1455B}.Release|x86.Build.0 = Release|x86 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Debug|ARM64.Build.0 = Debug|ARM64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Debug|x64.ActiveCfg = Debug|x64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Debug|x64.Build.0 = Debug|x64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Debug|x86.ActiveCfg = Debug|x86 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Debug|x86.Build.0 = Debug|x86 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Release|ARM64.ActiveCfg = Release|ARM64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Release|ARM64.Build.0 = Release|ARM64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Release|x64.ActiveCfg = Release|x64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Release|x64.Build.0 = Release|x64 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Release|x86.ActiveCfg = Release|x86 + {64F78541-F086-E55B-E80B-17FA72C35E48}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/app-sdk/sdk-runtime-installer/powershell-package/WindowsMidiServicesPowerShell.wixpdb b/src/app-sdk/sdk-runtime-installer/powershell-package/WindowsMidiServicesPowerShell.wixpdb new file mode 100644 index 000000000..c33f849e4 Binary files /dev/null and b/src/app-sdk/sdk-runtime-installer/powershell-package/WindowsMidiServicesPowerShell.wixpdb differ diff --git a/src/app-sdk/sdk-runtime-installer/powershell-package/WindowsMidiServicesPowerShell.wxs b/src/app-sdk/sdk-runtime-installer/powershell-package/WindowsMidiServicesPowerShell.wxs new file mode 100644 index 000000000..0f3f6e4cd --- /dev/null +++ b/src/app-sdk/sdk-runtime-installer/powershell-package/WindowsMidiServicesPowerShell.wxs @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app-sdk/sdk-runtime-installer/powershell-package/powershell-package.wixproj b/src/app-sdk/sdk-runtime-installer/powershell-package/powershell-package.wixproj new file mode 100644 index 000000000..a17e75ada --- /dev/null +++ b/src/app-sdk/sdk-runtime-installer/powershell-package/powershell-package.wixproj @@ -0,0 +1,13 @@ + + + WindowsMIDIServicesPowerShellSetup + + + false + true + + + false + true + + \ No newline at end of file diff --git a/src/app-sdk/tests/Benchmarks/Benchmarks.vcxproj b/src/app-sdk/tests/Benchmarks/Benchmarks.vcxproj index 6ec240b59..dcbb100f7 100644 --- a/src/app-sdk/tests/Benchmarks/Benchmarks.vcxproj +++ b/src/app-sdk/tests/Benchmarks/Benchmarks.vcxproj @@ -1,4 +1,4 @@ - + @@ -20,7 +20,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.2-preview-8.241123-1919 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 17.0 {4DABE157-7DD5-422A-8C77-B83EAC9987D0} Win32Proj @@ -29,7 +29,7 @@ true false C++/WinRT - true + true true true Benchmarks diff --git a/src/app-sdk/tests/Benchmarks/packages.config b/src/app-sdk/tests/Benchmarks/packages.config index 3414594b7..b6db04bde 100644 --- a/src/app-sdk/tests/Benchmarks/packages.config +++ b/src/app-sdk/tests/Benchmarks/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/app-sdk/tests/Benchmarks/stdafx.h b/src/app-sdk/tests/Benchmarks/stdafx.h index 3ea141a8b..3dbdf7eef 100644 --- a/src/app-sdk/tests/Benchmarks/stdafx.h +++ b/src/app-sdk/tests/Benchmarks/stdafx.h @@ -5,6 +5,9 @@ #ifndef STDAFX_H #define STDAFX_H +#pragma warning (push) +#pragma warning (disable: 4005) + #include @@ -55,5 +58,6 @@ namespace init = Microsoft::Windows::Devices::Midi2::Initialization; #endif +#pragma warning (pop) #endif \ No newline at end of file diff --git a/src/app-sdk/tests/InitializationExe/color.hpp b/src/app-sdk/tests/InitializationExe/color.hpp new file mode 100644 index 000000000..cb080b6e5 --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/color.hpp @@ -0,0 +1,881 @@ +// source: https://github.com/aafulei/color-console/blob/master/include/color.hpp + + +#ifndef COLOR_HPP +#define COLOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hue +{ + constexpr int DEFAULT_COLOR = 7; + constexpr int BAD_COLOR = -256; + + const std::map CODES = { + {"black", 0}, {"k", 0}, + {"blue", 1}, {"b", 1}, + {"green", 2}, {"g", 2}, + {"aqua", 3}, {"a", 3}, + {"red", 4}, {"r", 4}, + {"purple", 5}, {"p", 5}, + {"yellow", 6}, {"y", 6}, + {"white", 7}, {"w", 7}, + {"grey", 8}, {"e", 8}, + {"light blue", 9}, {"lb", 9}, + {"light green", 10}, {"lg", 10}, + {"light aqua", 11}, {"la", 11}, + {"light red", 12}, {"lr", 12}, + {"light purple", 13}, {"lp", 13}, + {"light yellow", 14}, {"ly", 14}, + {"bright white", 15}, {"bw", 15} + }; + + const std::map NAMES = { + { 0, "black"}, + { 1, "blue"}, + { 2, "green"}, + { 3, "aqua"}, + { 4, "red"}, + { 5, "purple"}, + { 6, "yellow"}, + { 7, "white"}, + { 8, "grey"}, + { 9, "light blue"}, + {10, "light green"}, + {11, "light aqua"}, + {12, "light red"}, + {13, "light purple"}, + {14, "light yellow"}, + {15, "bright white"} + }; + + inline bool is_good(int c) + { + return 0 <= c && c < 256; + } + + inline int itoc(int c) + { + return is_good(c) ? c : BAD_COLOR; + } + + inline int itoc(int a, int b) + { + return itoc(a + b * 16); + } + + // std::string to color + int stoc(std::string a) + { + // convert s to lowercase, and format variants like "light_blue" + std::transform(a.begin(), a.end(), a.begin(), [](char c) + { + if ('A' <= c && c <= 'Z') + c = c - 'A' + 'a'; + else if (c == '_' || c == '-') + c = ' '; + return c; + }); + + // operator[] on std::map is non-const, use std::map::at instead + return (CODES.find(a) != CODES.end()) ? CODES.at(a) : BAD_COLOR; + } + + int stoc(std::string a, std::string b) + { + return itoc(stoc(a), stoc(b)); + } + + std::string ctos(int c) + { + return (0 <= c && c < 256) ? + "(text) " + NAMES.at(c % 16) + " + " + + "(background) " + NAMES.at(c / 16) : + "BAD COLOR"; + } + + int get() + { + CONSOLE_SCREEN_BUFFER_INFO i; + return GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &i) ? + i.wAttributes : BAD_COLOR; + } + + int get_text() + { + return (get() != BAD_COLOR) ? get() % 16 : BAD_COLOR; + } + + int get_background() + { + return (get() != BAD_COLOR) ? get() / 16 : BAD_COLOR; + } + + void set(int c) + { + if (is_good(c)) + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); + } + + void set(int a, int b) + { + set(a + b * 16); + } + + void set(std::string a, std::string b) + { + set(stoc(a) + stoc(b) * 16); + } + + void set_text(std::string a) + { + set(stoc(a), get_background()); + } + + void set_background(std::string b) + { + set(get_text(), stoc(b)); + } + + void reset() + { + set(DEFAULT_COLOR); + } + + int invert(int c) + { + if (is_good(c)) { + int a = c % 16; + int b = c / 16; + return b + a * 16; + } + else + return BAD_COLOR; + } + + std::ostream& reset(std::ostream& os) { reset(); return os; } + std::ostream& black(std::ostream& os) { set_text("k"); return os; } + std::ostream& blue(std::ostream& os) { set_text("b"); return os; } + std::ostream& green(std::ostream& os) { set_text("g"); return os; } + std::ostream& aqua(std::ostream& os) { set_text("a"); return os; } + std::ostream& red(std::ostream& os) { set_text("r"); return os; } + std::ostream& purple(std::ostream& os) { set_text("p"); return os; } + std::ostream& yellow(std::ostream& os) { set_text("y"); return os; } + std::ostream& white(std::ostream& os) { set_text("w"); return os; } + std::ostream& grey(std::ostream& os) { set_text("e"); return os; } + std::ostream& light_blue(std::ostream& os) { set_text("lb"); return os; } + std::ostream& light_green(std::ostream& os) { set_text("lg"); return os; } + std::ostream& light_aqua(std::ostream& os) { set_text("la"); return os; } + std::ostream& light_red(std::ostream& os) { set_text("lr"); return os; } + std::ostream& light_purple(std::ostream& os) { set_text("lp"); return os; } + std::ostream& light_yellow(std::ostream& os) { set_text("ly"); return os; } + std::ostream& bright_white(std::ostream& os) { set_text("bw"); return os; } + std::ostream& on_black(std::ostream& os) { set_background("k"); return os; } + std::ostream& on_blue(std::ostream& os) { set_background("b"); return os; } + std::ostream& on_green(std::ostream& os) { set_background("g"); return os; } + std::ostream& on_aqua(std::ostream& os) { set_background("a"); return os; } + std::ostream& on_red(std::ostream& os) { set_background("r"); return os; } + std::ostream& on_purple(std::ostream& os) { set_background("p"); return os; } + std::ostream& on_yellow(std::ostream& os) { set_background("y"); return os; } + std::ostream& on_white(std::ostream& os) { set_background("w"); return os; } + std::ostream& on_grey(std::ostream& os) { set_background("e"); return os; } + std::ostream& on_light_blue(std::ostream& os) { set_background("lb"); return os; } + std::ostream& on_light_green(std::ostream& os) { set_background("lg"); return os; } + std::ostream& on_light_aqua(std::ostream& os) { set_background("la"); return os; } + std::ostream& on_light_red(std::ostream& os) { set_background("lr"); return os; } + std::ostream& on_light_purple(std::ostream& os) { set_background("lp"); return os; } + std::ostream& on_light_yellow(std::ostream& os) { set_background("ly"); return os; } + std::ostream& on_bright_white(std::ostream& os) { set_background("bw"); return os; } + std::ostream& black_on_black(std::ostream& os) { set("k", "k"); return os; } + std::ostream& black_on_blue(std::ostream& os) { set("k", "b"); return os; } + std::ostream& black_on_green(std::ostream& os) { set("k", "g"); return os; } + std::ostream& black_on_aqua(std::ostream& os) { set("k", "a"); return os; } + std::ostream& black_on_red(std::ostream& os) { set("k", "r"); return os; } + std::ostream& black_on_purple(std::ostream& os) { set("k", "p"); return os; } + std::ostream& black_on_yellow(std::ostream& os) { set("k", "y"); return os; } + std::ostream& black_on_white(std::ostream& os) { set("k", "w"); return os; } + std::ostream& black_on_grey(std::ostream& os) { set("k", "e"); return os; } + std::ostream& black_on_light_blue(std::ostream& os) { set("k", "lb"); return os; } + std::ostream& black_on_light_green(std::ostream& os) { set("k", "lg"); return os; } + std::ostream& black_on_light_aqua(std::ostream& os) { set("k", "la"); return os; } + std::ostream& black_on_light_red(std::ostream& os) { set("k", "lr"); return os; } + std::ostream& black_on_light_purple(std::ostream& os) { set("k", "lp"); return os; } + std::ostream& black_on_light_yellow(std::ostream& os) { set("k", "ly"); return os; } + std::ostream& black_on_bright_white(std::ostream& os) { set("k", "bw"); return os; } + std::ostream& blue_on_black(std::ostream& os) { set("b", "k"); return os; } + std::ostream& blue_on_blue(std::ostream& os) { set("b", "b"); return os; } + std::ostream& blue_on_green(std::ostream& os) { set("b", "g"); return os; } + std::ostream& blue_on_aqua(std::ostream& os) { set("b", "a"); return os; } + std::ostream& blue_on_red(std::ostream& os) { set("b", "r"); return os; } + std::ostream& blue_on_purple(std::ostream& os) { set("b", "p"); return os; } + std::ostream& blue_on_yellow(std::ostream& os) { set("b", "y"); return os; } + std::ostream& blue_on_white(std::ostream& os) { set("b", "w"); return os; } + std::ostream& blue_on_grey(std::ostream& os) { set("b", "e"); return os; } + std::ostream& blue_on_light_blue(std::ostream& os) { set("b", "lb"); return os; } + std::ostream& blue_on_light_green(std::ostream& os) { set("b", "lg"); return os; } + std::ostream& blue_on_light_aqua(std::ostream& os) { set("b", "la"); return os; } + std::ostream& blue_on_light_red(std::ostream& os) { set("b", "lr"); return os; } + std::ostream& blue_on_light_purple(std::ostream& os) { set("b", "lp"); return os; } + std::ostream& blue_on_light_yellow(std::ostream& os) { set("b", "ly"); return os; } + std::ostream& blue_on_bright_white(std::ostream& os) { set("b", "bw"); return os; } + std::ostream& green_on_black(std::ostream& os) { set("g", "k"); return os; } + std::ostream& green_on_blue(std::ostream& os) { set("g", "b"); return os; } + std::ostream& green_on_green(std::ostream& os) { set("g", "g"); return os; } + std::ostream& green_on_aqua(std::ostream& os) { set("g", "a"); return os; } + std::ostream& green_on_red(std::ostream& os) { set("g", "r"); return os; } + std::ostream& green_on_purple(std::ostream& os) { set("g", "p"); return os; } + std::ostream& green_on_yellow(std::ostream& os) { set("g", "y"); return os; } + std::ostream& green_on_white(std::ostream& os) { set("g", "w"); return os; } + std::ostream& green_on_grey(std::ostream& os) { set("g", "e"); return os; } + std::ostream& green_on_light_blue(std::ostream& os) { set("g", "lb"); return os; } + std::ostream& green_on_light_green(std::ostream& os) { set("g", "lg"); return os; } + std::ostream& green_on_light_aqua(std::ostream& os) { set("g", "la"); return os; } + std::ostream& green_on_light_red(std::ostream& os) { set("g", "lr"); return os; } + std::ostream& green_on_light_purple(std::ostream& os) { set("g", "lp"); return os; } + std::ostream& green_on_light_yellow(std::ostream& os) { set("g", "ly"); return os; } + std::ostream& green_on_bright_white(std::ostream& os) { set("g", "bw"); return os; } + std::ostream& aqua_on_black(std::ostream& os) { set("a", "k"); return os; } + std::ostream& aqua_on_blue(std::ostream& os) { set("a", "b"); return os; } + std::ostream& aqua_on_green(std::ostream& os) { set("a", "g"); return os; } + std::ostream& aqua_on_aqua(std::ostream& os) { set("a", "a"); return os; } + std::ostream& aqua_on_red(std::ostream& os) { set("a", "r"); return os; } + std::ostream& aqua_on_purple(std::ostream& os) { set("a", "p"); return os; } + std::ostream& aqua_on_yellow(std::ostream& os) { set("a", "y"); return os; } + std::ostream& aqua_on_white(std::ostream& os) { set("a", "w"); return os; } + std::ostream& aqua_on_grey(std::ostream& os) { set("a", "e"); return os; } + std::ostream& aqua_on_light_blue(std::ostream& os) { set("a", "lb"); return os; } + std::ostream& aqua_on_light_green(std::ostream& os) { set("a", "lg"); return os; } + std::ostream& aqua_on_light_aqua(std::ostream& os) { set("a", "la"); return os; } + std::ostream& aqua_on_light_red(std::ostream& os) { set("a", "lr"); return os; } + std::ostream& aqua_on_light_purple(std::ostream& os) { set("a", "lp"); return os; } + std::ostream& aqua_on_light_yellow(std::ostream& os) { set("a", "ly"); return os; } + std::ostream& aqua_on_bright_white(std::ostream& os) { set("a", "bw"); return os; } + std::ostream& red_on_black(std::ostream& os) { set("r", "k"); return os; } + std::ostream& red_on_blue(std::ostream& os) { set("r", "b"); return os; } + std::ostream& red_on_green(std::ostream& os) { set("r", "g"); return os; } + std::ostream& red_on_aqua(std::ostream& os) { set("r", "a"); return os; } + std::ostream& red_on_red(std::ostream& os) { set("r", "r"); return os; } + std::ostream& red_on_purple(std::ostream& os) { set("r", "p"); return os; } + std::ostream& red_on_yellow(std::ostream& os) { set("r", "y"); return os; } + std::ostream& red_on_white(std::ostream& os) { set("r", "w"); return os; } + std::ostream& red_on_grey(std::ostream& os) { set("r", "e"); return os; } + std::ostream& red_on_light_blue(std::ostream& os) { set("r", "lb"); return os; } + std::ostream& red_on_light_green(std::ostream& os) { set("r", "lg"); return os; } + std::ostream& red_on_light_aqua(std::ostream& os) { set("r", "la"); return os; } + std::ostream& red_on_light_red(std::ostream& os) { set("r", "lr"); return os; } + std::ostream& red_on_light_purple(std::ostream& os) { set("r", "lp"); return os; } + std::ostream& red_on_light_yellow(std::ostream& os) { set("r", "ly"); return os; } + std::ostream& red_on_bright_white(std::ostream& os) { set("r", "bw"); return os; } + std::ostream& purple_on_black(std::ostream& os) { set("p", "k"); return os; } + std::ostream& purple_on_blue(std::ostream& os) { set("p", "b"); return os; } + std::ostream& purple_on_green(std::ostream& os) { set("p", "g"); return os; } + std::ostream& purple_on_aqua(std::ostream& os) { set("p", "a"); return os; } + std::ostream& purple_on_red(std::ostream& os) { set("p", "r"); return os; } + std::ostream& purple_on_purple(std::ostream& os) { set("p", "p"); return os; } + std::ostream& purple_on_yellow(std::ostream& os) { set("p", "y"); return os; } + std::ostream& purple_on_white(std::ostream& os) { set("p", "w"); return os; } + std::ostream& purple_on_grey(std::ostream& os) { set("p", "e"); return os; } + std::ostream& purple_on_light_blue(std::ostream& os) { set("p", "lb"); return os; } + std::ostream& purple_on_light_green(std::ostream& os) { set("p", "lg"); return os; } + std::ostream& purple_on_light_aqua(std::ostream& os) { set("p", "la"); return os; } + std::ostream& purple_on_light_red(std::ostream& os) { set("p", "lr"); return os; } + std::ostream& purple_on_light_purple(std::ostream& os) { set("p", "lp"); return os; } + std::ostream& purple_on_light_yellow(std::ostream& os) { set("p", "ly"); return os; } + std::ostream& purple_on_bright_white(std::ostream& os) { set("p", "bw"); return os; } + std::ostream& yellow_on_black(std::ostream& os) { set("y", "k"); return os; } + std::ostream& yellow_on_blue(std::ostream& os) { set("y", "b"); return os; } + std::ostream& yellow_on_green(std::ostream& os) { set("y", "g"); return os; } + std::ostream& yellow_on_aqua(std::ostream& os) { set("y", "a"); return os; } + std::ostream& yellow_on_red(std::ostream& os) { set("y", "r"); return os; } + std::ostream& yellow_on_purple(std::ostream& os) { set("y", "p"); return os; } + std::ostream& yellow_on_yellow(std::ostream& os) { set("y", "y"); return os; } + std::ostream& yellow_on_white(std::ostream& os) { set("y", "w"); return os; } + std::ostream& yellow_on_grey(std::ostream& os) { set("y", "e"); return os; } + std::ostream& yellow_on_light_blue(std::ostream& os) { set("y", "lb"); return os; } + std::ostream& yellow_on_light_green(std::ostream& os) { set("y", "lg"); return os; } + std::ostream& yellow_on_light_aqua(std::ostream& os) { set("y", "la"); return os; } + std::ostream& yellow_on_light_red(std::ostream& os) { set("y", "lr"); return os; } + std::ostream& yellow_on_light_purple(std::ostream& os) { set("y", "lp"); return os; } + std::ostream& yellow_on_light_yellow(std::ostream& os) { set("y", "ly"); return os; } + std::ostream& yellow_on_bright_white(std::ostream& os) { set("y", "bw"); return os; } + std::ostream& white_on_black(std::ostream& os) { set("w", "k"); return os; } + std::ostream& white_on_blue(std::ostream& os) { set("w", "b"); return os; } + std::ostream& white_on_green(std::ostream& os) { set("w", "g"); return os; } + std::ostream& white_on_aqua(std::ostream& os) { set("w", "a"); return os; } + std::ostream& white_on_red(std::ostream& os) { set("w", "r"); return os; } + std::ostream& white_on_purple(std::ostream& os) { set("w", "p"); return os; } + std::ostream& white_on_yellow(std::ostream& os) { set("w", "y"); return os; } + std::ostream& white_on_white(std::ostream& os) { set("w", "w"); return os; } + std::ostream& white_on_grey(std::ostream& os) { set("w", "e"); return os; } + std::ostream& white_on_light_blue(std::ostream& os) { set("w", "lb"); return os; } + std::ostream& white_on_light_green(std::ostream& os) { set("w", "lg"); return os; } + std::ostream& white_on_light_aqua(std::ostream& os) { set("w", "la"); return os; } + std::ostream& white_on_light_red(std::ostream& os) { set("w", "lr"); return os; } + std::ostream& white_on_light_purple(std::ostream& os) { set("w", "lp"); return os; } + std::ostream& white_on_light_yellow(std::ostream& os) { set("w", "ly"); return os; } + std::ostream& white_on_bright_white(std::ostream& os) { set("w", "bw"); return os; } + std::ostream& grey_on_black(std::ostream& os) { set("e", "k"); return os; } + std::ostream& grey_on_blue(std::ostream& os) { set("e", "b"); return os; } + std::ostream& grey_on_green(std::ostream& os) { set("e", "g"); return os; } + std::ostream& grey_on_aqua(std::ostream& os) { set("e", "a"); return os; } + std::ostream& grey_on_red(std::ostream& os) { set("e", "r"); return os; } + std::ostream& grey_on_purple(std::ostream& os) { set("e", "p"); return os; } + std::ostream& grey_on_yellow(std::ostream& os) { set("e", "y"); return os; } + std::ostream& grey_on_white(std::ostream& os) { set("e", "w"); return os; } + std::ostream& grey_on_grey(std::ostream& os) { set("e", "e"); return os; } + std::ostream& grey_on_light_blue(std::ostream& os) { set("e", "lb"); return os; } + std::ostream& grey_on_light_green(std::ostream& os) { set("e", "lg"); return os; } + std::ostream& grey_on_light_aqua(std::ostream& os) { set("e", "la"); return os; } + std::ostream& grey_on_light_red(std::ostream& os) { set("e", "lr"); return os; } + std::ostream& grey_on_light_purple(std::ostream& os) { set("e", "lp"); return os; } + std::ostream& grey_on_light_yellow(std::ostream& os) { set("e", "ly"); return os; } + std::ostream& grey_on_bright_white(std::ostream& os) { set("e", "bw"); return os; } + std::ostream& light_blue_on_black(std::ostream& os) { set("lb", "k"); return os; } + std::ostream& light_blue_on_blue(std::ostream& os) { set("lb", "b"); return os; } + std::ostream& light_blue_on_green(std::ostream& os) { set("lb", "g"); return os; } + std::ostream& light_blue_on_aqua(std::ostream& os) { set("lb", "a"); return os; } + std::ostream& light_blue_on_red(std::ostream& os) { set("lb", "r"); return os; } + std::ostream& light_blue_on_purple(std::ostream& os) { set("lb", "p"); return os; } + std::ostream& light_blue_on_yellow(std::ostream& os) { set("lb", "y"); return os; } + std::ostream& light_blue_on_white(std::ostream& os) { set("lb", "w"); return os; } + std::ostream& light_blue_on_grey(std::ostream& os) { set("lb", "e"); return os; } + std::ostream& light_blue_on_light_blue(std::ostream& os) { set("lb", "lb"); return os; } + std::ostream& light_blue_on_light_green(std::ostream& os) { set("lb", "lg"); return os; } + std::ostream& light_blue_on_light_aqua(std::ostream& os) { set("lb", "la"); return os; } + std::ostream& light_blue_on_light_red(std::ostream& os) { set("lb", "lr"); return os; } + std::ostream& light_blue_on_light_purple(std::ostream& os) { set("lb", "lp"); return os; } + std::ostream& light_blue_on_light_yellow(std::ostream& os) { set("lb", "ly"); return os; } + std::ostream& light_blue_on_bright_white(std::ostream& os) { set("lb", "bw"); return os; } + std::ostream& light_green_on_black(std::ostream& os) { set("lg", "k"); return os; } + std::ostream& light_green_on_blue(std::ostream& os) { set("lg", "b"); return os; } + std::ostream& light_green_on_green(std::ostream& os) { set("lg", "g"); return os; } + std::ostream& light_green_on_aqua(std::ostream& os) { set("lg", "a"); return os; } + std::ostream& light_green_on_red(std::ostream& os) { set("lg", "r"); return os; } + std::ostream& light_green_on_purple(std::ostream& os) { set("lg", "p"); return os; } + std::ostream& light_green_on_yellow(std::ostream& os) { set("lg", "y"); return os; } + std::ostream& light_green_on_white(std::ostream& os) { set("lg", "w"); return os; } + std::ostream& light_green_on_grey(std::ostream& os) { set("lg", "e"); return os; } + std::ostream& light_green_on_light_blue(std::ostream& os) { set("lg", "lb"); return os; } + std::ostream& light_green_on_light_green(std::ostream& os) { set("lg", "lg"); return os; } + std::ostream& light_green_on_light_aqua(std::ostream& os) { set("lg", "la"); return os; } + std::ostream& light_green_on_light_red(std::ostream& os) { set("lg", "lr"); return os; } + std::ostream& light_green_on_light_purple(std::ostream& os) { set("lg", "lp"); return os; } + std::ostream& light_green_on_light_yellow(std::ostream& os) { set("lg", "ly"); return os; } + std::ostream& light_green_on_bright_white(std::ostream& os) { set("lg", "bw"); return os; } + std::ostream& light_aqua_on_black(std::ostream& os) { set("la", "k"); return os; } + std::ostream& light_aqua_on_blue(std::ostream& os) { set("la", "b"); return os; } + std::ostream& light_aqua_on_green(std::ostream& os) { set("la", "g"); return os; } + std::ostream& light_aqua_on_aqua(std::ostream& os) { set("la", "a"); return os; } + std::ostream& light_aqua_on_red(std::ostream& os) { set("la", "r"); return os; } + std::ostream& light_aqua_on_purple(std::ostream& os) { set("la", "p"); return os; } + std::ostream& light_aqua_on_yellow(std::ostream& os) { set("la", "y"); return os; } + std::ostream& light_aqua_on_white(std::ostream& os) { set("la", "w"); return os; } + std::ostream& light_aqua_on_grey(std::ostream& os) { set("la", "e"); return os; } + std::ostream& light_aqua_on_light_blue(std::ostream& os) { set("la", "lb"); return os; } + std::ostream& light_aqua_on_light_green(std::ostream& os) { set("la", "lg"); return os; } + std::ostream& light_aqua_on_light_aqua(std::ostream& os) { set("la", "la"); return os; } + std::ostream& light_aqua_on_light_red(std::ostream& os) { set("la", "lr"); return os; } + std::ostream& light_aqua_on_light_purple(std::ostream& os) { set("la", "lp"); return os; } + std::ostream& light_aqua_on_light_yellow(std::ostream& os) { set("la", "ly"); return os; } + std::ostream& light_aqua_on_bright_white(std::ostream& os) { set("la", "bw"); return os; } + std::ostream& light_red_on_black(std::ostream& os) { set("lr", "k"); return os; } + std::ostream& light_red_on_blue(std::ostream& os) { set("lr", "b"); return os; } + std::ostream& light_red_on_green(std::ostream& os) { set("lr", "g"); return os; } + std::ostream& light_red_on_aqua(std::ostream& os) { set("lr", "a"); return os; } + std::ostream& light_red_on_red(std::ostream& os) { set("lr", "r"); return os; } + std::ostream& light_red_on_purple(std::ostream& os) { set("lr", "p"); return os; } + std::ostream& light_red_on_yellow(std::ostream& os) { set("lr", "y"); return os; } + std::ostream& light_red_on_white(std::ostream& os) { set("lr", "w"); return os; } + std::ostream& light_red_on_grey(std::ostream& os) { set("lr", "e"); return os; } + std::ostream& light_red_on_light_blue(std::ostream& os) { set("lr", "lb"); return os; } + std::ostream& light_red_on_light_green(std::ostream& os) { set("lr", "lg"); return os; } + std::ostream& light_red_on_light_aqua(std::ostream& os) { set("lr", "la"); return os; } + std::ostream& light_red_on_light_red(std::ostream& os) { set("lr", "lr"); return os; } + std::ostream& light_red_on_light_purple(std::ostream& os) { set("lr", "lp"); return os; } + std::ostream& light_red_on_light_yellow(std::ostream& os) { set("lr", "ly"); return os; } + std::ostream& light_red_on_bright_white(std::ostream& os) { set("lr", "bw"); return os; } + std::ostream& light_purple_on_black(std::ostream& os) { set("lp", "k"); return os; } + std::ostream& light_purple_on_blue(std::ostream& os) { set("lp", "b"); return os; } + std::ostream& light_purple_on_green(std::ostream& os) { set("lp", "g"); return os; } + std::ostream& light_purple_on_aqua(std::ostream& os) { set("lp", "a"); return os; } + std::ostream& light_purple_on_red(std::ostream& os) { set("lp", "r"); return os; } + std::ostream& light_purple_on_purple(std::ostream& os) { set("lp", "p"); return os; } + std::ostream& light_purple_on_yellow(std::ostream& os) { set("lp", "y"); return os; } + std::ostream& light_purple_on_white(std::ostream& os) { set("lp", "w"); return os; } + std::ostream& light_purple_on_grey(std::ostream& os) { set("lp", "e"); return os; } + std::ostream& light_purple_on_light_blue(std::ostream& os) { set("lp", "lb"); return os; } + std::ostream& light_purple_on_light_green(std::ostream& os) { set("lp", "lg"); return os; } + std::ostream& light_purple_on_light_aqua(std::ostream& os) { set("lp", "la"); return os; } + std::ostream& light_purple_on_light_red(std::ostream& os) { set("lp", "lr"); return os; } + std::ostream& light_purple_on_light_purple(std::ostream& os) { set("lp", "lp"); return os; } + std::ostream& light_purple_on_light_yellow(std::ostream& os) { set("lp", "ly"); return os; } + std::ostream& light_purple_on_bright_white(std::ostream& os) { set("lp", "bw"); return os; } + std::ostream& light_yellow_on_black(std::ostream& os) { set("ly", "k"); return os; } + std::ostream& light_yellow_on_blue(std::ostream& os) { set("ly", "b"); return os; } + std::ostream& light_yellow_on_green(std::ostream& os) { set("ly", "g"); return os; } + std::ostream& light_yellow_on_aqua(std::ostream& os) { set("ly", "a"); return os; } + std::ostream& light_yellow_on_red(std::ostream& os) { set("ly", "r"); return os; } + std::ostream& light_yellow_on_purple(std::ostream& os) { set("ly", "p"); return os; } + std::ostream& light_yellow_on_yellow(std::ostream& os) { set("ly", "y"); return os; } + std::ostream& light_yellow_on_white(std::ostream& os) { set("ly", "w"); return os; } + std::ostream& light_yellow_on_grey(std::ostream& os) { set("ly", "e"); return os; } + std::ostream& light_yellow_on_light_blue(std::ostream& os) { set("ly", "lb"); return os; } + std::ostream& light_yellow_on_light_green(std::ostream& os) { set("ly", "lg"); return os; } + std::ostream& light_yellow_on_light_aqua(std::ostream& os) { set("ly", "la"); return os; } + std::ostream& light_yellow_on_light_red(std::ostream& os) { set("ly", "lr"); return os; } + std::ostream& light_yellow_on_light_purple(std::ostream& os) { set("ly", "lp"); return os; } + std::ostream& light_yellow_on_light_yellow(std::ostream& os) { set("ly", "ly"); return os; } + std::ostream& light_yellow_on_bright_white(std::ostream& os) { set("ly", "bw"); return os; } + std::ostream& bright_white_on_black(std::ostream& os) { set("bw", "k"); return os; } + std::ostream& bright_white_on_blue(std::ostream& os) { set("bw", "b"); return os; } + std::ostream& bright_white_on_green(std::ostream& os) { set("bw", "g"); return os; } + std::ostream& bright_white_on_aqua(std::ostream& os) { set("bw", "a"); return os; } + std::ostream& bright_white_on_red(std::ostream& os) { set("bw", "r"); return os; } + std::ostream& bright_white_on_purple(std::ostream& os) { set("bw", "p"); return os; } + std::ostream& bright_white_on_yellow(std::ostream& os) { set("bw", "y"); return os; } + std::ostream& bright_white_on_white(std::ostream& os) { set("bw", "w"); return os; } + std::ostream& bright_white_on_grey(std::ostream& os) { set("bw", "e"); return os; } + std::ostream& bright_white_on_light_blue(std::ostream& os) { set("bw", "lb"); return os; } + std::ostream& bright_white_on_light_green(std::ostream& os) { set("bw", "lg"); return os; } + std::ostream& bright_white_on_light_aqua(std::ostream& os) { set("bw", "la"); return os; } + std::ostream& bright_white_on_light_red(std::ostream& os) { set("bw", "lr"); return os; } + std::ostream& bright_white_on_light_purple(std::ostream& os) { set("bw", "lp"); return os; } + std::ostream& bright_white_on_light_yellow(std::ostream& os) { set("bw", "ly"); return os; } + std::ostream& bright_white_on_bright_white(std::ostream& os) { set("bw", "bw"); return os; } +} + + +namespace dye +{ + template + using bar = typename std::conditional::value, std::string, T>::type; + + template class colorful; + template class item; + + template + class colorful : private std::list> + { + public: + using std::list>::list; + + colorful& operator+=(const colorful& rhs) + { + this->insert(this->end(), rhs.begin(), rhs.end()); + return *this; + } + + colorful& operator+=(colorful&& rhs) + { + this->splice(this->end(), std::move(rhs)); + return *this; + } + + colorful& operator+=(T t) + { + this->push_back(std::move(t)); + return *this; + } + + void push_front(T t) + { + this->std::list>::push_front(item(std::move(t))); + } + + void push_back(T t) + { + this->std::list>::push_back(item(std::move(t))); + } + + colorful& invert() + { + for (auto& elem : *this) + elem.invert(); + return *this; + } + + template + friend std::ostream& operator<<(std::ostream&, const colorful&); + + template + friend colorful invert(colorful col); + }; + + template + colorful operator+(colorful lhs, colorful rhs) + { + colorful res(std::move(lhs)); + return res += rhs; + } + + template + colorful operator+(colorful lhs, std::string rhs) + { + colorful res(std::move(lhs)); + res.push_back(std::move(rhs)); + return res; + } + + template + colorful operator+(const std::string& lhs, colorful rhs) + { + colorful res(std::move(rhs)); + res.push_front(std::move(lhs)); + return res; + } + + template + std::ostream& operator<<(std::ostream& os, const colorful& colorful) + { + for (const auto& elem : colorful) + os << elem; + return os; + } + + template + colorful invert(colorful col) + { + colorful res(std::move(col)); + for (auto& elem : res) + elem.invert(); + return res; + } + + template + class item + { + T thing; + int color; + + public: + item(T t) : thing(std::move(t)), color(hue::get()) {} + item(T t, int a) : thing(std::move(t)), color(hue::itoc(a)) {} + item(T t, int a, int b) : thing(std::move(t)), color(hue::itoc(a, b)) {} + item(T t, std::string a) : thing(std::move(t)), color(hue::stoc(a)) {} + item(T t, std::string a, std::string b) : thing(std::move(t)), color(hue::stoc(a, b)) {} + + item& invert() + { + color = hue::invert(color); + return *this; + } + + template + friend class colorful; + + template + friend std::ostream& operator<<(std::ostream&, const item&); + }; + + template + std::ostream& operator<<(std::ostream& os, const item& it) + { + hue::set(it.color); + os << it.thing; + hue::reset(); + return os; + } + + template using R = colorful>; + template using S = item>; + + template R colorize(T t, std::string a) { return R { S(t, a) }; } + template R vanilla(T t) { return R { S(t) }; } + template R black(T t) { return R { S(t, "k") }; } + template R blue(T t) { return R { S(t, "b") }; } + template R green(T t) { return R { S(t, "g") }; } + template R aqua(T t) { return R { S(t, "a") }; } + template R red(T t) { return R { S(t, "r") }; } + template R purple(T t) { return R { S(t, "p") }; } + template R yellow(T t) { return R { S(t, "y") }; } + template R white(T t) { return R { S(t, "w") }; } + template R grey(T t) { return R { S(t, "e") }; } + template R light_blue(T t) { return R { S(t, "lb") }; } + template R light_green(T t) { return R { S(t, "lg") }; } + template R light_aqua(T t) { return R { S(t, "la") }; } + template R light_red(T t) { return R { S(t, "lr") }; } + template R light_purple(T t) { return R { S(t, "lp") }; } + template R light_yellow(T t) { return R { S(t, "ly") }; } + template R bright_white(T t) { return R { S(t, "bw") }; } + template R on_black(T t) { return R { S(t, "k", "k") }; } + template R on_blue(T t) { return R { S(t, "k", "b") }; } + template R on_green(T t) { return R { S(t, "k", "g") }; } + template R on_aqua(T t) { return R { S(t, "k", "a") }; } + template R on_red(T t) { return R { S(t, "k", "r") }; } + template R on_purple(T t) { return R { S(t, "k", "p") }; } + template R on_yellow(T t) { return R { S(t, "k", "y") }; } + template R on_white(T t) { return R { S(t, "k", "w") }; } + template R on_grey(T t) { return R { S(t, "k", "e") }; } + template R on_light_blue(T t) { return R { S(t, "k", "lb") }; } + template R on_light_green(T t) { return R { S(t, "k", "lg") }; } + template R on_light_aqua(T t) { return R { S(t, "k", "la") }; } + template R on_light_red(T t) { return R { S(t, "k", "lr") }; } + template R on_light_purple(T t) { return R { S(t, "k", "lp") }; } + template R on_light_yellow(T t) { return R { S(t, "k", "ly") }; } + template R on_bright_white(T t) { return R { S(t, "k", "bw") }; } + template R black_on_black(T t) { return R { S(t, "k", "k") }; } + template R black_on_blue(T t) { return R { S(t, "k", "b") }; } + template R black_on_green(T t) { return R { S(t, "k", "g") }; } + template R black_on_aqua(T t) { return R { S(t, "k", "a") }; } + template R black_on_red(T t) { return R { S(t, "k", "r") }; } + template R black_on_purple(T t) { return R { S(t, "k", "p") }; } + template R black_on_yellow(T t) { return R { S(t, "k", "y") }; } + template R black_on_white(T t) { return R { S(t, "k", "w") }; } + template R black_on_grey(T t) { return R { S(t, "k", "e") }; } + template R black_on_light_blue(T t) { return R { S(t, "k", "lb") }; } + template R black_on_light_green(T t) { return R { S(t, "k", "lg") }; } + template R black_on_light_aqua(T t) { return R { S(t, "k", "la") }; } + template R black_on_light_red(T t) { return R { S(t, "k", "lr") }; } + template R black_on_light_purple(T t) { return R { S(t, "k", "lp") }; } + template R black_on_light_yellow(T t) { return R { S(t, "k", "ly") }; } + template R black_on_bright_white(T t) { return R { S(t, "k", "bw") }; } + template R blue_on_black(T t) { return R { S(t, "b", "k") }; } + template R blue_on_blue(T t) { return R { S(t, "b", "b") }; } + template R blue_on_green(T t) { return R { S(t, "b", "g") }; } + template R blue_on_aqua(T t) { return R { S(t, "b", "a") }; } + template R blue_on_red(T t) { return R { S(t, "b", "r") }; } + template R blue_on_purple(T t) { return R { S(t, "b", "p") }; } + template R blue_on_yellow(T t) { return R { S(t, "b", "y") }; } + template R blue_on_white(T t) { return R { S(t, "b", "w") }; } + template R blue_on_grey(T t) { return R { S(t, "b", "e") }; } + template R blue_on_light_blue(T t) { return R { S(t, "b", "lb") }; } + template R blue_on_light_green(T t) { return R { S(t, "b", "lg") }; } + template R blue_on_light_aqua(T t) { return R { S(t, "b", "la") }; } + template R blue_on_light_red(T t) { return R { S(t, "b", "lr") }; } + template R blue_on_light_purple(T t) { return R { S(t, "b", "lp") }; } + template R blue_on_light_yellow(T t) { return R { S(t, "b", "ly") }; } + template R blue_on_bright_white(T t) { return R { S(t, "b", "bw") }; } + template R green_on_black(T t) { return R { S(t, "g", "k") }; } + template R green_on_blue(T t) { return R { S(t, "g", "b") }; } + template R green_on_green(T t) { return R { S(t, "g", "g") }; } + template R green_on_aqua(T t) { return R { S(t, "g", "a") }; } + template R green_on_red(T t) { return R { S(t, "g", "r") }; } + template R green_on_purple(T t) { return R { S(t, "g", "p") }; } + template R green_on_yellow(T t) { return R { S(t, "g", "y") }; } + template R green_on_white(T t) { return R { S(t, "g", "w") }; } + template R green_on_grey(T t) { return R { S(t, "g", "e") }; } + template R green_on_light_blue(T t) { return R { S(t, "g", "lb") }; } + template R green_on_light_green(T t) { return R { S(t, "g", "lg") }; } + template R green_on_light_aqua(T t) { return R { S(t, "g", "la") }; } + template R green_on_light_red(T t) { return R { S(t, "g", "lr") }; } + template R green_on_light_purple(T t) { return R { S(t, "g", "lp") }; } + template R green_on_light_yellow(T t) { return R { S(t, "g", "ly") }; } + template R green_on_bright_white(T t) { return R { S(t, "g", "bw") }; } + template R aqua_on_black(T t) { return R { S(t, "a", "k") }; } + template R aqua_on_blue(T t) { return R { S(t, "a", "b") }; } + template R aqua_on_green(T t) { return R { S(t, "a", "g") }; } + template R aqua_on_aqua(T t) { return R { S(t, "a", "a") }; } + template R aqua_on_red(T t) { return R { S(t, "a", "r") }; } + template R aqua_on_purple(T t) { return R { S(t, "a", "p") }; } + template R aqua_on_yellow(T t) { return R { S(t, "a", "y") }; } + template R aqua_on_white(T t) { return R { S(t, "a", "w") }; } + template R aqua_on_grey(T t) { return R { S(t, "a", "e") }; } + template R aqua_on_light_blue(T t) { return R { S(t, "a", "lb") }; } + template R aqua_on_light_green(T t) { return R { S(t, "a", "lg") }; } + template R aqua_on_light_aqua(T t) { return R { S(t, "a", "la") }; } + template R aqua_on_light_red(T t) { return R { S(t, "a", "lr") }; } + template R aqua_on_light_purple(T t) { return R { S(t, "a", "lp") }; } + template R aqua_on_light_yellow(T t) { return R { S(t, "a", "ly") }; } + template R aqua_on_bright_white(T t) { return R { S(t, "a", "bw") }; } + template R red_on_black(T t) { return R { S(t, "r", "k") }; } + template R red_on_blue(T t) { return R { S(t, "r", "b") }; } + template R red_on_green(T t) { return R { S(t, "r", "g") }; } + template R red_on_aqua(T t) { return R { S(t, "r", "a") }; } + template R red_on_red(T t) { return R { S(t, "r", "r") }; } + template R red_on_purple(T t) { return R { S(t, "r", "p") }; } + template R red_on_yellow(T t) { return R { S(t, "r", "y") }; } + template R red_on_white(T t) { return R { S(t, "r", "w") }; } + template R red_on_grey(T t) { return R { S(t, "r", "e") }; } + template R red_on_light_blue(T t) { return R { S(t, "r", "lb") }; } + template R red_on_light_green(T t) { return R { S(t, "r", "lg") }; } + template R red_on_light_aqua(T t) { return R { S(t, "r", "la") }; } + template R red_on_light_red(T t) { return R { S(t, "r", "lr") }; } + template R red_on_light_purple(T t) { return R { S(t, "r", "lp") }; } + template R red_on_light_yellow(T t) { return R { S(t, "r", "ly") }; } + template R red_on_bright_white(T t) { return R { S(t, "r", "bw") }; } + template R purple_on_black(T t) { return R { S(t, "p", "k") }; } + template R purple_on_blue(T t) { return R { S(t, "p", "b") }; } + template R purple_on_green(T t) { return R { S(t, "p", "g") }; } + template R purple_on_aqua(T t) { return R { S(t, "p", "a") }; } + template R purple_on_red(T t) { return R { S(t, "p", "r") }; } + template R purple_on_purple(T t) { return R { S(t, "p", "p") }; } + template R purple_on_yellow(T t) { return R { S(t, "p", "y") }; } + template R purple_on_white(T t) { return R { S(t, "p", "w") }; } + template R purple_on_grey(T t) { return R { S(t, "p", "e") }; } + template R purple_on_light_blue(T t) { return R { S(t, "p", "lb") }; } + template R purple_on_light_green(T t) { return R { S(t, "p", "lg") }; } + template R purple_on_light_aqua(T t) { return R { S(t, "p", "la") }; } + template R purple_on_light_red(T t) { return R { S(t, "p", "lr") }; } + template R purple_on_light_purple(T t) { return R { S(t, "p", "lp") }; } + template R purple_on_light_yellow(T t) { return R { S(t, "p", "ly") }; } + template R purple_on_bright_white(T t) { return R { S(t, "p", "bw") }; } + template R yellow_on_black(T t) { return R { S(t, "y", "k") }; } + template R yellow_on_blue(T t) { return R { S(t, "y", "b") }; } + template R yellow_on_green(T t) { return R { S(t, "y", "g") }; } + template R yellow_on_aqua(T t) { return R { S(t, "y", "a") }; } + template R yellow_on_red(T t) { return R { S(t, "y", "r") }; } + template R yellow_on_purple(T t) { return R { S(t, "y", "p") }; } + template R yellow_on_yellow(T t) { return R { S(t, "y", "y") }; } + template R yellow_on_white(T t) { return R { S(t, "y", "w") }; } + template R yellow_on_grey(T t) { return R { S(t, "y", "e") }; } + template R yellow_on_light_blue(T t) { return R { S(t, "y", "lb") }; } + template R yellow_on_light_green(T t) { return R { S(t, "y", "lg") }; } + template R yellow_on_light_aqua(T t) { return R { S(t, "y", "la") }; } + template R yellow_on_light_red(T t) { return R { S(t, "y", "lr") }; } + template R yellow_on_light_purple(T t) { return R { S(t, "y", "lp") }; } + template R yellow_on_light_yellow(T t) { return R { S(t, "y", "ly") }; } + template R yellow_on_bright_white(T t) { return R { S(t, "y", "bw") }; } + template R white_on_black(T t) { return R { S(t, "w", "k") }; } + template R white_on_blue(T t) { return R { S(t, "w", "b") }; } + template R white_on_green(T t) { return R { S(t, "w", "g") }; } + template R white_on_aqua(T t) { return R { S(t, "w", "a") }; } + template R white_on_red(T t) { return R { S(t, "w", "r") }; } + template R white_on_purple(T t) { return R { S(t, "w", "p") }; } + template R white_on_yellow(T t) { return R { S(t, "w", "y") }; } + template R white_on_white(T t) { return R { S(t, "w", "w") }; } + template R white_on_grey(T t) { return R { S(t, "w", "e") }; } + template R white_on_light_blue(T t) { return R { S(t, "w", "lb") }; } + template R white_on_light_green(T t) { return R { S(t, "w", "lg") }; } + template R white_on_light_aqua(T t) { return R { S(t, "w", "la") }; } + template R white_on_light_red(T t) { return R { S(t, "w", "lr") }; } + template R white_on_light_purple(T t) { return R { S(t, "w", "lp") }; } + template R white_on_light_yellow(T t) { return R { S(t, "w", "ly") }; } + template R white_on_bright_white(T t) { return R { S(t, "w", "bw") }; } + template R grey_on_black(T t) { return R { S(t, "e", "k") }; } + template R grey_on_blue(T t) { return R { S(t, "e", "b") }; } + template R grey_on_green(T t) { return R { S(t, "e", "g") }; } + template R grey_on_aqua(T t) { return R { S(t, "e", "a") }; } + template R grey_on_red(T t) { return R { S(t, "e", "r") }; } + template R grey_on_purple(T t) { return R { S(t, "e", "p") }; } + template R grey_on_yellow(T t) { return R { S(t, "e", "y") }; } + template R grey_on_white(T t) { return R { S(t, "e", "w") }; } + template R grey_on_grey(T t) { return R { S(t, "e", "e") }; } + template R grey_on_light_blue(T t) { return R { S(t, "e", "lb") }; } + template R grey_on_light_green(T t) { return R { S(t, "e", "lg") }; } + template R grey_on_light_aqua(T t) { return R { S(t, "e", "la") }; } + template R grey_on_light_red(T t) { return R { S(t, "e", "lr") }; } + template R grey_on_light_purple(T t) { return R { S(t, "e", "lp") }; } + template R grey_on_light_yellow(T t) { return R { S(t, "e", "ly") }; } + template R grey_on_bright_white(T t) { return R { S(t, "e", "bw") }; } + template R light_blue_on_black(T t) { return R { S(t, "lb", "k") }; } + template R light_blue_on_blue(T t) { return R { S(t, "lb", "b") }; } + template R light_blue_on_green(T t) { return R { S(t, "lb", "g") }; } + template R light_blue_on_aqua(T t) { return R { S(t, "lb", "a") }; } + template R light_blue_on_red(T t) { return R { S(t, "lb", "r") }; } + template R light_blue_on_purple(T t) { return R { S(t, "lb", "p") }; } + template R light_blue_on_yellow(T t) { return R { S(t, "lb", "y") }; } + template R light_blue_on_white(T t) { return R { S(t, "lb", "w") }; } + template R light_blue_on_grey(T t) { return R { S(t, "lb", "e") }; } + template R light_blue_on_light_blue(T t) { return R { S(t, "lb", "lb") }; } + template R light_blue_on_light_green(T t) { return R { S(t, "lb", "lg") }; } + template R light_blue_on_light_aqua(T t) { return R { S(t, "lb", "la") }; } + template R light_blue_on_light_red(T t) { return R { S(t, "lb", "lr") }; } + template R light_blue_on_light_purple(T t) { return R { S(t, "lb", "lp") }; } + template R light_blue_on_light_yellow(T t) { return R { S(t, "lb", "ly") }; } + template R light_blue_on_bright_white(T t) { return R { S(t, "lb", "bw") }; } + template R light_green_on_black(T t) { return R { S(t, "lg", "k") }; } + template R light_green_on_blue(T t) { return R { S(t, "lg", "b") }; } + template R light_green_on_green(T t) { return R { S(t, "lg", "g") }; } + template R light_green_on_aqua(T t) { return R { S(t, "lg", "a") }; } + template R light_green_on_red(T t) { return R { S(t, "lg", "r") }; } + template R light_green_on_purple(T t) { return R { S(t, "lg", "p") }; } + template R light_green_on_yellow(T t) { return R { S(t, "lg", "y") }; } + template R light_green_on_white(T t) { return R { S(t, "lg", "w") }; } + template R light_green_on_grey(T t) { return R { S(t, "lg", "e") }; } + template R light_green_on_light_blue(T t) { return R { S(t, "lg", "lb") }; } + template R light_green_on_light_green(T t) { return R { S(t, "lg", "lg") }; } + template R light_green_on_light_aqua(T t) { return R { S(t, "lg", "la") }; } + template R light_green_on_light_red(T t) { return R { S(t, "lg", "lr") }; } + template R light_green_on_light_purple(T t) { return R { S(t, "lg", "lp") }; } + template R light_green_on_light_yellow(T t) { return R { S(t, "lg", "ly") }; } + template R light_green_on_bright_white(T t) { return R { S(t, "lg", "bw") }; } + template R light_aqua_on_black(T t) { return R { S(t, "la", "k") }; } + template R light_aqua_on_blue(T t) { return R { S(t, "la", "b") }; } + template R light_aqua_on_green(T t) { return R { S(t, "la", "g") }; } + template R light_aqua_on_aqua(T t) { return R { S(t, "la", "a") }; } + template R light_aqua_on_red(T t) { return R { S(t, "la", "r") }; } + template R light_aqua_on_purple(T t) { return R { S(t, "la", "p") }; } + template R light_aqua_on_yellow(T t) { return R { S(t, "la", "y") }; } + template R light_aqua_on_white(T t) { return R { S(t, "la", "w") }; } + template R light_aqua_on_grey(T t) { return R { S(t, "la", "e") }; } + template R light_aqua_on_light_blue(T t) { return R { S(t, "la", "lb") }; } + template R light_aqua_on_light_green(T t) { return R { S(t, "la", "lg") }; } + template R light_aqua_on_light_aqua(T t) { return R { S(t, "la", "la") }; } + template R light_aqua_on_light_red(T t) { return R { S(t, "la", "lr") }; } + template R light_aqua_on_light_purple(T t) { return R { S(t, "la", "lp") }; } + template R light_aqua_on_light_yellow(T t) { return R { S(t, "la", "ly") }; } + template R light_aqua_on_bright_white(T t) { return R { S(t, "la", "bw") }; } + template R light_red_on_black(T t) { return R { S(t, "lr", "k") }; } + template R light_red_on_blue(T t) { return R { S(t, "lr", "b") }; } + template R light_red_on_green(T t) { return R { S(t, "lr", "g") }; } + template R light_red_on_aqua(T t) { return R { S(t, "lr", "a") }; } + template R light_red_on_red(T t) { return R { S(t, "lr", "r") }; } + template R light_red_on_purple(T t) { return R { S(t, "lr", "p") }; } + template R light_red_on_yellow(T t) { return R { S(t, "lr", "y") }; } + template R light_red_on_white(T t) { return R { S(t, "lr", "w") }; } + template R light_red_on_grey(T t) { return R { S(t, "lr", "e") }; } + template R light_red_on_light_blue(T t) { return R { S(t, "lr", "lb") }; } + template R light_red_on_light_green(T t) { return R { S(t, "lr", "lg") }; } + template R light_red_on_light_aqua(T t) { return R { S(t, "lr", "la") }; } + template R light_red_on_light_red(T t) { return R { S(t, "lr", "lr") }; } + template R light_red_on_light_purple(T t) { return R { S(t, "lr", "lp") }; } + template R light_red_on_light_yellow(T t) { return R { S(t, "lr", "ly") }; } + template R light_red_on_bright_white(T t) { return R { S(t, "lr", "bw") }; } + template R light_purple_on_black(T t) { return R { S(t, "lp", "k") }; } + template R light_purple_on_blue(T t) { return R { S(t, "lp", "b") }; } + template R light_purple_on_green(T t) { return R { S(t, "lp", "g") }; } + template R light_purple_on_aqua(T t) { return R { S(t, "lp", "a") }; } + template R light_purple_on_red(T t) { return R { S(t, "lp", "r") }; } + template R light_purple_on_purple(T t) { return R { S(t, "lp", "p") }; } + template R light_purple_on_yellow(T t) { return R { S(t, "lp", "y") }; } + template R light_purple_on_white(T t) { return R { S(t, "lp", "w") }; } + template R light_purple_on_grey(T t) { return R { S(t, "lp", "e") }; } + template R light_purple_on_light_blue(T t) { return R { S(t, "lp", "lb") }; } + template R light_purple_on_light_green(T t) { return R { S(t, "lp", "lg") }; } + template R light_purple_on_light_aqua(T t) { return R { S(t, "lp", "la") }; } + template R light_purple_on_light_red(T t) { return R { S(t, "lp", "lr") }; } + template R light_purple_on_light_purple(T t) { return R { S(t, "lp", "lp") }; } + template R light_purple_on_light_yellow(T t) { return R { S(t, "lp", "ly") }; } + template R light_purple_on_bright_white(T t) { return R { S(t, "lp", "bw") }; } + template R light_yellow_on_black(T t) { return R { S(t, "ly", "k") }; } + template R light_yellow_on_blue(T t) { return R { S(t, "ly", "b") }; } + template R light_yellow_on_green(T t) { return R { S(t, "ly", "g") }; } + template R light_yellow_on_aqua(T t) { return R { S(t, "ly", "a") }; } + template R light_yellow_on_red(T t) { return R { S(t, "ly", "r") }; } + template R light_yellow_on_purple(T t) { return R { S(t, "ly", "p") }; } + template R light_yellow_on_yellow(T t) { return R { S(t, "ly", "y") }; } + template R light_yellow_on_white(T t) { return R { S(t, "ly", "w") }; } + template R light_yellow_on_grey(T t) { return R { S(t, "ly", "e") }; } + template R light_yellow_on_light_blue(T t) { return R { S(t, "ly", "lb") }; } + template R light_yellow_on_light_green(T t) { return R { S(t, "ly", "lg") }; } + template R light_yellow_on_light_aqua(T t) { return R { S(t, "ly", "la") }; } + template R light_yellow_on_light_red(T t) { return R { S(t, "ly", "lr") }; } + template R light_yellow_on_light_purple(T t) { return R { S(t, "ly", "lp") }; } + template R light_yellow_on_light_yellow(T t) { return R { S(t, "ly", "ly") }; } + template R light_yellow_on_bright_white(T t) { return R { S(t, "ly", "bw") }; } + template R bright_white_on_black(T t) { return R { S(t, "bw", "k") }; } + template R bright_white_on_blue(T t) { return R { S(t, "bw", "b") }; } + template R bright_white_on_green(T t) { return R { S(t, "bw", "g") }; } + template R bright_white_on_aqua(T t) { return R { S(t, "bw", "a") }; } + template R bright_white_on_red(T t) { return R { S(t, "bw", "r") }; } + template R bright_white_on_purple(T t) { return R { S(t, "bw", "p") }; } + template R bright_white_on_yellow(T t) { return R { S(t, "bw", "y") }; } + template R bright_white_on_white(T t) { return R { S(t, "bw", "w") }; } + template R bright_white_on_grey(T t) { return R { S(t, "bw", "e") }; } + template R bright_white_on_light_blue(T t) { return R { S(t, "bw", "lb") }; } + template R bright_white_on_light_green(T t) { return R { S(t, "bw", "lg") }; } + template R bright_white_on_light_aqua(T t) { return R { S(t, "bw", "la") }; } + template R bright_white_on_light_red(T t) { return R { S(t, "bw", "lr") }; } + template R bright_white_on_light_purple(T t) { return R { S(t, "bw", "lp") }; } + template R bright_white_on_light_yellow(T t) { return R { S(t, "bw", "ly") }; } + template R bright_white_on_bright_white(T t) { return R { S(t, "bw", "bw") }; } +} + +#endif \ No newline at end of file diff --git a/src/app-sdk/tests/InitializationExe/main.cpp b/src/app-sdk/tests/InitializationExe/main.cpp new file mode 100644 index 000000000..975fd8e6c --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/main.cpp @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App SDK and should be used +// in your Windows application via an official binary distribution. +// Further information: https://aka.ms/midi +// ============================================================================ + + +#pragma once + +#include "pch.h" + +#include "color.hpp" + +// TEMP +#define VERIFY_IS_TRUE(c) c +#define VERIFY_IS_FALSE(c) c + + +int __cdecl main() +{ + // system("pause"); + + winrt::init_apartment(); + + // first init + { + init::MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Midi Time is: " << midi2::MidiClock::Now() << std::endl; + + // check COM reference count + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + } + + // second init + { + init::MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Midi Time is: " << midi2::MidiClock::Now() << std::endl; + + // todo: check COM reference count + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + } + + + // nested init + { + init::MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Outer Midi Time 1 is: " << midi2::MidiClock::Now() << std::endl; + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + + { + init::MidiDesktopAppSdkInitializer initializer2; + + VERIFY_IS_TRUE(initializer2.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer2.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + //std::cout << "Nested Midi Time is: " << midi2::MidiClock::Now() << std::endl; + //std::cout << "'" << winrt::to_string(midi2::MidiChannel::LongLabelPlural()) << "' is the long plural version of 'Channel'" << std::endl; + + // todo: check COM reference count + + + { + init::MidiDesktopAppSdkInitializer initializer3; + + VERIFY_IS_TRUE(initializer3.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer3.EnsureServiceAvailable()); + + //std::cout << "\nThis ref count should be 3" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer3.TESTGetCurrentRefCount() << std::endl; + initializer3.ShutdownSdkRuntime(); + } + + + + //std::cout << "\nThis ref count should be 2" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer2.TESTGetCurrentRefCount() << std::endl; + initializer2.ShutdownSdkRuntime(); + + // this should return false because this is unloaded + VERIFY_IS_FALSE(initializer2.CheckForMinimumRequiredSdkVersion(0, 0, 0)); + + } + + // call into initializer after shutting down the second instance of it + VERIFY_IS_TRUE(initializer.CheckForMinimumRequiredSdkVersion(0, 0, 0)); + VERIFY_IS_FALSE(initializer.CheckForMinimumRequiredSdkVersion(99, 0, 0)); + + + // verify we can still resolve the type and call code in the implementation + std::cout << "Outer Midi Time 2 is: " << midi2::MidiClock::Now() << std::endl; + // use a different type just in case there's caching + std::cout << "'" << winrt::to_string(midi2::MidiGroup::LongLabelPlural()) << "' is the long plural version of 'Group'" << std::endl; + + // todo: check COM reference count + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + + // this should return false. If it doesn't, our ref counts are messed up + VERIFY_IS_FALSE(initializer.CheckForMinimumRequiredSdkVersion(0, 0, 0)); + + } + + winrt::uninit_apartment(); +} diff --git a/src/app-sdk/tests/InitializationExe/packages.config b/src/app-sdk/tests/InitializationExe/packages.config new file mode 100644 index 000000000..b6db04bde --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/app-sdk/tests/InitializationExe/pch.cpp b/src/app-sdk/tests/InitializationExe/pch.cpp new file mode 100644 index 000000000..b437ca7e5 --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/pch.cpp @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App SDK and should be used +// in your Windows application via an official binary distribution. +// Further information: https://aka.ms/midi +// ============================================================================ + + +#include "pch.h" diff --git a/src/app-sdk/tests/InitializationExe/pch.h b/src/app-sdk/tests/InitializationExe/pch.h new file mode 100644 index 000000000..bd979f66a --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/pch.h @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation and Contributors. +// Licensed under the MIT License +// ============================================================================ +// This is part of the Windows MIDI Services App SDK and should be used +// in your Windows application via an official binary distribution. +// Further information: https://aka.ms/midi +// ============================================================================ + + +#pragma once + +#pragma warning (push) +#pragma warning (disable: 4005) + + +#include +#include + +#include + +//#pragma warning (disable: 4005) +//#include +//#pragma warning (pop) + + +#include +#include +#include + + +#include +#include +#include +#include + +namespace foundation = winrt::Windows::Foundation; +namespace collections = winrt::Windows::Foundation::Collections; + + +#include +#include +#include +#include +#include + + +namespace midi2 = winrt::Microsoft::Windows::Devices::Midi2; +namespace diag = winrt::Microsoft::Windows::Devices::Midi2::Diagnostics; +namespace rept = winrt::Microsoft::Windows::Devices::Midi2::Reporting; +namespace svc = winrt::Microsoft::Windows::Devices::Midi2::ServiceConfig; + + + + +#include "combaseapi.h" +#include +#include + +#include +#include +#include + +#include + +#include "winmidi/init/Microsoft.Windows.Devices.Midi2.Initialization.hpp" +namespace init = Microsoft::Windows::Devices::Midi2::Initialization; + +#include "wstring_util.h" +#include "MidiDefs.h" + +namespace internal = ::WindowsMidiServicesInternal; + +#pragma warning (pop) \ No newline at end of file diff --git a/src/app-sdk/tests/InitializationExe/sdkinittest.vcxproj b/src/app-sdk/tests/InitializationExe/sdkinittest.vcxproj new file mode 100644 index 000000000..8c930f60c --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/sdkinittest.vcxproj @@ -0,0 +1,198 @@ + + + + + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 + true + true + true + true + 15.0 + {26E8B9E3-1C56-4BCB-A635-676F5525EA65} + Win32Proj + sdkinittests + 10.0 + 10.0.20348.0 + sdk-init-tests-exe + + + + + + Debug + ARM64 + + + Debug + ARM64EC + + + Release + ARM64 + + + Debug + x64 + + + Release + ARM64EC + + + Release + x64 + + + + Application + v143 + v142 + v141 + v140 + Unicode + + + true + true + + + false + true + false + + + + + + + + + + + + $(SolutionDir)vsfiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)vsfiles\out\$(ProjectName)\$(Platform)\$(Configuration)\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)vsfiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)vsfiles\out\$(ProjectName)\$(Platform)\$(Configuration)\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)vsfiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)vsfiles\out\$(ProjectName)\$(Platform)\$(Configuration)\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)vsfiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)vsfiles\out\$(ProjectName)\$(Platform)\$(Configuration)\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)vsfiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)vsfiles\out\$(ProjectName)\$(Platform)\$(Configuration)\ + $(IncludePath) + $(LibraryPath) + + + $(SolutionDir)vsfiles\intermediate\$(ProjectName)\$(Platform)\$(Configuration)\ + $(SolutionDir)vsfiles\out\$(ProjectName)\$(Platform)\$(Configuration)\ + $(IncludePath) + $(LibraryPath) + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + stdcpp20 + stdcpp20 + stdcpp20 + %(AdditionalIncludeDirectories);$(SolutionDir)..\api\inc;$(SolutionDir)..\..\build\staging\version\;$(SolutionDir)client-initialization-redist;$(GeneratedFilesDir) + %(AdditionalIncludeDirectories);$(SolutionDir)..\api\inc;$(SolutionDir)..\..\build\staging\version\;$(SolutionDir)client-initialization-redist;$(GeneratedFilesDir) + %(AdditionalIncludeDirectories);$(SolutionDir)..\api\inc;$(SolutionDir)..\..\build\staging\version\;$(SolutionDir)client-initialization-redist;$(GeneratedFilesDir) + + + Console + false + ntdll.lib;%(AdditionalDependencies);winmm.lib;version.lib + ntdll.lib;%(AdditionalDependencies);winmm.lib;version.lib + ntdll.lib;%(AdditionalDependencies);winmm.lib;version.lib + + + + + WIN32;%(PreprocessorDefinitions) + stdcpp20 + + + ntdll.lib;%(AdditionalDependencies) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + stdcpp20 + stdcpp20 + stdcpp20 + %(AdditionalIncludeDirectories);$(SolutionDir)..\api\inc;$(SolutionDir)..\..\build\staging\version\;$(SolutionDir)client-initialization-redist;$(GeneratedFilesDir) + %(AdditionalIncludeDirectories);$(SolutionDir)..\api\inc;$(SolutionDir)..\..\build\staging\version\;$(SolutionDir)client-initialization-redist;$(GeneratedFilesDir) + %(AdditionalIncludeDirectories);$(SolutionDir)..\api\inc;$(SolutionDir)..\..\build\staging\version\;$(SolutionDir)client-initialization-redist;$(GeneratedFilesDir) + + + Console + true + true + false + ntdll.lib;%(AdditionalDependencies);winmm.lib;version.lib + ntdll.lib;%(AdditionalDependencies);winmm.lib;version.lib + ntdll.lib;%(AdditionalDependencies);winmm.lib;version.lib + + + + + + + + + + Create + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + \ No newline at end of file diff --git a/src/app-sdk/tests/InitializationExe/sdkinittest.vcxproj.filters b/src/app-sdk/tests/InitializationExe/sdkinittest.vcxproj.filters new file mode 100644 index 000000000..d58691aee --- /dev/null +++ b/src/app-sdk/tests/InitializationExe/sdkinittest.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/src/app-sdk/tests/Offline.unittests/Offline.unittests.vcxproj b/src/app-sdk/tests/Offline.unittests/Offline.unittests.vcxproj index 2d35c0626..c7d2a812f 100644 --- a/src/app-sdk/tests/Offline.unittests/Offline.unittests.vcxproj +++ b/src/app-sdk/tests/Offline.unittests/Offline.unittests.vcxproj @@ -1,4 +1,4 @@ - + @@ -20,7 +20,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.2-preview-8.241123-1919 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 17.0 {BCAE6353-5F32-4AE8-9949-039A2BC2C8C6} Win32Proj diff --git a/src/app-sdk/tests/Offline.unittests/packages.config b/src/app-sdk/tests/Offline.unittests/packages.config index 3414594b7..b6db04bde 100644 --- a/src/app-sdk/tests/Offline.unittests/packages.config +++ b/src/app-sdk/tests/Offline.unittests/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/app-sdk/tests/Offline.unittests/stdafx.h b/src/app-sdk/tests/Offline.unittests/stdafx.h index 5a8022e44..a927ad997 100644 --- a/src/app-sdk/tests/Offline.unittests/stdafx.h +++ b/src/app-sdk/tests/Offline.unittests/stdafx.h @@ -4,6 +4,10 @@ #ifndef STDAFX_H #define STDAFX_H +#pragma warning (push) +#pragma warning (disable: 4005) + + #include #include @@ -69,4 +73,7 @@ namespace init = Microsoft::Windows::Devices::Midi2::Initialization; #define LOG_OUTPUT(fmt, ...) WEX::Logging::Log::Comment(WEX::Common::String().Format(fmt, __VA_ARGS__)) #endif + +#pragma warning (pop) + #endif \ No newline at end of file diff --git a/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.cpp b/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.cpp index a0e5cdf98..4019bb94b 100644 --- a/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.cpp +++ b/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.cpp @@ -9,35 +9,300 @@ #include "stdafx.h" +#include "Microsoft.Windows.Devices.Midi2.Initialization.hpp" + +#include +#include + +#include "winstring.h" +#include "roapi.h" + +using namespace Microsoft::Windows::Devices::Midi2::Initialization; + +bool MidiAppSdkInitializationTests::ClassSetup() +{ + winrt::init_apartment(); + + return true; +} + +bool MidiAppSdkInitializationTests::ClassCleanup() +{ + winrt::uninit_apartment(); + + return true; +} + + +HRESULT CheckClassActivation(const winrt::hstring& activatableClassId) +{ + HSTRING clsid; + IInspectable* instance; + + RETURN_IF_FAILED(WindowsCreateString(activatableClassId.c_str(), activatableClassId.size(), &clsid)); + RETURN_IF_FAILED(RoActivateInstance(clsid, &instance)); + + RETURN_IF_FAILED(WindowsDeleteString(clsid)); + + return S_OK; +} + + + +HRESULT CheckClassRegistration(const winrt::hstring& activatableClassId, winrt::guid expectedClsid) +{ + HSTRING clsid; + IInspectable* instance; + + RETURN_IF_FAILED(WindowsCreateString(activatableClassId.c_str(), activatableClassId.size(), &clsid)); + RETURN_IF_FAILED(RoActivateInstance(clsid, &instance)); + + RETURN_IF_FAILED(WindowsDeleteString(clsid)); + + ULONG iidCount; + IID* iids; + + bool found{ false }; + + RETURN_IF_FAILED(instance->GetIids(&iidCount, &iids)); + RETURN_HR_IF_NULL(E_UNEXPECTED, iids); + + for (ULONG i = 0; i < iidCount; i++) + { + winrt::guid checkGuid = iids[i]; + + std::cout << winrt::to_string(winrt::to_hstring(checkGuid)) << std::endl; + + if (checkGuid == expectedClsid) + { + found = true; + break; + } + } + + CoTaskMemFree(iids); + + //if (found) + //{ + return S_OK; + //} + //else + //{ + // return E_NOINTERFACE; + //} + +} + + + void MidiAppSdkInitializationTests::TestInitialization() { - // check to see if SDK is installed. Obviously, for this test to work, it has to - // be run on a system with a valid SDK installation + std::shared_ptr initializer; + + initializer = std::make_shared(); + + VERIFY_IS_TRUE(initializer->InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer->EnsureServiceAvailable()); + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer->TESTGetCurrentRefCount() << std::endl; + initializer->ShutdownSdkRuntime(); + + initializer.reset(); + + // test the test + VERIFY_FAILED(CheckClassActivation(L"Microsoft.Windows.Devices.Midi2.BogusClassNameThatDoesntExist")); + + // Verify we can't activate an SDK type. The SDK is unloaded + // so the detours should be returned back to normal + VERIFY_FAILED(CheckClassActivation(L"Microsoft.Windows.Devices.Midi2.MidiGroup")); +} + +void MidiAppSdkInitializationTests::TestMultipleInitialization() +{ + // first init + { + MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Midi Time is: " << MidiClock::Now() << std::endl; + + // todo: check COM reference count + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + } + + // we shouldn't be able to resolve this here + VERIFY_FAILED(CheckClassActivation(L"Microsoft.Windows.Devices.Midi2.MidiGroup")); - //VERIFY_IS_TRUE(MidiServicesInitializer::IsCompatibleDesktopAppSdkRuntimeInstalled()); + // second init + { + MidiDesktopAppSdkInitializer initializer; - //VERIFY_IS_TRUE(MidiServicesInitializer::InitializeDesktopAppSdkRuntime()); - //VERIFY_IS_TRUE(MidiServicesInitializer::ShutdownDesktopAppSdkRuntime()); + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Midi Time is: " << MidiClock::Now() << std::endl; + + // todo: check COM reference count + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + } + + // we shouldn't be able to resolve this here + VERIFY_FAILED(CheckClassActivation(L"Microsoft.Windows.Devices.Midi2.MidiGroup")); + + // nested init + { + MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Outer Midi Time 1 is: " << MidiClock::Now() << std::endl; + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + + { + MidiDesktopAppSdkInitializer initializer2; + + VERIFY_IS_TRUE(initializer2.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer2.EnsureServiceAvailable()); + + // verify we can resolve the type and call code in the implementation + std::cout << "Nested Midi Time is: " << MidiClock::Now() << std::endl; + std::cout << "'" << winrt::to_string(MidiChannel::LongLabelPlural()) << "' is the long plural version of 'Channel'" << std::endl; + + + //std::cout << "\nThis ref count should be 2" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer2.TESTGetCurrentRefCount() << std::endl; + + { + MidiDesktopAppSdkInitializer initializer3; + + VERIFY_IS_TRUE(initializer3.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer3.EnsureServiceAvailable()); + + //std::cout << "\nThis ref count should be 3" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer3.TESTGetCurrentRefCount() << std::endl; + initializer3.ShutdownSdkRuntime(); + } + + { + MidiDesktopAppSdkInitializer initializer3; + + VERIFY_IS_TRUE(initializer3.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer3.EnsureServiceAvailable()); + + //std::cout << "\nThis ref count should be 3" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer3.TESTGetCurrentRefCount() << std::endl; + initializer3.ShutdownSdkRuntime(); + } + + { + MidiDesktopAppSdkInitializer initializer3; + + VERIFY_IS_TRUE(initializer3.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer3.EnsureServiceAvailable()); + + //std::cout << "\nThis ref count should be 3" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer3.TESTGetCurrentRefCount() << std::endl; + initializer3.ShutdownSdkRuntime(); + } + + + //std::cout << "\nThis ref count should be 2" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer2.TESTGetCurrentRefCount() << std::endl; + initializer2.ShutdownSdkRuntime(); + + // this should return false because this is unloaded + VERIFY_IS_FALSE(initializer2.CheckForMinimumRequiredSdkVersion(0, 0, 0)); + + } + + // call into initializer after shutting down the second instance of it + VERIFY_IS_TRUE(initializer.CheckForMinimumRequiredSdkVersion(0, 0, 0)); + VERIFY_IS_FALSE(initializer.CheckForMinimumRequiredSdkVersion(99, 0, 0)); + + + // verify we can still resolve the type and call code in the implementation + std::cout << "Outer Midi Time 2 is: " << MidiClock::Now() << std::endl; + // use a different type just in case there's caching + std::cout << "'" << winrt::to_string(MidiGroup::LongLabelPlural()) << "' is the long plural version of 'Group'" << std::endl; + + // todo: check COM reference count + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + + // this should return false. If it doesn't, our ref counts are messed up + VERIFY_IS_FALSE(initializer.CheckForMinimumRequiredSdkVersion(0, 0, 0)); + + } + + // we shouldn't be able to resolve this here + VERIFY_FAILED(CheckClassActivation(L"Microsoft.Windows.Devices.Midi2.MidiGroup")); } void MidiAppSdkInitializationTests::TestResolvingTypes() { - //VERIFY_IS_TRUE(MidiServicesInitializer::InitializeDesktopAppSdkRuntime()); + MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); - //auto cleanup = wil::scope_exit([&] - // { - // VERIFY_IS_TRUE(MidiServicesInitializer::ShutdownDesktopAppSdkRuntime()); - // }); + // TODO: test resolving every type - //// try to create the types - //std::wcout << L"MidiClock::Now() " << MidiClock::Now() << std::endl; + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); +} + - +void MidiAppSdkInitializationTests::ValidateBackwardsCompatibilityWinMD() +{ + MidiDesktopAppSdkInitializer initializer; + + VERIFY_IS_TRUE(initializer.InitializeSdkRuntime()); + VERIFY_IS_TRUE(initializer.EnsureServiceAvailable()); + + // TODO: validate that the clsids of each type have not changed + + // todo: use the xlang metadata parser to pull in our reference winmd and try to + // resolve and activate the types in it. + + + VERIFY_SUCCEEDED(CheckClassRegistration(L"Microsoft.Windows.Devices.Midi2.MidiChannel", winrt::guid())); + //VERIFY_SUCCEEDED(CheckClassRegistration(L"Microsoft.Windows.Devices.Midi2.IMidiChannelStatics", winrt::guid())); + VERIFY_SUCCEEDED(CheckClassRegistration(L"Microsoft.Windows.Devices.Midi2.MidiGroup", winrt::guid())); + //VERIFY_SUCCEEDED(CheckClassRegistration(L"Microsoft.Windows.Devices.Midi2.IMidiGroupStatics", winrt::guid())); + + // not all types can be activated, because many use factory methods from other classes. Example: midisession, midiconnection + //VERIFY_SUCCEEDED(CheckClassRegistration(L"Microsoft.Windows.Devices.Midi2.MidiSession", winrt::guid())); + + //std::cout << "\nThis ref count should be 1" << std::endl; + //std::cout << "Ref count before shutdown: " << initializer.TESTGetCurrentRefCount() << std::endl; + initializer.ShutdownSdkRuntime(); + } + + + + diff --git a/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.h b/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.h index 2a0fbe128..9fcfa5aea 100644 --- a/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.h +++ b/src/app-sdk/tests/SdkInitialization.unittests/MidiAppSdkInitializationTests.h @@ -17,16 +17,18 @@ class MidiAppSdkInitializationTests BEGIN_TEST_CLASS(MidiAppSdkInitializationTests) TEST_CLASS_PROPERTY(L"TestClassification", L"Unit") TEST_CLASS_PROPERTY(L"BinaryUnderTest", L"WindowsMidiServicesClientInitialization.dll") - END_TEST_CLASS() + END_TEST_CLASS() - //TEST_CLASS_SETUP(ClassSetup); - //TEST_CLASS_CLEANUP(ClassCleanup); + TEST_CLASS_SETUP(ClassSetup); + TEST_CLASS_CLEANUP(ClassCleanup); //TEST_METHOD_SETUP(TestSetup); //TEST_METHOD_CLEANUP(TestCleanup); TEST_METHOD(TestInitialization); + TEST_METHOD(TestMultipleInitialization); TEST_METHOD(TestResolvingTypes); + TEST_METHOD(ValidateBackwardsCompatibilityWinMD); private: diff --git a/src/app-sdk/tests/SdkInitialization.unittests/SdkInitialization.unittests.vcxproj b/src/app-sdk/tests/SdkInitialization.unittests/SdkInitialization.unittests.vcxproj index 9d8e39948..0ad08629d 100644 --- a/src/app-sdk/tests/SdkInitialization.unittests/SdkInitialization.unittests.vcxproj +++ b/src/app-sdk/tests/SdkInitialization.unittests/SdkInitialization.unittests.vcxproj @@ -1,4 +1,4 @@ - + @@ -20,7 +20,7 @@ - Microsoft.Windows.Devices.Midi2.1.0.2-preview-8.241123-1919 + Microsoft.Windows.Devices.Midi2.1.0.3-preview-11.250226-2321 17.0 {9803D8E6-5CA5-420C-A02B-5E3327355041} Win32Proj diff --git a/src/app-sdk/tests/SdkInitialization.unittests/packages.config b/src/app-sdk/tests/SdkInitialization.unittests/packages.config index 3414594b7..b6db04bde 100644 --- a/src/app-sdk/tests/SdkInitialization.unittests/packages.config +++ b/src/app-sdk/tests/SdkInitialization.unittests/packages.config @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/src/app-sdk/tests/SdkInitialization.unittests/stdafx.h b/src/app-sdk/tests/SdkInitialization.unittests/stdafx.h index d84a65772..0c85224ed 100644 --- a/src/app-sdk/tests/SdkInitialization.unittests/stdafx.h +++ b/src/app-sdk/tests/SdkInitialization.unittests/stdafx.h @@ -4,6 +4,9 @@ #ifndef STDAFX_H #define STDAFX_H +#pragma warning (push) +#pragma warning (disable: 4005) + #include #include @@ -44,4 +47,6 @@ namespace init = Microsoft::Windows::Devices::Midi2::Initialization; #define LOG_OUTPUT(fmt, ...) WEX::Logging::Log::Comment(WEX::Common::String().Format(fmt, __VA_ARGS__)) #endif +#pragma warning (pop) + #endif \ No newline at end of file diff --git a/src/app-sdk/tests/Service.integrationtests/stdafx.h b/src/app-sdk/tests/Service.integrationtests/stdafx.h index 8cdde7638..758a71c3a 100644 --- a/src/app-sdk/tests/Service.integrationtests/stdafx.h +++ b/src/app-sdk/tests/Service.integrationtests/stdafx.h @@ -4,6 +4,9 @@ #ifndef STDAFX_H #define STDAFX_H +#pragma warning (push) +#pragma warning (disable: 4005) + #include #include @@ -64,4 +67,6 @@ namespace init = Microsoft::Windows::Devices::Midi2::Initialization; #define LOG_OUTPUT(fmt, ...) WEX::Logging::Log::Comment(WEX::Common::String().Format(fmt, __VA_ARGS__)) #endif +#pragma warning (pop) + #endif \ No newline at end of file diff --git a/src/app-sdk/winrt/Initialization/MidiClientInitializer.cpp b/src/app-sdk/winrt/Initialization/MidiClientInitializer.cpp index 46721d239..17effd133 100644 --- a/src/app-sdk/winrt/Initialization/MidiClientInitializer.cpp +++ b/src/app-sdk/winrt/Initialization/MidiClientInitializer.cpp @@ -12,7 +12,7 @@ // init code goes in dllmain -winrt::com_ptr g_clientInitializer{ nullptr }; +std::unique_ptr g_clientInitializer{ nullptr }; @@ -29,23 +29,31 @@ MidiClientInitializer::Initialize( TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + // initialize can only be called once so let it finish at least one time. + auto lock = m_initializeLock.lock(); + if (m_initialized) { return S_OK; } - // midisrv client initialization. Doesn't actually connect to service here + auto cleanupOnError = wil::scope_exit([&]() + { + if (g_runtimeComponentCatalog) + { + g_runtimeComponentCatalog->Shutdown(); + g_runtimeComponentCatalog = nullptr; + } - RETURN_IF_FAILED(CoCreateInstance(__uuidof(Midi2MidiSrvTransport), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_serviceTransport))); - RETURN_IF_FAILED(m_serviceTransport->Activate(__uuidof(IMidiSessionTracker), (void**)&m_sessionTracker)); + RemoveWinRTActivationHooks(); + }); - RETURN_HR_IF_NULL(E_POINTER, m_sessionTracker); - RETURN_IF_FAILED(m_sessionTracker->Initialize()); + // midisrv client initialization. Doesn't actually connect to service here + RETURN_IF_FAILED(CoCreateInstance(__uuidof(Midi2MidiSrvTransport), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_serviceTransport))); // SDK initialization - g_runtimeComponentCatalog = std::make_shared(); if (g_runtimeComponentCatalog == nullptr) @@ -59,7 +67,7 @@ MidiClientInitializer::Initialize( TraceLoggingWideString(L"Unable to create runtime component catalog", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - RETURN_IF_FAILED(E_POINTER); + RETURN_IF_NULL_ALLOC(g_runtimeComponentCatalog); } auto hrcatalog = g_runtimeComponentCatalog->Initialize(); @@ -77,7 +85,6 @@ MidiClientInitializer::Initialize( RETURN_IF_FAILED(E_FAIL); } - try { auto hr = InstallWinRTActivationHooks(); @@ -112,7 +119,8 @@ MidiClientInitializer::Initialize( RETURN_IF_FAILED(E_FAIL); } - + // success, so remove all the unwinding code + cleanupOnError.release(); m_initialized = true; return S_OK; @@ -143,6 +151,8 @@ MidiClientInitializer::GetInstalledWindowsMidiServicesSdkVersion( TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + RETURN_HR_IF(E_UNEXPECTED, !m_initialized); + try { if (versionMajor != nullptr) @@ -281,18 +291,42 @@ MidiClientInitializer::EnsureServiceAvailable() noexcept TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + RETURN_HR_IF(E_UNEXPECTED, !m_initialized); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_serviceTransport); + + wil::com_ptr_nothrow sessionTracker; + RETURN_IF_FAILED(m_serviceTransport->Activate(__uuidof(IMidiSessionTracker), (void**)&sessionTracker)); + RETURN_HR_IF_NULL(E_POINTER, sessionTracker); - RETURN_HR_IF_NULL(E_POINTER, m_sessionTracker); + sessionTracker->Initialize(); + bool connected = sessionTracker->VerifyConnectivity(); + sessionTracker->Shutdown(); + sessionTracker.reset(); - if (m_sessionTracker->VerifyConnectivity()) + if (connected) { return S_OK; } + else + { + return E_FAIL; + } +} - return E_FAIL; +bool +MidiClientInitializer::CanUnloadNow() noexcept +{ + // if we've been initialized, the detours are in place, so cannot unload + if (m_initialized) + { + return false; + } + + return true; } + HRESULT MidiClientInitializer::Shutdown() noexcept { @@ -305,27 +339,74 @@ MidiClientInitializer::Shutdown() noexcept TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + auto lock = m_initializeLock.lock(); - // Remove activation hooks - RemoveWinRTActivationHooks(); - - if (m_serviceTransport) + if (m_initialized) { - m_serviceTransport = nullptr; + // Remove activation hooks + RemoveWinRTActivationHooks(); + + if (m_serviceTransport) + { + m_serviceTransport = nullptr; + } + + if (g_runtimeComponentCatalog != nullptr) + { + g_runtimeComponentCatalog->Shutdown(); + g_runtimeComponentCatalog.reset(); + } + + // this just feels strange + g_clientInitializer.reset(); + + m_initialized = false; } - if (m_sessionTracker) + return S_OK; +} + + +MidiClientInitializer::~MidiClientInitializer() +{ + //Shutdown(); +} + + +ULONG __stdcall MidiClientInitializer::AddRef() noexcept +{ + return InterlockedIncrement(&m_cRef); +} + +ULONG __stdcall MidiClientInitializer::Release() noexcept +{ + if (InterlockedDecrement(&m_cRef) == 0) { - m_sessionTracker->Shutdown(); - m_serviceTransport = nullptr; + //delete this; + Shutdown(); + return 0; } - if (g_runtimeComponentCatalog != nullptr) + return m_cRef; +} + +HRESULT __stdcall MidiClientInitializer::QueryInterface(const IID& iid, void** ppv) noexcept +{ + if (iid == IID_IUnknown) { - g_runtimeComponentCatalog->Shutdown(); - g_runtimeComponentCatalog.reset(); + *ppv = static_cast(this); } + else if (iid == __uuidof(IMidiClientInitializer)) + { + *ppv = static_cast(this); + } + else + { + *ppv = NULL; + return E_NOINTERFACE; + } + + reinterpret_cast(*ppv)->AddRef(); return S_OK; } - diff --git a/src/app-sdk/winrt/Initialization/MidiClientInitializer.h b/src/app-sdk/winrt/Initialization/MidiClientInitializer.h index 0be14ecab..a9f3161c9 100644 --- a/src/app-sdk/winrt/Initialization/MidiClientInitializer.h +++ b/src/app-sdk/winrt/Initialization/MidiClientInitializer.h @@ -24,8 +24,6 @@ typedef enum // famous arp + famous roland + famous yamaha (5=S) struct __declspec(uuid("8087b303-d551-bce2-1ead-a2500d50c580")) IMidiClientInitializer : ::IUnknown { - STDMETHOD(Initialize)() = 0; - STDMETHOD(Shutdown)() = 0; STDMETHOD(GetInstalledWindowsMidiServicesSdkVersion)( _Out_opt_ MidiAppSDKPlatform* buildPlatform, @@ -55,8 +53,9 @@ struct __declspec(uuid("8087b303-d551-bce2-1ead-a2500d50c580")) IMidiClientIniti // famous tune played on that synthesizer (first letter is not a note, 6=g) struct __declspec(uuid("c3263827-c3b0-bdbd-2500-ce63a3f3f2c3")) MidiClientInitializer : winrt::implements { - STDMETHOD(Initialize)() noexcept override; - STDMETHOD(Shutdown)() noexcept override; + STDMETHOD(Initialize)() noexcept; // not part of COM interface + STDMETHOD(Shutdown)() noexcept; // not part of COM interface + bool CanUnloadNow() noexcept; // not part of COM interface STDMETHOD(GetInstalledWindowsMidiServicesSdkVersion)( _Out_opt_ MidiAppSDKPlatform* buildPlatform, @@ -74,15 +73,22 @@ struct __declspec(uuid("c3263827-c3b0-bdbd-2500-ce63a3f3f2c3")) MidiClientInitia STDMETHOD(EnsureServiceAvailable)() noexcept override; + ULONG __stdcall AddRef() noexcept override; + ULONG __stdcall Release() noexcept override; + HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) noexcept override; + + ~MidiClientInitializer(); private: + ULONG m_cRef{ 0 }; + bool m_initialized{ false }; + wil::critical_section m_initializeLock{}; wil::com_ptr_nothrow m_serviceTransport{ nullptr }; - wil::com_ptr_nothrow m_sessionTracker{ nullptr }; }; -extern winrt::com_ptr g_clientInitializer; +extern std::unique_ptr g_clientInitializer; // uuid // famous phone number @@ -104,10 +110,28 @@ struct __declspec(uuid("18675309-5150-ca75-0b12-5648616c656e")) MidiClientInitia return CLASS_E_NOAGGREGATION; } + // SINGLETON // we don't make a new instance here, because we want only // a single instance of this class per-process - return g_clientInitializer->QueryInterface(iid, result); + // detours initialization is all done in the COM component + // but we want only a single instance per-process + if (g_clientInitializer == nullptr) + { + // uses normal non-ref counted pointer to not increase ref count from the start, + // and to hold the actual impl, not the COM interface + g_clientInitializer = std::make_unique(); + RETURN_IF_NULL_ALLOC(g_clientInitializer); + // Initialize via internal function + auto hr = g_clientInitializer->Initialize(); + + RETURN_IF_FAILED(hr); + } + + auto hrqi = g_clientInitializer->QueryInterface(iid, result); + RETURN_IF_FAILED(hrqi); + + return S_OK; } catch (...) { diff --git a/src/app-sdk/winrt/Initialization/MidiRoDetours.cpp b/src/app-sdk/winrt/Initialization/MidiRoDetours.cpp index 78fc29982..43ed1895a 100644 --- a/src/app-sdk/winrt/Initialization/MidiRoDetours.cpp +++ b/src/app-sdk/winrt/Initialization/MidiRoDetours.cpp @@ -462,6 +462,9 @@ RoResolveNamespaceDetour( //return hr; } +wil::critical_section m_detourSetupLock; +bool g_detourActive{ false }; + HRESULT InstallWinRTActivationHooks() { @@ -474,29 +477,36 @@ InstallWinRTActivationHooks() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - // If this is loaded in a Detours helper process and not the actual process - // to be hooked, just return without performing any other operations. - if (DetourIsHelperProcess()) - return S_OK; - - DetourRestoreAfterWith(); + auto lock = m_detourSetupLock.lock(); - RETURN_IF_WIN32_ERROR(DetourTransactionBegin()); - RETURN_IF_WIN32_ERROR(DetourUpdateThread(GetCurrentThread())); - RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoActivateInstance, RoActivateInstanceDetour)); - RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoGetActivationFactory, RoGetActivationFactoryDetour)); - RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoGetMetaDataFile, RoGetMetaDataFileDetour)); - RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoResolveNamespace, RoResolveNamespaceDetour)); - RETURN_IF_WIN32_ERROR(DetourTransactionCommit()); + if (!g_detourActive) + { + // If this is loaded in a Detours helper process and not the actual process + // to be hooked, just return without performing any other operations. + if (DetourIsHelperProcess()) + return S_OK; - TraceLoggingWrite( - Midi2SdkTelemetryProvider::Provider(), - MIDI_TRACE_EVENT_INFO, - TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), - TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(nullptr, "this"), - TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) - ); + DetourRestoreAfterWith(); + + RETURN_IF_WIN32_ERROR(DetourTransactionBegin()); + RETURN_IF_WIN32_ERROR(DetourUpdateThread(GetCurrentThread())); + RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoActivateInstance, RoActivateInstanceDetour)); + RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoGetActivationFactory, RoGetActivationFactoryDetour)); + RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoGetMetaDataFile, RoGetMetaDataFileDetour)); + RETURN_IF_WIN32_ERROR(DetourAttach(&(PVOID&)TrueRoResolveNamespace, RoResolveNamespaceDetour)); + RETURN_IF_WIN32_ERROR(DetourTransactionCommit()); + + TraceLoggingWrite( + Midi2SdkTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(nullptr, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + + g_detourActive = true; + } return S_OK; } @@ -512,14 +522,20 @@ void RemoveWinRTActivationHooks() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + auto lock = m_detourSetupLock.lock(); - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourDetach(&(PVOID&)TrueRoActivateInstance, RoActivateInstanceDetour); - DetourDetach(&(PVOID&)TrueRoGetActivationFactory, RoGetActivationFactoryDetour); - DetourDetach(&(PVOID&)TrueRoGetMetaDataFile, RoGetMetaDataFileDetour); - DetourDetach(&(PVOID&)TrueRoResolveNamespace, RoResolveNamespaceDetour); - DetourTransactionCommit(); + if (g_detourActive) + { + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourDetach(&(PVOID&)TrueRoActivateInstance, RoActivateInstanceDetour); + DetourDetach(&(PVOID&)TrueRoGetActivationFactory, RoGetActivationFactoryDetour); + DetourDetach(&(PVOID&)TrueRoGetMetaDataFile, RoGetMetaDataFileDetour); + DetourDetach(&(PVOID&)TrueRoResolveNamespace, RoResolveNamespaceDetour); + DetourTransactionCommit(); + + g_detourActive = false; + } TraceLoggingWrite( Midi2SdkTelemetryProvider::Provider(), @@ -529,7 +545,6 @@ void RemoveWinRTActivationHooks() TraceLoggingPointer(nullptr, "this"), TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - } //extern "C" void WINAPI winrtact_Initialize() diff --git a/src/app-sdk/winrt/MidiEndpointConnection_SendInfrastructure.cpp b/src/app-sdk/winrt/MidiEndpointConnection_SendInfrastructure.cpp index df223755e..e5a7683d7 100644 --- a/src/app-sdk/winrt/MidiEndpointConnection_SendInfrastructure.cpp +++ b/src/app-sdk/winrt/MidiEndpointConnection_SendInfrastructure.cpp @@ -28,30 +28,6 @@ namespace winrt::Microsoft::Windows::Devices::Midi2::implementation switch (hr) { - //case HR_S_MIDI_SENDMSG_IMMEDIATE: - // result |= midi2::MidiSendMessageResult::SentImmediately; - // break; - - //case HR_S_MIDI_SENDMSG_SCHEDULED: - // result |= midi2::MidiSendMessageResult::Scheduled; - // break; - - //case HR_S_MIDI_SENDMSG_SYSEX_PARKED: - // result |= midi2::MidiSendMessageResult::SystemExclusiveParked; - // break; - - //case HR_E_MIDI_SENDMSG_BUFFER_FULL: - // result |= midi2::MidiSendMessageResult::BufferFull; - // break; - - //case HR_E_MIDI_SENDMSG_SCHEDULER_QUEUE_FULL: - // result |= midi2::MidiSendMessageResult::SchedulerQueueFull; - // break; - - //case HR_E_MIDI_SENDMSG_INVALID_MESSAGE: - // result |= midi2::MidiSendMessageResult::InvalidMessageOther; - // break; - case HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER): result |= midi2::MidiSendMessageResults::BufferFull; break; @@ -145,6 +121,7 @@ namespace winrt::Microsoft::Windows::Devices::Midi2::implementation { auto hr = endpoint->SendMidiMessage(data, sizeInBytes, timestamp); + if (FAILED(hr)) { LOG_IF_FAILED(hr); // this also generates a fallback error with file and line number info diff --git a/src/app-sdk/winrt/MidiVirtualDevice.cpp b/src/app-sdk/winrt/MidiVirtualDevice.cpp index 32e183bcc..59fefd47c 100644 --- a/src/app-sdk/winrt/MidiVirtualDevice.cpp +++ b/src/app-sdk/winrt/MidiVirtualDevice.cpp @@ -306,6 +306,11 @@ namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::impleme if (internal::EndpointDiscoveryFilterRequestsEndpointInfoNotification(filterFlags)) { + // because we reuse the declaredendpointinfo type, it has a redundant field + // which can cause problems during discovery (mostly a long timeout). So + // just update the field here. + m_declaredEndpointInfo.DeclaredFunctionBlockCount = static_cast(m_functionBlocks.Size()); + // send endpoint info notification auto notification = msgs::MidiStreamMessageBuilder::BuildEndpointInfoNotificationMessage( @@ -313,7 +318,7 @@ namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::impleme m_declaredEndpointInfo.SpecificationVersionMajor, m_declaredEndpointInfo.SpecificationVersionMinor, m_declaredEndpointInfo.HasStaticFunctionBlocks, - m_declaredEndpointInfo.DeclaredFunctionBlockCount, /*(uint8_t)m_functionBlocks.Size(), */ + m_declaredEndpointInfo.DeclaredFunctionBlockCount, m_declaredEndpointInfo.SupportsMidi20Protocol, m_declaredEndpointInfo.SupportsMidi10Protocol, m_declaredEndpointInfo.SupportsReceivingJitterReductionTimestamps, @@ -339,12 +344,8 @@ namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::impleme if (internal::EndpointDiscoveryFilterRequestsDeviceIdentityNotification(filterFlags)) { - // internal::LogInfo(__FUNCTION__, L"Building/Sending Device Identity Notification"); - // send device identity notification - // TODO: Need to validate no indexes out of bounds here - auto identityNotification = msgs::MidiStreamMessageBuilder::BuildDeviceIdentityNotificationMessage( MidiClock::TimestampConstantSendImmediately(), m_declaredDeviceIdentity.SystemExclusiveIdByte1, // byte 1 @@ -360,8 +361,6 @@ namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::impleme m_declaredDeviceIdentity.SoftwareRevisionLevelByte4 // byte 4 ); - //OutputDebugString(L"MIDI SDK: Responding with Device Identity Notification\n"); - if (midi2::MidiEndpointConnection::SendMessageFailed(m_endpointConnection.SendSingleMessagePacket(identityNotification))) { LOG_IF_FAILED(E_FAIL); // this also generates a fallback error with file and line number info diff --git a/src/app-sdk/winrt/dllmain.cpp b/src/app-sdk/winrt/dllmain.cpp index f116ec9ec..73c864481 100644 --- a/src/app-sdk/winrt/dllmain.cpp +++ b/src/app-sdk/winrt/dllmain.cpp @@ -24,19 +24,11 @@ BOOL WINAPI DllMain(HINSTANCE hmodule, DWORD reason, LPVOID /*lpvReserved*/) wil::SetResultTelemetryFallback(Midi2SdkTelemetryProvider::FallbackTelemetryCallback); DisableThreadLibraryCalls(hmodule); - - // detours initialization is all done in the COM component - // but we want only a single instance per-process - if (g_clientInitializer == nullptr) - { - g_clientInitializer = winrt::make(); - } - } if (reason == DLL_PROCESS_DETACH) { - // g_clientInitializer = nullptr; + g_clientInitializer = nullptr; } TraceLoggingWrite( @@ -57,6 +49,10 @@ DllCanUnloadNow() { // TODO: See if the initializer has any references. If not, then forward to the WinRT function + if (g_clientInitializer == nullptr) return true; + if (g_clientInitializer->CanUnloadNow()) return true; + + // this is assuming applications are keeping the initializer around like they're supposed to. return WINRT_CanUnloadNow(); } @@ -83,8 +79,6 @@ DllGetClassObject(GUID const& clsid, GUID const& iid, void** result) return winrt::make()->QueryInterface(iid, result); } - // TODO: Is this going to fail for apps like MultitrackStudio which use the underlying COM interfaces for the WinRT classes? - // not a supported class return winrt::hresult_class_not_available().to_abi(); } catch (...) diff --git a/src/oob-setup-in-dev/main-bundle/Bundle.wxs b/src/oob-setup-in-dev/main-bundle/Bundle.wxs index 92e91e394..0d0ce3b97 100644 --- a/src/oob-setup-in-dev/main-bundle/Bundle.wxs +++ b/src/oob-setup-in-dev/main-bundle/Bundle.wxs @@ -24,6 +24,8 @@ diff --git a/src/oob-setup/main-bundle/Bundle.wxs b/src/oob-setup/main-bundle/Bundle.wxs index 2bf6cfd58..bebea5f9c 100644 --- a/src/oob-setup/main-bundle/Bundle.wxs +++ b/src/oob-setup/main-bundle/Bundle.wxs @@ -25,6 +25,8 @@ diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointCommandSettings.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointCommandSettings.cs index 885a78199..ec7fb4226 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointCommandSettings.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointCommandSettings.cs @@ -30,6 +30,11 @@ internal class SendMessageCommandSettings : EndpointCommandSettings [DefaultValue(MidiWordDataFormat.Hex)] public MidiWordDataFormat WordDataFormat { get; set; } + [LocalizedDescription("ParameterSendMessageNoWait")] + [CommandOption("-n|--no-wait")] + [DefaultValue(false)] + public bool NoWait { get; set; } + //[LocalizedDescription("TODO ParameterSendMessageAutoNegotiation")] //[CommandOption("-n|--auto-negotiation")] //[DefaultValue(true)] diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs index 729198c52..cea0366fe 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessageCommand.cs @@ -209,6 +209,7 @@ public override int Execute(CommandContext context, Settings settings) uint messagesSent = 0; uint messagesAttempted = 0; uint messageFailures = 0; + uint totalMessageRetries = 0; UInt64 maxTimestampScheduled = 0; @@ -259,22 +260,53 @@ public override int Execute(CommandContext context, Settings settings) } messagesAttempted++; - var sendResult = connection.SendSingleMessageWordArray(timestamp, 0, (byte)_parsedWords!.Count(), _parsedWords); - if (MidiEndpointConnection.SendMessageSucceeded(sendResult)) + bool continueToRetry = true; + uint retryAttempts = 0; + uint maxRetryAttempts = 500; + while (continueToRetry) { - messagesSent++; + var sendResult = connection.SendSingleMessageWordArray(timestamp, 0, (byte)_parsedWords!.Count(), _parsedWords); - if (timestamp > maxTimestampScheduled) + if (MidiEndpointConnection.SendMessageSucceeded(sendResult)) { - maxTimestampScheduled = timestamp; + messagesSent++; + continueToRetry = false; // message was sent, so no need to retry + + if (timestamp > maxTimestampScheduled) + { + maxTimestampScheduled = timestamp; + } } + else + { + continueToRetry = false; + // if the problem is that the buffer is full, we will retry + // we should sleep in this loop, but the sleep resolution is not + // low enough to make this a reasonable thing to do. + if ((sendResult & MidiSendMessageResults.BufferFull) == MidiSendMessageResults.BufferFull) + { + if (retryAttempts < maxRetryAttempts) + { + continueToRetry = true; + retryAttempts++; + totalMessageRetries++; + } + else + { + messageFailures++; + } + } + else + { + messageFailures++; + } + } } - else - { - messageFailures++; - } + + + if (settings.DelayBetweenMessages > 0) { @@ -355,7 +387,7 @@ public override int Execute(CommandContext context, Settings settings) if (messageFailures > 0) { // todo: localize - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Failed to send {messageFailures} of a planned {settings.Count} message(s).")); + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"{messageFailures} failed message sending attempts.")); } else { @@ -396,9 +428,15 @@ public override int Execute(CommandContext context, Settings settings) AnsiConsoleOutput.ConvertTicksToFriendlyTimeUnit(averageTicks, out averageTime, out averageTimeLabel); AnsiConsole.MarkupLine($"Total send time [steelblue1]{totalTime.ToString("N2")} {totalTimeLabel}[/], averaging [steelblue1]{averageTime.ToString("N2")} {averageTimeLabel}[/] per message."); + } + if (totalMessageRetries > 0) + { + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatWarning($"{totalMessageRetries} retried send operations.")); } + + // for scheduled messages, we wait if (maxTimestampScheduled > MidiClock.Now) { int sleepMs = (int)Math.Ceiling(MidiClock.ConvertTimestampTicksToMilliseconds(maxTimestampScheduled - MidiClock.Now)); @@ -410,29 +448,34 @@ public override int Execute(CommandContext context, Settings settings) Thread.Sleep(sleepMs); } - // BEGIN TEMP -------------------------------------------------- - //AnsiConsole.MarkupLine(Strings.MonitorPressEscapeToStopMonitoringMessage); - //AnsiConsole.WriteLine(); - //bool continueWaiting = true; + if (!settings.NoWait) + { + AnsiConsole.MarkupLine(Strings.SendMessagePressEscapeToCloseConnectionMessage); + AnsiConsole.WriteLine(); - //while (continueWaiting) - //{ - // if (Console.KeyAvailable) - // { - // var keyInfo = Console.ReadKey(true); + bool continueWaiting = true; - // if (keyInfo.Key == ConsoleKey.Escape) - // { - // continueWaiting = false; + while (continueWaiting) + { + if (Console.KeyAvailable) + { + var keyInfo = Console.ReadKey(true); - // AnsiConsole.WriteLine(); - // AnsiConsole.MarkupLine("🛑 " + Strings.MonitorEscapePressedMessage); - // } + if (keyInfo.Key == ConsoleKey.Escape) + { + continueWaiting = false; - // } - //} - // END TEMP -------------------------------------------------- + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("🛑 " + Strings.SendMessageEscapePressedMessage); + } + } + else + { + Thread.Sleep(500); + } + } + } session.DisconnectEndpointConnection(connection.ConnectionId); diff --git a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs index 2810005b0..d834e0f39 100644 --- a/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Endpoint/EndpointSendMessagesFileCommand.cs @@ -378,6 +378,34 @@ public override int Execute(CommandContext context, Settings settings) } + if (!settings.NoWait) + { + AnsiConsole.MarkupLine(Strings.SendMessagePressEscapeToCloseConnectionMessage); + AnsiConsole.WriteLine(); + + bool continueWaiting = true; + + while (continueWaiting) + { + if (Console.KeyAvailable) + { + var keyInfo = Console.ReadKey(true); + + if (keyInfo.Key == ConsoleKey.Escape) + { + continueWaiting = false; + + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine("🛑 " + Strings.SendMessageEscapePressedMessage); + } + } + else + { + Thread.Sleep(500); + } + } + } + if (session != null) session.Dispose(); diff --git a/src/user-tools/midi-console/Midi/Properties/launchSettings.json b/src/user-tools/midi-console/Midi/Properties/launchSettings.json index 88016ea04..84c62b0e7 100644 --- a/src/user-tools/midi-console/Midi/Properties/launchSettings.json +++ b/src/user-tools/midi-console/Midi/Properties/launchSettings.json @@ -34,6 +34,10 @@ "Enum Endpoints": { "commandName": "Project", "commandLineArgs": "enum endpoints" + }, + "Blast Messages": { + "commandName": "Project", + "commandLineArgs": "endpoint send-message 0x20905060 --count 20000 --pause 0" } } } \ No newline at end of file diff --git a/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs b/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs index 8833362d6..468d907a0 100644 --- a/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs +++ b/src/user-tools/midi-console/Midi/Resources/Strings.Designer.cs @@ -1293,6 +1293,15 @@ internal static string ParameterSendMessageEndpointDirectionDescription { } } + /// + /// Looks up a localized string similar to Do not prompt the user to hit any key to close the connection. + /// + internal static string ParameterSendMessageNoWait { + get { + return ResourceManager.GetString("ParameterSendMessageNoWait", resourceCulture); + } + } + /// /// Looks up a localized string similar to Path and filename of the text file to send. This can include lines which begin with # for a comment, or are completely empty for spacing. Remaining lines must be valid delimited (big-endian) hexadecimal UMP words.. /// @@ -2306,6 +2315,15 @@ internal static string SendMessageFloodWarning { } } + /// + /// Looks up a localized string similar to Press escape to close the connection when the endpoint has received all the messages.. + /// + internal static string SendMessagePressEscapeToCloseConnectionMessage { + get { + return ResourceManager.GetString("SendMessagePressEscapeToCloseConnectionMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Press escape to stop sending messages.. /// diff --git a/src/user-tools/midi-console/Midi/Resources/Strings.resx b/src/user-tools/midi-console/Midi/Resources/Strings.resx index f7df8b9f3..63e1d4748 100644 --- a/src/user-tools/midi-console/Midi/Resources/Strings.resx +++ b/src/user-tools/midi-console/Midi/Resources/Strings.resx @@ -929,4 +929,10 @@ Timestamp Unknown + + Do not prompt the user to hit any key to close the connection + + + Press escape to close the connection when the endpoint has received all the messages. + \ No newline at end of file diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/App.xaml.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/App.xaml.cs index b98859c52..16b01e755 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/App.xaml.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/App.xaml.cs @@ -76,6 +76,7 @@ public App() services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Views and ViewModels @@ -136,8 +137,8 @@ public App() services.AddTransient(); services.AddTransient(); - services.AddTransient(); - services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiConfigFileService.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiConfigFileService.cs index e2d542b2a..39b6daf1d 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiConfigFileService.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiConfigFileService.cs @@ -20,7 +20,6 @@ public interface IMidiConfigFile bool StoreLoopbackEndpointPair(Microsoft.Windows.Devices.Midi2.Endpoints.Loopback.MidiLoopbackEndpointCreationConfig creationConfig); - } @@ -44,5 +43,4 @@ public interface IMidiConfigFileService bool UpdateRegistryCurrentConfigFile(string configFileName); - } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiEndpointMonitorService.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiEndpointMonitorService.cs new file mode 100644 index 000000000..62f0b8ff8 --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiEndpointMonitorService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Midi.Settings.Contracts.Services +{ + public interface IMidiEndpointMonitorService + { + void MonitorMidiEndpoint(string midiEndpointDeviceId); + + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiServiceRegistrySettingsService.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiServiceRegistrySettingsService.cs new file mode 100644 index 000000000..a81cf7fe7 --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Contracts/Services/IMidiServiceRegistrySettingsService.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Midi.Settings.Contracts.Services +{ + public interface IMidiServiceRegistrySettingsService + { + bool GetDefaultToOldMidi1PortNaming(); + bool SetDefaultToOldMidi1PortNaming(bool newValue); + + + bool GetUseMMCSS(); + bool SetUseMMCSS(bool newValue); + + + bool GetMidi2DiscoveryEnabled(); + bool SetMidi2DiscoveryEnabled(bool newValue); + + + UInt32 GetMidi2DiscoveryTimeoutMS(); + bool SetMidi2DiscoveryTimeoutMS(UInt32 newValue); + + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupPage.xaml b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsPage.xaml similarity index 68% rename from src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupPage.xaml rename to src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsPage.xaml index 7a58c74b7..e41ddf1ad 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupPage.xaml +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsPage.xaml @@ -2,7 +2,7 @@ - + diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupPage.xaml.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsPage.xaml.cs similarity index 85% rename from src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupPage.xaml.cs rename to src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsPage.xaml.cs index fdbfb8e19..31cc95f76 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupPage.xaml.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsPage.xaml.cs @@ -26,19 +26,19 @@ namespace Microsoft.Midi.Settings.Views /// /// An empty page that can be used on its own or navigated to within a Frame. /// - public sealed partial class SetupPage : Page + public sealed partial class ConfigurationsPage : Page { private ILoggingService _loggingService; - public SetupViewModel ViewModel + public ConfigurationsViewModel ViewModel { get; } - public SetupPage() + public ConfigurationsPage() { _loggingService = App.GetService(); - ViewModel = App.GetService(); + ViewModel = App.GetService(); InitializeComponent(); } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupViewModel.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsViewModel.cs similarity index 70% rename from src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupViewModel.cs rename to src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsViewModel.cs index 0141038ae..7c20c78cb 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Setups/SetupViewModel.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Configurations/ConfigurationsViewModel.cs @@ -7,9 +7,9 @@ namespace Microsoft.Midi.Settings.ViewModels { - public class SetupViewModel : ObservableRecipient + public class ConfigurationsViewModel : ObservableRecipient { - public SetupViewModel() + public ConfigurationsViewModel() { } } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Endpoints/DeviceDetailPage.xaml.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Endpoints/DeviceDetailPage.xaml.cs index 6608ddbb8..2bbd2ac42 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Endpoints/DeviceDetailPage.xaml.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Endpoints/DeviceDetailPage.xaml.cs @@ -201,6 +201,8 @@ private async void BrowseSmallImage_Click(object sender, RoutedEventArgs e) private void OnOpenConsoleMonitor(object sender, RoutedEventArgs e) { + // TODO: Change this to use the Endpoint Monitor Service + try { string arguments = diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Settings/SettingsPage.xaml b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Settings/SettingsPage.xaml index 4bd93d6f3..92b6a7aa2 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Settings/SettingsPage.xaml +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Sections/Settings/SettingsPage.xaml @@ -68,9 +68,9 @@ - + - + @@ -80,10 +80,12 @@ Click="WindowsDeveloperSettingsHyperlinkButton_Click"/> + diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/MidiEndpointMonitorService.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/MidiEndpointMonitorService.cs new file mode 100644 index 000000000..15831e1b3 --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/MidiEndpointMonitorService.cs @@ -0,0 +1,41 @@ +using Microsoft.Midi.Settings.Contracts.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.UI.Popups; + +namespace Microsoft.Midi.Settings.Services +{ + public class MidiEndpointMonitorService : IMidiEndpointMonitorService + { + public void MonitorMidiEndpoint(string midiEndpointDeviceId) + { + // right now, this opens a console. In the future, it may be its own dedicated GUI window + + try + { + string arguments = + " endpoint " + + midiEndpointDeviceId + + " monitor"; + + using (var monitorProcess = new System.Diagnostics.Process()) + { + monitorProcess.StartInfo.FileName = "midi.exe"; + monitorProcess.StartInfo.Arguments = arguments; + + monitorProcess.Start(); + } + } + catch (Exception ex) + { + var dialog = new MessageDialog("Error opening console"); + dialog.Content = ex.ToString(); + + dialog.ShowAsync().Wait(); + } + } + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/MidiServiceRegistrySettingsService.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/MidiServiceRegistrySettingsService.cs new file mode 100644 index 000000000..8a0f0141c --- /dev/null +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/MidiServiceRegistrySettingsService.cs @@ -0,0 +1,206 @@ +using Microsoft.Midi.Settings.Contracts.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Midi.Settings.Services +{ + public class MidiServiceRegistrySettingsService : IMidiServiceRegistrySettingsService + { + const bool SettingsDefaultUseMMCSS = false; + const bool SettingsDefaultDiscoveryEnabled = true; + const bool SettingsDefaultMidi1PortNaming = true; + const UInt32 SettingsDefaultDiscoveryTimeout = 10000; + + + private const string MidiRootRegKey = @"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows MIDI Services"; + + private UInt32 GetRegistryNumericDWORDValue(string keyName, string valueName, UInt32 defaultValue) + { + try + { + var value = Microsoft.Win32.Registry.GetValue(keyName, valueName, defaultValue); + + if (value == null) + { + // happens when the key does not exist + return defaultValue; + } + else + { + return (UInt32)value; + } + } + catch (Exception) + { + return defaultValue; + } + } + + private bool GetRegistryBooleanDWORDValue(string keyName, string valueName, bool defaultValue) + { + try + { + UInt32 defaultNumericValue = 0; + + if (defaultValue) + { + defaultNumericValue = 1; + } + + var value = Microsoft.Win32.Registry.GetValue(keyName, valueName, defaultNumericValue); + + if (value == null) + { + // happens when the key does not exist + return defaultValue; + } + else if ((UInt32)value > 0) + { + return true; + } + else + { + return false; + } + } + catch (Exception) + { + return defaultValue; + } + + } + + private string GetRegistryStringValue(string keyName, string valueName, string defaultValue) + { + try + { + var value = Microsoft.Win32.Registry.GetValue(keyName, valueName, defaultValue); + + if (value == null) + { + // happens when the key does not exist + return defaultValue; + } + else if (value is string) + { + return (string)value; + } + else + { + return defaultValue; + } + } + catch (Exception) + { + return defaultValue; + } + } + + private bool SetRegistryNumericDWORDValue(string keyName, string valueName, UInt32 newValue) + { + try + { + Microsoft.Win32.Registry.SetValue(keyName, valueName, newValue); + + return true; + } + catch (Exception) + { + return false; + } + } + + private bool SetRegistryBooleanDWORDValue(string keyName, string valueName, bool newValue) + { + try + { + var val = newValue ? (UInt32)1 : (UInt32)0; + + Microsoft.Win32.Registry.SetValue(keyName, valueName, val); + + return true; + } + catch (Exception) + { + return false; + } + } + + private bool SetRegistryStringValue(string keyName, string valueName, string newValue) + { + try + { + Microsoft.Win32.Registry.SetValue(keyName, valueName, newValue.Trim()); + + return true; + } + catch (Exception) + { + return false; + } + } + + //private bool DeleteRegistryValue(string regKey, string regValue) + //{ + // // TODO + + // return false; + //} + + const string ValueName_DefaultToOldMidi1PortNaming = "DefaultToOldMidi1PortNaming"; + const string ValueName_Midi2DiscoveryEnabled = "Midi2DiscoveryEnabled"; + const string ValueName_Midi2DiscoveryTimeoutMS = "Midi2DiscoveryTimeoutMS"; + const string ValueName_UseMMCSS = "UseMMCSS"; + + public bool GetDefaultToOldMidi1PortNaming() + { + return GetRegistryBooleanDWORDValue(MidiRootRegKey, ValueName_DefaultToOldMidi1PortNaming, SettingsDefaultMidi1PortNaming); + } + + public bool GetMidi2DiscoveryEnabled() + { + return GetRegistryBooleanDWORDValue(MidiRootRegKey, ValueName_Midi2DiscoveryEnabled, SettingsDefaultDiscoveryEnabled); + } + + public UInt32 GetMidi2DiscoveryTimeoutMS() + { + return GetRegistryNumericDWORDValue(MidiRootRegKey, ValueName_Midi2DiscoveryTimeoutMS, SettingsDefaultDiscoveryTimeout); + } + + public bool GetUseMMCSS() + { + return GetRegistryBooleanDWORDValue(MidiRootRegKey, ValueName_UseMMCSS, SettingsDefaultUseMMCSS); + } + + public bool SetDefaultToOldMidi1PortNaming(bool newValue) + { + // TODO: if the new value is the default, then just delete the reg entry + + return SetRegistryBooleanDWORDValue(MidiRootRegKey, ValueName_DefaultToOldMidi1PortNaming, newValue); + } + + public bool SetMidi2DiscoveryEnabled(bool newValue) + { + // TODO: if the new value is the default, then just delete the reg entry + + return SetRegistryBooleanDWORDValue(MidiRootRegKey, ValueName_Midi2DiscoveryEnabled, newValue); + } + + public bool SetMidi2DiscoveryTimeoutMS(UInt32 newValue) + { + // TODO: if the new value is the default, then just delete the reg entry + + return SetRegistryNumericDWORDValue(MidiRootRegKey, ValueName_Midi2DiscoveryTimeoutMS, newValue); + } + + public bool SetUseMMCSS(bool newValue) + { + // if the new value is the default, then just delete the reg entry + + return SetRegistryBooleanDWORDValue(MidiRootRegKey, ValueName_UseMMCSS, newValue); + } + } +} diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/PageService.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/PageService.cs index 992b31490..e19905e4f 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/PageService.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Services/PageService.cs @@ -33,7 +33,7 @@ public PageService() Configure(); Configure(); Configure(); - Configure(); + Configure(); //Configure(); //Configure(); //Configure(); diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Strings/en-us/Resources.resw b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Strings/en-us/Resources.resw index 45ef20e6b..2a7944e32 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Strings/en-us/Resources.resw +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Strings/en-us/Resources.resw @@ -135,11 +135,11 @@ Processing & Filtering - - Setups + + Configurations - Devices & Setups + Devices & Configurations Management @@ -202,16 +202,16 @@ Troubleshooting - Normally off. Developer options are generally intended for developers, and those working with developers to test pre-release components. Non-developer MIDI users do not need to use developer options as they may adversely impact the stability, capability, or performance of MIDI on your computer. Developer options require that the PC be placed into Developer Mode in Windows settings. + Developer options are generally intended for developers, and those working with developers to test pre-release components. Non-developer MIDI users do not need to use developer options as they may adversely impact the stability, capability, or performance of MIDI on your computer. Developer options require that the PC be placed into Developer Mode in Windows settings. - Show developer options for MIDI + Developer options for MIDI Open Windows Settings "For developers" page - Developer Mode is not enabled. Enable developer mode in Windows Settings. + Developer Mode is not enabled. Enable developer mode in Windows Settings if you want to be able to install in-development and unsigned third-party service plugins. Windows MIDI Services on Discord @@ -238,7 +238,7 @@ Show developer options for MIDI - Developer Mode is currently enabled on this PC. To turn it off, use Windows Settings. + Developer Mode is currently enabled on this PC, enabling you to install in-development and third-party unsigned service plugins. To turn it off, use Windows Settings. MIDI Settings @@ -411,4 +411,64 @@ Other + + Discovery and Protocol Negotiation + + + Normally on. By default, the MIDI Service performs MIDI 2.0 discovery and protocol negotiation for any natively UMP device. If you are testing a device which breaks when those messages are sent, disable the process here. Requires a MIDI Service restart. + + + Enable MMCSS for the Incoming Message Queue + + + Normally off. MMCSS threads are a limited resource in Windows, and are often used for audio processing threads in DAW applications. If this feature is enabled, Windows MIDI Services will use MMCSS threads for handling incoming messages. This reduces the number of MMCSS threads available to the client process, and may cause applications to fail to work properly. + + + Windows MIDI Services will not use MMCSS threads in the client process. This is the recommended choice. + + + Service-based MIDI 2.0 discovery and protocol negotiation is currently disabled. + + + Windows MIDI Services will use MMCSS threads in the client process. This may cause some applications to malfunction. + + + Service-based MIDI 2.0 discovery and protocol negotiation is currently enabled. This is the recommended choice. + + + The Windows MIDI Service (midisrv.exe) must be restarted for changes to take effect. + + + Restart MIDI Service + + + https://aka.ms/midi + + + Windows MIDI Services Documentation + + + Windows MIDI and ASIO Discord Server + + + https://aka.ms/mididiscord + + + Windows MIDI Services GitHub Repo + + + https://aka.ms/midirepo + + + https://midi.org/specs + + + MIDI 1.0 and 2.0 Specifications at the MIDI Association + + + Discovery Timeout (milliseconds) + + + Save to Registry + \ No newline at end of file diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ForDevelopersViewModel.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ForDevelopersViewModel.cs index 6dee85b96..5b0fbb9a8 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ForDevelopersViewModel.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ForDevelopersViewModel.cs @@ -1,17 +1,109 @@ using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Midi.Settings.Contracts.Services; using Microsoft.Midi.Settings.Contracts.ViewModels; using Microsoft.Midi.Settings.Helpers; +using System.Windows.Input; namespace Microsoft.Midi.Settings.ViewModels; -public class ForDevelopersViewModel : ObservableRecipient +public partial class ForDevelopersViewModel : ObservableRecipient { + IMidiServiceRegistrySettingsService m_registrySettingsService; public bool IsDeveloperModeEnabled => WindowsDeveloperModeHelper.IsDeveloperModeEnabled; - public ForDevelopersViewModel() + [ObservableProperty] + public bool enableDiscoveryAndProtocolNegotiation; + + [ObservableProperty] + public bool enableMmcss; + + [ObservableProperty] + public bool serviceRestartRequired; + + + private UInt32 m_currentlySavedDiscoveryTimeoutValue; + + [ObservableProperty] + public UInt32 discoveryTimeout; + + public UInt32 DiscoveryTimeoutMaximum => 50000; // TODO: This needs to stay in sync with MidiDefs.h + public UInt32 DiscoveryTimeoutMinimum => 500; // TODO: This needs to stay in sync with MidiDefs.h + + [ObservableProperty] + public bool discoveryTimeoutHasChanged; + + + partial void OnDiscoveryTimeoutChanged(UInt32 value) { + if (value != m_currentlySavedDiscoveryTimeoutValue) + { + DiscoveryTimeoutHasChanged = true; + } + else + { + DiscoveryTimeoutHasChanged = false; + } + } + + partial void OnEnableDiscoveryAndProtocolNegotiationChanged(bool value) + { + // todo: should check against original values + ServiceRestartRequired = true; + } + + partial void OnEnableMmcssChanged(bool value) + { + // todo: should check against original values + ServiceRestartRequired = true; + } + + + public ICommand RestartServiceCommand { get; private set; } + + public ICommand CommitDiscoveryTimeoutCommand { get; private set; } + + public ICommand ShowMidiKernelStreamingDevicesCommand { get; private set; } + + + public ForDevelopersViewModel(IMidiServiceRegistrySettingsService registrySettingsService) + { + m_registrySettingsService = registrySettingsService; + + // TODO read these values from the registry + + m_currentlySavedDiscoveryTimeoutValue = m_registrySettingsService.GetMidi2DiscoveryTimeoutMS(); + + DiscoveryTimeout = m_currentlySavedDiscoveryTimeoutValue; + + EnableMmcss = m_registrySettingsService.GetUseMMCSS(); + EnableDiscoveryAndProtocolNegotiation = m_registrySettingsService.GetMidi2DiscoveryEnabled(); + + ServiceRestartRequired = false; + + + RestartServiceCommand = new RelayCommand(() => + { + // TODO: Call into service service to restart + }); + + CommitDiscoveryTimeoutCommand = new RelayCommand(() => + { + // TODO: save change to registry + + ServiceRestartRequired = true; + m_currentlySavedDiscoveryTimeoutValue = DiscoveryTimeout; + }); + + ShowMidiKernelStreamingDevicesCommand = new RelayCommand(() => + { + // TODO: save change to registry + + + }); + } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ShellViewModel.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ShellViewModel.cs index e8baddf06..96311cf2b 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ShellViewModel.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/ViewModels/ShellViewModel.cs @@ -18,10 +18,13 @@ public class ShellViewModel : ObservableRecipient private readonly IMidiConfigFileService m_configFileService; - public bool AreDeveloperOptionsEnabled - { - get => _generalSettingsService.ShowDeveloperOptions; - } + public bool IsDeveloperModeEnabled => WindowsDeveloperModeHelper.IsDeveloperModeEnabled; + + + //public bool AreDeveloperOptionsEnabled + //{ + // get => _generalSettingsService.ShowDeveloperOptions; + //} public INavigationService NavigationService { diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/ForDevelopersPage.xaml b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/ForDevelopersPage.xaml index 52488e3e2..0e9e89a17 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/ForDevelopersPage.xaml +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/ForDevelopersPage.xaml @@ -27,94 +27,72 @@ Margin="1,0,0,8" HorizontalAlignment="Left" TextWrapping="Wrap"> - + + + +