diff --git a/build/staging/version/BundleInfo.wxi b/build/staging/version/BundleInfo.wxi index 4aeb86dca..cbcd36974 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 18c9b039f..5d74cd150 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 = "Developer Preview 9"; - public const string BuildFullVersion = "1.0.2-preview-9.250106-1428"; + public const string BuildFullVersion = "1.0.2-preview-9.250112-1659"; public const string VersionMajor = "1"; public const string VersionMinor = "0"; public const string VersionRevision = "2"; - public const string VersionDateNumber = "250106"; - public const string VersionTimeNumber = "1428"; + public const string VersionDateNumber = "250112"; + public const string VersionTimeNumber = "1659"; } } diff --git a/build/staging/version/WindowsMidiServicesVersion.h b/build/staging/version/WindowsMidiServicesVersion.h index 5799c747f..66f3ed85f 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"Developer Preview 9" -#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.2-preview-9.250106-1428" +#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL L"1.0.2-preview-9.250112-1659" #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"2" -#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250106" -#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"1428" +#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_DATE_NUMBER L"250112" +#define WINDOWS_MIDI_SERVICES_BUILD_VERSION_TIME_NUMBER L"1659" #endif diff --git a/diagnostics/trace-logging/MidiServices.wprp b/diagnostics/trace-logging/MidiServices.wprp index 5a02a897f..88807371c 100644 --- a/diagnostics/trace-logging/MidiServices.wprp +++ b/diagnostics/trace-logging/MidiServices.wprp @@ -1,41 +1,12 @@ - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -65,9 +36,7 @@ - - - + diff --git a/samples/cpp-winrt/basics/client-basics-cpp.vcxproj b/samples/cpp-winrt/basics/client-basics-cpp.vcxproj index acd4c5e92..183d64bbf 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true false diff --git a/samples/cpp-winrt/basics/packages.config b/samples/cpp-winrt/basics/packages.config index adcdab936..1c7a38d46 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 ed42dd4ec..c264f300c 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true diff --git a/samples/cpp-winrt/loopback-endpoints/packages.config b/samples/cpp-winrt/loopback-endpoints/packages.config index adcdab936..1c7a38d46 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 adcdab936..1c7a38d46 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 d817f8320..2e71022df 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true 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 adcdab936..1c7a38d46 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 fd4d1fa97..22df94d7a 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true diff --git a/samples/cpp-winrt/static-enum-endpoints/packages.config b/samples/cpp-winrt/static-enum-endpoints/packages.config index adcdab936..1c7a38d46 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 d19fa4b5f..8a48d94ab 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true diff --git a/samples/cpp-winrt/watch-endpoints/packages.config b/samples/cpp-winrt/watch-endpoints/packages.config index adcdab936..1c7a38d46 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 f6f339ae7..8b0664f0e 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true diff --git a/src/api/Service/Exe/MidiDeviceManager.cpp b/src/api/Service/Exe/MidiDeviceManager.cpp index 4fdbde58e..dcf4c6a69 100644 --- a/src/api/Service/Exe/MidiDeviceManager.cpp +++ b/src/api/Service/Exe/MidiDeviceManager.cpp @@ -2259,20 +2259,20 @@ CMidiDeviceManager::UseFallbackMidi1PortDefinition( additionalProperties.Append(winrt::to_hstring(STRING_PKEY_MIDI_FunctionBlocksAreStatic)); auto deviceInfo = DeviceInformation::CreateFromIdAsync(umpDeviceInterfaceId, additionalProperties, winrt::Windows::Devices::Enumeration::DeviceInformationKind::DeviceInterface).get(); - auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_FunctionBlocksAreStatic)); - if (prop) - { - auto functionBlocksAreStatic = winrt::unbox_value>(prop); - if (functionBlocksAreStatic) - { - TraceLoggingWrite( - MidiSrvTelemetryProvider::Provider(), - MIDI_TRACE_EVENT_INFO, - TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), - TraceLoggingLevel(WINEVENT_LEVEL_INFO), - TraceLoggingPointer(this, "this"), - TraceLoggingWideString(L"Using static function blocks", MIDI_TRACE_EVENT_MESSAGE_FIELD) - ); + //auto prop = deviceInfo.Properties().Lookup(winrt::to_hstring(STRING_PKEY_MIDI_FunctionBlocksAreStatic)); + //if (prop) + //{ + //auto functionBlocksAreStatic = winrt::unbox_value>(prop); + //if (functionBlocksAreStatic) + //{ + // TraceLoggingWrite( + // MidiSrvTelemetryProvider::Provider(), + // MIDI_TRACE_EVENT_INFO, + // TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + // TraceLoggingLevel(WINEVENT_LEVEL_INFO), + // TraceLoggingPointer(this, "this"), + // TraceLoggingWideString(L"Using static function blocks", MIDI_TRACE_EVENT_MESSAGE_FIELD) + // ); for (UINT flow = 0; flow < 2; flow++) { @@ -2284,8 +2284,8 @@ CMidiDeviceManager::UseFallbackMidi1PortDefinition( portInfo[flow][groupIndex].InterfaceId = umpDeviceInterfaceId; } } - } - } + //} + //} return S_OK; } diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp index c47d774eb..8f44b4a85 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.cpp @@ -43,9 +43,16 @@ CMidi2NetworkMidiBiDi::Initialize( // look up the endpointDeviceInterfaceId in our list of endpoints, and connect to it m_connection = TransportState::Current().GetSessionConnection(m_endpointDeviceInterfaceId); - RETURN_HR_IF_NULL(E_INVALIDARG, m_connection); - m_connection->SetMidiCallback(this); + if (auto conn = m_connection.lock()) + { + RETURN_IF_FAILED(conn->ConnectMidiCallback(this)); + } + else + { + RETURN_IF_FAILED(E_INVALIDARG); + } + return S_OK; } @@ -62,15 +69,16 @@ CMidi2NetworkMidiBiDi::Shutdown() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - m_callback = nullptr; - m_context = 0; + // TODO: This causes the service to crash when the remote network endpoint disconnects. Need to look into this further. - if (m_connection) + if (auto ptr = m_connection.lock()) { - m_connection->RemoveMidiCallback(); + ptr->DisconnectMidiCallback(); m_connection.reset(); } - + + m_callback = nullptr; + m_context = 0; return S_OK; } @@ -88,9 +96,10 @@ CMidi2NetworkMidiBiDi::SendMidiMessage( RETURN_HR_IF_NULL(E_INVALIDARG, message); RETURN_HR_IF(E_INVALIDARG, size < sizeof(uint32_t)); - RETURN_HR_IF_NULL(E_UNEXPECTED, m_connection); - - RETURN_IF_FAILED(m_connection->SendMidiMessagesToNetwork(message, size)); + if (auto conn = m_connection.lock()) + { + RETURN_IF_FAILED(conn->SendMidiMessagesToNetwork(message, size)); + } return S_OK; } diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h index c3bcb6733..8b9dab1d7 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiBidi.h @@ -22,12 +22,12 @@ class CMidi2NetworkMidiBiDi : STDMETHOD(Shutdown)(); private: - IMidiCallback* m_callback{ nullptr }; + wil::com_ptr_nothrow m_callback{ nullptr }; LONGLONG m_context{ 0 }; std::wstring m_endpointDeviceInterfaceId{ }; - std::shared_ptr m_connection{ nullptr }; + std::weak_ptr m_connection; }; diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp index bf8a06ffa..9811715c5 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiConfigurationManager.cpp @@ -298,7 +298,9 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( { auto hostEntry = hostsSection.GetNamedObject(it.Current().Key()); - MidiNetworkHostDefinition definition{ }; + auto definition = std::make_shared(); + RETURN_IF_NULL_ALLOC(definition); + winrt::hstring validationErrorMessage{ }; // currently, UDP is the only allowed protocol @@ -309,18 +311,20 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( validationErrorMessage = L"Invalid network protocol '" + protocol + L"' specified."; } - definition.EntryIdentifier = internal::TrimmedHStringCopy(it.Current().Key()); + definition->EntryIdentifier = internal::TrimmedHStringCopy(it.Current().Key()); + + definition->Enabled = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY, true); + definition->Advertise = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_MDNS_ADVERTISE_KEY, true); - definition.Enabled = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY, true); - definition.Advertise = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_MDNS_ADVERTISE_KEY, true); + definition->CreateMidi1Ports = hostEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_CREATE_MIDI1_PORTS_KEY, MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT); - definition.UmpEndpointName = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_COMMON_NAME_PROPERTY, L"")); - definition.ProductInstanceId = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_PRODUCT_INSTANCE_ID_PROPERTY, L"")); + definition->UmpEndpointName = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_ENDPOINT_COMMON_NAME_PROPERTY, L"")); + definition->ProductInstanceId = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_PRODUCT_INSTANCE_ID_PROPERTY, L"")); - definition.Port = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO)); + definition->Port = internal::TrimmedHStringCopy(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO)); - definition.Authentication = MidiNetworkHostAuthenticationFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_VALUE_NONE)); - definition.ConnectionPolicy = MidiNetworkHostConnectionPolicyFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_ALLOW_IPV4_VALUE_ANY)); + definition->Authentication = MidiNetworkHostAuthenticationFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_HOST_AUTHENTICATION_VALUE_NONE)); + definition->ConnectionPolicy = MidiNetworkHostConnectionPolicyFromJsonString(hostEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_CONNECTION_POLICY_ALLOW_IPV4_VALUE_ANY)); // read the list of ip info //if (definition.ConnectionPolicy != MidiNetworkHostConnectionPolicy::PolicyAllowAllConnections) @@ -338,14 +342,14 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( //} // read authentication information - if (definition.Authentication != MidiNetworkHostAuthentication::NoAuthentication) + if (definition->Authentication != MidiNetworkHostAuthentication::NoAuthentication) { - if (definition.Authentication == MidiNetworkHostAuthentication::PasswordAuthentication) + if (definition->Authentication == MidiNetworkHostAuthentication::PasswordAuthentication) { // TODO: Read the password vault key } - else if (definition.Authentication == MidiNetworkHostAuthentication::UserAuthentication) + else if (definition->Authentication == MidiNetworkHostAuthentication::UserAuthentication) { // TODO: Read username/password vault key } @@ -376,7 +380,7 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( } } - definition.ServiceInstanceName = serviceInstanceNamePrefix; + definition->ServiceInstanceName = serviceInstanceNamePrefix; // TODO: See if the serviceInstanceName is already in use. If so, add a disambiguation number. Keep trying until unused one is found @@ -392,29 +396,29 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( if ((host.Type() == HostNameType::DomainName) && (host.RawName().ends_with(L".local"))) { - definition.HostName = host.RawName(); + definition->HostName = host.RawName(); break; } } - if (definition.Port == MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO || - definition.Port == L"" || - definition.Port == L"0") + if (definition->Port == MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PORT_VALUE_AUTO || + definition->Port == L"" || + definition->Port == L"0") { // this will cause us to use an auto-generated free port - definition.Port = L""; - definition.UseAutomaticPortAllocation = true; + definition->Port = L""; + definition->UseAutomaticPortAllocation = true; } else { - definition.UseAutomaticPortAllocation = false; + definition->UseAutomaticPortAllocation = false; } // validate the entry - if (SUCCEEDED(ValidateHostDefinition(definition, validationErrorMessage))) + if (SUCCEEDED(ValidateHostDefinition(*definition, validationErrorMessage))) { TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), @@ -425,15 +429,14 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( TraceLoggingWideString(L"Host definition validated. Creating host", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - // create the host - - auto host = std::make_shared(); - - RETURN_HR_IF_NULL(E_POINTER, host); - RETURN_IF_FAILED(host->Initialize(definition)); + // create the host definition // add to our collection of hosts - TransportState::Current().AddHost(host); + TransportState::Current().AddPendingHostDefinition(definition); + + responseObject.SetNamedValue( + MIDI_CONFIG_JSON_CONFIGURATION_RESPONSE_SUCCESS_PROPERTY_KEY, + jsonTrue); } else { @@ -448,7 +451,81 @@ CMidi2NetworkMidiConfigurationManager::UpdateConfiguration( // device (or application) if (clientsSection != nullptr && clientsSection.Size() > 0) { + for (auto const& it = clientsSection.First(); it.HasCurrent(); it.MoveNext()) + { + auto clientEntry = clientsSection.GetNamedObject(it.Current().Key()); + + auto definition = std::make_shared(); + RETURN_IF_NULL_ALLOC(definition); + + winrt::hstring validationErrorMessage{ }; + + // currently, UDP is the only allowed protocol + auto protocol = internal::ToLowerTrimmedHStringCopy(clientEntry.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PROTOCOL_KEY, MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PROTOCOL_VALUE_UDP)); + + if (protocol != MIDI_CONFIG_JSON_NETWORK_MIDI_NETWORK_PROTOCOL_VALUE_UDP) + { + validationErrorMessage = L"Invalid network protocol '" + protocol + L"' specified."; + } + else + { + definition->EntryIdentifier = internal::TrimmedHStringCopy(it.Current().Key()); + definition->Enabled = clientEntry.GetNamedBoolean(MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY, true); + + winrt::hstring localEndpointName{ }; + winrt::hstring localProductInstanceId{ }; + + // TODO: Add ability for config file to specify the localEndpointName and localProductInstanceId + if (localEndpointName.empty()) + { + std::wstring buffer{}; + DWORD bufferSize = MAX_COMPUTERNAME_LENGTH + 1; + buffer.resize(bufferSize); + + bool validName = GetComputerName(buffer.data(), &bufferSize); + if (validName) + { + localEndpointName = buffer; + } + } + // TODO: we may want to provide the local product instance id as a system-wide setting. Same with name + if (localProductInstanceId.empty()) + { + localProductInstanceId = L"8675309-OU812"; + } + + definition->LocalEndpointName = localEndpointName; + definition->LocalProductInstanceId = localProductInstanceId; + + auto matchSection = clientEntry.GetNamedObject(MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_OBJECT_KEY, nullptr); + + if (matchSection) + { + // TODO: Match on IP/Port, etc. + // for the moment, we only match on the actual device id, so must be mdns-advertised + + definition->MatchId = internal::TrimmedHStringCopy(matchSection.GetNamedString(MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_ID_KEY, L"")); + + + + + + + TransportState::Current().AddPendingClientDefinition(definition); + + responseObject.SetNamedValue( + MIDI_CONFIG_JSON_CONFIGURATION_RESPONSE_SUCCESS_PROPERTY_KEY, + jsonTrue); + } + else + { + // we have no way to match against endpoints, so this is a failure + validationErrorMessage = L"Missing match entry"; + } + } + + } } } diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp index 6206ac8de..ae2141e3c 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.cpp @@ -138,26 +138,28 @@ CMidi2NetworkMidiEndpointManager::OnDeviceWatcherAdded(enumeration::DeviceWatche m_foundAdvertisedHosts.insert_or_assign(args.Id(), args); - // TODO: do we need to signal the background thread to check the collection? + // signal the background thread to check the collection? + m_backgroundEndpointCreatorThreadWakeup.SetEvent(); return S_OK; } _Use_decl_annotations_ HRESULT -CMidi2NetworkMidiEndpointManager::OnDeviceWatcherUpdated(enumeration::DeviceWatcher const&, enumeration::DeviceInformationUpdate const& args) +CMidi2NetworkMidiEndpointManager::OnDeviceWatcherUpdated(enumeration::DeviceWatcher const&, enumeration::DeviceInformationUpdate const& /*args*/) { - TraceLoggingWrite( - MidiNetworkMidiTransportTelemetryProvider::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), - TraceLoggingWideString(args.Id().c_str(), "id") - ); - - // nothing to do here. We don't care about updates. + //TraceLoggingWrite( + // MidiNetworkMidiTransportTelemetryProvider::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), + // TraceLoggingWideString(args.Id().c_str(), "id") + //); + + // nothing to do here. We don't care about updates. This gets really spammy because + // the endpoint updates maybe with each mdns ad broadcast or something return S_OK; } @@ -232,17 +234,27 @@ CMidi2NetworkMidiEndpointManager::StartBackgroundEndpointCreator() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - std::jthread workerThread(&CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker, this); + std::jthread workerThread(std::bind_front(&CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker, this)); m_backgroundEndpointCreatorThread = std::move(workerThread); - m_backgroundEndpointCreatorThreadStopToken = m_backgroundEndpointCreatorThread.get_stop_token(); m_backgroundEndpointCreatorThread.detach(); + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + return S_OK; } +_Use_decl_annotations_ HRESULT -CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker() +CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker(std::stop_token stopToken) { TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), @@ -253,12 +265,24 @@ CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); +// winrt::init_apartment(); + //auto coInit = wil::CoInitializeEx(COINIT_MULTITHREADED); + // this is set up to run through one time before waiting for the wakeup // this way we can process anything added before the EndpointManager has been // initialized - while (!m_backgroundEndpointCreatorThreadStopToken.stop_requested()) + while (!stopToken.stop_requested()) { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Background worker loop", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + if (m_backgroundEndpointCreatorThreadWakeup.is_signaled()) { m_backgroundEndpointCreatorThreadWakeup.ResetEvent(); @@ -266,33 +290,212 @@ CMidi2NetworkMidiEndpointManager::EndpointCreatorWorker() // run through host entries - for (auto const& host : TransportState::Current().GetHosts()) + for (auto& definition : TransportState::Current().GetPendingHostDefinitions()) { - if (!host->HasStarted()) + if (definition->Created) { - LOG_IF_FAILED(host->Start()); + continue; + } + + if (!definition->Enabled) + { + continue; + } + + auto host = std::make_shared(); + LOG_IF_NULL_ALLOC(host); + + if (host != nullptr) + { + LOG_IF_FAILED(host->Initialize(*definition)); + + if (!host->HasStarted()) + { + LOG_IF_FAILED(host->Start()); + } + + // Remove pending entry + + definition->Created = true; + + // this ensures the host doesn't disappear + TransportState::Current().AddHost(host); } } // TODO: run through client definition entries. These aren't actual clients // but are instead just parameters needed to create connections to hosts when // they come online. - // for (auto const& clientDefinition : TransportState::Current().GetPendingClientDefinitions()) { - UNREFERENCED_PARAMETER(clientDefinition); + if (clientDefinition->Created) + { + continue; + } - // any requiring a match, see if we have a match in our deviceinformation collection + if (!clientDefinition->Enabled) + { + continue; + } + // any requiring a match, see if we have a match in our deviceinformation collection // - any that are IP/port direct, try to connect, but don't keep trying the same ones if they fail + // check for id first, as this is fastest + // TODO: right now, this is case-sensitive. It needs clean-up. + if (auto advertised = m_foundAdvertisedHosts.find(clientDefinition->MatchId); advertised != m_foundAdvertisedHosts.end()) + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Processing mdns entry", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(advertised->second.Id().c_str(), "id") + ); + + auto client = std::make_shared(); + LOG_IF_NULL_ALLOC(client); + + auto initHr = client->Initialize(*clientDefinition, advertised->second); + LOG_IF_FAILED(initHr); + + if (SUCCEEDED(initHr)) + { + TransportState::Current().AddClient(client); + + // todo: get the hostname and port, and then start the client + + //deviceId = advertised.Id(); + //deviceName = advertised.Name(); + + //host.HostName = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.HostName", L""); + //host.FullName = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.FullName", L""); + //host.ServiceInstanceName = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.InstanceName", L""); + //host.Port = internal::GetDeviceInfoProperty(entry.Properties(), L"System.Devices.Dnssd.PortNumber", 0); + + winrt::hstring hostNameString{}; + uint16_t port{ 0 }; + + const winrt::hstring hostNamePropertyKey = L"System.Devices.Dnssd.HostName"; + //const winrt::hstring hostNamePropertyKey = L"System.Devices.Dnssd.FullName"; + const winrt::hstring hostPortPropertyKey = L"System.Devices.Dnssd.PortNumber"; + const winrt::hstring ipAddressPropertyKey = L"System.Devices.IpAddress"; + + // we use IP address first, as that is the most reliable + if (advertised->second.Properties().HasKey(ipAddressPropertyKey)) + { + auto prop = advertised->second.Properties().Lookup(ipAddressPropertyKey).as>(); + winrt::com_array array; + prop.GetStringArray(array); + + // we only take the top one right now. We should take the others as well + if (array.size() > 0) + { + hostNameString = array.at(0); + } + } + // next we get the host name, but this relies on DNS being set up properly, + // which is often not the case on a network with just some devices and a laptop + else if (advertised->second.Properties().HasKey(hostNamePropertyKey)) + { + auto prop = advertised->second.Properties().Lookup(hostNamePropertyKey); + + if (prop) + { + hostNameString = winrt::unbox_value(prop); + } + } + + if (advertised->second.Properties().HasKey(hostPortPropertyKey)) + { + auto prop = advertised->second.Properties().Lookup(hostPortPropertyKey); + + if (prop) + { + port = winrt::unbox_value(prop); + } + } + + // the == 0 is hacky, but for midi, anything under 1024 is likely bogus + if (!hostNameString.empty() && port != 0) + { + + HostName hostName(hostNameString); + winrt::hstring portNameString = winrt::to_hstring(port); + + auto startHr = client->Start(hostName, portNameString); + + LOG_IF_FAILED(startHr); + + if (SUCCEEDED(startHr)) + { + // Mark as created so we don't try to process it again + clientDefinition->Created = true; + } + else + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Failed to start client", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(advertised->second.Id().c_str(), "id"), + TraceLoggingWideString(hostNameString.c_str(), "hostname string"), + TraceLoggingWideString(hostName.ToString().c_str(), "created hostname"), + TraceLoggingWideString(portNameString.c_str(), "port") + ); + } + } + else + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Unable to resolve remote device", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(advertised->second.Id().c_str(), "id") + ); + } + + } + else + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Failed to initialize client", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(advertised->second.Id().c_str(), "id") + ); + + } + + } + } // TODO: wait for notification of new hosts online or new entries added via config m_backgroundEndpointCreatorThreadWakeup.wait(10000); } + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + return S_OK; } @@ -427,6 +630,7 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint( std::wstring const& remoteEndpointProductInstanceId, winrt::Windows::Networking::HostName const& hostName, std::wstring const& networkPort, + bool umpOnly, std::wstring& createdNewDeviceInstanceId, std::wstring& createdNewEndpointDeviceInterfaceId ) @@ -527,7 +731,17 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint( // TODO: Add custom properties for the network information + std::wstring endpointDescription{ L"Network MIDI 2.0 endpoint "}; + switch (thisServiceRole) + { + case MidiNetworkConnectionRole::ConnectionWindowsIsHost: + endpointDescription += L"(This PC is the Network Host)"; + break; + case MidiNetworkConnectionRole::ConnectionWindowsIsClient: + endpointDescription += L"(This PC is a Network Client)"; + break; + } MIDIENDPOINTCOMMONPROPERTIES commonProperties{}; commonProperties.TransportId = TRANSPORT_LAYER_GUID; @@ -535,7 +749,7 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint( commonProperties.FriendlyName = friendlyName.c_str(); commonProperties.TransportCode = transportCode.c_str(); commonProperties.EndpointName = endpointName.c_str(); - commonProperties.EndpointDescription = nullptr; + commonProperties.EndpointDescription = endpointDescription.c_str(); commonProperties.CustomEndpointName = nullptr; commonProperties.CustomEndpointDescription = nullptr; commonProperties.UniqueIdentifier = remoteEndpointProductInstanceId.c_str(); @@ -551,7 +765,7 @@ CMidi2NetworkMidiEndpointManager::CreateNewEndpoint( RETURN_IF_FAILED(m_midiDeviceManager->ActivateEndpoint( (PCWSTR)m_parentDeviceId.c_str(), // parent instance Id - false, // UMP-only. When set to false, WinMM MIDI 1.0 ports are created + umpOnly, // UMP-only. When set to false, WinMM MIDI 1.0 ports are created MidiFlow::MidiFlowBidirectional, // MIDI Flow &commonProperties, (ULONG)0, //interfaceDeviceProperties.size(), diff --git a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h index 99fe22611..e68fae356 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/Midi2.NetworkMidiEndpointManager.h @@ -28,6 +28,7 @@ class CMidi2NetworkMidiEndpointManager : _In_ std::wstring const& remoteEndpointProductInstanceId, _In_ winrt::Windows::Networking::HostName const& hostName, _In_ std::wstring const& networkPort, + _In_ bool umpOnly, _Out_ std::wstring& createdNewDeviceInstanceId, _Out_ std::wstring& createdNewEndpointDeviceInterfaceId )); @@ -68,8 +69,8 @@ class CMidi2NetworkMidiEndpointManager : std::jthread m_backgroundEndpointCreatorThread; - std::stop_token m_backgroundEndpointCreatorThreadStopToken; + //std::stop_token m_backgroundEndpointCreatorThreadStopToken; wil::slim_event_manual_reset m_backgroundEndpointCreatorThreadWakeup; - HRESULT EndpointCreatorWorker(); + HRESULT EndpointCreatorWorker(_In_ std::stop_token stopToken); }; diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp index 78fa0a451..aaba5ba94 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.cpp @@ -31,7 +31,7 @@ inline const winrt::hstring BuildFullServiceInstanceName(_In_ winrt::hstring con } - +// TODO: Change this method to "Start" and have the parameters passed into Initialize instead of this. _Use_decl_annotations_ HRESULT @@ -56,18 +56,17 @@ MidiNetworkAdvertiser::Advertise( auto fullServiceName = BuildFullServiceInstanceName(serviceInstanceNameWithoutSuffix); - - auto service = DnssdServiceInstance( + m_serviceInstance = DnssdServiceInstance( fullServiceName, hostName, port); // add the txt attributes per the spec - service.TextAttributes().Insert(L"UMPEndpointName", midiEndpointName); - service.TextAttributes().Insert(L"ProductInstanceId", midiProductInstanceId); + m_serviceInstance.TextAttributes().Insert(L"UMPEndpointName", midiEndpointName); + m_serviceInstance.TextAttributes().Insert(L"ProductInstanceId", midiProductInstanceId); // register with the socket that's bound to the port - auto registration = service.RegisterDatagramSocketAsync(boundSocket).get(); + auto registration = m_serviceInstance.RegisterDatagramSocketAsync(boundSocket).get(); switch (registration.Status()) { @@ -137,6 +136,15 @@ MidiNetworkAdvertiser::Advertise( RETURN_IF_FAILED(E_FAIL); } + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); } @@ -153,7 +161,7 @@ MidiNetworkAdvertiser::Shutdown() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - + m_serviceInstance = nullptr; return S_OK; diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h index 4b51d3ca8..5e3819a1e 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkAdvertiser.h @@ -31,6 +31,6 @@ class MidiNetworkAdvertiser HRESULT Shutdown(); private: - + winrt::Windows::Networking::ServiceDiscovery::Dnssd::DnssdServiceInstance m_serviceInstance{ nullptr }; }; \ No newline at end of file diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp index 580d3ae4e..bfb86c89c 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.cpp @@ -11,7 +11,10 @@ _Use_decl_annotations_ HRESULT -MidiNetworkClient::Initialize(MidiNetworkClientDefinition& clientDefinition) +MidiNetworkClient::Initialize( + MidiNetworkClientDefinition& clientDefinition, + enumeration::DeviceInformation advertisedHost +) { TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), @@ -22,16 +25,226 @@ MidiNetworkClient::Initialize(MidiNetworkClientDefinition& clientDefinition) TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - UNREFERENCED_PARAMETER(clientDefinition); + UNREFERENCED_PARAMETER(advertisedHost); + m_createUmpEndpointsOnly = !clientDefinition.CreateMidi1Ports; + m_thisEndpointName = clientDefinition.LocalEndpointName; + m_thisProductInstanceId = clientDefinition.LocalProductInstanceId; return S_OK; } +_Use_decl_annotations_ +void MidiNetworkClient::OnMessageReceived( + _In_ winrt::Windows::Networking::Sockets::DatagramSocket const& sender, + _In_ winrt::Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs const& args) +{ + UNREFERENCED_PARAMETER(sender); + + auto reader = args.GetDataReader(); + + if (reader != nullptr && reader.UnconsumedBufferLength() < MINIMUM_VALID_UDP_PACKET_SIZE) + { + // not a message we understand. Needs to be at least the size of the + // MIDI header plus a command packet header. Really it needs to be larger, but + // just trying to weed out blips + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_WARNING, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_WARNING), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Undersized packet", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + + // todo: does the reader need to be cleared? + + return; + } + + uint32_t udpHeader = reader.ReadUInt32(); + + if (udpHeader != MIDI_UDP_PAYLOAD_HEADER) + { + // not a message we understand + + return; + } + if (m_networkConnection) + { + m_networkConnection->ProcessIncomingMessage(reader); + } + else + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Message received from remote client, connection is nullptr", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + } + + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + +} + + +_Use_decl_annotations_ +HRESULT +MidiNetworkClient::Start( + HostName const& remoteHostName, + winrt::hstring const& remotePort +) +{ + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::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), + TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"), + TraceLoggingWideString(remotePort.c_str(), "remote port")); + + auto conn = std::make_shared(); + RETURN_IF_NULL_ALLOC(conn); + + DatagramSocket socket; + m_socket = std::move(socket); + + m_socket.Control().QualityOfService(SocketQualityOfService::LowLatency); + m_socket.Control().DontFragment(true); + + auto messageReceivedHandler = winrt::Windows::Foundation::TypedEventHandler(this, &MidiNetworkClient::OnMessageReceived); + m_messageReceivedEventToken = m_socket.MessageReceived(messageReceivedHandler); + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Event handler hooked up. About to connect socket", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"), + TraceLoggingWideString(remotePort.c_str(), "remote port")); + + + //EndpointPair pair( + // nullptr, + // L"", + // remoteHostName, + // remotePort); + + // establish the remote connection + try + { + // this throws if the address can't be resolved or if + // the connect otherwise fails + + //m_socket.ConnectAsync(pair).get(); + m_socket.ConnectAsync(remoteHostName, remotePort); + } + catch (winrt::hresult_error err) + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"HRESULT Exception connecting to socket", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"), + TraceLoggingWideString(remotePort.c_str(), "remote port"), + TraceLoggingHResult(err.code(), "hresult"), + TraceLoggingWideString(err.message().c_str(), "error message") + ); + + RETURN_IF_FAILED(E_NOTFOUND); + } + catch (...) + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exception connecting to socket", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"), + TraceLoggingWideString(remotePort.c_str(), "remote port")); + + RETURN_IF_FAILED(E_NOTFOUND); + } + + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"About to initialize connection", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"), + TraceLoggingWideString(remotePort.c_str(), "remote port")); + + RETURN_IF_FAILED(conn->Initialize( + MidiNetworkConnectionRole::ConnectionWindowsIsClient, + m_socket, + remoteHostName, + remotePort, + m_thisEndpointName, + m_thisProductInstanceId, + TransportState::Current().TransportSettings.RetransmitBufferMaxCommandPacketCount, + TransportState::Current().TransportSettings.ForwardErrorCorrectionMaxCommandPacketCount, + m_createUmpEndpointsOnly + )); + + TransportState::Current().AddNetworkConnection(remoteHostName, remotePort, conn); + + m_networkConnection = conn; + + // try to establish connection in-protocol + + // TODO: Need to wire up other security approaches here + // TODO: The invitation send should be in a loop so it's repeated if + // there's no response + RETURN_IF_FAILED(conn->SendInvitation()); + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Invitation sent", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingWideString(remoteHostName.ToString().c_str(), "remote hostname"), + TraceLoggingWideString(remotePort.c_str(), "remote port")); + + + // todo: associate connection with the endpoint id + + + // todo: initiate discovery + + + return S_OK; +} + @@ -49,6 +262,13 @@ MidiNetworkClient::Shutdown() ); + if (m_socket) + { + m_socket.MessageReceived(m_messageReceivedEventToken); + m_socket.Close(); + m_socket = nullptr; + } + return S_OK; } diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h index 3bb7d5aab..4b71c0699 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkClient.h @@ -10,13 +10,29 @@ struct MidiNetworkClientDefinition { + bool Created{ false }; + winrt::hstring EntryIdentifier; // internal bool Enabled{ true }; + winrt::hstring Name; // the name of the endpoint before we do discovery + + winrt::hstring LocalEndpointName; // this is required by protocol + winrt::hstring LocalProductInstanceId; // also required by protocol + + + bool CreateMidi1Ports{ MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT }; + + // protocol // MidiNetworkHostProtocol NetworkProtocol{ MidiNetworkHostProtocol::ProtocolDefault }; + // all match criteria follows + + winrt::hstring MatchId{}; + + }; @@ -24,12 +40,34 @@ struct MidiNetworkClientDefinition class MidiNetworkClient { public: - HRESULT Initialize(_In_ MidiNetworkClientDefinition& clientDefinition); + // will need some different versions of Initialize for the different ways of connecting + HRESULT Initialize( + _In_ MidiNetworkClientDefinition& clientDefinition, + _In_ enumeration::DeviceInformation advertisedHost + ); + + HRESULT Start( + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort + ); HRESULT Shutdown(); private: + bool m_createUmpEndpointsOnly{ true }; + + std::shared_ptr m_networkConnection{ nullptr }; + winrt::Windows::Networking::Sockets::DatagramSocket m_socket{ nullptr }; + + std::wstring m_thisEndpointName{ }; + std::wstring m_thisProductInstanceId{ }; + + + winrt::event_token m_messageReceivedEventToken; + void OnMessageReceived( + _In_ winrt::Windows::Networking::Sockets::DatagramSocket const& sender, + _In_ winrt::Windows::Networking::Sockets::DatagramSocketMessageReceivedEventArgs const& args); }; \ No newline at end of file diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp index 54500d452..996dd1649 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.cpp @@ -29,7 +29,8 @@ MidiNetworkConnection::Initialize( std::wstring const& thisEndpointName, std::wstring const& thisProductInstanceId, uint16_t const retransmitBufferMaxCommandPacketCount, - uint8_t const maxForwardErrorCorrectionCommandPacketCount + uint8_t const maxForwardErrorCorrectionCommandPacketCount, + bool createUmpEndpointsOnly ) { TraceLoggingWrite( @@ -48,6 +49,8 @@ MidiNetworkConnection::Initialize( m_remoteHostName = hostName; m_remotePort = port; + m_createUmpEndpointsOnly = createUmpEndpointsOnly; + m_thisEndpointName = thisEndpointName; m_thisProductInstanceId = thisProductInstanceId; @@ -66,8 +69,7 @@ MidiNetworkConnection::Initialize( RETURN_IF_FAILED(E_OUTOFMEMORY); } - m_lastSentUmpCommandSequenceNumber = 0; - m_lastSentUmpCommandSequenceNumber--; // init to this so first real one is zero + RETURN_IF_FAILED(ResetSequenceNumbers()); // create the data writer m_writer = std::make_shared(); @@ -107,6 +109,15 @@ MidiNetworkConnection::StartOutboundProcessingThreads() + + + + + + + + + return S_OK; } @@ -115,20 +126,28 @@ MidiNetworkConnection::StartOutboundProcessingThreads() _Use_decl_annotations_ HRESULT -MidiNetworkConnection::SetMidiCallback( - IMidiCallback* callback +MidiNetworkConnection::ConnectMidiCallback( + wil::com_ptr_nothrow callback ) { + RETURN_HR_IF_NULL(E_INVALIDARG, callback); + TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::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) + TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingPointer(callback.get(), "callback") ); - RETURN_HR_IF_NULL(E_INVALIDARG, callback); + // the previous callback wasn't disconnected. Something + // is not as it should be, so we'll fail. + if (m_callback != nullptr) + { + RETURN_IF_FAILED(E_UNEXPECTED); + } m_callback = callback; @@ -136,7 +155,7 @@ MidiNetworkConnection::SetMidiCallback( } HRESULT -MidiNetworkConnection::RemoveMidiCallback() +MidiNetworkConnection::DisconnectMidiCallback() { TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), @@ -147,7 +166,10 @@ MidiNetworkConnection::RemoveMidiCallback() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - m_callback = nullptr; + if (m_callback != nullptr) + { + m_callback = nullptr; + } return S_OK; } @@ -170,7 +192,11 @@ MidiNetworkConnection::HandleIncomingUmpData( TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - if (m_callback != nullptr) + + // empty UMP packets are a keep-alive approach + // callback can be null if there are no open connections + // from the client, but the remote device is sending messages + if (m_sessionActive && words.size() > 0 && m_callback != nullptr) { // this may have more than one message, so we need to tease it apart here // and send the individual messages @@ -194,10 +220,25 @@ MidiNetworkConnection::HandleIncomingUmpData( return S_OK; } +HRESULT +MidiNetworkConnection::ResetSequenceNumbers() +{ + // reset the last sent sequence number + m_lastSentUmpCommandSequenceNumber = 0; + m_lastSentUmpCommandSequenceNumber--; // prepare for next + + // reset the last received sequence number. + m_lastReceivedUmpCommandSequenceNumber = 0; + m_lastReceivedUmpCommandSequenceNumber--; + // clear out retransmit buffer + m_retransmitBuffer.clear(); + + return S_OK; +} HRESULT -MidiNetworkConnection::HandleIncomingBye() +MidiNetworkConnection::EndActiveSession() { TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), @@ -208,29 +249,188 @@ MidiNetworkConnection::HandleIncomingBye() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + m_sessionActive = false; + + // clear the outbound queue + m_outgoingUmpMessages.clear(); + ResetSequenceNumbers(); + + m_callback = nullptr; + RETURN_IF_FAILED(TransportState::Current().GetEndpointManager()->DeleteEndpoint(m_sessionDeviceInstanceId)); + m_sessionDeviceInstanceId.clear(); // send bye reply + + auto lock = m_socketWriterLock.lock(); RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); RETURN_IF_FAILED(m_writer->WriteCommandByeReply()); RETURN_IF_FAILED(m_writer->Send()); + // clear the association with the SWD + RETURN_IF_FAILED(TransportState::Current().DisassociateMidiEndpointFromConnection(m_sessionEndpointDeviceInterfaceId)); + m_sessionEndpointDeviceInterfaceId.clear(); + + + return S_OK; +} + + +HRESULT +MidiNetworkConnection::HandleIncomingBye() +{ + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::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) + ); + + RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer); + RETURN_HR_IF_NULL(E_UNEXPECTED, TransportState::Current().GetEndpointManager()); + if (m_sessionActive) { - if (TransportState::Current().GetEndpointManager() != nullptr) + RETURN_IF_FAILED(EndActiveSession()); + } + else + { + // not an active session. Nothing to clean up + // but we should NAK the Bye saying there's no active session + + auto lock = m_socketWriterLock.lock(); + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + RETURN_IF_FAILED(m_writer->WriteCommandNAK(0, MidiNetworkCommandNAKReason::CommandNAKReason_CommandNotExpected, L"BYE received when there's no active session.")); + RETURN_IF_FAILED(m_writer->Send()); + } + + return S_OK; +} + + +_Use_decl_annotations_ +HRESULT +MidiNetworkConnection::HandleIncomingInvitationReplyAccepted( + MidiNetworkCommandPacketHeader const& header, + std::wstring const& remoteHostUmpEndpointName, + std::wstring const& remoteHostProductInstanceId +) +{ + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::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) + ); + + if (m_role == MidiNetworkConnectionRole::ConnectionWindowsIsClient) + { + if (m_sessionActive) + { + // per protocol, if we've already accepted this, then just ignore it + return S_OK; + } + + // TODO: will we accept a session invitation from the specified hostname? + // TODO: Also need to check auth mechanism and follow instructions in 6.4 and send a Bye if not supported + + // todo: see if we already have a session active for this remote. If so, use it. + // otherwise, we need to spin up a new session + + std::wstring newDeviceInstanceId{ }; + std::wstring newEndpointDeviceInterfaceId{ }; + + if (TransportState::Current().GetEndpointManager()->IsInitialized()) { - RETURN_IF_FAILED(TransportState::Current().GetEndpointManager()->DeleteEndpoint(m_sessionDeviceInstanceId)); - RETURN_IF_FAILED(TransportState::Current().RemoveSessionConnection(m_sessionEndpointDeviceInterfaceId)); + // Create the endpoint for Windows MIDI Services clients + HRESULT hr = S_OK; + + hr = TransportState::Current().GetEndpointManager()->CreateNewEndpoint( + MidiNetworkConnectionRole::ConnectionWindowsIsClient, + remoteHostUmpEndpointName, + remoteHostProductInstanceId, + m_remoteHostName, + m_remotePort, + m_createUmpEndpointsOnly, + newDeviceInstanceId, + newEndpointDeviceInterfaceId + ); - m_sessionActive = false; - m_sessionDeviceInstanceId.clear(); - m_sessionEndpointDeviceInterfaceId.clear(); + if (SUCCEEDED(hr)) + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Created MIDI endpoint", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + + m_sessionEndpointDeviceInterfaceId = internal::NormalizeEndpointInterfaceIdWStringCopy(newEndpointDeviceInterfaceId); + m_sessionDeviceInstanceId = internal::NormalizeDeviceInstanceIdWStringCopy(newDeviceInstanceId); + + // this is what the BiDi uses when it is created + RETURN_IF_FAILED(TransportState::Current().AssociateMidiEndpointWithConnection(m_sessionEndpointDeviceInterfaceId.c_str(), m_remoteHostName, m_remotePort.c_str())); + + // protocol negotiation needs to happen here, not in the endpoint creation + // because we need to wire up the connection first. Bit of a race. + + LOG_IF_FAILED(TransportState::Current().GetEndpointManager()->InitiateDiscoveryAndNegotiation(m_sessionEndpointDeviceInterfaceId)); + + m_sessionActive = true; + } + else + { + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Failed to create MIDI endpoint.", MIDI_TRACE_EVENT_MESSAGE_FIELD), + TraceLoggingHResult(hr, "hresult") + ); + + // let the other side know that we can't create the session + + auto lock = m_socketWriterLock.lock(); + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + // TODO: Move string to resources for localization + RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_Undefined, L"Error attempting to create endpoint. Bad data?")); + RETURN_IF_FAILED(m_writer->Send()); + + // exit out of here, and log while we're at it + RETURN_IF_FAILED(hr); + } } + } else { - // not an active session. Nothing to clean up + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_ERROR, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_ERROR), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"We are not in the client role, but received an invitation accept. Not normal.", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + + // we are a host, not a client, so NAK this per spec 6.4 + auto lock = m_socketWriterLock.lock(); + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + + // TODO: Move string to resources for localization + RETURN_IF_FAILED(m_writer->WriteCommandNAK(header.HeaderWord, MidiNetworkCommandNAKReason::CommandNAKReason_CommandNotExpected, L"Unexpected invitation accept sent to host.")); + RETURN_IF_FAILED(m_writer->Send()); } + + return S_OK; } @@ -252,17 +452,18 @@ MidiNetworkConnection::HandleIncomingInvitation( TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - UNREFERENCED_PARAMETER(capabilities); RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer); + RETURN_HR_IF_NULL(E_UNEXPECTED, TransportState::Current().GetEndpointManager()); if (m_role == MidiNetworkConnectionRole::ConnectionWindowsIsHost) { if (m_sessionActive) { - // if the session is already active, we simply accept + // if the session is already active, we simply accept it again + auto lock = m_socketWriterLock.lock(); RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); RETURN_IF_FAILED(m_writer->WriteCommandInvitationReplyAccepted(m_thisEndpointName, m_thisProductInstanceId)); RETURN_IF_FAILED(m_writer->Send()); @@ -281,50 +482,51 @@ MidiNetworkConnection::HandleIncomingInvitation( if (TransportState::Current().GetEndpointManager()->IsInitialized()) { - // Create the endpoint for Windows MIDI Services clients // This will also kick off discovery and protocol negotiation HRESULT hr = S_OK; - if (TransportState::Current().GetEndpointManager() == nullptr) - { - hr = E_UNEXPECTED; - } - else - { - hr = TransportState::Current().GetEndpointManager()->CreateNewEndpoint( - MidiNetworkConnectionRole::ConnectionWindowsIsHost, - clientUmpEndpointName, - clientProductInstanceId, - m_remoteHostName, - m_remotePort, - newDeviceInstanceId, - newEndpointDeviceInterfaceId - ); - } + + //UNREFERENCED_PARAMETER(clientProductInstanceId); + //UNREFERENCED_PARAMETER(clientUmpEndpointName); + hr = TransportState::Current().GetEndpointManager()->CreateNewEndpoint( + MidiNetworkConnectionRole::ConnectionWindowsIsHost, + clientUmpEndpointName, + clientProductInstanceId, + m_remoteHostName, + m_remotePort, + m_createUmpEndpointsOnly, + newDeviceInstanceId, + newEndpointDeviceInterfaceId + ); if (SUCCEEDED(hr)) { - m_sessionEndpointDeviceInterfaceId = newEndpointDeviceInterfaceId; - m_sessionDeviceInstanceId = newDeviceInstanceId; - - std::shared_ptr connection; - connection.reset(this); + m_sessionEndpointDeviceInterfaceId = internal::NormalizeEndpointInterfaceIdWStringCopy(newEndpointDeviceInterfaceId); + m_sessionDeviceInstanceId = internal::NormalizeDeviceInstanceIdWStringCopy(newDeviceInstanceId); // this is what the BiDi uses when it is created - TransportState::Current().AddSessionConnection(m_sessionEndpointDeviceInterfaceId, connection); + RETURN_IF_FAILED(TransportState::Current().AssociateMidiEndpointWithConnection(m_sessionEndpointDeviceInterfaceId.c_str(), m_remoteHostName, m_remotePort.c_str())); // protocol negotiation needs to happen here, not in the endpoint creation // because we need to wire up the connection first. Bit of a race. LOG_IF_FAILED(TransportState::Current().GetEndpointManager()->InitiateDiscoveryAndNegotiation(m_sessionEndpointDeviceInterfaceId)); + + // Tell the remote endpoint we've accepted the invitation + + auto lock = m_socketWriterLock.lock(); + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + RETURN_IF_FAILED(m_writer->WriteCommandInvitationReplyAccepted(m_thisEndpointName, m_thisProductInstanceId)); + RETURN_IF_FAILED(m_writer->Send()); + + m_sessionActive = true; } else { // let the other side know that we can't create the session auto lock = m_socketWriterLock.lock(); - RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); // TODO: Move string to resources for localization RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_Undefined, L"Error attempting to create endpoint. Bad data?")); @@ -333,23 +535,12 @@ MidiNetworkConnection::HandleIncomingInvitation( // exit out of here, and log while we're at it RETURN_IF_FAILED(hr); } - - // Tell the remote endpoint we've accepted the invitation - - auto lock = m_socketWriterLock.lock(); - - RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); - RETURN_IF_FAILED(m_writer->WriteCommandInvitationReplyAccepted(m_thisEndpointName, m_thisProductInstanceId)); - RETURN_IF_FAILED(m_writer->Send()); - - m_sessionActive = true; } else { // this shouldn't happen, but we handle it anyway auto lock = m_socketWriterLock.lock(); - RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); // TODO: Move string to resources for localization RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_Undefined, L"Host is unable to accept invitations at this time.")); @@ -384,6 +575,8 @@ MidiNetworkConnection::HandleIncomingPing(uint32_t const pingId) TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer); + auto lock = m_socketWriterLock.lock(); RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); @@ -435,6 +628,23 @@ std::wstring MidiNetworkConnection::ReadUtf8String( return ws; } +// TODO: This needs some checking. If we call this too many times, we +// should close the connection. Also, if there's a retransmit request +// already in progress, we shouldn't be asking for more if there's any +// overlap at all. +HRESULT +MidiNetworkConnection::RequestMissingPackets() +{ + auto lock = m_socketWriterLock.lock(); + + // this requests all packets after the last one we received + + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + RETURN_IF_FAILED(m_writer->WriteCommandRetransmitRequest(m_lastReceivedUmpCommandSequenceNumber + 1, 0)); + RETURN_IF_FAILED(m_writer->Send()); + + return S_OK; +} _Use_decl_annotations_ HRESULT @@ -499,13 +709,17 @@ MidiNetworkConnection::ProcessIncomingMessage( case CommandClientToHost_InvitationWithAuthentication: + // TODO break; case CommandClientToHost_InvitationWithUserAuthentication: + // TODO break; case CommandCommon_UmpData: { + // todo: If the session isn't active, we should reject any UMP data and NAK or BYE (see spec) + uint8_t numberOfWords = commandHeader.HeaderData.CommandPayloadLength; MidiSequenceNumber sequenceNumber(commandHeader.HeaderData.CommandSpecificData.AsUInt16); @@ -513,69 +727,64 @@ MidiNetworkConnection::ProcessIncomingMessage( // TODO: a message can have zero MIDI words, but a valid sequence number. Need to handle that. - - // TODO: This logic doesn't handle wrap. Need to have a window if (sequenceNumber <= m_lastReceivedUmpCommandSequenceNumber) { - - - // todo: skip all words + if (numberOfWords > 0) + { + // Skip all words in this command message because it is FEC. + for (uint8_t i = 0; i < numberOfWords && reader.UnconsumedBufferLength() >= sizeof(uint32_t); i++) + { + reader.ReadUInt32(); + } + } + else + { + // empty UMP message. This is fine + } } else if (sequenceNumber == m_lastReceivedUmpCommandSequenceNumber + 1) { - // todo: process UMP data - + // Process UMP data because this is the next expected sequence number m_lastReceivedUmpCommandSequenceNumber = sequenceNumber; - } - else - { - // skipped data. Re-request missing packets - } - - - - - - - - for (uint8_t i = 0; i < numberOfWords; i++) - { - if (reader.UnconsumedBufferLength() >= sizeof(uint32_t)) + if (numberOfWords > 0) { - // TODO: We need to check this sequence number, and if there's any gap from the last received, drop this data and request a retransmit - - if (sequenceNumber > m_lastReceivedUmpCommandSequenceNumber) + for (uint8_t i = 0; i < numberOfWords; i++) { - // add to our vector - words.push_back(reader.ReadUInt32()); - - } - else - { - // just read and discard - reader.ReadUInt32(); + if (reader.UnconsumedBufferLength() >= sizeof(uint32_t)) + { + // add to our vector + words.push_back(reader.ReadUInt32()); + } + else + { + // bad / incomplete packet + + auto lock = m_socketWriterLock.lock(); + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + RETURN_IF_FAILED(m_writer->WriteCommandNAK(commandHeader.HeaderWord, MidiNetworkCommandNAKReason::CommandNAKReason_CommandMalformed, L"Packet data incomplete")); + RETURN_IF_FAILED(m_writer->Send()); + + // TODO: We should request a retransmit of this packet + + RETURN_IF_FAILED(E_FAIL); + } } } else { - // bad / incomplete packet - - auto lock = m_socketWriterLock.lock(); - m_writer->WriteUdpPacketHeader(); - m_writer->WriteCommandNAK(commandHeader.HeaderWord, MidiNetworkCommandNAKReason::CommandNAKReason_CommandMalformed, L"Packet data incomplete"); - m_writer->Send(); - - // TODO: We should request a retransmit of this packet - - return E_FAIL; + // empty UMP messages. That's fine. } } - - if (sequenceNumber > m_lastReceivedUmpCommandSequenceNumber) + else if (sequenceNumber > m_lastReceivedUmpCommandSequenceNumber + 1) { - m_lastReceivedUmpCommandSequenceNumber = sequenceNumber; + // skipped data. Re-request missing packets + + // TODO: We should make sure we don't keep calling this for each + // UMP Data message within the same UDP packet. + + RETURN_IF_FAILED(RequestMissingPackets()); } if (words.size() > 0) @@ -606,6 +815,31 @@ MidiNetworkConnection::ProcessIncomingMessage( // TODO: handle session reset break; + case CommandHostToClient_InvitationReplyAccepted: + { + uint16_t endpointNameLengthInBytes = commandHeader.HeaderData.CommandSpecificData.AsBytes.Byte1 * sizeof(uint32_t); + uint16_t productInstanceIdLengthInBytes = commandHeader.HeaderData.CommandPayloadLength * sizeof(uint32_t) - endpointNameLengthInBytes; + + auto hostEndpointName = ReadUtf8String(reader, endpointNameLengthInBytes); + auto hostProductInstanceId = ReadUtf8String(reader, productInstanceIdLengthInBytes); + + LOG_IF_FAILED(HandleIncomingInvitationReplyAccepted(commandHeader, hostEndpointName, hostProductInstanceId)); + } + break; + + case CommandHostToClient_InvitationReplyPending: + // TODO: not sure we need to do anything with this + break; + + case CommandHostToClient_InvitationReplyAuthenticationRequired: + // TODO + break; + + case CommandHostToClient_InvitationReplyUserAuthenticationRequired: + // TODO + break; + + default: // TODO: unexpected command code. Send NAK. @@ -654,7 +888,8 @@ MidiNetworkConnection::HandleIncomingRetransmitRequest(uint16_t const startingSe TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - // TODO: A packet count of 0x0000 means to send all the data we have starting at the startingSequenceNumber + RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer); + // find the starting sequence number in the circular buffer @@ -686,6 +921,8 @@ MidiNetworkConnection::HandleIncomingRetransmitRequest(uint16_t const startingSe auto endPacket = firstPacket + retransmitPacketCount; + // packet count of 0 means to send everything we've got + if (retransmitPacketCount == 0x0000) { endPacket = m_retransmitBuffer.end(); @@ -705,6 +942,30 @@ MidiNetworkConnection::HandleIncomingRetransmitRequest(uint16_t const startingSe } +HRESULT +MidiNetworkConnection::SendInvitation() +{ + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::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) + ); + + auto lock = m_socketWriterLock.lock(); + + // TODO: When we support security, need to update the capabilities + + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + RETURN_IF_FAILED(m_writer->WriteCommandInvitation(MidiNetworkCommandInvitationCapabilities::Capabilities_None, m_thisEndpointName, m_thisProductInstanceId)); + RETURN_IF_FAILED(m_writer->Send()); + + return S_OK; +} + + _Use_decl_annotations_ HRESULT MidiNetworkConnection::SendMidiMessagesToNetwork(std::vector const& words) @@ -718,6 +979,8 @@ MidiNetworkConnection::SendMidiMessagesToNetwork(std::vector const& wo TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_writer); + auto sequenceNumber = ++m_lastSentUmpCommandSequenceNumber; auto lock = m_socketWriterLock.lock(); @@ -780,8 +1043,36 @@ MidiNetworkConnection::Shutdown() TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + if (m_callback) + { + m_callback = nullptr; + } + if (m_sessionActive) + { + RETURN_IF_FAILED(TransportState::Current().DisassociateMidiEndpointFromConnection(m_sessionEndpointDeviceInterfaceId)); + m_sessionActive = false; + } + + if (m_writer != nullptr) + { + auto lock = m_socketWriterLock.lock(); + RETURN_IF_FAILED(m_writer->WriteUdpPacketHeader()); + RETURN_IF_FAILED(m_writer->WriteCommandBye(MidiNetworkCommandByeReason::CommandByeReasonCommon_PowerDown, L"Connection closing.")); + RETURN_IF_FAILED(m_writer->Send()); + + LOG_IF_FAILED(m_writer->Shutdown()); + m_writer = nullptr; + } + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); return S_OK; } diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h index 0e9080683..18de33706 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkConnection.h @@ -32,7 +32,8 @@ class MidiNetworkConnection _In_ std::wstring const& thisEndpointName, _In_ std::wstring const& thisProductInstanceId, _In_ uint16_t const retransmitBufferMaxCommandPacketCount, - _In_ uint8_t const maxForwardErrorCorrectionCommandPacketCount + _In_ uint8_t const maxForwardErrorCorrectionCommandPacketCount, + _In_ bool createUmpEndpointsOnly ); HRESULT Shutdown(); @@ -48,12 +49,13 @@ class MidiNetworkConnection _In_ PVOID const bytes, _In_ UINT const byteCount); + HRESULT SendInvitation(); - HRESULT SetMidiCallback( - _In_ IMidiCallback* callback + HRESULT ConnectMidiCallback( + _In_ wil::com_ptr_nothrow callback ); - HRESULT RemoveMidiCallback(); + HRESULT DisconnectMidiCallback(); // todo: session info, connection to bidi streams, etc. @@ -61,8 +63,16 @@ class MidiNetworkConnection HRESULT StartOutboundProcessingThreads(); + HRESULT ResetSequenceNumbers(); + HRESULT EndActiveSession(); + HRESULT RequestMissingPackets(); + bool m_createUmpEndpointsOnly{ true }; + + + uint32_t m_emptyUmpIterationCounter{ 0 }; // this will increment over time + uint32_t m_emptyUmpIterationIntervalBeforeSending{ 10 }; // number of background thread iterations with no data before we send empty UMP packets. This changes over time. MidiNetworkConnectionRole m_role{}; @@ -75,7 +85,7 @@ class MidiNetworkConnection std::wstring m_sessionEndpointDeviceInterfaceId{}; // swd std::wstring m_sessionDeviceInstanceId{}; // what we used to create/delete the device bool m_sessionActive{ false }; - IMidiCallback* m_callback{ nullptr }; + wil::com_ptr_nothrow m_callback{ nullptr }; winrt::Windows::Networking::HostName m_remoteHostName{ nullptr }; std::wstring m_remotePort{ }; @@ -96,6 +106,11 @@ class MidiNetworkConnection _In_ std::wstring const& clientUmpEndpointName, _In_ std::wstring const& clientProductInstanceId); + HRESULT HandleIncomingInvitationReplyAccepted( + _In_ MidiNetworkCommandPacketHeader const& header, + _In_ std::wstring const& remoteHostUmpEndpointName, + _In_ std::wstring const& remoteHostProductInstanceId); + HRESULT HandleIncomingBye(); // this buffer holds the last n ping messages used to calculate latency diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp index 88e33f4ac..006192c96 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.cpp @@ -12,6 +12,9 @@ HRESULT MidiNetworkDataWriter::Send() { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + auto lock = m_dataWriterLock.lock(); m_dataWriter.StoreAsync().get(); @@ -24,6 +27,9 @@ HRESULT MidiNetworkDataWriter::WriteUdpPacketHeader() { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + auto lock = m_dataWriterLock.lock(); m_dataWriter.WriteUInt32(MIDI_UDP_PAYLOAD_HEADER); @@ -39,6 +45,9 @@ MidiNetworkDataWriter::InternalWriteCommandHeader( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + auto lock = m_dataWriterLock.lock(); m_dataWriter.WriteByte(commandCode); m_dataWriter.WriteByte(payloadLengthIn32BitWords); @@ -57,6 +66,10 @@ MidiNetworkDataWriter::InternalWriteCommandHeader( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + // don't lock here. the calling method will lock + //auto lock = m_dataWriterLock.lock(); m_dataWriter.WriteByte(commandCode); m_dataWriter.WriteByte(payloadLengthIn32BitWords); @@ -79,9 +92,10 @@ MidiNetworkDataWriter::Initialize( winrt::Windows::Storage::Streams::DataWriter writer(m_stream); - m_dataWriter = std::move(writer); + m_dataWriter = writer; RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + // per Network MIDI 2.0 specification m_dataWriter.ByteOrder(winrt::Windows::Storage::Streams::ByteOrder::BigEndian); return S_OK; @@ -92,11 +106,13 @@ HRESULT MidiNetworkDataWriter::WriteCommandPing(uint32_t pingId) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); byte payloadLengthIn32BitWords{ 1 }; - RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_Ping, payloadLengthIn32BitWords, 0)); + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_Ping, payloadLengthIn32BitWords, 0)); m_dataWriter.WriteUInt32(pingId); return S_OK; @@ -107,10 +123,13 @@ HRESULT MidiNetworkDataWriter::WriteCommandPingReply(uint32_t pingId) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + byte payloadLengthIn32BitWords{ 1 }; - RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_PingReply, payloadLengthIn32BitWords, 0)); + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_PingReply, payloadLengthIn32BitWords, 0)); m_dataWriter.WriteUInt32(pingId); return S_OK; @@ -128,6 +147,7 @@ MidiNetworkDataWriter::WriteCommandNAK( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); byte payloadLengthIn32BitWords{ 1 }; // 1 here to account for the original command header @@ -140,6 +160,8 @@ MidiNetworkDataWriter::WriteCommandNAK( payloadLengthIn32BitWords += CalculatePaddedStringSizeIn32BitWords(utf8, MIDI_MAX_NAK_MESSAGE_BYTE_COUNT); + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_NAK, payloadLengthIn32BitWords, reason, 0)); m_dataWriter.WriteUInt32(originalCommandHeader); @@ -159,6 +181,7 @@ MidiNetworkDataWriter::WriteCommandBye( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); byte payloadLengthIn32BitWords{ 0 }; // 1 here to account for the original command header @@ -171,6 +194,8 @@ MidiNetworkDataWriter::WriteCommandBye( payloadLengthIn32BitWords += CalculatePaddedStringSizeIn32BitWords(utf8, MIDI_MAX_BYE_MESSAGE_BYTE_COUNT); + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_Bye, payloadLengthIn32BitWords, reason, 0)); // write the text @@ -183,6 +208,9 @@ HRESULT MidiNetworkDataWriter::WriteCommandByeReply() { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + auto lock = m_dataWriterLock.lock(); RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_ByeReply, MIDI_COMMAND_PAYLOAD_LENGTH_NO_PAYLOAD, 0)); @@ -193,6 +221,9 @@ HRESULT MidiNetworkDataWriter::WriteCommandSessionReset() { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + auto lock = m_dataWriterLock.lock(); RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_SessionReset, MIDI_COMMAND_PAYLOAD_LENGTH_NO_PAYLOAD, 0)); @@ -203,12 +234,17 @@ HRESULT MidiNetworkDataWriter::WriteCommandSessionResetReply() { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + auto lock = m_dataWriterLock.lock(); RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_SessionResetReply, MIDI_COMMAND_PAYLOAD_LENGTH_NO_PAYLOAD, 0)); return S_OK; } +#define MIDI_NETWORK_RESERVED_UINT16 ((uint16_t)0) + _Use_decl_annotations_ HRESULT MidiNetworkDataWriter::WriteCommandRetransmitRequest( @@ -217,13 +253,16 @@ MidiNetworkDataWriter::WriteCommandRetransmitRequest( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); byte payloadLengthIn32BitWords{ 1 }; // 1 here to account for number of UMP commands + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_RetransmitRequest, payloadLengthIn32BitWords, sequenceNumber.Value())); m_dataWriter.WriteUInt16(numberOfUmpCommands); - m_dataWriter.WriteUInt16(0); + m_dataWriter.WriteUInt16(MIDI_NETWORK_RESERVED_UINT16); return S_OK; } @@ -237,13 +276,16 @@ MidiNetworkDataWriter::WriteCommandRetransmitError( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); byte payloadLengthIn32BitWords{ 1 }; // 1 here to account for sequence number + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_RetransmitError, payloadLengthIn32BitWords, errorReason, 0)); m_dataWriter.WriteUInt16(sequenceNumber.Value()); - m_dataWriter.WriteUInt16(0); + m_dataWriter.WriteUInt16(MIDI_NETWORK_RESERVED_UINT16); return S_OK; } @@ -258,6 +300,7 @@ MidiNetworkDataWriter::WriteCommandInvitation( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); byte payloadLengthIn32BitWords{ 0 }; @@ -270,6 +313,9 @@ MidiNetworkDataWriter::WriteCommandInvitation( payloadLengthIn32BitWords += umpEndpointNameLengthIn32BitWords; payloadLengthIn32BitWords += productInstanceIdLengthIn32BitWords; + + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandClientToHost_Invitation, payloadLengthIn32BitWords, umpEndpointNameLengthIn32BitWords, capabilities)); WritePaddedString(endpointNameUtf8, MIDI_MAX_UMP_ENDPOINT_NAME_BYTE_COUNT); @@ -286,10 +332,15 @@ MidiNetworkDataWriter::WriteCommandInvitationWithAuthentication( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); UNREFERENCED_PARAMETER(cryptoNonce); UNREFERENCED_PARAMETER(sharedSecret); + auto lock = m_dataWriterLock.lock(); + + // todo: we should NAK this for now + return E_NOTIMPL; } @@ -302,11 +353,16 @@ MidiNetworkDataWriter::WriteCommandInvitationWithUserAuthentication( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); UNREFERENCED_PARAMETER(cryptoNonce); UNREFERENCED_PARAMETER(userName); UNREFERENCED_PARAMETER(password); + auto lock = m_dataWriterLock.lock(); + + // TODO: we should NAK this for now + return E_NOTIMPL; } @@ -318,6 +374,10 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyAccepted( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); + + RETURN_HR_IF(E_INVALIDARG, hostUmpEndpointName.empty()); + RETURN_HR_IF(E_INVALIDARG, hostProductInstanceId.empty()); byte payloadLengthIn32BitWords{ 0 }; @@ -330,6 +390,8 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyAccepted( payloadLengthIn32BitWords += productInstanceIdLengthIn32BitWords; + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandHostToClient_InvitationReplyAccepted, payloadLengthIn32BitWords, umpEndpointNameLengthIn32BitWords, 0)); WritePaddedString(endpointNameUtf8, MIDI_MAX_UMP_ENDPOINT_NAME_BYTE_COUNT); @@ -346,10 +408,15 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyPending( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); UNREFERENCED_PARAMETER(hostUmpEndpointName); UNREFERENCED_PARAMETER(hostProductInstanceId); + auto lock = m_dataWriterLock.lock(); + + // TODO + return E_NOTIMPL; } @@ -364,12 +431,17 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyAuthenticationRequired( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); UNREFERENCED_PARAMETER(cryptoNonce); UNREFERENCED_PARAMETER(authenticationState); UNREFERENCED_PARAMETER(hostUmpEndpointName); UNREFERENCED_PARAMETER(hostProductInstanceId); + auto lock = m_dataWriterLock.lock(); + + // TODO + return E_NOTIMPL; } @@ -384,12 +456,17 @@ MidiNetworkDataWriter::WriteCommandInvitationReplyUserAuthenticationRequired( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); UNREFERENCED_PARAMETER(cryptoNonce); UNREFERENCED_PARAMETER(authenticationState); UNREFERENCED_PARAMETER(hostUmpEndpointName); UNREFERENCED_PARAMETER(hostProductInstanceId); + auto lock = m_dataWriterLock.lock(); + + // TODO + return E_NOTIMPL; } @@ -405,9 +482,13 @@ MidiNetworkDataWriter::WriteCommandUmpMessages( ) { RETURN_HR_IF_NULL(E_UNEXPECTED, m_dataWriter); + RETURN_HR_IF_NULL(E_UNEXPECTED, m_stream); RETURN_HR_IF(E_INVALIDARG, words.size() > MIDI_MAX_UMP_WORDS_PER_PACKET); + + auto lock = m_dataWriterLock.lock(); + RETURN_IF_FAILED(InternalWriteCommandHeader(MidiNetworkCommandCode::CommandCommon_UmpData, static_cast(words.size()), sequenceNumber.Value())); // we make the assumption that the calling code has already validated that the words @@ -424,8 +505,13 @@ MidiNetworkDataWriter::WriteCommandUmpMessages( HRESULT MidiNetworkDataWriter::Shutdown() { - m_dataWriter.Close(); - m_dataWriter = nullptr; + auto lock = m_dataWriterLock.lock(); + + if (m_dataWriter != nullptr) + { + m_dataWriter.Close(); + m_dataWriter = nullptr; + } m_stream = nullptr; diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h index 2e58efbc6..6d29bc3f3 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkDataWriter.h @@ -68,6 +68,9 @@ class MidiNetworkDataWriter inline void WritePaddedString(_In_ std::string s, _In_ size_t maxByteCount) { + // don't lock here. Calling method will lock + //auto lock = m_dataWriterLock.lock(); + size_t count{ 0 }; for (auto const& ch : s) @@ -121,5 +124,6 @@ class MidiNetworkDataWriter winrt::Windows::Storage::Streams::IOutputStream m_stream{ nullptr }; winrt::Windows::Storage::Streams::DataWriter m_dataWriter{ nullptr }; - + // data writer doesn't support concurrent writes and will throw exceptions if you try + wil::critical_section m_dataWriterLock; }; \ No newline at end of file diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp index aee557544..43e3f6ee7 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.cpp @@ -36,6 +36,8 @@ MidiNetworkHost::Initialize( m_started = false; + m_createUmpEndpointsOnly = !hostDefinition.CreateMidi1Ports; + m_hostEndpointName = hostDefinition.UmpEndpointName; m_hostProductInstanceId = hostDefinition.ProductInstanceId; @@ -46,6 +48,14 @@ MidiNetworkHost::Initialize( m_hostDefinition = hostDefinition; + DatagramSocket socket; + m_socket = std::move(socket); + + m_socket.Control().DontFragment(true); + //m_socket.Control().InboundBufferSizeInBytes(10000); + m_socket.Control().QualityOfService(SocketQualityOfService::LowLatency); + + if (m_hostDefinition.Advertise) { m_advertiser = std::make_shared(); @@ -65,6 +75,36 @@ MidiNetworkHost::Initialize( return S_OK; } +_Use_decl_annotations_ +HRESULT +MidiNetworkHost::CreateNetworkConnection(HostName const& remoteHostName, winrt::hstring const& remotePort) +{ + auto conn = std::make_shared(); + + if (conn) + { + RETURN_IF_FAILED(conn->Initialize( + MidiNetworkConnectionRole::ConnectionWindowsIsHost, + m_socket, + remoteHostName, + remotePort, + m_hostEndpointName, + m_hostProductInstanceId, + TransportState::Current().TransportSettings.RetransmitBufferMaxCommandPacketCount, + TransportState::Current().TransportSettings.ForwardErrorCorrectionMaxCommandPacketCount, + m_createUmpEndpointsOnly + )); + + TransportState::Current().AddNetworkConnection(remoteHostName, remotePort, conn); + } + else + { + // could not create the connection object. + } + + return S_OK; +} + HRESULT MidiNetworkHost::Start() { @@ -81,9 +121,9 @@ MidiNetworkHost::Start() // wire up to handle incoming events auto messageReceivedHandler = winrt::Windows::Foundation::TypedEventHandler(this, &MidiNetworkHost::OnMessageReceived); - m_messageReceivedRevoker = m_socket.MessageReceived(winrt::auto_revoke, messageReceivedHandler); + m_messageReceivedEventToken = m_socket.MessageReceived(messageReceivedHandler); - // this should have error checking + // TODO: this should have error checking m_socket.BindServiceNameAsync(winrt::to_hstring(m_hostDefinition.Port)).get(); auto boundPort = static_cast(std::stoi(winrt::to_string(m_socket.Information().LocalPort()))); @@ -101,6 +141,8 @@ MidiNetworkHost::Start() )); } + m_started = true; + TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), MIDI_TRACE_EVENT_INFO, @@ -110,21 +152,15 @@ MidiNetworkHost::Start() TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); - m_started = true; - return S_OK; } -// header sized plus a command packet header -#define MINIMUM_VALID_UDP_PACKET_SIZE (sizeof(uint32_t) * 2) - // "message" here means UDP packet message, not a MIDI message _Use_decl_annotations_ void MidiNetworkHost::OnMessageReceived( DatagramSocket const& sender, DatagramSocketMessageReceivedEventArgs const& args) { - // TEMP!! TraceLoggingWrite( MidiNetworkMidiTransportTelemetryProvider::Provider(), MIDI_TRACE_EVENT_INFO, @@ -134,11 +170,12 @@ void MidiNetworkHost::OnMessageReceived( TraceLoggingWideString(L"Enter", MIDI_TRACE_EVENT_MESSAGE_FIELD) ); + UNREFERENCED_PARAMETER(sender); auto reader = args.GetDataReader(); - if (reader.UnconsumedBufferLength() < MINIMUM_VALID_UDP_PACKET_SIZE) + if (reader != nullptr && reader.UnconsumedBufferLength() < MINIMUM_VALID_UDP_PACKET_SIZE) { // not a message we understand. Needs to be at least the size of the // MIDI header plus a command packet header. Really it needs to be larger, but @@ -167,7 +204,14 @@ void MidiNetworkHost::OnMessageReceived( return; } - auto conn = GetOrCreateConnection(args.RemoteAddress(), args.RemotePort()); + std::shared_ptr conn{ nullptr }; + + if (!TransportState::Current().NetworkConnectionExists(args.RemoteAddress(), args.RemotePort())) + { + LOG_IF_FAILED(CreateNetworkConnection(args.RemoteAddress(), args.RemotePort())); + } + + conn = TransportState::Current().GetNetworkConnection(args.RemoteAddress(), args.RemotePort()); if (conn) { @@ -185,6 +229,16 @@ void MidiNetworkHost::OnMessageReceived( ); } + + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); + } @@ -206,13 +260,39 @@ MidiNetworkHost::Shutdown() } // TODO: send "bye" to all sessions, and then unbind the socket + // but we need to restrict to this host, not every host/client + - // TODO: Stop packet processing thread + // TODO: Stop packet processing thread using the jthread stop token + m_keepProcessing = false; + if (m_socket) + { + m_socket.MessageReceived(m_messageReceivedEventToken); + m_socket.Close(); + m_socket = nullptr; + } + //while (m_connections.size() > 0) + //{ + // auto conn = m_connections.begin(); + // LOG_IF_FAILED(conn->second->Shutdown()); - m_socket.Close(); + // m_connections.erase(conn); + //} + TraceLoggingWrite( + MidiNetworkMidiTransportTelemetryProvider::Provider(), + MIDI_TRACE_EVENT_INFO, + TraceLoggingString(__FUNCTION__, MIDI_TRACE_EVENT_LOCATION_FIELD), + TraceLoggingLevel(WINEVENT_LEVEL_INFO), + TraceLoggingPointer(this, "this"), + TraceLoggingWideString(L"Exit", MIDI_TRACE_EVENT_MESSAGE_FIELD) + ); return S_OK; -} \ No newline at end of file +} + + + + diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h index 4c53281e0..397396b28 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkHost.h @@ -36,6 +36,8 @@ enum MidiNetworkHostProtocol struct MidiNetworkHostDefinition { + bool Created{ false }; + winrt::hstring EntryIdentifier; // internal bool UseAutomaticPortAllocation{ true }; @@ -48,6 +50,8 @@ struct MidiNetworkHostDefinition bool Enabled{ true }; bool Advertise{ true }; + bool CreateMidi1Ports{ MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT }; + // connection rules MidiNetworkHostConnectionPolicy ConnectionPolicy{ MidiNetworkHostConnectionPolicy::PolicyAllowAllConnections }; @@ -75,27 +79,22 @@ class MidiNetworkHost { public: HRESULT Initialize(_In_ MidiNetworkHostDefinition& hostDefinition); - HRESULT Start(); - HRESULT Shutdown(); bool HasStarted() { return m_started; } private: bool m_started{ false }; + bool m_createUmpEndpointsOnly{ true }; - //HRESULT EstablishNewSession(); std::wstring m_hostEndpointName{ }; std::wstring m_hostProductInstanceId{ }; std::atomic m_keepProcessing{ true }; - // todo: these probably aren't necessary. The only queues we need are for midi messages -// std::queue m_incomingOutOfBandCommands; -// std::queue m_outgoingOutOfBandCommands; + winrt::event_token m_messageReceivedEventToken; - winrt::impl::consume_Windows_Networking_Sockets_IDatagramSocket::MessageReceived_revoker m_messageReceivedRevoker; void OnMessageReceived( _In_ DatagramSocket const& sender, _In_ DatagramSocketMessageReceivedEventArgs const& args); @@ -107,68 +106,6 @@ class MidiNetworkHost DatagramSocket m_socket{ nullptr }; - // Map of MidiNetworkHostSessions and their related remote client addresses - // the keys for these two maps are the same values created with CreateSessionMapKey - std::map> m_connections{ }; - - inline std::string CreateConnectionMapKey(_In_ HostName const& hostName, _In_ winrt::hstring const& port) - { - return winrt::to_string(hostName.CanonicalName() + L":" + port); - } - - inline bool ConnectionExists(_In_ HostName const& hostName, _In_ winrt::hstring const& port) - { - auto key = CreateConnectionMapKey(hostName, port); - - return m_connections.find(key) != m_connections.end(); - } - - - inline void RemoveConnection(_In_ HostName const& hostName, _In_ winrt::hstring const& port) - { - if (ConnectionExists(hostName, port)) - { - auto entry = m_connections.find(CreateConnectionMapKey(hostName, port)); - - LOG_IF_FAILED(entry->second->Shutdown()); - - m_connections.erase(CreateConnectionMapKey(hostName, port)); - } - } - - inline std::shared_ptr GetOrCreateConnection(_In_ HostName const& hostName, _In_ winrt::hstring const& port) - { - auto key = CreateConnectionMapKey(hostName, port); - - if (!ConnectionExists(hostName, port)) - { - // need to create it and then add one - - auto conn = std::make_shared(); - - if (conn) - { - conn->Initialize( - MidiNetworkConnectionRole::ConnectionWindowsIsHost, - m_socket, - hostName, - port, - m_hostEndpointName, - m_hostProductInstanceId, - TransportState::Current().TransportSettings.RetransmitBufferMaxCommandPacketCount, - TransportState::Current().TransportSettings.ForwardErrorCorrectionMaxCommandPacketCount - ); - - m_connections.insert_or_assign(key, conn); - } - else - { - // could not create the connection object. - } - } - - return m_connections.find(key)->second; - } - + HRESULT CreateNetworkConnection(_In_ winrt::Windows::Networking::HostName const& remoteHostName, _In_ winrt::hstring const& remotePort); }; diff --git a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h index 40e14e27d..82162e05c 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/MidiNetworkMessages.h @@ -80,6 +80,7 @@ enum MidiNetworkCommandRetransmitErrorReason : byte enum MidiNetworkCommandInvitationCapabilities : byte { + Capabilities_None = 0x00, Capabilities_ClientSupportsInvitationWithAuthentication = 0x01, Capabilities_ClientSupportsInvitationWithUserAuthentication = 0x02, }; diff --git a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp index 0debd2f28..71ee3552e 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp +++ b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.cpp @@ -22,7 +22,6 @@ TransportState& TransportState::Current() } - HRESULT TransportState::ConstructEndpointManager() { @@ -31,7 +30,6 @@ TransportState::ConstructEndpointManager() return S_OK; } - HRESULT TransportState::ConstructConfigurationManager() { @@ -41,9 +39,12 @@ TransportState::ConstructConfigurationManager() } + + _Use_decl_annotations_ HRESULT -TransportState::AddHost(std::shared_ptr host) +TransportState::AddHost( + std::shared_ptr host) { RETURN_HR_IF_NULL(E_INVALIDARG, host); @@ -52,10 +53,35 @@ TransportState::AddHost(std::shared_ptr host) return S_OK; } +_Use_decl_annotations_ +HRESULT +TransportState::AddPendingHostDefinition( + std::shared_ptr hostDefinition) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, hostDefinition); + + m_pendingHostDefinitions.push_back(hostDefinition); + + return S_OK; +} _Use_decl_annotations_ HRESULT -TransportState::AddPendingClientDefinition(std::shared_ptr clientDefinition) +TransportState::AddClient( + std::shared_ptr client) +{ + RETURN_HR_IF_NULL(E_INVALIDARG, client); + + m_clients.push_back(client); + + return S_OK; +} + + +_Use_decl_annotations_ +HRESULT +TransportState::AddPendingClientDefinition( + std::shared_ptr clientDefinition) { RETURN_HR_IF_NULL(E_INVALIDARG, clientDefinition); @@ -65,33 +91,58 @@ TransportState::AddPendingClientDefinition(std::shared_ptr connection) +TransportState::AssociateMidiEndpointWithConnection( + _In_ std::wstring endpointDeviceInterfaceId, + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort) { auto cleanId = internal::NormalizeEndpointInterfaceIdWStringCopy(endpointDeviceInterfaceId); - m_sessionConnections.insert_or_assign(cleanId, connection); + // find the connection and then associate it here + + auto connection = GetNetworkConnection(remoteHostName, remotePort); + + if (connection != nullptr) + { + m_sessionConnections.insert_or_assign(cleanId, connection); + + return S_OK; + } + else + { + return E_FAIL; + } - return S_OK; } _Use_decl_annotations_ HRESULT -TransportState::RemoveSessionConnection(_In_ std::wstring endpointDeviceInterfaceId) +TransportState::DisassociateMidiEndpointFromConnection( + std::wstring endpointDeviceInterfaceId) { + RETURN_HR_IF(E_INVALIDARG, endpointDeviceInterfaceId.empty()); auto cleanId = internal::NormalizeEndpointInterfaceIdWStringCopy(endpointDeviceInterfaceId); + RETURN_HR_IF(E_INVALIDARG, cleanId.empty()); if (m_sessionConnections.find(cleanId) != m_sessionConnections.end()) { + //m_sessionConnections.find(cleanId)->second.reset(); m_sessionConnections.erase(cleanId); } + else + { + RETURN_IF_FAILED(E_NOTFOUND); + } return S_OK; } _Use_decl_annotations_ -std::shared_ptr TransportState::GetSessionConnection(_In_ std::wstring endpointDeviceInterfaceId) +std::shared_ptr +TransportState::GetSessionConnection(_In_ std::wstring endpointDeviceInterfaceId) { auto cleanId = internal::NormalizeEndpointInterfaceIdWStringCopy(endpointDeviceInterfaceId); @@ -103,3 +154,68 @@ std::shared_ptr TransportState::GetSessionConnection(_In_ return nullptr; } + + + + +_Use_decl_annotations_ +bool +TransportState::NetworkConnectionExists( + winrt::Windows::Networking::HostName const& remoteHostName, + winrt::hstring const& port) +{ + auto key = CreateNetworkConnectionMapKey(remoteHostName, port); + + return m_networkConnections.find(key) != m_networkConnections.end(); +} + + +_Use_decl_annotations_ +HRESULT +TransportState::RemoveNetworkConnection( + winrt::Windows::Networking::HostName const& remoteHostName, + winrt::hstring const& remotePort) +{ + if (NetworkConnectionExists(remoteHostName, remotePort)) + { + auto entry = m_networkConnections.find(CreateNetworkConnectionMapKey(remoteHostName, remotePort)); + + LOG_IF_FAILED(entry->second->Shutdown()); + + m_networkConnections.erase(CreateNetworkConnectionMapKey(remoteHostName, remotePort)); + } + + return S_OK; +} + +_Use_decl_annotations_ +std::shared_ptr +TransportState::GetNetworkConnection( + winrt::Windows::Networking::HostName const& remoteHostName, + winrt::hstring const& remotePort) +{ + auto key = CreateNetworkConnectionMapKey(remoteHostName, remotePort); + + if (NetworkConnectionExists(remoteHostName, remotePort)) + { + return m_networkConnections.find(key)->second; + } + + return nullptr; +} + + +_Use_decl_annotations_ +HRESULT +TransportState::AddNetworkConnection( + winrt::Windows::Networking::HostName const& remoteHostName, + winrt::hstring const& remotePort, + std::shared_ptr connection +) +{ + auto key = CreateNetworkConnectionMapKey(remoteHostName, remotePort); + + m_networkConnections.insert_or_assign(key, connection); + + return S_OK; +} diff --git a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h index d34db4020..b02a6d479 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/TransportState.h @@ -48,7 +48,7 @@ class TransportState //} - HRESULT Cleanup() + HRESULT Shutdown() { m_endpointManager.reset(); m_configurationManager.reset(); @@ -61,17 +61,55 @@ class TransportState HRESULT ConstructEndpointManager(); HRESULT ConstructConfigurationManager(); - HRESULT AddHost(_In_ std::shared_ptr); + HRESULT AddHost( + _In_ std::shared_ptr); std::vector> GetHosts() { return m_hosts; } - HRESULT AddPendingClientDefinition(_In_ std::shared_ptr); + HRESULT AddPendingHostDefinition( + _In_ std::shared_ptr); + std::vector> GetPendingHostDefinitions() { return m_pendingHostDefinitions; } + + HRESULT AddClient( + _In_ std::shared_ptr); + std::vector> GetClients() { return m_clients; } + + HRESULT AddPendingClientDefinition( + _In_ std::shared_ptr); std::vector> GetPendingClientDefinitions() { return m_pendingClientDefinitions; } - HRESULT AddSessionConnection(_In_ std::wstring endpointDeviceInterfaceId, _In_ std::shared_ptr connection); - HRESULT RemoveSessionConnection(_In_ std::wstring endpointDeviceInterfaceId); - std::shared_ptr GetSessionConnection(_In_ std::wstring endpointDeviceInterfaceId); + // these two sets of functions, and their related maps, work with the same + // connection objects, just in different states + + // these are for when the connection is associated with a UMP endpoint + HRESULT AssociateMidiEndpointWithConnection( + _In_ std::wstring endpointDeviceInterfaceId, + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort); + + HRESULT DisassociateMidiEndpointFromConnection( + _In_ std::wstring endpointDeviceInterfaceId); + + std::shared_ptr GetSessionConnection( + _In_ std::wstring endpointDeviceInterfaceId); + + // these are for when the connection is first created. They also live through when they become UMP endpoints + bool NetworkConnectionExists( + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort); + + std::shared_ptr GetNetworkConnection( + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort); + HRESULT AddNetworkConnection( + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort, + _In_ std::shared_ptr connection); + + HRESULT RemoveNetworkConnection( + _In_ winrt::Windows::Networking::HostName const& remoteHostName, + _In_ winrt::hstring const& remotePort); private: TransportState(); @@ -81,10 +119,21 @@ class TransportState wil::com_ptr m_endpointManager; wil::com_ptr m_configurationManager; - // key is the host identifier std::vector> m_hosts{ }; + std::vector> m_clients{ }; + + std::vector> m_pendingHostDefinitions{ }; std::vector> m_pendingClientDefinitions{ }; std::map> m_sessionConnections{ }; + // Map of MidiNetworkConnections and their related remote client addresses + // the keys for these two maps are the values created with CreateConnectionMapKey + std::map> m_networkConnections{ }; + + inline std::string CreateNetworkConnectionMapKey(_In_ winrt::Windows::Networking::HostName const& remoteHostName, _In_ winrt::hstring const& remotePort) + { + return winrt::to_string(remoteHostName.CanonicalName() + L":" + remotePort); + } + }; \ No newline at end of file diff --git a/src/api/Transport/UdpNetworkMidi2Transport/pch.h b/src/api/Transport/UdpNetworkMidi2Transport/pch.h index 293a2aa53..781497cc4 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/pch.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/pch.h @@ -7,7 +7,7 @@ #endif #include - +#include // mostly for E_NOTFOUND #include #include #include @@ -110,6 +110,7 @@ class MidiNetworkHost; class MidiNetworkClient; class MidiNetworkConnection; struct MidiNetworkClientDefinition; +struct MidiNetworkHostDefinition; //#include "MidiNetworkEndpointDefinition.h" diff --git a/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h b/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h index 581c2b5bb..03a90a4f8 100644 --- a/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h +++ b/src/api/Transport/UdpNetworkMidi2Transport/transport_defs.h @@ -53,9 +53,14 @@ #define MIDI_NETWORK_OUTBOUND_PING_INTERVAL_UPPER_BOUND 120000 #define MIDI_NETWORK_OUTBOUND_PING_INTERVAL_LOWER_BOUND 250 +#define MIDI_NETWORK_STARTING_OUTBOUND_UMP_QUEUE_CAPACITY 50 + +#define MIDI_NETWORK_MIDI_CREATE_MIDI1_PORTS_DEFAULT false + +// header sized plus a command packet header +#define MINIMUM_VALID_UDP_PACKET_SIZE (sizeof(uint32_t) * 2) -#define MIDI_NETWORK_STARTING_OUTBOUND_UMP_QUEUE_CAPACITY 50 // JSON keys. Can move to json_defs when in-box @@ -78,6 +83,8 @@ #define MIDI_CONFIG_JSON_NETWORK_MIDI_MDNS_ADVERTISE_KEY L"advertise" // boolean #define MIDI_CONFIG_JSON_NETWORK_MIDI_ENABLED_KEY L"enabled" // boolean +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CREATE_MIDI1_PORTS_KEY L"createMidi1Ports" // boolean - set to true to enable creating WinMM/WinRT 1.0 ports + #define MIDI_CONFIG_JSON_NETWORK_MIDI_SERVICE_INSTANCE_NAME_KEY L"serviceInstanceName" // just the first part (before the . ) of the host instance name. Defaults to machine name @@ -104,13 +111,14 @@ #define MIDI_CONFIG_JSON_NETWORK_MIDI_PRODUCT_INSTANCE_ID_PROPERTY L"productInstanceId" -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_ID L"id" // Windows ID like: DnsSd#kb7C5D0A_1._midi2._udp.local#0 -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_SERVICE_INSTANCE L"serviceInstance" // Like kb7C5D0A_1 or bomeboxdin-8q6d2z-1 -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_IP L"ipAddress" // Like 192.168.1.253 (port is also required) -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_HOST_NAME L"hostName" // Like kissbox or BomeBox.local (port is also required) -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_PORT L"port" // Like 5004 (ip address or host name is also required) -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_NAME L"umpEndpointName" // Like UMP2TR @253 Port 1 or BomeBox -#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_PID L"umpProductInstanceId" // Like kb7C5D0A_1 or CC851C0080257A96 +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_OBJECT_KEY L"match" // object which contains match criteria +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_ID_KEY L"id" // Windows ID like: DnsSd#kb7C5D0A_1._midi2._udp.local#0 +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_SERVICE_INSTANCE_KEY L"serviceInstance" // Like kb7C5D0A_1 or bomeboxdin-8q6d2z-1 +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_IPV4_KEY L"ipv4Address" // Like 192.168.1.253 (port is also required) +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_HOST_NAME_KEY L"hostName" // Like kissbox or BomeBox.local (port is also required) +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_PORT_KEY L"port" // Like 5004 (ip address or host name is also required) +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_NAME_KEY L"umpEndpointName" // Like UMP2TR @253 Port 1 or BomeBox +#define MIDI_CONFIG_JSON_NETWORK_MIDI_CLIENT_MATCH_UMP_ENDPOINT_PID_KEY L"umpProductInstanceId" // Like kb7C5D0A_1 or CC851C0080257A96 enum MidiNetworkConnectionRole diff --git a/src/app-sdk/mididiag/color.hpp b/src/app-sdk/mididiag/color.hpp new file mode 100644 index 000000000..cb080b6e5 --- /dev/null +++ b/src/app-sdk/mididiag/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/mididiag/main.cpp b/src/app-sdk/mididiag/main.cpp index 6e05ac941..0b2b384f5 100644 --- a/src/app-sdk/mididiag/main.cpp +++ b/src/app-sdk/mididiag/main.cpp @@ -11,7 +11,13 @@ #include "pch.h" -const std::wstring fieldSeparator = MIDIDIAG_FIELD_SEPARATOR; +#include "color.hpp" + +void OutputFieldSeparator() +{ + std::cout + << dye::grey(MIDIDIAG_FIELD_SEPARATOR); +} void OutputBlankLine() { @@ -19,77 +25,77 @@ void OutputBlankLine() << std::endl; } -void OutputSectionHeader(_In_ std::wstring const& headerText) +void OutputSectionHeader(_In_ std::string const& headerText) { - const std::wstring sectionHeaderSeparator = std::wstring(MIDIDIAG_SEPARATOR_REPEATING_CHAR_COUNT_PER_LINE, MIDIDIAG_SECTION_HEADER_SEPARATOR_CHAR); + const auto sectionHeaderSeparator = std::string(MIDIDIAG_SEPARATOR_REPEATING_CHAR_COUNT_PER_LINE, MIDIDIAG_SECTION_HEADER_SEPARATOR_CHAR); - std::wcout + std::cout << std::endl - << sectionHeaderSeparator + << dye::grey(sectionHeaderSeparator) << std::endl - << headerText + << dye::aqua(headerText) << std::endl - << sectionHeaderSeparator + << dye::grey(sectionHeaderSeparator) << std::endl << std::endl; } void OutputItemSeparator() { - const std::wstring itemSeparator = std::wstring(MIDIDIAG_SEPARATOR_REPEATING_CHAR_COUNT_PER_LINE, MIDIDIAG_ITEM_SEPARATOR_CHAR); + const auto itemSeparator = std::string(MIDIDIAG_SEPARATOR_REPEATING_CHAR_COUNT_PER_LINE, MIDIDIAG_ITEM_SEPARATOR_CHAR); - std::wcout - << itemSeparator + std::cout + << dye::grey(itemSeparator) << std::endl; } -void OutputHeader(_In_ std::wstring const& headerText) +void OutputHeader(_In_ std::string const& headerText) { - std::wcout - << headerText + std::cout + << dye::aqua(headerText) << std::endl; } -void OutputFieldLabel(_In_ std::wstring const& fieldName) +void OutputFieldLabel(_In_ std::string const& fieldName) { - std::wcout + std::cout << std::setw(MIDIDIAG_MAX_FIELD_LABEL_WIDTH) << std::left - << fieldName; + << dye::yellow(fieldName); } -void OutputStringField(_In_ std::wstring const& fieldName, _In_ winrt::hstring const& value) +void OutputStringField(_In_ std::string const& fieldName, _In_ winrt::hstring const& value) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << value.c_str() << std::endl; } -void OutputStringField(_In_ std::wstring const& fieldName, _In_ std::wstring const& value) +void OutputStringField(_In_ std::string const& fieldName, _In_ std::wstring const& value) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << value << std::endl; } -void OutputBooleanField(_In_ std::wstring const& fieldName, _In_ bool const& value) +void OutputBooleanField(_In_ std::string const& fieldName, _In_ bool const& value) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << std::boolalpha << value << std::endl; } -void OutputGuidField(_In_ std::wstring const& fieldName, _In_ winrt::guid const& value) +void OutputGuidField(_In_ std::string const& fieldName, _In_ winrt::guid const& value) { OutputStringField(fieldName, internal::GuidToString(value)); } @@ -108,45 +114,45 @@ void OutputCurrentTime() -void OutputTimestampField(_In_ std::wstring const& fieldName, _In_ uint64_t const value) +void OutputTimestampField(_In_ std::string const& fieldName, _In_ uint64_t const value) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << value << std::endl; } -void OutputNumericField(_In_ std::wstring const& fieldName, _In_ uint32_t const value) +void OutputNumericField(_In_ std::string const& fieldName, _In_ uint32_t const value) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << std::dec << value << std::endl; } -void OutputDoubleField(_In_ std::wstring const& fieldName, _In_ double const value, _In_ uint32_t precision) +void OutputDoubleField(_In_ std::string const& fieldName, _In_ double const value, _In_ uint32_t precision) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << std::dec << std::setprecision(precision) << value << std::endl; } -void OutputDecimalMillisecondsField(_In_ std::wstring const& fieldName, _In_ double const value, _In_ uint32_t precision) +void OutputDecimalMillisecondsField(_In_ std::string const& fieldName, _In_ double const value, _In_ uint32_t precision) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << std::dec << std::setprecision(precision) << std::fixed @@ -157,12 +163,12 @@ void OutputDecimalMillisecondsField(_In_ std::wstring const& fieldName, _In_ dou -void OutputHexNumericField(_In_ std::wstring const& fieldName, _In_ uint32_t const value) +void OutputHexNumericField(_In_ std::string const& fieldName, _In_ uint32_t const value) { OutputFieldLabel(fieldName); + OutputFieldSeparator(); std::wcout - << fieldSeparator << L"0x" << std::hex << value @@ -170,7 +176,7 @@ void OutputHexNumericField(_In_ std::wstring const& fieldName, _In_ uint32_t con } -bool OutputFileVersion(_In_ std::wstring const& fieldName, _In_ std::wstring const& fileName) +bool OutputFileVersion(_In_ std::string const& fieldName, _In_ std::wstring const& fileName) { DWORD handle{ 0 }; @@ -191,10 +197,10 @@ bool OutputFileVersion(_In_ std::wstring const& fieldName, _In_ std::wstring con VS_FIXEDFILEINFO* verInfo = (VS_FIXEDFILEINFO*)(verInfoBuffer); OutputFieldLabel(fieldName); + OutputFieldSeparator(); // major.minor.build.revision format std::wcout - << fieldSeparator << std::dec << static_cast((verInfo->dwFileVersionMS >> 16) & 0xffff) << L"." @@ -215,31 +221,27 @@ bool OutputFileVersion(_In_ std::wstring const& fieldName, _In_ std::wstring con void OutputError(_In_ winrt::hresult_error const& error) { - const std::wstring errorLabel = L"ERROR"; - - OutputFieldLabel(errorLabel); + OutputFieldLabel(MIDIDIAG_FIELD_LABEL_ERROR); + OutputFieldSeparator(); - std::wcout - << fieldSeparator + std::cout << std::hex << error.code() - << L" : "; + << " : "; std::cout - << winrt::to_string(error.message()) + << dye::light_red(winrt::to_string(error.message())) << std::endl; } -void OutputError(_In_ std::wstring const& errorMessage) +void OutputError(_In_ std::string const& errorMessage) { - const std::wstring errorLabel = L"ERROR"; + OutputFieldLabel(MIDIDIAG_FIELD_LABEL_ERROR); + OutputFieldSeparator(); - OutputFieldLabel(errorLabel); - - std::wcout - << fieldSeparator - << errorMessage + std::cout + << dye::light_red(errorMessage) << std::endl; } @@ -253,7 +255,7 @@ void OutputError(_In_ std::wstring const& errorMessage) -void OutputRegStringValue(std::wstring label, HKEY const key, std::wstring value) +void OutputRegStringValue(std::string label, HKEY const key, std::wstring value) { auto keyValue = wil::reg::try_get_value_string(key, value.c_str()); if (keyValue.has_value()) @@ -267,7 +269,7 @@ void OutputRegStringValue(std::wstring label, HKEY const key, std::wstring value } // dword value > 0 == true -void OutputRegDWordBooleanValue(std::wstring label, HKEY const key, std::wstring value) +void OutputRegDWordBooleanValue(std::string label, HKEY const key, std::wstring value) { auto keyValue = wil::reg::try_get_value_dword(key, value.c_str()); if (keyValue.has_value()) @@ -282,7 +284,7 @@ void OutputRegDWordBooleanValue(std::wstring label, HKEY const key, std::wstring -void OutputCOMComponentInfo(std::wstring const dllNameFieldName, std::wstring const classid) +void OutputCOMComponentInfo(std::string const dllNameFieldName, std::wstring const classid) { // InprocServer32 Value @@ -301,13 +303,72 @@ void OutputCOMComponentInfo(std::wstring const dllNameFieldName, std::wstring co } else { - OutputError(L"Could not find in-proc server value under HKEY_CLASSES_ROOT, " + inprocServerKeyLocation); + OutputStringField(dllNameFieldName, std::wstring{ L"ERROR" }); + OutputError("Could not find COM component in-proc server value under HKEY_CLASSES_ROOT"); + } +} + + +bool DoSectionDrivers32RegistryEntries(_In_ bool const verbose) +{ + UNREFERENCED_PARAMETER(verbose); + + OutputSectionHeader(MIDIDIAG_SECTION_LABEL_ENUM_REGISTRY_DRIVERS32); + + // list all MIDI values under Drivers32 + + try + { + std::wstring drivers32KeyLocation = std::wstring{ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32" }; + wil::unique_hkey drivers32Key{ }; + + if (SUCCEEDED(wil::reg::open_unique_key_nothrow(HKEY_LOCAL_MACHINE, drivers32KeyLocation.c_str(), drivers32Key, wil::reg::key_access::read))) + { + for (const auto& valueData : wil::make_range(wil::reg::value_iterator{ drivers32Key.get() }, wil::reg::value_iterator{})) + { + //valueData.name; + //valueData.type; + + if (valueData.name.starts_with(L"midi") && valueData.name != L"midimapper") + { + auto val = wil::reg::try_get_value_string(drivers32Key.get(), valueData.name.c_str()); + + if (val.has_value()) + { + OutputStringField(MIDIDIAG_FIELD_LABEL_REGISTRY_DRIVERS32_ENTRY, valueData.name + L" = " + val.value()); + } + } + else if (valueData.name == L"MidisrvTransferComplete") + { + auto val = wil::reg::try_get_value_dword(drivers32Key.get(), valueData.name.c_str()); + + if (val.has_value()) + { + OutputStringField(MIDIDIAG_FIELD_LABEL_REGISTRY_DRIVERS32_ENTRY, valueData.name + L" = " + std::to_wstring(val.value())); + } + } + + } + } + else + { + OutputStringField("ERROR", drivers32KeyLocation); + OutputError("Could not open Drivers32 Key"); + } } + catch (...) + { + OutputError("Exception enumerating registry keys and values."); + + return false; } + return true; +} + -bool DoSectionRegistryEntries(_In_ bool const verbose) +bool DoSectionMidi2RegistryEntries(_In_ bool const verbose) { UNREFERENCED_PARAMETER(verbose); @@ -317,14 +378,20 @@ bool DoSectionRegistryEntries(_In_ bool const verbose) { // check to see if the root is there - const auto rootKey = wil::reg::open_unique_key(HKEY_LOCAL_MACHINE, MIDI_ROOT_REG_KEY, wil::reg::key_access::read); - - // list all values in the root + wil::unique_hkey rootKey{}; + if (SUCCEEDED(wil::reg::open_unique_key_nothrow(HKEY_LOCAL_MACHINE, MIDI_ROOT_REG_KEY, rootKey, wil::reg::key_access::read))) + { + // list all values in the root - OutputRegStringValue(MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_CURRENT_CONFIG, rootKey.get(), MIDI_CONFIG_FILE_REG_VALUE); - OutputRegDWordBooleanValue(MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_DISCOVERY_ENABLED, rootKey.get(), MIDI_DISCOVERY_ENABLED_REG_VALUE); - OutputRegDWordBooleanValue(MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_USE_MMCSS, rootKey.get(), MIDI_USE_MMCSS_REG_VALUE); - OutputItemSeparator(); + OutputRegStringValue(MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_CURRENT_CONFIG, rootKey.get(), MIDI_CONFIG_FILE_REG_VALUE); + OutputRegDWordBooleanValue(MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_DISCOVERY_ENABLED, rootKey.get(), MIDI_DISCOVERY_ENABLED_REG_VALUE); + OutputRegDWordBooleanValue(MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_USE_MMCSS, rootKey.get(), MIDI_USE_MMCSS_REG_VALUE); + OutputItemSeparator(); + } + else + { + OutputError("Unable to find or open root Windows MIDI Services registry key. Is Windows MIDI Services installed?"); + } // List midisrv info @@ -342,18 +409,18 @@ bool DoSectionRegistryEntries(_In_ bool const verbose) } else { - OutputError(L"Found midisrv service entry in registry, but it does not have an ImagePath"); + OutputError("Found midisrv service entry in registry, but it does not have an ImagePath"); } } else { - OutputError(L"Found midisrv service entry in registry, but it does not have an ImagePath"); + OutputError("midisrv registry key not found or is not valid, or user does not have permissions to read it."); } } else { - OutputError(L"Unable to find Midisrv entry in registry services key"); + OutputError("Unable to find or open Midisrv entry in registry services key. Is Windows MIDI Services installed?"); } OutputItemSeparator(); @@ -371,12 +438,12 @@ bool DoSectionRegistryEntries(_In_ bool const verbose) } else { - OutputError(L"No MIDI Desktop app SDK runtime registry 'Installed' value."); + OutputError("No MIDI Desktop app SDK runtime registry 'Installed' value. The Windows MIDI Services SDK does not appear to be installed."); } } else { - OutputError(L"No MIDI Desktop app SDK runtime registry root key."); + OutputError("No MIDI Desktop app SDK runtime registry root key. The Windows MIDI Services SDK does not appear to be installed."); } OutputItemSeparator(); @@ -430,7 +497,7 @@ bool DoSectionRegistryEntries(_In_ bool const verbose) } else { - OutputError(L"No clsid found in MIDI transport entry"); + OutputError("No clsid found in MIDI transport entry"); } } @@ -440,50 +507,13 @@ bool DoSectionRegistryEntries(_In_ bool const verbose) } else { - OutputError(L"Unable to enumerate transport plugins from registry."); + OutputError("Unable to enumerate transport plugins from registry."); } - // list all MIDI values under Drivers32 - - std::wstring drivers32KeyLocation = std::wstring{ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32" }; - wil::unique_hkey drivers32Key{ }; - - if (SUCCEEDED(wil::reg::open_unique_key_nothrow(HKEY_LOCAL_MACHINE, drivers32KeyLocation.c_str(), drivers32Key, wil::reg::key_access::read))) - { - for (const auto& valueData : wil::make_range(wil::reg::value_iterator{ drivers32Key.get() }, wil::reg::value_iterator{})) - { - //valueData.name; - //valueData.type; - - if (valueData.name.starts_with(L"midi") && valueData.name != L"midimapper") - { - auto val = wil::reg::try_get_value_string(drivers32Key.get(), valueData.name.c_str()); - - if (val.has_value()) - { - OutputStringField(MIDIDIAG_FIELD_LABEL_REGISTRY_DRIVERS32_ENTRY, valueData.name + L" = " + val.value()); - } - } - else if (valueData.name == L"MidisrvTransferComplete") - { - auto val = wil::reg::try_get_value_dword(drivers32Key.get(), valueData.name.c_str()); - - if (val.has_value()) - { - OutputStringField(MIDIDIAG_FIELD_LABEL_REGISTRY_DRIVERS32_ENTRY, valueData.name + L" = " + std::to_wstring(val.value())); - } - } - - } - } - else - { - OutputError(L"Could not open Drivers32 Key " + drivers32KeyLocation); - } } catch (...) { - OutputError(L"Exception enumerating registry keys and values."); + OutputError("Exception enumerating registry keys and values."); return false; } @@ -517,13 +547,13 @@ bool DoSectionTransports(_In_ bool const verbose) } else { - OutputError(L"Enumerating transports returned no matches. This is not expected and indicates an installation problem or that the service is not running."); + OutputError("Enumerating transports returned no matches. This is not expected and indicates an installation problem or that the service is not running."); return false; } } catch (...) { - OutputError(L"Exception enumerating transports."); + OutputError("Exception enumerating transports."); return false; } @@ -552,7 +582,7 @@ bool DoSectionMidi2ApiEndpoints(_In_ bool const verbose) } catch (...) { - OutputError(L"Unable to access Windows MIDI Services and enumerate devices."); + OutputError("Unable to access Windows MIDI Services and enumerate devices."); return false; } @@ -590,7 +620,7 @@ bool DoSectionMidi2ApiEndpoints(_In_ bool const verbose) } else { - OutputError(L"Unable to find endpoint parent"); + OutputError("Unable to find endpoint parent"); } if (i != devices.Size() - 1) @@ -601,7 +631,7 @@ bool DoSectionMidi2ApiEndpoints(_In_ bool const verbose) } else { - OutputError(L"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 problem or that the service is not running."); return false; } @@ -635,7 +665,7 @@ bool DoSectionWinRTMidi1ApiEndpoints(_In_ bool const verbose) } catch (...) { - OutputError(L"Enumerating WinRT MIDI 1.0 devices encountered an exception."); + OutputError("Enumerating WinRT MIDI 1.0 devices encountered an exception."); return false; } @@ -662,7 +692,7 @@ bool DoSectionWinRTMidi1ApiEndpoints(_In_ bool const verbose) } catch (...) { - OutputError(L"Enumerating WinRT MIDI 1.0 devices encountered an exception."); + OutputError("Enumerating WinRT MIDI 1.0 devices encountered an exception."); return false; } @@ -714,7 +744,7 @@ bool DoSectionWinMMMidi1ApiEndpoints(_In_ bool const verbose) } catch (...) { - OutputError(L"Enumerating WinMM MIDI 1.0 input devices encountered an exception."); + OutputError("Enumerating WinMM MIDI 1.0 input devices encountered an exception."); return false; } @@ -759,7 +789,7 @@ bool DoSectionWinMMMidi1ApiEndpoints(_In_ bool const verbose) } catch (...) { - OutputError(L"Enumerating WinMM MIDI 1.0 output devices encountered an exception."); + OutputError("Enumerating WinMM MIDI 1.0 output devices encountered an exception."); return false; } @@ -799,7 +829,7 @@ bool DoSectionPingTest(_In_ bool const verbose, _In_ uint8_t const pingCount) } else { - OutputError(L"Ping test failed"); + OutputError("Ping test failed"); OutputStringField(MIDIDIAG_FIELD_LABEL_PING_FAILURE_REASON, pingResult.FailureReason()); return false; @@ -807,13 +837,13 @@ bool DoSectionPingTest(_In_ bool const verbose, _In_ uint8_t const pingCount) } else { - OutputError(L"Ping test failed. Return was null."); + OutputError("Ping test failed. Return was null."); return false; } } catch (...) { - OutputError(L"Ping test failed with exception"); + OutputError("Ping test failed with exception"); return false; } @@ -837,8 +867,8 @@ bool DoSectionClock(_In_ bool const verbose) } catch (winrt::hresult_error ex) { - OutputError(ex.message().c_str()); - OutputError(L"winrt::hresult_error getting clock information. Likely an issue with SDK activation."); + OutputError(winrt::to_string(ex.message())); + OutputError("winrt::hresult_error getting clock information. Likely an issue with SDK activation."); return false; } @@ -863,7 +893,7 @@ bool DoSectionSdkStatus(_In_ bool const verbose) if (!initialized) { - OutputError(L"Failed to initialize Windows MIDI Services SDK runtime."); + OutputError("Failed to initialize Windows MIDI Services SDK runtime. Is it installed?"); } return initialized; @@ -883,7 +913,7 @@ bool DoSectionServiceStatus(_In_ bool const verbose) if (!available) { - OutputError(L"Failed to start MIDI Service"); + OutputError("Failed to start MIDI Service"); } return available; @@ -957,18 +987,24 @@ std::wstring GetProcessorArchitectureString(WORD const arch) } -#define ENV_PROCESSOR_ARCHITECTURE L"PROCESSOR_ARCHITECTURE" -#define ENV_PROCESSOR_IDENTIFIER L"PROCESSOR_IDENTIFIER" -#define ENV_PROCESSOR_LEVEL L"PROCESSOR_LEVEL" -#define ENV_PROCESSOR_REVISION L"PROCESSOR_REVISION" +#define ENV_PROCESSOR_ARCHITECTURE "PROCESSOR_ARCHITECTURE" +#define ENV_PROCESSOR_IDENTIFIER "PROCESSOR_IDENTIFIER" +#define ENV_PROCESSOR_LEVEL "PROCESSOR_LEVEL" +#define ENV_PROCESSOR_REVISION "PROCESSOR_REVISION" + +#define ENV_PROCESSOR_ARCHITECTURE_WIDE L"PROCESSOR_ARCHITECTURE" +#define ENV_PROCESSOR_IDENTIFIER_WIDE L"PROCESSOR_IDENTIFIER" +#define ENV_PROCESSOR_LEVEL_WIDE L"PROCESSOR_LEVEL" +#define ENV_PROCESSOR_REVISION_WIDE L"PROCESSOR_REVISION" + void OutputProcessorEnvVariables() { - OutputStringField(ENV_PROCESSOR_ARCHITECTURE, std::wstring{ _wgetenv(ENV_PROCESSOR_ARCHITECTURE)}); - OutputStringField(ENV_PROCESSOR_IDENTIFIER, std::wstring{ _wgetenv(ENV_PROCESSOR_IDENTIFIER) }); - OutputStringField(ENV_PROCESSOR_LEVEL, std::wstring{ _wgetenv(ENV_PROCESSOR_LEVEL) }); - OutputStringField(ENV_PROCESSOR_REVISION, std::wstring{ _wgetenv(ENV_PROCESSOR_REVISION) }); + OutputStringField(ENV_PROCESSOR_ARCHITECTURE, std::wstring{ _wgetenv(ENV_PROCESSOR_ARCHITECTURE_WIDE)}); + OutputStringField(ENV_PROCESSOR_IDENTIFIER, std::wstring{ _wgetenv(ENV_PROCESSOR_IDENTIFIER_WIDE) }); + OutputStringField(ENV_PROCESSOR_LEVEL, std::wstring{ _wgetenv(ENV_PROCESSOR_LEVEL_WIDE) }); + OutputStringField(ENV_PROCESSOR_REVISION, std::wstring{ _wgetenv(ENV_PROCESSOR_REVISION_WIDE) }); } @@ -1017,12 +1053,12 @@ void OutputProcessAndNativeMachine() } else { - OutputError(L"Unidentified process and/or machine architecture"); + OutputError("Unidentified process and/or machine architecture"); } } else { - OutputError(L"Unable to query process and machine architecture."); + OutputError("Unable to query process and machine architecture."); } } @@ -1097,28 +1133,39 @@ int __cdecl main() bool pingTest = true; bool midiClock = true; + OutputSectionHeader(MIDIDIAG_SECTION_LABEL_HEADER); + + OutputHeader("This application Copyright (c) 2024- Microsoft Corporation"); + OutputHeader("Information, license, and source available at https://aka.ms/midi"); + OutputBlankLine(); OutputHeader(MIDIDIAG_PRODUCT_NAME); - OutputHeader(L"This application Copyright (c) 2024- Microsoft Corporation"); - OutputHeader(L"Information, license, and source available at https://aka.ms/midi"); + OutputBlankLine(); OutputStringField(MIDIDIAG_HEADER_FIELD_LABEL_VERSION_BUILD_SOURCE, std::wstring{ WINDOWS_MIDI_SERVICES_BUILD_SOURCE }); OutputStringField(MIDIDIAG_HEADER_FIELD_LABEL_VERSION_NAME, std::wstring{ WINDOWS_MIDI_SERVICES_BUILD_VERSION_NAME }); OutputStringField(MIDIDIAG_HEADER_FIELD_LABEL_VERSION_FULL, std::wstring{ WINDOWS_MIDI_SERVICES_BUILD_VERSION_FULL }); - OutputSectionHeader(MIDIDIAG_SECTION_LABEL_HEADER); - // this fails on Windows 10 // OutputCurrentTime(); try { // do anything which doesn't rely on the service or SDK - DoSectionSystemInfo(verbose); - DoSectionRegistryEntries(verbose); + + // try to get all the classic MIDI 1.0 info up-front, so there's + // some level of info available even if Windows MIDI Services is not installed + + DoSectionDrivers32RegistryEntries(verbose); // don't bail if fails + + DoSectionWinRTMidi1ApiEndpoints(verbose); // we don't bail if this fails + + DoSectionWinMMMidi1ApiEndpoints(verbose); // we don't bail if this fails + + DoSectionMidi2RegistryEntries(verbose); // don't bail if fails // check the service and SDK installs. Do SDK first because // the service status uses the SDK initializer to try to start - // the windows service. + // the windows service and verify the presence of the SDK runtime if (!DoSectionSdkStatus(verbose)) goto abort_run; // we've made it past initializing the SDK, so make sure we shut it down @@ -1130,7 +1177,6 @@ int __cdecl main() }); - if (!DoSectionServiceStatus(verbose)) goto abort_run; // only show midi clock info if the sdk init has worked @@ -1144,14 +1190,7 @@ int __cdecl main() if (transportsWorked) { if (!DoSectionMidi2ApiEndpoints(verbose)) goto abort_run; - } - - DoSectionWinRTMidi1ApiEndpoints(verbose); // we don't bail if this fails - - DoSectionWinMMMidi1ApiEndpoints(verbose); // we don't bail if this fails - if (transportsWorked) - { // ping the service if (pingTest) { @@ -1160,23 +1199,23 @@ int __cdecl main() if (!DoSectionPingTest(verbose, pingCount)) goto abort_run; } } - } catch (...) { - OutputError(L"Exception attempting to gather MIDI information."); + OutputError("Exception attempting to gather MIDI information."); + OutputSectionHeader(MIDIDIAG_SECTION_LABEL_END_OF_FILE); RETURN_FAIL; } - OutputSectionHeader(L"Successful Run"); + OutputSectionHeader("*** Successful Run ***"); OutputSectionHeader(MIDIDIAG_SECTION_LABEL_END_OF_FILE); RETURN_SUCCESS; abort_run: - OutputSectionHeader(L"Aborted Run"); - OutputError(L"Aborting MIDI Diag run due to failure(s)."); + OutputSectionHeader("Aborted Run"); + OutputError("Aborting MIDI Diag run due to failure(s)."); OutputSectionHeader(MIDIDIAG_SECTION_LABEL_END_OF_FILE); RETURN_FAIL; diff --git a/src/app-sdk/mididiag/mididiag.vcxproj b/src/app-sdk/mididiag/mididiag.vcxproj index 328c314f6..ee67036a8 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true @@ -167,6 +167,7 @@ + diff --git a/src/app-sdk/mididiag/mididiag.vcxproj.filters b/src/app-sdk/mididiag/mididiag.vcxproj.filters index 0f31e7641..b93e0b08e 100644 --- a/src/app-sdk/mididiag/mididiag.vcxproj.filters +++ b/src/app-sdk/mididiag/mididiag.vcxproj.filters @@ -21,6 +21,9 @@ Header Files + + Header Files + diff --git a/src/app-sdk/mididiag/mididiag_field_defs.h b/src/app-sdk/mididiag/mididiag_field_defs.h index e2ab9e289..95c608b94 100644 --- a/src/app-sdk/mididiag/mididiag_field_defs.h +++ b/src/app-sdk/mididiag/mididiag_field_defs.h @@ -11,124 +11,125 @@ // All field names here in case you want to parse the file. Just include or // copy these definitions into your code -#define MIDIDIAG_PRODUCT_NAME L"Microsoft Windows MIDI Services - Diagnostics Report" +#define MIDIDIAG_PRODUCT_NAME "Microsoft Windows MIDI Services - Diagnostics Report" #define MIDIDIAG_MAX_FIELD_LABEL_WIDTH 25 -#define MIDIDIAG_FIELD_LABEL_FILE_VERSION L"file_version" +#define MIDIDIAG_FIELD_LABEL_FILE_VERSION "file_version" -#define MIDIDIAG_HEADER_FIELD_LABEL_VERSION_BUILD_SOURCE L"ver_build_source" -#define MIDIDIAG_HEADER_FIELD_LABEL_VERSION_NAME L"ver_build_name" -#define MIDIDIAG_HEADER_FIELD_LABEL_VERSION_FULL L"ver_build_full" +#define MIDIDIAG_HEADER_FIELD_LABEL_VERSION_BUILD_SOURCE "ver_build_source" +#define MIDIDIAG_HEADER_FIELD_LABEL_VERSION_NAME "ver_build_name" +#define MIDIDIAG_HEADER_FIELD_LABEL_VERSION_FULL "ver_build_full" -#define MIDIDIAG_FIELD_SEPARATOR L" : " +#define MIDIDIAG_FIELD_SEPARATOR " : " #define MIDIDIAG_SECTION_HEADER_SEPARATOR_CHAR '=' #define MIDIDIAG_ITEM_SEPARATOR_CHAR '-' -#define MIDIDIAG_SEPARATOR_REPEATING_CHAR_COUNT_PER_LINE 79 +#define MIDIDIAG_SEPARATOR_REPEATING_CHAR_COUNT_PER_LINE 120 -#define MIDIDIAG_FIELD_LABEL_ERROR L"ERROR" +#define MIDIDIAG_FIELD_LABEL_ERROR "ERROR" -#define MIDIDIAG_SECTION_LABEL_ENUM_REGISTRY L"enum_registry" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_VALUE L"transport_id" +#define MIDIDIAG_SECTION_LABEL_ENUM_REGISTRY "enum_registry" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_VALUE "transport_id" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT L"reg_root" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_DISCOVERY_ENABLED L"reg_discovery_enabled" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_USE_MMCSS L"reg_use_mmcss" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_CURRENT_CONFIG L"reg_current_config" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT "reg_root" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_DISCOVERY_ENABLED "reg_discovery_enabled" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_USE_MMCSS "reg_use_mmcss" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_ROOT_CURRENT_CONFIG "reg_current_config" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_SDK_INSTALLED L"reg_sdk_installed" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_SDK_INSTALLED "reg_sdk_installed" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_MIDISRV_EXENAME L"reg_midisrv_exe" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_MIDISRV_EXENAME "reg_midisrv_exe" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORTS L"reg_transports" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_NAME L"reg_transport_name" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_CLSID L"reg_transport_clsid" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_ENABLED L"reg_transport_enabled" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_DLLNAME L"reg_transport_dll" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_PROGID L"reg_transport_progid" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORTS "reg_transports" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_NAME "reg_transport_name" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_CLSID "reg_transport_clsid" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_ENABLED "reg_transport_enabled" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_DLLNAME "reg_transport_dll" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSPORT_PROGID "reg_transport_progid" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSFORMS L"reg_transforms" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_TRANSFORMS "reg_transforms" -#define MIDIDIAG_FIELD_LABEL_REGISTRY_DRIVERS32_ENTRY L"reg_drivers32_entry" +#define MIDIDIAG_SECTION_LABEL_ENUM_REGISTRY_DRIVERS32 "reg_drivers32" +#define MIDIDIAG_FIELD_LABEL_REGISTRY_DRIVERS32_ENTRY "reg_drivers32_entry" -#define MIDIDIAG_SECTION_LABEL_ENUM_COM L"enum_com" +#define MIDIDIAG_SECTION_LABEL_ENUM_COM "enum_com" -#define MIDIDIAG_SECTION_LABEL_ENUM_TRANSPORTS L"enum_transports" -#define MIDIDIAG_FIELD_LABEL_TRANSPORT_ID L"transport_id" -#define MIDIDIAG_FIELD_LABEL_TRANSPORT_NAME L"name" -#define MIDIDIAG_FIELD_LABEL_TRANSPORT_CODE L"transport_code" -#define MIDIDIAG_FIELD_LABEL_TRANSPORT_VERSION L"version" -#define MIDIDIAG_FIELD_LABEL_TRANSPORT_AUTHOR L"author" -#define MIDIDIAG_FIELD_LABEL_TRANSPORT_DESCRIPTION L"description" +#define MIDIDIAG_SECTION_LABEL_ENUM_TRANSPORTS "enum_transports" +#define MIDIDIAG_FIELD_LABEL_TRANSPORT_ID "transport_id" +#define MIDIDIAG_FIELD_LABEL_TRANSPORT_NAME "name" +#define MIDIDIAG_FIELD_LABEL_TRANSPORT_CODE "transport_code" +#define MIDIDIAG_FIELD_LABEL_TRANSPORT_VERSION "version" +#define MIDIDIAG_FIELD_LABEL_TRANSPORT_AUTHOR "author" +#define MIDIDIAG_FIELD_LABEL_TRANSPORT_DESCRIPTION "description" -#define MIDIDIAG_SECTION_LABEL_MIDI2_API_ENDPOINTS L"enum_ump_api_endpoints" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_ID L"endpoint_device_id" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_NAME L"name" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_TRANSPORT_CODE L"transport_code" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_USER_SUPPLIED_NAME L"name_user_supplied" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_ENDPOINT_SUPPLIED_NAME L"name_endpoint_supplied" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_TRANSPORT_SUPPLIED_NAME L"name_transport_supplied" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_TRANSPORT_SUPPLIED_DESC L"desc_transport_supplied" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_USER_SUPPLIED_DESC L"desc_user_supplied" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_PARENT_ID L"parent_id" -#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_PARENT_NAME L"parent_name" +#define MIDIDIAG_SECTION_LABEL_MIDI2_API_ENDPOINTS "enum_ump_api_endpoints" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_ID "endpoint_device_id" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_NAME "name" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_TRANSPORT_CODE "transport_code" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_USER_SUPPLIED_NAME "name_user_supplied" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_ENDPOINT_SUPPLIED_NAME "name_endpoint_supplied" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_TRANSPORT_SUPPLIED_NAME "name_transport_supplied" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_TRANSPORT_SUPPLIED_DESC "desc_transport_supplied" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_USER_SUPPLIED_DESC "desc_user_supplied" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_PARENT_ID "parent_id" +#define MIDIDIAG_FIELD_LABEL_MIDI2_ENDPOINT_PARENT_NAME "parent_name" -#define MIDIDIAG_SECTION_LABEL_MIDI1_API_INPUT_ENDPOINTS L"enum_winrt_midi1_api_input_ports" -#define MIDIDIAG_SECTION_LABEL_MIDI1_API_OUTPUT_ENDPOINTS L"enum_winrt_midi1_api_output_ports" -#define MIDIDIAG_FIELD_LABEL_MIDI1_ENDPOINT_ID L"endpoint_device_id" -#define MIDIDIAG_FIELD_LABEL_MIDI1_ENDPOINT_NAME L"name" +#define MIDIDIAG_SECTION_LABEL_MIDI1_API_INPUT_ENDPOINTS "enum_winrt_midi1_api_input_ports" +#define MIDIDIAG_SECTION_LABEL_MIDI1_API_OUTPUT_ENDPOINTS "enum_winrt_midi1_api_output_ports" +#define MIDIDIAG_FIELD_LABEL_MIDI1_ENDPOINT_ID "endpoint_device_id" +#define MIDIDIAG_FIELD_LABEL_MIDI1_ENDPOINT_NAME "name" -#define MIDIDIAG_SECTION_LABEL_WINMM_API_INPUT_ENDPOINTS L"enum_winmm_midi1_api_input_ports" -#define MIDIDIAG_SECTION_LABEL_WINMM_API_OUTPUT_ENDPOINTS L"enum_winmm_midi1_api_output_ports" -#define MIDIDIAG_FIELD_LABEL_WINMM_ENDPOINT_ID L"endpoint_index" -#define MIDIDIAG_FIELD_LABEL_WINMM_ENDPOINT_NAME L"name" -#define MIDIDIAG_FIELD_LABEL_WINMM_ENDPOINT_COUNT L"endpoint_count" -#define MIDIDIAG_FIELD_LABEL_WINMM_ERROR_COUNT L"dev_caps_error_count" +#define MIDIDIAG_SECTION_LABEL_WINMM_API_INPUT_ENDPOINTS "enum_winmm_midi1_api_input_ports" +#define MIDIDIAG_SECTION_LABEL_WINMM_API_OUTPUT_ENDPOINTS "enum_winmm_midi1_api_output_ports" +#define MIDIDIAG_FIELD_LABEL_WINMM_ENDPOINT_ID "endpoint_index" +#define MIDIDIAG_FIELD_LABEL_WINMM_ENDPOINT_NAME "name" +#define MIDIDIAG_FIELD_LABEL_WINMM_ENDPOINT_COUNT "endpoint_count" +#define MIDIDIAG_FIELD_LABEL_WINMM_ERROR_COUNT "dev_caps_error_count" -#define MIDIDIAG_SECTION_LABEL_PING_TEST L"ping_test" -#define MIDIDIAG_FIELD_LABEL_PING_ATTEMPT_COUNT L"ping_attempt_count" -#define MIDIDIAG_FIELD_LABEL_PING_RETURN_COUNT L"ping_return_count" -#define MIDIDIAG_FIELD_LABEL_PING_ROUND_TRIP_TOTAL_TICKS L"round_trip_total_ticks" -#define MIDIDIAG_FIELD_LABEL_PING_ROUND_TRIP_AVERAGE_TICKS L"round_trip_average_ticks" -#define MIDIDIAG_FIELD_LABEL_PING_FAILURE_REASON L"ping_failure_reason" +#define MIDIDIAG_SECTION_LABEL_PING_TEST "ping_test" +#define MIDIDIAG_FIELD_LABEL_PING_ATTEMPT_COUNT "ping_attempt_count" +#define MIDIDIAG_FIELD_LABEL_PING_RETURN_COUNT "ping_return_count" +#define MIDIDIAG_FIELD_LABEL_PING_ROUND_TRIP_TOTAL_TICKS "round_trip_total_ticks" +#define MIDIDIAG_FIELD_LABEL_PING_ROUND_TRIP_AVERAGE_TICKS "round_trip_average_ticks" +#define MIDIDIAG_FIELD_LABEL_PING_FAILURE_REASON "ping_failure_reason" -#define MIDIDIAG_SECTION_LABEL_MIDI_CLOCK L"midi_clock" -#define MIDIDIAG_FIELD_LABEL_CLOCK_FREQUENCY L"clock_frequency" -#define MIDIDIAG_FIELD_LABEL_CLOCK_NOW L"clock_now" +#define MIDIDIAG_SECTION_LABEL_MIDI_CLOCK "midi_clock" +#define MIDIDIAG_FIELD_LABEL_CLOCK_FREQUENCY "clock_frequency" +#define MIDIDIAG_FIELD_LABEL_CLOCK_NOW "clock_now" -#define MIDIDIAG_SECTION_LABEL_SERVICE_STATUS L"service_status" -#define MIDIDIAG_FIELD_LABEL_SERVICE_AVAILABLE L"available" +#define MIDIDIAG_SECTION_LABEL_SERVICE_STATUS "service_status" +#define MIDIDIAG_FIELD_LABEL_SERVICE_AVAILABLE "available" -#define MIDIDIAG_SECTION_LABEL_SDK_STATUS L"sdk_runtime_status" -#define MIDIDIAG_FIELD_LABEL_SDK_INITIALIZED L"sdk_runtime_initialized" +#define MIDIDIAG_SECTION_LABEL_SDK_STATUS "sdk_runtime_status" +#define MIDIDIAG_FIELD_LABEL_SDK_INITIALIZED "sdk_runtime_initialized" -#define MIDIDIAG_SECTION_LABEL_OS L"os" -#define MIDIDIAG_FIELD_LABEL_OS_VERSION L"os_version" +#define MIDIDIAG_SECTION_LABEL_OS "os" +#define MIDIDIAG_FIELD_LABEL_OS_VERSION "os_version" -#define MIDIDIAG_SECTION_LABEL_PROCESSOR_ENV L"processor_env" -#define MIDIDIAG_SECTION_LABEL_NATIVE_SYSTEM_INFO L"native_system_info" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_ARCH L"processor_architecture" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_LEVEL L"processor_level" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_REVISION L"processor_revision" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_EMULATION L"running_emulated" +#define MIDIDIAG_SECTION_LABEL_PROCESSOR_ENV "processor_env" +#define MIDIDIAG_SECTION_LABEL_NATIVE_SYSTEM_INFO "native_system_info" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_ARCH "processor_architecture" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_LEVEL "processor_level" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_REVISION "processor_revision" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_PROCESSOR_EMULATION "running_emulated" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMECAPS_ERROR L"timecaps_error" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMECAPS_MIN_PERIOD L"timecaps_min_period" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMECAPS_MAX_PERIOD L"timecaps_max_period" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMECAPS_ERROR "timecaps_error" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMECAPS_MIN_PERIOD "timecaps_min_period" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMECAPS_MAX_PERIOD "timecaps_max_period" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMER_RESOLUTION_MIN_MS L"timer_resolution_min" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMER_RESOLUTION_MAX_MS L"timer_resolution_max" -#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMER_RESOLUTION_CURRENT_MS L"timer_resolution_current" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMER_RESOLUTION_MIN_MS "timer_resolution_min" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMER_RESOLUTION_MAX_MS "timer_resolution_max" +#define MIDIDIAG_FIELD_LABEL_SYSTEM_INFO_TIMER_RESOLUTION_CURRENT_MS "timer_resolution_current" -#define MIDIDIAG_SECTION_LABEL_HEADER L"header" -#define MIDIDIAG_FIELD_LABEL_CURRENT_TIME L"current_time" +#define MIDIDIAG_SECTION_LABEL_HEADER "header" +#define MIDIDIAG_FIELD_LABEL_CURRENT_TIME "current_time" -#define MIDIDIAG_SECTION_LABEL_END_OF_FILE L"end_of_file" +#define MIDIDIAG_SECTION_LABEL_END_OF_FILE "end_of_file" diff --git a/src/app-sdk/mididiag/packages.config b/src/app-sdk/mididiag/packages.config index c93db8ddc..2e6835610 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 a575d4c77..6914369bd 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true diff --git a/src/app-sdk/midimdnsinfo/packages.config b/src/app-sdk/midimdnsinfo/packages.config index c93db8ddc..2e6835610 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/midiusbinfo.vcxproj b/src/app-sdk/midiusbinfo/midiusbinfo.vcxproj index 37a51809a..52133a8da 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.2-preview-9.250106-1428 + Microsoft.Windows.Devices.Midi2.1.0.2-preview-9.250112-1659 true true true diff --git a/src/app-sdk/midiusbinfo/packages.config b/src/app-sdk/midiusbinfo/packages.config index c93db8ddc..2e6835610 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/sdk-runtime-installer/settings-package/_SetupFiles.wxs b/src/app-sdk/sdk-runtime-installer/settings-package/_SetupFiles.wxs index 45a48b0d6..2b3f34ab8 100644 --- a/src/app-sdk/sdk-runtime-installer/settings-package/_SetupFiles.wxs +++ b/src/app-sdk/sdk-runtime-installer/settings-package/_SetupFiles.wxs @@ -43,8 +43,6 @@ - - diff --git a/src/user-tools/midi-console/Midi/Commands/Service/ServiceRestartCommand.cs b/src/user-tools/midi-console/Midi/Commands/Service/ServiceRestartCommand.cs index 4639e9994..f5c009e7a 100644 --- a/src/user-tools/midi-console/Midi/Commands/Service/ServiceRestartCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Service/ServiceRestartCommand.cs @@ -51,7 +51,7 @@ public override int Execute(CommandContext context, Settings settings) { if (controller.Status == ServiceControllerStatus.Running) { - AnsiConsole.Write("Stopping service.."); + AnsiConsole.Write("Attempting to stop service.."); MidiServiceHelper.StopServiceWithConsoleStatusUpdate(controller); } @@ -64,14 +64,14 @@ public override int Execute(CommandContext context, Settings settings) if (controller.Status == ServiceControllerStatus.Stopped) { - AnsiConsole.Write("Restarting service.."); + AnsiConsole.Write("Attempting to restart service.."); MidiServiceHelper.StartServiceWithConsoleStatusUpdate(controller); } if (controller.Status != ServiceControllerStatus.Running) { - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to restart service. Service is not currently running. It may be locked in a tight loop due to an error.")); + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to restart service. Service is not currently running.")); return (int)MidiConsoleReturnCode.ErrorGeneralFailure; } @@ -81,7 +81,7 @@ public override int Execute(CommandContext context, Settings settings) } catch (Exception) { - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to restart service.")); + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to restart service. It may be locked in a tight loop due to an error. Consider killing the midisrv.exe process, or rebooting.")); return (int)MidiConsoleReturnCode.ErrorGeneralFailure; } diff --git a/src/user-tools/midi-console/Midi/Commands/Service/ServiceStopCommand.cs b/src/user-tools/midi-console/Midi/Commands/Service/ServiceStopCommand.cs index b08644278..df18ce191 100644 --- a/src/user-tools/midi-console/Midi/Commands/Service/ServiceStopCommand.cs +++ b/src/user-tools/midi-console/Midi/Commands/Service/ServiceStopCommand.cs @@ -51,7 +51,7 @@ public override int Execute(CommandContext context, Settings settings) { if (controller.Status == ServiceControllerStatus.Running) { - AnsiConsole.Write("Stopping service.."); + AnsiConsole.Write("Attempting to stop service.."); MidiServiceHelper.StopServiceWithConsoleStatusUpdate(controller); AnsiConsole.WriteLine(); @@ -62,7 +62,7 @@ public override int Execute(CommandContext context, Settings settings) } else { - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to stop service.")); + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to stop service. It may be in-use by other apps, or it may be locked up. Consider killing the midisrv.exe process, or rebooting.")); return (int)MidiConsoleReturnCode.ErrorGeneralFailure; } } @@ -74,7 +74,7 @@ public override int Execute(CommandContext context, Settings settings) } catch (Exception) { - AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to stop service.")); + AnsiConsole.MarkupLine(AnsiMarkupFormatter.FormatError($"Unable to stop service. It may be in-use by other apps, or it may be locked up. Consider killing the midisrv.exe process, or rebooting.")); return (int)MidiConsoleReturnCode.ErrorGeneralFailure; } } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj index 5d76eecad..d893a4d42 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Microsoft.Midi.Settings.csproj @@ -100,8 +100,8 @@ - - + + diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Models/AppState.cs b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Models/AppState.cs index d1c7cd10d..ecd4523a6 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Models/AppState.cs +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Models/AppState.cs @@ -20,7 +20,28 @@ public class AppState private AppState() { - MidiServicesInitializer.EnsureServiceAvailable(); + var initializer = Microsoft.Windows.Devices.Midi2.Initialization.MidiDesktopAppSdkInitializer.Create(); + + if (initializer == null) + { + // TODO: Failed + return; + + } + + // initialize SDK runtime + if (!initializer.InitializeSdkRuntime()) + { + // TODO: Failed + return; + } + + // start the service + if (!initializer.EnsureServiceAvailable()) + { + // TODO: Failed + return; + } StartDeviceWatcher(true); } diff --git a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml index 9fd97506b..f5c0ac49c 100644 --- a/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml +++ b/src/user-tools/midi-settings/Microsoft.Midi.Settings/Views/DeviceDetailPage.xaml @@ -261,7 +261,7 @@ Text="First Group" Style="{StaticResource SmallPropertyLabel}" />